**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-core` 0.18.2 - Spring AI 1.1.7 with `spring-ai-starter-mcp-server-webmvc` - `org.springaicommunity:mcp-annotations` 0.9.0 - Spring Boot 3.5.14, Java 17 - Client: MCP Inspector 0.21.2, but any client that calls `completion/complete` on 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**. 1. Build a Spring AI MCP server with two prompts, where only one of them has an `@McpComplete` handler. The presence of any `@McpComplete` is enough to enable the `completions` capability for the whole server. 2. Connect the MCP Inspector and open the prompt that has no completion handler. 3. Type anything in one of its argument fields. The server responds: ```json { "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. <img width="1908" height="946" alt="Image" src="https://github.com/user-attachments/assets/410bfb65-2955-4fe5-9326-115ad624115f" /> **Expected behavior** A well-formed `completion/complete` request for a ref that has no registered handler should return an empty result: ```json { "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** ```java @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)))); } } } ``` ```yaml spring: ai: mcp: server: protocol: STREAMABLE streamable-http: mcp-endpoint: /mcp ``` After `initialize` + `notifications/initialized`: ```bash 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`](https://github.com/modelcontextprotocol/typescript-sdk/blob/e12cbd7078db388152f6e839abdbe09ba01f3f32/src/server/mcp.ts#L431-L457) 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](https://github.com/modelcontextprotocol/python-sdk/blob/62137874ff26dd74d2fea80ff528a7fd9ca7a5e7/src/mcp/server/lowlevel/server.py#L610-L638), 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()`](https://github.com/modelcontextprotocol/java-sdk/blob/304650c5a0df3a4be0536bf9eb5bfa3728632823/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java#L919-L1020) [`completionCompleteRequestHandler()`](https://github.com/modelcontextprotocol/java-sdk/blob/304650c5a0df3a4be0536bf9eb5bfa3728632823/mcp-core/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java#L704-L803) methods: ```diff - 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** - #784 same pattern - #932 / PR #934 the recent fix for problem like this