2.5 KiB
Security
The MCP endpoint is secured by flash-ext-security-core:
whatever mechanisms the application registers — OAuth2 bearer tokens, API keys, custom ones —
authenticate /mcp exactly as they authenticate every other route.
McpConfig.security(...) |
|
|---|---|
REQUIRED (default) |
every call must be authenticated; boot fails without a SecurityExtension |
NONE |
a public endpoint; a tool carrying security annotations fails the boot |
OAuth2 protected resource
When a registered mechanism publishes an OAuth2 issuer — flash-ext-security-oidc does — the endpoint
behaves as the MCP authorization spec requires, with nothing to configure:
GET /.well-known/oauth-protected-resource/mcpserves RFC 9728 metadata: theresource(derived per request fromX-Forwarded-Proto/-HostorHost), every issuer asauthorization_servers, andscopes_supportedwhenMcpConfig.scopesSupported(...)is set;- an anonymous call gets
401withWWW-Authenticate: Bearer resource_metadata="…"; - a token whose
auddoes not include the resource is403(RFC 8707) and logged atWARN. Credentials that are not audience-bound, such as API keys, are unaffected.
For Keycloak, the audience comes from an Audience protocol mapper whose included custom audience is
the resource URL, attached to a client scope every MCP client receives (the built-in basic scope is the
one that needs no client cooperation). Clients that register dynamically need Keycloak's anonymous
client registration policies relaxed for the trusted hosts.
McpConfig.requireTokenAudience(false) drops that last check for an authorization server that cannot
mint a resource audience at all — Keycloak ignores RFC 8707's resource parameter, so a deployment that
cannot add the mapper has no other way in. Every token a registered issuer signs is then accepted on the
endpoint, and the boot logs say so.
Tool policies
The core annotations work on tools as on handlers, checked per tools/call against the caller the route
authenticated:
@Tool(name = "approve", description = "Approves a pending proposal")
@RolesAllowed(value = "REVIEWER", on = {"project", "locale"}) // read from the tool's arguments
public class ApproveTool extends McpTool { … }
A denial is a tool result with isError: true — the call reached the server, the tool did not run.
McpConfig.middleware(...) runs after authentication, for rate limiting, auditing or tracing.