MCP gateway
The MCP gateway turns Dwara into an MCP (Model Context Protocol) server and router. Configured tools are exposed over JSON-RPC 2.0 on a reserved HTTP path (default /mcp); the gateway authenticates every request, authorizes per-tool access, proxies tool calls to upstream HTTP endpoints, manages agent sessions in the state store, and correlates tool calls in analytics.
The gateway is a ROUTER, not a tool executor: each tool names an upstream HTTP endpoint, and tools/call proxies the call (POST JSON body, get JSON response). The upstream's transport -- endpoint set, TLS trust, connection pooling, timeouts -- is the same machinery every other upstream gets.
When to use this
Use the MCP gateway when:
- You want to expose internal HTTP services as MCP tools that AI agents can discover and call through a single JSON-RPC endpoint.
- You want centralized authentication and per-tool authorization for tool access, reusing the gateway's existing authn/authz modules.
- You want agent sessions managed and correlated in analytics alongside your other AI traffic.
Configuration
The MCP gateway is configured under the ai.mcp block:
ai:
mcp:
path: /mcp
sessions:
ttl_secs: 3600
max_concurrent: 1000
tools:
search-docs:
description: Search the internal documentation
upstream: docs-api
path: /search
method: POST
timeout_ms: 30000
input_schema:
type: object
properties:
query:
type: string
required: [query]
authz:
groups: [internal]| Field | Default | Description |
|---|---|---|
path | /mcp | The reserved HTTP path for the MCP JSON-RPC endpoint. Must start with /. Shadows any configured route. |
sessions.ttl_secs | 3600 | Session TTL in seconds. Expired sessions are cleaned up and rejected on use. |
sessions.max_concurrent | 1000 | Maximum concurrent active sessions. New initialize requests beyond this limit are rejected. |
tools | (empty) | The tool table, keyed by tool name. |
Tool configuration
Each tool names an upstream that carries the transport and a path on that upstream. The tool's arguments are sent as the JSON request body, and the upstream's response body becomes the tool's output.
| Field | Default | Description |
|---|---|---|
description | (required) | Human-readable description returned to the client in tools/list. |
upstream | (required) | Name of the upstreams[] entry that carries this tool's transport. |
path | / | The path on the upstream (appended to the endpoint's address:port). |
method | POST | The HTTP method for the upstream call (GET, POST, PUT, PATCH, DELETE). |
input_schema | (required) | The JSON Schema for the tool's arguments (returned as inputSchema in tools/list). |
authz | (none) | Optional per-tool authorization. When present, the tool is only callable by consumers satisfying the authz rules. |
timeout_ms | 30000 | Upstream call timeout in milliseconds. |
Session management
The MCP protocol is JSON-RPC 2.0 over HTTP. The lifecycle:
initialize-- the client sends its protocol version and client info; the server creates a session, responds with its protocol version, capabilities, and server info, and returns the session id in theMcp-Session-Idresponse header.notifications/initialized-- the client acknowledges (a notification: no response).tools/list-- the server returns the tool definitions (filtered by the caller's authz).tools/call-- the server authorizes, proxies the call to the upstream, and returns the result.shutdown-- the server deletes the session.
Sessions are state-store backed (the mcp_sessions table) when DWARA_STATE_DB is set. Without a state store, sessions are stateless (the session id is still returned but not persisted). Session ids are 128-bit hex handles (mcp-<hex>), unique per process. The TTL (default 1 hour) controls expiry; expired sessions are rejected on use and cleaned up periodically. The max-concurrent limit (default 1000) rejects new initialize requests when the active session count is at the cap.
Authentication and authorization
Every MCP request runs through the same security/authn module as the proxy path: API key, Basic, JWT via JWKS, mTLS client-cert, or HMAC request signing. An unauthenticated request gets 401.
Per-tool authorization uses the same security/authz module as the proxy path. A tool with an authz attachment is only callable by consumers satisfying the rules (consumer/group/scope/claim rules against the authenticated identity, IP ACLs against the effective client IP). A tool without an authz attachment is open to any authenticated consumer. The tools/list response is filtered to only show tools the caller is allowed to invoke.
Analytics
Every tools/call is recorded in the mcp_tool_calls analytics table with the session id, consumer, tool name, authorization result, duration, error code, and status (success, error, or denied). The session id correlates calls within one agent session. Records are written fire-and-forget (never blocks the request path).
Resources and prompts
In addition to tools, the MCP gateway serves static resources and prompt templates. These are configured under ai.mcp.resources and ai.mcp.prompts and served inline (no upstream proxy).
Resources
ai:
mcp:
resources:
docs-index:
uri: dwara://docs/index
name: docs-index
description: Index of available documentation
mime_type: text/plain
content: |
Welcome to the documentation index.
Available topics: routing, budgets, guardrails.| Field | Default | Description |
|---|---|---|
uri | (required) | The resource URI. |
name | (required) | The resource name (key in the config map). |
description | (required) | Human-readable description. |
mime_type | text/plain | The MIME type of the content. |
content | (required) | The resource content, returned verbatim in resources/read. |
resources/list returns all configured resources with their URI, name, description, and MIME type. resources/read returns the content for a given URI. Unknown URIs return an MCP JSON-RPC error.
Prompts
ai:
mcp:
prompts:
summarize:
description: Summarize a document
template: |
Please summarize the following document:
{{document}}
Provide a concise summary in {{length}} sentences.
arguments:
- name: document
description: The document text to summarize
required: true
- name: length
description: Number of sentences for the summary
required: false| Field | Default | Description |
|---|---|---|
description | (required) | Human-readable description. |
template | (required) | The prompt body with placeholders. |
arguments | (empty) | Argument definitions. |
arguments[].name | (required) | The argument name. |
arguments[].description | (required) | Human-readable description. |
arguments[].required | false | Whether the argument is required. |
prompts/list returns all configured prompts with their description and argument definitions. prompts/get returns the prompt with placeholders substituted from the request arguments. Unknown prompt names return an MCP JSON-RPC error.
Admin API endpoints
GET /mcp/sessions-- list active (non-expired) MCP sessions from the state store, ordered by creation time descending.DELETE /mcp/sessions/:id-- teardown an MCP session by id (idempotent: returns 200 regardless of whether a row was deleted).GET /mcp/tools-- list configured MCP tools from the current snapshot'sai.mcpconfig block.GET /mcp/calls?from_ms=...&to_ms=...&session_id=...&consumer=...&tool_name=...-- query MCP tool call analytics.from_msandto_msare required;session_id,consumer,tool_name, andlimitare optional filters.
Metrics
dwara_mcp_sessions_total{state}-- MCP session lifecycle transitions.stateisinitialized(a new session created byinitialize),closed(a session deleted byshutdown), orexpired(a session reaped by the TTL cleanup).dwara_mcp_tool_calls_total{tool,status}-- MCP tool calls.toolis the config-declared tool name (config-bounded);statusissuccess,error, ordenied. No consumer label (cardinality rule).dwara_mcp_tool_duration_seconds{tool}-- MCP tool call duration (authz check through upstream response), by tool (config-bounded label).
Runnable demo
Run the MCP gateway against a live gateway: demos/07-ai-gateway/ in the repository (test script: test-11-mcp-gateway.sh -- tools/list over /mcp). The category README covers prerequisites and teardown.