Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
node_modules/
/.pnp
.pnp.*
.yarn/*
Expand Down
25 changes: 18 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ This is a Next.js 15 fullstack AI agent chat application using LangGraph.js with
- **Agent Factory**: `src/lib/agent/index.ts` - Builds the agent with LangChain v1's `createAgent`
(from the top-level `langchain` package), a prebuilt ReAct agent over LangGraph. `ensureAgent()`
first initializes the Postgres checkpointer, then assembles the model, tools, and middleware.
- **MCP Integration**: `src/lib/agent/mcp.ts` - Dynamically loads tools from MCP servers stored in Postgres
- **MCP Integration**: MCP servers are stored in Postgres; the agent sees their tools through the
disclosure middleware and connects on demand (see "Progressive disclosure" below).
`src/lib/agent/mcp.ts` now holds only the Connections page's tool-listing probe.
- **Persistent Memory**: Uses LangGraph's Postgres checkpointer for conversation history
- **Built-in finance tools** (always registered server-side in `index.ts`, provider-agnostic Zod):
- `src/lib/agent/tools/finance.ts` — `log_expense` (mutating), `query_transactions` (read-only,
Expand Down Expand Up @@ -159,8 +161,8 @@ This is a Next.js 15 fullstack AI agent chat application using LangGraph.js with
- **Tool Approval**: Human-in-the-loop via `humanInTheLoopMiddleware`. Approval is gated **per-tool**
through an `interruptOn` map that lists only **mutating** tools (`log_expense`,
`import_transactions_csv`, `create_category`, `set_config`, `update_transaction`,
`delete_transaction`); read tools (incl. `run_sql`) and MCP tools
auto-approve. **The gate cannot be switched off from the product**: no UI control, no query
`delete_transaction`); read tools (incl. `run_sql`), MCP tools and `load_tools` (which only changes
what the model sees) auto-approve. **The gate cannot be switched off from the product**: no UI control, no query
param, and nothing on `MessageOptions` can disable it — `bypassApprovalForEval` omits the
middleware for the eval harness alone, which calls the agent factory in-process and has no human
to answer an interrupt (`approvalGate.test.ts` pins that boundary). Decisions: `allow`→approve,
Expand Down Expand Up @@ -264,7 +266,7 @@ Instructions loaded on demand, in the standard `SKILL.md` format — full detail
`drizzle.config.ts`.
- **Repository seam**: application code NEVER imports Drizzle directly. All DB access goes through
the `src/lib/repositories/` files (`threadRepository`, `mcpServerRepository`, `transactionRepository`,
`categoryRepository`, `analyticsRepository`, `configRepository`), which return **plain domain objects**
`categoryRepository`, `analyticsRepository`, `configRepository`, `mcpServerCatalogRepository`), which return **plain domain objects**
(`ThreadRecord`, `MCPServer` in `src/types/mcp.ts`; `Transaction`, `Category` in
`src/types/finance.ts`; `ConfigEntry` in `src/types/config.ts`), not Drizzle row types. This keeps the persistence layer swappable — replacing the ORM touches only these files.
Add new DB access as repository functions, not inline queries.
Expand Down Expand Up @@ -353,10 +355,16 @@ Instructions loaded on demand, in the standard `SKILL.md` format — full detail
goes stale while looking current. Failures are typed (`auth_required`, `invalid_config`, `timeout`,
`connect_failed`), never an empty list.
- `toServerConfig()` (`src/lib/mcp/serverConfig.ts`) turns a row into a client config for BOTH the
agent (`getMCPTools`) and the probe, so the page connects exactly the way the agent will.
agent (`src/lib/mcp/connector.ts`) and the probe, so the page connects exactly the way the
agent will.
- Tool names are prefixed `server__tool`; server names are restricted to `[A-Za-z0-9_-]` in the form
because the prefix becomes part of a provider tool name.
- HTTP servers may require OAuth authentication - see [docs/OAUTH.md](docs/OAUTH.md)
- **Progressive disclosure** ([docs/MCP_DISCLOSURE.md](docs/MCP_DISCLOSURE.md), read it before
changing this code): the model sees a short list of capabilities plus `load_tools`, and a tool's
definition only once a search has loaded it; nothing connects until a tool is called. The logic
is the workspace package `packages/mcp-disclosure`, which must not import app code or the MCP
client library; `src/lib/mcp/connector.ts` is the only agent-path file using the adapters.

### Tool Approval Workflow

Expand Down Expand Up @@ -392,7 +400,8 @@ Instructions loaded on demand, in the standard `SKILL.md` format — full detail
### Agent Configuration

- `ensureAgent()` ensures Postgres checkpointer is initialized before agent creation
- MCP servers queried from database on each agent creation for dynamic tool loading
- MCP servers and their cached catalog are read on each agent creation; a server is connected
only when one of its tools is called (pooled across requests)
- **The owner picks the provider and model in the app; nothing is inferred.** Supported:
`google`, `openai`, `anthropic`, `openai-compatible` (any OpenAI chat-completions endpoint —
Ollama, vLLM, Groq, OpenRouter, DeepSeek). There is no default pair and no env var for the
Expand Down Expand Up @@ -589,7 +598,9 @@ Phoenix by default. Full detail in [docs/OBSERVABILITY.md](docs/OBSERVABILITY.md
default destination in code; the production compose stack points at its own `phoenix-prod`.
- **Content capture is a separate opt-in** (`OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true`).
Off, no prompt, message, tool argument, tool result or error message leaves the process — only
names, counts, timings and token usage.
names, counts, timings and token usage. On, each `chat` span also gets
`gen_ai.tool.definitions` (the tools offered on that call — they travel beside the prompt, so
the prompt alone never shows them), normalized per provider by `toSemconvToolDefinitions`.
- **`GenAiTracer`** (`src/lib/tracing/genAiTracer.ts`) is a LangChain callback attached per
request in `agentService.ts`. It spans the run (`invoke_agent`), each model call (`chat`) and
each tool call (`execute_tool`) — never graph nodes or middleware — and records the approval
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ RUN corepack enable

# Only the manifests, so this layer busts on a dependency change rather than on every edit.
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/mcp-disclosure/package.json ./packages/mcp-disclosure/
RUN pnpm install --frozen-lockfile

# ── build ────────────────────────────────────────────────────────────────────────────────────────
Expand Down
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,16 @@ itself.

The technical detail lives in [`docs/`](docs/) rather than here:

| Doc | What's in it |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [ARCHITECTURE.md](docs/ARCHITECTURE.md) | System overview, agent workflow, data flow, database schema, MCP integration, approval process, streaming, **project structure**, **available scripts** |
| [SKILLS.md](docs/SKILLS.md) | How skills load, the validation rules, and how they're packaged for production |
| [TESTING.md](docs/TESTING.md) | Why the unit suite stays free and offline, and what belongs in evals instead |
| [API.md](docs/API.md) | The generated OpenAPI spec, served at `/api/openapi` and browsable at `/api-docs` |
| [OAUTH.md](docs/OAUTH.md) | OAuth for MCP servers that need it |
| [OBSERVABILITY.md](docs/OBSERVABILITY.md) | OpenTelemetry tracing to a self-hosted backend (Phoenix by default), what a trace contains, and swapping backends |
| [eval/README.md](eval/README.md) | The paid, non-deterministic eval suite — never in CI |
| Doc | What's in it |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [ARCHITECTURE.md](docs/ARCHITECTURE.md) | System overview, agent workflow, data flow, database schema, MCP integration, approval process, streaming, **project structure**, **available scripts** |
| [SKILLS.md](docs/SKILLS.md) | How skills load, the validation rules, and how they're packaged for production |
| [TESTING.md](docs/TESTING.md) | Why the unit suite stays free and offline, and what belongs in evals instead |
| [API.md](docs/API.md) | The generated OpenAPI spec, served at `/api/openapi` and browsable at `/api-docs` |
| [OAUTH.md](docs/OAUTH.md) | OAuth for MCP servers that need it |
| [MCP_DISCLOSURE.md](docs/MCP_DISCLOSURE.md) | Progressive disclosure: a server catalog instead of every MCP tool on every turn (POC) |
| [OBSERVABILITY.md](docs/OBSERVABILITY.md) | OpenTelemetry tracing to a self-hosted backend (Phoenix by default), what a trace contains, and swapping backends |
| [eval/README.md](eval/README.md) | The paid, non-deterministic eval suite — never in CI |

[AGENTS.md](AGENTS.md) carries the conventions a coding agent needs to work in this repo.

Expand Down
2 changes: 1 addition & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ async function buildAgent(cfg?: AgentConfigOptions) {
systemPrompt: cfg?.systemPrompt || SYSTEM_PROMPT,
checkpointer: postgresCheckpointer,
middleware,
}).withConfig({ recursionLimit: 25 });
}).withConfig({ recursionLimit: 50 });
}
```

Expand Down
Loading
Loading