Merge pull request #402 from koic/resource_not_found_error_data
Standardize Resource Not Found Errors on -32602 with URI Data per SEP-2164
This commit is contained in:
commit
783eb6303f
4 changed files with 104 additions and 1 deletions
13
README.md
13
README.md
|
|
@ -927,6 +927,19 @@ end
|
|||
|
||||
otherwise `resources/read` requests will be a no-op.
|
||||
|
||||
For unknown URIs, raise `MCP::Server::ResourceNotFoundError` from the handler.
|
||||
Per SEP-2164, the server then responds with the standard JSON-RPC Invalid Params error (`-32602`)
|
||||
carrying the requested URI in the error `data` member:
|
||||
|
||||
```ruby
|
||||
server.resources_read_handler do |params|
|
||||
resource = lookup(params[:uri])
|
||||
raise MCP::Server::ResourceNotFoundError.new(params[:uri], params) unless resource
|
||||
|
||||
[{ uri: params[:uri], mimeType: resource.mime_type, text: resource.body }]
|
||||
end
|
||||
```
|
||||
|
||||
### Resource Templates
|
||||
|
||||
The `MCP::ResourceTemplate` class provides a way to register resource templates with the server.
|
||||
|
|
|
|||
|
|
@ -58,6 +58,32 @@ module MCP
|
|||
end
|
||||
end
|
||||
|
||||
# Raised when a requested resource URI does not exist. Per SEP-2164,
|
||||
# resource-not-found errors use the standard JSON-RPC Invalid Params code (-32602)
|
||||
# with the requested URI in the error `data` member. Raise this from
|
||||
# a `resources_read_handler` block for unknown URIs:
|
||||
#
|
||||
# server.resources_read_handler do |params|
|
||||
# raise MCP::Server::ResourceNotFoundError.new(params[:uri], params) unless known?(params[:uri])
|
||||
# do_something(params[:uri])
|
||||
# end
|
||||
#
|
||||
# https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2164
|
||||
class ResourceNotFoundError < RequestHandlerError
|
||||
def initialize(uri, request = nil)
|
||||
# The explicit `error_code` keeps the descriptive message in the JSON-RPC
|
||||
# error response; `error_type: :invalid_params` alone would replace it
|
||||
# with the generic "Invalid params" string.
|
||||
super(
|
||||
"Resource not found: #{uri}",
|
||||
request,
|
||||
error_type: :invalid_params,
|
||||
error_code: JsonRpcHandler::ErrorCode::INVALID_PARAMS,
|
||||
error_data: { uri: uri },
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
class MethodAlreadyDefinedError < StandardError
|
||||
attr_reader :method_name
|
||||
|
||||
|
|
@ -846,7 +872,7 @@ module MCP
|
|||
uri = ref[:uri]
|
||||
found = @resource_index.key?(uri) || @resource_templates.any? { |t| t.uri_template == uri }
|
||||
unless found
|
||||
raise RequestHandlerError.new("Resource not found: #{uri}", params, error_type: :invalid_params)
|
||||
raise ResourceNotFoundError.new(uri, params)
|
||||
end
|
||||
else
|
||||
raise RequestHandlerError.new("Invalid ref type: #{ref[:type]}", params, error_type: :invalid_params)
|
||||
|
|
|
|||
|
|
@ -610,6 +610,27 @@ module MCP
|
|||
assert_raises(Client::ServerError) { client.read_resource(uri: "file:///missing") }
|
||||
end
|
||||
|
||||
def test_read_resource_surfaces_resource_not_found_code_and_data
|
||||
# Per SEP-2164, servers report unknown resource URIs with -32602 and
|
||||
# the URI in the error `data`; the client must expose both unmodified.
|
||||
transport = mock
|
||||
mock_response = {
|
||||
"error" => {
|
||||
"code" => -32_602,
|
||||
"message" => "Resource not found: file:///missing.txt",
|
||||
"data" => { "uri" => "file:///missing.txt" },
|
||||
},
|
||||
}
|
||||
|
||||
transport.expects(:send_request).returns(mock_response).once
|
||||
|
||||
client = Client.new(transport: transport)
|
||||
error = assert_raises(Client::ServerError) { client.read_resource(uri: "file:///missing.txt") }
|
||||
|
||||
assert_equal(-32_602, error.code)
|
||||
assert_equal({ "uri" => "file:///missing.txt" }, error.data)
|
||||
end
|
||||
|
||||
def test_get_prompt_raises_server_error_on_error_response
|
||||
transport = mock
|
||||
mock_response = { "error" => { "code" => -32_602, "message" => "Prompt not found" } }
|
||||
|
|
|
|||
|
|
@ -724,6 +724,25 @@ module MCP
|
|||
)
|
||||
end
|
||||
|
||||
test "#handle resources/read returns -32602 with the uri in error data when the handler raises ResourceNotFoundError" do
|
||||
# Per SEP-2164, resource-not-found errors use the standard JSON-RPC Invalid Params code (-32602)
|
||||
# and carry the requested URI in `data`.
|
||||
@server.resources_read_handler do |request|
|
||||
raise Server::ResourceNotFoundError.new(request[:uri], request)
|
||||
end
|
||||
|
||||
response = @server.handle({
|
||||
jsonrpc: "2.0",
|
||||
method: "resources/read",
|
||||
id: 1,
|
||||
params: { uri: "file:///missing.txt" },
|
||||
})
|
||||
|
||||
assert_equal(-32602, response[:error][:code])
|
||||
assert_equal("Resource not found: file:///missing.txt", response[:error][:message])
|
||||
assert_equal({ uri: "file:///missing.txt" }, response[:error][:data])
|
||||
end
|
||||
|
||||
test "#handle resources/templates/list returns a list of resource templates" do
|
||||
request = {
|
||||
jsonrpc: "2.0",
|
||||
|
|
@ -2206,6 +2225,30 @@ module MCP
|
|||
assert_equal(-32_602, response[:error][:code])
|
||||
end
|
||||
|
||||
test "#handle completion/complete resource-not-found error carries the uri in error data" 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/resource", uri: "unknown://template" },
|
||||
argument: { name: "arg", value: "val" },
|
||||
},
|
||||
})
|
||||
|
||||
assert_equal(-32602, response[:error][:code])
|
||||
assert_equal("Resource not found: unknown://template", response[:error][:message])
|
||||
assert_equal({ uri: "unknown://template" }, response[:error][:data])
|
||||
end
|
||||
|
||||
test "#handle completion/complete returns error for invalid ref type" do
|
||||
server = Server.new(
|
||||
name: "test_server",
|
||||
|
|
|
|||
Loading…
Reference in a new issue