Skip to content

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

cancelled: bool

Whether the client has sent notifications/cancelled for this call.

state property

state: Any

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

session_id: str | None

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

client_info: dict[str, Any]

The client's implementation block from initialize, or empty.

client_capabilities property

client_capabilities: dict[str, Any]

The capabilities the client advertised, or empty off a stateful transport.

is_background_task property

is_background_task: bool

Whether this call is running as a task rather than inline.

request_meta property

request_meta: dict[str, Any]

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

client_id: str | None

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

request_id: Any

The JSON-RPC id of the call being served, or None for a notification.

origin_request_id property

origin_request_id: Any

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

task_id: str | None

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

transport: str | None

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

lifespan_context: Any

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

result_meta: dict[str, Any]

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

client_supports(capability: str) -> bool

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

debug(message: Any, logger: str | None = None) -> None

Send a debug-level log message to the client.

info async

info(message: Any, logger: str | None = None) -> None

Send an info-level log message to the client.

warning async

warning(message: Any, logger: str | None = None) -> None

Send a warning-level log message to the client.

error async

error(message: Any, logger: str | None = None) -> None

Send an error-level log message to the client.

log async

log(level: str, message: Any, logger: str | None = None) -> None

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(progress: float, total: float | None = None, message: str | None = None) -> None

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_resource(uri: str) -> dict[str, Any]

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

get_prompt(name: str, arguments: dict[str, Any] | None = None) -> dict[str, Any]

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_resources() -> list[dict[str, Any]]

List this server's registered resources, as resources/list reports them.

Routed through the one listing builder rather than a private twin, so a primitive this connection hid with hide() is absent here too - the twin applied scope narrowing and not the hidden set, so a handler enumerating the catalogue contradicted what the client's own listing showed. Unpaged: a handler cannot ask again for the next page.

list_prompts

list_prompts() -> list[dict[str, Any]]

List this server's registered prompts, as prompts/list reports them.

Hidden-aware and unpaged, for the reasons list_resources gives.

send_notification async

send_notification(method: str, params: dict[str, Any] | None = None) -> None

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

roots() -> list[dict[str, Any]]

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(*names: str) -> None

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.

unhide async

unhide(*names: str) -> None

Show primitives hidden earlier on this connection.

reset_visibility async

reset_visibility() -> None

Show everything this connection had hidden.

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

public_id: str

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_initialize(params: dict[str, Any]) -> None

Record the client's advertised capabilities and info from initialize.

record_request_meta

record_request_meta(meta: dict[str, Any] | None) -> None

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

supports(capability: str) -> bool

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.

capabilities property

capabilities: tuple[Capability, ...]

The capabilities this server was built with, in registration order.

What a capability owns - its methods, which of them are handshake-only, and how it advertises itself per protocol revision - is readable from each entry. Asking what a server actually supports otherwise means reading a private tuple, and an out-of-tree tool auditing an MCP mount has the same question a test does.

set_notifier staticmethod

set_notifier(notifier: Callable[[dict[str, Any]], Awaitable[None]]) -> None

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

set_requester(requester: Callable[[str, dict[str, Any]], Awaitable[dict[str, Any]]]) -> None

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

current_request_id() -> Any

Return the JSON-RPC id being dispatched, or None for a notification.

send_to_current_connection async staticmethod

send_to_current_connection(message: dict[str, Any]) -> None

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

unregister_connection(token: object | None) -> None

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

notify_resource_updated(uri: str) -> None

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

notify_resources_list_changed() -> None

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

notify_tools_list_changed() -> None

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

notify_prompts_list_changed() -> None

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

connection_is_stateful() -> bool

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.

connection_can_stream

connection_can_stream(session: MCPSession) -> bool

Whether session holds an open outbound stream to push messages down.

Weaker than connection_is_stateful and deliberately so: a stream that lives only for the request that opened it still delivers, which is the whole shape of a modern subscriptions/listen.

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

describe() -> dict[str, Any]

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.

The era is read from the message being answered rather than passed in: creation and polling supplied it while cancellation and the status notification did not, so one task reported ttlMs on the way in and ttl on the way out.

touch

touch() -> None

Record that the task changed without altering its status.

is_terminal

is_terminal() -> bool

Return whether the task has settled (no further transition happens).

settle

settle(status: str, result: dict[str, Any], message: str | None = None) -> None

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

register(item: T) -> None

Add item, rejecting a key already present with a primitive-specific error.

get

get(name: str) -> T | None

Return the primitive registered under name, or None.

add

add(tool: MCPTool) -> None

Register tool, rejecting a name already taken by the same version.

Two registrations sharing a name and declaring different versions are both kept: the higher one is listed and answers a call naming no version, and either answers a call naming its own.

resolve

resolve(name: str, version: str | None) -> MCPTool | None

Return the tool name registered under version, or the listed one.

ResourceRegistry dataclass

Bases: Registry[MCPResource]

URI -> MCPResource, plus the shared JSON Schema component registry.

register

register(item: T) -> None

Add item, rejecting a key already present with a primitive-specific error.

get

get(name: str) -> T | None

Return the primitive registered under name, or None.

add

add(resource: MCPResource) -> None

Register resource, rejecting a URI already taken.

statics

statics() -> list[MCPResource]

Return the concrete-URI resources (for resources/list).

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.

register

register(item: T) -> None

Add item, rejecting a key already present with a primitive-specific error.

get

get(name: str) -> T | None

Return the primitive registered under name, or None.

add

add(prompt: MCPPrompt) -> None

Register prompt, rejecting a name already taken.

TaskRegistry dataclass

Bases: Registry[MCPTask]

Task id -> MCPTask, the in-memory store of created tasks.

register

register(item: T) -> None

Add item, rejecting a key already present with a primitive-specific error.

get

get(name: str) -> T | None

Return the primitive registered under name, or None.

evict_expired

evict_expired() -> None

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.

owned_by

owned_by(owner_key: int | None) -> list[MCPTask]

Return the tasks created by the given connection.

Used to reclaim a session's tasks when it is evicted so a never-settling task does not pin memory for the process lifetime after its owner is gone.

drop

drop(task: MCPTask) -> None

Remove a task from the store, settled or not (used on owner eviction).

The seam an out-of-tree spec area implements against: subclass Capability, annotate the handler map with MethodHandler, and pass an instance to MCPServer(capabilities=[...]).

Capability

One MCP spec area: its initialize advertisement and its method handlers.

advertise

advertise(*, modern: bool = False) -> dict[str, Any] | None

Return this capability's entry for the initialize capabilities object.

A single-key mapping (e.g. {"tools": {"listChanged": False}}) merged into the advertised capabilities, or None when the app exposes nothing for this area so the client does not probe an empty primitive.

Declare a modern keyword parameter to vary the entry by protocol revision - the server passes it when the signature accepts it, so a capability whose methods a revision retired can withhold or narrow what it advertises rather than promising something the dispatcher refuses.

handlers

handlers() -> dict[str, MethodHandler]

Return the {json_rpc_method: handler} map this capability answers.

extensions

extensions() -> dict[str, Any] | None

Return this capability's entry for the advertised extensions object.

None - the default - means this capability contributes no extension, which is true of most of them. A capability contributes an entry only when the feature it names is actually available, so a server with no task-capable tool advertises no tasks extension and a client will never offer one.

Declared here rather than resolved by getattr on the server side, which would leave the third member of the contract invisible to anyone reading the base class - and this class is the documented seam an out-of-tree capability implements against.

MethodHandler module-attribute

MethodHandler = Callable[[dict[str, Any]], Awaitable[dict[str, Any] | None]]

ToolsCapability

Bases: _ServerCapability

The tools/list and tools/call methods, always advertised.

extensions

extensions() -> dict[str, Any] | None

Return this capability's entry for the advertised extensions object.

None - the default - means this capability contributes no extension, which is true of most of them. A capability contributes an entry only when the feature it names is actually available, so a server with no task-capable tool advertises no tasks extension and a client will never offer one.

Declared here rather than resolved by getattr on the server side, which would leave the third member of the contract invisible to anyone reading the base class - and this class is the documented seam an out-of-tree capability implements against.

ResourcesCapability

Bases: _ServerCapability

The resource methods, advertised only when the app exposes a resource.

extensions

extensions() -> dict[str, Any] | None

Return this capability's entry for the advertised extensions object.

None - the default - means this capability contributes no extension, which is true of most of them. A capability contributes an entry only when the feature it names is actually available, so a server with no task-capable tool advertises no tasks extension and a client will never offer one.

Declared here rather than resolved by getattr on the server side, which would leave the third member of the contract invisible to anyone reading the base class - and this class is the documented seam an out-of-tree capability implements against.

PromptsCapability

Bases: _ServerCapability

The prompt methods, advertised only when the app exposes a prompt.

extensions

extensions() -> dict[str, Any] | None

Return this capability's entry for the advertised extensions object.

None - the default - means this capability contributes no extension, which is true of most of them. A capability contributes an entry only when the feature it names is actually available, so a server with no task-capable tool advertises no tasks extension and a client will never offer one.

Declared here rather than resolved by getattr on the server side, which would leave the third member of the contract invisible to anyone reading the base class - and this class is the documented seam an out-of-tree capability implements against.

LoggingCapability

Bases: _ServerCapability

The logging/setLevel method, advertised on the revisions that have it.

Any tool may emit a log message through MCPContext.log, and a handshake-era client may raise the minimum level once per connection. The modern revision removed the method - a client sets its level per request in _meta - so the capability is withheld there rather than advertised and then refused.

extensions

extensions() -> dict[str, Any] | None

Return this capability's entry for the advertised extensions object.

None - the default - means this capability contributes no extension, which is true of most of them. A capability contributes an entry only when the feature it names is actually available, so a server with no task-capable tool advertises no tasks extension and a client will never offer one.

Declared here rather than resolved by getattr on the server side, which would leave the third member of the contract invisible to anyone reading the base class - and this class is the documented seam an out-of-tree capability implements against.

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

extensions() -> dict[str, Any] | None

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.

extensions

extensions() -> dict[str, Any] | None

Return this capability's entry for the advertised extensions object.

None - the default - means this capability contributes no extension, which is true of most of them. A capability contributes an entry only when the feature it names is actually available, so a server with no task-capable tool advertises no tasks extension and a client will never offer one.

Declared here rather than resolved by getattr on the server side, which would leave the third member of the contract invisible to anyone reading the base class - and this class is the documented seam an out-of-tree capability implements against.

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.

extensions

extensions() -> dict[str, Any] | None

Return this capability's entry for the advertised extensions object.

None - the default - means this capability contributes no extension, which is true of most of them. A capability contributes an entry only when the feature it names is actually available, so a server with no task-capable tool advertises no tasks extension and a client will never offer one.

Declared here rather than resolved by getattr on the server side, which would leave the third member of the contract invisible to anyone reading the base class - and this class is the documented seam an out-of-tree capability implements against.

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, siblings: dict[str, str]) -> CompletionResult:
    matches = await lookup_names(prefix=value, scope=siblings)
    return CompletionResult(matches[:100], total=len(matches))

A completer is always called with two arguments - the partial value, and the sibling argument values the client has already filled - so it must declare both. Declaring only the value raises at call time and surfaces as an internal error.

ToolFilter module-attribute

ToolFilter = Callable[['MCPTool', Any], 'bool | Awaitable[bool]']

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

has_default: bool

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.

text property

text: str

Return the final answer's text blocks, joined by newlines.

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

to_payload() -> dict[str, Any]

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

to_payload() -> dict[str, Any]

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

to_payload() -> dict[str, Any]

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

to_payload() -> dict[str, Any]

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

to_payload() -> dict[str, Any]

Render this block as its MCP content-block dict, merging annotations.

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

to_payload() -> dict[str, Any]

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

to_payload() -> dict[str, Any]

Render this icon as its MCP icon dict, omitting unset optionals.

Transports, and the store that lets HTTP sessions outlive one worker.

Transport

Bases: Protocol

Carry one-way JSON-RPC messages from the server to the client.

send async

send(message: dict[str, Any]) -> None

Write one JSON-RPC message (a notification or a response) to the client.

BidirectionalTransport

Bases: Transport, Protocol

A transport that can also issue server->client requests and await replies.

send async

send(message: dict[str, Any]) -> None

Write one JSON-RPC message (a notification or a response) to the client.

request async

request(method: str, params: dict[str, Any]) -> dict[str, Any]

Issue a server->client request and await its correlated result.

The transport owns id minting and correlation, so it takes the method and params (not a pre-built message) and returns the reply's result.

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.

register_sse_transport

register_sse_transport(app: Any, server: MCPServer, path: str = '/sse', message_path: str = '/messages', auth: MCPAuth | None = None, allowed_origins: frozenset[str] | None = None, exclude_middleware: Sequence[str] | None = None) -> None

Mount the legacy split-endpoint SSE transport for server on app.

path opens the stream; message_path receives the client's messages. The client is told the second by the first, so the two only have to agree here.

auth makes both endpoints an OAuth 2.1 resource server, and allowed_origins enables Origin validation (DNS-rebinding defense) - the same options the Streamable HTTP transport takes, since both are ordinary HTTP endpoints on this app.

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

serve() -> None

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

send(message: dict[str, Any]) -> None

Write one server-initiated JSON-RPC message line to the client.

request async

