-
Notifications
You must be signed in to change notification settings - Fork 1.3k
MCP server: Authentication lost in tool execution #2506
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
Comments
Same issue here. This is a major blocker for any remote server application that relies on authentication. |
I had similar issue, and had a quick look at the implementation (for 1.0.0-M6 and respective dependency versions) For this to work, the mono chain needs to be preserved through out the entire chain. Having a brief look, it seems that the implementation of the Seems it's not spring-ai issue. After changing the implementation in public DefaultMcpSession(Duration requestTimeout, McpTransport transport,
Map<String, RequestHandler<?>> requestHandlers, Map<String, NotificationHandler> notificationHandlers) {
Assert.notNull(requestTimeout, "The requstTimeout can not be null");
Assert.notNull(transport, "The transport can not be null");
Assert.notNull(requestHandlers, "The requestHandlers can not be null");
Assert.notNull(notificationHandlers, "The notificationHandlers can not be null");
this.requestTimeout = requestTimeout;
this.transport = transport;
this.requestHandlers.putAll(requestHandlers);
this.notificationHandlers.putAll(notificationHandlers);
// TODO: consider mono.transformDeferredContextual where the Context contains
// the
// Observation associated with the individual message - it can be used to
// create child Observation and emit it together with the message to the
// consumer
this.connection = this.transport.connect(
mono -> mono.flatMap(message -> {
if (message instanceof McpSchema.JSONRPCResponse response) {
logger.debug("Received Response: {}", response);
var sink = pendingResponses.remove(response.id());
if (sink == null) {
logger.warn("Unexpected response for unkown id {}", response.id());
}
else {
sink.success(response);
}
return Mono.just(message);
}
else if (message instanceof McpSchema.JSONRPCRequest request) {
logger.debug("Received request: {}", request);
return handleIncomingRequest(request).flatMap(transport::sendMessage).onErrorResume(
error -> {
var errorResponse = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(),
null, new McpSchema.JSONRPCResponse.JSONRPCError(
McpSchema.ErrorCodes.INTERNAL_ERROR, error.getMessage(), null));
return transport.sendMessage(errorResponse);
}).thenReturn(message);
}
else if (message instanceof McpSchema.JSONRPCNotification notification) {
logger.debug("Received notification: {}", notification);
return handleIncomingNotification(notification).thenReturn(message);
}
return Mono.just(message);
})
).subscribe();
} SecurityContext is preserved. The main issue in the original implementation is the subscription to the handler that is being done instead of chaining the response with the request: else if (message instanceof McpSchema.JSONRPCRequest request) {
logger.debug("Received request: {}", request);
handleIncomingRequest(request).subscribe(response -> transport.sendMessage(response).subscribe(),
error -> {
var errorResponse = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(),
null, new McpSchema.JSONRPCResponse.JSONRPCError(
McpSchema.ErrorCodes.INTERNAL_ERROR, error.getMessage(), null));
transport.sendMessage(errorResponse).subscribe();
});
} |
@GregoireW please also note that despite fixing the issue as above, the I had to create registrations by hand @Bean
public List<McpServerFeatures.AsyncToolRegistration> tools(TaskService taskService,
ObjectMapper objectMapper) {
return List.of(
new McpServerFeatures.AsyncToolRegistration(
new McpSchema.Tool("tasks", "tasks", "{\n" +
" \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n" +
" \"$id\": \"file://schemas/simple.schema.json\",\n" +
" \"title\": \"simplified data\",\n" +
" \"description\": \"simple\",\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" }\n" +
" }}"),
(request) -> {
return taskService.getCurrentUserTasks()
.map(task -> {
try {
return new McpSchema.TextContent(
objectMapper.writeValueAsString(task)
);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
})
.cast(McpSchema.Content.class)
.collectList()
.flatMap(i -> Mono.just(new McpSchema.CallToolResult(i, false)));
}
)
);
} |
@emdzej Thanks for the heads up and for drilling a little bit more on that (I did not took a deep dive in this subject and did something else for my use case) Next time I will know what to check |
Looks like this has been fixed in mcp java-sdk 0.8.x https://github.com/modelcontextprotocol/java-sdk/blob/0.8.x/mcp/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java So... hopefully with next release will be covered :) |
Wonderful news! |
Doesnt seem to be working on snapshot yet, even tho it has mcp java-sdk 0.8.x. |
@los-ko have you tried with the tool annotation or manual registration? |
We have only tried with the tool annotation. |
Hi, if I don't use |
Bug description
I'm in a situation where I have authentication with OIDC (so an access token basically).
When I set authentication required on the /sse and /mcp/** endpoint, then the client side only connect when I provide the correct access token. This is ok.
Even when the client send a call to a tool, the authentication is needed, but inside the executed code, I cannot access the authentication.
SecurityContextHolder.getContext().getAuthentication() and ReactiveSecurityContextHolder.getContext() return null.
Long story short, I cannot control data ownership so this is bad, and my MCP server also execute some api call that need to be authenticated, and I use the oauth2ClientRequestInterceptor to do token exchange so this also fail. ( the MCP core even with SYNC option goes through reactive code and the original servlet thread is put on hold, giving the execution to a 'boundedElactic' thread )
Environment
Spring MVC ( springboot 3.4 )
Spring AI 1.0.0-M6 ( spring-ai-mcp-server-webmvc-spring-boot-starter )
spring-boot-starter-oauth2-resource-server (for oauth2 authentication )
Steps to reproduce
Enable authentication on an application, create a Tool that just return the authenticated user.
Expected behavior
I expect to be able to find the security context when a tool is called from MCP
Minimal Complete Reproducible example
Enable authentication on a springboot with MCP server activated,
Create a Tool
call the tool. It should answer your sub and not null.
The text was updated successfully, but these errors were encountered: