Skip to content

Add SDK MCP OAuth host token handlers#1669

Draft
roji wants to merge 5 commits into
mainfrom
roji/roji-mcp-auth-sdk
Draft

Add SDK MCP OAuth host token handlers#1669
roji wants to merge 5 commits into
mainfrom
roji/roji-mcp-auth-sdk

Conversation

@roji

@roji roji commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add SDK-facing MCP OAuth host-token callbacks across Node, Python, Go, .NET, Java, and Rust.
  • Add generated RPC/event surfaces for session.mcp.oauth.handlePendingRequest, mcp.oauth_required, and mcp.oauth_completed.
  • Thread optional mcp.oauth_required.data.resourceMetadata through generated event DTOs and public MCP auth request handler models across SDK languages.
  • Preserve optional behavior for metadata and parsed wwwAuthenticateParams when older runtimes omit them.
  • Register mcp.oauth_required interest only when the high-level MCP auth callback is configured.
  • Add Node E2E coverage with a fake OAuth-protected HTTP MCP server fixture.

Notes

  • Draft while the corresponding runtime work lands: github/copilot-agent-runtime#10195.
  • The top local-runtime validation commit is intentionally temporary: it points the Node MCP OAuth E2E at the local sibling runtime checkout for development validation and should be removed before final merge.

Validation

  • Installed local validation dependencies: Go, Maven/JDK, uv, and user-local .NET 8 SDK/runtime.
  • cd nodejs && npm run typecheck && npx vitest run test/client.test.ts -t "MCP OAuth|McpAuth" passed.
  • cd nodejs && npx prettier --check src/generated/session-events.ts src/types.ts test/client.test.ts test/e2e/mcp_oauth.e2e.test.ts ../test/harness/test-mcp-oauth-server.mjs passed.
  • Rebuilt sibling runtime at /Users/roji/.copilot/repos/copilot-worktrees/copilot-agent-runtime/roji-ubiquitous-parakeet with pnpm run build.
  • cd nodejs && npx vitest run test/e2e/mcp_oauth.e2e.test.ts passed against the rebuilt sibling runtime.
  • cd go && go test ./... passed.
  • cd python && uv run --extra dev pytest ../python/test_client.py -k mcp_auth passed.
  • cd dotnet && DOTNET_ROOT="$HOME/.dotnet" PATH="$HOME/.dotnet:$PATH" dotnet test test/GitHub.Copilot.SDK.Test.csproj --filter "FullyQualifiedName~SessionEventSerializationTests" passed.
  • cd java && mvn spotless:apply && mvn verify passed.
  • cd rust && cargo test --features test-support --test session_test mcp_oauth_required_data_allows_optional_metadata passed.

roji and others added 2 commits June 14, 2026 14:03
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Auto-committed by java-codegen-check workflow.
@github-actions github-actions Bot added the dependencies Pull requests that update a dependency file label Jun 14, 2026
Comment thread python/copilot/client.py
InfiniteSessionConfig,
LargeToolOutputConfig,
MCPServerConfig,
McpAuthHandler,
MCPOauthPendingRequestResponse = Union[MCPOauthPendingRequestResponseCancelled, MCPOauthPendingRequestResponseToken]


def _load_mcp_oauth_pending_request_response(obj: Any) -> "MCPOauthPendingRequestResponse":
@github-actions

Copy link
Copy Markdown
Contributor

Cross-SDK Consistency Review

Feature coverage: ✅ All six SDKs covered

The MCP OAuth host-token callback is implemented across Node.js, Python, Go, .NET, Java, and Rust in this PR. The feature is broadly consistent, with the API shaped appropriately for each language's idioms.


🐛 Bug found — Rust SessionConfig::fmt (inline comment above)

mcp_auth_handler is registered twice in the Debug impl for SessionConfig (rust/src/types.rs, lines 1466–1469 duplicate 1462–1465). The second block should be deleted.


Minor design variation worth noting (not blocking)

requestId exposure to the handler callback differs across SDKs:

SDK requestId accessible to handler?
Node.js ✅ in McpAuthRequest.requestId
Python ✅ in McpAuthRequest["requestId"]
Go ✅ in MCPAuthRequest.RequestID
Java ✅ in McpAuthRequest.requestId()
Rust ✅ as separate request_id: RequestId parameter
.NET ❌ not exposed in McpAuthContext

requestId is internal routing data that users typically don't need. The .NET McpAuthContext design (hiding it) is arguably cleaner, but if there's ever a need for host-side correlation of concurrent OAuth requests, the .NET handler would be unable to distinguish them. This may be fine by design — just worth a deliberate call.


Everything else looks consistent ✅

  • Handler registration pattern (opt-in interest registration for mcp.oauth_required) is identical across all 6 SDKs.
  • McpAuthResult cancellation semantics are consistent (each language uses its idiomatic pattern: enum in Rust, nullable in .NET, discriminated union in Node/Python, struct in Go, record in Java).
  • Both create_session / resume_session paths register interest in all SDKs.
  • Error-path fallback to cancellation on handler exceptions is present in all SDKs.

Generated by SDK Consistency Review Agent for issue #1669 · sonnet46 2.1M ·

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Generated by SDK Consistency Review Agent for issue #1669 · sonnet46 2.1M

Comment thread rust/src/types.rs
Comment on lines +1466 to +1469
.field(
"mcp_auth_handler",
&self.mcp_auth_handler.as_ref().map(|_| "<set>"),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Duplicate Debug field — Rust SessionConfig

The mcp_auth_handler field is emitted twice in the Debug implementation (lines 1462–1465 and 1466–1469 are identical). The second block should be removed; otherwise debug/trace output will contain duplicate keys and DebugStruct::finish() may behave unexpectedly with some formatters.

// Remove this duplicate block (lines 1466-1469):
.field(
    "mcp_auth_handler",
    &self.mcp_auth_handler.as_ref().map(|_| "<set>"),
)

Comment thread dotnet/src/Session.cs
Comment on lines +763 to +773
catch (Exception)
{
try
{
await Rpc.Mcp.Oauth.HandlePendingRequestAsync(requestId, new McpOauthPendingRequestResponseCancelled());
}
catch (Exception rpcEx) when (rpcEx is IOException or ObjectDisposedException)
{
// Connection lost or RPC error — nothing we can do.
}
}
roji and others added 3 commits June 17, 2026 18:38
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread nodejs/src/types.ts
Comment on lines +1559 to +1565
export interface McpAuthStaticClientConfig {
/** OAuth client ID for the server. */
clientId: string;
/** Optional non-default OAuth grant type. */
grantType?: "client_credentials";
/** Whether this is a public OAuth client. */
publicClient?: boolean;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

what about the clientSecret? A lot of AS's use client secrets even for public clients. (VS Code allows specifying a client secret, stored in secret storage)

Comment thread nodejs/src/types.ts
Comment on lines +1585 to +1594
export interface McpAuthToken {
/** Access token acquired by the SDK host. */
accessToken: string;
/** OAuth token type. Defaults to Bearer when omitted. */
tokenType?: string;
/** Refresh token supplied by the host, if available. */
refreshToken?: string;
/** Token lifetime in seconds, if known. */
expiresIn?: number;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

what about an idToken? idTokens are useful as they contain claims that are good to display like usernames/email/etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants