McpServer

The transport-agnostic core of an MCP server.

McpServer owns registration and JSON-RPC dispatch. It has no I/O: feed it parsed messages via handle (or raw text via handleRaw) and it returns the response to write back. Transports (stdio, HTTP) are thin drivers over this.

@safe final
class McpServer {}

Constructors

this
this(Implementation serverInfo, Nullable!string instructions)

Construct a server from a fully-populated Implementation, letting the author advertise a display title (>= 2025-06-18) plus description, websiteUrl, and icons (>= 2025-11-25) in the initialize / server/discover serverInfo. Fields newer than the negotiated protocol version are stripped from the wire response (see Implementation.forVersion), so older peers see only what they understand. Mirrors the client's full Implementation clientInfo support.

Members

Functions

acknowledgedSubsetFor
Json acknowledgedSubsetFor(SubscriptionFilter f)

The acknowledged subset for a single subscriptions/listen request's filter, built from exactly that one stream's opt-in (draft basic/utilities/subscriptions Acknowledgment). Each subscription is independent — identified by its own listen request id (§Multiple Concurrent Subscriptions) — so the ack a transport sends as the first event on a stream MUST reflect only THAT request's opt-in, never the server-wide accumulation across other (or already-closed) concurrent streams. The three list-changed types appear as booleans ({ "<type>": true }) and resourceSubscriptions as the agreed string[] of URIs; an empty object when the filter opted into nothing.

advertiseExtension
void advertiseExtension(string identifier, Json settings)

Advertise a draft protocol extension (e.g. "io.modelcontextprotocol/tasks") with an optional per-extension settings object. The identifier and its settings appear in the extensions field of the server capabilities sent during initialize / server/discover, per the draft Extension Negotiation rules. settings defaults to an empty object.

bindConnection
void bindConnection(ConnectionState conn)

Let a transport point this server at the ConnectionState it owns for a mount/connection at wire-up (mountMcp / runStdio / serveStdio). Used by the notify/push path, which fires OUTSIDE any request and therefore cannot receive the state as a dispatch argument. This sets the fallback activeConnection; HTTP requests still resolve their own per-session state.

capabilities
ServerCapabilities capabilities()

Capabilities this server advertises, derived from what is registered.

clientCapabilities
ClientCapabilities clientCapabilities()

The capabilities advertised by the connected client (valid after initialize).

clientExtensions
Json clientExtensions()

The extension identifiers and settings the connected client advertised (valid after initialize). Json.undefined if the client advertised none.

clientTasks
Nullable!TasksCapability clientTasks()

The tasks capability the connected client advertised (valid after initialize). Null if the client advertised none.

connectedSessions
string[] connectedSessions()

The session tokens (Mcp-Session-Ids) that currently have a connected GET SSE stream, so a caller can pingClient(token) each live session in turn rather than guessing. Empty when there is no push channel or no session-scoped GET stream is open.

currentLogLevel
string currentLogLevel()

The most recently set log level (default "info").

disableInputSchemaValidation
void disableInputSchemaValidation()

Opt out of the default input-schema validation, so tool arguments are passed to handlers unchecked. Discouraged — the spec says servers MUST validate tool inputs — but available for handlers that perform their own validation or intentionally accept arbitrary arguments.

enableInputSchemaValidation
void enableInputSchemaValidation()

Validate each tool call's arguments against the tool's registered inputSchema before the handler is invoked. Per the spec (server/tools § Security Considerations, all versions), "Servers MUST: Validate all tool inputs", so this is **on by default**. § Error Handling classifies an inputSchema violation (missing required property or wrong type) as an *input-validation* error, i.e. a Tool Execution Error: a tools/call whose arguments do not conform yields a CallToolResult with isError:true and a descriptive text content block (so the model can self-correct), NOT a JSON-RPC -32602 protocol error. Tools without an inputSchema are unaffected. This method is retained for explicitness and to re-enable validation after disableInputSchemaValidation.

enableLogging
void enableLogging()

Advertise the logging capability and accept logging/setLevel.

enableOutputSchemaValidation
void enableOutputSchemaValidation()

Opt in to enforcing each tool's declared outputSchema before the result is sent. Per the spec, "If an output schema is provided: Servers MUST provide structured results that conform to this schema." With validation enabled, both halves of that MUST are enforced: a successful result for a tool that declares an outputSchema must (a) carry structuredContent and (b) have it conform to the schema. A handler that omits structuredContent or emits non-conforming structuredContent surfaces a clear internal error (so the bug is caught at the server) rather than silently shipping bad output. Tools without an outputSchema are unaffected; tool-execution errors (isError:true) and MRTR InputRequiredResults are exempt (the MUST governs successful structured results). Off by default to preserve existing behaviour.

enablePromptsListChanged
void enablePromptsListChanged()

Advertise the prompts listChanged capability so capabilities() emits prompts: { listChanged: true }. Declare this (before initialize / server/discover) when the server may add or remove prompts at runtime and will emit notifications/prompts/list_changed via notifyPromptsListChanged.

enableResourceSubscriptions
void enableResourceSubscriptions()

Advertise the resources subscribe capability and accept resources/subscribe + resources/unsubscribe.

enableResourcesListChanged
void enableResourcesListChanged()

Advertise the resources listChanged capability so capabilities() emits resources: { listChanged: true }. Declare this (before initialize / server/discover) when the server may add or remove resources or resource templates at runtime and will emit notifications/resources/list_changed via notifyResourcesListChanged.

enableTasks
void enableTasks(bool list, bool cancel, Json requests)

Advertise the 2025-11-25 tasks capability, i.e. support for task-augmented requests. list/cancel indicate support for tasks/list and tasks/cancel; requests is the nested-by-category object describing which requests may be task-augmented. Its spec shape is the nested form {"tools": {"call": {}}}, NOT a flat "tools/call" key. Build it with TaskRequests, for example enableTasks(true, true, TaskRequests().tool().toJson()). The capability appears in the tasks field of the server capabilities sent during initialize / server/discover.

enableToolsListChanged
void enableToolsListChanged()

Advertise the tools listChanged capability so capabilities() emits tools: { listChanged: true }. Declare this (before initialize / server/discover) when the server may add or remove tools at runtime and will emit notifications/tools/list_changed via notifyToolsListChanged.

handle
Nullable!Json handle(Message msg, RequestContext ctx)

Dispatch a single parsed message. Returns the JSON-RPC response for requests, or Nullable.init for notifications (which get no reply). ctx is the channel for any server->client traffic the handler emits; when omitted, a NullContext is used (no streaming).

handle
Nullable!Json handle(Message msg)

Convenience overload using a NullContext (no server->client channel).

handleRaw
string handleRaw(string text)

Process a raw wire payload (single message or batch) and return the raw response text, or empty string when there is nothing to send back (e.g. a notification, or an all-notification batch). Parse/envelope failures become JSON-RPC error responses with a null id.

handleRaw
string handleRaw(string text, ConnectionState conn)

