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
1 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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue