Summary
The MCP endpoint currently has no authentication/authorization and only supports SSE transport. As agentic development becomes mainstream, the MCP server needs to be a first-class citizen in kafbat's security model — not an afterthought that bypasses existing controls (see #1751).
This proposal outlines adding OAuth 2.0 Protected Resource semantics and Streamable HTTP transport to the MCP endpoint, making it compatible with enterprise authorization servers and the latest MCP specification.
Motivation
Key Finding: Streamable HTTP Already Available
WebFluxStreamableServerTransportProvider — the reactive/WebFlux-native streamable HTTP transport — already exists in mcp-spring-webflux v0.18.x. Kafbat is on v0.10.0 which only has WebFluxSseServerTransportProvider. This is a version bump, not new development.
| SDK version |
WebFlux transports available |
| 0.10.0 (kafbat today) |
WebFluxSseServerTransportProvider only |
| 0.18.2 |
WebFluxSseServerTransportProvider + WebFluxStreamableServerTransportProvider + WebFluxStatelessServerTransport |
As of MCP Java SDK 1.0, the Spring transport modules moved to Spring AI:
| Before (0.x) |
After (1.0+) |
io.modelcontextprotocol.sdk:mcp-spring-webflux:0.18.2 |
org.springframework.ai:mcp-spring-webflux (Spring AI 2.0) |
Either coordinate works. The 0.18.x path is simpler since it doesn't require adopting the full Spring AI dependency tree.
Why Streamable HTTP Matters for OAuth
|
SSE (legacy) |
Streamable HTTP |
| Spec status |
Deprecated (2025-06-18) |
Current |
| Transport |
Two endpoints (/sse + /message) |
Single endpoint (/mcp) |
| Auth |
Awkward — EventSource in browsers can't set Authorization header |
Clean — Bearer token on every HTTP POST |
| Resumability |
Not built-in |
Built-in via session tokens |
| Proxy-friendly |
SSE connections problematic behind LBs |
Standard HTTP POST/GET |
The key point: SSE makes OAuth difficult because the initial EventSource connection cannot set custom headers. Streamable HTTP is standard HTTP with Bearer tokens on every request — exactly what OAuth resource servers expect.
Proposed Changes
Phase 1: Upgrade MCP SDK + Add Streamable HTTP Transport
Bump mcp-spring-webflux from 0.10.0 → 0.18.2. In McpConfig.java, replace:
// Current (SSE only, v0.10.0)
@Bean
public WebFluxSseServerTransportProvider sseServerTransport(ObjectMapper mapper) {
return new WebFluxSseServerTransportProvider(mapper, "/mcp/message", "/mcp/sse");
}
With:
// Streamable HTTP (v0.18.x)
@Bean
public WebFluxStreamableServerTransportProvider streamableServerTransport(ObjectMapper mapper) {
return WebFluxStreamableServerTransportProvider.builder()
.objectMapper(mapper)
.messageEndpoint("/mcp")
.build();
}
Keep SSE available for backwards compatibility (both transports can coexist). The RouterFunction bean and McpAsyncServer wiring remain structurally the same.
Phase 2: OAuth 2.0 Resource Server for MCP (Security)
Wire Spring Security's existing reactive resource server support into the MCP endpoint paths.
2a. Protected Resource Discovery Endpoint
Expose GET /.well-known/oauth-protected-resource (RFC 9728) that tells MCP clients where to authenticate:
{
"resource": "https://your-kafbat-instance.com/mcp",
"authorization_servers": ["https://your-oauth-server.com"],
"jwks_uri": "https://your-oauth-server.com/.well-known/jwks.json",
"scopes_supported": ["mcp.read", "mcp.write"]
}
Configurable via application properties:
mcp:
enabled: true
transport: streamable-http # or "sse" for backwards compat
auth:
enabled: true
resource-url: ${MCP_RESOURCE_URL:}
authorization-server: ${MCP_AUTH_SERVER:}
jwks-uri: ${MCP_JWKS_URI:}
audience: ${MCP_AUDIENCE:}
scopes-supported:
- mcp.read
- mcp.write
2b. JWT Validation on MCP Endpoints
Apply the existing oauth2ResourceServer JWT filter chain to /mcp/** paths. Since kafbat already supports resource server configuration (auth.oauth2.resource-server), this means ensuring the MCP router function participates in the same SecurityWebFilterChain.
The existing RBAC role extraction already works for REST API endpoints and can be reused for MCP sessions directly. (and is being improved upon)
2c. Scope-Based Tool Gating
Respect OAuth scopes to control which MCP tools are available:
mcp.read — read-only tools (list topics, describe configs, get messages)
mcp.write — mutating tools (create topics, produce messages, alter configs)
This also fixes #1751 more robustly: scope enforcement at the security layer prevents unauthorized mutations before tool dispatch runs.
Configuration Design
mcp:
enabled: true
transport: streamable-http # "sse" | "streamable-http" | "both"
auth:
enabled: true # default false for backwards compat
resource-url: ${MCP_RESOURCE_URL:}
authorization-server: ${MCP_AUTH_SERVER:}
jwks-uri: ${MCP_JWKS_URI:}
audience: ${MCP_AUDIENCE:}
scopes-supported:
- mcp.read
- mcp.write
scope-enforcement:
read-tools: mcp.read
write-tools: mcp.write
When mcp.auth.enabled=false, the MCP endpoint behaves as it does today (no auth). This preserves backwards compatibility for local/dev usage.
Implementation Considerations
- Reactive stack: Kafbat is WebFlux-based (Spring Boot 3.5, Spring Security 6.x). The
WebFluxStreamableServerTransportProvider is purpose-built for this — it uses RouterFunction<ServerResponse>, Mono/Flux, and Netty.
- Existing RBAC integration: JWT validation + configurable claim extraction already works for REST endpoints. Extending to MCP paths reuses the same
RbacReactiveJwtAuthenticationConverter.
- Backwards compatibility: SSE transport should remain available (with deprecation notice) while streamable HTTP is added. A
mcp.transport config property can control which transports are active.
Prior Art
Related Issues
Questions for the Community
- Should the SDK upgrade target 0.18.x (minimal change) or Spring AI 2.0 coordinates (future-proof)?
- Should
mcp.auth.enabled default to true or false? (Backwards compat vs. secure-by-default)
- Should scope enforcement be configurable per-tool, or is the read/write binary sufficient?
Proposed Implementation Order
- Phase 1: Bump SDK version + add
WebFluxStreamableServerTransportProvider alongside SSE
- Phase 2a:
/.well-known/oauth-protected-resource endpoint
- Phase 2b: JWT validation on
/mcp/** paths (reuse existing resource server config)
- Phase 2c: Scope-based tool gating
Happy to contribute implementation for all phases. Would appreciate maintainer input on the SDK version strategy (0.18.x vs Spring AI 2.0 coordinates).
Summary
The MCP endpoint currently has no authentication/authorization and only supports SSE transport. As agentic development becomes mainstream, the MCP server needs to be a first-class citizen in kafbat's security model — not an afterthought that bypasses existing controls (see #1751).
This proposal outlines adding OAuth 2.0 Protected Resource semantics and Streamable HTTP transport to the MCP endpoint, making it compatible with enterprise authorization servers and the latest MCP specification.
Motivation
readOnlyrestrictions because it sidesteps the HTTP filter chain entirely. This is symptomatic of a deeper issue: the MCP endpoint has no security integration.Key Finding: Streamable HTTP Already Available
WebFluxStreamableServerTransportProvider— the reactive/WebFlux-native streamable HTTP transport — already exists inmcp-spring-webfluxv0.18.x. Kafbat is on v0.10.0 which only hasWebFluxSseServerTransportProvider. This is a version bump, not new development.WebFluxSseServerTransportProvideronlyWebFluxSseServerTransportProvider+WebFluxStreamableServerTransportProvider+WebFluxStatelessServerTransportAs of MCP Java SDK 1.0, the Spring transport modules moved to Spring AI:
io.modelcontextprotocol.sdk:mcp-spring-webflux:0.18.2org.springframework.ai:mcp-spring-webflux(Spring AI 2.0)Either coordinate works. The 0.18.x path is simpler since it doesn't require adopting the full Spring AI dependency tree.
Why Streamable HTTP Matters for OAuth
/sse+/message)/mcp)EventSourcein browsers can't setAuthorizationheaderThe key point: SSE makes OAuth difficult because the initial EventSource connection cannot set custom headers. Streamable HTTP is standard HTTP with Bearer tokens on every request — exactly what OAuth resource servers expect.
Proposed Changes
Phase 1: Upgrade MCP SDK + Add Streamable HTTP Transport
Bump
mcp-spring-webfluxfrom 0.10.0 → 0.18.2. InMcpConfig.java, replace:With:
Keep SSE available for backwards compatibility (both transports can coexist). The
RouterFunctionbean andMcpAsyncServerwiring remain structurally the same.Phase 2: OAuth 2.0 Resource Server for MCP (Security)
Wire Spring Security's existing reactive resource server support into the MCP endpoint paths.
2a. Protected Resource Discovery Endpoint
Expose
GET /.well-known/oauth-protected-resource(RFC 9728) that tells MCP clients where to authenticate:{ "resource": "https://your-kafbat-instance.com/mcp", "authorization_servers": ["https://your-oauth-server.com"], "jwks_uri": "https://your-oauth-server.com/.well-known/jwks.json", "scopes_supported": ["mcp.read", "mcp.write"] }Configurable via application properties:
2b. JWT Validation on MCP Endpoints
Apply the existing
oauth2ResourceServerJWT filter chain to/mcp/**paths. Since kafbat already supports resource server configuration (auth.oauth2.resource-server), this means ensuring the MCP router function participates in the sameSecurityWebFilterChain.The existing RBAC role extraction already works for REST API endpoints and can be reused for MCP sessions directly. (and is being improved upon)
2c. Scope-Based Tool Gating
Respect OAuth scopes to control which MCP tools are available:
mcp.read— read-only tools (list topics, describe configs, get messages)mcp.write— mutating tools (create topics, produce messages, alter configs)This also fixes #1751 more robustly: scope enforcement at the security layer prevents unauthorized mutations before tool dispatch runs.
Configuration Design
When
mcp.auth.enabled=false, the MCP endpoint behaves as it does today (no auth). This preserves backwards compatibility for local/dev usage.Implementation Considerations
WebFluxStreamableServerTransportProvideris purpose-built for this — it usesRouterFunction<ServerResponse>,Mono/Flux, and Netty.RbacReactiveJwtAuthenticationConverter.mcp.transportconfig property can control which transports are active.Prior Art
Related Issues
Questions for the Community
mcp.auth.enableddefault totrueorfalse? (Backwards compat vs. secure-by-default)Proposed Implementation Order
WebFluxStreamableServerTransportProvideralongside SSE/.well-known/oauth-protected-resourceendpoint/mcp/**paths (reuse existing resource server config)Happy to contribute implementation for all phases. Would appreciate maintainer input on the SDK version strategy (0.18.x vs Spring AI 2.0 coordinates).