Repository navigation
completion/complete should return EMPTY_COMPLETION_RESULT when no handler registered #991
Description
Bug description
When a prompt has at least one @McpComplete handler registered somewhere in the server, the completions capability gets enabled globally. After that, the upstream MCP Inspector (and any well-behaved client) fires completion/complete for every keystroke in any prompt-argument or resource-template-argument input.
The problem is that the Java server throws -32602 "AsyncCompletionSpecification not found" for any prompt or resource template that does not have its own matching @McpComplete. The Inspector shows this as a red error toast on every keystroke, which is pretty bad UX for what should be a silent "no suggestions" case.
The TypeScript and Python reference SDKs handle this gracefully, they just return an empty completion result. The Java SDK already returns an empty result in several sibling branches of the same handler (including the one fixed by #934). Only the final "no spec registered for this ref" branch still throws.
Environment
io.modelcontextprotocol.sdk:mcp-core0.18.2- Spring AI 1.1.7 with
spring-ai-starter-mcp-server-webmvc org.springaicommunity:mcp-annotations0.9.0- Spring Boot 3.5.14, Java 17
- Client: MCP Inspector 0.21.2, but any client that calls
completion/completeon a partially-covered prompt reproduces it
The same throw site exists unchanged in every released version I checked: v0.18.2, 1.0.0-RC3, v1.1.0, v1.1.2, v1.1.3, v2.0.0 M3 and on main, atm no open PR/Issue currently with this problem
Steps to reproduce
The bug lives in the server classes, not in the transports. The same if (specification == null) { return Mono.error(...) } block exists in two places:
McpAsyncServer.completionCompleteRequestHandler()used by all stateful transports (Streamable HTTP, SSE, stdio)McpStatelessAsyncServer.completionCompleteRequestHandler()used by the stateless HTTP transport
McpSyncServer and McpStatelessSyncServer are thin wrappers over the async counterparts and inherit the same behavior. Reproduced the -32602 on Streamable HTTP, SSE, and stateless HTTP.
- Build a Spring AI MCP server with two prompts, where only one of them has an
@McpCompletehandler. The presence of any@McpCompleteis enough to enable thecompletionscapability for the whole server. - Connect the MCP Inspector and open the prompt that has no completion handler.
- Type anything in one of its argument fields.
The server responds:
{
"jsonrpc": "2.0",
"id": 42,
"error": {
"code": -32602,
"message": "AsyncCompletionSpecification not found: PromptReference[type=ref/prompt, name=greeting, title=null]"
}
}The Inspector renders this as a red error toast on every keystroke.

Expected behavior
A well-formed completion/complete request for a ref that has no registered handler should return an empty result:
{
"jsonrpc": "2.0",
"id": 42,
"result": {
"completion": {
"values": [],
"total": 0,
"hasMore": false
}
}
}The client UI then shows "no suggestions" silently, which is what users see when they talk to a TypeScript or Python MCP server.
The current strict throw is still appropriate when the ref itself is malformed, unknown prompt name, missing resource template, or an argument that is not actually a template variable. Those cases are already handled and should stay as they are.
Minimal Complete Reproducible example
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }
@Component
public static class Prompts {
// covered by a completion handler
@McpPrompt(name = "covered", description = "Prompt with completion")
public GetPromptResult covered(@McpArg(name = "topic", required = true) String topic) {
return new GetPromptResult("", List.of(new PromptMessage(Role.USER, new TextContent(topic))));
}
@McpComplete(prompt = "covered")
public List<String> coveredTopic(String value) {
return List.of("alpha", "beta", "gamma");
}
// no @McpComplete completion/complete on its argument throws
@McpPrompt(name = "uncovered", description = "Prompt with no completion handler")
public GetPromptResult uncovered(@McpArg(name = "name", required = true) String name) {
return new GetPromptResult("", List.of(new PromptMessage(Role.USER, new TextContent("Hi " + name))));
}
}
}spring:
ai:
mcp:
server:
protocol: STREAMABLE
streamable-http:
mcp-endpoint: /mcpAfter initialize + notifications/initialized:
curl -H "Accept: application/json, text/event-stream" \
-H "Content-Type: application/json" \
-H "mcp-session-id: <session>" \
-X POST http://localhost:8080/mcp \
-d '{
"jsonrpc":"2.0","id":42,"method":"completion/complete",
"params":{
"ref":{"type":"ref/prompt","name":"uncovered"},
"argument":{"name":"name","value":"A"}
}
}'The server returns the -32602 error shown above. Expected: an empty completion result.
How the other SDKs handle this
In the TypeScript SDK, handlePromptCompletion only throws when the prompt name itself is unknown or the prompt is disabled. If the prompt has no argsSchema, no completable() field, or no completer function, it returns an empty result.
The Python SDK does it through a single decorator-style handler, when the user-provided function returns None, the SDK wraps it as Completion(values=[], total=None, hasMore=None).
The Java SDK already does the same in several places inside completionCompleteRequestHandler. The constant EMPTY_COMPLETION_RESULT is used for missing argument names and for fixed (non-template) resource URIs. The TS-style comment in the resource branch ("not an error in the spec (but probably should be)") is even copied verbatim into the Java file. So the intent is clearly aligned with the other SDKs, only this one branch was left strict.
Propose fix
Change in both McpAsyncServer.java and McpStatelessAsyncServer.java, completionCompleteRequestHandler() completionCompleteRequestHandler() methods:
- if (specification == null) {
- return Mono.error(McpError.builder(ErrorCodes.INVALID_PARAMS)
- .message("AsyncCompletionSpecification not found: " + request.ref())
- .build());
- }
+ if (specification == null) {
+ logger.debug("No completion specification for ref: {}", request.ref());
+ return EMPTY_COMPLETION_RESULT;
+ }Ready to discuss and open PR
Related
- Stateless server handler throws exception instead of returning JSON-RPC -32601 (Method not found) for not registered capabilities #784 same pattern
- NullPointerException in completion handler when Prompt has null arguments #932 / PR fix: Return empty prompt completion result when prompt has no arguments #934 the recent fix for problem like this
Activity
[-]completion/complete throws -32602 when no @McpComplete handler matches the ref; should return EMPTY_COMPLETION_RESULT to match TS/Python SDK[/-][+]completion/complete should return EMPTY_COMPLETION_RESULT when no handler registered[/+]a-simeshin commentedon Jun 25, 2026
@Kehrlann @jstar0 tyvm!