Repository navigation
feat(mcp): MCP gateway, MVP: registry, /mcp proxy, access per key, accounting - #136
Merged
Merged
Conversation
…counting
MCP clients such as Claude Code reach the MCP servers registered in Keystone
at /mcp (every server the key may use, tools named server__tool) and
/mcp/{server}, with a KTMS key of the new mcp scope. The gateway is
stateless Streamable HTTP on HTTP_ADDR; upstreams speak Streamable HTTP or
SSE through one shared session per server, rebuilt when the spec or
credential changes.
- mcp_server and mcp_credential tables; UpsertMCPServer, DeleteMCPServer,
SetMCPCredential, DeleteMCPCredential and ListMCPTools RPCs; GetConfig
lists servers and credential fingerprints, never values.
- Credentials are encrypted with the vendor keys' keyring, re-encrypted by
RotateEncryption, added by the gateway and never returned, logged or
passed to clients.
- allowed_mcp ("server" or "server/tool") on key policies and teams; other
tools are hidden and refused (-32003) without calling the upstream.
- Private, loopback, link-local and metadata addresses are refused at
registration and at every connection unless allow_private is set.
- tool_rpm per key and tool (Redis), timeout_ms, max_response_bytes.
- Each call is a usage event (vendor mcp, model server__tool, ToolCall) and
counted in ktms_mcp_tool_calls_total, _duration_seconds, _response_bytes.
- ktms mcp-servers, the mcp_servers and mcp_credentials file sections,
--allowed-mcp, MCP_GATEWAY_ENABLED and the chart's mcpGatewayEnabled.
docs#183
|
The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).
|
This was referenced Oct 27, 2026
- Upstream redirects are refused: the credential is set by the transport, so a followed redirect carried it to the target host. - A tools/call is no longer retried on a closed connection, which does not say whether it ran; only an unknown session is. A replaced or removed upstream is retired and never reconnects with the old credential. - A reply cut at max_response_bytes is a too_large tool error: the cut is marked on the call's context, as the SDK drops the read error. - Names the server does not list are unknown tools, refused before access, limits and metrics, so labels and rate keys stay bounded. - A server with no tools lists none instead of being unavailable, and the empty list is cached. - Tool call events cut server and tool to light-tokenmeter's limits. - A rotation that cannot read an MCP credential says so and names SetMCPCredential.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MCP clients (Claude Code, Cursor, agents) reach MCP servers registered in Keystone through
/mcpwith a KTMS key. Task: ai-process/docs#183.mcp_server/mcp_credentialtables;UpsertMCPServer,DeleteMCPServer,SetMCPCredential,DeleteMCPCredential,ListMCPTools;GetConfiglists servers and credential fingerprints.ktms mcp-servers,mcp_servers/mcp_credentialsfile sections./mcp(all allowed servers, toolsserver__tool) and/mcp/{server}, stateless Streamable HTTP onHTTP_ADDR; upstreams Streamable HTTP or SSE, one shared session per server, rebuilt when spec or credential changes.MCP_GATEWAY_ENABLED(chartmcpGatewayEnabled).mcpscope;allowed_mcp(serverorserver/tool) on key policy and team, intersected; others hidden, calls refused with-32003without reaching the upstream.RotateEncryption, injected by the gateway, never returned, logged or in usage events.allow_private; proxy env ignored;timeout_ms,max_response_bytes.tool_rpmper key and tool (Redis); usage event per call (vendormcp, modelserver__tool, additiveToolCallinusage.v1);ktms_mcp_tool_calls_total,_duration_seconds,_response_bytes.Verified:
go vet,go test ./... -racewith Postgres,buf lint,buf breakingagainst main. Live smoke on the built binary: two upstream servers, Claude Codeclaude mcp addconnects (caught and fixed an emptycacheScopeit rejects), only allowed tools listed, a denied call never reaches the upstream, a generate-only key gets 403, metrics move, credential absent from logs, events land in light-tokenmeter (ai-process/light-tokenmeter PR,tool_callstored).