The gateway exposes the same Phase 2 pipeline as a set of Model Context
Protocol tools, so LLM agents (Claude Desktop, IDE assistants, anything
that speaks MCP) can drive the full RAG stack — retrieve, query,
ingest — without a bespoke HTTP/gRPC client.
The server is built on the official mcp Python SDK
(mcp.server.fastmcp.FastMCP) and transported over stdio. There is
one implementation, in Python (rag_gateway.mcp); the
@ragplatform/mcp npm package is a thin launcher that shells out to it
(see the launcher README).
Server name: ragplatform.
| Tool | REST equivalent | Returns |
|---|---|---|
query |
POST /v1/query |
answer (when generate=true) + citations + routing summary |
retrieve |
POST /v1/retrieve |
fused chunk references + routing summary |
ingest |
POST /v1/ingest/document |
per-document ingest outcome |
Every call must carry a tenant_id; results are tenant-scoped.
# Directly
python -m rag_gateway.mcp
# Via the npm launcher (shells out to the Python server)
npx -y @ragplatform/mcpWire it into an MCP client (e.g. Claude Desktop claude_desktop_config.json):
{
"mcpServers": {
"ragplatform": {
"command": "npx",
"args": ["-y", "@ragplatform/mcp"]
}
}
}uv run ragctl mcp-query "what is RAG?" --tenant acme --generateDrives an in-memory MCP client/server session — no stdio process, no
infrastructure. Mirrors ragctl query (REST) and ragctl grpc-query
(gRPC) so the three surfaces stay in lock-step.
ragctl mcp-serve runs the blocking stdio server (the same entry point
as python -m rag_gateway.mcp).
End-to-end RAG: understand → route → optional rerank → optional pack → optional generate.
| Argument | Type | Default | Notes |
|---|---|---|---|
query |
string | — | required |
tenant_id |
string | — | required; empty → error |
principal_id |
string | mcp-anon |
recorded on the RequestContext |
corpus_ids |
string[] | — | restrict to these corpora |
top_k |
int | 5 |
candidates to retrieve/rerank |
rerank |
bool | true |
run the two-stage reranker |
pack |
bool | true |
run the context packer |
pack_budget |
int | 2048 |
token budget for packing |
generate |
bool | false |
call the LLM for an answer |
generate_max_tokens |
int | 1024 |
LLM cap when generate=true |
generate_temperature |
float | 0.0 |
LLM temperature |
Returns McpQueryResult: request_id, query, shape,
bm25_fallback, answer (null unless generate=true and chunks were
retrieved), citations (each: index, chunk_id, document_id,
source_uri, excerpt, score), chunks_n, chunk_refs_n,
total_ms. Citation index matches the bracket markers in answer.
Retrieval only — understand → route, returning fused chunk references and the routing decision without rerank/pack/LLM.
| Argument | Type | Default |
|---|---|---|
query |
string | required |
tenant_id |
string | required |
principal_id |
string | mcp-anon |
corpus_ids |
string[] | — |
top_k |
int | 5 |
Returns McpRetrieveResult: request_id, query, shape,
bm25_fallback, chunk_refs (each: chunk_id, corpus_id, score),
total_ms.
Ingest a single text document into a corpus through the full pipeline (parse → chunk → enrich → PII → embed → store).
| Argument | Type | Default | Notes |
|---|---|---|---|
corpus_id |
string | — | required |
tenant_id |
string | — | required |
content |
string | — | required; UTF-8 text, empty → error |
principal_id |
string | mcp-anon |
|
filename |
string | — | informational; used for MIME sniffing |
mime_type |
string | — | overrides MIME detection |
source_uri |
string | — | defaults to upload:///<filename> |
Returns McpIngestResult: document_id, status, chunk_count,
embedding_count, blocked_chunk_count, bytes_in, error.
The query / retrieve tool arguments are kept field-for-field in sync
with rag_core.gateway_types.QueryRequest / RetrieveRequest (minus the
two deferred fields below). A conformance test
(tests/contract/test_mcp_tool_compat.py)
fails CI if a request-model field is added without a matching tool
argument:
filters— structured filter expressions are deferred from the MCP surface (mirrors the gRPCQueryshape); revisit when an agent use case needs them.request_id— minted fresh per tool call (an MCP client has no request-id concept), so it is never caller-supplied.
Domain failures are raised as MCP ToolError, so the client receives a
result with isError = true and a content text of the form
code: message — the same code + message carried by the REST
GatewayError body and the gRPC trailing-metadata envelope. All three
surfaces report identical failure modes; only the transport differs.
| Trigger | Detail prefix |
|---|---|
missing tenant_id |
AUTH_ERROR: tenant_id is required |
empty ingest content |
INVALID_ARGUMENT-class ValueError: empty document content |
ACLDeniedError |
ACL_DENIED: … |
RateLimitError |
RATE_LIMITED: … |
RetrievalError |
RETRIEVAL_ERROR: … |
RagError subclasses contribute their .code; non-RagError
exceptions fall back to the class name.
Structured-log events emitted via rag-observability (same
extra={"event_kind": …} pattern as the REST + gRPC surfaces):
mcp.server_built— one perbuild_mcp_servercall.mcp.server_listening— emitted byserve()before the stdio loop.mcp.query_complete— one per successfulquerytool call.mcp.ingest_complete— one per successfulingesttool call.
The implementation lives under
apps/gateway/src/rag_gateway/mcp/:
server.py—build_mcp_server(...)factory, the three@mcp.toolregistrations,serve(...)stdio entry point, and the error mapping.backend.py— theToolBackendProtocol (the seam) +InProcessBackend(composes the Phase 2 pipeline directly, reusingrag_gateway.query's answer/citation helpers).types.py— the lean agent-facing output models + projection functions.__main__.py—python -m rag_gateway.mcp.
build_mcp_server accepts the same composition kwargs as
rag_gateway.app.build_app, so a single set of SPI implementations
serves REST + gRPC + MCP in one process. Defaults pull from the shared
build_default_* helpers.
- Backend topology: pass a ready
ToolBackendtobuild_mcp_serverto fully control execution. The current default isInProcessBackend; a futureGatewayClientBackend(HTTP client to a running gateway) slots in at this seam without touching the tool layer. See ADR-0012. - PolicyEngine: threaded through to the same
egress_textconsult used by REST in_generate_answer— seearchitecture/policy-engine.md. - LLM / reranker / packer / understanding / router / ingest
pipeline: each is an injectable kwarg on
build_mcp_server.
- ADR-0012 — server topology + npm distribution decision.
architecture/mcp-server.md— internals, composition diagram, deferred work.guides/mcp-quickstart.md— 5-minute walkthrough.- the launcher README —
@ragplatform/mcpnpm package + Python interpreter discovery.