-
Notifications
You must be signed in to change notification settings - Fork 497
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
base: main
Are you sure you want to change the base?
Conversation
this.redirectHandler = redirectHandler; | ||
this.callbackHandler = callbackHandler; | ||
this.timeout = timeout; | ||
this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(30)).build(); |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pushed an update
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); | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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)
String authUrl = buildAuthorizationUrl(clientInfo); | ||
|
||
// Redirect user for authorization | ||
return redirectHandler.apply(authUrl) | ||
.thenCompose(v -> callbackHandler.apply(null)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
@andrey-star thanks for reviewing the PR, I'll address your comments by tomorrow |
@bzsurbhi really excited for this!! :) |
@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. |
@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:
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? |
@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. |
@stantonk The current implementation only handles server-level authentication, similar to the python-sdk. The specification doesn't currently address tool-specific authorization controls. |
@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:
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:
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. |
@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. |
@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
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? |
Agreed. And the spec clearly states:
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.
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:
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. |
First of all, really appreciate you taking the time to respond and discuss! ❤️
Agreed! We're on the same page.
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.
Yes!!! This in line with what I am thinking, that was my point here:
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:
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? |
@bzsurbhi do we support Client Credentials grant type when the client is another application (not a human)? |
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 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. |
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
Implementation Details
Server-Side Components
OAuthHttpServletSseServerTransportProvider
: Extends the standard transport provider to handle OAuth endpointsAuthContext
: Thread-local storage for authentication informationBearerAuthenticator
: Validates Bearer tokens in Authorization headersClientAuthenticator
: Validates client credentialsClient-Side Components
OAuthClientProvider
: Manages OAuth flow and token lifecycleTokenStorage
: Interface for storing tokens securelyHttpClientAuthenticator
: Adds authentication headers to outgoing requestsAuthenticatedTransportBuilder
: Adds authentication to transport buildersAuthentication Flow
/authorize
endpointExample Implementation
The PR includes a complete example in the
auth-example
directory demonstrating:Breaking Changes
This is a completely new change
Types of changes
Checklist
Additional context