fastmcp 4.21.2 → 4.22.1

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.
Files changed (36) hide show
  1. package/README.md +12 -2
  2. package/dist/FastMCP.cjs +3 -3
  3. package/dist/FastMCP.d.cts +2 -2
  4. package/dist/FastMCP.d.ts +2 -2
  5. package/dist/FastMCP.js +2 -2
  6. package/dist/{OAuthProvider-C4idKF29.d.cts → OAuthProvider-BcDb5bOz.d.cts} +8 -1
  7. package/dist/{OAuthProvider-C4idKF29.d.ts → OAuthProvider-BcDb5bOz.d.ts} +8 -1
  8. package/dist/auth/index.cjs +2 -2
  9. package/dist/auth/index.d.cts +2 -2
  10. package/dist/auth/index.d.ts +2 -2
  11. package/dist/auth/index.js +1 -1
  12. package/dist/{chunk-C5VOVQPC.cjs → chunk-RN2Z26T4.cjs} +118 -16
  13. package/dist/chunk-RN2Z26T4.cjs.map +1 -0
  14. package/dist/{chunk-QMRS4ZLD.js → chunk-XFUILY54.js} +115 -13
  15. package/dist/chunk-XFUILY54.js.map +1 -0
  16. package/dist/{chunk-AR3LVWUF.js → chunk-XPKMA7DH.js} +22 -1
  17. package/dist/chunk-XPKMA7DH.js.map +1 -0
  18. package/dist/{chunk-GTPK7A57.cjs → chunk-YOBCXNFP.cjs} +22 -1
  19. package/dist/chunk-YOBCXNFP.cjs.map +1 -0
  20. package/dist/edge/index.cjs +15 -12
  21. package/dist/edge/index.cjs.map +1 -1
  22. package/dist/edge/index.js +7 -4
  23. package/dist/edge/index.js.map +1 -1
  24. package/dist/examples/custom-routes.cjs +3 -3
  25. package/dist/examples/custom-routes.js +2 -2
  26. package/dist/openapi/index.cjs +165 -149
  27. package/dist/openapi/index.cjs.map +1 -1
  28. package/dist/openapi/index.d.cts +5 -3
  29. package/dist/openapi/index.d.ts +5 -3
  30. package/dist/openapi/index.js +155 -139
  31. package/dist/openapi/index.js.map +1 -1
  32. package/package.json +2 -2
  33. package/dist/chunk-AR3LVWUF.js.map +0 -1
  34. package/dist/chunk-C5VOVQPC.cjs.map +0 -1
  35. package/dist/chunk-GTPK7A57.cjs.map +0 -1
  36. package/dist/chunk-QMRS4ZLD.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  cancelResponseBody
3
- } from "./chunk-AR3LVWUF.js";
3
+ } from "./chunk-XPKMA7DH.js";
4
4
  import {
5
5
  strictInputSchema
6
6
  } from "./chunk-UMDUIBFW.js";
@@ -464,6 +464,17 @@ var capCompletionValues = (completion) => {
464
464
  values: completion.values.slice(0, COMPLETION_VALUES_LIMIT)
465
465
  };
466
466
  };
467
+ var DEFAULT_LOGGING_LEVEL = "info";
468
+ var LOGGING_LEVEL_SEVERITY = {
469
+ alert: 6,
470
+ critical: 5,
471
+ debug: 0,
472
+ emergency: 7,
473
+ error: 4,
474
+ info: 1,
475
+ notice: 2,
476
+ warning: 3
477
+ };
467
478
  var FastMCPSessionEventEmitterBase = EventEmitter;
468
479
  var ServerState = /* @__PURE__ */ ((ServerState2) => {
469
480
  ServerState2["Error"] = "error";
@@ -481,7 +492,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
481
492
  return this.#connectionState === "ready";
482
493
  }
483
494
  get loggingLevel() {
484
- return this.#loggingLevel;
495
+ return this.#loggingLevel ?? DEFAULT_LOGGING_LEVEL;
485
496
  }
486
497
  get roots() {
487
498
  return this.#roots;
@@ -530,7 +541,11 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
530
541
  #clientCapabilities;
531
542
  #connectionState = "connecting";
532
543
  #logger;
533
- #loggingLevel = "info";
544
+ /**
545
+ * The minimum level the client asked for with `logging/setLevel`, or
546
+ * `undefined` while it has not asked. See {@link DEFAULT_LOGGING_LEVEL}.
547
+ */
548
+ #loggingLevel;
534
549
  #needsEventLoopFlush = false;
535
550
  #onToolCall;
536
551
  #pingConfig;
@@ -561,6 +576,12 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
561
576
  */
562
577
  #subscriptions = /* @__PURE__ */ new Set();
563
578
  #transportType;
579
+ /**
580
+ * Capabilities this session has already warned about in `#canRefresh`, so a
581
+ * server that registers a hundred resources at runtime logs one line rather
582
+ * than a hundred.
583
+ */
584
+ #unnegotiatedWarnings = /* @__PURE__ */ new Set();
564
585
  #utils;
565
586
  constructor({
566
587
  auth,
@@ -600,7 +621,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
600
621
  this.#needsEventLoopFlush = transportType === "httpStream";
601
622
  const supportsTools = hasTools ?? tools.length > 0;
602
623
  if (supportsTools) {
603
- this.#capabilities.tools = {};
624
+ this.#capabilities.tools = { listChanged: true };
604
625
  }
605
626
  if (resources.length || resourcesTemplates.length) {
606
627
  this.#capabilities.resources = { listChanged: true, subscribe: true };
@@ -744,6 +765,9 @@ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
744
765
  }
745
766
  }
746
767
  promptsListChanged(prompts) {
768
+ if (!this.#canRefresh("prompts")) {
769
+ return;
770
+ }
747
771
  this.#prompts.clear();
748
772
  for (const prompt of prompts) {
749
773
  this.addPrompt(prompt);
@@ -758,6 +782,9 @@ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
758
782
  return this.#server.createMessage(message, options);
759
783
  }
760
784
  resourcesListChanged(resources) {
785
+ if (!this.#canRefresh("resources")) {
786
+ return;
787
+ }
761
788
  this.#resources.clear();
762
789
  for (const resource of resources) {
763
790
  this.addResource(resource);
@@ -766,6 +793,9 @@ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
766
793
  this.triggerListChangedNotification("notifications/resources/list_changed");
767
794
  }
768
795
  resourceTemplatesListChanged(resourceTemplates) {
796
+ if (!this.#canRefresh("resources")) {
797
+ return;
798
+ }
769
799
  this.#resourceTemplates.clear();
770
800
  for (const resourceTemplate of resourceTemplates) {
771
801
  this.addResourceTemplate(resourceTemplate);
@@ -795,6 +825,9 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
795
825
  }
796
826
  }
797
827
  toolsListChanged(tools) {
828
+ if (!this.#canRefresh("tools")) {
829
+ return;
830
+ }
798
831
  this.setupToolHandlers(
799
832
  toolsVisibleTo(tools, this.#auth, this.#authRequired)
800
833
  );
@@ -857,6 +890,30 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
857
890
  this.#abortController.abort(new SessionError("Session closed"));
858
891
  }
859
892
  }
893
+ /**
894
+ * Whether this session can be told about a primitive added at runtime.
895
+ *
896
+ * A session that connected before the server had any tool, resource or prompt
897
+ * of its own never advertised that capability, and MCP fixes capabilities
898
+ * during `initialize` for the life of the session. Registering the handler
899
+ * anyway makes the SDK throw `Server does not support <capability>` — out of
900
+ * `addTool()`, `addResource()`, `addResourceTemplate()` or `addPrompt()`,
901
+ * which are the public builder methods, and out of the middle of the loop
902
+ * over sessions, so the sessions after this one never heard about the change
903
+ * either. Warn once instead and let the rest of the loop run.
904
+ */
905
+ #canRefresh(capability) {
906
+ if (this.#capabilities[capability]) {
907
+ return true;
908
+ }
909
+ if (!this.#unnegotiatedWarnings.has(capability)) {
910
+ this.#unnegotiatedWarnings.add(capability);
911
+ this.#logger.warn(
912
+ `[FastMCP warning] This session advertised no '${capability}' capability, because the server had none when the client connected, so it cannot be shown the ones registered since. Register one before start(), or reconnect the client; sessions that connect from now on do see them.`
913
+ );
914
+ }
915
+ return false;
916
+ }
860
917
  /**
861
918
  * Builds the context object passed as the third argument to
862
919
  * `resource.load` / `resourceTemplate.load` / `prompt.load`.
@@ -917,6 +974,9 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
917
974
  * disconnects mid-tool takes the server process down with it.
918
975
  */
919
976
  #sendLog(level, message, context) {
977
+ if (this.#loggingLevel && LOGGING_LEVEL_SEVERITY[level] < LOGGING_LEVEL_SEVERITY[this.#loggingLevel]) {
978
+ return;
979
+ }
920
980
  this.#server.sendLoggingMessage({
921
981
  data: {
922
982
  context,
@@ -940,6 +1000,12 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
940
1000
  * request's own stream, which is the only server-to-client route that exists
941
1001
  * when running stateless.
942
1002
  *
1003
+ * It is deliberately not filtered against the level the client set with
1004
+ * `logging/setLevel`, unlike the `log` object a tool is handed: its job is to
1005
+ * put bytes on the stream, and a client that asked for `error` only would
1006
+ * otherwise switch off a feature the server opted into. Raise
1007
+ * `streamKeepalive.logLevel` if the level it uses matters to you.
1008
+ *
943
1009
  * @returns a function that stops the keepalive.
944
1010
  */
945
1011
  #startStreamKeepalive(extra, toolName) {
@@ -1258,8 +1324,9 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1258
1324
  };
1259
1325
  }
1260
1326
  throw new McpError(
1261
- ErrorCode.MethodNotFound,
1262
- `Resource not found: '${request.params.uri}'. Available resources: ${Array.from(this.#resources.values()).map((r) => r.uri).join(", ") || "none"}`
1327
+ ErrorCode.InvalidParams,
1328
+ `Resource not found: '${request.params.uri}'. Available resources: ${Array.from(this.#resources.values()).map((r) => r.uri).join(", ") || "none"}`,
1329
+ { uri: request.params.uri }
1263
1330
  );
1264
1331
  }
1265
1332
  if (!("uri" in resource)) {
@@ -1408,7 +1475,7 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1408
1475
  const tool = toolsMap.get(request.params.name);
1409
1476
  if (!tool) {
1410
1477
  throw new McpError(
1411
- ErrorCode.MethodNotFound,
1478
+ ErrorCode.InvalidParams,
1412
1479
  `Unknown tool: ${request.params.name}`
1413
1480
  );
1414
1481
  }
@@ -1622,6 +1689,17 @@ function parseBasicAuthHeader(authHeader) {
1622
1689
  return null;
1623
1690
  }
1624
1691
  }
1692
+ function protectedResourceMetadataUrl(resource) {
1693
+ let url;
1694
+ try {
1695
+ url = new URL(resource);
1696
+ } catch {
1697
+ return `${resource}/.well-known/oauth-protected-resource`;
1698
+ }
1699
+ const path = url.pathname === "/" ? "" : url.pathname;
1700
+ url.pathname = `/.well-known/oauth-protected-resource${path}`;
1701
+ return url.toString();
1702
+ }
1625
1703
  var OAUTH_PROXY_MAX_BODY_SIZE = 1024 * 1024;
1626
1704
  var OAUTH_CREDENTIAL_RESPONSE_HEADERS = {
1627
1705
  "Cache-Control": "no-store",
@@ -1664,10 +1742,9 @@ var FastMCP = class extends FastMCPEventEmitter {
1664
1742
  this.#authenticate = options.authenticate;
1665
1743
  }
1666
1744
  if (!options.oauth) {
1667
- this.#options = {
1668
- ...options,
1669
- oauth: options.auth.getOAuthConfig()
1670
- };
1745
+ const oauth = options.auth.getOAuthConfig();
1746
+ this.#providerResourceBase = oauth.protectedResource.resource;
1747
+ this.#options = { ...options, oauth };
1671
1748
  }
1672
1749
  } else {
1673
1750
  this.#authenticate = options.authenticate;
@@ -1685,6 +1762,8 @@ var FastMCP = class extends FastMCPEventEmitter {
1685
1762
  #logger;
1686
1763
  #options;
1687
1764
  #prompts = [];
1765
+ /** The auth provider's base URL, when it supplied the OAuth config. */
1766
+ #providerResourceBase;
1688
1767
  #resources = [];
1689
1768
  #resourcesTemplates = [];
1690
1769
  #serverState = "stopped" /* Stopped */;
@@ -2111,6 +2190,7 @@ var FastMCP = class extends FastMCPEventEmitter {
2111
2190
  httpConfig.basePath,
2112
2191
  httpConfig.endpoint
2113
2192
  );
2193
+ this.#useEndpointAsResource(httpConfig.endpoint);
2114
2194
  if (httpConfig.stateless) {
2115
2195
  this.#logger.info(
2116
2196
  `[FastMCP info] Starting server in stateless mode on HTTP Stream at ${protocol}://${httpConfig.host}:${httpConfig.port}${streamEndpoint}`
@@ -2279,7 +2359,7 @@ var FastMCP = class extends FastMCPEventEmitter {
2279
2359
  ];
2280
2360
  if (resource) {
2281
2361
  wwwAuthenticateParts.push(
2282
- `resource_metadata="${resource}/.well-known/oauth-protected-resource"`
2362
+ `resource_metadata="${protectedResourceMetadataUrl(resource)}"`
2283
2363
  );
2284
2364
  }
2285
2365
  return new Response(
@@ -2930,6 +3010,28 @@ var FastMCP = class extends FastMCPEventEmitter {
2930
3010
  session.toolsListChanged(tools);
2931
3011
  }
2932
3012
  }
3013
+ /**
3014
+ * RFC 9728: the protected resource is the MCP endpoint clients connect to,
3015
+ * and the metadata served at `/.well-known/oauth-protected-resource<endpoint>`
3016
+ * must name it. An auth provider only knows its base URL, so the endpoint is
3017
+ * appended once the server knows it. An explicit `oauth` config is left as
3018
+ * it is.
3019
+ */
3020
+ #useEndpointAsResource(endpoint) {
3021
+ const oauth = this.#options.oauth;
3022
+ if (this.#providerResourceBase === void 0 || !oauth?.protectedResource)
3023
+ return;
3024
+ this.#options = {
3025
+ ...this.#options,
3026
+ oauth: {
3027
+ ...oauth,
3028
+ protectedResource: {
3029
+ ...oauth.protectedResource,
3030
+ resource: `${this.#providerResourceBase.replace(/\/+$/, "")}${normalizePath(endpoint)}`
3031
+ }
3032
+ }
3033
+ };
3034
+ }
2933
3035
  };
2934
3036
 
2935
3037
  export {
@@ -2946,4 +3048,4 @@ export {
2946
3048
  FastMCPSession,
2947
3049
  FastMCP
2948
3050
  };
2949
- //# sourceMappingURL=chunk-QMRS4ZLD.js.map
3051
+ //# sourceMappingURL=chunk-XFUILY54.js.map