Architecture | Project Structure | Startup Process | Server Features | Extension Points | How It Works
The server serves two protocol eras from one process, over stdio and over Streamable HTTP. A client's first message picks its era:
- 2026-07-28 (
modern): the client opens withserver/discover, with noinitializeand no session, and sends its protocol version, identity and capabilities in each request's_meta. - 2025-era (
legacy, 2024-10-07 through 2025-11-25): the client opens with theinitializehandshake. The HTTP+SSE transport serves this era only.
Each feature below is marked both, 2026-07-28 or 2025-era. Roots, sampling and logging are deprecated as of 2026-07-28 (SEP-2577), as is the HTTP+SSE transport; they stay available and are labelled deprecated in their descriptions.
| Feature | 2026-07-28 | 2025-era |
|---|---|---|
| Discovery | server/discover |
initialize |
| Client input (elicitation, sampling, roots) | in-band: the tool returns input_required, the client retries |
the SDK's legacy shim sends real server-to-client requests for the same handler |
| State across rounds | requestState, signed and expiring |
the same, replayed by the shim |
| Change notifications | on a subscriptions/listen stream the client opened |
unsolicited, or after resources/subscribe |
| Log level | per request, _meta io.modelcontextprotocol/logLevel (none named: no logs) |
per session, logging/setLevel |
| Tasks | the io.modelcontextprotocol/tasks extension |
not served |
| Cache hints | ttlMs / cacheScope on lists, reads and server/discover |
not sent |
x-mcp-header |
mirrored into Mcp-Param-* headers over Streamable HTTP |
not mirrored |
echo(tools/echo.ts), both: Echoes the providedmessage: string. Uses Zod to validate inputs.get-annotated-message(tools/get-annotated-message.ts), both: Returns atextmessage annotated withpriorityandaudiencebased onmessageType(error,success, ordebug); can optionally include an annotatedimage.get-env(tools/get-env.ts), both: Returns all environment variables from the running process as pretty-printed JSON text. Every name is returned, but the value of a credential-like name (one with aKEY,TOKEN,SECRET,PASSWORD,PASSWD,CREDENTIAL,AUTHorCOOKIEsegment between underscores, in any case) is replaced with"<redacted>". SetGET_ENV_INCLUDE_SECRETS=1(ortrue) in the server's environment to return every value unmasked, for debugging.get-header-params(tools/get-header-params.ts), both: Returns itsregionargument and theMcp-Param-RegionHTTP header it arrived in. Its input and output schemas are plain JSON Schema 2020-12, andregioncarriesx-mcp-header: "Region", so a 2026-07-28 Streamable HTTP client mirrors it into that header and the server refuses a call whose header disagrees with the body (-32020). The header isnullon stdio and for a 2025-era client.get-resource-links(tools/get-resource-links.ts), both: Returns an introtextblock followed by multipleresource_linkitems. For a requestedcount(1–10), alternates between dynamic Text resources (even IDs) and Blob resources (odd IDs), using URIs fromresources/templates.ts.get-resource-reference(tools/get-resource-reference.ts), both: AcceptsresourceType(textorblob) andresourceId(positive integer). Returns a concreteresourcecontent block (with itsuri,mimeType, and data) with surrounding explanatorytext.get-roots-list(tools/get-roots-list.ts), both, deprecated: Asks the client for its roots with a multi-round-trip request (inputRequired.listRoots()) and lists them. Needs client capabilityroots.gzip-file-as-resource(tools/gzip-file-as-resource.ts), both: Accepts anameanddata(URL or data URI), fetches the data subject to size/time/domain constraints, compresses it, and returns either aresource_link(default) or an inlineresourcedepending onoutputType, withmimeType: application/gzip. The linked resource is registered as a session resource atdemo://resource/session/<name>for a 2025-era client, and stored for ten minutes at a minteddemo://resource/minted/<id>/<name>for a 2026-07-28 client.get-structured-content(tools/get-structured-content.ts), both: Demonstrates structured responses. Acceptslocationinput and returns both backward‑compatiblecontent(atextblock containing JSON) andstructuredContentvalidated by anoutputSchema(temperature, conditions, humidity).get-sum(tools/get-sum.ts), both: For two numbersaandbcalculates and returns their sum. Uses Zod to validate inputs.get-tiny-image(tools/get-tiny-image.ts), both: Returns a tiny PNG MCP logo as animagecontent item with brief descriptive text before and after.simulate-research-query(tools/simulate-research-query.ts), 2026-07-28: Runs as a task on the tasks extension. The call returns a task handle; the task works through four stages, one a second, which the client follows withtasks/get. Withambiguous: trueand an elicitation-capable client it pauses atinput_required, carrying a clarification request the client answers withtasks/update. Needs a client that declares the extension.toggle-simulated-logging(tools/toggle-simulated-logging.ts), 2025-era, deprecated: Starts or stops simulated, random‑leveled logging for the connection. Respects the level the client set withlogging/setLevel.toggle-subscriber-updates(tools/toggle-subscriber-updates.ts), 2025-era, deprecated: Starts or stops simulated resource update notifications for URIs the connection has subscribed to withresources/subscribe.trigger-elicitation-request(tools/trigger-elicitation-request.ts), both: Asks for a form of every field type form-mode elicitation supports (strings, numbers, booleans, enums, and format validation) with a multi-round-trip request, and returns the resulting action/content. Needs client form-mode elicitation:elicitation.form, or anelicitationobject that declares neither mode.trigger-elicitation-request-async(tools/trigger-elicitation-request-async.ts), 2026-07-28: Runs as a task whoseinput_requiredstatus carries a form elicitation; the client answers withtasks/updateand the task completes with a summary. Needs a client that declares the tasks extension and form-mode elicitation.trigger-list-changed(tools/trigger-list-changed.ts), both: Sendsnotifications/<list>/list_changedfortools,promptsorresources: on 2026-07-28 to eachsubscriptions/listenstream that asked for that list, on a 2025-era connection unsolicited.trigger-log-messages(tools/trigger-log-messages.ts), both, deprecated: Sends one log message at each level during the call. A 2026-07-28 request gets those at or above the level its_metanames (none if it names none); a 2025-era client those at or above itslogging/setLevellevel.trigger-long-running-operation(tools/trigger-long-running-operation.ts), both: Simulates a multi-step operation over a givendurationand number ofsteps; reports progress vianotifications/progresswhen aprogressTokenis provided by the client. Honors cancellation: a cancelled call clears its timer and sends no further progress.trigger-multi-step-elicitation(tools/trigger-multi-step-elicitation.ts), both: Asks for a name, then for a favorite color addressed by that name, carrying the first answer to the second round inrequestState. The state is HMAC-signed and expires after ten minutes; a tampered or expired one is refused with-32602. A blank name or a color that is not a choice is asked again. Needs form-mode elicitation.trigger-resource-update(tools/trigger-resource-update.ts), both: Sendsnotifications/resources/updatedfor a URI: on 2026-07-28 to each listen stream whoseresourceSubscriptionsname it, on a 2025-era connection once the client has subscribed to it.trigger-sampling-request(tools/trigger-sampling-request.ts), both, deprecated: Asks the client's LLM for a completion with a multi-round-trip request, using the providedpromptandmaxTokens; returns the LLM's response payload. Needs client capabilitysampling.trigger-sampling-request-async(tools/trigger-sampling-request-async.ts), 2026-07-28, deprecated: Runs as a task whoseinput_requiredstatus carries a sampling request; the client answers withtasks/updateand the task completes with the result. Needs a client that declares the tasks extension and sampling.trigger-url-elicitation(tools/trigger-url-elicitation.ts), both: Directs the user to a URL (URL-mode elicitation). On 2026-07-28 the elicitation comes in-band (input_required), with noelicitationId. On a 2025-era connection it issues anelicitation/createrequest in URL mode with anelicitationId, or throws MCP error-32042(UrlElicitationRequiredError) whenerrorPath=true. On the error path the prerequisite elicitation it returns points at a different URL than the failing request (https://modelcontextprotocol.io); when the client satisfies it and retries the same call, the retry ignoreserrorPathand proceeds via the request path, so the client does not loop on the same error. The retry marker is one-shot per(url, elicitationId)on the connection: it is cleared on the recognized retry, so re-running the error path with identical arguments without an intervening prerequisite is treated as a retry and proceeds. Needs client capabilityelicitation.url.
On a 2025-era connection the tools that need a client capability are registered only for a client that declared it at initialize. On 2026-07-28 they are always listed, because the capabilities arrive with each request, and the SDK refuses a call whose embedded request the client did not declare (-32021).
simple-prompt(prompts/simple.ts): No-argument prompt that returns a static user message.args-prompt(prompts/args.ts): Two-argument prompt withcity(required) andstate(optional) used to compose a question.completable-prompt(prompts/completions.ts): Demonstrates argument auto-completions with the SDK’scompletablehelper;departmentcompletions drive context-awarenamesuggestions.resource-prompt(prompts/resource.ts): AcceptsresourceType("Text" or "Blob") andresourceId(string convertible to integer) and returns messages that include an embedded dynamic resource of the selected type generated viaresources/templates.ts.
- Dynamic Text:
demo://resource/dynamic/text/{index}(content generated on the fly) - Dynamic Blob:
demo://resource/dynamic/blob/{index}(base64 payload generated on the fly) - Static Documents:
demo://resource/static/document/<filename>(serves files fromsrc/everything/docs/as static file-based resources; a 2026-07-28 read carries a one-hour public cache hint) - Session Scoped, 2025-era:
demo://resource/session/<name>(per-session resources registered dynamically; available only for the lifetime of the session) - Minted, 2026-07-28:
demo://resource/minted/<id>/<name>(resources a tool created, under a server-minted id; readable by any client that has the URI, for ten minutes; the process keeps at most 100 of them and 64 MiB of content, dropping the oldest first) - Collections:
demo://resource/collection/summer-specials,demo://resource/collection/by-category/{category}anddemo://resource/collection/item/{itemId}(resources/collections.ts; oneresources/readof a collection returns several contents, one per item, each under its item's own URI, which reads back on its own;categoryanditemIdcomplete)
A read of a resource that does not exist (an unknown URI, a template id that is not a positive integer, a collection category or item that is not in the catalog, an expired minted resource) is answered with -32602 and the URI in the error's data.
- 2026-07-28: a client opens a
subscriptions/listenstream whose filter names the notifications it wants (toolsListChanged,promptsListChanged,resourcesListChanged, andresourceSubscriptionsfor per-resource updates).trigger-list-changedandtrigger-resource-updatepublish onto those streams; over Streamable HTTP, through the handler's subscription bus, so a change made in one request reaches streams other requests opened. - 2025-era: clients subscribe and unsubscribe to resource URIs with
resources/subscribeandresources/unsubscribe.toggle-subscriber-updatesstarts or stops a per-connection interval that emitsnotifications/resources/updated { uri }for the URIs that connection subscribed to, andtrigger-resource-updatesends one on demand. List changes arrive unsolicited. - Each client's subscriptions belong to the server instance serving its connection, so concurrent clients are independent.
Both. The server demonstrates icons with light and dark themes (SEP-973). Its serverInfo (in initialize and in server/discover) and every entry of tools/list, prompts/list, resources/list and resources/templates/list carry an icons array of two scalable SVG icons, one with theme: "light" (drawn for a light background) and one with theme: "dark". Each kind has its own glyph: a server rack, a wrench for tools, a speech bubble for prompts, a document for resources, and a document with braces for templates. The icons are inline data: URIs, so a client fetches nothing; they are defined in server/icons.ts.
Logging is deprecated as of 2026-07-28 (SEP-2577) and remains available on both eras.
- 2026-07-28: a request names its log level in
_meta(io.modelcontextprotocol/logLevel); a request that names none gets no log messages. Usetrigger-log-messagesto see the filter at work. - 2025-era: clients set the minimum level for the session with
logging/setLevel.toggle-simulated-loggingstarts or stops periodic log messages of varying levels (debug, info, notice, warning, error, critical, alert, emergency).
2026-07-28. The server implements the server side of the tasks extension (io.modelcontextprotocol/tasks, SEP-2663), advertised under capabilities.extensions in server/discover. simulate-research-query, trigger-sampling-request-async and trigger-elicitation-request-async answer tools/call with a task handle (resultType: "task") for a client that declares the extension, and serve:
tasks/get: the task's status,statusMessage,ttlMsandpollIntervalMs, plus itsinputRequestswhileinput_requiredand itsresultoncecompleted;tasks/update: the client'sinputResponsesto the outstanding input requests;tasks/cancel: stop a task that has not finished.
A task tool called by a client whose request does not declare the extension, or the capability its task asks for input with (sampling for trigger-sampling-request-async, form-mode elicitation for trigger-elicitation-request-async), is refused with -32021 (missing required client capability), naming what is missing in data.requiredCapabilities; so is a tasks/* request from a client without the extension. An unknown or expired task, and a tasks/update whose answers exceed 64 KiB serialized (counted in UTF-8 bytes), are refused with -32602. A tasks/cancel is acknowledged whatever the task's status (cancellation is cooperative), and a finished task stays as it ended. An update's answers to requests that are not outstanding (never issued, already answered, or sent while the task is not waiting for input) are ignored, as the extension asks. Each input request a task makes has its own key, never reused once answered, so a request asked again after an answer that does not fit gets a new key (form-2, sampling-2). The process keeps at most 100 tasks, each until its TTL; at that limit a new task is refused with a tool error. The task id is a server-minted UUID, so a task lives across requests (each 2026-07-28 HTTP request is served by a fresh server instance) and is reached only through its handle. A task's progress is computed from the clock when it is read, so nothing runs in the background.
The 2025-11-25 core task methods (tasks/result, tasks/list, task-augmented requests) are not served: SDK v2 removed the experimental layer they were built on.