feat(mcp): `altimate-code mcp serve` exposes data tools to MCP clients without an LLM
Issue for this PR
Closes # (feature; no linked issue)
Type of change
- Bug fix
- New feature
- Refactor / code improvement
- Documentation
What does this PR do?
There are two ways to get altimate-code's data tools into another agent today, and both are poor fits:
- The Claude Code plugin hands the whole task to
altimate-code run. That pays for a second model and replaces the client's own reasoning loop. - The tools themselves can only be used inside an altimate-code session. altimate-code is an MCP client only.
This PR adds altimate-code mcp serve, a stdio MCP server. Any MCP client, such as Claude Code, can call the data tools directly while its own model does the reasoning. The server never calls a model, so an MCP client's subscription covers the whole LLM cost.
What it exposes: five data tools, reusing the existing implementations, each with a short description:
warehouse_listschema_inspectsql_executesql_checksql_materialize
Added since the first revision:
answer_commit(MCP only). The client's model commits its final answer through this tool. It runs the same requested-item and number-provenance checks as the native analyst, and returns aFINAL ANSWER:line for the model to reply with. It returns plain text only: Claude Code showsstructuredContentto the model in place of the text, and the model then rewrote the answer as prose.- Server
instructions: the analyst method, 723 characters, sent in the initialize response. analyzeprompt: interactive clients show it as a slash command.- Reading question (elicitation): when a commit lists two readings and the client supports elicitation, the server asks once which reading the user meant. If the user picks the other reading, the commit is rejected and the model recomputes under it. The question times out after 20 s and follows the tool call's abort signal, so a client that never answers cannot stall the commit. Decline, cancel and timeout all keep the commit accepted.
resource_link: whensql_executespills a large result to a file, the result also carries a link to that file.
Behaviour:
- Read-only by default. Writes request the
sql_execute_writepermission, which the server denies unless it was started with--allow-write. Reads are auto-allowed.sql_materializestill requires a read-only source query and writes only to the server session's temporaryscratchwarehouse, which is removed on exit. - Clean results. A local session is bound at start-up, so results no longer carry the "Not routed through the bound workspace" notice.
- Cancellation. An MCP request's abort is forwarded into the tool call.
- Client-neutral hints. Spill-file hints say "open this path with any file reader" instead of naming the agent-only
readtool. This applies only on the MCP path, through a context flag, so normal agent output is unchanged. - Connections.
--connections <file>points at a connections file; without it, the server uses normal connection discovery. - Clean protocol. stdout carries only MCP protocol messages; logs go to stderr. The CLI's start-up middleware (banner, telemetry, agent init) is skipped for
mcp serveonly. Cold start is about 1.5 s.
Shared files touched:
| File | Change |
|---|---|
cli/cmd/mcp.ts | new serve subcommand (marked) |
index.ts | middleware skip for mcp serve only (marked) |
workspace/precedence.ts | bindLocalSession / releaseLocalSession helpers |
sql-execute.ts, sql-result-format.ts | the mcpClient hint flag |
Normal agent behaviour is unchanged.
Docs: a new "Use Altimate Code tools from another MCP client" section in docs/docs/configure/mcp-servers.md.
How did you verify your code works?
- Unit and integration:
bun testontest/altimate/mcp-serverplus every suite that references the shared files touched: 2,696 pass, 0 fail. The MCP tests run a real in-process MCP client and a stdio client against a temporary database. They:- list the 5 tools;
- run
warehouse_list,schema_inspectandsql_execute; - check a mutating
sql_executeis refused by default and allowed with--allow-write; - check the routing notice is absent;
- check cancellation works.
- Typecheck and markers:
bun run typecheckis clean, and the marker checkscript/upstream/analyze.ts --markers --base main --strictpasses. - End to end with the real Claude Code CLI (2.1.302, Sonnet):
- Setup:
claude -pwith--mcp-configpointing ataltimate-code mcp servefrom this branch,--strict-mcp-config, and Bash disallowed. The database was a 60-row SQLiteorderstable. - The server reported
connected. - Claude called
warehouse_list,schema_inspectandsql_execute, and answered correctly: 30 paid orders totalling 12,555, checked by hand. - A requested
DELETEwas refused withPermission denied: sql_execute_write. Restart with --allow-write to enable writes. - Cost: $0.09.
- Setup:
- Not run: Postgres, Snowflake and other remote warehouses through the MCP path. They reuse the same tool implementations and connection registry.
Screenshots / recordings
Not a UI change.
Checklist
- I have tested my changes locally
- I have not included unrelated changes in this PR
[!NOTE]
Medium Risk
New SQL execution surface over MCP with read-only defaults and permission gating, but it reuses core query paths and misconfiguration or --allow-write could allow mutations against configured warehouses.
Overview
Adds altimate-code mcp serve, a stdio MCP server so external MCP clients can call Altimate’s five warehouse tools (warehouse_list, schema_inspect, sql_execute, sql_check, sql_materialize) without starting an agent or calling a model. A thin adapter wires the existing tool implementations with a local session, MCP-specific permissions, and client-neutral truncation/spill hints.
Safety and behavior: Source SQL writes are denied unless --allow-write; otherwise reads propagate forceReadOnly through dispatch so Postgres/SQLite/DuckDB get database-level protection, with stderr/warehouse_list caveats for classification-only engines. Optional --connections loads an isolated connections file via Registry.loadFile. Stdio keeps protocol on stdout; CLI middleware (telemetry/heap) is skipped for mcp serve. Scratch materialization and private spill directories are torn down on shutdown; MCP abort signals cancel in-flight queries.
Supporting changes: Truncate.makeLayer for per-server output dirs; docs for client setup; CI runs MCP server tests in a separate process with real drivers (ALTIMATE_MCP_E2E).
Reviewed by Cursor Bugbot for commit d88dd208359146583f5413730e404b8126ba9e9e. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Adds altimate-code mcp serve, a stdio MCP server that exposes altimate-code's data tools to MCP clients without calling a model, so the client's own model does the reasoning and its subscription covers LLM cost.
- Exposes exactly
warehouse_list,schema_inspect,sql_execute,sql_check,sql_materialize, andanswer_commit, reusing existing implementations.schema_inspectwithout a table lists schemas with table counts (capped at 200) via one bounded catalog aggregate, then up to 500 tables per schema through pushed-down catalog queries; MongoDB uses scalardbStatscounts and a boundedlistCollectionscursor. Row counts are local SQLite/DuckDB only (at most 50 tables under a 3-second budget); remote engines show catalog estimates, neverCOUNT(*). warehouse_listadds local tables with columns and a one-row sample (SQLite and local DuckDB base tables only), bounded by table budgets and a 4-second deadline; remote connections run no queries.sql_executeaccepts up to 8 read-only queries per call as one labeled block each with a shared byte/line budget andfull_text_columnsforwarded per query; a failing item does not stop the rest. A result too large to show inline becomes a scratch tableresult_N(typed likesql_materialize, sequential names) with a 10-row preview and aSELECT ... FROM result_Nhint; the full rows spill to a CSV file with a single header row (NULL empty,""kept), described as a CSV copy since CSV text cannot preserve binary, JSON, orbase64:values byte-for-byte.answer_commitrequires every requested item to map to its own field (at most two rejections per turn) and, once per commit, rejects afieldsresult with fewer fields than the request asks for; an unchanged recommit is accepted. MCP commits skip the number-provenance warning, which the server cannot verify for numbers computed in the client; native sessions keep it. Calls withoutaskedbehave as before. The user's question is passed verbatim as an MCP-onlyrequestargument, stripped before parsing; the compact description tells clients not to collapse a compound request into one field ('the repository and the most copied file' needs two). Complete commits (arowscommit, or afieldscommit covering every requested item) return the basis as context above an answer-onlyFINAL ANSWER: <text>line without structured content; under-asked commits keep the earlier rendering. When a commit records two possible readings it asks once per session which reading was meant, with a 20-second timeout (elicitTimeoutMs) that follows the tool call's signal so a silent client cannot stall the commit; declining, an unsupporting client, or a timeout leaves the commit accepted.- Read-only by default; writes need
--allow-write. Without it, source queries request database-enforced read-only on Postgres, SQLite, and DuckDB; other engines are classification-only, so the server warns at startup and inwarehouse_listto use a read-only database user.sql_materializereads the source read-only and writes only to a session-local scratch warehouse removed on exit. - Spill output goes to a private per-process directory deleted on close; the original file stays as a copy and fallback, with client-neutral hints (no agent-only
readorlineage_checktext) and aresource_linkbeside the result. Tool output drops rule separator lines,sql_materializereceipts omit the echoed SQL, and usage hints are shown once per session. - Forwards MCP cancellation to tool calls, binds a local session so results lack the routing notice, and sends analyst-method instructions in the initialize response, including an
analyzeprompt for interactive clients. Advertised tool schemas are compacted for the client (filler unions collapsed, parser-enforced keys dropped) while keepingsql_check.joinallowed values, thequery_fromrule, and theanswer_commitcompound-request guidance, without changing parsing. Writes only MCP frames to stdout with logs on stderr;--print-logs/--log-levelapply and telemetry startup is skipped. --connections <file>is the sole source of connections and rejects the reservedscratchname; otherwise normal discovery applies.- The stdio server is driven over raw JSON-RPC in tests so MCP SDK client mocks cannot leak in.
- The analyst requests
highreasoning effort on adaptive-thinking models when neither the request nor agent config chooses one;ALTIMATE_ANALYST_EFFORToverrides it or turns it off.
Summary by CodeRabbit
- New Features
- Added a standalone MCP server that gives MCP clients access to five Altimate tools for warehouse discovery, schema inspection, SQL execution, SQL checks, and materialization.
- Supports an optional connections file. SQL writes are disabled by default; enabling them remains subject to safety checks. Materialization uses a read-only source query and a temporary scratch destination.
- Large results can be opened from a local file path, with temporary output cleaned up when the client closes.
- Documentation
- Added setup instructions, connection-source details, tool descriptions, write-permission guidance, output limits, and notes on read-only enforcement.
Update: answer_commit over MCP (stacked on #native commit 15d2766115)
This branch now merges 15d2766115 (the native feat/analyst-commit-slots-effort commit, no PR of its own) so the server reuses answer-commit-checks.ts instead of a copy. Until that commit lands on main, this PR's diff includes it; the MCP-specific change is the last two commits.
- New sixth tool
answer_commit: asked-item coverage (at most two rejections per turn), one-time number-provenance warning, canonicalFINAL ANSWER: <text>result. The adapter keeps session history (tool outputs and rejections) in the shape the shared checks read, and starts a new turn after an accepted commit. - The 400-character
basislimit is not enforced over MCP: longer text is trimmed to 400 characters instead of rejected. The native tool is unchanged. - Server instructions step 7 now points at
answer_commit. - Help snapshot updated for
mcp serve.
Tests: ALTIMATE_MCP_E2E=1 bun test test/altimate/mcp-server 33 pass, 0 fail ("Ran 33 tests across 2 files"); typecheck clean; marker check ok. Full package run: 20221 tests, 19335 pass, 18 fail, 790 skip, 78 todo; after the snapshot fix, the remaining failures (mcp.headers, HttpApi status/experimental, --time-budget run tests, and a few environment-dependent suites) fail identically on the native branch without these commits. Codex read-only review of the MCP diff: no P0/P1.
🤖 Generated with Claude Code