Skip to content

Feature: Authorization support #297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

bzsurbhi
Copy link

@bzsurbhi bzsurbhi commented Jun 7, 2025

OAuth Authentication Support for MCP Java SDK

Overview

This PR adds comprehensive OAuth 2.0 authentication support to the MCP Java SDK, enabling secure communication between clients and servers.

Key Features

  • OAuth 2.0 Authorization Code Flow with PKCE support
  • Server-side authentication middleware for validating access tokens
  • Client-side authentication for obtaining and refreshing tokens
  • Seamless integration with existing transport layer
  • Example Implementation of an authenticated MCP client and server

Implementation Details

Server-Side Components

  • OAuthHttpServletSseServerTransportProvider: Extends the standard transport provider to handle OAuth endpoints
  • AuthContext: Thread-local storage for authentication information
  • BearerAuthenticator: Validates Bearer tokens in Authorization headers
  • ClientAuthenticator: Validates client credentials

Client-Side Components

  • OAuthClientProvider: Manages OAuth flow and token lifecycle
  • TokenStorage: Interface for storing tokens securely
  • HttpClientAuthenticator: Adds authentication headers to outgoing requests
  • AuthenticatedTransportBuilder: Adds authentication to transport builders

Authentication Flow

  1. Client requests authorization via /authorize endpoint
  2. User authenticates and grants permissions
  3. Server redirects to client with authorization code
  4. Client exchanges code for access and refresh tokens via /token endpoint
  5. Client includes access token in subsequent API requests
  6. Server validates token and provides access to protected resources

Example Implementation

The PR includes a complete example in the auth-example directory demonstrating:

  • Server setup with OAuth endpoints
  • Client authentication flow

Breaking Changes

This is a completely new change

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
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

this.redirectHandler = redirectHandler;
this.callbackHandler = callbackHandler;
this.timeout = timeout;
this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(30)).build();

Choose a reason for hiding this comment

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

Can you make the OAuth HttpClient configurable like it is in HttpClientSseClientTransport?

Copy link
Author

Choose a reason for hiding this comment

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

Pushed an update

Comment on lines 31 to 40
return builder.customizeRequest(requestBuilder -> {
String token = authProvider.getAccessToken();
if (token != null) {
requestBuilder.setHeader("Authorization", "Bearer " + token);
}
}).requestInterceptor(new RequestResponseInterceptor() {
@Override
public CompletableFuture<HttpRequest> interceptRequest(HttpRequest.Builder requestBuilder) {
return authenticator.authenticate(requestBuilder).thenApply(HttpRequest.Builder::build);
}

Choose a reason for hiding this comment

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

Seems like these 2 blocks set the Auth header twice

Copy link
Author

Choose a reason for hiding this comment

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

Removed the redundancy

}

try {
String requestBody = objectMapper.writeValueAsString(clientMetadata);

Choose a reason for hiding this comment

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

Testing with mcp.wix.com I had to add .setSerializationInclusion(JsonInclude.Include.NON_NULL) and .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE). The server did not accept optional fields with null values, and redirectUris had to be redirect_uris. Ideally we just @JsonProperty annotations to the OAuthClientMetadata object according to the spec.

throw new RuntimeException("Registration failed: " + response.statusCode());
}
try {
return objectMapper.readValue(response.body(), OAuthClientInformation.class);

Choose a reason for hiding this comment

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

with mcp.wix.com this failed because the server returned an unknown registration_client_uri. OAuthClientInformation should probably have @JsonIgnoreProperties(ignoreUnknown = true)

Comment on lines +172 to +176
String authUrl = buildAuthorizationUrl(clientInfo);

// Redirect user for authorization
return redirectHandler.apply(authUrl)
.thenCompose(v -> callbackHandler.apply(null))

Choose a reason for hiding this comment

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

Can we untie, or at least allow executing these steps independently?

In many cases I can imagine the URL generation, and the callback visit cannot be part of the same process that gets blocked until the user responds. So we would need to allow storing state during URL generation, and looking it up during the callback for the final token exchange.

Choose a reason for hiding this comment

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

I realised this problem is shared across SDKs. Also opened an issue in the python SDK modelcontextprotocol/python-sdk#992

@bzsurbhi
Copy link
Author

@andrey-star thanks for reviewing the PR, I'll address your comments by tomorrow

@stantonk
Copy link

@bzsurbhi really excited for this!! :)

@stantonk
Copy link

stantonk commented Jun 21, 2025

@bzsurbhi i've spent some time reviewing, I am curious how Authn/Z can be performed on a per-tool basis, e.g. like https://quarkus.io/blog/secure-mcp-sse-server/#tool?

Authn/z for connecting to an MCP Server and then having access to all of its Tools, Prompts, etc. is insufficient for many use cases.

@jgrandja
Copy link

@bzsurbhi Thank you for your initiative with this MCP Authorization implementation. At first glance, there is quite a bit of code to digest here which makes it difficult and time consuming to review.

I'm not sure if you noticed the Contributing Guidelines:

For non-trivial changes, please clarify with the maintainers in an issue whether you can contribute the change and the desired scope of the change.

I'm guessing you did not see it, since this was only recently added (May 16). Either way, before any major features are added to a release, it's very common in the OSS world to have an initial discussion with the team and community to see if the feature is needed or in demand from the community perspective. If it is, then it would be prioritized for a specific release. Furthermore, if the feature has an added level of complexity then it likely will get broken down into sensible chunks of work that can more easily be reviewed and ultimately merged.

Having said that, we are already tracking this major feature in spring-security#16992 and have been having ongoing discussions around implementation strategies for providing this support.

Many years have been invested in providing OIDC/OAuth2 support in Spring Security and Spring Authorization Server so the plan is to reuse this expertise as one of the implementation strategies.

We realize that some of our target audience are not Spring users so we do need to address that end as well. I will say that application security is a cross cutting concern and we will likely not embed the security logic directly in the Java SDK. Instead, we will define hooks where MCP Authorization implementations can plug into to provide the features defined in the spec.

I hope this all makes sense?

@stantonk
Copy link

@jgrandja glad to see it is on y'alls radar. I am curious how folks can plug into the efforts there or get additions here in the framework agnostic SDK, as the lack of various forms of Authentication & Authorization capabilities are a huge blocker for pretty much anyone who wants to put stuff into production -- and there's more approaches required than the full OAuth 2.1 spec since not every internal-only architecture fits well with the full blown OAuth 2.1 approach.

@bzsurbhi
Copy link
Author

@bzsurbhi i've spent some time reviewing, I am curious how Authn/Z can be performed on a per-tool basis, e.g. like https://quarkus.io/blog/secure-mcp-sse-server/#tool?

Authn/z for connecting to an MCP Server and then having access to all of its Tools, Prompts, etc. is insufficient for many use cases.

@stantonk The current implementation only handles server-level authentication, similar to the python-sdk. The specification doesn't currently address tool-specific authorization controls.

@asaikali
Copy link

asaikali commented Jun 23, 2025

@stantonk I see this as a design tradeoff about layering and responsibility.

To keep the MCP SDK truly framework-agnostic, we need to acknowledge that it can’t—and shouldn’t—do everything on its own. Especially for remote servers, a host framework (like Spring, Quarkus, or Micronaut) is essential. It handles things the SDK isn’t designed to own: HTTP, security, and application wiring.

Responsibilities of the host framework:

  • HTTP request routing – e.g. url of the MCP server /mcp, /checkout/mcp, or something else
  • Security stack integration – including support for:
  • Idiomatic integration – security configuration, DI, error handling, observability, and other platform-native behaviors

The idea is that MCP server authors should be able to pick the framework they already use and build a secure, idiomatic MCP server using its existing infrastructure.

Responsibilities of the MCP SDK:

The SDK’s job is to provide a clean, minimal surface for protocol compliance. That includes:

  • Core message types that represent the JSON-RPC protocol used by MCP
  • Pluggable interfaces for things like session handling (e.g. how to manage Mcp-Session-Id)
  • Abstractions for protocol-level concepts, like tools and prompts, without assuming any specific transport or runtime

This separation allows host frameworks to do what they do best—HTTP and security—while the SDK stays focused, portable, and easy to embed across stacks.

If we push host-layer concerns like OAuth or routing into the SDK itself, we risk making it heavy, opinionated, and harder to maintain. Drawing this clear boundary keeps the SDK lean and gives each host framework the flexibility to integrate in a way that feels natural to its ecosystem.

And importantly: this also means that developers building production-grade MCP servers should not be working directly with the MCP SDK most of the time. They should rely on their host framework's integration layer—which wires up the SDK behind the scenes using idiomatic patterns they already know. In the ideal case, most app developers don’t even need to know the SDK exists.

@stantonk
Copy link

@asaikali I understand the desire to have separation of concerns, but the idea that the official MCP implementation for Java (or any language for that matter) is neither production-grade, nor enables implementation of common sense security (including what is part of the specification), doesn't make any sense.

The current state of the interfaces and implementations actively fight putting authentication and authorization into individual Tool calls, which is a requirement for any responsible, production implementation of MCP. That's a much lower lift / maintenance burden than having all of the OAuth implementation present here. It's built directly on top of Servlet, and should allow for using all of the existing ecosystem of higher order libraries to add authentication and authorization as companies see fit.

Ultimately people should not be required to use Spring or Quarkus to build with MCP safely.

Where is the best place to discuss further? I and @bzsurbhi are certainly highly motivated to help out here and contribute in the most effective way for the project! I think there's a fairly smaller set of changes that could be made that at least make it possible for greater pluggability into other frameworks and codebases that are Servlet based and have pre-existing authentication and authorization libraries and requirements.

@asaikali
Copy link

asaikali commented Jun 24, 2025

@stantonk I agree with your statement "Ultimately people should not be required to use Spring or Quarkus to build with MCP safely." that does not mean that MCP SDK should implement the OAuth RFCs needed per the Streamable HTTP spec. So what is the right way to do it?

create a project that depends on the mcp-java-sdk that integrates into servlets exactly the way you want to do it without needing Spring or Quarkus. As you implement this project if you find that the sdk is not exposing the hooks that you need, then contribute those hooks via issues, or pull requests, demos to help evolve the sdk so that it can be embedded inside a host framework. This enables the whole community to benefit and helps stress test the API surface of the SDK.

Can you elaborate on what you mean by

The current state of the interfaces and implementations actively fight putting authentication and authorization into individual Tool calls, which is a requirement for any responsible, production implementation of MCP.

I am assuming you want to be able to ask the sdk what user is currently making the request, and what permissions do they have? Is my assumption correct?

@jgrandja
Copy link

@stantonk

and there's more approaches required than the full OAuth 2.1 spec since not every internal-only architecture fits well with the full blown OAuth 2.1 approach

Agreed. And the spec clearly states:

Authorization is OPTIONAL for MCP implementations.

Implementations using alternative transports MUST follow established security best practices for their protocol.

Obviously, for a production deployment, secured interactions is a strict requirement, however, it appears that OAuth2 is not necessarily required and any industry standard appsec protocol can be used.

This further validates that an OAuth2 implementation, or any appsec protocol implementation SHOULD NOT live in the Java SDK.

I think there's a fairly smaller set of changes that could be made that at least make it possible for greater pluggability into other frameworks and codebases

At this point, I am thinking the same in that we might need to define a smaller set of core interfaces in the Java SDK that provide an abstraction around a "Security Context" and/or "Security Identity". Whatever these abstractions end up being, these will ultimately be the extension points for the external security frameworks/libraries (e.g. Spring Security).

One last point that I would like to re-emphasize is:

appsec protocol implementation SHOULD NOT live in the Java SDK

Application security is a very specialized domain that needs to be handled by engineers that work in that domain. If we were to embed that logic into the Java SDK, it would highly complicate the codebase and introduce very high risk. The OAuth2 set of specifications is a maze that one can easily get lost in. It has taken me many years to develop the expertise and it's not viable or sustainable for an engineer to "ramp up quickly" and provide security-related enhancements via PR's. Going down this path could easily introduce CVE's into the SDK and effectively lose the trust in the community leading to project failure. We need to be very careful with the next steps that we take.

Lastly, where do we stop? With an embedded OAuth2 implementation? What about SAML or mTLS or other authn/authz standards? I guarantee that if we provide an embedded OAuth2 implementation, other users will ask to support another authn/authz standard and this will ultimately put us in a bind.

@stantonk
Copy link

@jgrandja

First of all, really appreciate you taking the time to respond and discuss! ❤️

and there's more approaches required than the full OAuth 2.1 spec since not every internal-only architecture fits well with the full blown OAuth 2.1 approach

Agreed. And the spec clearly states:

Authorization is OPTIONAL for MCP implementations.

Implementations using alternative transports MUST follow established security best practices for their protocol.

Obviously, for a production deployment, secured interactions is a strict requirement, however, it appears that OAuth2 is not necessarily required and any industry standard appsec protocol can be used.

Agreed! We're on the same page.

Application security is a very specialized domain that needs to be handled by engineers that work in that domain. If we were to embed that logic into the Java SDK, it would highly complicate the codebase and introduce very high risk. The OAuth2 set of specifications is a maze that one can easily get lost in.

Appreciate you calling this out, it's a really important consideration!! It helps inform why the separation of concerns makes sense, while still needing to create interfaces that allow for more flexibility and give downstream frameworks and teams ways to integrate the Authn/Z they require.

At this point, I am thinking the same in that we might need to define a smaller set of core interfaces in the Java SDK that provide an abstraction around a "Security Context" and/or "Security Identity". Whatever these abstractions end up being, these will ultimately be the extension points for the external security frameworks/libraries (e.g. Spring Security).

Yes!!! This in line with what I am thinking, that was my point here:

The current state of the interfaces and implementations actively fight putting authentication and authorization into individual Tool calls, which is a requirement for any responsible, production implementation of MCP. That's a much lower lift / maintenance burden than having all of the OAuth implementation present here. It's built directly on top of Servlet, and should allow for using all of the existing ecosystem of higher order libraries to add authentication and authorization as companies see fit.

Some kind of context object that is passed to all handlers of Client Messages would be ideal, but Tool Calls are so ubiquitous already that IMO starting there is really key.

To me it should be similar to how REST API frameworks have a context that includes things like the HTTP Headers, but perhaps also a set of K/V pairs that can be used for other context types that don't strictly fall under HTTP Headers.

Next steps:

  1. I already have a working example of Dropwizard + this Java SDK, I can put it up somewhere on github and take an initial crack at carrying the context down into the SyncToolSpecification and AsyncToolSpecification and share it here. How does that sound?
  2. If you already have some ideas on how this might work more generally, I'd love to see a mockup / pseudocode of what you're thinking.
  3. To your last question:

Lastly, where do we stop? With an embedded OAuth2 implementation? What about SAML or mTLS or other authn/authz standards? I guarantee that if we provide an embedded OAuth2 implementation, other users will ask to support another authn/authz standard and this will ultimately put us in a bind.

I see where your concern lies and it does make sense. The tricky part to me is you want to have enough "examples" of ways people wish to hook into the interfaces you expose in the SDK so you can develop a robust and flexible enough set of interfaces, and having real implementations to vet the interfaces is really helpful -- but you don't want to make them part of the SDK. Maybe a separate module that is only for integration testing? What have you seen work well in the past?

@Zizo-Vi
Copy link

Zizo-Vi commented Jun 26, 2025

@bzsurbhi do we support Client Credentials grant type when the client is another application (not a human)?
https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization#oauth-grant-types

image

@asaikali
Copy link

asaikali commented Jun 26, 2025

@bzsurbhi do we support Client Credentials grant type when the client is another application (not a human)? https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization#oauth-grant-types

According to the spec, the MCP server is an OAuth resource server. When the Mcp server gets a request, the host security framework extracts the http Authorization: Bearer header to get the access token, the security framework then validates the access token. The resource server does not know how the access token was created by the authorization server.

If the authorization server configured for the MCP server supports client credentials then you can use client credentials from the MCP client to obtain the access token and call the upstream Mcp Server.

If the client application is running on platform that issues application instance credentials it can trade those instance identity creds for an access token with the authorization server. For example, if the app is running on Kuberentes, it has JWT for a k8s service account, you can configure the Authorization server so that you can trade the k8s service account JWT for an access token to the MCP server. The configuration of something like this can be quite complex because there are a lot of security concepts that need to be understood, and the details depend on the platform like k8s, or a cloud provider.

Checkout this blog post https://aaronparecki.com/2025/05/12/27/enterprise-ready-mcp from an OAuth industry guru that gets into a lot of the details.

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.

6 participants