Fix default handler return values to comply with MCP spec
The no-op handlers for `completion/complete`, `resources/subscribe`,
and `resources/unsubscribe` returned `nil`.
When a client accesses the expected response structure (e.g., `result[:completion][:values]`),
a `nil` result raises `NoMethodError`.
The MCP spec requires specific response structures even for no-op handlers.
Returning spec-compliant defaults ensures that clients receive valid responses without
requiring servers to explicitly override each handler.
`completion/complete` now returns `{ completion: { values: [], hasMore: false } }`.
ref: https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion
`resources/subscribe` and `resources/unsubscribe` now return `{}` (empty result).
ref: https://modelcontextprotocol.io/specification/2025-11-25/server/resources#subscriptions
This commit is contained in:
parent
5b04089e06
commit
7b910bca23
2 changed files with 84 additions and 3 deletions
|
|
@ -99,9 +99,9 @@ module MCP
|
|||
Methods::LOGGING_SET_LEVEL => method(:configure_logging_level),
|
||||
|
||||
# No op handlers for currently unsupported methods
|
||||
Methods::RESOURCES_SUBSCRIBE => ->(_) {},
|
||||
Methods::RESOURCES_UNSUBSCRIBE => ->(_) {},
|
||||
Methods::COMPLETION_COMPLETE => ->(_) {},
|
||||
Methods::RESOURCES_SUBSCRIBE => ->(_) { {} },
|
||||
Methods::RESOURCES_UNSUBSCRIBE => ->(_) { {} },
|
||||
Methods::COMPLETION_COMPLETE => ->(_) { { completion: { values: [], hasMore: false } } },
|
||||
Methods::ELICITATION_CREATE => ->(_) {},
|
||||
}
|
||||
@transport = transport
|
||||
|
|
|
|||
|
|
@ -1540,6 +1540,87 @@ module MCP
|
|||
assert_includes response[:result][:content][0][:text], "Invalid arguments"
|
||||
end
|
||||
|
||||
test "#handle completion/complete returns default completion result" do
|
||||
server = Server.new(
|
||||
name: "test_server",
|
||||
capabilities: { completions: {} },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
jsonrpc: "2.0",
|
||||
id: 2,
|
||||
method: "completion/complete",
|
||||
params: {
|
||||
ref: { type: "ref/prompt", name: "test" },
|
||||
argument: { name: "arg", value: "val" },
|
||||
},
|
||||
})
|
||||
|
||||
assert_equal(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: 2,
|
||||
result: { completion: { values: [], hasMore: false } },
|
||||
},
|
||||
response,
|
||||
)
|
||||
end
|
||||
|
||||
test "#handle resources/subscribe returns empty result" do
|
||||
server = Server.new(
|
||||
name: "test_server",
|
||||
capabilities: { resources: { subscribe: true } },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
jsonrpc: "2.0",
|
||||
id: 2,
|
||||
method: "resources/subscribe",
|
||||
params: { uri: "https://example.com/resource" },
|
||||
})
|
||||
|
||||
assert_equal(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: 2,
|
||||
result: {},
|
||||
},
|
||||
response,
|
||||
)
|
||||
end
|
||||
|
||||
test "#handle resources/unsubscribe returns empty result" do
|
||||
server = Server.new(
|
||||
name: "test_server",
|
||||
capabilities: { resources: { subscribe: true } },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
jsonrpc: "2.0",
|
||||
id: 2,
|
||||
method: "resources/unsubscribe",
|
||||
params: { uri: "https://example.com/resource" },
|
||||
})
|
||||
|
||||
assert_equal(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: 2,
|
||||
result: {},
|
||||
},
|
||||
response,
|
||||
)
|
||||
end
|
||||
|
||||
test "tools/call with no args" do
|
||||
server = Server.new(tools: [@tool_with_no_args])
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue