Validate Required Params for initialize and resources/subscribe
## Motivation and Context Fixes https://github.com/modelcontextprotocol/ruby-sdk/issues/450 The MCP schema requires `protocolVersion`, `capabilities`, and `clientInfo` on `initialize` params, and `uri` on `resources/subscribe` and `resources/unsubscribe` params. The server accepted requests with these fields absent: `initialize` silently negotiated the server's default protocol version, and `resources/subscribe` returned an empty success result. Both now return `-32602` Invalid params, matching the Python SDK, which rejects such requests during pydantic validation. The `uri` check runs before dispatch so it also covers handlers registered via `resources_subscribe_handler` and `resources_unsubscribe_handler`. A `protocolVersion` value that is present but unsupported still negotiates the server's fallback version, as the spec's lifecycle section requires. ## How Has This Been Tested? Reproduced the issue's stdio repro steps: both malformed requests now return `-32602` instead of succeeding. ## Breaking Changes Clients that send `initialize` without `protocolVersion`, `capabilities`, or `clientInfo`, or `resources/subscribe` / `resources/unsubscribe` without `uri`, now receive `-32602` Invalid params instead of a success response. Such requests violate the MCP schema, but any code relying on the previous lenient behavior (for example, test harnesses sending bare `initialize` requests) must now include the required fields.
This commit is contained in:
parent
d71ec506c4
commit
abd1070cfa
8 changed files with 369 additions and 219 deletions
|
|
@ -531,6 +531,7 @@ module MCP
|
|||
when Methods::RESOURCES_READ
|
||||
build_read_resource_result(read_resource_contents(params, session: session, related_request_id: related_request_id, cancellation: cancellation))
|
||||
when Methods::RESOURCES_SUBSCRIBE, Methods::RESOURCES_UNSUBSCRIBE
|
||||
validate_resource_subscription_params!(params)
|
||||
dispatch_optional_context_handler(@handlers[method], params, session: session, related_request_id: related_request_id, cancellation: cancellation)
|
||||
{}
|
||||
when Methods::TOOLS_CALL
|
||||
|
|
@ -605,6 +606,8 @@ module MCP
|
|||
end
|
||||
|
||||
def init(params, session: nil)
|
||||
validate_initialize_params!(params)
|
||||
|
||||
# MCP spec: the initialization phase MUST be the first interaction between client and server.
|
||||
# Reject duplicate `initialize` on an already-initialized session so the negotiated
|
||||
# client identity and capabilities cannot be silently overwritten.
|
||||
|
|
@ -612,15 +615,13 @@ module MCP
|
|||
raise RequestHandlerError.new("Invalid Request: Server already initialized", params, error_type: :invalid_request)
|
||||
end
|
||||
|
||||
if params
|
||||
if session
|
||||
session.store_client_info(client: params[:clientInfo], capabilities: params[:capabilities])
|
||||
else
|
||||
@client = params[:clientInfo]
|
||||
@client_capabilities = params[:capabilities]
|
||||
end
|
||||
protocol_version = params[:protocolVersion]
|
||||
if session
|
||||
session.store_client_info(client: params[:clientInfo], capabilities: params[:capabilities])
|
||||
else
|
||||
@client = params[:clientInfo]
|
||||
@client_capabilities = params[:capabilities]
|
||||
end
|
||||
protocol_version = params[:protocolVersion]
|
||||
|
||||
negotiated_version = if Configuration::SUPPORTED_STABLE_PROTOCOL_VERSIONS.include?(protocol_version)
|
||||
protocol_version
|
||||
|
|
@ -649,6 +650,31 @@ module MCP
|
|||
}.compact
|
||||
end
|
||||
|
||||
def validate_resource_subscription_params!(params)
|
||||
unless params.is_a?(Hash) && params[:uri].is_a?(String)
|
||||
raise RequestHandlerError.new("Missing or invalid uri", params, error_type: :invalid_params)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_initialize_params!(params)
|
||||
unless params.is_a?(Hash)
|
||||
raise RequestHandlerError.new("Invalid params", params, error_type: :invalid_params)
|
||||
end
|
||||
|
||||
unless params[:protocolVersion].is_a?(String)
|
||||
raise RequestHandlerError.new("Missing or invalid protocolVersion", params, error_type: :invalid_params)
|
||||
end
|
||||
|
||||
unless params[:capabilities].is_a?(Hash)
|
||||
raise RequestHandlerError.new("Missing or invalid capabilities", params, error_type: :invalid_params)
|
||||
end
|
||||
|
||||
client_info = params[:clientInfo]
|
||||
if !client_info.is_a?(Hash) || client_info[:name].nil? || client_info[:version].nil?
|
||||
raise RequestHandlerError.new("Missing or invalid clientInfo", params, error_type: :invalid_params)
|
||||
end
|
||||
end
|
||||
|
||||
# Handles `server/discover` (MCP 2026-07-28 draft, SEP-2575): sessionless capability discovery.
|
||||
# Unlike `init`, this is state-free and idempotent: it stores no client info, does not mark
|
||||
# the session initialized, and responds regardless of capability declarations or initialization state,
|
||||
|
|
|
|||
13
test/initialize_params_test_helper.rb
Normal file
13
test/initialize_params_test_helper.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module InitializeParamsTestHelper
|
||||
# `initialize` params satisfying the fields required by the MCP schema
|
||||
# (protocolVersion, capabilities, and clientInfo).
|
||||
def initialize_params(**overrides)
|
||||
{
|
||||
protocolVersion: MCP::Configuration::LATEST_STABLE_PROTOCOL_VERSION,
|
||||
capabilities: {},
|
||||
clientInfo: { name: "test-client", version: "1.0.0" },
|
||||
}.merge(overrides)
|
||||
end
|
||||
end
|
||||
|
|
@ -108,6 +108,7 @@ module MCP
|
|||
id: "1",
|
||||
params: {
|
||||
protocolVersion: "2025-11-25",
|
||||
capabilities: {},
|
||||
clientInfo: { name: "stdio-client", version: "1.0" },
|
||||
},
|
||||
}
|
||||
|
|
@ -140,6 +141,7 @@ module MCP
|
|||
id: "first",
|
||||
params: {
|
||||
protocolVersion: "2025-11-25",
|
||||
capabilities: {},
|
||||
clientInfo: { name: "original", version: "1.0" },
|
||||
},
|
||||
}
|
||||
|
|
@ -149,6 +151,7 @@ module MCP
|
|||
id: "second",
|
||||
params: {
|
||||
protocolVersion: "2024-11-05",
|
||||
capabilities: {},
|
||||
clientInfo: { name: "intruder", version: "9.9" },
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ module MCP
|
|||
class Server
|
||||
module Transports
|
||||
class StreamableHTTPNotificationIntegrationTest < ActiveSupport::TestCase
|
||||
include InitializeParamsTestHelper
|
||||
|
||||
setup do
|
||||
@server = Server.new(
|
||||
name: "test_server",
|
||||
|
|
@ -23,7 +25,7 @@ module MCP
|
|||
"POST",
|
||||
"/",
|
||||
{ "CONTENT_TYPE" => "application/json" },
|
||||
{ jsonrpc: "2.0", method: "initialize", id: "init" }.to_json,
|
||||
{ jsonrpc: "2.0", method: "initialize", id: "init", params: initialize_params }.to_json,
|
||||
)
|
||||
init_response = @transport.handle_request(init_request)
|
||||
session_id = init_response[1]["Mcp-Session-Id"]
|
||||
|
|
@ -72,7 +74,7 @@ module MCP
|
|||
"POST",
|
||||
"/",
|
||||
{ "CONTENT_TYPE" => "application/json" },
|
||||
{ jsonrpc: "2.0", method: "initialize", id: "123" }.to_json,
|
||||
{ jsonrpc: "2.0", method: "initialize", id: "123", params: initialize_params }.to_json,
|
||||
)
|
||||
init_response1 = @transport.handle_request(init_request1)
|
||||
session_id1 = init_response1[1]["Mcp-Session-Id"]
|
||||
|
|
@ -81,7 +83,7 @@ module MCP
|
|||
"POST",
|
||||
"/",
|
||||
{ "CONTENT_TYPE" => "application/json" },
|
||||
{ jsonrpc: "2.0", method: "initialize", id: "456" }.to_json,
|
||||
{ jsonrpc: "2.0", method: "initialize", id: "456", params: initialize_params }.to_json,
|
||||
)
|
||||
init_response2 = @transport.handle_request(init_request2)
|
||||
session_id2 = init_response2[1]["Mcp-Session-Id"]
|
||||
|
|
@ -127,7 +129,7 @@ module MCP
|
|||
"POST",
|
||||
"/",
|
||||
{ "CONTENT_TYPE" => "application/json" },
|
||||
{ jsonrpc: "2.0", method: "initialize", id: "init" }.to_json,
|
||||
{ jsonrpc: "2.0", method: "initialize", id: "init", params: initialize_params }.to_json,
|
||||
)
|
||||
init_response = @transport.handle_request(init_request)
|
||||
session_id = init_response[1]["Mcp-Session-Id"]
|
||||
|
|
@ -163,7 +165,7 @@ module MCP
|
|||
"POST",
|
||||
"/",
|
||||
{ "CONTENT_TYPE" => "application/json" },
|
||||
{ jsonrpc: "2.0", method: "initialize", id: "init" }.to_json,
|
||||
{ jsonrpc: "2.0", method: "initialize", id: "init", params: initialize_params }.to_json,
|
||||
)
|
||||
init_response = @transport.handle_request(init_request)
|
||||
session_id = init_response[1]["Mcp-Session-Id"]
|
||||
|
|
@ -207,7 +209,7 @@ module MCP
|
|||
"POST",
|
||||
"/",
|
||||
{ "CONTENT_TYPE" => "application/json" },
|
||||
{ jsonrpc: "2.0", method: "initialize", id: "init" }.to_json,
|
||||
{ jsonrpc: "2.0", method: "initialize", id: "init", params: initialize_params }.to_json,
|
||||
)
|
||||
init_response = @transport.handle_request(init_request)
|
||||
session_id = init_response[1]["Mcp-Session-Id"]
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -87,9 +87,9 @@ module MCP
|
|||
assert_equal({ roots: { listChanged: true } }, server.client_capabilities)
|
||||
end
|
||||
|
||||
test "init stores nil when client sends no capabilities" do
|
||||
test "init rejects params without capabilities and stores nothing" do
|
||||
server = Server.new(name: "test", version: "1.0")
|
||||
server.handle({
|
||||
response = server.handle({
|
||||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
|
|
@ -99,6 +99,7 @@ module MCP
|
|||
},
|
||||
})
|
||||
|
||||
assert_equal(-32602, response[:error][:code])
|
||||
assert_nil server.client_capabilities
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ require "test_helper"
|
|||
module MCP
|
||||
class ServerTest < ActiveSupport::TestCase
|
||||
include InstrumentationTestHelper
|
||||
include InitializeParamsTestHelper
|
||||
setup do
|
||||
@tool = Tool.define(
|
||||
name: "test_tool",
|
||||
|
|
@ -151,7 +152,7 @@ module MCP
|
|||
refute_predicate session, :initialized?
|
||||
# `initialize` must still succeed afterwards.
|
||||
response = @server.handle(
|
||||
{ jsonrpc: "2.0", method: "initialize", id: 2, params: { clientInfo: { name: "c", version: "1" } } },
|
||||
{ jsonrpc: "2.0", method: "initialize", id: 2, params: initialize_params },
|
||||
session: session,
|
||||
)
|
||||
refute_nil response[:result]
|
||||
|
|
@ -176,6 +177,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: initialize_params,
|
||||
}
|
||||
|
||||
response = @server.handle(request)
|
||||
|
|
@ -204,7 +206,7 @@ module MCP
|
|||
}
|
||||
|
||||
assert_equal expected_result, response
|
||||
assert_instrumentation_data({ method: "initialize" })
|
||||
assert_instrumentation_data({ method: "initialize", client: { name: "test-client", version: "1.0.0" } })
|
||||
end
|
||||
|
||||
test "#handle initialize result carries declared capability extensions" do
|
||||
|
|
@ -216,7 +218,7 @@ module MCP
|
|||
},
|
||||
)
|
||||
|
||||
response = server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
response = server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
|
||||
assert_equal(
|
||||
{ "com.example/feature" => { enabled: true } },
|
||||
|
|
@ -230,7 +232,7 @@ module MCP
|
|||
capabilities.support_extensions("io.modelcontextprotocol/tasks" => {})
|
||||
|
||||
server = Server.new(name: "extensions_test", capabilities: capabilities)
|
||||
response = server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
response = server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
|
||||
assert_equal(
|
||||
{
|
||||
|
|
@ -247,10 +249,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: {
|
||||
clientInfo: { name: "test_client", version: "1.0.0" },
|
||||
capabilities: { extensions: extensions },
|
||||
},
|
||||
params: initialize_params(capabilities: { extensions: extensions }),
|
||||
}
|
||||
|
||||
@server.handle(request)
|
||||
|
|
@ -265,10 +264,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: {
|
||||
clientInfo: { name: "test_client", version: "1.0.0" },
|
||||
capabilities: { extensions: extensions },
|
||||
},
|
||||
params: initialize_params(capabilities: { extensions: extensions }),
|
||||
}
|
||||
|
||||
@server.handle(request, session: session)
|
||||
|
|
@ -282,9 +278,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: {
|
||||
clientInfo: client_info,
|
||||
},
|
||||
params: initialize_params(clientInfo: client_info),
|
||||
}
|
||||
|
||||
@server.handle(request)
|
||||
|
|
@ -297,9 +291,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: {
|
||||
clientInfo: client_info,
|
||||
},
|
||||
params: initialize_params(clientInfo: client_info),
|
||||
}
|
||||
@server.handle(initialize_request)
|
||||
|
||||
|
|
@ -319,7 +311,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: { clientInfo: { name: "original", version: "1.0" } },
|
||||
params: initialize_params(clientInfo: { name: "original", version: "1.0" }),
|
||||
}
|
||||
first_response = @server.handle(first_request, session: session)
|
||||
refute_nil first_response[:result]
|
||||
|
|
@ -328,7 +320,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 2,
|
||||
params: { clientInfo: { name: "intruder", version: "9.9" }, protocolVersion: "2024-11-05" },
|
||||
params: initialize_params(clientInfo: { name: "intruder", version: "9.9" }, protocolVersion: "2024-11-05"),
|
||||
}
|
||||
second_response = @server.handle(second_request, session: session)
|
||||
|
||||
|
|
@ -337,6 +329,59 @@ module MCP
|
|||
assert_equal({ name: "original", version: "1.0" }, session.client)
|
||||
end
|
||||
|
||||
test "#handle initialize with empty params returns -32602 and does not initialize the session" do
|
||||
session = ServerSession.new(server: @server, transport: mock)
|
||||
|
||||
response = @server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: {} }, session: session)
|
||||
|
||||
assert_equal(-32602, response[:error][:code])
|
||||
assert_equal "Invalid params", response[:error][:message]
|
||||
refute_predicate session, :initialized?
|
||||
end
|
||||
|
||||
test "#handle initialize without params returns -32602" do
|
||||
response = @server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
|
||||
assert_equal(-32602, response[:error][:code])
|
||||
assert_equal "Invalid params", response[:error][:message]
|
||||
end
|
||||
|
||||
test "#handle initialize with a missing required field returns -32602" do
|
||||
[:protocolVersion, :capabilities, :clientInfo].each do |field|
|
||||
params = initialize_params.tap { |hash| hash.delete(field) }
|
||||
|
||||
response = @server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: params })
|
||||
|
||||
assert_equal(-32602, response[:error][:code], "expected -32602 when #{field} is missing")
|
||||
assert_includes response[:error][:data], field.to_s
|
||||
end
|
||||
end
|
||||
|
||||
test "#handle initialize with clientInfo missing name or version returns -32602" do
|
||||
[{ name: "client-without-version" }, { version: "1.0.0" }].each do |client_info|
|
||||
response = @server.handle({
|
||||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: initialize_params(clientInfo: client_info),
|
||||
})
|
||||
|
||||
assert_equal(-32602, response[:error][:code])
|
||||
assert_includes response[:error][:data], "clientInfo"
|
||||
end
|
||||
end
|
||||
|
||||
test "#handle initialize with an unsupported protocolVersion still negotiates the fallback version" do
|
||||
response = @server.handle({
|
||||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: initialize_params(protocolVersion: "1999-01-01"),
|
||||
})
|
||||
|
||||
assert_equal Configuration::LATEST_STABLE_PROTOCOL_VERSION, response[:result][:protocolVersion]
|
||||
end
|
||||
|
||||
test "instrumentation data does not include client key when no clientInfo provided" do
|
||||
request = {
|
||||
jsonrpc: "2.0",
|
||||
|
|
@ -1540,6 +1585,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: initialize_params,
|
||||
}
|
||||
|
||||
response = @server.handle(request)
|
||||
|
|
@ -1552,6 +1598,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: initialize_params,
|
||||
}
|
||||
|
||||
response = server.handle(request)
|
||||
|
|
@ -1567,6 +1614,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: initialize_params,
|
||||
}
|
||||
|
||||
response = server.handle(request)
|
||||
|
|
@ -1580,6 +1628,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: initialize_params,
|
||||
}
|
||||
response = server.handle(request)
|
||||
|
||||
|
|
@ -1592,6 +1641,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: initialize_params,
|
||||
}
|
||||
response = server.handle(request)
|
||||
|
||||
|
|
@ -1607,6 +1657,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: initialize_params,
|
||||
}
|
||||
response = server.handle(request)
|
||||
expected_icons = [{ mimeType: "image/png", sizes: ["48x48"], src: "https://example.com", theme: "light" }]
|
||||
|
|
@ -1620,6 +1671,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: initialize_params,
|
||||
}
|
||||
|
||||
response = server.handle(request)
|
||||
|
|
@ -1632,6 +1684,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: initialize_params,
|
||||
}
|
||||
|
||||
response = server.handle(request)
|
||||
|
|
@ -1905,6 +1958,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: initialize_params(protocolVersion: "1999-01-01"),
|
||||
}
|
||||
|
||||
response = server.handle(request)
|
||||
|
|
@ -1918,9 +1972,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: {
|
||||
protocolVersion: "2025-06-18",
|
||||
},
|
||||
params: initialize_params(protocolVersion: "2025-06-18"),
|
||||
}
|
||||
|
||||
response = server.handle(request)
|
||||
|
|
@ -1934,9 +1986,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: {
|
||||
protocolVersion: "1999-01-01",
|
||||
},
|
||||
params: initialize_params(protocolVersion: "1999-01-01"),
|
||||
}
|
||||
|
||||
response = server.handle(request)
|
||||
|
|
@ -1954,9 +2004,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: {
|
||||
protocolVersion: "2025-06-18",
|
||||
},
|
||||
params: initialize_params(protocolVersion: "2025-06-18"),
|
||||
}
|
||||
|
||||
response = server.handle(request)
|
||||
|
|
@ -1972,9 +2020,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: {
|
||||
protocolVersion: "2025-03-26",
|
||||
},
|
||||
params: initialize_params(protocolVersion: "2025-03-26"),
|
||||
}
|
||||
|
||||
response = server.handle(request)
|
||||
|
|
@ -1990,9 +2036,7 @@ module MCP
|
|||
jsonrpc: "2.0",
|
||||
method: "initialize",
|
||||
id: 1,
|
||||
params: {
|
||||
protocolVersion: "2024-11-05",
|
||||
},
|
||||
params: initialize_params(protocolVersion: "2024-11-05"),
|
||||
}
|
||||
|
||||
response = server.handle(request)
|
||||
|
|
@ -2381,7 +2425,7 @@ module MCP
|
|||
capabilities: { completions: {} },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2419,7 +2463,7 @@ module MCP
|
|||
{ completion: { values: ["python", "pytorch", "pyside"], total: 10, hasMore: true } }
|
||||
end
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2457,7 +2501,7 @@ module MCP
|
|||
{ completion: { values: ["file:///src", "file:///spec"], hasMore: false } }
|
||||
end
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2500,7 +2544,7 @@ module MCP
|
|||
{ completion: { values: ["flask"], hasMore: false } }
|
||||
end
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
server.handle({
|
||||
|
|
@ -2529,7 +2573,7 @@ module MCP
|
|||
{ completion: { values: (1..150).map(&:to_s), hasMore: false } }
|
||||
end
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2556,7 +2600,7 @@ module MCP
|
|||
capabilities: { completions: {} },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2578,7 +2622,7 @@ module MCP
|
|||
capabilities: { completions: {} },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2600,7 +2644,7 @@ module MCP
|
|||
capabilities: { completions: {} },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2624,7 +2668,7 @@ module MCP
|
|||
capabilities: { completions: {} },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2646,7 +2690,7 @@ module MCP
|
|||
capabilities: { completions: {} },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2677,7 +2721,7 @@ module MCP
|
|||
{ completion: { values: ["file:///README.md"], hasMore: false } }
|
||||
end
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2708,7 +2752,7 @@ module MCP
|
|||
capabilities: { completions: {} },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2731,7 +2775,7 @@ module MCP
|
|||
capabilities: { completions: {} },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2759,7 +2803,7 @@ module MCP
|
|||
nil
|
||||
end
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2794,7 +2838,7 @@ module MCP
|
|||
{ "completion" => { "values" => ["alpha", "beta"], "hasMore" => true } }
|
||||
end
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2818,7 +2862,7 @@ module MCP
|
|||
capabilities: { completions: {} },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2837,7 +2881,7 @@ module MCP
|
|||
prompts: [],
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2860,7 +2904,7 @@ module MCP
|
|||
capabilities: { resources: { subscribe: true } },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2886,7 +2930,7 @@ module MCP
|
|||
capabilities: { resources: { subscribe: true } },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2918,7 +2962,7 @@ module MCP
|
|||
{}
|
||||
end
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2951,7 +2995,7 @@ module MCP
|
|||
{}
|
||||
end
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1 })
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
|
|
@ -2972,6 +3016,73 @@ module MCP
|
|||
assert_equal "https://example.com/resource", received_params[:uri]
|
||||
end
|
||||
|
||||
test "#handle resources/subscribe without uri returns -32602" do
|
||||
server = Server.new(
|
||||
name: "test_server",
|
||||
capabilities: { resources: { subscribe: true } },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
jsonrpc: "2.0",
|
||||
id: 2,
|
||||
method: "resources/subscribe",
|
||||
params: {},
|
||||
})
|
||||
|
||||
assert_equal(-32602, response[:error][:code])
|
||||
assert_equal "Invalid params", response[:error][:message]
|
||||
assert_includes response[:error][:data], "uri"
|
||||
end
|
||||
|
||||
test "#handle resources/unsubscribe without uri returns -32602" do
|
||||
server = Server.new(
|
||||
name: "test_server",
|
||||
capabilities: { resources: { subscribe: true } },
|
||||
)
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
jsonrpc: "2.0",
|
||||
id: 2,
|
||||
method: "resources/unsubscribe",
|
||||
params: {},
|
||||
})
|
||||
|
||||
assert_equal(-32602, response[:error][:code])
|
||||
assert_equal "Invalid params", response[:error][:message]
|
||||
end
|
||||
|
||||
test "#handle resources/subscribe without uri does not invoke a custom handler" do
|
||||
server = Server.new(
|
||||
name: "test_server",
|
||||
capabilities: { resources: { subscribe: true } },
|
||||
)
|
||||
|
||||
handler_called = false
|
||||
server.resources_subscribe_handler do |_params|
|
||||
handler_called = true
|
||||
{}
|
||||
end
|
||||
|
||||
server.handle({ jsonrpc: "2.0", method: "initialize", id: 1, params: initialize_params })
|
||||
server.handle({ jsonrpc: "2.0", method: "notifications/initialized" })
|
||||
|
||||
response = server.handle({
|
||||
jsonrpc: "2.0",
|
||||
id: 2,
|
||||
method: "resources/subscribe",
|
||||
params: {},
|
||||
})
|
||||
|
||||
assert_equal(-32602, response[:error][:code])
|
||||
refute handler_called
|
||||
end
|
||||
|
||||
test "tools/call with no args" do
|
||||
server = Server.new(tools: [@tool_with_no_args])
|
||||
|
||||
|
|
|
|||
|
|
@ -22,3 +22,4 @@ require "active_support/test_case"
|
|||
require "sorbet-runtime" if RUBY_VERSION >= "3.0"
|
||||
|
||||
require_relative "instrumentation_test_helper"
|
||||
require_relative "initialize_params_test_helper"
|
||||
|
|
|
|||
Loading…
Reference in a new issue