Skip to content

feat: Add Enterprise Managed Authorization (SEP-990) support#1

Open
prachi-okta wants to merge 27 commits intomainfrom
feature/enterprise-managed-authorization
Open

feat: Add Enterprise Managed Authorization (SEP-990) support#1
prachi-okta wants to merge 27 commits intomainfrom
feature/enterprise-managed-authorization

Conversation

@prachi-okta
Copy link
Copy Markdown
Owner

@prachi-okta prachi-okta commented Mar 2, 2026

Implements Enterprise Managed Authorization (SEP-990) for the Kotlin MCP SDK

Enables MCP clients to leverage enterprise Identity Providers for seamless authorization without per-server user authentication.

Motivation and Context

Enterprise environments require OAuth flows where users authenticate with a centralized IdP, and applications need to securely access protected MCP resources on their behalf. SEP-990 addresses this via:

  • Token Exchange (RFC 8693): Exchanges a user's ID token from an enterprise IdP for a JWT Authorization Grant (ID-JAG)
  • JWT Bearer Grant (RFC 7523): Exchanges the ID-JAG for an access token at the MCP authorization server
  • OAuth Discovery (RFC 8414): Automatically discovers authorization server metadata for both IdP and MCP servers

This follows the same provider pattern as existing auth implementations and is consistent with the Java, TypeScript, C#, and Go SDK implementations. The provider is implemented as a Ktor HttpClientPlugin that intercepts outgoing requests via HttpSend, making it a first-class citizen of the Ktor client ecosystem.

How Has This Been Tested?

Added 32 unit tests across two test classes using Kotlin Multiplatform (commonTest), Kotest assertions, and Ktor MockEngine:

EnterpriseAuthTest (20 tests):

  • Authorization server metadata discovery — success via oauth-authorization-server, fallback to openid-configuration on 404/500, both-fail error, trailing slash stripping
  • JAG token exchange — success, optional params in body, wrong issued_token_type throws, non-standard token_type succeeds (informational per RFC 8693 §2.2.1), missing access_token throws, HTTP error throws
  • discoverAndRequestJwtAuthorizationGrant — full discovery flow, skips discovery when idpTokenEndpoint provided, no token_endpoint in metadata throws
  • JWT bearer grant — success with expiresAt computed from monotonic clock, missing access_token throws, HTTP error throws
  • isExpired — null expiresAt, future expiresAt, past expiresAt

EnterpriseAuthProviderTest (12 tests):

  • End-to-end Authorization: Bearer header injection via Ktor plugin
  • Token caching across multiple requests
  • invalidateCache forces re-fetch on next request
  • Discovery failure and assertion callback error propagation
  • Correct resourceUrl and authorizationServerUrl passed to callback
  • prepare validation — clientId null throws, assertionCallback null throws
  • Blank assertion callback return throws IllegalArgumentException
  • Token without expires_in cached indefinitely
  • Custom expiryBuffer respected
  • Full end-to-end flow — header set and token reused

Breaking Changes

None — this is a purely additive feature. No existing APIs are modified.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally (./gradlew :kotlin-sdk-client:jvmTest)
  • ./gradlew apiCheck passes
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Key design decisions:

  • Ktor plugin patternEnterpriseAuthProvider implements HttpClientPlugin<Config, EnterpriseAuthProvider>, installed via install(EnterpriseAuthProvider) { ... } DSL; uses HttpSend interceptor for transparent header injection
  • Assertion callback pattern decouples IdP interaction from the provider — callers can implement JAG caching inside the callback to reduce IdP round-trips
  • client_secret_basic (Basic Auth header) used for JWT bearer grant, aligned with SEP-990 conformance requirements (RFC 6749 §2.3.1)
  • token_type in JAG response is not strictly validated per RFC 8693 §2.2.1 — it is informational and strict checking rejects conformant IdPs
  • Refresh tokens returned by the MCP AS are intentionally ignored — RFC 7523 is a stateless grant; using a refresh token would bypass IdP session/revocation policies
  • Monotonic clock (TimeSource.Monotonic) used for token expiry to avoid wall-clock skew issues
  • Double-checked locking with Mutex for thread-safe token caching on coroutines

Related SDK implementations:

kpavlov and others added 22 commits February 26, 2026 09:04
…elcontextprotocol#565)

## Summary
Fix SSE endpoint resolution so absolute paths resolve against the
origin, and add tests covering absolute vs relative endpoint events.

## Motivation and Context
The SSE transport currently resolves all endpoint events against the SSE
URL’s base path. When the server sends an absolute endpoint (starting
with `/`), the client incorrectly prefixes it with the SSE path. This
change resolves absolute endpoints against the origin while keeping
relative behavior unchanged.

addresses: modelcontextprotocol#329

## How Has This Been Tested?
Unit tests added in `SseClientTransportTest` for absolute and relative
endpoint events.

## Breaking Changes
No.

## Types of changes
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update

## Checklist
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed

## Additional context
The endpoint resolution logic now treats paths starting with `/` as
origin-relative. Relative paths continue to resolve against the SSE base
URL.
…textprotocol#560)

## Introduce Configuration class for StreamableHttpServerTransport

Replace the six-parameter flat constructor of
`StreamableHttpServerTransport` with a typed `Configuration` class. This
improves API ergonomics, enables structural equality and copy(), and
provides a stable extension point for future options without further
breaking the constructor signature.

### Changes:

- Add `Configuration` as a public class nested directly on
`StreamableHttpServerTransport`, with `enableJsonResponse` as the first
parameter (most commonly set)
  - Change the primary constructor to accept `Configuration`.
- Rename `retryIntervalMillis: Long?` to `retryInterval: Duration?` in
Configuration, aligning with Kotlin's type-safe time API
  - Deprecate the old flat constructor with a compatibility bridge
  - Update KotlinTestBase integration test to use the new constructor
- Enable test
AbstractResourceIntegrationTest.testSubscribeAndUnsubscribe() since modelcontextprotocol#249
is closed

## Motivation and Context

The current StreamableHttpServerTransport API cannot be easily extended:
adding more parameters would be a breaking change. But this is already
needed for modelcontextprotocol#521.
This PR is a prerequisite for modelcontextprotocol#535

## How Has This Been Tested?
Regression tests

## Breaking Changes
No. Current StreamableHttpServerTransport constructor was deprecated

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](actions/download-artifact@v7...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Update link in readme.md

## Motivation and Context

Kotlin SDK has moved from
https://modelcontextprotocol.github.io/kotlin-sdk/ to
https://kotlin.sdk.modelcontextprotocol.io/

-
[Announcement](https://discord.com/channels/1358869848138059966/1399986970851282954/1476078438266830931)
- DNS change PR: modelcontextprotocol/dns#5

## Breaking Changes
No

## Types of changes

- [x] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [x] I have added or updated documentation as needed
…xtprotocol#579)

## Add Codecov coverage upload and XML report generation

- Add `koverXmlReport` to Gradle tasks in Ubuntu workflow
- Configure Codecov action to upload coverage reports

Reports will be available on
https://app.codecov.io/gh/modelcontextprotocol/kotlin-sdk

## Motivation and Context
To provide better test coverage visibility

## How Has This Been Tested?
CI

## Breaking Changes
No

## Types of changes
- [x] CI update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
…protocol#557)

Bumps [dev.mokksy:mokksy](https://github.com/mokksy/ai-mocks) from 0.6.2
to 0.8.0.

- Update dependency and migrate to new API

---------

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com>
…ontextprotocol#580)

ci: update Codecov configuration for test and coverage upload

- Unified usage of `codecov/codecov-action@v5` for both test results and
coverage upload.
- Added `CODECOV_TOKEN` and `report_type` parameters for improved
configuration.

## Motivation and Context

Actually, CODECOV_TOKEN is required, even though the CodeCov banner
previously said the project can upload without a token.

## How Has This Been Tested?
CI

## Breaking Changes
No

## Types of changes

- [x] CI fix 

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed
…in StdioServerTransport, fix ReadBuffer (modelcontextprotocol#571)

## Rethrow CancellationException in StdioServerTransport, fix ReadBuffer

### Changes
1. StdioServerTransport
- Replace generic `Throwable` catches with specific
`CancellationException` and `Exception` for clarity and correctness.
**breaking change** `Throwable` is not handled any more!
    - Refactor launching jobs to separate methods
- Introduce `READ_BUFFER_SIZE` constant to replace inline buffer size
literals.
- Add suppress annotations for clearer intent and constructor
documentation for better usability.
    - Extend StdioServerTransportTest

2. ReadBuffer

Previously, readMessage() returned null after consuming an unparseable
line even when more complete lines existed in the buffer. This caused
valid messages following a bad line in the same chunk to be silently
dropped until the next chunk arrived (or forever, in tests).
    
Fix the loop so null means only "no complete line available", not
"encountered a parse failure". Blank/whitespace-only lines are now
silently skipped via isBlank() rather than forwarded to the deserializer
and logged as errors.
    
    Refactor the method into three focused helpers:
     - readMessage() — outer loop over lines
- readLine() — consume the next newline-delimited line from the buffer
     - tryRecover()  — attempt deserialization from the first '{' onward

3. Add utility method `runIntegrationTest` for integration testing
(`runBlocking` + `withTimeout`). Use it in StdioServerTransportTest

4. Store processing Job reference and `calcelAndJoin()` it on close.
Partially addressing modelcontextprotocol#574

## Motivation and Context

See modelcontextprotocol#575, modelcontextprotocol#564, modelcontextprotocol#242 

## How Has This Been Tested?
Unit test added

## Breaking Changes

Semantic change: `Throwable` and CancellationException are not handled
any more!

## Types of changes

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
- Updated `detekt.yml` to exclude test folders from
`EmptyFunctionBlock`, `LargeClass`, and `LongMethod` checks.
- Cleanup detekt-baseline files.
- Simplified `.idea/detekt.xml`.

## Motivation and Context
To reduce linter noise and reduce tension

## How Has This Been Tested?
`./gradlew detekt`

## Breaking Changes
No

## Types of changes
- [x] Build tool/ci update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context

modelcontextprotocol#567
- add a simple streamable http server example with auth
- update readme: replace quickstart example from sse server to
streamable server
- add readme files for samples, simple-streamable-server, notebooks

closes modelcontextprotocol#170 

## How Has This Been Tested?
knit and inspector

## Breaking Changes
NaN

## Types of changes
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update

## Checklist
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [x] I have added or updated documentation as needed
closes modelcontextprotocol#543 

## How Has This Been Tested?
unit

## Breaking Changes
binary compatibility in the Tool class

## Types of changes
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

---------

Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com>
… SSE reconnection with retry support (modelcontextprotocol#596) (modelcontextprotocol#585)

Adds a comprehensive conformance test suite for the Kotlin MCP SDK,
covering core protocol operations, tool calls, elicitation, resources,
prompts, and 20 OAuth/auth scenarios

- Conformance server and client implementations
- OAuth/auth test scenarios: JWT, authorization code flow, client
credentials, PKCE, scope handling, cross-app access, client registration
- CI workflow
- Baseline file for tracking expected failures
- Shell script

fixes:
- modelcontextprotocol#592
- modelcontextprotocol#593
- modelcontextprotocol#596


## Remaining known failures (tracked issues, will be fixed directly in
`main`)

- [x] `tools-call-with-logging`, `tools-call-with-progress`,
`tools-call-sampling`, `tools-call-elicitation`,
`elicitation-sep1034-defaults`-  see modelcontextprotocol#599,
- [x] `elicitation-sep1330-enums` - modelcontextprotocol#587 modelcontextprotocol#600
- [x] `initialize` - modelcontextprotocol#588 
- [x] `tools_call`, `auth/scope-step-up`, `auth/scope-retry-limit` -
modelcontextprotocol#589
- [ ] `elicitation-sep1034-client-defaults` - modelcontextprotocol#414 
- [x] `sse-retry` - modelcontextprotocol#590 
- [ ] `resources-templates-read` - modelcontextprotocol#591 

## Breaking Changes
from modelcontextprotocol#596 
- `StreamableHttpClientTransport` and
`mcpStreamableHttp`/`mcpStreamableHttpTransport`: old constructors
accepting `Duration` timeout are now `@Deprecated` — use the new
overloads with `ReconnectionOptions` instead
- `StreamableHttpClientTransport.close()` no longer calls
`terminateSession()` automatically

## Types of changes
- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed

---------

Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com>
…ontextprotocol#587) (modelcontextprotocol#599)

## Changes

- fix(server): keep SSE connection open until explicitly cancelled
- conformance: add logging to test_tool_with_logging and update
troubleshooting documentation
- chore(conformance): remove 5 passing server tests from conformance
baselines:
    - tools-call-with-logging
    - tools-call-with-progress
    - tools-call-sampling
    - tools-call-elicitation
    - elicitation-sep1034-defaults

## Motivation and Context

Two bugs in StreamableHttpServerTransport caused server-to-client
notifications (e.g. notifications/message during tool calls) to be
silently dropped.
   
**Bug 1 — GET SSE stream closed immediately**
`handleGetRequest` rejected GET requests with 405 when
`enableJsonResponse = true` (which `mcpStreamableHttp` always sets).
Since the Ktor `sse {}` handler commits response headers before the body
runs, the reject failed silently and the function returned, causing Ktor
to close the SSE stream. There was also no `awaitCancellation()` to keep
the connection alive.
**Bug 2 — Notifications discarded in JSON mode**

In `send()`, notifications with a relatedRequestId entered the
POST-stream path but hit `if (!isTerminated) return` without being
forwarded anywhere.
   
**Fix**
- Remove the enableJsonResponse guard from handleGetRequest. The GET SSE
stream is an always-required notification channel, orthogonal to how
POST responses are delivered.
- Add `awaitCancellation()` to keep the GET SSE connection open until
the client disconnects or the transport closes.
- In `send()`, route notifications with a relatedRequestId to the
standalone GET SSE stream when `enableJsonResponse = true`.

## How Has This Been Tested?

Verified against the MCP conformance test suite — the
ToolsCallWithLogging scenario now passes. ngrep confirms the `GET /mcp`
stream stays open and `notifications/message` events arrive before the
`tools/call` response.


## Breaking Changes
<!-- Will users need to update their code or configurations? -->

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context

Fixes modelcontextprotocol#587
Requires modelcontextprotocol#585 to be merged first.

---------

Co-authored-by: devcrocod <devcrocod@gmail.com>
…ep1330_enums (modelcontextprotocol#600)

## Correct SEP-1330 enum schemas in test_elicitation_sep1330_enums

**NB! This PR contains changes from modelcontextprotocol#599 and should be rebased and
merged after modelcontextprotocol#599 is merged.**

Update conformance test according to [test
requirements](https://github.com/modelcontextprotocol/conformance/blob/main/src/scenarios/server/elicitation-enums.ts#L14C1-L35C9).

- Fix legacyEnum: was using `oneOf` with const/title pairs; now
correctly uses `enum` + `enumNames` arrays per LegacyEnumSchema spec
- Fix titledMulti: items were using `oneOf` with extra `type:"string"`;
now correctly uses `anyOf` per TitledMultiSelectEnumSchema spec
- Fix return text format to match expected "Elicitation completed:
action=..., content=..."
- Remove _elicitation-sep1330-enums_ from conformance baseline (test now
passes)

## How Has This Been Tested?

```shell
./conformance-test/run-conformance.sh server
```

## Breaking Changes
No

## Types of changes

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

---------

Co-authored-by: devcrocod <devcrocod@gmail.com>
Updated the client and server examples for further use in the following
guides: https://modelcontextprotocol.io/docs/develop/build-server and
https://modelcontextprotocol.io/docs/develop/build-client

## How Has This Been Tested?
claude desktop, cli

## Breaking Changes
NaN

## Types of changes
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Example, Documentation update

## Checklist
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed
@prachi-okta prachi-okta force-pushed the feature/enterprise-managed-authorization branch from 68ba4d5 to 8c26488 Compare March 17, 2026 05:30
@prachi-okta prachi-okta changed the title Feature/enterprise managed authorization feat: Add Enterprise Managed Authorization (SEP-990) support Mar 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants