AUSTA | Adversarial Intelligence

MCP Security

Spring AI MCP Server Security: Authentication, OAuth2 & Tests

Protect Spring AI's MCP Streamable HTTP endpoint with OAuth2 JWT authentication, enforce a separate scope on each tool, and prove anonymous, cross-user, and unauthorized write requests fail. The linked example is pinned to Spring AI 2.0.1 and Spring Boot 4.1.1.

By Austa · Published · Example pinned to Spring AI 2.0.1 and Spring Boot 4.1.1 · ~14 min read

TL;DR (October 2026):

  • Spring AI's MCP HTTP transport wires up the endpoint; it does not supply authentication or authorization automatically.
  • The documented default endpoint is POST /mcp, and the starter's default security behavior accepts requests without credentials.
  • Spring Security can protect the HTTP route, but a route matcher cannot express every per-tool or per-resource decision because MCP calls share the same endpoint.
  • Test both HTTP access and the authorization decision for each sensitive tool. An authenticated call is not necessarily an authorized action.
  • Spring AI's separate community MCP Security module is marked work in progress in the documentation and has compatibility limitations to check.

A Spring AI MCP server can look protected because the surrounding application already uses Spring Security. That assumption is unsafe until you confirm that the MCP endpoint is part of the active SecurityFilterChain and that the authenticated principal is checked against the tool being called. MCP carries multiple operations through one transport endpoint, so securing the path and authorizing the operation are related but separate jobs.

What the starter exposes by default

Spring AI's MCP Server Boot Starter provides auto-configuration for tools, resources, prompts, and HTTP transports. The starter documentation says those transports are authentication-agnostic: they wire the endpoint but do not enforce authentication or authorization. It describes the default POST /mcp endpoint as accepting requests, which can let a network-reachable caller enumerate and invoke registered tools, resources, and prompts.

The practical consequence is that registering a method as an MCP tool is an exposure decision. A developer may have intended the tool for a trusted assistant running locally, then deploy the same application behind a load balancer where the endpoint is reachable by any client. If the tool can read a tenant record, run an operation, or make a privileged downstream request, the endpoint's reachability becomes a security boundary.

Do not assume that an endpoint is private because it uses a non-obvious path, is not linked from the application UI, or is called only by an internal agent today. Verify the listener address, ingress rules, reverse proxy routes, service mesh policy, and cloud load balancer. Then confirm the application itself denies requests that reach the endpoint without the required identity.

Protect the transport route first

Use a Spring Security filter chain that explicitly covers the configured MCP endpoint and the HTTP methods used by the transport. The exact path can be changed by application configuration, reverse proxy rewriting, or servlet context path, so inspect the built application rather than copying a matcher from an example. For a bearer-token API, verify issuer, signature, audience, expiry, and relevant claims. For session-cookie authentication, understand the CSRF protections that apply to state-changing requests.

Reference configuration: Spring AI 2.0.1 and Spring Boot 4.1.1

The example uses the WebMVC Streamable HTTP starter and the default /mcp endpoint. This filter chain requires a valid OAuth2 resource-server JWT for MCP requests. It exempts that path from CSRF only because this sample accepts bearer tokens and does not authenticate with browser cookies; keep CSRF protections if your deployment uses cookie sessions.

@Bean
SecurityFilterChain mcpSecurity(HttpSecurity http) throws Exception {
    return http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/mcp", "/mcp/**").authenticated()
            .anyRequest().permitAll())
        .csrf(csrf -> csrf.ignoringRequestMatchers("/mcp", "/mcp/**"))
        .oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()))
        .build();
}

Set spring.security.oauth2.resourceserver.jwt.issuer-uri to your trusted issuer. At the tool layer, add method security and check a narrow scope before using the authenticated identity to load a tenant-owned object:

import org.springframework.ai.mcp.annotation.McpTool;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;

@McpTool(name = "read_invoice", description = "Read an invoice summary")
@PreAuthorize("hasAuthority('SCOPE_invoice.read')")
public String readInvoice(String invoiceId) {
    String userId = SecurityContextHolder.getContext().getAuthentication().getName();
    return invoiceRepository.readForUser(invoiceId, userId);
}

The scope check answers whether the caller may use this tool. readForUser must still verify that the specific invoice belongs to that principal or tenant. The sample repeats that ownership check and tests the negative path instead of relying on a prompt or tool name to enforce it.

Authentication must be applied consistently to initialization and later requests. Do not leave a route that permits an unauthenticated caller to initialize a session, negotiate capabilities, or obtain a session identifier that later requests can reuse. If the selected transport supports more than POST, include its streaming or session-management requests in the review. A filter rule that covers one method while another method is exposed is still a boundary failure.

Check the behavior at each network layer:

There are cases where the MCP server runs on stdio inside a host process rather than serving HTTP. An HTTP filter chain does not secure that local process boundary. For stdio, assess who can start the process, which environment variables and files it can read, what operating-system identity it runs as, and which client applications can connect. Do not apply an HTTP control description to a transport that does not use HTTP.

Route authentication is not tool authorization

All MCP tools usually arrive through the same JSON-RPC route. A Spring Security request matcher sees an HTTP path and method; it may not know that one request calls a harmless search tool and another calls a sensitive account-update tool. Once the framework has authenticated the request, the application still needs to authorize the requested tool and its arguments for that principal.

Build an explicit policy around tool identity, authenticated user or service principal, tenant, target resource, and operation. Use the smallest set of tools the server needs. Avoid exposing a generic tool that can construct arbitrary SQL, execute unrestricted shell commands, fetch arbitrary URLs, or call a broad internal API. If a tool can cause a side effect, check the business permission immediately before that side effect and enforce it in the downstream service as well.

For example, a user may be allowed to read invoices belonging to their company but not to issue a refund. The policy should bind the principal to the tenant, check the target invoice belongs to that tenant, and separately check the refund permission and any approval threshold. A model instruction saying not to refund is not an authorization mechanism. The same is true for prompts that ask the model to check a user role: policy must live in trusted code or in a service that enforces it.

Make the tool policy inspect the parsed operation, not just a client-supplied label. The server should map a call to a registered tool and validate its argument schema before using the values. If one tool supports different operations based on an argument, authorize those operations separately. If a tool accepts a tenant ID, account ID, file path, or URL, verify that the authenticated principal may access that specific target.

Identity propagation and downstream calls

Decide whether a tool acts as the application, as an organization service, or on behalf of an individual user. These are different credential models. A service credential that can access every tenant must not become an accidental substitute for the caller's narrower permission. When a downstream API supports delegated user access, use an audience-specific token issued for that resource and preserve the relationship between the original user and the tool invocation.

Spring's SecurityContext can be available to the request thread while a tool implementation runs. If your application dispatches work asynchronously, on an executor, or across a message queue, verify how authentication context is propagated. Do not assume a user principal survives a background handoff. Either pass a narrowly scoped, verifiable authorization context or have the worker re-authorize against trusted records before it performs the operation.

Make identity visible in audit events as structured fields: subject, tenant, client or agent identity when known, tool name, resource identifier, policy outcome, request correlation ID, and downstream result. Redact access tokens, secrets, sensitive prompts, and unnecessary personal data. A trace that records only an HTTP 200 response does not show which identity was authorized to perform the tool call.

Regression test matrix

TestRequest or setupExpected result
Anonymous accessSend initialize, tools/list, and a harmless tools/call request without credentials.Every protected operation is denied before tool execution. No tool inventory leaks if policy requires authentication first.
Invalid credentialsRepeat with an expired token, wrong issuer, wrong audience, bad signature, and malformed bearer header.Each request is rejected consistently; none falls through to anonymous access.
HTTP method coverageExercise every method and path used by the selected transport, including streaming or session requests if configured.No alternate method or rewritten route bypasses the same authentication policy.
Cross-user readAuthenticate as tenant A and request a test record owned by tenant B through a read tool.The tool or downstream service denies access even though the endpoint request is authenticated.
Unauthorized side effectAuthenticate a user who can read but cannot update, delete, approve, or refund a test object.The operation is denied before the side effect, and no downstream write is recorded.
Argument substitutionChange the target account, tenant, URL, or resource identifier while keeping the same token.The server authorizes the actual parsed target, not a separate client-provided identity hint.
Async handoffCall a test tool that queues work or runs on an executor, then inspect the worker's authorization decision.The worker receives a verifiable, scoped context or re-checks permission. It does not run as an ambient broad service account.
Transport boundaryTry the MCP endpoint through the public ingress, internal ingress, and direct service address from an untrusted network zone.Only approved paths and callers can reach it; network restriction and application authentication agree.

Keep these tests in a regression suite that calls the running application through its real security chain. A unit test of a tool method can prove one policy branch but misses proxy routing, filter ordering, authentication conversion, and transport behavior. For side effects, use a fake downstream service or a test tenant and assert that denied calls produced no write.

The Spring AI security example pins Spring AI 2.0.1 and Spring Boot 4.1.1. Run mvn test to check unauthenticated HTTP access, object ownership, scope denial, and that a denied refund produces zero writes. It does not contact a live identity provider; set OIDC_ISSUER_URI for a real deployment.

Review Spring AI's security module as a dependency

Spring AI documents a separate MCP Security project with OAuth and API-key support, including a server module and examples. Its documentation currently labels the project work in progress, community-driven, and not yet officially endorsed by Spring AI or the MCP project. It also lists implementation compatibility constraints, including WebMVC-based MCP servers for the server-security module. Treat this as a dependency under review, not as a guarantee that every starter and transport is covered.

Before adopting it, check the exact Spring AI and Spring Boot versions, transport, servlet stack, token validation behavior, supported HTTP methods, tool authorization hooks, and maintenance status in the release you deploy. Confirm the behavior with the test matrix above. A library can provide useful plumbing while the application still owns its authorization rules and data boundaries.

Configuration review checklist

The framework starter gets a server listening and registers capabilities. Your production security review must still prove who can reach that server, who can call each capability, which resources those calls may touch, and where the final side effect is enforced. These checks matter as much as the prompt-level behavior of the model.

FAQ

Does Spring AI secure an MCP server automatically?
No. Spring AI's MCP server transport documentation says the HTTP endpoint is authentication-agnostic and the default endpoint accepts requests without credentials. Add and test a security layer before exposing the endpoint beyond a trusted local environment.
Is securing POST /mcp enough?
Not always. Confirm the actual configured endpoint, reverse-proxy path, servlet context, and every method used by the selected transport. Also test tool-level authorization because multiple MCP operations share the HTTP route.
Does a valid login allow every MCP tool?
No. Authentication identifies the caller. The application still needs to decide which tool, resource, tenant, and operation that caller is allowed to use, and the downstream service should enforce the permission for consequential actions.

Spring AI's server-starter documentation describes the default endpoint and the transport's authentication-agnostic behavior in its MCP Server Boot Starter reference.