request(method: str, params: dict[str, Any]) -> dict[str, Any]

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: the serve loop is the sole reader. Reading here would make the loop and the calling handler two readers of one blocking stream, which cannot both be live - and both are live during a task-augmented call. Because there is only one reader, 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.

The wire is isolated from the rest of the process for the duration: the protocol is carried on private duplicates of descriptors 0 and 1 while the standard ones point at the null device and at stderr, so a handler that prints, logs to stdout or spawns a child cannot corrupt the JSON-RPC stream.

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.

delete async

delete(session_id: str) -> None

Drop session_id, whether or not it was live.

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"],
))

metadata

metadata() -> dict[str, object]

Build the RFC 9728 protected-resource metadata document.

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"],
))

metadata

metadata() -> dict[str, Any]

Build the RFC 8414 authorization server metadata document.

verifier

verifier(*, resource: str | None = None) -> Callable[[str], Awaitable[Principal | None]]

Return the token verifier to hand MCPAuth(verify=...).

Resolves an opaque token to its Principal, refusing one that has expired or that was minted for a different resource.

resource is this server's canonical URI - the same value passed as MCPAuth(resource_server_url=...). Give it, and a token whose RFC 8707 resource names a different server is refused: that is the audience binding, and it is what stops a token obtained for one MCP server being replayed against another sharing the authorization server.

Omitting it leaves audience binding off, and says so: the check cannot be performed without knowing which server this is, and silently accepting another server's token is the failure the parameter exists to prevent.

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.

save_client async

save_client(client: OAuthClient) -> None

Record a newly registered client.

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

delete_token(token_digest: str) -> None

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.

family_of_spent_refresh async

family_of_spent_refresh(refresh_digest: str) -> str | None

Return the family a already-spent refresh token belonged to.

revoke_family async

revoke_family(family_id: str) -> int

Drop every token descended from one authorization. Returns the count.

OAuthClient dataclass

A registered client: who may redirect where, and how it authenticates.

is_public property

is_public: bool

Whether this client has no secret and relies on PKCE.

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

to_error(msg_id: Any) -> dict[str, Any]

Render this error as a JSON-RPC 2.0 error response.

InvalidRequestError

Bases: MCPError

A malformed JSON-RPC request object.

to_error

to_error(msg_id: Any) -> dict[str, Any]

Render this error as a JSON-RPC 2.0 error response.

MethodNotFoundError

Bases: MCPError

A request named a method this server does not implement.

to_error

to_error(msg_id: Any) -> dict[str, Any]

Render this error as a JSON-RPC 2.0 error response.

InvalidParamsError

Bases: MCPError

A call's params are missing, mistyped, or fail validation.

to_error

to_error(msg_id: Any) -> dict[str, Any]

Render this error as a JSON-RPC 2.0 error response.

InternalError

Bases: MCPError

An unexpected server-side failure handling the request.

to_error

to_error(msg_id: Any) -> dict[str, Any]

Render this error as a JSON-RPC 2.0 error response.

ResourceNotFoundError

Bases: MCPError

A resources/read named a URI the server cannot resolve.

to_error

to_error(msg_id: Any) -> dict[str, Any]

Render this error as a JSON-RPC 2.0 error response.

AuthorizationError

Bases: MCPError

The principal lacks a required scope — reported as a forbidden error.

Carries the required scopes so the HTTP transport can surface them in an RFC 6750 WWW-Authenticate insufficient-scope challenge.

to_error

to_error(msg_id: Any) -> dict[str, Any]

Render this error as a JSON-RPC 2.0 error response.

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

to_error(msg_id: Any) -> dict[str, Any]

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

to_error(msg_id: Any) -> dict[str, Any]

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

to_error(msg_id: Any) -> dict[str, Any]

Render this error as a JSON-RPC 2.0 error response.

HeaderMismatchError

Bases: MCPError

A standard request header disagrees with the body it labels.

A fronting intermediary routes on Mcp-Method / Mcp-Name while the server executes the JSON-RPC body, so a divergence between the two is a request smuggling primitive rather than a formatting nit: the two ends of the hop would act on different requests. The transport rejects it before dispatch (MCP 2026-07-28 Streamable HTTP, "Server Validation"), which also covers a required header that is absent and a value carrying characters the header encoding does not permit.

to_error

to_error(msg_id: Any) -> dict[str, Any]

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

to_error(msg_id: Any) -> dict[str, Any]

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

to_error(msg_id: Any) -> dict[str, Any]

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).

JSON_SCHEMA_DIALECT module-attribute

JSON_SCHEMA_DIALECT = 'https://json-schema.org/draft/2020-12/schema'