As handleRaw, but dispatched against an explicit per-request ConnectionState (for the Streamable HTTP batch back-compat path, which resolves the request's session/per-request state in the transport). The batch version gate and per-message dispatch both consult conn instead of the single bound activeConnection, so a session that negotiated 2025-03-26 can actually reach the legacy batch path and a modern session is gated on its own negotiated version. null falls back to the no-arg behaviour.

handleRaw
string handleRaw(string text, void delegate(string) @(safe) sink)

As handleRaw, but with a server->client write sink for transports (such as stdio) that can deliver out-of-band frames on the same channel. Each message is dispatched with a StdioContext bound to sink, so a handler's ctx.log() / ctx.reportProgress() are serialised and pushed to sink as they happen — before the request's reply, which is still returned as the string result. When sink is null a NullContext is used (no streaming), preserving the in-process behaviour of the no-argument overload.

handleRaw
string handleRaw(string text, void delegate(string) @(safe) sink, Json delegate(string, Json) @(safe) serverRequest)

As handleRaw(text, sink), but with the stdio server->client request channel: when a handler calls ctx.sendRequest (e.g. via ctx.sample/ctx.elicit) serverRequest(method, params) is invoked to write the request and block the current task for the client's reply (the DuplexChannel correlates it on its read loop). null reproduces the no-channel behaviour (server->client requests throw).

isSubscribed
bool isSubscribed(string uri)

Whether a client is currently subscribed to updates for uri.

lastListenFilter
SubscriptionFilter lastListenFilter()

The per-stream SubscriptionFilter parsed from the most recent subscriptions/listen request handled by this server. The transport reads it immediately after routing a listen request so it can attach the exact opt-in to that stream's push-channel listener (draft basic/utilities/subscriptions §Notification Filter), ensuring a notification is delivered only to a stream that explicitly requested its type.

mode
ServerMode mode()

The statefulness model this server was constructed with (default stateless). Transports derive session minting from this.

negotiatedVersion
ProtocolVersion negotiatedVersion()

The protocol version negotiated with the client (valid after initialize).

notify
size_t notify(string method, Json params)

Send an *unsolicited* JSON-RPC notification to every client currently listening on the standalone GET SSE stream. This is the public entry point for server-initiated traffic outside an in-flight request — e.g. a notifications/resources/updated for a subscribed resource, or a notifications/tools/list_changed. Returns the number of listeners the notification was delivered to; 0 when no GET stream is open (or the server is not on a Streamable HTTP transport). On a stdio server with an active draft subscriptions/listen it is additionally written to stdout (stamped with the listen subscriptionId), since that transport shares one channel for all server->client traffic.

notifyElicitationComplete
size_t notifyElicitationComplete(string elicitationId)

Emit a notifications/elicitation/complete for a URL-mode elicitation, telling the client an out-of-band interaction it was asked to complete (via RequestContext.elicitUrl) has finished, so the client can stop waiting on it (basic/utilities/elicitation §"Completion Notifications for URL Mode Elicitation"). Per spec the notification MUST carry the elicitationId that correlates it with the original request. It is delivered on the standalone GET SSE stream (the unsolicited server->client channel); returns the number of listeners reached, or 0 when no GET stream is open (or the server is not on a Streamable HTTP transport). Throws invalidParams on an empty elicitationId.

notifyPromptsListChanged
size_t notifyPromptsListChanged()

Broadcast a notifications/prompts/list_changed to every client listening on the standalone GET SSE stream, informing them the set of available prompts changed (per the server/prompts List Changed Notification). Returns the number of listeners reached; 0 when no GET stream is open. Call after a runtime registerDynamicPrompt (or a removal). For the draft protocol, the notification is suppressed unless a client opted in via subscriptions/listen with promptsListChanged:true.

notifyResourceUpdated
size_t notifyResourceUpdated(string uri)

Notify subscribers that a watched resource changed by emitting a notifications/resources/updated on the standalone GET SSE stream (per server/resources Subscriptions: "Server delivers notifications/resources/updated ... whenever a watched resource changes"). Per ResourceUpdatedNotificationParams in every spec version (2024-11-05 .. 2025-11-25 .. draft) the params carry exactly { "uri": ... } (plus the inherited optional _meta); there is no title field on this notification (a resource's title lives on the Resource object in resources/list). It is delivered only when a client is currently subscribed to uri (via resources/subscribe); for an unsubscribed URI it is a no-op returning 0. For the draft protocol the notification is additionally suppressed unless a client opted in via subscriptions/listen with resourceSubscriptions:true. Returns the number of GET-stream listeners reached; 0 when no GET stream is open.

notifyResourcesListChanged
size_t notifyResourcesListChanged()

Broadcast a notifications/resources/list_changed to every client listening on the standalone GET SSE stream, informing them the set of available resources changed (per the server/resources List Changed Notification). Returns the number of listeners reached; 0 when no GET stream is open. Call after a runtime registerResource / registerResourceTemplate (or a removal). For the draft protocol, the notification is suppressed unless a client opted in via subscriptions/listen with resourcesListChanged:true.

notifyToolsListChanged
size_t notifyToolsListChanged()

Broadcast a notifications/tools/list_changed to every client listening on the standalone GET SSE stream, informing them the set of available tools changed (per the server/tools List Changed Notification). Returns the number of listeners reached; 0 when no GET stream is open. Call after a runtime registerDynamicTool / removeTool. For the draft protocol, the notification is suppressed unless a client opted in via subscriptions/listen with toolsListChanged:true.

pingClient
void pingClient(Duration timeout)

Initiate a server->client ping on the standalone GET SSE push channel and block until a connected client acknowledges with the spec-mandated empty result (basic/utilities/ping: "Either the client or server can initiate a ping by sending a ping request"). This is the server-side counterpart to McpClient.ping(), exposing the SHOULD-periodic connection-health probe the spec describes for either party. The probe rides the same push channel notify uses, and the client's reply is correlated via the shared StreamCoordinator when it POSTs the response.

pingClient
void pingClient(string sessionId, Duration timeout)

Initiate a server->client ping on the GET SSE stream owned by the session sessionId (its Mcp-Session-Id). On a stateful HTTP server every per-session GET stream is registered under its session id as the listener's owner token, so the probe must be scoped to that token to reach the right stream — an empty token (the no-arg pingClient) matches no session-scoped listener and would always fail. The waiter is likewise scoped to sessionId, so only a response POSTed under the same session resolves it. Throws as the no-arg form does, plus when no GET stream is connected for that session.

registerDynamicPrompt
void registerDynamicPrompt(Prompt descriptor, GetPromptResult delegate(Json) @(safe) handler)

Register a *dynamic* prompt with the handler that produces its messages.

registerDynamicPrompt
void registerDynamicPrompt(Prompt descriptor, MrtrPromptHandler handler)

Register a *dynamic* prompt whose handler may, on a stateless (MRTR) draft request, ask the client for more input instead of returning a final result.

registerDynamicTool
void registerDynamicTool(Tool descriptor, ToolHandler handler)

Register a *dynamic* tool with a context-aware handler (progress / logging / sampling / elicitation available via ctx).

registerDynamicTool
void registerDynamicTool(Tool descriptor, CallToolResult delegate(Json) @(safe) handler)

Register a *dynamic* tool with a simple handler that ignores the request context. See registerDynamicTool(Tool, ToolHandler) for when to use the dynamic path versus the typed UDA layer.

registerDynamicTool
void registerDynamicTool(Tool descriptor, MrtrToolHandler handler)

Register a *dynamic* tool whose handler may ask the client for more input on a stateless (MRTR) request. The handler branches on ctx.isStateless: when stateless it reads ctx.inputResponses and returns either ToolResponse.complete or ToolResponse.inputRequired; otherwise it may call the blocking ctx.elicit/ctx.sample. A server that wants to serve both protocol eras handles both branches here.

registerResource
void registerResource(Resource descriptor, ResourceContents delegate() @(safe) reader, Nullable!CacheHint cache)

Register a direct resource with a reader for its contents. An optional per-resource draft CacheableResult freshness hint is emitted on this resource's resources/read response (draft protocol only).

registerResourceTemplate
void registerResourceTemplate(ResourceTemplate descriptor, ResourceContents delegate(string uri, string[string] params) @(safe) reader, Nullable!CacheHint cache)

Register a resource template with a reader receiving the matched URI and captured {var} parameters. An optional per-template draft CacheableResult freshness hint is emitted on a matching resources/read (draft only).

registerResourceTemplate
void registerResourceTemplate(ResourceTemplate descriptor, TemplateReader reader, Nullable!CacheHint cache)

Register a resource template whose reader also receives the per-request RequestContext, so a template handler can log, poll cancellation, or elicit through the real request channel. Otherwise identical to the context-less overload.

removeResource
bool removeResource(string uri)

Unregister a previously registered direct resource by URI. Returns true if a resource was removed, false if no resource with that URI was registered. The mirror of registerResource; pair with notifyResourcesListChanged to inform connected clients that the available resource set changed.

removeTool
bool removeTool(string name)

Unregister a previously registered tool by name. Returns true if a tool was removed, false if no tool with that name was registered. Pair with notifyToolsListChanged to inform connected clients that the tool list changed.

requireInitialized
void requireInitialized()

Enforce the lifecycle rule that the server SHOULD NOT respond to a request (other than ping) before the client has sent notifications/initialized. With this enabled, a stateful session that receives e.g. tools/list after the initialize response but before notifications/initialized is rejected with -32002. initialize and ping are always allowed; the stateless path (which has no per-session initialized handshake) is exempt. OFF by default — the rule is a SHOULD and well-behaved clients send initialized immediately.

secureRequestState
void secureRequestState(RequestStateSecurity sec)

Enable the opt-in secure codec for the MRTR (SEP-2322) requestState. Once enabled, the dispatch path transparently wraps every outgoing requestState (tool, prompt, and task input-required results) and verifies every echoed incoming one — handlers keep calling inputRequired!T / requestStateAs!T against plaintext. This delivers the three SEP-2322 protections at once: integrity (the client cannot tamper with the opaque state), expiry (ttl), and user-binding (the echoed state must belong to the currently authenticated subject, defending against replay/hijack).

serverPushChannel
ServerPushChannel serverPushChannel(StreamCoordinator coord)

The server->client push channel for *unsolicited* traffic — the messages a server sends on the standalone SSE stream a client opens with an HTTP GET to the MCP endpoint (basic/transports §Listening for Messages from the Server), outside any in-flight POST. The Streamable HTTP transport creates it (sharing the supplied StreamCoordinator) when the mount is set up; it is created lazily on first access so callers can hold a reference before mounting. Use notify (or the returned channel's emit) to deliver notifications/requests to every connected GET listener.

serverPushChannel
ServerPushChannel serverPushChannel()

The active server->client push channel, or null if none has been created (e.g. the server is not mounted on a Streamable HTTP transport).

sessionPushEligibility
bool delegate(string method, string uri) @(safe) sessionPushEligibility(ConnectionState conn)

Build the per-session plain-GET eligibility predicate a standalone GET stream registers with the push channel, bound to the session's resolved ConnectionState. The Streamable HTTP transport supplies this when opening the GET stream so notifications/resources/updated delivery consults the listener's own per-session subscriptions (keyed on Mcp-Session-Id) instead of the shared fallback connection.

setArgumentCompleter
void setArgumentCompleter(CompletionReference reference, string argumentName, string[] delegate(string prefix) @(safe) completer)

Register a completer for a single (reference, argumentName) pair, so a consumer no longer hand-routes every completable argument inside one global setCompletionRequestHandler delegate. The delegate receives the partial value typed so far and returns the candidate completions; the server wraps them in a CompleteResult (use CompleteResult.prefixMatch for the common prefix-matching case). Declaring any completer advertises the completions capability. completion/complete dispatch tries the global handler first (when set) and falls back to a matching per-argument completer, then to an empty CompleteResult.

setClientNotificationHandler
void setClientNotificationHandler(void delegate(string method, Json params) @(safe) handler)

Observe inbound client-originated notifications.

setCompletionRequestHandler
void setCompletionRequestHandler(CompleteResult delegate(CompleteRequest request) @(safe) handler)

Set the handler for completion/complete, receiving a parsed, typed CompleteRequest (the ref, the argument name/value, and any context.arguments) rather than raw Json. Declaring it advertises the completions capability. Use request.isPrompt / request.isResource to route to the appropriate per-target completer.

setListCacheHint
void setListCacheHint(string listMethod, CacheHint hint)

Configure the per-list draft CacheableResult freshness hint (ttlMs/cacheScope) emitted on a specific */list result when speaking the draft protocol. listMethod MUST be one of tools/list, resources/list, resources/templates/list, or prompts/list. Per-resource and per-template hints are supplied at registration time instead.

setPageSize
void setPageSize(size_t size)

Set the maximum number of items returned per */list page (server/utilities/pagination). When size > 0, tools/list, resources/list, resources/templates/list and prompts/list return at most size items per response and emit an opaque nextCursor whenever more results remain; the client passes that cursor back as params.cursor to fetch the next page (the bundled McpClient list helpers follow these cursors automatically). A size of 0 (the default) disables pagination: each list returns its full contents in a single response with no cursor.

setRootsListChangedHandler
void setRootsListChangedHandler(void delegate() @(safe) handler)

Convenience observer fired specifically on notifications/roots/list_changed. Invoked in addition to any handler registered via setClientNotificationHandler. Set to react to client root-list changes without inspecting the method string yourself.

setToolRequiredClientCapabilities
bool setToolRequiredClientCapabilities(string name, ClientCapabilities caps)

Declare the client capabilities a registered tool's handler requires.

toolInputSchema
Json toolInputSchema(string name)

The effective input schema of a registered tool (the default empty-object schema if none was provided), or Json.undefined if the tool is unknown. Used by the transport for draft x-mcp-header validation.

tryServeStdioListen
bool tryServeStdioListen(Message msg, void delegate(string) @(safe) writeLine)

If msg is a draft subscriptions/listen request, serve it on the stdio transport's single channel and return true; otherwise return false (the caller dispatches it normally). On the stdio transport every message shares one stdout channel, so — unlike Streamable HTTP — there is no separate SSE stream to open. Per the draft, a subscriptions/listen reply is NOT a { acknowledged: true } JSON-RPC result (the schema defines no such Result); the acknowledgement is a notifications/subscriptions/acknowledged notification that MUST be the first message on the stream. This records the opted-in change-notification filters, installs writeLine as the delivery sink (so subsequent notify*/notifyResourceUpdated are written to stdout, each stamped with the listen id as io.modelcontextprotocol/subscriptionId), and writes the stamped acknowledgement as that leading message. Pre-draft versions never defined subscriptions/listen, so they take the normal path (returns false) and the request is answered conventionally.

Static functions

stateful
McpServer stateful(string name, string version_, Nullable!string instructions)

Construct a stateful server (opt-in, pre-draft only). initialize mints an Mcp-Session-Id and creates per-session state; the draft is excluded from negotiation; the full feature set (elicitation, GET stream, subscriptions, logging/setLevel) is available, all session-isolated.

stateful
McpServer stateful(Implementation serverInfo, Nullable!string instructions)

As stateful(string, string, ...), from a full Implementation.

stateless
McpServer stateless(string name, string version_, Nullable!string instructions)

Construct a stateless server (the default mode). On the draft protocol this is modern stateless (per-request _meta, MRTR); on pre-draft it is legacy stateless (no-op initialize, no session id, correlation features error). No Mcp-Session-Id is ever minted. See the README "Statefulness" section.

stateless
McpServer stateless(Implementation serverInfo, Nullable!string instructions)

As stateless(string, string, ...), from a full Implementation.