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
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,67 @@ This attribute may be placed on a method to provide for the tool, prompt, or res
XML comments may also be used; if an `[McpServerTool]`, `[McpServerPrompt]`, or `[McpServerResource]`-attributed method is marked as `partial`,
XML comments placed on the method will be used automatically to generate `[Description]` attributes for the method and its parameters.

## Enterprise Auth / Enterprise Managed Authorization (SEP-990)

The SDK provides Enterprise Auth utilities for the Identity Assertion Authorization Grant flow (SEP-990),
enabling enterprise SSO scenarios where users authenticate once via their enterprise Identity Provider and
access MCP servers without per-server authorization prompts.

The flow consists of two token operations:
1. **RFC 8693 Token Exchange** at the IdP: ID Token → JWT Authorization Grant (JAG)
2. **RFC 7523 JWT Bearer Grant** at the MCP Server: JAG → Access Token

### Using the Layer 2 utilities directly

```csharp
using ModelContextProtocol.Authentication;

// Step 1: Exchange ID token for a JAG at the enterprise IdP
var jag = await EnterpriseAuth.DiscoverAndRequestJwtAuthorizationGrantAsync(
new DiscoverAndRequestJwtAuthGrantOptions
{
IdpUrl = "https://company.okta.com",
Audience = "https://auth.mcp-server.example.com",
Resource = "https://mcp-server.example.com",
IdToken = myIdToken, // obtained via SSO/OIDC login
ClientId = "idp-client-id",
});

// Step 2: Exchange JAG for an access token at the MCP authorization server
var tokens = await EnterpriseAuth.ExchangeJwtBearerGrantAsync(
new ExchangeJwtBearerGrantOptions
{
TokenEndpoint = "https://auth.mcp-server.example.com/token",
Assertion = jag,
ClientId = "mcp-client-id",
});
```

### Using the EnterpriseAuthProvider (Layer 3)

```csharp
var provider = new EnterpriseAuthProvider(new EnterpriseAuthProviderOptions
{
ClientId = "mcp-client-id",
AssertionCallback = async (context, ct) =>
{
return await EnterpriseAuth.DiscoverAndRequestJwtAuthorizationGrantAsync(
new DiscoverAndRequestJwtAuthGrantOptions
{
IdpUrl = "https://company.okta.com",
Audience = context.AuthorizationServerUrl.ToString(),
Resource = context.ResourceUrl.ToString(),
IdToken = myIdToken,
ClientId = "idp-client-id",
}, ct);
}
});

var tokens = await provider.GetAccessTokenAsync(
resourceUrl: new Uri("https://mcp-server.example.com"),
authorizationServerUrl: new Uri("https://auth.mcp-server.example.com"));
Copy link
Contributor

@halter73 halter73 Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We experimented with adding some of these OAuth flows in #1178, but we haven't added it yet since it's an Auth Extension rather than a primary part of the spec. See https://github.com/modelcontextprotocol/ext-auth/blob/5c50488e96e242b4d09c2b97ae47fa152d96a836/specification/draft/enterprise-managed-authorization.mdx and https://github.com/modelcontextprotocol/conformance/blob/066b2d70800a8bd6bca82e000007c9877b443e5b/src/scenarios/client/auth/index.ts#L50-L55.

I do like that this doesn't pollute the ClientOAuthOptions like #1178 did, but I wonder if this wouldn't be better as a separate package. It looks like you could use these APIs to get the access token for any HttpClient needing to support these OAuth flows. It seems like something with more features like Duende.AccessTokenManagement.OpenIdConnect might be a better general solution for this than something tied specifically to the MCP C# SDK.

Copy link
Author

@aniket-okta aniket-okta Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review @halter73 ! Good points - I want to address the packaging question.

Why I placed this in ModelContextProtocol.Core:

  • This was requested in Implement SEP-990: Enterprise Managed Authorization (Extension) #949, filed by the MCP spec team and tracked on the project board. Both the Go SDK (go-sdk#770) and TS SDK (typescript-sdk#1531) include this in their main packages - not as separate packages.
  • While the underlying RFCs (8693, 7523) are generic, the implementation is MCP-specific - it uses the urn:ietf:params:oauth:token-type:id-jag token type, MCP resource URIs, and the EnterpriseAuthProvider is built around the MCP orchestrator context (ResourceUrl, AuthorizationServerUrl).
  • Re: Duende - it handles OIDC token lifecycle for ASP.NET Core apps, but doesn't implement the ID-JAG token exchange or the MCP-specific JWT bearer grant pattern. Users would still need to orchestrate the full flow manually.
  • There are conformance tests (CrossAppAccessCompleteFlowScenario) for this flow already.

That said, I'm happy to move this to a separate package (e.g. ModelContextProtocol.Auth.Enterprise) if the team prefers that pattern for auth extensions. The code is self-contained with no dependencies on SDK internals, so it would be a straightforward move. What do you think?

```

## Acknowledgements

The starting point for this library was a project called [mcpdotnet](https://github.com/PederHP/mcpdotnet), initiated by [Peder Holdgaard Pedersen](https://github.com/PederHP). We are grateful for the work done by Peder and other contributors to that repository, which created a solid foundation for this library.
Expand Down
Loading