@prefecthq/fastmcp-ts 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ The TypeScript framework for building [Model Context Protocol](https://modelcont
4
4
 
5
5
  Built on version 2 of the official [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) — the scoped `@modelcontextprotocol/server` and `@modelcontextprotocol/client` packages. FastMCP handles the protocol plumbing so you can focus on what your server actually does.
6
6
 
7
- FastMCP 1.0 speaks both MCP protocol generations: the 2025 legacy era and the 2026-07-28 modern era. A server serves both at once, and a client negotiates one. Upgrading from 0.x keeps your existing code running — the client defaults to the legacy era until you opt into the modern one. The [migration guide](docs/migration.mdx) covers the upgrade path.
7
+ FastMCP 1.0 speaks both MCP protocol generations: the 2025 legacy era and the 2026-07-28 modern era. A server serves both at once, and a client negotiates one. The client negotiates automatically by default: it probes once at connect, uses the modern era when the server offers it, and falls back to legacy otherwise, so upgrading from 0.x keeps your existing code running against servers of either generation. Pass `versionNegotiation: { mode: 'legacy' }` to opt out of the probe. The [migration guide](docs/migration.mdx) covers the upgrade path.
8
8
 
9
9
  ## Installation
10
10
 
@@ -289,7 +289,7 @@ fastmcp run server.ts --transport http --port 3000
289
289
  fastmcp inspect --file server.ts
290
290
  fastmcp inspect --url http://localhost:3000
291
291
  fastmcp inspect --file server.ts --json
292
- fastmcp inspect --file server.ts --modern # negotiate the 2026-07-28 protocol era over stdio
292
+ fastmcp inspect --file server.ts --legacy # force the legacy 2025 era, no probe
293
293
  fastmcp inspect --file server.ts --pin 2026-07-28 # require that exact era
294
294
 
295
295
  # Call a tool, read a resource, or get a prompt
@@ -312,7 +312,7 @@ fastmcp install claude-desktop server.ts
312
312
  fastmcp discover
313
313
  ```
314
314
 
315
- `fastmcp inspect`, `fastmcp list`, and `fastmcp call` connect over stdio by default, when you pass `--file` or `--command`. Add `--modern` to negotiate the newer 2026-07-28 protocol era. A `--url` connection negotiates the protocol version automatically, so `--modern` is not needed for HTTP. `--pin <version>` works on every transport, including HTTP. It forces that exact protocol revision. The connection fails if the server does not offer it.
315
+ `fastmcp inspect`, `fastmcp list`, and `fastmcp call` negotiate the protocol era automatically on every transport: one probe at connect, the modern 2026-07-28 era when the server offers it, and the legacy 2025 era otherwise. Add `--legacy` to skip the probe and force the legacy era. `--pin <version>` works on every transport, including HTTP. It forces that exact protocol revision. The connection fails if the server does not offer it. `--modern` is a deprecated no-op, kept for compatibility.
316
316
 
317
317
  ---
318
318
 
@@ -1172,8 +1172,8 @@ var init_version = __esm({
1172
1172
  async run({ args }) {
1173
1173
  if (args.json) setJsonMode(true);
1174
1174
  const data = {
1175
- fastmcp: "1.0.0",
1176
- "mcp-sdk": "^2.0.0",
1175
+ fastmcp: "1.2.0",
1176
+ "mcp-sdk": "2.0.0",
1177
1177
  node: process.version,
1178
1178
  platform: `${process.platform} ${process.arch}`
1179
1179
  };
@@ -28348,6 +28348,33 @@ var init_auth = __esm({
28348
28348
  }
28349
28349
  });
28350
28350
 
28351
+ // src/client/capabilities.ts
28352
+ function buildClientCapabilities(supplied, inferred) {
28353
+ const capabilities = { ...supplied };
28354
+ if (inferred.sampling) {
28355
+ capabilities.sampling = {
28356
+ ...supplied?.sampling,
28357
+ tools: supplied?.sampling?.tools ?? {}
28358
+ };
28359
+ }
28360
+ if (inferred.elicitation) {
28361
+ const elicitation = supplied?.elicitation;
28362
+ capabilities.elicitation = elicitation ? { ...elicitation, form: elicitation.form ?? {} } : {};
28363
+ }
28364
+ if (inferred.rootsListChanged !== void 0) {
28365
+ capabilities.roots = {
28366
+ ...supplied?.roots,
28367
+ listChanged: inferred.rootsListChanged
28368
+ };
28369
+ }
28370
+ return capabilities;
28371
+ }
28372
+ var init_capabilities = __esm({
28373
+ "src/client/capabilities.ts"() {
28374
+ "use strict";
28375
+ }
28376
+ });
28377
+
28351
28378
  // src/client/handlers.ts
28352
28379
  function defaultLogHandler(message) {
28353
28380
  const prefix = message.logger ? `[${message.logger}]` : "[server]";
@@ -29288,6 +29315,7 @@ var init_multi_server = __esm({
29288
29315
  "src/client/multi-server.ts"() {
29289
29316
  "use strict";
29290
29317
  init_dist4();
29318
+ init_capabilities();
29291
29319
  init_handlers();
29292
29320
  init_transports();
29293
29321
  init_client();
@@ -29300,6 +29328,7 @@ var init_multi_server = __esm({
29300
29328
  _config;
29301
29329
  _handlers;
29302
29330
  _roots;
29331
+ _capabilities;
29303
29332
  _defaultOptions;
29304
29333
  _versionNegotiation;
29305
29334
  _resourceSubscriptions = /* @__PURE__ */ new Map();
@@ -29321,8 +29350,13 @@ var init_multi_server = __esm({
29321
29350
  sampling: options?.handlers?.sampling,
29322
29351
  elicitation: options?.handlers?.elicitation
29323
29352
  };
29324
- this._versionNegotiation = options?.versionNegotiation;
29353
+ this._versionNegotiation = options?.versionNegotiation ?? { mode: "auto" };
29325
29354
  this._roots = options?.roots;
29355
+ this._capabilities = buildClientCapabilities(options?.capabilities, {
29356
+ sampling: this._handlers.sampling !== void 0,
29357
+ elicitation: this._handlers.elicitation !== void 0,
29358
+ rootsListChanged: this._roots === void 0 ? void 0 : false
29359
+ });
29326
29360
  this._defaultOptions = options?.defaultOptions ?? {};
29327
29361
  }
29328
29362
  // -------------------------------------------------------------------------
@@ -29734,18 +29768,11 @@ ${errors.map(String).join("\n")}`);
29734
29768
  return new Client(
29735
29769
  { name: "fastmcp-ts", version: "1.0.0" },
29736
29770
  {
29737
- capabilities: this._buildCapabilities(),
29738
- ...this._versionNegotiation ? { versionNegotiation: this._versionNegotiation } : {}
29771
+ capabilities: this._capabilities,
29772
+ versionNegotiation: this._versionNegotiation
29739
29773
  }
29740
29774
  );
29741
29775
  }
29742
- _buildCapabilities() {
29743
- return {
29744
- ...this._handlers.sampling ? { sampling: { tools: {} } } : {},
29745
- ...this._handlers.elicitation ? { elicitation: {} } : {},
29746
- ...this._roots ? { roots: { listChanged: false } } : {}
29747
- };
29748
- }
29749
29776
  _registerHandlers(sdk) {
29750
29777
  sdk.setNotificationHandler("notifications/message", (notification) => {
29751
29778
  void this._handlers.log({
@@ -29831,6 +29858,7 @@ var init_client = __esm({
29831
29858
  "use strict";
29832
29859
  init_dist4();
29833
29860
  init_auth();
29861
+ init_capabilities();
29834
29862
  init_handlers();
29835
29863
  init_transports();
29836
29864
  init_multi_server();
@@ -29869,6 +29897,7 @@ var init_client = __esm({
29869
29897
  _auth;
29870
29898
  _handlers;
29871
29899
  _roots;
29900
+ _capabilities;
29872
29901
  _autoInitialize;
29873
29902
  _versionNegotiation;
29874
29903
  _prior;
@@ -29896,8 +29925,13 @@ var init_client = __esm({
29896
29925
  onPromptsListChanged: options?.handlers?.onPromptsListChanged
29897
29926
  };
29898
29927
  this._roots = options?.roots ? normalizeRootsOption(options.roots) : void 0;
29928
+ this._capabilities = buildClientCapabilities(options?.capabilities, {
29929
+ sampling: this._handlers.sampling !== void 0,
29930
+ elicitation: this._handlers.elicitation !== void 0,
29931
+ rootsListChanged: this._roots === void 0 ? void 0 : true
29932
+ });
29899
29933
  this._autoInitialize = options?.autoInitialize ?? true;
29900
- this._versionNegotiation = options?.versionNegotiation;
29934
+ this._versionNegotiation = options?.versionNegotiation ?? { mode: "auto" };
29901
29935
  this._prior = options?.prior;
29902
29936
  this._inputRequired = options?.inputRequired;
29903
29937
  this._responseCacheStore = options?.responseCacheStore;
@@ -29930,9 +29964,9 @@ var init_client = __esm({
29930
29964
  const sdkClient = new Client(
29931
29965
  { name: "fastmcp-ts", version: "1.0.0" },
29932
29966
  {
29933
- capabilities: this._buildCapabilities(),
29967
+ capabilities: this._capabilities,
29934
29968
  listChanged: this._buildListChangedConfig(),
29935
- ...this._versionNegotiation ? { versionNegotiation: this._versionNegotiation } : {},
29969
+ versionNegotiation: this._versionNegotiation,
29936
29970
  ...this._inputRequired ? { inputRequired: this._inputRequired } : {},
29937
29971
  ...this._responseCacheStore ? { responseCacheStore: this._responseCacheStore } : {},
29938
29972
  ...this._cachePartition !== void 0 ? { cachePartition: this._cachePartition } : {},
@@ -29956,7 +29990,10 @@ var init_client = __esm({
29956
29990
  if (err instanceof UnauthorizedError && this._auth instanceof OAuth) {
29957
29991
  const callbackParams = await this._auth.waitForCallback();
29958
29992
  await this._finishAuth(transport, callbackParams);
29959
- const retry = await resolveTransport(this._input, this._auth);
29993
+ const retry = await resolveTransport(this._input, this._auth, {
29994
+ legacySSE: this._legacySSE,
29995
+ versionNegotiation: this._versionNegotiation
29996
+ });
29960
29997
  if (retry.beforeConnect) await retry.beforeConnect();
29961
29998
  this._transport = retry.transport;
29962
29999
  await sdkClient.connect(retry.transport);
@@ -30037,6 +30074,35 @@ var init_client = __esm({
30037
30074
  getProtocolEra() {
30038
30075
  return this._sdkClient?.getProtocolEra();
30039
30076
  }
30077
+ /**
30078
+ * The connected server's self-reported identity (`serverInfo`: name, version,
30079
+ * and optional title / websiteUrl / icons), when it identified itself. On a
30080
+ * legacy connection it comes from the `initialize` result, where it is
30081
+ * required. On a modern (2026-07-28) connection it comes from the
30082
+ * `server/discover` result's metadata — FastMCP servers advertise it there,
30083
+ * but an anonymous modern server may omit it, leaving this `undefined`.
30084
+ * `undefined` before connect().
30085
+ */
30086
+ getServerInfo() {
30087
+ return this._sdkClient?.getServerVersion();
30088
+ }
30089
+ /**
30090
+ * The server's `instructions` string, when the server provided one — from
30091
+ * the legacy `initialize` result or, on a modern (2026-07-28) connection,
30092
+ * from the `server/discover` result. `undefined` otherwise and before
30093
+ * connect().
30094
+ */
30095
+ getInstructions() {
30096
+ return this._sdkClient?.getInstructions();
30097
+ }
30098
+ /**
30099
+ * The capabilities the server advertised during connection — on the legacy
30100
+ * `initialize` result, or on the `server/discover` result for a modern
30101
+ * (2026-07-28) connection. `undefined` before connect().
30102
+ */
30103
+ getServerCapabilities() {
30104
+ return this._sdkClient?.getServerCapabilities();
30105
+ }
30040
30106
  // -------------------------------------------------------------------------
30041
30107
  // Tools (IToolsClient)
30042
30108
  // -------------------------------------------------------------------------
@@ -30370,13 +30436,6 @@ var init_client = __esm({
30370
30436
  }
30371
30437
  return sdkOptions;
30372
30438
  }
30373
- _buildCapabilities() {
30374
- return {
30375
- ...this._handlers.sampling ? { sampling: { tools: {} } } : {},
30376
- ...this._handlers.elicitation ? { elicitation: {} } : {},
30377
- ...this._roots ? { roots: { listChanged: true } } : {}
30378
- };
30379
- }
30380
30439
  _buildListChangedConfig() {
30381
30440
  const { onToolsListChanged, onResourcesListChanged, onPromptsListChanged } = this._handlers;
30382
30441
  if (!onToolsListChanged && !onResourcesListChanged && !onPromptsListChanged) return void 0;
@@ -30439,14 +30498,13 @@ var init_client = __esm({
30439
30498
  });
30440
30499
 
30441
30500
  // src/cli/utils/connect.ts
30442
- function resolveVersionNegotiation2(kind, era) {
30501
+ function resolveVersionNegotiation2(era) {
30443
30502
  if (era?.pin) return { mode: { pin: era.pin } };
30444
- if (kind === "url") return { mode: "auto" };
30445
- if (era?.modern) return { mode: "auto" };
30446
- return void 0;
30503
+ if (era?.legacy) return { mode: "legacy" };
30504
+ return { mode: "auto" };
30447
30505
  }
30448
30506
  async function connectClient(mode, auth2, era) {
30449
- const versionNegotiation = resolveVersionNegotiation2(mode.kind, era);
30507
+ const versionNegotiation = resolveVersionNegotiation2(era);
30450
30508
  if (mode.kind === "url") {
30451
30509
  const client2 = new Client2(mode.url, { auth: auth2, versionNegotiation });
30452
30510
  await client2.connect();
@@ -30500,6 +30558,86 @@ var init_auth2 = __esm({
30500
30558
  }
30501
30559
  });
30502
30560
 
30561
+ // src/cli/utils/manifest.ts
30562
+ function componentKey(prefix, identifier) {
30563
+ return `${prefix}:${identifier}@`;
30564
+ }
30565
+ function buildFastmcpManifest(input) {
30566
+ const { serverInfo } = input;
30567
+ return {
30568
+ server: {
30569
+ name: serverInfo?.name ?? null,
30570
+ instructions: input.instructions ?? null,
30571
+ // Python reports an unset version as null, never ''.
30572
+ version: serverInfo?.version || null,
30573
+ website_url: serverInfo?.websiteUrl ?? null,
30574
+ icons: serverInfo?.icons ?? null,
30575
+ generation: input.generation,
30576
+ capabilities: input.capabilities ?? {}
30577
+ },
30578
+ environment: {
30579
+ fastmcp: input.fastmcpVersion,
30580
+ mcp: input.mcpVersion
30581
+ },
30582
+ tools: input.tools.map((t2) => ({
30583
+ key: componentKey("tool", t2.name),
30584
+ name: t2.name,
30585
+ description: t2.description ?? null,
30586
+ input_schema: t2.inputSchema ?? {},
30587
+ output_schema: t2.outputSchema ?? null,
30588
+ annotations: t2.annotations ?? null,
30589
+ tags: null,
30590
+ title: t2.title ?? null,
30591
+ icons: t2.icons ?? null,
30592
+ meta: t2._meta ?? null
30593
+ })),
30594
+ prompts: input.prompts.map((p2) => ({
30595
+ key: componentKey("prompt", p2.name),
30596
+ name: p2.name,
30597
+ description: p2.description ?? null,
30598
+ arguments: p2.arguments?.length ? p2.arguments.map((a) => ({
30599
+ name: a.name,
30600
+ description: a.description ?? null,
30601
+ required: a.required ?? null
30602
+ })) : null,
30603
+ tags: null,
30604
+ title: p2.title ?? null,
30605
+ icons: p2.icons ?? null,
30606
+ meta: p2._meta ?? null
30607
+ })),
30608
+ resources: input.resources.map((r) => ({
30609
+ key: componentKey("resource", r.uri),
30610
+ uri: r.uri,
30611
+ name: r.name ?? null,
30612
+ description: r.description ?? null,
30613
+ mime_type: r.mimeType ?? null,
30614
+ annotations: r.annotations ?? null,
30615
+ tags: null,
30616
+ title: r.title ?? null,
30617
+ icons: r.icons ?? null,
30618
+ meta: r._meta ?? null
30619
+ })),
30620
+ templates: input.templates.map((t2) => ({
30621
+ key: componentKey("template", t2.uriTemplate),
30622
+ uri_template: t2.uriTemplate,
30623
+ name: t2.name ?? null,
30624
+ description: t2.description ?? null,
30625
+ mime_type: t2.mimeType ?? null,
30626
+ parameters: null,
30627
+ annotations: t2.annotations ?? null,
30628
+ tags: null,
30629
+ title: t2.title ?? null,
30630
+ icons: t2.icons ?? null,
30631
+ meta: t2._meta ?? null
30632
+ }))
30633
+ };
30634
+ }
30635
+ var init_manifest = __esm({
30636
+ "src/cli/utils/manifest.ts"() {
30637
+ "use strict";
30638
+ }
30639
+ });
30640
+
30503
30641
  // node_modules/fast-string-truncated-width/dist/utils.js
30504
30642
  var isAmbiguous, isFullWidth, isWide;
30505
30643
  var init_utils = __esm({
@@ -33194,6 +33332,7 @@ var init_inspect = __esm({
33194
33332
  init_file_spec();
33195
33333
  init_connect();
33196
33334
  init_auth2();
33335
+ init_manifest();
33197
33336
  init_spinner();
33198
33337
  init_format();
33199
33338
  init_output();
@@ -33208,11 +33347,16 @@ var init_inspect = __esm({
33208
33347
  export: { type: "string", description: "Named export to resolve (e.g. server); overrides file:export syntax" },
33209
33348
  auth: { type: "string", description: "Bearer token" },
33210
33349
  json: { type: "boolean", description: "Output JSON", default: false },
33211
- modern: { type: "boolean", description: "Turn on version negotiation for stdio and in-process connections", default: false },
33350
+ format: { type: "string", description: "Output format: fastmcp (the snake_case manifest the Python FastMCP CLI emits for --format fastmcp). Implies JSON output." },
33351
+ modern: { type: "boolean", description: "Deprecated no-op: auto-negotiation is the default on every transport", default: false },
33352
+ legacy: { type: "boolean", description: "Force the legacy 2025 protocol era; skips the server/discover probe", default: false },
33212
33353
  pin: { type: "string", description: "Pin the protocol era to this revision (e.g. 2026-07-28)" }
33213
33354
  },
33214
33355
  async run({ args }) {
33215
- if (args.json) setJsonMode(true);
33356
+ if (args.format !== void 0 && args.format !== "fastmcp") {
33357
+ cliError(`Unknown format "${args.format}". Supported formats: fastmcp`);
33358
+ }
33359
+ if (args.json || args.format) setJsonMode(true);
33216
33360
  if (!args.url && !args.command && !args.file) {
33217
33361
  cliError("Provide a server URL, --command <cmd>, or --file <file>");
33218
33362
  }
@@ -33226,7 +33370,7 @@ var init_inspect = __esm({
33226
33370
  }
33227
33371
  }
33228
33372
  const mode = args.file ? { kind: "inprocess", spec: fileSpec } : args.command ? { kind: "stdio", command: args.command } : { kind: "url", url: args.url };
33229
- const era = { modern: args.modern, pin: args.pin };
33373
+ const era = { modern: args.modern, legacy: args.legacy, pin: args.pin };
33230
33374
  let client;
33231
33375
  try {
33232
33376
  client = await withSpinner(
@@ -33237,6 +33381,32 @@ var init_inspect = __esm({
33237
33381
  cliError(formatError2(err), { code: EXIT.CONNECTION });
33238
33382
  }
33239
33383
  try {
33384
+ if (args.format === "fastmcp") {
33385
+ const [tools2, resources2, templates, prompts2] = await Promise.all([
33386
+ client.listTools().catch(() => []),
33387
+ client.listResources().catch(() => []),
33388
+ client.listResourceTemplates().catch(() => []),
33389
+ client.listPrompts().catch(() => [])
33390
+ ]);
33391
+ const manifest = buildFastmcpManifest({
33392
+ serverInfo: client.getServerInfo(),
33393
+ instructions: client.getInstructions(),
33394
+ capabilities: client.getServerCapabilities(),
33395
+ // A --file entrypoint is a FastMCP server (Python generation 2, the
33396
+ // only case Python's inspect supports); over --url/--command the
33397
+ // generation is unknowable from the wire.
33398
+ generation: mode.kind === "inprocess" ? 2 : null,
33399
+ fastmcpVersion: "1.2.0",
33400
+ mcpVersion: "2.0.0",
33401
+ tools: tools2,
33402
+ prompts: prompts2,
33403
+ resources: resources2,
33404
+ templates
33405
+ });
33406
+ output(manifest, () => {
33407
+ });
33408
+ return;
33409
+ }
33240
33410
  const [tools, resources, prompts] = await Promise.all([
33241
33411
  client.listTools().catch(() => []),
33242
33412
  client.listResources().catch(() => []),
@@ -33328,7 +33498,8 @@ var init_list = __esm({
33328
33498
  prompts: { type: "boolean", description: "Also list prompts", default: false },
33329
33499
  "input-schema": { type: "boolean", description: "Expand input schemas", default: false },
33330
33500
  json: { type: "boolean", description: "Output JSON", default: false },
33331
- modern: { type: "boolean", description: "Turn on version negotiation for stdio and in-process connections", default: false },
33501
+ modern: { type: "boolean", description: "Deprecated no-op: auto-negotiation is the default on every transport", default: false },
33502
+ legacy: { type: "boolean", description: "Force the legacy 2025 protocol era; skips the server/discover probe", default: false },
33332
33503
  pin: { type: "string", description: "Pin the protocol era to this revision (e.g. 2026-07-28)" }
33333
33504
  },
33334
33505
  async run({ args }) {
@@ -33346,7 +33517,7 @@ var init_list = __esm({
33346
33517
  }
33347
33518
  }
33348
33519
  const mode = args.file ? { kind: "inprocess", spec: fileSpec } : args.command ? { kind: "stdio", command: args.command } : { kind: "url", url: args.url };
33349
- const era = { modern: args.modern, pin: args.pin };
33520
+ const era = { modern: args.modern, legacy: args.legacy, pin: args.pin };
33350
33521
  let client;
33351
33522
  try {
33352
33523
  client = await withSpinner(
@@ -33620,7 +33791,8 @@ var init_call = __esm({
33620
33791
  auth: { type: "string", description: "Bearer token" },
33621
33792
  "input-json": { type: "string", description: "Raw JSON input instead of key=value args" },
33622
33793
  json: { type: "boolean", description: "Output JSON", default: false },
33623
- modern: { type: "boolean", description: "Turn on version negotiation for stdio and in-process connections", default: false },
33794
+ modern: { type: "boolean", description: "Deprecated no-op: auto-negotiation is the default on every transport", default: false },
33795
+ legacy: { type: "boolean", description: "Force the legacy 2025 protocol era; skips the server/discover probe", default: false },
33624
33796
  pin: { type: "string", description: "Pin the protocol era to this revision (e.g. 2026-07-28)" }
33625
33797
  },
33626
33798
  async run({ args, rawArgs }) {
@@ -33638,7 +33810,7 @@ var init_call = __esm({
33638
33810
  }
33639
33811
  }
33640
33812
  const mode = args.file ? { kind: "inprocess", spec: fileSpec } : args.command ? { kind: "stdio", command: args.command } : { kind: "url", url: args.url };
33641
- const era = { modern: args.modern, pin: args.pin };
33813
+ const era = { modern: args.modern, legacy: args.legacy, pin: args.pin };
33642
33814
  let client;
33643
33815
  try {
33644
33816
  client = await withSpinner(
@@ -46728,7 +46900,7 @@ init_format();
46728
46900
  var main = defineCommand({
46729
46901
  meta: {
46730
46902
  name: "fastmcp",
46731
- version: "1.0.0",
46903
+ version: "1.2.0",
46732
46904
  description: "FastMCP CLI \u2014 build, run, and manage MCP servers"
46733
46905
  },
46734
46906
  args: {