Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci-build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
- name: 🔧 Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20'
node-version: '22'

- name: 📦 Install pinned npm dependencies for tests
run: npm ci
Expand Down
33 changes: 33 additions & 0 deletions docs/concepts/transports/transports.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,39 @@ var transport = new HttpClientTransport(new HttpClientTransportOptions
});
```

#### OAuth PKCE metadata validation (1.x)

When using <xref:ModelContextProtocol.Authentication.ClientOAuthOptions> with an HTTP transport, authorization server metadata must advertise `S256` in `code_challenge_methods_supported`. A document that omits that field or advertises only other methods is skipped while the client tries alternative well-known endpoints. If none supplies valid metadata with `S256`, authentication fails before requesting an authorization code or exchanging tokens. This is a compatibility tightening: servers that implement PKCE but do not advertise it must update their metadata.

When protected resource metadata is available, authorization server metadata must also include an `issuer` that exactly matches the selected authorization server identifier, as required by [RFC 8414, Section 3.3](https://www.rfc-editor.org/rfc/rfc8414.html#section-3.3). Comparison is ordinal and uses the original URI strings: differences in case, trailing slashes, or percent-encoding are rejected even when URI normalization would make them equivalent. A missing or mismatched issuer immediately stops discovery before authorization or token exchange; the client does not try another metadata endpoint or use synthetic defaults. For the MCP 2025-03-26 legacy path without protected resource metadata, these issuer checks remain exempt because discovery uses the MCP server's origin, which need not equal the canonical issuer. PKCE requirements still apply on that path.

These are **metadata issuer** checks, not RFC 9207 authorization-response `iss` validation. The 1.x code-only callback cannot provide a response issuer for SDK validation; that separate response check is not included. PKCE is not a substitute for issuer validation.

The 1.x SDK's authorization callback returns only a code, so the SDK cannot validate response `state`. [RFC 9700, Section 4.7.1](https://www.rfc-editor.org/rfc/rfc9700.html#section-4.7.1) permits relying on PKCE for CSRF protection only when authorization server support has been ensured. Metadata verification checks **advertised support**, not the server's actual implementation. The authorization server must correctly enforce PKCE, including the [downgrade defenses in Section 4.8.2](https://www.rfc-editor.org/rfc/rfc9700.html#section-4.8.2).

**Legacy compatibility escape hatch.** By default, the 1.x client also rejects authentication when authorization server metadata cannot be discovered; it no longer assumes `S256` support. For a trusted legacy deployment whose PKCE support you have independently established, you can explicitly restore the MCP 2025-03-26 default endpoint fallback at application startup:

```csharp
AppContext.SetSwitch(
"ModelContextProtocol.Authentication.AllowLegacyOAuthMetadataFallback",
true);
```

Alternatively, configure the same switch in the application's project file:

```xml
<ItemGroup>
<RuntimeHostConfigurationOption
Include="ModelContextProtocol.Authentication.AllowLegacyOAuthMetadataFallback"
Value="true" />
</ItemGroup>
```

The switch is **disabled when absent or `false`**. When enabled, it restores default `/authorize`, `/token`, and `/register` paths on the MCP server's origin only when protected resource metadata is unavailable and no usable authorization server metadata document was discovered. It never bypasses PKCE validation of a discovered document. The client still sends an `S256` challenge and a code verifier, and logs a warning whenever it uses the unverified fallback.

> [!WARNING]
> Enabling this switch does not verify PKCE support or add state validation. Use it only after independently establishing that your trusted authorization server correctly enforces PKCE, including downgrade defenses. The setting is **process-wide**, not a per-server allowlist: it affects every OAuth client in the process. Do not enable it in a process that also connects to untrusted authorization servers, or toggle it around individual requests. Prefer fixing metadata discovery and advertising `S256` so the switch can remain disabled.

#### Resuming sessions

Streamable HTTP supports session resumption. Save the session ID, server capabilities, and server info from the original session, then use <xref:ModelContextProtocol.Client.McpClient.ResumeSessionAsync*> to reconnect:
Expand Down
227 changes: 222 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"private": true,
"description": "Pinned npm dependencies for MCP C# SDK integration and conformance tests",
"dependencies": {
"@modelcontextprotocol/conformance": "0.1.13",
"@modelcontextprotocol/conformance": "0.1.15",
"@modelcontextprotocol/server-everything": "2026.1.26",
"@modelcontextprotocol/server-memory": "2026.1.26"
}
Expand Down
2 changes: 2 additions & 0 deletions samples/ProtectedMcpClient/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ The client is configured with:
- **OAuth Server**: `https://localhost:7029`
- **Protected Resource**: `http://localhost:7071`

The authorization server must advertise `S256` PKCE support in its metadata. When protected resource metadata is available, its metadata `issuer` must also exactly match the selected authorization server identifier. The test server satisfies both requirements by default. The 1.x SDK rejects missing PKCE advertisement and, by default, unavailable authorization server metadata; see [OAuth PKCE metadata validation](../../docs/concepts/transports/transports.md#oauth-pkce-metadata-validation-1x) for the issuer matching rules, narrowly scoped process-wide legacy compatibility switch, and security requirements.

## Available Tools

Once authenticated, the client can access weather tools including:
Expand Down
13 changes: 13 additions & 0 deletions src/ModelContextProtocol.Core/Authentication/ClientOAuthOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@ namespace ModelContextProtocol.Authentication;
/// <summary>
/// Provides configuration options for the <see cref="ClientOAuthProvider"/>.
/// </summary>
/// <remarks>
/// Authorization server metadata must advertise the <c>S256</c> PKCE method. If metadata discovery fails,
/// authorization fails before requesting an authorization code or exchanging tokens. When protected resource
/// metadata is available, the authorization server metadata issuer must exactly match the selected authorization
/// server identifier, without normalizing URI case, trailing slashes, or percent-encoding. Missing or mismatched
/// issuers immediately stop discovery. These issuer checks are skipped for the 2025-03-26 legacy path without
/// protected resource metadata, where discovery uses the server origin rather than the canonical issuer. For trusted legacy
/// deployments without protected resource metadata and without usable authorization server metadata, the process-wide
/// <c>ModelContextProtocol.Authentication.AllowLegacyOAuthMetadataFallback</c> <see cref="AppContext"/> switch
/// can explicitly enable the 2025-03-26 default endpoint fallback. It is disabled by default and does not
/// bypass PKCE validation of discovered metadata. Hosts enabling it must independently establish that
/// their authorization servers correctly enforce PKCE, including downgrade defenses.
/// </remarks>
public sealed class ClientOAuthOptions
{
/// <summary>
Expand Down
Loading
Loading