Skip to content

feat(auth): add enterprise refresh-token and ID-JAG exchanges - #1234

Merged
alexhancock merged 3 commits into
modelcontextprotocol:mainfrom
nicksteele-oai:dev/steele/ema-upstream
Sep 4, 2026
Merged

feat(auth): add enterprise refresh-token and ID-JAG exchanges#1234
alexhancock merged 3 commits into
modelcontextprotocol:mainfrom
nicksteele-oai:dev/steele/ema-upstream

Conversation

@nicksteele-oai

@nicksteele-oai nicksteele-oai commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

This PR adds the EMA/XAA token exchange flow behind auth-enterprise-managed: enterprise refresh token → ID-JAG → MCP access token. It covers the refresh-token portion of the request for support in #531.

Each authorization server has its own client authentication configuration. Clients can use a secret or a fresh JWT assertion from an application-provided signer. Public clients remain supported where explicitly allowed.

Scope

The exchange targets one MCP resource and preserves scope narrowing. The SDK checks ID-JAG structure and claim bindings. The resource authorization server verifies the signature.

Applications may supply approved server metadata and client registrations. Discovery, login, credential storage, automatic refresh, and reauth remain application responsibilities. JWT signing stays with the application. RAR and DPoP remain unsupported; nonempty authorization_details are rejected.

Testing

Local validation passed for revision dc26f27a8228:

  • 211 OAuth tests with all features on Rust 1.96.
  • 50 doctests passed; 10 ignored.
  • Rust 1.88 minimal-feature check passed (--no-default-features --features auth-enterprise-managed).
  • Formatting, strict rmcp Clippy (--all-targets --all-features), and commitlint passed.
  • The MCP connection example compiles with its documented features.

Tests use mocked responses and local HTTP fixtures.

Breaking Changes

None intended and existing OAuth behavior is unchanged.

Breaking Changes

I've added a PR to extend conformance test coverage for EMA.

Conformance Test Results against rust-sdk#1234

@github-actions github-actions Bot added T-documentation Documentation improvements T-dependencies Dependencies related changes T-test Testing related changes T-config Configuration file changes T-core Core library changes T-transport Transport layer changes labels Sep 1, 2026
Redeem ID-JAGs at the approved resource authorization server and return
its bearer token, lifetime, and effective granted scopes. Preserve scope
narrowing and redacted diagnostics, and reuse the default HTTP adapter.

Support independently configured client authentication at both servers.
Document the exchange profile and test redirects and staged failures.

Partially addresses modelcontextprotocol#531.
@alexhancock
alexhancock self-requested a review September 4, 2026 01:15

@alexhancock alexhancock 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.

The code for this LGTM from what I know.

I have a bit of trouble independently reproducing the conformance test results but It's likely something silly. When I run the steps you defined

TASK_RUST_DIR=/path/to/rust-sdk
TASK_CONFORMANCE_DIR=/path/to/conformance
TASK_TARGET_DIR=/Users/alexhancock/some/dir

cd "$TASK_RUST_DIR"
cargo +1.96 build -p mcp-conformance --bin conformance-client \
  --target-dir "$TASK_TARGET_DIR"

cd "$TASK_CONFORMANCE_DIR"

npm run build

node dist/index.js client \
  --command "$TASK_TARGET_DIR/debug/conformance-client" \
  --scenario auth/enterprise-managed-authorization-refresh-token

node dist/index.js client \
  --command "$TASK_TARGET_DIR/debug/conformance-client" \
  --scenario auth/enterprise-managed-authorization-refresh-token \
  --spec-version 2026-07-28 --force

node dist/index.js client \
  --command "$TASK_TARGET_DIR/debug/conformance-client" \
  --suite extensions \
  --expected-failures "$TASK_RUST_DIR/conformance/expected-failures-extensions.yaml"

I get

Stdout:
2026-09-04T01:14:59.764981Z  INFO conformance_client: Running scenario 'auth/enterprise-managed-authorization-refresh-token' against http://localhost:64726/mcp


Stderr:
Error: Unsupported conformance scenario: auth/enterprise-managed-authorization-refresh-token

Checks:
2026-09-04T01:14:59.766Z [complete-flow-mcp-access    ] FAILURE Client did not complete an MCP operation with the issued access token
2026-09-04T01:14:59.767Z [complete-flow-token-exchange] FAILURE Client did not perform token exchange
2026-09-04T01:14:59.767Z [complete-flow-jwt-bearer    ] FAILURE Client did not perform JWT bearer grant exchange

Thoughts?

Also do you want to wait for the conformance tests to merge or go ahead with the PR to the rust-sdk to get it usable more quickly? Pull out of draft when ready.

@nicksteele-oai
nicksteele-oai marked this pull request as ready for review September 4, 2026 14:21
@nicksteele-oai
nicksteele-oai requested a review from a team as a code owner September 4, 2026 14:21
@alexhancock

Copy link
Copy Markdown
Contributor

The client changes had been left out of the conformance commit. Now that they are there, I can independently reproduce the conformance checks all pass for the new scenarios.

Merging.

@alexhancock
alexhancock merged commit 3023198 into modelcontextprotocol:main Sep 4, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-config Configuration file changes T-core Core library changes T-dependencies Dependencies related changes T-documentation Documentation improvements T-test Testing related changes T-transport Transport layer changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants