The MCP surface¶
Design authority:
docs/jdlib/capabilities/mcp.md— this guide is the developer-facing shape of that page, not a second authority for it. Where the two disagree, the authority page and the code win.
What it is¶
A second surface over the same application: tool declarations, a boundary that turns a request's credential into a SecurityContext, and an invocation chain -- boundary, authorization, handler, audit -- inside the request's context.
Why it exists¶
A model-driven surface is a new entry point to the same data, and the temptation is to give it its own shortcuts. The library's shape is the opposite: the same authenticator, the same policy engine, the same services -- so a decision cannot differ by protocol.
When to use it¶
When an agent or an automation needs to act on a tenant's data and the action is something an operator would otherwise do by hand: read a resource, list them, archive one.
When not to use it¶
As a second API for humans, and for anything whose effect is not reversible or not authorized the same way an HTTP route would be. If a capability has no HTTP route, ask why it should exist as a tool.
How it works¶
A McpTool declares its name, schema, capability and handler; McpToolRegistry refuses one without a declared capability. McpSecurityBoundary authenticates the request, resolves the tenant and builds the context; McpInvoker then authorizes the declared capability, runs the handler inside the context scope, and audits the outcome. Arguments that name an identity are refused, not read.
Architecture¶
The chain, in the order the sentences above happen:
sequenceDiagram
participant Client as MCP client
participant Server as build_mcp_server
participant Boundary as McpSecurityBoundary
participant Invoker as McpInvoker
participant Tool as the declared handler
participant Audit as the audit emitter
Client->>Server: tools/call { name, arguments }
Server->>Boundary: authenticate the request
Boundary->>Boundary: resolve the tenant, build the SecurityContext
Boundary->>Invoker: invoke(name, arguments, context)
Invoker->>Invoker: authorize the declared capability
alt the capability is not granted, or an argument names an identity
Invoker->>Audit: the refusal, as a decision
Invoker-->>Client: refused, with nothing about the resource
else granted
Invoker->>Tool: run inside the context scope
Tool-->>Invoker: the result
Invoker->>Audit: the outcome
Invoker-->>Client: the result
end
integrations/mcp/tools.py (declaration and registry), boundary.py (identity), invocation.py (the chain), server.py (build_mcp_server), graph_tools.py and the resources (the library's own tools). The application supplies the collaborators, not the policy.
Example¶
examples/enterprise/app/mcp/ declares three tools and builds the server from the same collaborators create_app uses; the example's unit tests assert that identity, and its live layer drives the whole chain -- a real key in the control plane, the live engine, the real database and the real graph.
Security¶
The capability is declared and enforced before the handler; a schema that could carry an identity is refused; a denial raises before execution and the audit trail records it. The chain fails closed: no credential, no tenant, no decision, no execution.
Reliability¶
Tool calls share the reliability primitives: the invocation has a timeout, and the graph tools go through the same circuit breaker as any other graph access. A degraded policy engine refuses rather than allowing.
Observability¶
mcp.* span names carry the tool, the outcome and the tenant; the correlation id is the caller's, so a tool call and the HTTP request that started it join.
Audit¶
Three events per refused call in the example's trail -- the boundary's mcp.request, the engine's decision and the chain's refusal -- and the same vocabulary on the allowed path.
Configuration¶
The mcp extra (the framework is imported lazily), the transport the deployment chooses, and the tools the application declares. Nothing about the security chain is configurable.
Testing¶
The library's MCP suites cover the registry, the boundary, the invocation and the adversarial cases; the example adds an inventory test, a capability test and a live chain.
Common mistakes¶
Building a second authenticator for the tool surface; declaring a tool without a capability; accepting an identity argument; skipping the context scope so the handler sees no tenant; letting a tool reach a driver instead of a service.
Production checklist¶
Serve the tool surface on the deployment's authenticated transport; keep the tool inventory small and reviewed; watch the denial rate; and make sure the process refuses to start when the chain's dependencies are missing.