Repository navigation
Description
Problem
altinity-mcp with --openapi already looks more like a ClickHouse frontend than a pure MCP server. To make it a drop-in plug-in replacement in front of ClickHouse, it should also speak the native ClickHouse HTTP API documented at https://clickhouse.com/docs/interfaces/http, so existing tools, dashboards, and clients can point at altinity-mcp without changes:
curl 'http://mcp.example.com/?query=SELECT+1'
curl -u alice:pass 'http://mcp.example.com/?query=SELECT+1&database=analytics&default_format=JSONCompact'
curl -X POST 'http://mcp.example.com/' --data-binary 'SELECT 1'Today any of these hit the MCP streamable-HTTP endpoint and fail.
Feasibility — hook points already exist
The HTTP router is centralized:
cmd/altinity-mcp/main.go— mux setup aroundmcpRoutePatterns(MCP transport) andopenAPIRoutePatterns(lines 407-433); a newchHTTPRoutePatternscan be added to the same mux or, better, registered as a catch-all that sniffs?query=or a POST body before handing off to MCP.pkg/server/server.go:2519— the OpenAPI dispatcher already routes by path suffix in aswitch. Adding acasethat matches?query=is a one-line addition.handleExecuteQueryOpenAPI(pkg/server/server.go) — the core 'parse request → run query → serialize' pipeline already exists. The CH-compat handler can reuse it; the work is input parsing (CH params/headers/body) and output formatting (TSV/JSON-variants), not query execution.GetClickHouseClientFromCtx/GetClickHouseClientWithOAuth— already resolves the right backend connection based on JWE/OAuth/cluster-secret context. The CH-compat handler should funnel through the same path so all three auth modes work identically to MCP/OpenAPI.
Proposed dispatch
In the HTTP mux, before the MCP handler, route to a new handleClickHouseHTTP when any of these is true on the request:
- Method is
GETand?query=is non-empty - Method is
POSTandContent-Typeistext/plain; charset=UTF-8(CH's native format) or the body starts with SQL keywords - Request has
X-ClickHouse-User/X-ClickHouse-Key/X-ClickHouse-Databaseheaders (unambiguous CH-client signal)
Otherwise fall through to existing MCP/OpenAPI handlers. This keeps MCP and OpenAPI clients unaffected.
Scope — phase 1 (minimum viable plug-in)
Request parsing
-
?query=(URL-encoded SQL) -
?database=→ overrides default database -
?default_format=→ picks response format - POST body as query (when URL has no
?query=) - Mixed mode:
?query=SELECT+1+FROM++ POST body = concatenation (matches CH semantics) - HTTP Basic Auth (username:password) → maps to ClickHouse user via cluster-secret / static / JWE / OAuth mode (whichever altinity-mcp is configured for)
-
X-ClickHouse-User/X-ClickHouse-Key/X-ClickHouse-Databaseheaders (higher priority than basic auth, per CH docs)
Response formats (pick the most useful subset first)
-
TabSeparated(CH default — required) -
TabSeparatedWithNames -
JSONCompact -
JSONEachRow -
FORMATclause detection in the SQL (CH-compat: trailingFORMAT JSONCompactwins overdefault_format)
Errors
- Query errors → HTTP 500 with CH-style plain-text body (
Code: 62. DB::Exception: ... (SYNTAX_ERROR)) - Auth failures → HTTP 401 with
WWW-Authenticate: Basicheader - Unknown user / permission denied → HTTP 403
Preserves
- Read-only mode flag still enforced
-
clickhouse-limitstill applied (or: bypass since a real CH client expects unbounded results?) - Cluster-secret impersonation still uses
oauthClaims.Emailif the request arrives authenticated via OAuth
Scope — phase 2 (nice-to-have, file separate issues as needed)
- Streaming responses for large result sets (currently we buffer everything in memory in
executeSelect) -
INSERT ... FORMATwith body data (POST to upload TSV/CSV) - Additional formats:
RowBinary,Native,Values,Pretty*,CSV,CSVWithNames -
?param_<name>=query parameters (CH's{name:Type}substitution) - Compression (
Accept-Encoding: gzip,lz4) -
X-ClickHouse-Progress,X-ClickHouse-Summaryresponse headers -
readonly,max_result_rows,max_execution_timeand other settings as query params
Out of scope
- Binary native-protocol HTTP upgrades
- Replica / cluster discovery endpoints (
/replicas_status,/pingetc. — these are trivial to add if asked, but not part of 'query execution' plug-in) - TLS termination differences — inherit from existing server TLS
Why this is cleaner than it sounds
The CH HTTP API is substantial but altinity-mcp already has:
- Query execution with auth context —
handleExecuteQueryOpenAPIpath - Per-request ClickHouse client resolution with JWE/OAuth/cluster-secret context
QueryResult { Columns, Types, Rows, Count }internal type — straightforward to serialize to TSV/JSON variants
Phase 1 is mostly a format writer + a request parser + one route case. The heavy lifting (auth, execution, OpenAPI parity) stays untouched.
Acceptance criteria
curl 'http://localhost:8080/?query=SELECT+1'returns1\nwithContent-Type: text/tab-separated-valuescurl -u user:pass 'http://localhost:8080/?query=SELECT+currentUser()'runs asuser(cluster-secret / JWE / OAuth mode, whichever is configured)curl 'http://localhost:8080/?query=SELECT+1&default_format=JSONCompact'returns valid JSONCompact- Invalid SQL returns HTTP 500 with
Code:prefix body - Existing MCP clients and
/openapiendpoints continue to work unchanged - Integration test: point a
clickhouse-client --urlat altinity-mcp and execute a simple SELECT
Activity