MCP (AI Tools)¶
The Model Context Protocol server, registries, and transports. Most applications drive MCP through app.mount_mcp(...), @app.mcp_tool, and @app.mcp_prompt; these names are for callers that assemble or serve the registry themselves.
MCPContext
¶
Per-invocation context for an MCP tool call.
Usage::
@app.mcp_tool(description="Look up a user by id")
async def get_user(user_id: int, ctx: MCPContext) -> dict:
await ctx.log("info", f"looking up {user_id}")
await ctx.report_progress(1, 2)
return {"id": user_id}
cancelled
property
¶
Whether the client has sent notifications/cancelled for this call.
state
property
¶
Scratch space shared by everything resolving this one call.
The same object a handler declaring request: Request reaches through
request.state, so a dependency and the handler holding this context
read and write one store rather than two that could disagree. Scoped to
the call: a later tools/call starts clean.
Usage::
@app.mcp_tool(description="Look something up")
async def lookup(ctx: MCPContext) -> dict:
ctx.state.started = time.monotonic()
return {"ok": True}
session_id
property
¶
The dispatching connection's id, or None on the stateless path.
Unique across processes, so it stays a safe key for per-client state
under a multi-worker server. It identifies a connection, not a client:
a reconnecting client gets a new one, and under HTTP without a shared
session_backend a client that lands on another worker does too.
client_info
property
¶
The client's implementation block from initialize, or empty.
client_capabilities
property
¶
The capabilities the client advertised, or empty off a stateful transport.
is_background_task
property
¶
Whether this call is running as a task rather than inline.
request_meta
property
¶
The _meta the client sent with this request, or an empty mapping.
The protocol reserves _meta for metadata it does not define - a
progress token, a trace id, an extension's own block - so a handler that
needs to read or relay what the client attached finds it here.
client_id
property
¶
The authenticated caller's id, or None when the call is unauthenticated.
The subject of the principal the transport established - for a
client-credentials token that is the registered MCP client. client_info
is what the client said it was at initialize; this is what it proved.
request_id
property
¶
The JSON-RPC id of the call being served, or None for a notification.
origin_request_id
property
¶
The id of the tools/call that created this task, or None inline.
A background task outlives the request that started it, so its own
request_id is not the one the client is correlating against.
task_id
property
¶
The id of the task this call is running as, or None when inline.
The same handle the client polls with tasks/get, so a handler can
record it against whatever it writes.
transport
property
¶
The transport serving this call - "stdio", "http" or "sse".
None off a transport. A handler that needs to know whether a
server-initiated request can reach the client should ask
client_supports(...) instead; this is for logging and diagnostics.
lifespan_context
property
¶
The application state established at startup.
The same app.state an HTTP handler reaches, so a connection pool or a
client opened in a lifespan hook is reached the same way through either
door. None off a server.
result_meta
property
¶
Scratch _meta sent back on this call's result.
The protocol reserves _meta for metadata it does not define, so this is
where a handler puts what its client agreed to read - a cost, a trace id,
an extension's own block. Mutated in place::
ctx.result_meta["io.example/trace"] = trace_id
It belongs to one call: the next one starts empty. A handler that never
touches it sends no _meta at all.
client_supports
¶
Return whether the client advertised capability (dotted for nested).
ctx.client_supports("sampling") and ctx.client_supports("sampling.tools")
both work; the same lookup the server-initiated requests gate on.
debug
async
¶
Send a debug-level log message to the client.
info
async
¶
Send an info-level log message to the client.
warning
async
¶
Send a warning-level log message to the client.
error
async
¶
Send an error-level log message to the client.
log
async
¶
Send a log message to the MCP client (notifications/message).
Dropped when no notification channel is wired, or when level is below the
client's logging/setLevel minimum.
report_progress
async
¶
Report progress to the MCP client (notifications/progress).
Dropped when no notification channel is wired, or when the client did not
send a progressToken with the call (progress is only reported on request).
read_resource
async
¶
Read one of this server's registered resources by URI.
Goes through the same handler resources/read serves, so the resource's
declared scopes are enforced against the calling principal exactly as they
would be for a direct client read - a tool cannot reach a resource its
caller could not have read itself.
get_prompt
async
¶
Render one of this server's registered prompts by name.
Goes through the same handler prompts/get serves, including its scope
check, for the same reason read_resource does.
list_resources
¶
List this server's registered resources, as resources/list reports them.
list_prompts
¶
List this server's registered prompts, as prompts/list reports them.
send_notification
async
¶
Send a JSON-RPC notification to the client.
Inert when no notification channel is wired, matching log and
report_progress.
sample
async
¶
sample(messages: list[dict[str, Any]], *, max_tokens: int, model_preferences: dict[str, Any] | None = None, system_prompt: str | None = None, temperature: float | None = None, stop_sequences: list[str] | None = None, tools: list[dict[str, Any]] | None = None, tool_choice: dict[str, Any] | None = None, metadata: dict[str, Any] | None = None, include_context: str | None = None) -> dict[str, Any]
Ask the client's LLM to sample a completion (sampling/createMessage).
Returns the client's result (its chosen model, role, and content). Requires
a bidirectional transport and a client that advertised the sampling
capability; tools / tool_choice additionally require sampling.tools.
include_context asks the client to attach context from MCP servers to the
prompt - "none", "thisServer", or "allServers". The client MAY
ignore the request, so it is a hint rather than a guarantee.
sample_with_tools
async
¶
sample_with_tools(messages: list[dict[str, Any]], *, tools: list[str], max_tokens: int, max_tool_rounds: int = 5, model_preferences: dict[str, Any] | None = None, system_prompt: str | None = None, temperature: float | None = None, stop_sequences: list[str] | None = None, tool_choice: dict[str, Any] | None = None, metadata: dict[str, Any] | None = None) -> SamplingRun
Sample with tools, executing the ones the model asks for, until it answers.
tools names tools of this server the model may drive. Each request the
model makes runs through the same path tools/call serves - declared
scopes, call hooks, timeout and error shaping included - and its result
is fed back as the next message. Returns a SamplingRun carrying the
answer, the transcript, and every tool call made.
max_tool_rounds caps how many times tools are executed. On the round
after the cap the model is asked to answer without tools, so a run ends
with an answer rather than an unanswered request; a client that ignores
that instruction ends the run where it stands.
Requires a client that advertised sampling.tools.
elicit
async
¶
elicit(message: str, *, requested_schema: dict[str, Any] | None = None, url: str | None = None, elicitation_id: str | None = None) -> dict[str, Any]
Ask the client to gather input from its user (elicitation/create).
Form mode passes a requested_schema (the JSON Schema of the fields to
collect); URL mode passes a url the client opens instead. Returns the
client's response (its action and any collected content). Requires a
bidirectional transport and a client that advertised elicitation.
URL mode also carries the elicitationId the spec requires, which names
the interaction in a later notifications/elicitation/complete. One is
minted per call; pass elicitation_id to use an identifier the URL flow
already knows, so the completion can be correlated with whatever happens
out of band.
roots
async
¶
List the client's exposed filesystem roots (roots/list).
Returns the client's roots array. Requires a bidirectional transport and
a client that advertised the roots capability.
hide
async
¶
Hide tools, prompts or resources from this connection's listings.
Names a tool or prompt by name and a resource by URI. The change belongs to the connection that made the call - another client's listings are untouched - and this connection is told its lists changed so it fetches them again.
Hiding is not enforcement. A hidden primitive is still callable, exactly
as with a mount_mcp(tool_filter=...) policy: what a caller may invoke is
decided by its declared scopes, so a hidden name cannot be mistaken for a
permission boundary.
MCPSession
¶
Lifecycle state for one stateful MCP connection.
Holds whether the connection has initialized and the client's advertised
capabilities / implementation info, recorded from the initialize request.
public_id
property
¶
A globally unique identity for this connection, safe to key state on.
connection_id alone restarts at 1 in every worker process, so it is an
ownership key for this process's registries and nothing more. This is
what application code is handed. Composed on access rather than stored:
a stateless HTTP POST builds a session per request, and a string it
never reads would be pure per-request cost.
record_initialize
¶
Record the client's advertised capabilities and info from initialize.
record_request_meta
¶
Record the client identity a modern request carries in its _meta.
The modern revision has no initialize: a client states who it is and what
it supports on every request. Recording it here keeps one place -
client_info / client_capabilities - answering for both eras, so nothing
downstream has to know which handshake produced them. Absent keys leave the
previous values alone rather than clearing them, since a session may be
persistent across requests.
supports
¶
Return whether the client advertised the named top-level capability.
MCPServer
¶
Bases: TasksMixin, InvocationMixin
Serve a Veloce app's MCP tools over JSON-RPC 2.0.
Build once with the app; the registry is assembled eagerly so a registration-time safety violation (missing description, duplicate name) surfaces before any client connects.
set_notifier
staticmethod
¶
Wire the current context's outbound one-way notification sink.
Sets the per-request _notifier_var; the stdio transport calls this once
in its serve task, while the Streamable HTTP transport sets the var per
request so concurrent calls never cross notifications.
set_requester
staticmethod
¶
Wire the current context's server->client request issuer.
Sets the per-context _requester_var; a bidirectional transport (the stdio
loop) calls this once in its serve task so a tool's MCPContext.sample /
elicit / roots reaches the client. A one-way transport never calls it,
leaving those methods to raise.
current_request_id
staticmethod
¶
The JSON-RPC id of the request being dispatched, or None for a notification.
send_to_current_connection
async
staticmethod
¶
Send one server-initiated message down the dispatching connection.
current_session
staticmethod
¶
current_session() -> MCPSession | None
Return the session of the connection currently dispatching, or None.
Set per dispatch by handle_message when a stateful transport supplies a
session; None on the stateless HTTP path or off-dispatch.
register_connection
¶
register_connection(session: MCPSession, sink: Callable[[dict[str, Any]], Awaitable[None]]) -> object | None
Record an open stateful connection so it can receive resource updates.
Returns an opaque token a transport passes back to unregister_connection
to drop exactly this stream, so concurrent streams on one session are
tracked independently. A no-op returning None when subscriptions are
disabled, so a transport may call this unconditionally.
unregister_connection
¶
Drop the connection named by its token (a no-op when token is None).
Any subscriptions/listen streams the connection held go with it: the
transport is gone, so there is nowhere to send a graceful close, and a
stream left registered would keep a dead session reachable by fan-out.
evict_session
¶
evict_session(session: MCPSession) -> None
Reclaim everything an evicted session owns: its connection and tasks.
Called when a session's transport drops it (idle TTL on HTTP). Beyond unregistering the subscription connection, this cancels and drops the session's tasks - including a never-settling one TTL eviction would leave in place - so an abandoned session cannot pin a task for the process lifetime.
notify_resource_updated
async
¶
Tell subscribed clients a resource changed (notifications/resources/updated).
Call this from the app when a resource's data changes; the server fans the
notification out to every connection subscribed to uri. A no-op when
subscriptions are disabled or no connection subscribed to uri.
notify_resources_list_changed
async
¶
Tell clients the resource list changed (notifications/resources/list_changed).
Call this from the app when the set of available resources changes; the server fans the notification out to every open connection. A no-op when subscriptions are disabled.
notify_tools_list_changed
async
¶
Tell listening clients the tool list changed.
Reaches only the subscriptions/listen streams that asked for
toolsListChanged; the spec forbids sending a type a client did not
request. A no-op when nothing is listening.
notify_prompts_list_changed
async
¶
Tell listening clients the prompt list changed.
Reaches only the streams that asked for promptsListChanged.
handle_message
async
¶
handle_message(message: dict[str, Any], session: MCPSession | None = None) -> dict[str, Any] | None
Dispatch one decoded JSON-RPC request; return the response object.
Returns None for a notification (a request with no id), which
carries no response per JSON-RPC 2.0 Sec. 4.1.
A stateful transport (the serial stdio loop) passes its session so the
server records the client's advertised capabilities from initialize and
enforces the lifecycle ordering: before initialize completes the only
requests answered are initialize and ping. The stateless HTTP
transport passes none, leaving its fast path unaffected.
close_listen_stream
async
¶
close_listen_stream(session: MCPSession, subscription_id: Any) -> None
End one open stream, answering its long-lived request as it closes.
The response is what tells the client the subscription ended cleanly, as opposed to a transport that simply dropped.
connection_is_stateful
¶
Whether the connection being answered persists beyond this request.
A stateful connection has an outbound channel and can carry per-connection state; a stateless request has neither, and nothing it is told survives the response.
MCPTool
dataclass
¶
Bases: MCPDescriptor
One registered MCP tool.
MCPResource
dataclass
¶
Bases: MCPDescriptor
One registered MCP resource (a read-only route addressed by URI).
MCPPrompt
dataclass
¶
Bases: MCPDescriptor
One registered MCP prompt template.
MCPTask
dataclass
¶
Bases: MCPDescriptor
One in-flight or settled task created from a task-augmented tool call.
name (inherited) is the task id the client polls by. The task records its
lifecycle status, the tool it runs, its created / last-updated timestamps and
time-to-live, and - once it settles - the tools/call result the client
retrieves with tasks/result.
describe
¶
Shape this task into the MCP Task object the task methods return.
The extension renamed the two duration fields, so a modern client is sent
ttlMs / pollIntervalMs and a handshake client the names its revision
defined. Everything else is common to both.
is_terminal
¶
Return whether the task has settled (no further transition happens).
settle
¶
Move the task to a terminal status carrying its final result.
A no-op once the task is already terminal: this guards against a racing
tasks/cancel and the natural completion of _run_task both settling the
same task, so the first terminal status (e.g. cancelled) is never
overwritten by the second.
ToolRegistry
dataclass
¶
Bases: Registry[MCPTool]
Name -> MCPTool, plus the shared JSON Schema component registry.
schemas holds Pydantic-model components shared across tool input
schemas (mirroring OpenAPI components.schemas); a tool input schema
references them by $ref.
register
¶
Add item, rejecting a key already present with a primitive-specific error.
ResourceRegistry
dataclass
¶
Bases: Registry[MCPResource]
URI -> MCPResource, plus the shared JSON Schema component registry.
register
¶
Add item, rejecting a key already present with a primitive-specific error.
templates
¶
templates() -> list[MCPResource]
Return the URI-template resources (for resources/templates/list).
match
¶
match(uri: str) -> tuple[MCPResource, dict[str, str]] | None
Resolve a concrete URI to its resource and extracted path parameters.
A static resource matches by exact URI (no parameters); a template resource matches by its compiled pattern, yielding the path-parameter values to invoke the route with. Static resources are tried first so a concrete URI never falls through to a template that would also match it.
PromptRegistry
dataclass
¶
Bases: Registry[MCPPrompt]
Name -> MCPPrompt, plus the shared JSON Schema component registry.
TaskRegistry
dataclass
¶
Bases: Registry[MCPTask]
Task id -> MCPTask, the in-memory store of created tasks.
register
¶
Add item, rejecting a key already present with a primitive-specific error.
evict_expired
¶
Drop tasks whose time-to-live has elapsed so a stale task does not leak.
Only a settled task is evicted on expiry; a still-working task is left in place even past its ttl so its eventual result is never discarded out from under a client that is still polling.
TasksCapability
¶
Bases: _ServerCapability
The tasks/get|result|list|cancel methods and the tasks advertisement.
Advertised only when at least one tool opts into task support, so a server whose tools all run synchronously stays inert and a client never probes an empty capability.
extensions
¶
Advertise the tasks extension when any tool opts into task execution.
SubscriptionsCapability
¶
Bases: _ServerCapability
The resources/subscribe / resources/unsubscribe methods, opt-in.
Folded into the resource area but kept a separate capability so the base
ResourcesCapability stays unchanged when subscriptions are off. It
contributes no initialize entry of its own — the resource advertisement
(the subscribe/listChanged sub-capability flags) lives on
ResourcesCapability, which reads the same opt-in flag — so advertise
returns None.
CompletionsCapability
¶
Bases: _ServerCapability
The completion/complete method, advertised when a completer exists.
Completion is opt-in: the capability is advertised only when at least one prompt or resource argument carries a registered completer, so a server with none stays inert and a client never probes an empty capability.
CompletionResult
dataclass
¶
An explicit completion response: candidate values plus optional totals.
Return this from a completer to declare the full match total and whether
more values exist beyond those returned; return a bare sequence of strings
instead to let the capability derive both from the values it received.
Usage::
@app.mcp_completer(prompt="greet", argument="name")
async def complete_name(value: str) -> CompletionResult:
matches = await lookup_names(prefix=value)
return CompletionResult(matches[:100], total=len(matches))
build_registry
¶
build_registry(app: Any) -> ToolRegistry
Assemble the tool registry from explicit tools plus exposed routes.
build_resource_registry
¶
build_resource_registry(app: Any) -> ResourceRegistry
Assemble the resource registry from routes flagged expose_as_mcp_resource.
Mirrors the tool registry walk: every route is visited (including those
hidden from the OpenAPI schema), WebSocket routes are skipped, and a
multi-verb route is deduplicated by RouteInfo identity so it is exposed
once with its full verb set.
build_prompt_registry
¶
build_prompt_registry(app: Any) -> PromptRegistry
Assemble the prompt registry from @app.mcp_prompt registrations.
Composing a surface from more than one source: tools mounted from a sub-application, served from an upstream MCP server, or derived from a tool this application already registers.
add_mcp_proxy
async
¶
add_mcp_proxy(app: Any, namespace: str, request: Callable[[str, dict[str, Any]], Awaitable[dict[str, Any]]], *, scopes: Sequence[str] | None = None, tags: Sequence[str] | None = None) -> list[str]
Discover an upstream MCP server's tools and serve them from app.
namespace prefixes every discovered tool name, so two upstreams offering a
tool of the same name stay distinct and a local tool is never shadowed.
Returns the local names registered, in the order the upstream listed them.
scopes requires them of a caller before any of these tools is invoked or
listed, the same check a locally registered tool's scopes= performs - a
gateway is where that matters most, since the upstream cannot see who is
asking. tags labels them for a mount_mcp(tool_filter=...) policy.
Call this before mount_mcp, which builds the registry.
derive_tool
¶
derive_tool(tool: MCPTool, *, name: str | None = None, description: str | None = None, arguments: dict[str, ArgTransform] | None = None) -> MCPTool
Return a new tool that calls tool's handler through a narrower surface.
arguments maps an argument of the original to how it should appear. An
argument not mentioned is published unchanged. Naming one the original does
not have is refused, since it would silently do nothing.
ArgTransform
dataclass
¶
How one argument of a derived tool differs from the original's.
name publishes the argument under a different name; the value is translated
back before the handler runs. description and schema reshape what the
agent is told. default supplies a value when the caller omits one. hide
removes the argument from the schema entirely, in which case default is
what the handler receives - a hidden argument with no default would leave the
handler short of something it requires, so that combination is refused.
has_default
property
¶
Whether this transform supplies a value when the caller omits one.
What a sample_with_tools run reports back to the handler that started it.
SamplingRun
dataclass
¶
The outcome of a sample_with_tools loop.
content is the final assistant content, messages the whole transcript
including that answer as its closing turn - so extending it for another run
carries the reply along - and tool_calls every tool the model drove, in the
order it drove them.
SampledToolCall
dataclass
¶
One tool the model asked for during a run, and what it answered.
The content a tool, resource, or prompt may return beyond plain text.
ContentBlock
¶
Base MCP content block: its type payload plus optional annotations.
to_payload
¶
Render this block as its MCP content-block dict, merging annotations.
TextContent
¶
Bases: ContentBlock
A text content block carrying a plain-text value.
to_payload
¶
Render this block as its MCP content-block dict, merging annotations.
ImageContent
¶
Bases: ContentBlock
An image content block carrying base64 bytes and their media type.
to_payload
¶
Render this block as its MCP content-block dict, merging annotations.
AudioContent
¶
Bases: ContentBlock
An audio content block carrying base64 bytes and their media type.
to_payload
¶
Render this block as its MCP content-block dict, merging annotations.
EmbeddedResource
¶
Bases: ContentBlock
A resource block inlining a resource's contents directly in the result.
Carries the resource-contents entry (the uri/mimeType plus a text or
base64 blob value) the client would otherwise fetch, so an agent reads the
data without a follow-up resources/read.
to_payload
¶
Render this block as its MCP content-block dict, merging annotations.
ResourceLink
¶
Bases: ContentBlock
A resource_link block referencing a resource by URI rather than inlining it.
A tool whose result points an agent at a server resource emits this in place
of the bytes: the client follows the uri with a resources/read. The
name is required by the spec; title, description, and mime_type are
optional hints and are omitted when unset.
to_payload
¶
Render this block as its MCP content-block dict, merging annotations.
Icon
dataclass
¶
One MCP icon: a source URI plus optional media type and rendered sizes.
to_payload
¶
Render this icon as its MCP icon dict, omitting unset optionals.
Transports, and the store that lets HTTP sessions outlive one worker.
register_http_transport
¶
register_http_transport(app: Any, server: MCPServer, path: str = '/mcp', auth: MCPAuth | None = None, allowed_origins: frozenset[str] | None = None, exclude_middleware: Sequence[str] | None = None, sessions: bool = False, resumable: bool = False, session_backend: SessionBackend | None = None) -> None
Mount the Streamable HTTP transport for server at path on app.
When auth is given the endpoint becomes an OAuth 2.1 resource server: each
request is authenticated before dispatch, and the RFC 9728 protected-resource
metadata is served so a client can discover the authorization server.
allowed_origins enables Origin validation (DNS-rebinding defense).
exclude_middleware names app middleware the transport routes opt out of -
typically an app-wide auth middleware the transport's own auth replaces.
sessions opts into Mcp-Session-Id lifecycle: the server assigns a session
id on the initialize result, requires it on every later request (HTTP 400 if
missing, 404 once terminated), and accepts a DELETE to terminate it. The
default keeps the stateless behavior with no per-request session bookkeeping.
session_backend shares those sessions between workers: without one a session
lives in the process that minted it, so a request reaching another worker is
answered 404 and the client starts over.
resumable opts into SSE resumability: each streamed event carrying a payload
gets an id encoding its originating stream, the events are kept in a bounded
SSEEventStore, and a GET carrying Last-Event-ID replays only that stream's
missed events. The default keeps no event ids or history and answers a GET
405.
StdioTransport
¶
Drive an MCPServer over a line-delimited JSON byte stream.
Satisfies BidirectionalTransport: send writes one outbound JSON-RPC
message line (wired as the server's notification sink) and request issues a
server->client request, awaiting the client's correlated reply read by the
serve loop.
serve
async
¶
Read, dispatch, and reply line-by-line until the input closes.
A blank line is skipped; an unparseable line yields a JSON-RPC parse
error; a notification (no response) writes nothing; a reply to a pending
server->client request resolves it instead of dispatching. The loop ends
when read_line returns None (EOF).
Framing is newline-delimited per the MCP stdio transport spec
("messages are delimited by newlines, and MUST NOT contain embedded
newlines"). This is deliberate and correct: the MCP stdio transport
does NOT use LSP-style Content-Length: header framing - that belongs
to the Language Server Protocol, not MCP. One JSON line in, one JSON
line out. Do not "fix" this into header framing.
send
async
¶
Write one server-initiated JSON-RPC message line to the client.
request
async
¶
Issue a server->client request and await the client's correlated reply.
Sends the JSON-RPC request and awaits the future the serve loop settles
when the correlated reply arrives. Returns the reply's result; an error
reply raises MCPRequestError.
This does not read the stream itself. It used to, which made the serve loop and the calling handler two readers of one blocking stream - so it had to be refused from a task-augmented call, where both are live at once. The loop is now the sole reader, so there is nothing to refuse and a task-augmented tool may sample, elicit and list roots like any other.
serve_stdio
async
¶
serve_stdio(server: MCPServer) -> None
Serve server over the real process stdin / stdout.
Blocking stdin reads are offloaded to the default thread executor so the event loop stays responsive; stdout writes are flushed per line so a client reading the pipe sees each response immediately.
SessionBackend
¶
Bases: Protocol
Where session records live when more than one worker serves a client.
The methods are async because a shared backend is I/O - a round trip to Redis or a database - and a blocking call would stall the worker's event loop.
Usage::
class RedisSessions:
def __init__(self, client):
self._client = client
async def read(self, session_id):
raw = await self._client.get(f"mcp:{session_id}")
return None if raw is None else SessionRecord(**json.loads(raw))
async def write(self, session_id, record, ttl):
await self._client.set(
f"mcp:{session_id}", json.dumps(asdict(record)), ex=int(ttl)
)
async def delete(self, session_id):
await self._client.delete(f"mcp:{session_id}")
app.mount_mcp(transport="http", sessions=True, session_backend=RedisSessions(redis))
read
async
¶
read(session_id: str) -> SessionRecord | None
Return the record for session_id, or None if it is not live.
write
async
¶
write(session_id: str, record: SessionRecord, ttl: float) -> None
Store record under session_id, expiring it after ttl idle seconds.
SessionRecord
dataclass
¶
What a session id means, independently of the worker serving it.
This is the whole of what a shared backend stores: the lifecycle flag and the identity the client declared. Everything else a session owns is bound to one worker's connection and is rebuilt there.
Authorization: validating a bearer token on the resource server, and issuing one from an authorization server of your own.
MCPAuth
dataclass
¶
OAuth 2.1 Resource Server configuration for the MCP HTTP transport.
Usage::
app.mount_mcp(transport="http", auth=MCPAuth(
verify=my_token_verifier, # str token -> Principal | None
required_scopes=["mcp:tools"],
resource_server_url="https://api.example.com/mcp",
authorization_servers=["https://auth.example.com"],
))
MCPAuthorizationServer
dataclass
¶
An OAuth 2.1 authorization server for MCP clients.
Usage::
from veloce.contrib.mcp import (
MCPAuth, MCPAuthorizationServer, register_authorization_server,
)
def authenticate(request):
user = request.session.get("user")
if user is None:
return RedirectResponse(f"/login?next={request.url}")
return Principal(subject=user, scopes={"mcp:tools"})
authorization = MCPAuthorizationServer(
issuer="https://api.example.com",
authenticate=authenticate,
scopes_supported=["mcp:tools"],
)
register_authorization_server(app, authorization)
app.mount_mcp(transport="http", auth=MCPAuth(
verify=authorization.verifier(),
resource_server_url="https://api.example.com/mcp",
authorization_servers=["https://api.example.com"],
))
register_authorization_server
¶
register_authorization_server(app: Any, server: MCPAuthorizationServer, prefix: str = '', exclude_middleware: Sequence[str] | None = None) -> None
Mount server's OAuth endpoints on app.
Registers the RFC 8414 metadata document, /authorize, /token, and - when
dynamic registration is on - /register. prefix mounts them under a path
segment; the metadata document advertises whatever the issuer says, so the
prefix and the issuer must agree.
AuthorizationStore
¶
Bases: Protocol
Where issued clients, codes and tokens live.
Codes and tokens are keyed by digest, never by the credential, so a store that
leaks yields nothing usable. take_code is single-use by contract: it must
return a code at most once, so a replayed code finds nothing.
get_client
async
¶
get_client(client_id: str) -> OAuthClient | None
Return the registered client, or None.
save_code
async
¶
save_code(code_digest: str, code: AuthorizationCode) -> None
Record an issued authorization code under its digest.
take_code
async
¶
take_code(code_digest: str) -> AuthorizationCode | None
Return and remove the code, so a second redemption finds nothing.
save_token
async
¶
save_token(token_digest: str, token: AccessToken) -> None
Record an issued access token under its digest.
get_token
async
¶
get_token(token_digest: str) -> AccessToken | None
Return the token recorded under token_digest, or None.
delete_token
async
¶
Drop a token, whether or not it was present.
take_refresh
async
¶
take_refresh(refresh_digest: str) -> tuple[str, AccessToken] | None
Return and remove the (token_digest, token) a refresh token replaces.
InMemoryAuthorizationStore
¶
A single-process store, for development and for tests.
Everything is lost on restart, and nothing is shared between workers. A
deployment that survives either needs its own AuthorizationStore.
OAuthClient
dataclass
¶
A registered client: who may redirect where, and how it authenticates.
AuthorizationCode
dataclass
¶
One issued code, bound to everything it was issued for.
AccessToken
dataclass
¶
One issued token: who it is for, what it may do, and until when.
Errors. A handler raising one of these surfaces it to the client as the JSON-RPC error carrying its code; anything else a tool raises is reported in-band as an isError result.
MCPError
¶
Bases: Exception
Base for any MCP failure that maps to a JSON-RPC error object.
to_error
¶
Render this error as a JSON-RPC 2.0 error response.
InvalidRequestError
¶
MethodNotFoundError
¶
InvalidParamsError
¶
InternalError
¶
ResourceNotFoundError
¶
AuthorizationError
¶
MCPCapabilityError
¶
Bases: InvalidRequestError, _InBandError
A server->client request needs a client capability the client did not advertise.
Raised by MCPContext.sample / elicit / roots (and their sub-capabilities)
when the connected client did not advertise the matching capability in
initialize, so the request cannot be issued.
In-band on the tool path: the caller's own request was well formed, and what
failed is something the tool tried to do while running it. The model can act
on that - carry on without the sampled text, ask its user directly - which it
cannot do with a JSON-RPC error. Resources and prompts have no in-band
channel, so the same failure travels there as the -32600 its base carries.
to_error
¶
Render this error as a JSON-RPC 2.0 error response.
OriginNotAllowedError
¶
Bases: InvalidRequestError
A request carries an Origin header outside the configured allowlist.
Per the MCP transport's DNS-rebinding defense the server MUST reject a
present, disallowed Origin with HTTP 403 (a missing Origin, a non-browser
client, is allowed).
to_error
¶
Render this error as a JSON-RPC 2.0 error response.
ProtocolVersionError
¶
Bases: InvalidRequestError
The HTTP MCP-Protocol-Version header names a version this server rejects.
Per the MCP 2025-06-18 Streamable HTTP transport the server MUST answer a request carrying an invalid or unsupported protocol version with HTTP 400.
to_error
¶
Render this error as a JSON-RPC 2.0 error response.
SessionRequiredError
¶
Bases: InvalidRequestError
A post-initialization request omitted the required Mcp-Session-Id header.
Per the MCP 2025-06-18 Streamable HTTP transport, once the server has assigned a session id the client MUST echo it on every later request; a request missing it is answered HTTP 400.
to_error
¶
Render this error as a JSON-RPC 2.0 error response.
SessionNotFoundError
¶
Bases: MCPError
A request named an Mcp-Session-Id the server no longer recognizes.
Per the MCP 2025-06-18 Streamable HTTP transport a terminated (or never issued) session is answered HTTP 404, signalling the client to start a new session.
to_error
¶
Render this error as a JSON-RPC 2.0 error response.
MCPRequestError
¶
Bases: Exception
A server->client request failed (the client replied with an error or closed).