fastmcp 3.20.1 → 3.21.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 +11 -9
- package/dist/FastMCP.d.ts +32 -1
- package/dist/FastMCP.js +92 -3
- package/dist/FastMCP.js.map +1 -1
- package/package.json +4 -1
- package/.github/workflows/feature.yaml +0 -39
- package/.github/workflows/main.yaml +0 -50
- package/.prettierignore +0 -1
- package/.roo/mcp.json +0 -11
- package/eslint.config.ts +0 -14
- package/jsr.json +0 -7
- package/src/FastMCP.oauth.test.ts +0 -225
- package/src/FastMCP.session-context.test.ts +0 -136
- package/src/FastMCP.session-id.test.ts +0 -359
- package/src/FastMCP.test.ts +0 -4389
- package/src/FastMCP.ts +0 -2548
- package/src/bin/fastmcp.ts +0 -191
- package/src/examples/addition.ts +0 -333
- package/src/examples/custom-logger.ts +0 -201
- package/src/examples/oauth-server.ts +0 -113
- package/src/examples/session-context.ts +0 -269
- package/src/examples/session-id-counter.ts +0 -230
- package/tsconfig.json +0 -8
- package/vitest.config.js +0 -9
package/README.md
CHANGED
|
@@ -1316,10 +1316,15 @@ In this example, only clients authenticating with the `admin` role will be able
|
|
|
1316
1316
|
FastMCP includes built-in support for OAuth discovery endpoints, supporting both **MCP Specification 2025-03-26** and **MCP Specification 2025-06-18** for OAuth integration. This makes it easy to integrate with OAuth authorization flows by providing standard discovery endpoints that comply with RFC 8414 (OAuth 2.0 Authorization Server Metadata) and RFC 9470 (OAuth 2.0 Protected Resource Metadata):
|
|
1317
1317
|
|
|
1318
1318
|
```ts
|
|
1319
|
-
import { FastMCP } from "fastmcp";
|
|
1319
|
+
import { FastMCP, DiscoveryDocumentCache } from "fastmcp";
|
|
1320
1320
|
import { buildGetJwks } from "get-jwks";
|
|
1321
1321
|
import fastJwt from "fast-jwt";
|
|
1322
1322
|
|
|
1323
|
+
// Create a cache for discovery documents (reuse across requests)
|
|
1324
|
+
const discoveryCache = new DiscoveryDocumentCache({
|
|
1325
|
+
ttl: 3600000, // Cache for 1 hour (default)
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1323
1328
|
const server = new FastMCP({
|
|
1324
1329
|
name: "My Server",
|
|
1325
1330
|
version: "1.0.0",
|
|
@@ -1351,19 +1356,16 @@ const server = new FastMCP({
|
|
|
1351
1356
|
|
|
1352
1357
|
// Validate OAuth JWT access token using OpenID Connect discovery
|
|
1353
1358
|
try {
|
|
1354
|
-
//
|
|
1355
|
-
// Discover OAuth/OpenID configuration from well-known endpoint
|
|
1359
|
+
// Fetch and cache the discovery document
|
|
1356
1360
|
const discoveryUrl =
|
|
1357
1361
|
"https://auth.example.com/.well-known/openid-configuration";
|
|
1358
1362
|
// Alternative: Use OAuth authorization server metadata endpoint
|
|
1359
1363
|
// const discoveryUrl = 'https://auth.example.com/.well-known/oauth-authorization-server';
|
|
1360
1364
|
|
|
1361
|
-
const
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
const config = await discoveryResponse.json();
|
|
1365
|
+
const config = (await discoveryCache.get(discoveryUrl)) as {
|
|
1366
|
+
jwks_uri: string;
|
|
1367
|
+
issuer: string;
|
|
1368
|
+
};
|
|
1367
1369
|
const jwksUri = config.jwks_uri;
|
|
1368
1370
|
const issuer = config.issuer;
|
|
1369
1371
|
|
package/dist/FastMCP.d.ts
CHANGED
|
@@ -10,6 +10,37 @@ import http from 'http';
|
|
|
10
10
|
import { StrictEventEmitter } from 'strict-event-emitter-types';
|
|
11
11
|
import { z } from 'zod';
|
|
12
12
|
|
|
13
|
+
declare class DiscoveryDocumentCache {
|
|
14
|
+
#private;
|
|
15
|
+
get size(): number;
|
|
16
|
+
/**
|
|
17
|
+
* @param options - configuration options
|
|
18
|
+
* @param options.ttl - time-to-live in miliseconds
|
|
19
|
+
*/
|
|
20
|
+
constructor(options?: {
|
|
21
|
+
ttl?: number;
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* @param url - optional URL to clear. if omitted, clears all cached documents.
|
|
25
|
+
*/
|
|
26
|
+
clear(url?: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* fetches a discovery document from the given URL.
|
|
29
|
+
* uses cached value if available and not expired.
|
|
30
|
+
* coalesces concurrent requests for the same URL to prevent duplicate fetches.
|
|
31
|
+
*
|
|
32
|
+
* @param url - the discovery document URL (e.g., /.well-known/openid-configuration)
|
|
33
|
+
* @returns the discovery document as a JSON object
|
|
34
|
+
* @throws Error if the fetch fails or returns non-OK status
|
|
35
|
+
*/
|
|
36
|
+
get(url: string): Promise<unknown>;
|
|
37
|
+
/**
|
|
38
|
+
* @param url - the URL to check
|
|
39
|
+
* @returns true if the URL is cached and nott expired
|
|
40
|
+
*/
|
|
41
|
+
has(url: string): boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
13
44
|
interface Logger {
|
|
14
45
|
debug(...args: unknown[]): void;
|
|
15
46
|
error(...args: unknown[]): void;
|
|
@@ -648,4 +679,4 @@ declare class FastMCP<T extends FastMCPSessionAuth = FastMCPSessionAuth> extends
|
|
|
648
679
|
stop(): Promise<void>;
|
|
649
680
|
}
|
|
650
681
|
|
|
651
|
-
export { type AudioContent, type Content, type ContentResult, type Context, FastMCP, type FastMCPEvents, FastMCPSession, type FastMCPSessionEvents, type ImageContent, type InputPrompt, type InputPromptArgument, type Logger, type LoggingLevel, type Progress, type Prompt, type PromptArgument, type Resource, type ResourceContent, type ResourceResult, type ResourceTemplate, type ResourceTemplateArgument, type SSEServer, type SerializableValue, type ServerOptions, type TextContent, type Tool, type ToolParameters, UnexpectedStateError, UserError, audioContent, imageContent };
|
|
682
|
+
export { type AudioContent, type Content, type ContentResult, type Context, DiscoveryDocumentCache, FastMCP, type FastMCPEvents, FastMCPSession, type FastMCPSessionEvents, type ImageContent, type InputPrompt, type InputPromptArgument, type Logger, type LoggingLevel, type Progress, type Prompt, type PromptArgument, type Resource, type ResourceContent, type ResourceResult, type ResourceTemplate, type ResourceTemplateArgument, type SSEServer, type SerializableValue, type ServerOptions, type TextContent, type Tool, type ToolParameters, UnexpectedStateError, UserError, audioContent, imageContent };
|
package/dist/FastMCP.js
CHANGED
|
@@ -20,16 +20,104 @@ import { readFile } from "fs/promises";
|
|
|
20
20
|
import Fuse from "fuse.js";
|
|
21
21
|
import { startHTTPServer } from "mcp-proxy";
|
|
22
22
|
import { setTimeout as delay } from "timers/promises";
|
|
23
|
-
import { fetch } from "undici";
|
|
23
|
+
import { fetch as fetch2 } from "undici";
|
|
24
24
|
import parseURITemplate from "uri-templates";
|
|
25
25
|
import { toJsonSchema } from "xsschema";
|
|
26
26
|
import { z } from "zod";
|
|
27
|
+
|
|
28
|
+
// src/DiscoveryDocumentCache.ts
|
|
29
|
+
var DiscoveryDocumentCache = class {
|
|
30
|
+
get size() {
|
|
31
|
+
return this.#cache.size;
|
|
32
|
+
}
|
|
33
|
+
#cache = /* @__PURE__ */ new Map();
|
|
34
|
+
#inFlight = /* @__PURE__ */ new Map();
|
|
35
|
+
#ttl;
|
|
36
|
+
/**
|
|
37
|
+
* @param options - configuration options
|
|
38
|
+
* @param options.ttl - time-to-live in miliseconds
|
|
39
|
+
*/
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
this.#ttl = options.ttl ?? 36e5;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* @param url - optional URL to clear. if omitted, clears all cached documents.
|
|
45
|
+
*/
|
|
46
|
+
clear(url) {
|
|
47
|
+
if (url) {
|
|
48
|
+
this.#cache.delete(url);
|
|
49
|
+
} else {
|
|
50
|
+
this.#cache.clear();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* fetches a discovery document from the given URL.
|
|
55
|
+
* uses cached value if available and not expired.
|
|
56
|
+
* coalesces concurrent requests for the same URL to prevent duplicate fetches.
|
|
57
|
+
*
|
|
58
|
+
* @param url - the discovery document URL (e.g., /.well-known/openid-configuration)
|
|
59
|
+
* @returns the discovery document as a JSON object
|
|
60
|
+
* @throws Error if the fetch fails or returns non-OK status
|
|
61
|
+
*/
|
|
62
|
+
async get(url) {
|
|
63
|
+
const now = Date.now();
|
|
64
|
+
const cached = this.#cache.get(url);
|
|
65
|
+
if (cached && cached.expiresAt > now) {
|
|
66
|
+
return cached.data;
|
|
67
|
+
}
|
|
68
|
+
const inFlight = this.#inFlight.get(url);
|
|
69
|
+
if (inFlight) {
|
|
70
|
+
return inFlight;
|
|
71
|
+
}
|
|
72
|
+
const fetchPromise = this.#fetchAndCache(url);
|
|
73
|
+
this.#inFlight.set(url, fetchPromise);
|
|
74
|
+
try {
|
|
75
|
+
const data = await fetchPromise;
|
|
76
|
+
return data;
|
|
77
|
+
} finally {
|
|
78
|
+
this.#inFlight.delete(url);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* @param url - the URL to check
|
|
83
|
+
* @returns true if the URL is cached and nott expired
|
|
84
|
+
*/
|
|
85
|
+
has(url) {
|
|
86
|
+
const cached = this.#cache.get(url);
|
|
87
|
+
if (!cached) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
if (cached.expiresAt <= now) {
|
|
92
|
+
this.#cache.delete(url);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
async #fetchAndCache(url) {
|
|
98
|
+
const res = await fetch(url);
|
|
99
|
+
if (!res.ok) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Failed to fetch discovery document from ${url}: ${res.status} ${res.statusText}`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
const data = await res.json();
|
|
105
|
+
const expiresAt = Date.now() + this.#ttl;
|
|
106
|
+
this.#cache.set(url, {
|
|
107
|
+
data,
|
|
108
|
+
expiresAt
|
|
109
|
+
});
|
|
110
|
+
return data;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// src/FastMCP.ts
|
|
27
115
|
var imageContent = async (input) => {
|
|
28
116
|
let rawData;
|
|
29
117
|
try {
|
|
30
118
|
if ("url" in input) {
|
|
31
119
|
try {
|
|
32
|
-
const response = await
|
|
120
|
+
const response = await fetch2(input.url);
|
|
33
121
|
if (!response.ok) {
|
|
34
122
|
throw new Error(
|
|
35
123
|
`Server responded with status: ${response.status} - ${response.statusText}`
|
|
@@ -82,7 +170,7 @@ var audioContent = async (input) => {
|
|
|
82
170
|
try {
|
|
83
171
|
if ("url" in input) {
|
|
84
172
|
try {
|
|
85
|
-
const response = await
|
|
173
|
+
const response = await fetch2(input.url);
|
|
86
174
|
if (!response.ok) {
|
|
87
175
|
throw new Error(
|
|
88
176
|
`Server responded with status: ${response.status} - ${response.statusText}`
|
|
@@ -1382,6 +1470,7 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1382
1470
|
}
|
|
1383
1471
|
};
|
|
1384
1472
|
export {
|
|
1473
|
+
DiscoveryDocumentCache,
|
|
1385
1474
|
FastMCP,
|
|
1386
1475
|
FastMCPSession,
|
|
1387
1476
|
UnexpectedStateError,
|