@prefecthq/fastmcp-ts 1.7.2 → 1.8.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/dist/cli/index.cjs +300 -140
- package/dist/cli/index.cjs.map +1 -1
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/server.d.ts +71 -3
- package/dist/server.js +161 -22
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/server.d.ts
CHANGED
|
@@ -903,8 +903,9 @@ interface FastMCPOptions {
|
|
|
903
903
|
* Bearer-token verifier, or a RequestVerifier for header-based
|
|
904
904
|
* (trusted-proxy) deployments. See each type's docs. A RequestVerifier
|
|
905
905
|
* applies to HTTP transports only; stdio's FASTMCP_CLI_AUTH_TOKEN needs a
|
|
906
|
-
* TokenVerifier.
|
|
907
|
-
*
|
|
906
|
+
* TokenVerifier. Wrap either in `remoteAuth(...)` to also serve RFC 9728
|
|
907
|
+
* protected resource metadata over HTTP. When both `oauth` and `auth` are
|
|
908
|
+
* set, `oauth` serves the endpoint and `auth` is ignored.
|
|
908
909
|
*/
|
|
909
910
|
auth?: TokenVerifier | RequestVerifier;
|
|
910
911
|
/** Full OAuth 2.1 server with Dynamic Client Registration support. */
|
|
@@ -1156,6 +1157,7 @@ declare class FastMCP {
|
|
|
1156
1157
|
private _templateResources;
|
|
1157
1158
|
private _prompts;
|
|
1158
1159
|
private _customRoutes;
|
|
1160
|
+
private _protectedResourceRoutePath;
|
|
1159
1161
|
private _promptsPageSize;
|
|
1160
1162
|
private _middleware;
|
|
1161
1163
|
private _transforms;
|
|
@@ -1507,6 +1509,7 @@ declare class FastMCP {
|
|
|
1507
1509
|
private _runHttpOAuth;
|
|
1508
1510
|
private _runHttpSimple;
|
|
1509
1511
|
close(): Promise<void>;
|
|
1512
|
+
private _unregisterProtectedResourceRoute;
|
|
1510
1513
|
}
|
|
1511
1514
|
|
|
1512
1515
|
declare class Image {
|
|
@@ -1527,6 +1530,71 @@ declare class ToolResult {
|
|
|
1527
1530
|
|
|
1528
1531
|
declare function multiAuth(...verifiers: TokenVerifier[]): TokenVerifier;
|
|
1529
1532
|
|
|
1533
|
+
/** Options for {@link remoteAuth}. */
|
|
1534
|
+
interface RemoteAuthOptions<V extends TokenVerifier | RequestVerifier> {
|
|
1535
|
+
/** Verifier that checks each request's credentials, such as `jwtVerifier(...)`. */
|
|
1536
|
+
verifier: V;
|
|
1537
|
+
/** Issuer URLs of the authorization servers that mint tokens for this server. */
|
|
1538
|
+
authorizationServers: Array<string | URL>;
|
|
1539
|
+
/** Public URL clients use to reach this server, without the MCP path. */
|
|
1540
|
+
baseUrl: string | URL;
|
|
1541
|
+
/** Scopes to advertise in the metadata document and in 401 challenges. */
|
|
1542
|
+
scopesSupported?: string[];
|
|
1543
|
+
/** Human-readable name of this server, shown by some clients. */
|
|
1544
|
+
resourceName?: string;
|
|
1545
|
+
/** URL of documentation for this server. */
|
|
1546
|
+
resourceDocumentation?: string | URL;
|
|
1547
|
+
}
|
|
1548
|
+
/** Validated protected-resource settings that `run()` serves as metadata. */
|
|
1549
|
+
interface ProtectedResourceConfig {
|
|
1550
|
+
readonly baseUrl: URL;
|
|
1551
|
+
readonly authorizationServers: readonly string[];
|
|
1552
|
+
readonly scopesSupported?: readonly string[];
|
|
1553
|
+
readonly resourceName?: string;
|
|
1554
|
+
readonly resourceDocumentation?: string;
|
|
1555
|
+
}
|
|
1556
|
+
/** A verifier that also carries {@link ProtectedResourceConfig}. */
|
|
1557
|
+
type RemoteAuthProvider<V extends TokenVerifier | RequestVerifier> = V & {
|
|
1558
|
+
readonly protectedResource: ProtectedResourceConfig;
|
|
1559
|
+
};
|
|
1560
|
+
/**
|
|
1561
|
+
* Make this server an OAuth resource server for an external authorization
|
|
1562
|
+
* server. Pass the result to `FastMCPOptions.auth`.
|
|
1563
|
+
*
|
|
1564
|
+
* Use it when another service issues the tokens, such as Auth0, Keycloak or
|
|
1565
|
+
* Okta, and this server only verifies them. `verifier` checks each request, as
|
|
1566
|
+
* with `jwtVerifier` or `introspectionVerifier`. `remoteAuth` adds the
|
|
1567
|
+
* discovery information that lets MCP clients find the authorization server on
|
|
1568
|
+
* their own.
|
|
1569
|
+
*
|
|
1570
|
+
* The resource URL is `baseUrl` joined with the path given to `run()`. For a
|
|
1571
|
+
* `baseUrl` of `https://mcp.example.com` and the default path `/mcp`, the
|
|
1572
|
+
* resource is `https://mcp.example.com/mcp`. Set `baseUrl` to the public URL
|
|
1573
|
+
* that clients use, such as the address of your reverse proxy. A wrong value
|
|
1574
|
+
* makes the advertised resource differ from the URL clients call.
|
|
1575
|
+
*
|
|
1576
|
+
* Over HTTP the server then serves the RFC 9728 metadata document at
|
|
1577
|
+
* `/.well-known/oauth-protected-resource` followed by the MCP path. Each 401
|
|
1578
|
+
* response also carries a `WWW-Authenticate: Bearer` challenge whose
|
|
1579
|
+
* `resource_metadata` parameter points to that document.
|
|
1580
|
+
*
|
|
1581
|
+
* If you set both this and `oauth`, `oauth` takes precedence.
|
|
1582
|
+
*
|
|
1583
|
+
* @example
|
|
1584
|
+
* ```ts
|
|
1585
|
+
* const mcp = new FastMCP({
|
|
1586
|
+
* name: 'my-server',
|
|
1587
|
+
* auth: remoteAuth({
|
|
1588
|
+
* verifier: jwtVerifier({ jwksUri: 'https://auth.example.com/.well-known/jwks.json' }),
|
|
1589
|
+
* authorizationServers: ['https://auth.example.com'],
|
|
1590
|
+
* baseUrl: 'https://mcp.example.com',
|
|
1591
|
+
* }),
|
|
1592
|
+
* })
|
|
1593
|
+
* ```
|
|
1594
|
+
*/
|
|
1595
|
+
declare function remoteAuth(options: RemoteAuthOptions<TokenVerifier>): RemoteAuthProvider<TokenVerifier>;
|
|
1596
|
+
declare function remoteAuth(options: RemoteAuthOptions<RequestVerifier>): RemoteAuthProvider<RequestVerifier>;
|
|
1597
|
+
|
|
1530
1598
|
interface JwtVerifierOptions {
|
|
1531
1599
|
jwksUri: string;
|
|
1532
1600
|
issuer?: string;
|
|
@@ -1970,4 +2038,4 @@ declare class FormInput {
|
|
|
1970
2038
|
constructor(options: FormInputOptions);
|
|
1971
2039
|
}
|
|
1972
2040
|
|
|
1973
|
-
export { type AccessToken, Approval, Area, type AuthCheck, AuthorizationError, type BackendToolConfig, Badge, Bar, type BrowserPermissions, Button, type CacheKeyFn, CachingMiddleware, CancellationMiddleware, type CatalogEntry, Choice, Column, type CompleteCallback, type CompletionContext, type CompletionResult, type Component, type ComponentFn, type CorsOptions, type CspPolicy, type CustomRouteConfig, type CustomRouteHandler, type ElicitationResult, type ElicitationSchema, type EntrypointConfig, ErrorNormalizationMiddleware, FastMCP, FastMCPApp, type FastMCPAppOptions, type FastMCPOptions, File, type FileHandle, type FileStorageAdapter, FileUpload, type FileUploadOptions, FilterTransform, ForEach, FormInput, type FormInputOptions, type FrameworkLogLevel, GenerativeUI, Grid, type HTTPRoute, type HealthOptions, type HttpMethod, type HttpRequestContext, If, type IfNode, Image, Input, Line, type LogLevel, type Logger, LoggingMiddleware, type MCPType, type McpContext, type Middleware, type MiddlewareContext, NamespaceTransform, type Next, type OAuthConfig, type OAuthProviderOptions, type OAuthProxyOptions, type OpenAPIClientOptions, type OpenAPIComponent, type OpenAPIResourceComponent, type OpenAPIResourceTemplateComponent, type OpenAPIServerOptions, type OpenAPIToolComponent, type ParameterInfo, Pie, type PromptArgument, type PromptConfig, type PromptContent, type PromptMessage, PromptResult, type PromptView, PromptsAsTools, type ProxyTransport, RateLimitingMiddleware, type RequestBodyInfo, type RequestVerifier, type ResourceAnnotations, type ResourceConfig, ResourceResult, type ResourceUiMeta, type ResourceView, ResourcesAsTools, type ResponseInfo, type Root, type RouteMap, type RouteMapFn, Row, type RunOptions, Rx, type SamplingMessage, type SamplingParams, type SamplingResult, Select, type ServerAddress, SizeLimitingMiddleware, type SynthesizedTool, Table, Text, type TokenVerifier, type ToolAnnotations, type ToolConfig, ToolResult, type ToolView, type Transform, type UiToolMeta, VersionFilter, type Visibility, actionRef, createOpenAPIServer, createProxy, debugTokenVerifier, forwardableHeaders, introspectionVerifier, jwtVerifier, multiAuth, oauthProvider, oauthProxy, redescribeTool, renameTool, requireScopes, staticTokenVerifier };
|
|
2041
|
+
export { type AccessToken, Approval, Area, type AuthCheck, AuthorizationError, type BackendToolConfig, Badge, Bar, type BrowserPermissions, Button, type CacheKeyFn, CachingMiddleware, CancellationMiddleware, type CatalogEntry, Choice, Column, type CompleteCallback, type CompletionContext, type CompletionResult, type Component, type ComponentFn, type CorsOptions, type CspPolicy, type CustomRouteConfig, type CustomRouteHandler, type ElicitationResult, type ElicitationSchema, type EntrypointConfig, ErrorNormalizationMiddleware, FastMCP, FastMCPApp, type FastMCPAppOptions, type FastMCPOptions, File, type FileHandle, type FileStorageAdapter, FileUpload, type FileUploadOptions, FilterTransform, ForEach, FormInput, type FormInputOptions, type FrameworkLogLevel, GenerativeUI, Grid, type HTTPRoute, type HealthOptions, type HttpMethod, type HttpRequestContext, If, type IfNode, Image, Input, Line, type LogLevel, type Logger, LoggingMiddleware, type MCPType, type McpContext, type Middleware, type MiddlewareContext, NamespaceTransform, type Next, type OAuthConfig, type OAuthProviderOptions, type OAuthProxyOptions, type OpenAPIClientOptions, type OpenAPIComponent, type OpenAPIResourceComponent, type OpenAPIResourceTemplateComponent, type OpenAPIServerOptions, type OpenAPIToolComponent, type ParameterInfo, Pie, type PromptArgument, type PromptConfig, type PromptContent, type PromptMessage, PromptResult, type PromptView, PromptsAsTools, type ProtectedResourceConfig, type ProxyTransport, RateLimitingMiddleware, type RemoteAuthOptions, type RemoteAuthProvider, type RequestBodyInfo, type RequestVerifier, type ResourceAnnotations, type ResourceConfig, ResourceResult, type ResourceUiMeta, type ResourceView, ResourcesAsTools, type ResponseInfo, type Root, type RouteMap, type RouteMapFn, Row, type RunOptions, Rx, type SamplingMessage, type SamplingParams, type SamplingResult, Select, type ServerAddress, SizeLimitingMiddleware, type SynthesizedTool, Table, Text, type TokenVerifier, type ToolAnnotations, type ToolConfig, ToolResult, type ToolView, type Transform, type UiToolMeta, VersionFilter, type Visibility, actionRef, createOpenAPIServer, createProxy, debugTokenVerifier, forwardableHeaders, introspectionVerifier, jwtVerifier, multiAuth, oauthProvider, oauthProxy, redescribeTool, remoteAuth, renameTool, requireScopes, staticTokenVerifier };
|
package/dist/server.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
|
|
5
5
|
// src/server/FastMCP.ts
|
|
6
6
|
import { hostHeaderValidation, originValidation } from "@modelcontextprotocol/node";
|
|
7
|
-
import { ProtocolError as ProtocolError3, ProtocolErrorCode as ProtocolErrorCode3, ResourceNotFoundError, Server as Server2, createMcpHandler, legacyStatelessFallback, isLegacyRequest, isJsonContentType, createRequestStateCodec, localhostAllowedHostnames, localhostAllowedOrigins, assertCompleteRequestPrompt, assertCompleteRequestResourceTemplate } from "@modelcontextprotocol/server";
|
|
7
|
+
import { ProtocolError as ProtocolError3, ProtocolErrorCode as ProtocolErrorCode3, ResourceNotFoundError, Server as Server2, createMcpHandler, legacyStatelessFallback, isLegacyRequest, isJsonContentType, createRequestStateCodec, localhostAllowedHostnames, localhostAllowedOrigins, getOAuthProtectedResourceMetadataUrl as getOAuthProtectedResourceMetadataUrl2, assertCompleteRequestPrompt, assertCompleteRequestResourceTemplate } from "@modelcontextprotocol/server";
|
|
8
8
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9
9
|
|
|
10
10
|
// src/server/auth/types.ts
|
|
@@ -18,6 +18,98 @@ var AuthorizationError = class extends Error {
|
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
// src/server/auth/remote.ts
|
|
22
|
+
import {
|
|
23
|
+
getOAuthProtectedResourceMetadataUrl
|
|
24
|
+
} from "@modelcontextprotocol/server";
|
|
25
|
+
var SCOPE_TOKEN = /^[\x21\x23-\x5B\x5D-\x7E]+$/;
|
|
26
|
+
function parseHttpUrl(value, option) {
|
|
27
|
+
let url;
|
|
28
|
+
try {
|
|
29
|
+
url = new URL(value);
|
|
30
|
+
} catch {
|
|
31
|
+
throw new Error(`remoteAuth: ${option} must be a valid URL, got "${String(value)}"`);
|
|
32
|
+
}
|
|
33
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
34
|
+
throw new Error(`remoteAuth: ${option} must use http or https, got "${String(value)}"`);
|
|
35
|
+
}
|
|
36
|
+
return url;
|
|
37
|
+
}
|
|
38
|
+
function remoteAuth(options) {
|
|
39
|
+
const { verifier } = options;
|
|
40
|
+
const baseUrl = parseHttpUrl(options.baseUrl, "baseUrl");
|
|
41
|
+
if (baseUrl.search !== "" || baseUrl.hash !== "") {
|
|
42
|
+
throw new Error("remoteAuth: baseUrl must not have a query string or fragment");
|
|
43
|
+
}
|
|
44
|
+
if (options.authorizationServers.length === 0) {
|
|
45
|
+
throw new Error("remoteAuth: authorizationServers must contain at least one entry");
|
|
46
|
+
}
|
|
47
|
+
const authorizationServers = options.authorizationServers.map((entry) => {
|
|
48
|
+
const url = parseHttpUrl(entry, "authorizationServers entry");
|
|
49
|
+
return typeof entry === "string" ? entry : url.href;
|
|
50
|
+
});
|
|
51
|
+
for (const scope of options.scopesSupported ?? []) {
|
|
52
|
+
if (!SCOPE_TOKEN.test(scope)) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`remoteAuth: scopesSupported entry "${scope}" must not be empty or contain spaces, quotes or backslashes`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
let resourceDocumentation;
|
|
59
|
+
if (options.resourceDocumentation !== void 0) {
|
|
60
|
+
try {
|
|
61
|
+
resourceDocumentation = new URL(options.resourceDocumentation).href;
|
|
62
|
+
} catch {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`remoteAuth: resourceDocumentation must be a valid URL, got "${String(options.resourceDocumentation)}"`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const protectedResource = {
|
|
69
|
+
baseUrl,
|
|
70
|
+
authorizationServers,
|
|
71
|
+
...options.scopesSupported && { scopesSupported: [...options.scopesSupported] },
|
|
72
|
+
...options.resourceName !== void 0 && { resourceName: options.resourceName },
|
|
73
|
+
...resourceDocumentation !== void 0 && { resourceDocumentation }
|
|
74
|
+
};
|
|
75
|
+
if (isRequestVerifier(verifier)) {
|
|
76
|
+
return { verifyRequest: (request) => verifier.verifyRequest(request), protectedResource };
|
|
77
|
+
}
|
|
78
|
+
return { verify: (token) => verifier.verify(token), protectedResource };
|
|
79
|
+
}
|
|
80
|
+
function getProtectedResource(auth) {
|
|
81
|
+
return auth?.protectedResource;
|
|
82
|
+
}
|
|
83
|
+
function resolveResourceUrl(baseUrl, mcpPath) {
|
|
84
|
+
const base = baseUrl.href.replace(/\/+$/, "");
|
|
85
|
+
return new URL(`${base}/${mcpPath.replace(/^\/+/, "")}`);
|
|
86
|
+
}
|
|
87
|
+
function protectedResourceRoute(config, mcpPath) {
|
|
88
|
+
const resourceUrl = resolveResourceUrl(config.baseUrl, mcpPath);
|
|
89
|
+
const metadataUrl = getOAuthProtectedResourceMetadataUrl(resourceUrl);
|
|
90
|
+
return {
|
|
91
|
+
path: new URL(metadataUrl).pathname,
|
|
92
|
+
metadataUrl,
|
|
93
|
+
metadata: {
|
|
94
|
+
resource: resourceUrl.href,
|
|
95
|
+
authorization_servers: [...config.authorizationServers],
|
|
96
|
+
...config.scopesSupported && { scopes_supported: [...config.scopesSupported] },
|
|
97
|
+
bearer_methods_supported: ["header"],
|
|
98
|
+
...config.resourceName !== void 0 && { resource_name: config.resourceName },
|
|
99
|
+
...config.resourceDocumentation !== void 0 && {
|
|
100
|
+
resource_documentation: config.resourceDocumentation
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function bearerChallenge(config, error) {
|
|
106
|
+
const parts = ['realm="mcp"'];
|
|
107
|
+
if (error) parts.push(`error="${error}"`);
|
|
108
|
+
if (config.scopes && config.scopes.length > 0) parts.push(`scope="${config.scopes.join(" ")}"`);
|
|
109
|
+
parts.push(`resource_metadata="${config.resourceMetadataUrl}"`);
|
|
110
|
+
return `Bearer ${parts.join(", ")}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
21
113
|
// src/server/legacyEventStore.ts
|
|
22
114
|
import { randomUUID } from "crypto";
|
|
23
115
|
var DEFAULT_MAX_EVENTS = 256;
|
|
@@ -312,6 +404,10 @@ var CustomRouteRegistry = class {
|
|
|
312
404
|
}
|
|
313
405
|
this._routes.set(path, { methods: new Set(methods), handler });
|
|
314
406
|
}
|
|
407
|
+
/** Remove the route at `path`, if any. */
|
|
408
|
+
unregister(path) {
|
|
409
|
+
this._routes.delete(path);
|
|
410
|
+
}
|
|
315
411
|
/** Throws when a registered route sits on the MCP endpoint path. Called at HTTP startup. */
|
|
316
412
|
assertNoMcpCollision(mcpPath) {
|
|
317
413
|
if (this._routes.has(mcpPath)) {
|
|
@@ -996,6 +1092,9 @@ function isUiCapable(clientCapabilities) {
|
|
|
996
1092
|
const uiCapability = clientCapabilities?.extensions?.[UI_EXTENSION_KEY];
|
|
997
1093
|
return !!uiCapability?.mimeTypes?.includes(UI_RESOURCE_MIME_TYPE);
|
|
998
1094
|
}
|
|
1095
|
+
function shouldServeUi(clientCapabilities) {
|
|
1096
|
+
return clientCapabilities === void 0 || isUiCapable(clientCapabilities);
|
|
1097
|
+
}
|
|
999
1098
|
|
|
1000
1099
|
// src/server/prompt.ts
|
|
1001
1100
|
var PromptResult = class {
|
|
@@ -1483,6 +1582,9 @@ var FastMCP = class {
|
|
|
1483
1582
|
_templateResources = /* @__PURE__ */ new Map();
|
|
1484
1583
|
_prompts = /* @__PURE__ */ new Map();
|
|
1485
1584
|
_customRoutes = new CustomRouteRegistry();
|
|
1585
|
+
// The metadata route that run() registers for remoteAuth; close() removes it
|
|
1586
|
+
// so that a later run() can register it again.
|
|
1587
|
+
_protectedResourceRoutePath = null;
|
|
1486
1588
|
_promptsPageSize;
|
|
1487
1589
|
_middleware;
|
|
1488
1590
|
_transforms;
|
|
@@ -1609,7 +1711,7 @@ var FastMCP = class {
|
|
|
1609
1711
|
return contextStore.run(
|
|
1610
1712
|
ctx,
|
|
1611
1713
|
() => runMiddlewareChain(this._middleware, "tools/list", req.params, ctx, async () => {
|
|
1612
|
-
const clientIsUiCapable =
|
|
1714
|
+
const clientIsUiCapable = shouldServeUi(server.getClientCapabilities());
|
|
1613
1715
|
const allVisible = (await Promise.all(
|
|
1614
1716
|
[...this._tools.values()].map(async (t) => {
|
|
1615
1717
|
if (t.config.disabled) return null;
|
|
@@ -1761,11 +1863,10 @@ var FastMCP = class {
|
|
|
1761
1863
|
let resultValue = await executePromise;
|
|
1762
1864
|
if (resolvedTool.config.output) resultValue = await validateInput(resolvedTool.config.output, resultValue);
|
|
1763
1865
|
const callResult = convertResult(resultValue, opts?.stateless, this._logger);
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
}
|
|
1866
|
+
const uiGateClosed = resolvedTool.config.ui !== void 0 && !shouldServeUi(server.getClientCapabilities()) && !resolvedTool.config.output && !resolvedTool.config.outputSchema;
|
|
1867
|
+
if (uiGateClosed && callResult.structuredContent !== void 0) {
|
|
1868
|
+
const { structuredContent: _dropped, ...textResult } = callResult;
|
|
1869
|
+
return textResult;
|
|
1769
1870
|
}
|
|
1770
1871
|
return callResult;
|
|
1771
1872
|
})
|
|
@@ -1808,7 +1909,7 @@ var FastMCP = class {
|
|
|
1808
1909
|
}
|
|
1809
1910
|
const page = transformedResources.slice(startIdx, startIdx + pageSize);
|
|
1810
1911
|
const nextCursor = startIdx + pageSize < transformedResources.length ? Buffer.from(page[page.length - 1].uri).toString("base64url") : void 0;
|
|
1811
|
-
const clientIsUiCapableRes =
|
|
1912
|
+
const clientIsUiCapableRes = shouldServeUi(server.getClientCapabilities());
|
|
1812
1913
|
return {
|
|
1813
1914
|
resources: page.map((r) => ({
|
|
1814
1915
|
uri: r.uri,
|
|
@@ -1899,7 +2000,7 @@ var FastMCP = class {
|
|
|
1899
2000
|
}
|
|
1900
2001
|
const result = await executePromise;
|
|
1901
2002
|
const converted = convertResourceResult(result, requestedUri, resource.config.mimeType, opts?.stateless);
|
|
1902
|
-
return attachResourceUiMeta(converted, resource.config.ui,
|
|
2003
|
+
return attachResourceUiMeta(converted, resource.config.ui, shouldServeUi(server.getClientCapabilities()));
|
|
1903
2004
|
})
|
|
1904
2005
|
);
|
|
1905
2006
|
});
|
|
@@ -2743,11 +2844,29 @@ var FastMCP = class {
|
|
|
2743
2844
|
)
|
|
2744
2845
|
);
|
|
2745
2846
|
}
|
|
2746
|
-
this.
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2847
|
+
const protectedResource = this._oauth ? void 0 : getProtectedResource(this._auth);
|
|
2848
|
+
let challenge;
|
|
2849
|
+
if (protectedResource) {
|
|
2850
|
+
const route = protectedResourceRoute(protectedResource, path);
|
|
2851
|
+
this._customRoutes.register(
|
|
2852
|
+
{ path: route.path },
|
|
2853
|
+
(request) => Response.json(route.metadata, {
|
|
2854
|
+
headers: this._cors ? corsResponseHeaders(this._cors, request.headers.get("origin") ?? void 0, this._logger) : {}
|
|
2855
|
+
})
|
|
2856
|
+
);
|
|
2857
|
+
this._protectedResourceRoutePath = route.path;
|
|
2858
|
+
challenge = { resourceMetadataUrl: route.metadataUrl, scopes: protectedResource.scopesSupported };
|
|
2859
|
+
}
|
|
2860
|
+
try {
|
|
2861
|
+
this._customRoutes.assertNoMcpCollision(path);
|
|
2862
|
+
if (this._oauth) {
|
|
2863
|
+
await this._runHttpOAuth(port, host, path);
|
|
2864
|
+
} else {
|
|
2865
|
+
await this._runHttpSimple(port, host, path, challenge);
|
|
2866
|
+
}
|
|
2867
|
+
} catch (err) {
|
|
2868
|
+
this._unregisterProtectedResourceRoute();
|
|
2869
|
+
throw err;
|
|
2751
2870
|
}
|
|
2752
2871
|
}
|
|
2753
2872
|
}
|
|
@@ -2911,16 +3030,19 @@ var FastMCP = class {
|
|
|
2911
3030
|
});
|
|
2912
3031
|
const bound = httpServer.address();
|
|
2913
3032
|
const issuerUrl = oauth.issuerUrl ?? new URL(`http://${bound.address}:${bound.port}`);
|
|
3033
|
+
const resourceServerUrl = new URL(path, issuerUrl);
|
|
3034
|
+
const resourceMetadataUrl = getOAuthProtectedResourceMetadataUrl2(resourceServerUrl);
|
|
2914
3035
|
app.use(
|
|
2915
3036
|
mcpAuthRouter({
|
|
2916
3037
|
provider: oauth.provider,
|
|
2917
3038
|
issuerUrl,
|
|
3039
|
+
resourceServerUrl,
|
|
2918
3040
|
scopesSupported: oauth.scopes
|
|
2919
3041
|
})
|
|
2920
3042
|
);
|
|
2921
3043
|
app.all(
|
|
2922
3044
|
path,
|
|
2923
|
-
requireBearerAuth({ verifier: oauth.provider }),
|
|
3045
|
+
requireBearerAuth({ verifier: oauth.provider, resourceMetadataUrl }),
|
|
2924
3046
|
async (req, res) => {
|
|
2925
3047
|
await this._dispatchHttp(req, res);
|
|
2926
3048
|
}
|
|
@@ -2931,7 +3053,7 @@ var FastMCP = class {
|
|
|
2931
3053
|
this._logger.startupBanner({ name: this.name, version: this.version, transport: "http", url });
|
|
2932
3054
|
this._logger.listening(url);
|
|
2933
3055
|
}
|
|
2934
|
-
async _runHttpSimple(port, host, path) {
|
|
3056
|
+
async _runHttpSimple(port, host, path, challenge) {
|
|
2935
3057
|
this._resolveDnsRebindingGuards(host);
|
|
2936
3058
|
const { createServer } = await import("http");
|
|
2937
3059
|
const auth = this._auth;
|
|
@@ -2964,7 +3086,10 @@ var FastMCP = class {
|
|
|
2964
3086
|
if (err instanceof AuthorizationError) {
|
|
2965
3087
|
res.writeHead(403, { "Content-Type": "application/json" }).end(JSON.stringify({ error: err.message }));
|
|
2966
3088
|
} else {
|
|
2967
|
-
res.writeHead(401, {
|
|
3089
|
+
res.writeHead(401, {
|
|
3090
|
+
"Content-Type": "application/json",
|
|
3091
|
+
...challenge && { "WWW-Authenticate": bearerChallenge(challenge) }
|
|
3092
|
+
}).end(JSON.stringify({ error: err instanceof Error ? err.message : "Authentication failed" }));
|
|
2968
3093
|
}
|
|
2969
3094
|
return;
|
|
2970
3095
|
}
|
|
@@ -2988,7 +3113,10 @@ var FastMCP = class {
|
|
|
2988
3113
|
const authHeader = req.headers.authorization;
|
|
2989
3114
|
const bearer = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
2990
3115
|
if (!bearer) {
|
|
2991
|
-
res.writeHead(401, {
|
|
3116
|
+
res.writeHead(401, {
|
|
3117
|
+
"Content-Type": "application/json",
|
|
3118
|
+
"WWW-Authenticate": challenge ? bearerChallenge(challenge) : 'Bearer realm="mcp"'
|
|
3119
|
+
}).end(JSON.stringify({ error: "Missing bearer token" }));
|
|
2992
3120
|
return;
|
|
2993
3121
|
}
|
|
2994
3122
|
try {
|
|
@@ -3004,7 +3132,10 @@ var FastMCP = class {
|
|
|
3004
3132
|
if (err instanceof AuthorizationError) {
|
|
3005
3133
|
res.writeHead(403, { "Content-Type": "application/json" }).end(JSON.stringify({ error: err.message }));
|
|
3006
3134
|
} else {
|
|
3007
|
-
res.writeHead(401, {
|
|
3135
|
+
res.writeHead(401, {
|
|
3136
|
+
"Content-Type": "application/json",
|
|
3137
|
+
"WWW-Authenticate": challenge ? bearerChallenge(challenge, "invalid_token") : 'Bearer realm="mcp"'
|
|
3138
|
+
}).end(JSON.stringify({ error: err instanceof Error ? err.message : "Authentication failed" }));
|
|
3008
3139
|
}
|
|
3009
3140
|
return;
|
|
3010
3141
|
}
|
|
@@ -3044,6 +3175,7 @@ var FastMCP = class {
|
|
|
3044
3175
|
this._stdioState = null;
|
|
3045
3176
|
this._stdioModern = false;
|
|
3046
3177
|
}
|
|
3178
|
+
this._unregisterProtectedResourceRoute();
|
|
3047
3179
|
if (this._httpServer) {
|
|
3048
3180
|
await new Promise((resolve, reject) => {
|
|
3049
3181
|
this._httpServer.close((err) => err != null ? reject(err) : resolve());
|
|
@@ -3053,6 +3185,12 @@ var FastMCP = class {
|
|
|
3053
3185
|
this._address = null;
|
|
3054
3186
|
await this._primaryServer.close();
|
|
3055
3187
|
}
|
|
3188
|
+
_unregisterProtectedResourceRoute() {
|
|
3189
|
+
if (this._protectedResourceRoutePath) {
|
|
3190
|
+
this._customRoutes.unregister(this._protectedResourceRoutePath);
|
|
3191
|
+
this._protectedResourceRoutePath = null;
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3056
3194
|
};
|
|
3057
3195
|
|
|
3058
3196
|
// src/server/auth/authorization.ts
|
|
@@ -3189,7 +3327,7 @@ function debugTokenVerifier() {
|
|
|
3189
3327
|
|
|
3190
3328
|
// src/server/auth/oauth/provider.ts
|
|
3191
3329
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3192
|
-
import { UnsupportedGrantTypeError } from "@modelcontextprotocol/server-legacy/auth";
|
|
3330
|
+
import { InvalidTokenError, UnsupportedGrantTypeError } from "@modelcontextprotocol/server-legacy/auth";
|
|
3193
3331
|
function oauthProvider(options = {}) {
|
|
3194
3332
|
const clients = /* @__PURE__ */ new Map();
|
|
3195
3333
|
const codes = /* @__PURE__ */ new Map();
|
|
@@ -3250,8 +3388,8 @@ function oauthProvider(options = {}) {
|
|
|
3250
3388
|
},
|
|
3251
3389
|
async verifyAccessToken(token) {
|
|
3252
3390
|
const data = tokens.get(token);
|
|
3253
|
-
if (!data) throw new
|
|
3254
|
-
if (data.expiresAt < Math.floor(Date.now() / 1e3)) throw new
|
|
3391
|
+
if (!data) throw new InvalidTokenError("Invalid token");
|
|
3392
|
+
if (data.expiresAt < Math.floor(Date.now() / 1e3)) throw new InvalidTokenError("Token has expired");
|
|
3255
3393
|
return {
|
|
3256
3394
|
token,
|
|
3257
3395
|
clientId: data.clientId,
|
|
@@ -5646,6 +5784,7 @@ export {
|
|
|
5646
5784
|
oauthProvider,
|
|
5647
5785
|
oauthProxy,
|
|
5648
5786
|
redescribeTool,
|
|
5787
|
+
remoteAuth,
|
|
5649
5788
|
renameTool,
|
|
5650
5789
|
requireScopes,
|
|
5651
5790
|
staticTokenVerifier
|