Developer

MCP for AI assistants

Sanshain exposes its catalog to AI assistants over the Model Context Protocol. An assistant with the endpoint configured answers "who provides the orders API and who depends on it?", "where is the endpoint that creates an invoice?", "what changed in payments between 2.3.0 and 2.4.0?" or "show me the spec of inventory" straight from the catalog. Read-only, and only the catalog: never users, roles, settings, authentication, audit or observability data.

What changed in 2.6.0

describe_service answers the system's catalog context: owner, description, source location and links from its Backstage catalog-info.yaml, or null when the build never pushed one. The endpoint and the other tools are as in 2.5.0.

In short#

The token#

The endpoint authenticates with API tokens, the same tokens CI uses, and nothing else. Create one on /account.html and tick Read-only (for AI / MCP clients): a read_only token can query everything the tools answer but is refused on every route that changes the catalog (provide, retire, require, catalog push), so a misdirected assistant cannot publish or pin anything. A full token works too; prefer read-only unless the same token also drives a build. The token list on the account page shows when an assistant last used its token.

The endpoint#

/mcp speaks MCP's Streamable HTTP transport in stateless mode: every request carries the token, no session is kept, and a restart loses nothing a client could notice. JSON-RPC requests are POSTed with Accept: application/json, text/event-stream; the route also answers GET and DELETE as the transport requires.

Condition Answer
No Authorization header 401 with WWW-Authenticate: Bearer
A session token, or an unknown or expired API token 401: unknown or expired API token (session tokens are not accepted here)
MCP_ENABLED=false 404: the route is not mounted
Over the rate limit A tool result with isError: true and rate limited - retry in N s, never an HTTP 429, which would break the client's session
Answer above MCP_MAX_RESULT_BYTES That tool's summary and a hint on how to narrow the question
Unknown service, version or endpoint A tool result with isError: true and a message naming it

Developer mode substitutes the dev user when no header is sent, exactly as the API does. Local only.

Client configuration#

Claude Code

claude mcp add --transport http sanshain https://<host>/mcp --header "Authorization: Bearer <token>"

Any stdio-only client, through the mcp-remote bridge:

{
  "mcpServers": {
    "sanshain": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://<host>/mcp", "--header", "Authorization: Bearer <token>"]
    }
  }
}

Cursor (.cursor/mcp.json):

{
  "mcpServers": {
    "sanshain": {
      "url": "https://<host>/mcp",
      "headers": { "Authorization": "Bearer <token>" }
    }
  }
}

VS Code (.vscode/mcp.json) spells the top-level key servers and the transport "type": "http":

{
  "servers": {
    "sanshain": {
      "type": "http",
      "url": "https://<host>/mcp",
      "headers": { "Authorization": "Bearer <token>" }
    }
  }
}

Keep the token out of files you commit; most clients read it from an environment variable (${env:SANSHAIN_TOKEN} in VS Code, ${SANSHAIN_TOKEN} in Cursor).

The tools#

Every tool answers compact JSON stamped with the scope (and at) it was computed for. Scopes are the reports': dev (default) is everything ever provided or required, main is what trunk builds currently pin, and a sanshain-branch name is a release cut; main and branches take an at instant on get_graph. Unknown names are refused with a message, never with a transport error.

Tool Parameters Answers, and an example question
list_services scope? Producers (domain, icon, API types, version line per type, endpoint and consumer counts, unrequired) and Consumers (what they require). Which services are in production?
describe_service name, scope? One service: version lines (with last_provided_at, superseded, origin), endpoints of the latest version per line with their one-line summary from the spec, its consumers and what they pin, what it requires, its periphery and who shares it, and its context from catalog-info, tags included. Who owns orders, and where is its code?
find_endpoint query, method?, api_type?, all_versions? Endpoints across all Producers whose path, channel or method name contains query (case-insensitive, * wildcard), latest versions by default, each with its one-line summary from the spec and the consumer count. Where is the endpoint that cancels an order, and what does it do?
get_spec service, api_type, version?, path?, method?, offset?, limit? The stored document (latest version when version is omitted) or one endpoint's snippet, paged by line with offset/limit and next_offset. Show me the request body of POST /orders.
get_graph scope?, at?, service?, api_type?, include_periphery? Nodes (producer, consumer, both, missing, resource) and edges (api_type, method, path, version, stability, outdated, superseded, harvested, peripheral). What did the release graph look like on the 1st?
diff_versions service, api_type, from, to, ignore_whitespace?, path?, method? The unified diff (the UI's engine), the added and removed line counts and the origin of both versions (from_origin, to_origin: build, change, tickets, note). What changed between 2.3.0 and 2.4.0 of payments, and why?
blame service, api_type, version, path?, method? Blocks of lines with the version that last changed them, who provided it, when, and its origin. Since when has this field been required, and which change made it so?
endpoint_structure service, api_type, version?, path, method, with_examples? One OpenAPI or AsyncAPI endpoint as the viewer's structure tree: parameters, request body, responses or messages, every $ref inlined, each node with type, flags, description, values and changed_in (with inside for a newer change below). with_examples adds a JSON example per body. What does POST /orders need, and since when is currency in it?

api_type is openapi, asyncapi or proto; versions are MAJOR.MINOR.PATCH; path and method always go together. Every schema refuses unknown parameters.

Over-budget answers#

An answer above MCP_MAX_RESULT_BYTES is never truncated silently:

Limits and settings#

Variable Default Meaning
MCP_ENABLED true Mount /mcp. false leaves it unmounted (404).
MCP_CALLS_PER_MINUTE 60 Tool calls per minute per API token: a token bucket, a burst of 60, refilled continuously. Must be greater than 0.
MCP_MAX_RESULT_BYTES 65536 The size budget of one tool answer; above it the summary is answered. Must be greater than 0.

initialize and tools/list do not count against the rate limit; only tool calls do. The HTTP body limit (MAX_SPEC_BODY_BYTES) applies to requests as everywhere else. Two Prometheus counters on /metrics describe the traffic: sanshain_mcp_tool_calls_total{tool, outcome} with outcome one of ok, error, rate_limited, over_budget, and sanshain_mcp_result_bytes_total{tool}.

Security notes#

Checked against v2.6.0.