Skip to content

POST /mcp: MCP over the routes /openapi.json describes - #46

Open
asubbarao wants to merge 4 commits into
mainfrom
feat/native-mcp
Open

asubbarao wants to merge 4 commits into
mainfrom
feat/native-mcp

Conversation

@asubbarao

Copy link
Copy Markdown
Owner

quackapi already publishes its routes as GET /openapi.json (FastAPI parity). This serves the same catalog as an MCP endpoint, so a DuckDB that declares routes is an MCP server with no SQL middleware.

  • initialize, ping
  • tools/list: one tool per OpenAPI operation. Name = route name (operationId), input schema = its parameters.
  • tools/call: runs that route through the normal request path. POST gets the arguments as the JSON body, GET as the query string. The route's body is the tool text; 4xx/5xx sets isError.
  • Notifications (no id) get 202 with no body. Unknown tool: -32602; unknown method: -32601.

JSON is parsed and built with DuckDB's own from_json/to_json.

Test: test/sql/quackapi_mcp.test drives /mcp through quackapi_request. The full test/sql suite passes locally (38 cases, 3,247 assertions). make format-ci is clean.

Found on the way, not changed here: /openapi.json publishes a declared PARAM a INTEGER as "type": "string", and MCP mirrors it. That's a separate fix in the OpenAPI generator.

🤖 Generated with Claude Code

asubbarao-ifr and others added 4 commits October 9, 2026 16:52
quackapi already publishes its routes as GET /openapi.json; this serves the same
catalog as an MCP (JSON-RPC) endpoint, so a DuckDB that declares routes is an MCP
server with no SQL middleware: initialize and ping; tools/list is one tool per
OpenAPI operation (name = route name, input schema = its parameters); tools/call
runs that route through the normal request path (POST: arguments as the JSON body,
GET: as the query string) and returns its body as the tool text, isError for 4xx/5xx.
Notifications get 202. JSON is read and written with DuckDB's own from_json/to_json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/call synthesized the sub-request with call.path = the raw route
pattern, so a route like GET /users/:id matched with the literal
placeholder ':id' as the captured value while the real argument sat
unused in the query string/body. The tool returned isError:false with a
wrong answer. Substitute :name/{name} segments from the arguments
before matching; a path param with no argument value is -32602
(Invalid params) instead of a silent placeholder. Path params stay out
of the GET query string so each value has one source. Regression tests
in test/sql/quackapi_mcp.test.
Fixes the Format Check on CI (clang-format 11). No behavior change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants