Security and Authorization support for Model Context Protocol in Spring AI.
⚠️ Versions 0.1.x ofmcp-securityonly work Spring AI's 2.0.x branch. For Spring AI 1.1.x, use version0.0.6.
- Overview
- MCP Server Security
- MCP Client Security
- Authorization Server
- Samples
- Integrations (Cursor, Claude Desktop, ...)
- License
This repository provides Authorization support for Spring AI integrations with the Model Context Protocol (MCP). It covers both MCP Clients, MCP Servers, and Spring Authorization Server.
The project enables developers to:
- Secure MCP servers with OAuth 2.0 authentication
- Configure MCP clients with OAuth 2.0 authorization flows
- Set up authorization servers specifically designed for MCP workflows
- Implement fine-grained access control for MCP tools and resources
Provides OAuth 2.0 resource server capabilities for Spring AI's MCP servers. It also provides basic support for API-key based servers. This module is compatible with Spring WebMVC-based servers only.
The easiest way to add OAuth2 security to your MCP server is with the Boot auto-configuration module.
It provides a default SecurityFilterChain that secures all endpoints, with no additional configuration
required beyond setting the issuer URI.
Maven
<dependencies>
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>mcp-server-security-spring-boot</artifactId>
<version>0.1.6</version>
</dependency>
</dependencies>Gradle
implementation("org.springaicommunity:mcp-server-security-spring-boot:0.1.6")Then configure your application.properties:
spring.ai.mcp.server.name=my-cool-mcp-server
spring.ai.mcp.server.protocol=STREAMABLE
# The issuer URI of the authorization server
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:9000That's it. When spring.security.oauth2.resourceserver.jwt.issuer-uri is set, the auto-configuration
creates a SecurityFilterChain that secures all endpoints using the McpServerOAuth2Configurer.
For a complete working example, see the sample-mcp-server module.
If you prefer wiring beans yourself (e.g. for advanced customization, API key support, or non-Boot use-cases),
you can use the lower-level mcp-server-security module directly.
Maven
<dependencies>
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>mcp-server-security</artifactId>
<version>0.1.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- OPTIONAL -->
<!-- If you would like to use OAuth2, ensure you import the Resource Server dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
</dependencies>Gradle
implementation("org.springaicommunity:mcp-server-security:0.1.6")
implementation("org.springframework.boot:spring-boot-starter-security")
// OPTIONAL
// If you would like to use OAuth2, ensure you import the Resource Server dependencies
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")Ensure that MCP server is enabled in your application.properties:
spring.ai.mcp.server.name=my-cool-mcp-server
# Supported protocols: STREAMABLE, STATELESS
spring.ai.mcp.server.protocol=STREAMABLEThen, configure the security for your project in the usual Spring-Security way, adding the provided configurer.
Create a configuration class, and reference the authorization server's URI.
In this example, we have set the authz server's issuer URI in the well known Spring property
spring.security.oauth2.resourceserver.jwt.issuer-uri.
Using this exact name is not a requirement, and you may use a custom property.
@Configuration
@EnableWebSecurity
class McpServerConfiguration {
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuerUrl;
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) {
return http
// Enforce authentication with token on EVERY request
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
// Configure OAuth2 on the MCP server
.with(
McpServerOAuth2Configurer.mcpServerOAuth2(),
(mcpAuthorization) -> {
// REQUIRED: the issuerURI
mcpAuthorization.authorizationServer(issuerUrl);
// OPTIONAL: enforce the `aud` claim in the JWT token.
// Not all authorization servers support resource indicators,
// so it may be absent. Defaults to `false`.
// See RFC 8707 Resource Indicators for OAuth 2.0
// https://www.rfc-editor.org/rfc/rfc8707.html
//
// mcpAuthorization.validateAudienceClaim(true);
// OPTIONAL: bind the MCP session to the user's identity
// This ensures that a session created by a user can only be accessed by that user
//
// mcpAuthorization.sessionBinding(Customizer.withDefaults());
}
)
.build();
}
}It is also possible to secure the tools only, and not the rest of the MCP Server. For example, both initialize and
tools/list are made public, but tools/call is authenticated.
To enable this, update the security configuration, turn on method security and requests to /mcp are allowed:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // ⬅️ enable annotation-driven security
class McpServerConfiguration {
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuerUrl;
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) {
return http
// ⬇️ Open every request on the server
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/mcp").permitAll();
auth.anyRequest().authenticated();
})
// Configure OAuth2 on the MCP server
.with(
McpServerOAuth2Configurer.mcpServerOAuth2(),
(mcpAuthorization) -> {
// REQUIRED: the issuerURI
mcpAuthorization.authorizationServer(issuerUrl);
}
)
.build();
}
}Then, secure your tool calls using the @PreAuthorize annotation,
using method security.
Inside the annotation, you can apply
a security-based SpEL expression.
At the most basic level, you can use isAuthenticated(), ensuring that the MCP client sent a request with a valid
bearer token:
@Service
public class MyToolsService {
// Note: you can also use Spring AI's @Tool
@PreAuthorize("isAuthenticated()")
@McpTool(name = "greeter", description = "A tool that greets you, in the selected language")
public String greet(
@McpToolParam(description = "The language for the greeting (example: english, french, ...)") String language
) {
if (!StringUtils.hasText(language)) {
language = "";
}
return switch (language.toLowerCase()) {
case "english" -> "Hello you!";
case "french" -> "Salut toi!";
default -> "I don't understand language \"%s\". So I'm just going to say Hello!".formatted(language);
};
}
}Note that you can also access the current authentication directly from the tool method itself, using the thread-local
SecurityContextHolder:
@McpTool(name = "greeter", description = "A tool that greets the user by name, in the selected language")
@PreAuthorize("isAuthenticated()")
public String greet(
@McpToolParam(description = "The language for the greeting (example: english, french, ...)") String language
) {
if (!StringUtils.hasText(language)) {
language = "";
}
var authentication = SecurityContextHolder.getContext().getAuthentication();
var name = authentication.getName();
return switch (language.toLowerCase()) {
case "english" -> "Hello, %s!".formatted(name);
case "french" -> "Salut %s!".formatted(name);
default -> ("I don't understand language \"%s\". " +
"So I'm just going to say Hello %s!").formatted(language, name);
};
}Ensure that MCP server is enabled in your application.properties:
spring.ai.mcp.server.name=my-cool-mcp-server
# Supported protocols: STREAMABLE, STATELESS
spring.ai.mcp.server.protocol=STREAMABLEFor this, you'll need to provide your own implementation of ApiKeyEntityRepository, for storing ApiKeyEntity
objects.
These represent the "entities" which have API keys.
Each entry has an ID, a secret for storing API keys in a secure way (e.g. bcrypt, argon2, ...), as well as a name used
for display purposes.
A sample implementation is available with an InMemoryApiKeyEntityRepository along with a default ApiKeyEntityImpl.
You can bring your own entity implementation with the in-memory repository.
⚠️ TheInMemoryApiKeyEntityRepositoryuses on bcrypt for storing the API keys, and, as such, will be computationally expensive. It is not suited for high-traffic production use. In that case, you must ship your ownApiKeyEntityRepositoryimplementation.
With that, you can configure the security for your project in the usual Spring-Security way:
@Configuration
@EnableWebSecurity
class McpServerConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) {
return http.authorizeHttpRequests(authz -> authz.anyRequest().authenticated())
.with(
mcpServerApiKey(),
(apiKey) -> {
// REQUIRED: the repo for API keys
apiKey.apiKeyRepository(apiKeyRepository());
// OPTIONAL: name of the header containing the API key.
// Here for example, api keys will be sent with "CUSTOM-API-KEY: <value>"
// Replaces .authenticationConverter(...) (see below)
//
// apiKey.headerName("CUSTOM-API-KEY");
// OPTIONAL: custom converter for transforming an http request
// into an authentication object. Useful when the header is
// "Authorization: Bearer <value>".
// Replaces .headerName(...) (see above)
//
// apiKey.authenticationConverter(request -> {
// var key = extractKey(request);
// return ApiKeyAuthenticationToken.unauthenticated(key);
// });
// OPTIONAL: bind the MCP session to the user's identity
// This ensures that a session created with a given API key
// can only be used with that API key
//
// apiKey.sessionBinding(Customizer.withDefaults());
}
)
.build();
}
/**
* Provide a repository of {@link ApiKeyEntity}.
*/
private ApiKeyEntityRepository<ApiKeyEntityImpl> apiKeyRepository() {
//@formatter:off
var apiKey = ApiKeyEntityImpl.builder()
.name("test api key")
.id("api01")
.secret("mycustomapikey")
.build();
//@formatter:on
return new InMemoryApiKeyEntityRepository<>(List.of(apiKey));
}
}Then you should be able to call your MCP server with a header X-API-key: api01.mycustomapikey.
- The deprecated SSE transport is not supported. Use Streamable HTTP or stateless transport. (the link for stateless does not work out of the box, reload the page if required)
- WebFlux-based servers are not supported.
- Opaque tokens are not supported. Use JWT.
Provides OAuth 2 support
for Spring AI's MCP clients.
This module supports McpSyncClients only, with HttpClient-based clients (from spring-ai-starter-mcp-client) and
WebClient-based clients (from spring-ai-starter-mcp-client-webflux).
Key features:
- OAuth2
authorization_code,client_credentials, and hybrid flows - Dynamic Client Registration (DCR) with automatic MCP server metadata discovery
- Scope step-up: automatic re-authorization when the MCP server requires additional scopes
- Spring Boot auto-configuration via
mcp-client-security-spring-boot
The easiest way to add OAuth2 support to your MCP clients is with the Boot auto-configuration module.
Maven
<dependencies>
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>mcp-client-security-spring-boot</artifactId>
<version>0.1.6</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
</dependencies>Gradle
implementation("org.springaicommunity:mcp-client-security-spring-boot:0.1.6")
implementation("org.springframework.ai:spring-ai-starter-mcp-client")Configure your MCP client connections in application.properties:
spring.ai.mcp.client.type=SYNC
spring.ai.mcp.client.initialized=false
# MCP server connections (Spring AI auto-configures these)
spring.ai.mcp.client.streamable-http.connections.my-mcp-server.url=http://localhost:8090
# Enable Dynamic Client Registration (default: false)
spring.ai.mcp.client.authorization.dynamic-client-registration.enabled=trueThen, configure a SecurityFilterChain with the provided McpClientOAuth2Configurer:
@Configuration
@EnableWebSecurity
class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
.with(McpClientOAuth2Configurer.mcpClientOAuth2(), Customizer.withDefaults())
.csrf(CsrfConfigurer::disable)
.build();
}
}This is all you need. The auto-configuration module sets up the following beans:
McpClientRegistrationRepository: aClientRegistrationRepositorythat also tracks the associated MCP resource identifierMcpOAuth2ClientManager: manages Dynamic Client Registration (DCR) and scope step-upMcpMetadataDiscoveryServiceandDynamicClientRegistrationService: infrastructure for DCR- Various
McpClientConfigurerto update MCP transports and MCP clients so a token is added on every request
For a complete working example, see the sample-mcp-client module.
If you prefer wiring beans yourself (e.g. for advanced or non-Boot use-cases), you can use the lower-level
mcp-client-security module directly.
Maven
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>mcp-client-security</artifactId>
<version>0.1.6</version>
</dependency>Gradle
implementation("org.springaicommunity:mcp-client-security:0.1.6")For MCP clients, there are three flows available for obtaining tokens:
authorization_code-based flows. This is the flow that the MCP spec illustrates. A user is present, and the MCP client makes HTTP requests using a bearer token on behalf of that user.client_credentials-based flows. This is not detailed in the spec, but compatible. Client credentials is for machine-to-machine use-cases, where there is no human in the loop. The MCP client makes HTTP requests with a token for itself.- Hybrid flows. In some use-cases, the user might not be present for some MCP client calls, such
as
initializeortools/list. In that case, the MCP client makes calls withclient_credentialstokens representing the client itself. But the user may be present fortools/call, and in that case, the client will use anauthorization_codetoken representing the user.
🤔 Which flow should I use?
- If there are user-level permissions, AND you know every MCP request will be made within the context of a user request
(ensure there are no
tools/listcalls on app startup), then use theauthorization_codeflow. - If there are no user-level permissions, and you want to secure "client-to-server" communication with an access token,
use the
client_credentialsflow. - If there are user-level permissions, AND you configure your MCP clients using Spring Boot properties (such as
spring.ai.mcp.client.streamable-http.connections.<server-name>.url=<server-url>), then, on application startup, Spring AI will try to list the tools. And startup happens without a user present. In that specific case, use a hybrid flow.
MCP Security supports RFC 7591 Dynamic Client Registration as described in the MCP Authorization spec. When enabled, the flow works as follows:
- The MCP client sends a request to the MCP server without a token.
- The server responds with HTTP 401 and a
WWW-Authenticateheader containing the resource metadata URL. - The
OAuth2SyncAuthorizationErrorHandlerfetches the protected resource metadata and discovers the authorization server. - The handler performs dynamic client registration with the authorization server.
- If the server later responds with HTTP 403 and
insufficient_scope, the handler requests the additional scopes (scope step-up).
DCR is disabled by default in the mcp-client-security-spring-boot auto-configuration.
To enable it, set spring.ai.mcp.client.authorization.dynamic-client-registration.enabled=true.
When disabled, ensure you either have a single ClientRegistration registered under spring.security.oauth2.client.registration, or provide your own OAuth2HttpClientTransportCustomizer bean.
Scope step-up is still supported when DCR is disabled.
McpClientOAuth2Configurer is a Spring Security configurer that sets up OAuth2 client support for MCP,
including authorization request and token request customization (e.g. adding the resource= parameter).
You can also use it to pre-register OAuth2 clients for specific MCP servers:
@Configuration
@EnableWebSecurity
class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
.with(McpClientOAuth2Configurer.mcpClientOAuth2(), mcpOAuth2 -> {
// Pre-register an OAuth2 client for a specific MCP server
mcpOAuth2.registerMcpOAuth2Client("my-mcp-server", "http://localhost:8090/mcp");
// Optionally set the base URL for redirect URIs
// mcpOAuth2.baseUrl("http://localhost:8080");
})
.build();
}
}When using spring-ai-starter-mcp-client, the underlying MCP client transport is based on the JDK's
HttpClient.
With auto-configuration (mcp-client-security-spring-boot):
The OAuth2HttpClientTransportCustomizer is auto-configured and applies OAuth2 support
(request customization and authorization error handling) to each transport automatically. No extra beans are needed.
Manual configuration:
You can expose an OAuth2HttpClientTransportCustomizer bean, which configures both the request customizer
and the authorization error handler on each transport:
@Configuration
class McpConfiguration {
@Bean
OAuth2HttpClientTransportCustomizer transportCustomizer(
OAuth2AuthorizedClientManager clientManager,
ClientRegistrationRepository clientRegistrationRepository,
McpOAuth2ClientManager mcpOAuth2ClientManager
) {
return new OAuth2HttpClientTransportCustomizer(
clientManager,
clientRegistrationRepository,
mcpOAuth2ClientManager
);
}
}By default, the customizer maps each MCP connection name to an OAuth2 client registration with the same name. You can customize this mapping:
// Use a single registration for all transports
new OAuth2HttpClientTransportCustomizer(clientManager, repo, mcpManager, "my-registration-id");
// Or use a custom resolver function
new OAuth2HttpClientTransportCustomizer(clientManager, repo, mcpManager, transportName -> "prefix-" + transportName);Alternatively, if you do not need DCR or authorization error handling, you can use the lower-level request customizers directly:
OAuth2AuthorizationCodeSyncHttpRequestCustomizer(user-level tokens)OAuth2ClientCredentialsSyncHttpRequestCustomizer(machine-to-machine)OAuth2HybridSyncHttpRequestCustomizer(both)
All request customizers rely on authentication data passed through McpTransportContext.
When not using the Boot auto-configuration, you need to add an AuthenticationMcpTransportContextProvider:
@Configuration
class McpConfiguration {
@Bean
McpSyncClientCustomizer syncClientCustomizer() {
return (name, syncSpec) ->
syncSpec.transportContextProvider(
new AuthenticationMcpTransportContextProvider()
);
}
@Bean
McpSyncHttpClientRequestCustomizer requestCustomizer(
OAuth2AuthorizedClientManager clientManager,
ClientRegistrationRepository clientRegistrationRepository
) {
return new OAuth2AuthorizationCodeSyncHttpRequestCustomizer(
clientManager,
clientRegistrationRepository,
"authserver"
);
}
}When using spring-ai-starter-mcp-client-webflux, the underlying MCP client transport will be based on a Spring
reactive WebClient.
In that case, you can expose a bean of type WebClient.Builder, configured with an MCP implementation of
ExchangeFilterFunction.
Depending on your authorization flow of choice, you may use one of the following
implementations:
McpOAuth2AuthorizationCodeExchangeFilterFunction(preferred)McpOAuth2ClientCredentialsExchangeFilterFunction(machine-to-machine)McpOAuth2HybridExchangeFilterFunction(last resort)
All these filter functions rely on authentication data passed through McpTransportContext.
When not using the Boot auto-configuration, you need to add an AuthenticationMcpTransportContextProvider:
@Configuration
class McpConfiguration {
@Bean
McpSyncClientCustomizer syncClientCustomizer() {
return (name, syncSpec) ->
syncSpec.transportContextProvider(
new AuthenticationMcpTransportContextProvider()
);
}
@Bean
WebClient.Builder mcpWebClientBuilder(OAuth2AuthorizedClientManager clientManager) {
return WebClient.builder().filter(
new McpOAuth2AuthorizationCodeExchangeFilterFunction(
clientManager,
"authserver"
)
);
}
}When using the .stream() method of the chat client, you will be using Reactor under the hood. Reactor does not
guarantee on which thread the work is executed, and will lose thread locals. You need to manually extract the
information and inject it in the Reactor context:
class Example {
void doTheThing() {
chatClient
.prompt("<your prompt>")
.stream()
.content()
// ... any streaming operation ...
.contextWrite(AuthenticationMcpTransportContextProvider.writeToReactorContext());
}
}MCP Security's default client support integrates with Spring Security to add OAuth2 support. Essentially, it gets a token on behalf of the user, and modifies the HTTP request from the Client to the Server, adding that token in an Authorization header.
If you'd like to modify HTTP requests beyond what MCP Security provides, you can create your own
McpSyncHttpClientRequestCustomizer or ExchangeFilterFunction.
For HTTP clients:
@Configuration
class McpConfiguration {
@Bean
McpSyncHttpClientRequestCustomizer requestCustomizer() {
return (builder, method, endpoint, body, context) ->
builder
.header("x-custom-header", "custom-value")
.header("x-life-the-universe-everything", "42");
}
}For web clients:
@Configuration
class McpConfiguration {
@Bean
WebClient.Builder mcpWebClientBuilder() {
return WebClient.builder().filter((request, next) -> {
var newRequest = ClientRequest.from(request)
.header("x-custom-header", "custom-value")
.header("x-life-the-universe-everything", "42")
.build();
return next.exchange(newRequest);
});
}
}There is no way to guarantee on which thread these request customizers will run.
As such, thread-locals are not available in these lambda functions.
If you would like to use thread-locals in this context, use a McpTransportContextProvider bean.
It can extract thread-locals and make them available in an McpTransportContext object.
For HttpClient-based request customizers, the McpTransportContext will be available in the customize method. See,
for example, with a Sync client (async works similarly):
@Configuration
class McpConfiguration {
@Bean
McpSyncClientCustomizer syncClientCustomizer() {
return (name, syncSpec) -> syncSpec.transportContextProvider(() -> {
var myThing = MyThreadLocalThing.get();
return McpTransportContext.create(Map.of("custom-key", myThing));
});
}
@Bean
McpSyncHttpClientRequestCustomizer requestCustomizer() {
return (builder, method, endpoint, body, context) ->
builder.header("x-custom-header", context.get("custom-key"));
}
}For WebClient-based filter functions, the McpTransportContext will be available in the Reactor context, under
McpTransportContext.KEY:
@Configuration
class McpConfiguration {
@Bean
McpSyncClientCustomizer syncClientCustomizer() {
return (name, syncSpec) -> syncSpec.transportContextProvider(() -> {
var myThing = MyThreadLocalThing.get();
return McpTransportContext.create(Map.of("custom-key", myThing));
});
}
@Bean
WebClient.Builder mcpWebClientBuilder() {
return WebClient.builder()
.filter((request, next) ->
Mono.deferContextual(reactorCtx -> {
var transportCtx = reactorCtx.get(McpTransportContext.class);
String customThing = transportCtx.get("custom-key").toString();
var newRequest = ClientRequest.from(request)
.header("x-custom-header", customThing)
.build();
return next.exchange(newRequest);
})
);
}
}If you'd like to bypass Spring AI's autoconfiguration altogether, you can create the MCP clients programmatically. The easiest way is to draw some inspiration on the transport auto-configurations (HttpClient, WebClient) as well as the client auto-configuration.
All in all, it could look like so:
// For HttpClient-based clients
@Bean
McpSyncClient client(
ObjectMapper objectMapper,
McpSyncHttpClientRequestCustomizer requestCustomizer,
McpClientCommonProperties commonProps
) {
var transport = HttpClientStreamableHttpTransport.builder(mcpServerUrl)
.clientBuilder(HttpClient.newBuilder())
.jsonMapper(new JacksonMcpJsonMapper(objectMapper))
.httpRequestCustomizer(requestCustomizer)
.build();
var clientInfo = new McpSchema.Implementation("client-name", commonProps.getVersion());
return McpClient.sync(transport)
.clientInfo(clientInfo)
.requestTimeout(commonProps.getRequestTimeout())
.transportContextProvider(new AuthenticationMcpTransportContextProvider())
.build();
}
//
// -------------------------
//
// For WebClient based clients
@Bean
McpSyncClient client(
WebClient.Builder mcpWebClientBuilder,
ObjectMapper objectMapper,
McpClientCommonProperties commonProperties
) {
var builder = mcpWebClientBuilder.baseUrl(mcpServerUrl);
var transport = WebClientStreamableHttpTransport.builder(builder)
.jsonMapper(new JacksonMcpJsonMapper(objectMapper))
.build();
var clientInfo = new McpSchema.Implementation("clientName", commonProperties.getVersion());
return McpClient.sync(transport)
.clientInfo(clientInfo)
.requestTimeout(commonProperties.getRequestTimeout())
.transportContextProvider(new AuthenticationMcpTransportContextProvider())
.build();
}You can then add it to the tools available to a chat client:
var chatResponse = chatClient.prompt("Prompt the LLM to _do the thing_")
.toolCallbacks(new SyncMcpToolCallbackProvider(mcpClient1, mcpClient2, mcpClient3))
.call()
.content();- Spring WebFlux servers are not supported.
- Spring AI autoconfiguration initializes the MCP client on app start.
Most MCP servers want calls to be authenticated with a token, so you
need to turn initialization off with
spring.ai.mcp.client.initialized=false.
Note:
- Unlike the
mcp-server-securitymodule, the client implementation supports the SSE transport, both withHttpClientandWebClient.
Enhances Spring Security's OAuth 2.0 Authorization Server support with the RFCs and features relevant to the MCP authorization spec, such as Dynamic Client Registration and Resource Indicators.
The easiest way to set up an MCP authorization server is with the Boot auto-configuration module.
It provides default SecurityFilterChains that secure all endpoints and configure an MCP authorization server, with no additional configuration required.
Maven
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>mcp-authorization-server-spring-boot</artifactId>
<version>0.1.6</version>
</dependency>Gradle
implementation("org.springaicommunity:mcp-authorization-server-spring-boot:0.1.6")If you prefer wiring beans yourself (e.g. for advanced customization or non-Boot use-cases), you can use the lower-level mcp-authorization-server module directly.
Maven
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>mcp-authorization-server</artifactId>
<version>0.1.6</version>
</dependency>Gradle
implementation("org.springaicommunity:mcp-authorization-server:0.1.6")Configure the authorization server properties (see reference documentation).
Here is an example application.yml for registering a default client:
spring:
application:
name: sample-authorization-server
security:
oauth2:
authorizationserver:
client:
default-client:
token:
access-token-time-to-live: 1h
registration:
client-id: "default-client"
client-secret: "{noop}default-secret"
client-authentication-methods:
- "client_secret_basic"
- "none"
authorization-grant-types:
- "authorization_code"
- "client_credentials"
redirect-uris:
- "http://127.0.0.1:8080/authorize/oauth2/code/authserver"
- "http://localhost:8080/authorize/oauth2/code/authserver"
# mcp-inspector
- "http://localhost:6274/oauth/callback"
# claude code
- "https://claude.ai/api/mcp/auth_callback"
user:
# A single user, named "user"
name: user
password: password
server:
servlet:
session:
cookie:
# Override the default cookie name (JSESSIONID).
# This allows running multiple Spring apps on localhost, and they'll each have their own cookie.
# Otherwise, since the cookies do not take the port into account, they are confused.
name: MCP_AUTHORIZATION_SERVER_SESSIONIDWhen using the manual setup, you must configure the authorization server properties as shown above, and then activate the authorization server capabilities with the usual Spring Security APIs in your security filter chain:
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) {
return http
// all requests must be authenticated
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
// enable authorization server customizations
.with(McpAuthorizationServerConfigurer.mcpAuthorizationServer(), withDefaults())
// enable form-based login, for user "user"/"password"
.formLogin(withDefaults())
.build();
}By default, the authorization server supports Dynamic Client Registration (DCR).
If you are using the Boot auto-configuration (mcp-authorization-server-spring-boot), you can disable it with the following property:
spring.ai.mcp.authorizationserver.dynamic-client-registration.enabled=falseIf you are configuring the server manually, you can disable it via the configurer:
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) {
return http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.with(McpAuthorizationServerConfigurer.mcpAuthorizationServer(), mcp -> {
mcp.dynamicClientRegistration(false);
})
.build();
}- Spring WebFlux servers are not supported.
- Every client supports ALL
resourceidentifiers.
The samples directory contains samples for these libraries.
A README.md contains instructions for running
those samples.
A special directory is samples/integration-tests, which contains integration tests for all the submodules in this
project.
This is a work-in-progress, but with mcp-server-security, and a supporting mcp-authorization-server, you should be
able to integrate with Cursor, Claude Code, and the MCP inspector.
Note: if you use the MCP Inspector you may need to turn off CSRF and CORS protection.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Note: This is a community-driven project and is not officially endorsed by Spring AI or the MCP project.