Skip to content

Commit 41dbe86

Browse files
authored
Merge pull request #371 from koic/use_json_rpc_envelope_for_transport_errors
Use JSON-RPC error envelope for `StreamableHTTPTransport` errors
2 parents a5acf6b + c917028 commit 41dbe86

2 files changed

Lines changed: 103 additions & 58 deletions

File tree

lib/mcp/server/transports/streamable_http_transport.rb

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,11 @@ def handle_post(request)
389389
end
390390
rescue StandardError => e
391391
MCP.configuration.exception_reporter.call(e, { request: body_string })
392-
[500, { "Content-Type" => "application/json" }, [{ error: "Internal server error" }.to_json]]
392+
json_rpc_error_response(
393+
status: 500,
394+
code: JsonRpcHandler::ErrorCode::INTERNAL_ERROR,
395+
message: "Internal server error",
396+
)
393397
end
394398

395399
def handle_get(request)
@@ -513,19 +517,19 @@ def validate_content_type(request)
513517
media_type = content_type&.split(";")&.first&.strip&.downcase
514518
return if media_type == "application/json"
515519

516-
[
517-
415,
518-
{ "Content-Type" => "application/json" },
519-
[{ error: "Unsupported Media Type: Content-Type must be application/json" }.to_json],
520-
]
520+
json_rpc_error_response(
521+
status: 415,
522+
code: JsonRpcHandler::ErrorCode::INVALID_REQUEST,
523+
message: "Unsupported Media Type: Content-Type must be application/json",
524+
)
521525
end
522526

523527
def not_acceptable_response(required_types)
524-
[
525-
406,
526-
{ "Content-Type" => "application/json" },
527-
[{ error: "Not Acceptable: Accept header must include #{required_types.join(" and ")}" }.to_json],
528-
]
528+
json_rpc_error_response(
529+
status: 406,
530+
code: JsonRpcHandler::ErrorCode::INVALID_REQUEST,
531+
message: "Not Acceptable: Accept header must include #{required_types.join(" and ")}",
532+
)
529533
end
530534

531535
def parse_request_body(body_string)
@@ -535,7 +539,11 @@ def parse_request_body(body_string)
535539
end
536540

537541
def invalid_json_response
538-
[400, { "Content-Type" => "application/json" }, [{ error: "Invalid JSON" }.to_json]]
542+
json_rpc_error_response(
543+
status: 400,
544+
code: JsonRpcHandler::ErrorCode::PARSE_ERROR,
545+
message: "Parse error: Invalid JSON",
546+
)
539547
end
540548

541549
def initialize_request?(body)
@@ -548,15 +556,16 @@ def validate_protocol_version_header(request)
548556
return if MCP::Configuration::SUPPORTED_STABLE_PROTOCOL_VERSIONS.include?(header_value)
549557

550558
supported = MCP::Configuration::SUPPORTED_STABLE_PROTOCOL_VERSIONS.join(", ")
551-
body = {
552-
jsonrpc: "2.0",
553-
id: nil,
554-
error: {
555-
code: JsonRpcHandler::ErrorCode::INVALID_REQUEST,
556-
message: "Bad Request: Unsupported protocol version: #{header_value}. Supported versions: #{supported}",
557-
},
558-
}
559-
[400, { "Content-Type" => "application/json" }, [body.to_json]]
559+
json_rpc_error_response(
560+
status: 400,
561+
code: JsonRpcHandler::ErrorCode::INVALID_REQUEST,
562+
message: "Bad Request: Unsupported protocol version: #{header_value}. Supported versions: #{supported}",
563+
)
564+
end
565+
566+
def json_rpc_error_response(status:, code:, message:)
567+
body = { jsonrpc: "2.0", id: nil, error: { code: code, message: message } }
568+
[status, { "Content-Type" => "application/json" }, [body.to_json]]
560569
end
561570

562571
def notification?(body)
@@ -793,15 +802,27 @@ def session_active?(session_id)
793802
end
794803

795804
def method_not_allowed_response
796-
[405, { "Content-Type" => "application/json" }, [{ error: "Method not allowed" }.to_json]]
805+
json_rpc_error_response(
806+
status: 405,
807+
code: JsonRpcHandler::ErrorCode::INVALID_REQUEST,
808+
message: "Method not allowed",
809+
)
797810
end
798811

799812
def missing_session_id_response
800-
[400, { "Content-Type" => "application/json" }, [{ error: "Missing session ID" }.to_json]]
813+
json_rpc_error_response(
814+
status: 400,
815+
code: JsonRpcHandler::ErrorCode::INVALID_REQUEST,
816+
message: "Missing session ID",
817+
)
801818
end
802819

803820
def session_not_found_response
804-
[404, { "Content-Type" => "application/json" }, [{ error: "Session not found" }.to_json]]
821+
json_rpc_error_response(
822+
status: 404,
823+
code: JsonRpcHandler::ErrorCode::INVALID_REQUEST,
824+
message: "Session not found",
825+
)
805826
end
806827

807828
def already_initialized_response(request_id)
@@ -821,11 +842,11 @@ def invalid_request_response(message, request_id: nil)
821842
end
822843

823844
def session_already_connected_response
824-
[
825-
409,
826-
{ "Content-Type" => "application/json" },
827-
[{ error: "Conflict: Only one SSE stream is allowed per session" }.to_json],
828-
]
845+
json_rpc_error_response(
846+
status: 409,
847+
code: JsonRpcHandler::ErrorCode::INVALID_REQUEST,
848+
message: "Conflict: Only one SSE stream is allowed per session",
849+
)
829850
end
830851

831852
def setup_sse_stream(session_id)

0 commit comments

Comments
 (0)