Skip to content

Commit ee221a7

Browse files
authored
feat: add missing OAuth Protected Resource Metadata fields (#156)
Add all missing optional OAuth Protected Resource Metadata fields as specified in RFC 9728: - scopesSupported - serviceDocumentation - resourceSigningAlgValuesSupported - resourceName - resourceTosUri - tlsClientCertificateBoundAccessTokens - authorizationDetailsTypesSupported - dpopSigningAlgValuesSupported - dpopBoundAccessTokensRequired Add index signature support for vendor-specific extensions Update documentation with RFC and MCP specification references Add tests for all new fields including dynamic properties Resolves #155
1 parent 48c9639 commit ee221a7

File tree

3 files changed

+211
-5
lines changed

3 files changed

+211
-5
lines changed

src/FastMCP.oauth.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,28 @@ describe("FastMCP OAuth Support", () => {
7474
oauth: {
7575
enabled: true,
7676
protectedResource: {
77+
authorizationDetailsTypesSupported: ["payment_initiation"],
7778
authorizationServers: ["https://auth.example.com"],
7879
bearerMethodsSupported: ["header"],
80+
dpopBoundAccessTokensRequired: true,
81+
dpopSigningAlgValuesSupported: ["ES256", "RS256"],
7982
jwksUri: "https://test-server.example.com/.well-known/jwks.json",
8083
resource: "mcp://test-server",
8184
resourceDocumentation: "https://docs.example.com/api",
85+
resourceName: "Test API",
86+
resourcePolicyUri: "https://test-server.example.com/policy",
87+
resourceSigningAlgValuesSupported: ["RS256"],
88+
resourceTosUri: "https://test-server.example.com/tos",
89+
scopesSupported: ["read", "write", "admin"],
90+
serviceDocumentation: "https://developer.example.com/api",
91+
tlsClientCertificateBoundAccessTokens: false,
92+
vendorPrefix_complexObject: {
93+
nestedArray: [1, 2, 3],
94+
nestedProperty: "nested value",
95+
},
96+
// Vendor extensions (dynamic properties)
97+
vendorPrefix_customField: "custom value",
98+
x_api_version: "2.0",
8299
},
83100
},
84101
version: "1.0.0",
@@ -110,6 +127,37 @@ describe("FastMCP OAuth Support", () => {
110127
expect(metadata.resource_documentation).toBe(
111128
"https://docs.example.com/api",
112129
);
130+
131+
// New fields added for RFC 9728 compliance
132+
expect(metadata.authorization_details_types_supported).toEqual([
133+
"payment_initiation",
134+
]);
135+
expect(metadata.dpop_bound_access_tokens_required).toBe(true);
136+
expect(metadata.dpop_signing_alg_values_supported).toEqual([
137+
"ES256",
138+
"RS256",
139+
]);
140+
expect(metadata.resource_name).toBe("Test API");
141+
expect(metadata.resource_policy_uri).toBe(
142+
"https://test-server.example.com/policy",
143+
);
144+
expect(metadata.resource_signing_alg_values_supported).toEqual(["RS256"]);
145+
expect(metadata.resource_tos_uri).toBe(
146+
"https://test-server.example.com/tos",
147+
);
148+
expect(metadata.scopes_supported).toEqual(["read", "write", "admin"]);
149+
expect(metadata.service_documentation).toBe(
150+
"https://developer.example.com/api",
151+
);
152+
expect(metadata.tls_client_certificate_bound_access_tokens).toBe(false);
153+
154+
// Vendor extensions (dynamic properties)
155+
expect(metadata.vendor_prefix_custom_field).toBe("custom value");
156+
expect(metadata.vendor_prefix_complex_object).toEqual({
157+
nestedArray: [1, 2, 3],
158+
nestedProperty: "nested value",
159+
});
160+
expect(metadata.x_api_version).toBe("2.0");
113161
} finally {
114162
await server.stop();
115163
}

src/FastMCP.ts

Lines changed: 151 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -624,20 +624,162 @@ type ServerOptions<T extends FastMCPSessionAuth> = {
624624
enabled: boolean;
625625

626626
/**
627-
* OAuth Protected Resource metadata for /.well-known/oauth-protected-resource
627+
* OAuth Protected Resource metadata for `/.well-known/oauth-protected-resource`
628628
*
629-
* This endpoint follows RFC 9470 (OAuth 2.0 Protected Resource Metadata)
630-
* and provides metadata about the OAuth 2.0 protected resource.
629+
* This endpoint follows {@link https://www.rfc-editor.org/rfc/rfc9728.html | RFC 9728}
630+
* and provides metadata describing how an OAuth 2.0 protected resource (in this case,
631+
* an MCP server) expects to be accessed.
631632
*
632-
* Required by MCP Specification 2025-06-18
633+
* When configured, FastMCP will automatically serve this metadata at the
634+
* `/.well-known/oauth-protected-resource` endpoint. The `authorizationServers` and `resource`
635+
* fields are required. All others are optional and will be omitted from the published
636+
* metadata if not specified.
637+
*
638+
* This satisfies the requirements of the MCP Authorization specification's
639+
* {@link https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization#authorization-server-location | Authorization Server Location section}.
640+
*
641+
* Clients consuming this metadata MUST validate that any presented values comply with
642+
* RFC 9728, including strict validation of the `resource` identifier and intended audience
643+
* when access tokens are issued and presented (per RFC 8707 §2).
644+
*
645+
* @remarks Required by MCP Specification version 2025-06-18
633646
*/
634647
protectedResource?: {
648+
/**
649+
* Allows for additional metadata fields beyond those defined in RFC 9728.
650+
*
651+
* @remarks This supports vendor-specific or experimental extensions.
652+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2.3 | RFC 9728 §2.3}
653+
*/
654+
[key: string]: unknown;
655+
656+
/**
657+
* Supported values for the `authorization_details` parameter (RFC 9396).
658+
*
659+
* @remarks Used when fine-grained access control is in play.
660+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2-2.23 | RFC 9728 §2.2.23}
661+
*/
662+
authorizationDetailsTypesSupported?: string[];
663+
664+
/**
665+
* List of OAuth 2.0 authorization server issuer identifiers.
666+
*
667+
* These correspond to ASes that can issue access tokens for this protected resource.
668+
* MCP clients use these values to locate the relevant `/.well-known/oauth-authorization-server`
669+
* metadata for initiating the OAuth flow.
670+
*
671+
* @remarks Required by the MCP spec. MCP servers MUST provide at least one issuer.
672+
* Clients are responsible for choosing among them (see RFC 9728 §7.6).
673+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2-2.3 | RFC 9728 §2.2.3}
674+
*/
635675
authorizationServers: string[];
676+
677+
/**
678+
* List of supported methods for presenting OAuth 2.0 bearer tokens.
679+
*
680+
* @remarks Valid values are `header`, `body`, and `query`.
681+
* If omitted, clients MAY assume only `header` is supported, per RFC 6750.
682+
* This is a client-side interpretation and not a serialization default.
683+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2-2.9 | RFC 9728 §2.2.9}
684+
*/
636685
bearerMethodsSupported?: string[];
686+
687+
/**
688+
* Whether this resource requires all access tokens to be DPoP-bound.
689+
*
690+
* @remarks If omitted, clients SHOULD assume this is `false`.
691+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2-2.27 | RFC 9728 §2.2.27}
692+
*/
693+
dpopBoundAccessTokensRequired?: boolean;
694+
695+
/**
696+
* Supported algorithms for verifying DPoP proofs (RFC 9449).
697+
*
698+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2-2.25 | RFC 9728 §2.2.25}
699+
*/
700+
dpopSigningAlgValuesSupported?: string[];
701+
702+
/**
703+
* JWKS URI of this resource. Used to validate access tokens or sign responses.
704+
*
705+
* @remarks When present, this MUST be an `https:` URI pointing to a valid JWK Set (RFC 7517).
706+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2-2.5 | RFC 9728 §2.2.5}
707+
*/
637708
jwksUri?: string;
709+
710+
/**
711+
* Canonical OAuth resource identifier for this protected resource (the MCP server).
712+
*
713+
* @remarks Typically the base URL of the MCP server. Clients MUST use this as the
714+
* `resource` parameter in authorization and token requests (per RFC 8707).
715+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2-2.1 | RFC 9728 §2.2.1}
716+
*/
638717
resource: string;
718+
719+
/**
720+
* URL to developer-accessible documentation for this resource.
721+
*
722+
* @remarks This field MAY be localized.
723+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2-2.15 | RFC 9728 §2.2.15}
724+
*/
639725
resourceDocumentation?: string;
726+
727+
/**
728+
* Human-readable name for display purposes (e.g., in UIs).
729+
*
730+
* @remarks This field MAY be localized using language tags (`resource_name#en`, etc.).
731+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2-2.13 | RFC 9728 §2.2.13}
732+
*/
733+
resourceName?: string;
734+
735+
/**
736+
* URL to a human-readable policy page describing acceptable use.
737+
*
738+
* @remarks This field MAY be localized.
739+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2-2.17 | RFC 9728 §2.2.17}
740+
*/
640741
resourcePolicyUri?: string;
742+
743+
/**
744+
* Supported JWS algorithms for signed responses from this resource (e.g., response signing).
745+
*
746+
* @remarks MUST NOT include `none`.
747+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2-2.11 | RFC 9728 §2.2.11}
748+
*/
749+
resourceSigningAlgValuesSupported?: string[];
750+
751+
/**
752+
* URL to the protected resource’s Terms of Service.
753+
*
754+
* @remarks This field MAY be localized.
755+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2-2.19 | RFC 9728 §2.2.19}
756+
*/
757+
resourceTosUri?: string;
758+
759+
/**
760+
* Supported OAuth scopes for requesting access to this resource.
761+
*
762+
* @remarks Useful for discovery, but clients SHOULD still request the minimal scope required.
763+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2-2.7 | RFC 9728 §2.2.7}
764+
*/
765+
scopesSupported?: string[];
766+
767+
/**
768+
* Developer-accessible documentation for how to use the service (not end-user docs).
769+
*
770+
* @remarks Semantically equivalent to `resourceDocumentation`, but included under its
771+
* alternate name for compatibility with tools or schemas expecting either.
772+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2-2.15 | RFC 9728 §2.2.15}
773+
*/
774+
serviceDocumentation?: string;
775+
776+
/**
777+
* Whether mutual-TLS-bound access tokens are required.
778+
*
779+
* @remarks If omitted, clients SHOULD assume this is `false` (client-side behavior).
780+
* @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-2-2.21 | RFC 9728 §2.2.21}
781+
*/
782+
tlsClientCertificateBoundAccessTokens?: boolean;
641783
};
642784
};
643785

@@ -1451,7 +1593,11 @@ export class FastMCPSession<
14511593
"[FastMCP debug] listRoots method not supported by client",
14521594
);
14531595
} else {
1454-
console.error("[FastMCP error] Error listing roots", error);
1596+
console.error(
1597+
`[FastMCP error] received error listing roots.\n\n${
1598+
error instanceof Error ? error.stack : JSON.stringify(error)
1599+
}`,
1600+
);
14551601
}
14561602
});
14571603
},

src/examples/oauth-server.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,24 @@ const server = new FastMCP({
4646
},
4747
enabled: true,
4848
protectedResource: {
49+
authorizationDetailsTypesSupported: [
50+
"payment_initiation",
51+
"account_access",
52+
],
4953
authorizationServers: ["https://auth.example.com"],
5054
bearerMethodsSupported: ["header"],
55+
dpopBoundAccessTokensRequired: false,
56+
dpopSigningAlgValuesSupported: ["ES256", "RS256"],
5157
jwksUri: "https://oauth-example-server.example.com/.well-known/jwks.json",
5258
resource: "mcp://oauth-example-server",
5359
resourceDocumentation: "https://docs.example.com/mcp-api",
60+
resourceName: "OAuth Example API",
5461
resourcePolicyUri: "https://example.com/resource-policy",
62+
resourceSigningAlgValuesSupported: ["RS256", "ES256"],
63+
resourceTosUri: "https://example.com/terms-of-service",
64+
scopesSupported: ["read", "write", "admin"],
65+
serviceDocumentation: "https://developer.example.com/api-docs",
66+
tlsClientCertificateBoundAccessTokens: false,
5567
},
5668
},
5769
version: "1.0.0",

0 commit comments

Comments
 (0)