- f8b0c1d: Preserve metadata and extension fields in client tool call results.
- a9ba088: Allow clients to advertise custom capabilities and extensions alongside FastMCP-inferred capabilities.
- 738fd3b: Add
--format fastmcptofastmcp inspect. The flag emits a snake_case manifest with the same field set the Python FastMCP CLI produces for its--format fastmcp, so cross-language consumers (Horizon) can parse one manifest shape from both stacks. The default--jsonoutput is unchanged.
- e826045: Version negotiation now defaults to
{ mode: 'auto' }everywhere β the client probes once withserver/discoverat connect, uses the modern (2026-07-28) era when the server offers it, and falls back to the plain legacyinitializehandshake otherwise. This applies toClient,MultiServerClient, and every connecting CLI command. PassversionNegotiation: { mode: 'legacy' }(or the new CLI--legacyflag) for the old probe-free legacy behavior; the CLI--modernflag is now a deprecated no-op.{ pin }is unchanged.
-
f74ddff:
ClientCredentialsOptionsis now a discriminated union.The
ClientCredentialsoptions changed from a single interface to a discriminated union with two members. One member takes aclientSecret, with an optionalauthMethodofclient_secret_post(the default, unchanged) orclient_secret_basic. The other member takes aprivateKeyand analgorithmforprivate_key_jwt(RFC 7523), and it requires anaudience. The two members are mutually exclusive at compile time.This is a breaking type change. Code that added fields to the old interface through declaration merging no longer compiles. Build the options as one of the two union members instead. Existing
clientSecretconfigurations keep working, because the shared-secret member matches the old shape. -
f74ddff: FastMCP 1.0 β the 2026-07-28 modern protocol era on version 2 of the MCP TypeScript SDK.
FastMCP now speaks two protocol eras from one codebase. A server serves both the 2025 legacy era and the 2026-07-28 modern era at the same time. It needs no configuration to do so. A client speaks the legacy era by default, so your 0.x code keeps working. You opt into the modern era per connection when you are ready.
This release moves the library onto version 2 of the official MCP TypeScript SDK β the scoped
@modelcontextprotocol/serverand@modelcontextprotocol/clientpackages. FastMCP wraps the SDK, so the change reaches your code only where you imported the SDK directly. The package name and the entry points do not change. You still install@prefecthq/fastmcp-tsand import from@prefecthq/fastmcp-ts/serverand@prefecthq/fastmcp-ts/client.Headline features
- Era negotiation. A client selects the era per connection with
versionNegotiation.{ mode: 'auto' }probes the server withserver/discoverand uses the modern era when the server offers it.{ mode: { pin: '2026-07-28' } }requires the modern era.getProtocolEra()reports the negotiated era after connect. - Return-value interactivity (
inputRequired). A tool asks the caller for input by returninginputRequired(...). The client answers and retries the same call. One handler serves both eras, because a legacy shim turns the return value into a server-to-client request for 2025 clients. This replaces the deprecatedctx.sample,ctx.elicit, andctx.listRoots. - Server discovery (
server/discover). A client reads a server's supported eras and capabilities before it connects. - Resource subscriptions on both eras.
server.notifyResourceUpdated(uri)pushes one change signal. The legacy era backs it withresources/subscribeandresources/unsubscribe. The modern era backs it with asubscriptions/listenstream. - Argument completion. A
completecallback supplies suggestions for prompt arguments and resource-template variables. - DNS-rebinding protection. The server validates the
HostandOriginheaders on a loopback bind. ConfigurednsRebindingto protect a routable bind and to silence the warning on an exposed bind. - Loopback default bind. An HTTP server now binds
127.0.0.1by default, not0.0.0.0. This matches the FastMCP Python project and turns the DNS-rebinding guard on out of the box. - Client auth family. OAuth adds client-ID metadata documents (
clientMetadataUrl, CIMD) and RFC 9207issvalidation. New strategies ship:JwtBearerAuth(RFC 7523),EnterpriseManagedAuth(SEP-990), andAsyncHeaderAuth. Step-up authorization answers a401raised after connect. - SSE resumability. The legacy HTTP transport replays missed events after a dropped connection (SEP-1699).
- CLI era flags. The
fastmcpCLI gains--modernand--pin <version>to select the era. An HTTP--urlnegotiates automatically.
Request-scoped state and handles
ctx.mintRequestStateandctx.requestStatecarry signed state across one input-required flow. A modern HTTP request has no session, so the session accessorsctx.getState,ctx.setState, andctx.deleteStatethrow a pointed error there and name the request-scoped replacements.Breaking changes
The upgrade keeps most 0.x code running. The breaks fall into two groups. A small group applies the moment you upgrade. A larger group applies only when you opt a client into the modern era. The categories are:
- Error classes and one error code.
McpErrorandErrorCodebecomeProtocolErrorandProtocolErrorCode. Resource-not-found moves from-32002to-32602. - SSE becomes an explicit
legacySSE: trueopt-in. A plain URL connects over Streamable HTTP only. - The loopback default bind and the DNS-rebinding guard, as described above.
- The
CachingMiddlewaredefault key gains an auth-identity partition, so one caller's cached result no longer reaches another identity. - Modern-era changes to session state and to server-initiated requests (
ctx.sample,ctx.elicit,ctx.listRoots).
Read the authoritative, ordered upgrade guide before you upgrade: Migrating from 0.x.
0.x enters maintenance
The 0.x line enters maintenance with this release. It receives critical fixes only. Plan your move to 1.0 with the migration guide above.
Pre-release
This is a release-candidate line (
1.0.0-rc.*), published while the MCP TypeScript SDK is in beta. The SDK dependencies are pinned to an exact beta build. A stable1.0.0follows once the SDK reaches general availability. - Era negotiation. A client selects the era per connection with
-
f74ddff: Legacy SSE transport: wire-format changes.
The legacy HTTP+SSE transport changes what it sends on the wire (SEP-1699). Every result event on the legacy HTTP+SSE transport now carries an
id:field. The server sends priming events when a stream opens. AGETrequest replays the events after the last acknowledgedid, so a client resumes after a dropped connection. The server answers400when the requested anchor is no longer in the replay buffer. The buffer holds a bounded number of recent events per session.Code that uses the transport through the FastMCP client needs no change. The client handles the new framing and the replay. Code that parses the raw SSE stream must account for the
id:field, the priming events, and the400on an evicted anchor.
-
f74ddff: Client auth: step-up authorization now runs after connect.
A server can raise a
401after the client connects β for example, when a tool needs a scope that the first token does not carry. The client now answers this challenge with a step-up authorization request. With an interactive OAuth flow configured, the client can open a browser to complete the authorization. In 0.x the client failed fast on a post-connect401instead.The client retries at most twice, and concurrent calls share one authorization request. A client that must stay non-interactive β a headless or automated client β must configure a non-interactive auth strategy, so a post-connect challenge does not try to open a browser.
-
70a179e: Add stateless legacy-era HTTP. Set
RunOptions.statelessorFASTMCP_STATELESS_HTTPand the server serves each legacy HTTP request from a fresh server and transport: no session registry, incomingmcp-session-idignored, and no session id issued. Use it behind a load balancer or on serverless compute, where consecutive requests reach different instances and a sessionful server answers 404 "Session not found".Two things to check before you upgrade, not just before you opt in:
FASTMCP_STATELESS_HTTPis a new variable, but if a deployment already happens to export it, this release is not a no-op for that deployment. The variable was inert before this release; it is now load-bearing, and the server flips to stateless mode on upgrade with no code change.- The variable is read on every transport, including stdio. An unrecognized value now aborts startup rather than falling back silently, so a server that starts today can fail to start after upgrading, even if it never turns stateless mode on.
Stateless mode gives up what a session provides, and says so rather than failing quietly:
ctx.getState,ctx.setState, andctx.deleteStatethrow a pointed error.ctx.elicit,ctx.sample, andctx.listRootsthrow a pointed error. Client capabilities are negotiated once atinitializeand the client replies on a separate request, and neither survives per-request serving.inputRequired({ inputRequests })throws for the same reason. OnlyinputRequired({ requestState }), with noinputRequests, still works β but not because the client retries anything. The SDK re-enters your handler itself, in-process, inside the same HTTP request, pacing each round with a short sleep (about 250ms). That holds the request open longer for every round, and it gives up after a bounded number of rounds (8 by default) β so it suits a handler that can finish on its own schedule, not one waiting on an external event.resources.subscribeis not advertised, and both subscribe methods are rejected.GETandDELETEreturn 405, and SSE resumability is off.ctx.onClosenever fires.
Sessionful mode is unchanged and remains the default, and the modern (2026-07-28) era was already stateless.
-
f74ddff:
client_secret_basic: credentials use RFC 6749 Β§2.3.1 encoding.The
client_secret_basicmethod percent-encodes the client id and the client secret before it builds the HTTP BasicAuthorizationheader, as RFC 6749 Β§2.3.1 requires. A space becomes%20. Many clients in the ecosystem instead Base64-encode the raw values. The two agree for a secret that holds only unreserved characters. They differ for a secret that holds reserved or special characters, sent to an authorization server that does not form-decode the header.If your secret holds special characters and the authorization server rejects the request, confirm that the server decodes the Basic header per RFC 6749 Β§2.3.1.
-
f74ddff: Legacy-HTTP sampling, elicitation, and roots requests now ride the in-flight request's stream.
On a legacy sessionful HTTP connection,
ctx.sample(),ctx.elicit(), andctx.listRoots()raise a server-to-client request. Before this change, the server sent that request on the standalone server-to-client stream. A client opens that stream afterinitialize. The server dropped the request when the stream was not yet open. A client that called a sampling or elicitation tool as its first operation could then wait for its timeout.FastMCP now tags each such request with the in-flight tool call's request id. The server sends the request on that tool call's own response stream, which the client already reads. This removes the startup hang window. The stdio and modern paths are unchanged.
-
f74ddff: Fix: a modern stdio server no longer advertises
resources.subscribe.A server on stdio in the modern era advertised the
resources.subscribecapability by mistake. The modern era carries resource change signals over thesubscriptions/listenstream, not overresources/subscribe, so the advertisement was wrong. The server now reports the correct capabilities for the negotiated era. A modern client no longer sees a subscribe capability that the modern era does not use.
-
70a179e: Add stateless legacy-era HTTP. Set
RunOptions.statelessorFASTMCP_STATELESS_HTTPand the server serves each legacy HTTP request from a fresh server and transport: no session registry, incomingmcp-session-idignored, and no session id issued. Use it behind a load balancer or on serverless compute, where consecutive requests reach different instances and a sessionful server answers 404 "Session not found".Two things to check before you upgrade, not just before you opt in:
FASTMCP_STATELESS_HTTPis a new variable, but if a deployment already happens to export it, this release is not a no-op for that deployment. The variable was inert before this release; it is now load-bearing, and the server flips to stateless mode on upgrade with no code change.- The variable is read on every transport, including stdio. An unrecognized value now aborts startup rather than falling back silently, so a server that starts today can fail to start after upgrading, even if it never turns stateless mode on.
Stateless mode gives up what a session provides, and says so rather than failing quietly:
ctx.getState,ctx.setState, andctx.deleteStatethrow a pointed error.ctx.elicit,ctx.sample, andctx.listRootsthrow a pointed error. Client capabilities are negotiated once atinitializeand the client replies on a separate request, and neither survives per-request serving.inputRequired({ inputRequests })throws for the same reason. OnlyinputRequired({ requestState }), with noinputRequests, still works β but not because the client retries anything. The SDK re-enters your handler itself, in-process, inside the same HTTP request, pacing each round with a short sleep (about 250ms). That holds the request open longer for every round, and it gives up after a bounded number of rounds (8 by default) β so it suits a handler that can finish on its own schedule, not one waiting on an external event.resources.subscribeis not advertised, and both subscribe methods are rejected.GETandDELETEreturn 405, and SSE resumability is off.ctx.onClosenever fires.
Sessionful mode is unchanged and remains the default, and the modern (2026-07-28) era was already stateless.
-
f74ddff:
ClientCredentialsOptionsis now a discriminated union.The
ClientCredentialsoptions changed from a single interface to a discriminated union with two members. One member takes aclientSecret, with an optionalauthMethodofclient_secret_post(the default, unchanged) orclient_secret_basic. The other member takes aprivateKeyand analgorithmforprivate_key_jwt(RFC 7523), and it requires anaudience. The two members are mutually exclusive at compile time.This is a breaking type change. Code that added fields to the old interface through declaration merging no longer compiles. Build the options as one of the two union members instead. Existing
clientSecretconfigurations keep working, because the shared-secret member matches the old shape. -
f74ddff: FastMCP 1.0 β the 2026-07-28 modern protocol era on version 2 of the MCP TypeScript SDK.
FastMCP now speaks two protocol eras from one codebase. A server serves both the 2025 legacy era and the 2026-07-28 modern era at the same time. It needs no configuration to do so. A client speaks the legacy era by default, so your 0.x code keeps working. You opt into the modern era per connection when you are ready.
This release moves the library onto version 2 of the official MCP TypeScript SDK β the scoped
@modelcontextprotocol/serverand@modelcontextprotocol/clientpackages. FastMCP wraps the SDK, so the change reaches your code only where you imported the SDK directly. The package name and the entry points do not change. You still install@prefecthq/fastmcp-tsand import from@prefecthq/fastmcp-ts/serverand@prefecthq/fastmcp-ts/client.Headline features
- Era negotiation. A client selects the era per connection with
versionNegotiation.{ mode: 'auto' }probes the server withserver/discoverand uses the modern era when the server offers it.{ mode: { pin: '2026-07-28' } }requires the modern era.getProtocolEra()reports the negotiated era after connect. - Return-value interactivity (
inputRequired). A tool asks the caller for input by returninginputRequired(...). The client answers and retries the same call. One handler serves both eras, because a legacy shim turns the return value into a server-to-client request for 2025 clients. This replaces the deprecatedctx.sample,ctx.elicit, andctx.listRoots. - Server discovery (
server/discover). A client reads a server's supported eras and capabilities before it connects. - Resource subscriptions on both eras.
server.notifyResourceUpdated(uri)pushes one change signal. The legacy era backs it withresources/subscribeandresources/unsubscribe. The modern era backs it with asubscriptions/listenstream. - Argument completion. A
completecallback supplies suggestions for prompt arguments and resource-template variables. - DNS-rebinding protection. The server validates the
HostandOriginheaders on a loopback bind. ConfigurednsRebindingto protect a routable bind and to silence the warning on an exposed bind. - Loopback default bind. An HTTP server now binds
127.0.0.1by default, not0.0.0.0. This matches the FastMCP Python project and turns the DNS-rebinding guard on out of the box. - Client auth family. OAuth adds client-ID metadata documents (
clientMetadataUrl, CIMD) and RFC 9207issvalidation. New strategies ship:JwtBearerAuth(RFC 7523),EnterpriseManagedAuth(SEP-990), andAsyncHeaderAuth. Step-up authorization answers a401raised after connect. - SSE resumability. The legacy HTTP transport replays missed events after a dropped connection (SEP-1699).
- CLI era flags. The
fastmcpCLI gains--modernand--pin <version>to select the era. An HTTP--urlnegotiates automatically.
Request-scoped state and handles
ctx.mintRequestStateandctx.requestStatecarry signed state across one input-required flow. A modern HTTP request has no session, so the session accessorsctx.getState,ctx.setState, andctx.deleteStatethrow a pointed error there and name the request-scoped replacements.Breaking changes
The upgrade keeps most 0.x code running. The breaks fall into two groups. A small group applies the moment you upgrade. A larger group applies only when you opt a client into the modern era. The categories are:
- Error classes and one error code.
McpErrorandErrorCodebecomeProtocolErrorandProtocolErrorCode. Resource-not-found moves from-32002to-32602. - SSE becomes an explicit
legacySSE: trueopt-in. A plain URL connects over Streamable HTTP only. - The loopback default bind and the DNS-rebinding guard, as described above.
- The
CachingMiddlewaredefault key gains an auth-identity partition, so one caller's cached result no longer reaches another identity. - Modern-era changes to session state and to server-initiated requests (
ctx.sample,ctx.elicit,ctx.listRoots).
Read the authoritative, ordered upgrade guide before you upgrade: Migrating from 0.x.
0.x enters maintenance
The 0.x line enters maintenance with this release. It receives critical fixes only. Plan your move to 1.0 with the migration guide above.
Pre-release
This is a release-candidate line (
1.0.0-rc.*), published while the MCP TypeScript SDK is in beta. The SDK dependencies are pinned to an exact beta build. A stable1.0.0follows once the SDK reaches general availability. - Era negotiation. A client selects the era per connection with
-
f74ddff: Legacy SSE transport: wire-format changes.
The legacy HTTP+SSE transport changes what it sends on the wire (SEP-1699). Every result event on the legacy HTTP+SSE transport now carries an
id:field. The server sends priming events when a stream opens. AGETrequest replays the events after the last acknowledgedid, so a client resumes after a dropped connection. The server answers400when the requested anchor is no longer in the replay buffer. The buffer holds a bounded number of recent events per session.Code that uses the transport through the FastMCP client needs no change. The client handles the new framing and the replay. Code that parses the raw SSE stream must account for the
id:field, the priming events, and the400on an evicted anchor.
-
f74ddff: Client auth: step-up authorization now runs after connect.
A server can raise a
401after the client connects β for example, when a tool needs a scope that the first token does not carry. The client now answers this challenge with a step-up authorization request. With an interactive OAuth flow configured, the client can open a browser to complete the authorization. In 0.x the client failed fast on a post-connect401instead.The client retries at most twice, and concurrent calls share one authorization request. A client that must stay non-interactive β a headless or automated client β must configure a non-interactive auth strategy, so a post-connect challenge does not try to open a browser.
-
f74ddff:
client_secret_basic: credentials use RFC 6749 Β§2.3.1 encoding.The
client_secret_basicmethod percent-encodes the client id and the client secret before it builds the HTTP BasicAuthorizationheader, as RFC 6749 Β§2.3.1 requires. A space becomes%20. Many clients in the ecosystem instead Base64-encode the raw values. The two agree for a secret that holds only unreserved characters. They differ for a secret that holds reserved or special characters, sent to an authorization server that does not form-decode the header.If your secret holds special characters and the authorization server rejects the request, confirm that the server decodes the Basic header per RFC 6749 Β§2.3.1.
-
f74ddff: Legacy-HTTP sampling, elicitation, and roots requests now ride the in-flight request's stream.
On a legacy sessionful HTTP connection,
ctx.sample(),ctx.elicit(), andctx.listRoots()raise a server-to-client request. Before this change, the server sent that request on the standalone server-to-client stream. A client opens that stream afterinitialize. The server dropped the request when the stream was not yet open. A client that called a sampling or elicitation tool as its first operation could then wait for its timeout.FastMCP now tags each such request with the in-flight tool call's request id. The server sends the request on that tool call's own response stream, which the client already reads. This removes the startup hang window. The stdio and modern paths are unchanged.
-
f74ddff: Fix: a modern stdio server no longer advertises
resources.subscribe.A server on stdio in the modern era advertised the
resources.subscribecapability by mistake. The modern era carries resource change signals over thesubscriptions/listenstream, not overresources/subscribe, so the advertisement was wrong. The server now reports the correct capabilities for the negotiated era. A modern client no longer sees a subscribe capability that the modern era does not use.
-
7d0a3ce: Add entrypoint-export support to the CLI.
fastmcp run <file>:<export>and--file/--exportoninspect,list, andcallnow resolve a named export (or a sync/async factory function returning one) and start or introspect it directly, instead of requiring the file to call.run()itself. When no export is given,default,mcp,server, andappare auto-detected in that order, mirroring Python FastMCP's entrypoint convention. Files that already start their own server continue to work unchanged.Adds
FastMCP.isRunning, a read-only getter reporting whetherrun()/connect()has been called on the instance.fastmcp runalso gains--hostand--pathflags (alongside the existing--transportand--port), settingMCP_HOST/MCP_PATHfor the spawned server the same way--transport/--portalready setMCP_TRANSPORT/MCP_PORT.
- 9cbddac: Fix MCP SDK subpath imports used by createProxy transports.
- 9cbddac: Fix MCP SDK subpath imports used by FastMCP run transports.
- 703bff6: Set up automated release pipeline (Changesets + GitHub Actions + npm Trusted Publishing). No runtime changes.