mcp 0.25.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +177 -0
- data/lib/mcp/client/tool.rb +3 -2
- data/lib/mcp/client.rb +1 -0
- data/lib/mcp/configuration.rb +17 -2
- data/lib/mcp/error_codes.rb +14 -8
- data/lib/mcp/request_envelope.rb +95 -0
- data/lib/mcp/server.rb +51 -8
- data/lib/mcp/tool/response.rb +8 -2
- data/lib/mcp/version.rb +1 -1
- data/lib/mcp.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 356017978ae1c4a455d50d37bd9a178aba8989ee5e001bed8ab309977071f71f
|
|
4
|
+
data.tar.gz: cc62cd7e64421b3a45c49d60709610cf0bf552183b4a2f08c2adef9e0bde2e29
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5464fcaa9535b838cd7b3bccffa7c9d4f461cb4ca91ac08067d9f9fa97bde4124bd9214c57c6d7928ce4315135dcabcfff445d48b24f325f0acaace0b8af5fc9
|
|
7
|
+
data.tar.gz: 118dba080f17a6464892b4d65a57e147ffe405539457940a7f25184ead416af03accf4ac27809e5b4c0fb2d1a6942e58edcc8f73b71c88fc364bf658861353f0
|
data/README.md
CHANGED
|
@@ -872,6 +872,53 @@ class WeatherTool < MCP::Tool
|
|
|
872
872
|
end
|
|
873
873
|
```
|
|
874
874
|
|
|
875
|
+
### Tool Responses with Image, Audio, and Embedded Resources
|
|
876
|
+
|
|
877
|
+
Tool responses are not limited to text. The `MCP::Content` module provides `Image`, `Audio`, and `EmbeddedResource` content types,
|
|
878
|
+
which serialize to the `image`, `audio`, and `resource` content blocks defined by the MCP spec. Image and audio data is passed as
|
|
879
|
+
a base64-encoded string together with its MIME type:
|
|
880
|
+
|
|
881
|
+
```ruby
|
|
882
|
+
class ChartTool < MCP::Tool
|
|
883
|
+
description "Render a chart as a PNG image"
|
|
884
|
+
|
|
885
|
+
def self.call(server_context:)
|
|
886
|
+
MCP::Tool::Response.new([
|
|
887
|
+
MCP::Content::Text.new("Here is the rendered chart:").to_h,
|
|
888
|
+
MCP::Content::Image.new(Base64.strict_encode64(render_chart_png), "image/png").to_h,
|
|
889
|
+
])
|
|
890
|
+
end
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
class SpeechTool < MCP::Tool
|
|
894
|
+
description "Synthesize speech audio"
|
|
895
|
+
|
|
896
|
+
def self.call(server_context:)
|
|
897
|
+
MCP::Tool::Response.new([
|
|
898
|
+
MCP::Content::Audio.new(Base64.strict_encode64(synthesize_wav), "audio/wav").to_h,
|
|
899
|
+
])
|
|
900
|
+
end
|
|
901
|
+
end
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
An embedded resource wraps `MCP::Resource::TextContents` or `MCP::Resource::BlobContents`, allowing a tool to return resource contents inline:
|
|
905
|
+
|
|
906
|
+
```ruby
|
|
907
|
+
class ReportTool < MCP::Tool
|
|
908
|
+
description "Return a report as an embedded resource"
|
|
909
|
+
|
|
910
|
+
def self.call(server_context:)
|
|
911
|
+
contents = MCP::Resource::TextContents.new(
|
|
912
|
+
uri: "report://monthly",
|
|
913
|
+
mime_type: "application/json",
|
|
914
|
+
text: { total: 42 }.to_json,
|
|
915
|
+
)
|
|
916
|
+
|
|
917
|
+
MCP::Tool::Response.new([MCP::Content::EmbeddedResource.new(contents).to_h])
|
|
918
|
+
end
|
|
919
|
+
end
|
|
920
|
+
```
|
|
921
|
+
|
|
875
922
|
### Prompts
|
|
876
923
|
|
|
877
924
|
MCP spec includes [Prompts](https://modelcontextprotocol.io/specification/latest/server/prompts), which enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs.
|
|
@@ -1010,6 +1057,49 @@ The server will handle prompt listing and execution through the MCP protocol met
|
|
|
1010
1057
|
- `prompts/list` - Lists all registered prompts and their schemas
|
|
1011
1058
|
- `prompts/get` - Retrieves and executes a specific prompt with arguments
|
|
1012
1059
|
|
|
1060
|
+
### Prompts with Image and Embedded Resource Content
|
|
1061
|
+
|
|
1062
|
+
Prompt messages are not limited to text. The same `MCP::Content` types used in tool responses can be used as message content,
|
|
1063
|
+
letting a prompt template include images or inline resource contents. Unlike tool responses, the content object is passed directly rather than as a hash;
|
|
1064
|
+
`MCP::Prompt::Message` serializes it when the prompt result is returned:
|
|
1065
|
+
|
|
1066
|
+
```ruby
|
|
1067
|
+
class CodeReviewPrompt < MCP::Prompt
|
|
1068
|
+
prompt_name "code_review"
|
|
1069
|
+
description "Review a source file with an accompanying diagram"
|
|
1070
|
+
arguments [
|
|
1071
|
+
MCP::Prompt::Argument.new(name: "file_uri", description: "URI of the file to review", required: true),
|
|
1072
|
+
]
|
|
1073
|
+
|
|
1074
|
+
class << self
|
|
1075
|
+
def template(args, server_context:)
|
|
1076
|
+
MCP::Prompt::Result.new(
|
|
1077
|
+
messages: [
|
|
1078
|
+
MCP::Prompt::Message.new(
|
|
1079
|
+
role: "user",
|
|
1080
|
+
content: MCP::Content::EmbeddedResource.new(
|
|
1081
|
+
MCP::Resource::TextContents.new(
|
|
1082
|
+
uri: args["file_uri"],
|
|
1083
|
+
mime_type: "text/x-ruby",
|
|
1084
|
+
text: read_source(args["file_uri"]),
|
|
1085
|
+
),
|
|
1086
|
+
),
|
|
1087
|
+
),
|
|
1088
|
+
MCP::Prompt::Message.new(
|
|
1089
|
+
role: "user",
|
|
1090
|
+
content: MCP::Content::Image.new(architecture_diagram_base64, "image/png"),
|
|
1091
|
+
),
|
|
1092
|
+
MCP::Prompt::Message.new(
|
|
1093
|
+
role: "user",
|
|
1094
|
+
content: MCP::Content::Text.new("Please review the code above, using the diagram for context."),
|
|
1095
|
+
),
|
|
1096
|
+
],
|
|
1097
|
+
)
|
|
1098
|
+
end
|
|
1099
|
+
end
|
|
1100
|
+
end
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1013
1103
|
### Resources
|
|
1014
1104
|
|
|
1015
1105
|
MCP spec includes [Resources](https://modelcontextprotocol.io/specification/latest/server/resources).
|
|
@@ -1126,6 +1216,34 @@ server.resources_read_handler do |params|
|
|
|
1126
1216
|
end
|
|
1127
1217
|
```
|
|
1128
1218
|
|
|
1219
|
+
### Reading Binary Resources
|
|
1220
|
+
|
|
1221
|
+
For binary resources, respond with a base64-encoded `blob` field instead of `text`.
|
|
1222
|
+
The `MCP::Resource::TextContents` and `MCP::Resource::BlobContents` classes build the two contents shapes defined by the spec:
|
|
1223
|
+
|
|
1224
|
+
```ruby
|
|
1225
|
+
server.resources_read_handler do |params|
|
|
1226
|
+
case params[:uri]
|
|
1227
|
+
when "file:///logo.png"
|
|
1228
|
+
[
|
|
1229
|
+
MCP::Resource::BlobContents.new(
|
|
1230
|
+
uri: params[:uri],
|
|
1231
|
+
mime_type: "image/png",
|
|
1232
|
+
data: Base64.strict_encode64(File.binread("logo.png")),
|
|
1233
|
+
).to_h,
|
|
1234
|
+
]
|
|
1235
|
+
else
|
|
1236
|
+
[
|
|
1237
|
+
MCP::Resource::TextContents.new(
|
|
1238
|
+
uri: params[:uri],
|
|
1239
|
+
mime_type: "text/plain",
|
|
1240
|
+
text: "Hello from example resource!",
|
|
1241
|
+
).to_h,
|
|
1242
|
+
]
|
|
1243
|
+
end
|
|
1244
|
+
end
|
|
1245
|
+
```
|
|
1246
|
+
|
|
1129
1247
|
### Resource Templates
|
|
1130
1248
|
|
|
1131
1249
|
Resource templates follow the same pattern. Class-based templates declare a `uri_template` and
|
|
@@ -1201,6 +1319,31 @@ server = MCP::Server.new(
|
|
|
1201
1319
|
)
|
|
1202
1320
|
```
|
|
1203
1321
|
|
|
1322
|
+
Registered templates are listed through the `resources/templates/list` protocol method.
|
|
1323
|
+
To serve reads for URIs that match a template, extract the variable parts of the URI in your `resources_read_handler`:
|
|
1324
|
+
|
|
1325
|
+
```ruby
|
|
1326
|
+
resource_template = MCP::ResourceTemplate.new(
|
|
1327
|
+
uri_template: "file:///items/{item_id}",
|
|
1328
|
+
name: "item",
|
|
1329
|
+
mime_type: "application/json",
|
|
1330
|
+
)
|
|
1331
|
+
|
|
1332
|
+
server = MCP::Server.new(name: "my_server", resource_templates: [resource_template])
|
|
1333
|
+
|
|
1334
|
+
server.resources_read_handler do |params|
|
|
1335
|
+
if (match = params[:uri].match(%r{\Afile:///items/(?<item_id>[^/]+)\z}))
|
|
1336
|
+
[{
|
|
1337
|
+
uri: params[:uri],
|
|
1338
|
+
mimeType: "application/json",
|
|
1339
|
+
text: { id: match[:item_id] }.to_json,
|
|
1340
|
+
}]
|
|
1341
|
+
else
|
|
1342
|
+
raise MCP::Server::ResourceNotFoundError.new(params[:uri], params)
|
|
1343
|
+
end
|
|
1344
|
+
end
|
|
1345
|
+
```
|
|
1346
|
+
|
|
1204
1347
|
### Roots
|
|
1205
1348
|
|
|
1206
1349
|
The Model Context Protocol allows servers to request filesystem roots from clients through the `roots/list` method.
|
|
@@ -1793,6 +1936,40 @@ server.define_tool(name: "collect_contact", description: "Collect contact info")
|
|
|
1793
1936
|
end
|
|
1794
1937
|
```
|
|
1795
1938
|
|
|
1939
|
+
The `requested_schema` must be a flat object schema: a top-level `type: "object"` whose `properties` are limited to
|
|
1940
|
+
primitive types (`string`, `number`, `integer`, `boolean`). Nested objects and arrays are not allowed, which keeps
|
|
1941
|
+
the schema simple enough for clients to render as a form. Per the MCP specification, the client validates
|
|
1942
|
+
the user's input against this schema before returning it, so the `content` of an `accept` response matches the requested shape.
|
|
1943
|
+
|
|
1944
|
+
#### Default Values and Enums
|
|
1945
|
+
|
|
1946
|
+
Properties may declare a `default` value (SEP-1034), which clients use to pre-fill the form.
|
|
1947
|
+
String properties may declare `enum` values, optionally with human-readable `enumNames` (SEP-1330), which clients render as a choice list:
|
|
1948
|
+
|
|
1949
|
+
```ruby
|
|
1950
|
+
server.define_tool(name: "configure_deploy", description: "Configure a deployment") do |server_context:|
|
|
1951
|
+
result = server_context.create_form_elicitation(
|
|
1952
|
+
message: "Configure the deployment",
|
|
1953
|
+
requested_schema: {
|
|
1954
|
+
type: "object",
|
|
1955
|
+
properties: {
|
|
1956
|
+
replicas: { type: "integer", default: 3 },
|
|
1957
|
+
verbose: { type: "boolean", default: false },
|
|
1958
|
+
environment: {
|
|
1959
|
+
type: "string",
|
|
1960
|
+
enum: ["dev", "staging", "prod"],
|
|
1961
|
+
enumNames: ["Development", "Staging", "Production"],
|
|
1962
|
+
default: "dev",
|
|
1963
|
+
},
|
|
1964
|
+
},
|
|
1965
|
+
required: ["environment"],
|
|
1966
|
+
},
|
|
1967
|
+
)
|
|
1968
|
+
|
|
1969
|
+
MCP::Tool::Response.new([{ type: "text", text: "Deploying to #{result[:content][:environment]}" }])
|
|
1970
|
+
end
|
|
1971
|
+
```
|
|
1972
|
+
|
|
1796
1973
|
#### URL Mode
|
|
1797
1974
|
|
|
1798
1975
|
URL mode directs the user to an external URL for out-of-band interactions such as OAuth flows:
|
data/lib/mcp/client/tool.rb
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
module MCP
|
|
4
4
|
class Client
|
|
5
5
|
class Tool
|
|
6
|
-
attr_reader :name, :description, :input_schema, :output_schema
|
|
6
|
+
attr_reader :name, :description, :input_schema, :output_schema, :annotations
|
|
7
7
|
|
|
8
|
-
def initialize(name:, description:, input_schema:, output_schema: nil)
|
|
8
|
+
def initialize(name:, description:, input_schema:, output_schema: nil, annotations: nil)
|
|
9
9
|
@name = name
|
|
10
10
|
@description = description
|
|
11
11
|
@input_schema = input_schema
|
|
12
12
|
@output_schema = output_schema
|
|
13
|
+
@annotations = annotations
|
|
13
14
|
end
|
|
14
15
|
end
|
|
15
16
|
end
|
data/lib/mcp/client.rb
CHANGED
data/lib/mcp/configuration.rb
CHANGED
|
@@ -2,12 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
module MCP
|
|
4
4
|
class Configuration
|
|
5
|
-
LATEST_STABLE_PROTOCOL_VERSION = "
|
|
5
|
+
LATEST_STABLE_PROTOCOL_VERSION = "2026-07-28"
|
|
6
6
|
SUPPORTED_STABLE_PROTOCOL_VERSIONS = [
|
|
7
|
-
LATEST_STABLE_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05",
|
|
7
|
+
LATEST_STABLE_PROTOCOL_VERSION, "2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05",
|
|
8
8
|
].freeze
|
|
9
9
|
DEFAULT_NEGOTIATED_PROTOCOL_VERSION = "2025-03-26"
|
|
10
10
|
|
|
11
|
+
# Protocol versions of the stateless "modern" lifecycle introduced by the MCP 2026-07-28 spec release (SEP-2575).
|
|
12
|
+
# 2026-07-28 serves both lifecycles of the dual-era model: it is negotiable through the legacy `initialize`
|
|
13
|
+
# handshake (so it also appears in `SUPPORTED_STABLE_PROTOCOL_VERSIONS`), and it is the version of the modern
|
|
14
|
+
# lifecycle, where each request carries its own version in `_meta` and is validated against this list
|
|
15
|
+
# independently, with no handshake.
|
|
16
|
+
# https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575
|
|
17
|
+
LATEST_MODERN_PROTOCOL_VERSION = "2026-07-28"
|
|
18
|
+
SUPPORTED_MODERN_PROTOCOL_VERSIONS = [LATEST_MODERN_PROTOCOL_VERSION].freeze
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def modern_protocol_version?(version)
|
|
22
|
+
SUPPORTED_MODERN_PROTOCOL_VERSIONS.include?(version)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
11
26
|
attr_writer :exception_reporter, :around_request
|
|
12
27
|
|
|
13
28
|
# @deprecated Use {#around_request=} instead. `instrumentation_callback`
|
data/lib/mcp/error_codes.rb
CHANGED
|
@@ -3,18 +3,24 @@
|
|
|
3
3
|
module MCP
|
|
4
4
|
# MCP-specific JSON-RPC error codes, complementing the generic codes in `JsonRpcHandler::ErrorCode`.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
# `UNSUPPORTED_PROTOCOL_VERSION` rejects a request whose `_meta`-carried protocol version the server does not
|
|
8
|
-
# support (`error.data: { supported: [...], requested: "..." }`), and `MISSING_REQUIRED_CLIENT_CAPABILITY`
|
|
9
|
-
# rejects a request that requires a client capability the request did not declare
|
|
10
|
-
# (`error.data: { requiredCapabilities: {...} }`). The SDK exports the vocabulary; it does not raise
|
|
11
|
-
# these codes itself yet.
|
|
6
|
+
# All three constants below are introduced by the stateless lifecycle of the MCP 2026-07-28 draft (SEP-2575):
|
|
12
7
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
8
|
+
# - `HEADER_MISMATCH` rejects an HTTP request whose headers do not match the corresponding body values,
|
|
9
|
+
# or whose required headers are missing or malformed (no `error.data`). It is reserved for
|
|
10
|
+
# the Streamable HTTP transport, since headers do not exist on stdio.
|
|
11
|
+
# - `MISSING_REQUIRED_CLIENT_CAPABILITY` rejects a request that requires a client capability
|
|
12
|
+
# the request did not declare (`error.data: { requiredCapabilities: {...} }`).
|
|
13
|
+
# Raised via `Server::MissingRequiredClientCapabilityError`.
|
|
14
|
+
# - `UNSUPPORTED_PROTOCOL_VERSION` rejects a request whose `_meta`-carried protocol version the server
|
|
15
|
+
# does not support (`error.data: { supported: [...], requested: "..." }`). Raised via
|
|
16
|
+
# `Server::UnsupportedProtocolVersionError`.
|
|
17
|
+
#
|
|
18
|
+
# The values come from the spec's MCP-specific error code block, which is allocated sequentially from `-32020`
|
|
19
|
+
# toward `-32099`.
|
|
15
20
|
#
|
|
16
21
|
# https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575
|
|
17
22
|
module ErrorCodes
|
|
23
|
+
HEADER_MISMATCH = -32020
|
|
18
24
|
MISSING_REQUIRED_CLIENT_CAPABILITY = -32021
|
|
19
25
|
UNSUPPORTED_PROTOCOL_VERSION = -32022
|
|
20
26
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MCP
|
|
4
|
+
# The per-request `_meta` envelope of the stateless "modern" lifecycle (MCP 2026-07-28, SEP-2575).
|
|
5
|
+
# The modern lifecycle has no `initialize` handshake: every request identifies its protocol version,
|
|
6
|
+
# client, and client capabilities through reserved `_meta` keys, and the server validates
|
|
7
|
+
# each request independently. Servers MUST NOT infer capabilities from prior requests,
|
|
8
|
+
# which is why the envelope is a per-request value object rather than session state.
|
|
9
|
+
#
|
|
10
|
+
# https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575
|
|
11
|
+
class RequestEnvelope
|
|
12
|
+
PROTOCOL_VERSION_META_KEY = "io.modelcontextprotocol/protocolVersion"
|
|
13
|
+
CLIENT_INFO_META_KEY = "io.modelcontextprotocol/clientInfo"
|
|
14
|
+
CLIENT_CAPABILITIES_META_KEY = "io.modelcontextprotocol/clientCapabilities"
|
|
15
|
+
|
|
16
|
+
# Optional per-request log level, replacing the `logging/setLevel` RPC in the modern lifecycle.
|
|
17
|
+
# Deprecated as of 2026-07-28 (SEP-2577) but still part of the wire format.
|
|
18
|
+
LOG_LEVEL_META_KEY = "io.modelcontextprotocol/logLevel"
|
|
19
|
+
|
|
20
|
+
REQUIRED_META_KEYS = [
|
|
21
|
+
PROTOCOL_VERSION_META_KEY,
|
|
22
|
+
CLIENT_INFO_META_KEY,
|
|
23
|
+
CLIENT_CAPABILITIES_META_KEY,
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
# A request is classified as modern only when the full REQUIRED triple is present,
|
|
28
|
+
# matching the TypeScript SDK's `RequestMetaEnvelopeSchema` and the Python SDK's `_has_modern_envelope`.
|
|
29
|
+
# A partial triple is treated as legacy so existing `_meta` usage (`progressToken`, trace context) keeps
|
|
30
|
+
# flowing through the legacy path.
|
|
31
|
+
def modern?(params)
|
|
32
|
+
meta = extract_meta(params)
|
|
33
|
+
return false unless meta.is_a?(Hash)
|
|
34
|
+
|
|
35
|
+
REQUIRED_META_KEYS.all? { |key| !read(meta, key).nil? }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Parses and validates the envelope. `request` is only used to enrich the raised error;
|
|
39
|
+
# callers dispatching notifications can omit it.
|
|
40
|
+
def parse!(params, request: nil)
|
|
41
|
+
meta = extract_meta(params)
|
|
42
|
+
meta = {} unless meta.is_a?(Hash)
|
|
43
|
+
|
|
44
|
+
protocol_version = read(meta, PROTOCOL_VERSION_META_KEY)
|
|
45
|
+
client_info = read(meta, CLIENT_INFO_META_KEY)
|
|
46
|
+
client_capabilities = read(meta, CLIENT_CAPABILITIES_META_KEY)
|
|
47
|
+
|
|
48
|
+
unless protocol_version.is_a?(String) && client_info.is_a?(Hash) && client_capabilities.is_a?(Hash)
|
|
49
|
+
raise Server::RequestHandlerError.new(
|
|
50
|
+
"Invalid Request: modern requests require `#{REQUIRED_META_KEYS.join("`, `")}` in `_meta`",
|
|
51
|
+
request,
|
|
52
|
+
error_type: :invalid_request,
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
unless Configuration.modern_protocol_version?(protocol_version)
|
|
57
|
+
raise Server::UnsupportedProtocolVersionError.new(protocol_version, request)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
new(
|
|
61
|
+
protocol_version: protocol_version,
|
|
62
|
+
client_info: client_info,
|
|
63
|
+
client_capabilities: client_capabilities,
|
|
64
|
+
log_level: read(meta, LOG_LEVEL_META_KEY),
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# `Server#handle` accepts hashes parsed with either symbol or string keys, so read both forms
|
|
71
|
+
# (the same tolerance as `Server#handle_cancelled_notification`).
|
|
72
|
+
def extract_meta(params)
|
|
73
|
+
return unless params.is_a?(Hash)
|
|
74
|
+
|
|
75
|
+
meta = params[:_meta]
|
|
76
|
+
meta.nil? ? params["_meta"] : meta
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def read(meta, key)
|
|
80
|
+
value = meta[key.to_sym]
|
|
81
|
+
value.nil? ? meta[key] : value
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
attr_reader :protocol_version, :client_info, :client_capabilities, :log_level
|
|
86
|
+
|
|
87
|
+
def initialize(protocol_version:, client_info:, client_capabilities:, log_level: nil)
|
|
88
|
+
@protocol_version = protocol_version
|
|
89
|
+
@client_info = client_info
|
|
90
|
+
@client_capabilities = client_capabilities
|
|
91
|
+
@log_level = log_level
|
|
92
|
+
freeze
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
data/lib/mcp/server.rb
CHANGED
|
@@ -59,6 +59,41 @@ module MCP
|
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
+
# Raised when a request carries a protocol version the server does not support under the stateless lifecycle of
|
|
63
|
+
# MCP 2026-07-28 (SEP-2575). Maps to JSON-RPC error `-32022` with `data: { supported: [...], requested: "..." }`
|
|
64
|
+
# so the client can select a mutually supported version and retry.
|
|
65
|
+
#
|
|
66
|
+
# https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575
|
|
67
|
+
class UnsupportedProtocolVersionError < RequestHandlerError
|
|
68
|
+
def initialize(requested, request = nil, supported: Configuration::SUPPORTED_MODERN_PROTOCOL_VERSIONS)
|
|
69
|
+
super(
|
|
70
|
+
"Unsupported protocol version",
|
|
71
|
+
request,
|
|
72
|
+
error_type: :unsupported_protocol_version,
|
|
73
|
+
error_code: ErrorCodes::UNSUPPORTED_PROTOCOL_VERSION,
|
|
74
|
+
error_data: { supported: supported, requested: requested || "unknown" },
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Raised when processing a request requires a client capability the request did not declare in `_meta`
|
|
80
|
+
# (`io.modelcontextprotocol/clientCapabilities`). Per SEP-2575, servers MUST NOT rely on capabilities
|
|
81
|
+
# the client has not declared. Maps to JSON-RPC error `-32021` with `data: { requiredCapabilities: {...} }`
|
|
82
|
+
# listing the missing capabilities.
|
|
83
|
+
#
|
|
84
|
+
# https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575
|
|
85
|
+
class MissingRequiredClientCapabilityError < RequestHandlerError
|
|
86
|
+
def initialize(required_capabilities, request = nil)
|
|
87
|
+
super(
|
|
88
|
+
"Missing required client capability",
|
|
89
|
+
request,
|
|
90
|
+
error_type: :missing_required_client_capability,
|
|
91
|
+
error_code: ErrorCodes::MISSING_REQUIRED_CLIENT_CAPABILITY,
|
|
92
|
+
error_data: { requiredCapabilities: required_capabilities },
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
62
97
|
# Raised when a requested resource URI does not exist. Per SEP-2164,
|
|
63
98
|
# resource-not-found errors use the standard JSON-RPC Invalid Params code (-32602)
|
|
64
99
|
# with the requested URI in the error `data` member. Raise this from
|
|
@@ -745,11 +780,15 @@ module MCP
|
|
|
745
780
|
|
|
746
781
|
progress_token = request.dig(:_meta, :progressToken)
|
|
747
782
|
|
|
748
|
-
|
|
783
|
+
response = call_tool_with_args(
|
|
749
784
|
tool, arguments, server_context_with_meta(request), progress_token: progress_token, session: session, related_request_id: related_request_id, cancellation: cancellation
|
|
750
785
|
)
|
|
786
|
+
result = response.to_h
|
|
751
787
|
validate_tool_call_result!(tool, result)
|
|
752
|
-
serialize_structured_content_fallback(
|
|
788
|
+
serialize_structured_content_fallback(
|
|
789
|
+
result,
|
|
790
|
+
content_provided: response.respond_to?(:content_provided?) && response.content_provided?,
|
|
791
|
+
)
|
|
753
792
|
rescue RequestHandlerError, CancelledError
|
|
754
793
|
# CancelledError is intentionally not wrapped so `handle_request` can turn it into
|
|
755
794
|
# `JsonRpcHandler::NO_RESPONSE` per the MCP cancellation spec.
|
|
@@ -986,14 +1025,18 @@ module MCP
|
|
|
986
1025
|
|
|
987
1026
|
# Per SEP-2106, `structuredContent` may be any JSON value, not only an object.
|
|
988
1027
|
# Clients on older protocol versions may only read `content`,
|
|
989
|
-
# so when a tool returns non-object structured content without
|
|
990
|
-
#
|
|
991
|
-
def serialize_structured_content_fallback(result)
|
|
1028
|
+
# so when a tool returns non-object structured content without explicitly
|
|
1029
|
+
# providing `content`, mirror the value into serialized JSON text.
|
|
1030
|
+
def serialize_structured_content_fallback(result, content_provided: false)
|
|
992
1031
|
structured = result[:structuredContent]
|
|
993
1032
|
return result if structured.nil? || structured.is_a?(Hash)
|
|
1033
|
+
return result if content_provided
|
|
994
1034
|
return result unless result[:content].nil? || result[:content].empty?
|
|
995
1035
|
|
|
996
|
-
|
|
1036
|
+
serialized = JSON.generate(structured)
|
|
1037
|
+
return result if JSON.parse(serialized).is_a?(Hash)
|
|
1038
|
+
|
|
1039
|
+
result.merge(content: [{ type: "text", text: serialized }])
|
|
997
1040
|
end
|
|
998
1041
|
|
|
999
1042
|
# Whether a tool/prompt handler opts in to receiving an `MCP::ServerContext`.
|
|
@@ -1026,9 +1069,9 @@ module MCP
|
|
|
1026
1069
|
related_request_id: related_request_id,
|
|
1027
1070
|
cancellation: cancellation,
|
|
1028
1071
|
)
|
|
1029
|
-
tool.call(**args, server_context: server_context)
|
|
1072
|
+
tool.call(**args, server_context: server_context)
|
|
1030
1073
|
else
|
|
1031
|
-
tool.call(**args)
|
|
1074
|
+
tool.call(**args)
|
|
1032
1075
|
end
|
|
1033
1076
|
end
|
|
1034
1077
|
|
data/lib/mcp/tool/response.rb
CHANGED
|
@@ -7,18 +7,24 @@ module MCP
|
|
|
7
7
|
|
|
8
8
|
attr_reader :content, :structured_content, :meta
|
|
9
9
|
|
|
10
|
-
def initialize(content =
|
|
10
|
+
def initialize(content = NOT_GIVEN, deprecated_error = NOT_GIVEN, error: false, structured_content: nil, meta: nil)
|
|
11
11
|
if deprecated_error != NOT_GIVEN
|
|
12
12
|
warn("Passing `error` with the 2nd argument of `Response.new` is deprecated. Use keyword argument like `Response.new(content, error: error)` instead.", uplevel: 1)
|
|
13
13
|
error = deprecated_error
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
content_given = !content.equal?(NOT_GIVEN)
|
|
17
|
+
@content_provided = content_given && !content.nil?
|
|
18
|
+
@content = content_given ? (content || []) : []
|
|
17
19
|
@error = error
|
|
18
20
|
@structured_content = structured_content
|
|
19
21
|
@meta = meta
|
|
20
22
|
end
|
|
21
23
|
|
|
24
|
+
def content_provided?
|
|
25
|
+
@content_provided
|
|
26
|
+
end
|
|
27
|
+
|
|
22
28
|
def error?
|
|
23
29
|
!!@error
|
|
24
30
|
end
|
data/lib/mcp/version.rb
CHANGED
data/lib/mcp.rb
CHANGED
|
@@ -16,6 +16,7 @@ module MCP
|
|
|
16
16
|
autoload :ErrorCodes, "mcp/error_codes"
|
|
17
17
|
autoload :Icon, "mcp/icon"
|
|
18
18
|
autoload :Prompt, "mcp/prompt"
|
|
19
|
+
autoload :RequestEnvelope, "mcp/request_envelope"
|
|
19
20
|
autoload :Resource, "mcp/resource"
|
|
20
21
|
autoload :ResourceTemplate, "mcp/resource_template"
|
|
21
22
|
autoload :ResultType, "mcp/result_type"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mcp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Model Context Protocol
|
|
@@ -69,6 +69,7 @@ files:
|
|
|
69
69
|
- lib/mcp/prompt/argument.rb
|
|
70
70
|
- lib/mcp/prompt/message.rb
|
|
71
71
|
- lib/mcp/prompt/result.rb
|
|
72
|
+
- lib/mcp/request_envelope.rb
|
|
72
73
|
- lib/mcp/resource.rb
|
|
73
74
|
- lib/mcp/resource/contents.rb
|
|
74
75
|
- lib/mcp/resource/embedded.rb
|
|
@@ -97,7 +98,7 @@ licenses:
|
|
|
97
98
|
- Apache-2.0
|
|
98
99
|
metadata:
|
|
99
100
|
allowed_push_host: https://rubygems.org
|
|
100
|
-
changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/
|
|
101
|
+
changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v1.1.0
|
|
101
102
|
homepage_uri: https://ruby.sdk.modelcontextprotocol.io
|
|
102
103
|
source_code_uri: https://github.com/modelcontextprotocol/ruby-sdk
|
|
103
104
|
bug_tracker_uri: https://github.com/modelcontextprotocol/ruby-sdk/issues
|