mcp 0.3.0 → 0.4.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/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +2 -2
- data/.github/workflows/release.yml +1 -1
- data/AGENTS.md +119 -0
- data/CHANGELOG.md +30 -0
- data/README.md +72 -12
- data/examples/http_server.rb +0 -1
- data/examples/stdio_server.rb +0 -1
- data/examples/streamable_http_server.rb +0 -1
- data/lib/mcp/client.rb +33 -5
- data/lib/mcp/configuration.rb +29 -7
- data/lib/mcp/content.rb +0 -1
- data/lib/mcp/prompt/argument.rb +9 -5
- data/lib/mcp/prompt/message.rb +0 -1
- data/lib/mcp/prompt/result.rb +0 -1
- data/lib/mcp/prompt.rb +19 -3
- data/lib/mcp/resource/contents.rb +0 -1
- data/lib/mcp/resource/embedded.rb +0 -1
- data/lib/mcp/resource.rb +0 -1
- data/lib/mcp/resource_template.rb +0 -1
- data/lib/mcp/server/transports/streamable_http_transport.rb +11 -3
- data/lib/mcp/server.rb +4 -4
- data/lib/mcp/string_utils.rb +0 -1
- data/lib/mcp/tool/input_schema.rb +4 -1
- data/lib/mcp/tool/output_schema.rb +27 -24
- data/lib/mcp/tool/response.rb +5 -4
- data/lib/mcp/tool.rb +14 -4
- data/lib/mcp/version.rb +1 -1
- metadata +3 -2
- data/.cursor/rules/release-changelogs.mdc +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 451bdb587fa89621924916d7cc137cf44a3a2293cf7803c3c6af1d97fb57332a
|
4
|
+
data.tar.gz: 3175f6a5d60426d5441b99b2bbf6d50b90d31daa9b713796029547f5efc6ab6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b9e312c047ebc31d0826ec2520e14fef34fd0a23f42d9bf849f4a64b8cdb924341aa8a431aab497824849da476de5b8b2d55ad3b3996b9871e04b9022161aa3
|
7
|
+
data.tar.gz: 772762d9cf650278dd0756867a995e57e13366e1a4a2a5b306433d8d9246e8bed2aadb00e057d4f31a521603dab6f8fadcbcdfc9650806c79d9881e2e1913acb
|
data/.github/workflows/ci.yml
CHANGED
@@ -13,7 +13,7 @@ jobs:
|
|
13
13
|
- { ruby: head, allowed-failure: true }
|
14
14
|
name: Test Ruby ${{ matrix.entry.ruby }}
|
15
15
|
steps:
|
16
|
-
- uses: actions/checkout@
|
16
|
+
- uses: actions/checkout@v5
|
17
17
|
- uses: ruby/setup-ruby@v1
|
18
18
|
with:
|
19
19
|
ruby-version: ${{ matrix.entry.ruby }}
|
@@ -25,7 +25,7 @@ jobs:
|
|
25
25
|
runs-on: ubuntu-latest
|
26
26
|
name: RuboCop
|
27
27
|
steps:
|
28
|
-
- uses: actions/checkout@
|
28
|
+
- uses: actions/checkout@v5
|
29
29
|
- uses: ruby/setup-ruby@v1
|
30
30
|
with:
|
31
31
|
ruby-version: 3.2 # Specify the oldest supported Ruby version.
|
@@ -16,7 +16,7 @@ jobs:
|
|
16
16
|
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
17
17
|
contents: write # IMPORTANT: this permission is required for `rake release` to push the release tag
|
18
18
|
steps:
|
19
|
-
- uses: actions/checkout@
|
19
|
+
- uses: actions/checkout@v5
|
20
20
|
- name: Set up Ruby
|
21
21
|
uses: ruby/setup-ruby@v1
|
22
22
|
with:
|
data/AGENTS.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
# AGENTS.md
|
2
|
+
|
3
|
+
## Project overview
|
4
|
+
|
5
|
+
This is the official Ruby SDK for the Model Context Protocol (MCP), implementing both server and client functionality for JSON-RPC 2.0 based communication between LLM applications and context providers.
|
6
|
+
|
7
|
+
## Dev environment setup
|
8
|
+
|
9
|
+
- Ruby 3.2.0+ required
|
10
|
+
- Run `bundle install` to install dependencies
|
11
|
+
- Dependencies: `json_rpc_handler` ~> 0.1, `json-schema` >= 4.1
|
12
|
+
|
13
|
+
## Build and test commands
|
14
|
+
|
15
|
+
- `bundle install` - Install dependencies
|
16
|
+
- `rake test` - Run all tests
|
17
|
+
- `rake rubocop` - Run linter
|
18
|
+
- `rake` - Run tests and linting (default task)
|
19
|
+
- `ruby -I lib -I test test/path/to/specific_test.rb` - Run single test file
|
20
|
+
- `gem build mcp.gemspec` - Build the gem
|
21
|
+
|
22
|
+
## Testing instructions
|
23
|
+
|
24
|
+
- Test files are in `test/` directory with `_test.rb` suffix
|
25
|
+
- Run full test suite with `rake test`
|
26
|
+
- Run individual tests with `ruby -I lib -I test test/path/to/file_test.rb`
|
27
|
+
- Tests should pass before submitting PRs
|
28
|
+
|
29
|
+
## Code style guidelines
|
30
|
+
|
31
|
+
- Follow RuboCop rules (run `rake rubocop`)
|
32
|
+
- Use frozen string literals
|
33
|
+
- Follow Ruby community conventions
|
34
|
+
- Keep dependencies minimal
|
35
|
+
|
36
|
+
## Commit message conventions
|
37
|
+
|
38
|
+
- Use conventional commit format when possible
|
39
|
+
- Include clear, descriptive commit messages
|
40
|
+
- Releases are triggered by updating version in `lib/mcp/version.rb` and merging to main
|
41
|
+
|
42
|
+
## Release process
|
43
|
+
|
44
|
+
- Follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format in CHANGELOG.md
|
45
|
+
- Update CHANGELOG.md before cutting releases
|
46
|
+
- Use git history and PR merge commits to construct changelog entries
|
47
|
+
- Format entries as: "Terse description of the change (#nnn)"
|
48
|
+
- Keep entries in flat list format (no nesting)
|
49
|
+
- Git tags mark commits that cut new releases
|
50
|
+
- Exclude maintenance PRs that don't concern end users
|
51
|
+
- Check upstream remote for PRs if available
|
52
|
+
|
53
|
+
## Architecture overview
|
54
|
+
|
55
|
+
### Core Components
|
56
|
+
|
57
|
+
**MCP::Server** (`lib/mcp/server.rb`):
|
58
|
+
|
59
|
+
- Main server class handling JSON-RPC requests
|
60
|
+
- Implements MCP protocol methods: initialize, ping, tools/list, tools/call, prompts/list, prompts/get, resources/list, resources/read
|
61
|
+
- Supports custom method registration via `define_custom_method`
|
62
|
+
- Handles instrumentation, exception reporting, and notifications
|
63
|
+
- Uses JsonRpcHandler for request processing
|
64
|
+
|
65
|
+
**MCP::Client** (`lib/mcp/client.rb`):
|
66
|
+
|
67
|
+
- Client interface for communicating with MCP servers
|
68
|
+
- Transport-agnostic design with pluggable transport layers
|
69
|
+
- Supports tool listing and invocation
|
70
|
+
|
71
|
+
**Transport Layer**:
|
72
|
+
|
73
|
+
- `MCP::Server::Transports::StdioTransport` - Command-line stdio transport
|
74
|
+
- `MCP::Server::Transports::StreamableHttpTransport` - HTTP with streaming support
|
75
|
+
- `MCP::Client::HTTP` - HTTP client transport (requires faraday gem)
|
76
|
+
|
77
|
+
**Protocol Components**:
|
78
|
+
|
79
|
+
- `MCP::Tool` - Tool definition with input/output schemas and annotations
|
80
|
+
- `MCP::Prompt` - Prompt templates with argument validation
|
81
|
+
- `MCP::Resource` - Resource registration and retrieval
|
82
|
+
- `MCP::Configuration` - Global configuration with exception reporting and instrumentation
|
83
|
+
|
84
|
+
### Key Patterns
|
85
|
+
|
86
|
+
**Three Ways to Define Components**:
|
87
|
+
|
88
|
+
1. Class inheritance (e.g., `class MyTool < MCP::Tool`)
|
89
|
+
2. Define methods (e.g., `MCP::Tool.define(name: "my_tool") { ... }`)
|
90
|
+
3. Server registration (e.g., `server.define_tool(name: "my_tool") { ... }`)
|
91
|
+
|
92
|
+
**Schema Validation**:
|
93
|
+
|
94
|
+
- Tools support input_schema and output_schema for JSON Schema validation
|
95
|
+
- Protocol version 2025-03-26+ supports tool annotations (destructive_hint, idempotent_hint, etc.)
|
96
|
+
- Validation is configurable via `configuration.validate_tool_call_arguments`
|
97
|
+
|
98
|
+
**Context Passing**:
|
99
|
+
|
100
|
+
- `server_context` hash passed through tool/prompt calls for request-specific data
|
101
|
+
- Methods can accept `server_context:` keyword argument for accessing context
|
102
|
+
|
103
|
+
### Dependencies
|
104
|
+
|
105
|
+
- `json_rpc_handler` ~> 0.1 - JSON-RPC 2.0 message handling
|
106
|
+
- `json-schema` >= 4.1 - Schema validation
|
107
|
+
- Ruby 3.2.0+ required
|
108
|
+
|
109
|
+
### Integration patterns
|
110
|
+
|
111
|
+
- **Rails controllers**: Use `server.handle_json(request.body.read)` for HTTP endpoints
|
112
|
+
- **Command-line tools**: Use `StdioTransport.new(server).open` for CLI applications
|
113
|
+
- **HTTP services**: Use `StreamableHttpTransport` for web-based servers
|
114
|
+
|
115
|
+
### Component definition patterns
|
116
|
+
|
117
|
+
1. **Class inheritance**: `class MyTool < MCP::Tool`
|
118
|
+
2. **Define methods**: `MCP::Tool.define(name: "my_tool") { ... }`
|
119
|
+
3. **Server registration**: `server.define_tool(name: "my_tool") { ... }`
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [0.4.0] - 2025-10-15
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- Client resources support with `resources/list` and `resources/read` methods (#160)
|
15
|
+
- `_meta` field support for Tool schema (#124)
|
16
|
+
- `_meta` field support for Prompt
|
17
|
+
- `title` field support for prompt arguments
|
18
|
+
- `call_tool_raw` method to client for accessing full tool responses (#149)
|
19
|
+
- Structured content support in tool responses (#147)
|
20
|
+
- AGENTS.md development guidance documentation (#134)
|
21
|
+
- Dependabot configuration for automated dependency updates (#138)
|
22
|
+
|
23
|
+
### Changed
|
24
|
+
|
25
|
+
- Set default `content` to empty array instead of `nil` (#150)
|
26
|
+
- Improved prompt spec compliance (#153)
|
27
|
+
- Allow output schema to be array of objects (#144)
|
28
|
+
- Return 202 response code for accepted JSON-RPC notifications (#114)
|
29
|
+
- Added validation to `MCP::Configuration` setters (#145)
|
30
|
+
- Updated metaschema URI format for cross-OS compatibility
|
31
|
+
|
32
|
+
### Fixed
|
33
|
+
|
34
|
+
- Client tools functionality and test coverage (#166)
|
35
|
+
- Client resources test for empty responses (#162)
|
36
|
+
- Documentation typos and incorrect examples (#157, #146)
|
37
|
+
- Removed redundant transport requires (#154)
|
38
|
+
- Cleaned up unused block parameters and magic comments
|
39
|
+
|
10
40
|
## [0.3.0] - 2025-09-14
|
11
41
|
|
12
42
|
### Added
|
data/README.md
CHANGED
@@ -131,7 +131,7 @@ Notifications follow the JSON-RPC 2.0 specification and use these method names:
|
|
131
131
|
|
132
132
|
```ruby
|
133
133
|
server = MCP::Server.new(name: "my_server")
|
134
|
-
transport = MCP::Transports::
|
134
|
+
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
|
135
135
|
server.transport = transport
|
136
136
|
|
137
137
|
# When tools change, notify clients
|
@@ -178,7 +178,6 @@ If you want to build a local command-line application, you can use the stdio tra
|
|
178
178
|
|
179
179
|
```ruby
|
180
180
|
require "mcp"
|
181
|
-
require "mcp/server/transports/stdio_transport"
|
182
181
|
|
183
182
|
# Create a simple tool
|
184
183
|
class ExampleTool < MCP::Tool
|
@@ -425,10 +424,10 @@ tool = MCP::Tool.define(
|
|
425
424
|
end
|
426
425
|
```
|
427
426
|
|
428
|
-
3. By using the `
|
427
|
+
3. By using the `MCP::Server#define_tool` method with a block:
|
429
428
|
|
430
429
|
```ruby
|
431
|
-
server =
|
430
|
+
server = MCP::Server.new
|
432
431
|
server.define_tool(
|
433
432
|
name: "my_tool",
|
434
433
|
description: "This tool performs specific functionality...",
|
@@ -546,6 +545,27 @@ class DataTool < MCP::Tool
|
|
546
545
|
end
|
547
546
|
```
|
548
547
|
|
548
|
+
Output schema may also describe an array of objects:
|
549
|
+
|
550
|
+
```ruby
|
551
|
+
class WeatherTool < MCP::Tool
|
552
|
+
output_schema(
|
553
|
+
type: "array",
|
554
|
+
item: {
|
555
|
+
properties: {
|
556
|
+
temperature: { type: "number" },
|
557
|
+
condition: { type: "string" },
|
558
|
+
humidity: { type: "integer" }
|
559
|
+
},
|
560
|
+
required: ["temperature", "condition", "humidity"]
|
561
|
+
}
|
562
|
+
)
|
563
|
+
end
|
564
|
+
```
|
565
|
+
|
566
|
+
Please note: in this case, you must provide `type: "array"`. The default type
|
567
|
+
for output schemas is `object`.
|
568
|
+
|
549
569
|
MCP spec for the [Output Schema](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema) specifies that:
|
550
570
|
|
551
571
|
- **Server Validation**: Servers MUST provide structured results that conform to the output schema
|
@@ -555,6 +575,38 @@ MCP spec for the [Output Schema](https://modelcontextprotocol.io/specification/2
|
|
555
575
|
|
556
576
|
The output schema follows standard JSON Schema format and helps ensure consistent data exchange between MCP servers and clients.
|
557
577
|
|
578
|
+
### Tool Responses with Structured Content
|
579
|
+
|
580
|
+
Tools can return structured data alongside text content using the `structured_content` parameter.
|
581
|
+
|
582
|
+
The structured content will be included in the JSON-RPC response as the `structuredContent` field.
|
583
|
+
|
584
|
+
```ruby
|
585
|
+
class APITool < MCP::Tool
|
586
|
+
description "Get current weather and return structured data"
|
587
|
+
|
588
|
+
def self.call(endpoint:, server_context:)
|
589
|
+
# Call weather API and structure the response
|
590
|
+
api_response = WeatherAPI.fetch(location, units)
|
591
|
+
weather_data = {
|
592
|
+
temperature: api_response.temp,
|
593
|
+
condition: api_response.description,
|
594
|
+
humidity: api_response.humidity_percent
|
595
|
+
}
|
596
|
+
|
597
|
+
output_schema.validate_result(weather_data)
|
598
|
+
|
599
|
+
MCP::Tool::Response.new(
|
600
|
+
[{
|
601
|
+
type: "text",
|
602
|
+
text: weather_data.to_json
|
603
|
+
}],
|
604
|
+
structured_content: weather_data
|
605
|
+
)
|
606
|
+
end
|
607
|
+
end
|
608
|
+
```
|
609
|
+
|
558
610
|
### Prompts
|
559
611
|
|
560
612
|
MCP spec includes [Prompts](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts), which enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs.
|
@@ -571,10 +623,12 @@ class MyPrompt < MCP::Prompt
|
|
571
623
|
arguments [
|
572
624
|
MCP::Prompt::Argument.new(
|
573
625
|
name: "message",
|
626
|
+
title: "Message Title",
|
574
627
|
description: "Input message",
|
575
628
|
required: true
|
576
629
|
)
|
577
630
|
]
|
631
|
+
meta({ version: "1.0", category: "example" })
|
578
632
|
|
579
633
|
class << self
|
580
634
|
def template(args, server_context:)
|
@@ -608,10 +662,12 @@ prompt = MCP::Prompt.define(
|
|
608
662
|
arguments: [
|
609
663
|
MCP::Prompt::Argument.new(
|
610
664
|
name: "message",
|
665
|
+
title: "Message Title",
|
611
666
|
description: "Input message",
|
612
667
|
required: true
|
613
668
|
)
|
614
|
-
]
|
669
|
+
],
|
670
|
+
meta: { version: "1.0", category: "example" }
|
615
671
|
) do |args, server_context:|
|
616
672
|
MCP::Prompt::Result.new(
|
617
673
|
description: "Response description",
|
@@ -629,20 +685,22 @@ prompt = MCP::Prompt.define(
|
|
629
685
|
end
|
630
686
|
```
|
631
687
|
|
632
|
-
3. Using the `
|
688
|
+
3. Using the `MCP::Server#define_prompt` method:
|
633
689
|
|
634
690
|
```ruby
|
635
|
-
server =
|
636
|
-
server.
|
691
|
+
server = MCP::Server.new
|
692
|
+
server.define_prompt(
|
637
693
|
name: "my_prompt",
|
638
694
|
description: "This prompt performs specific functionality...",
|
639
695
|
arguments: [
|
640
696
|
Prompt::Argument.new(
|
641
697
|
name: "message",
|
698
|
+
title: "Message Title",
|
642
699
|
description: "Input message",
|
643
700
|
required: true
|
644
701
|
)
|
645
|
-
]
|
702
|
+
],
|
703
|
+
meta: { version: "1.0", category: "example" }
|
646
704
|
) do |args, server_context:|
|
647
705
|
Prompt::Result.new(
|
648
706
|
description: "Response description",
|
@@ -665,7 +723,7 @@ e.g. around authentication state or user preferences.
|
|
665
723
|
|
666
724
|
### Key Components
|
667
725
|
|
668
|
-
- `MCP::Prompt::Argument` - Defines input parameters for the prompt template
|
726
|
+
- `MCP::Prompt::Argument` - Defines input parameters for the prompt template with name, title, description, and required flag
|
669
727
|
- `MCP::Prompt::Message` - Represents a message in the conversation with a role and content
|
670
728
|
- `MCP::Prompt::Result` - The output of a prompt template containing description and messages
|
671
729
|
- `MCP::Content::Text` - Text content for messages
|
@@ -774,8 +832,10 @@ The `MCP::Client` class provides an interface for interacting with MCP servers.
|
|
774
832
|
|
775
833
|
This class supports:
|
776
834
|
|
777
|
-
- Tool listing via the `tools/list` method
|
778
|
-
- Tool invocation via the `tools/call` method
|
835
|
+
- Tool listing via the `tools/list` method (`MCP::Client#tools`)
|
836
|
+
- Tool invocation via the `tools/call` method (`MCP::Client#call_tools`)
|
837
|
+
- Resource listing via the `resources/list` method (`MCP::Client#resources`)
|
838
|
+
- Resource reading via the `resources/read` method (`MCP::Client#read_resources`)
|
779
839
|
- Automatic JSON-RPC 2.0 message formatting
|
780
840
|
- UUID request ID generation
|
781
841
|
|
data/examples/http_server.rb
CHANGED
data/examples/stdio_server.rb
CHANGED
data/lib/mcp/client.rb
CHANGED
@@ -44,28 +44,56 @@ module MCP
|
|
44
44
|
end || []
|
45
45
|
end
|
46
46
|
|
47
|
-
#
|
47
|
+
# Returns the list of resources available from the server.
|
48
|
+
# Each call will make a new request – the result is not cached.
|
49
|
+
#
|
50
|
+
# @return [Array<Hash>] An array of available resources.
|
51
|
+
def resources
|
52
|
+
response = transport.send_request(request: {
|
53
|
+
jsonrpc: JsonRpcHandler::Version::V2_0,
|
54
|
+
id: request_id,
|
55
|
+
method: "resources/list",
|
56
|
+
})
|
57
|
+
|
58
|
+
response.dig("result", "resources") || []
|
59
|
+
end
|
60
|
+
|
61
|
+
# Calls a tool via the transport layer and returns the full response from the server.
|
48
62
|
#
|
49
63
|
# @param tool [MCP::Client::Tool] The tool to be called.
|
50
64
|
# @param arguments [Object, nil] The arguments to pass to the tool.
|
51
|
-
# @return [
|
65
|
+
# @return [Hash] The full JSON-RPC response from the transport.
|
52
66
|
#
|
53
67
|
# @example
|
54
68
|
# tool = client.tools.first
|
55
|
-
#
|
69
|
+
# response = client.call_tool(tool: tool, arguments: { foo: "bar" })
|
70
|
+
# structured_content = response.dig("result", "structuredContent")
|
56
71
|
#
|
57
72
|
# @note
|
58
73
|
# The exact requirements for `arguments` are determined by the transport layer in use.
|
59
74
|
# Consult the documentation for your transport (e.g., MCP::Client::HTTP) for details.
|
60
75
|
def call_tool(tool:, arguments: nil)
|
61
|
-
|
76
|
+
transport.send_request(request: {
|
62
77
|
jsonrpc: JsonRpcHandler::Version::V2_0,
|
63
78
|
id: request_id,
|
64
79
|
method: "tools/call",
|
65
80
|
params: { name: tool.name, arguments: arguments },
|
66
81
|
})
|
82
|
+
end
|
83
|
+
|
84
|
+
# Reads a resource from the server by URI and returns the contents.
|
85
|
+
#
|
86
|
+
# @param uri [String] The URI of the resource to read.
|
87
|
+
# @return [Array<Hash>] An array of resource contents (text or blob).
|
88
|
+
def read_resource(uri:)
|
89
|
+
response = transport.send_request(request: {
|
90
|
+
jsonrpc: JsonRpcHandler::Version::V2_0,
|
91
|
+
id: request_id,
|
92
|
+
method: "resources/read",
|
93
|
+
params: { uri: uri },
|
94
|
+
})
|
67
95
|
|
68
|
-
response.dig("result", "
|
96
|
+
response.dig("result", "contents") || []
|
69
97
|
end
|
70
98
|
|
71
99
|
private
|
data/lib/mcp/configuration.rb
CHANGED
@@ -5,20 +5,29 @@ module MCP
|
|
5
5
|
DEFAULT_PROTOCOL_VERSION = "2025-06-18"
|
6
6
|
SUPPORTED_PROTOCOL_VERSIONS = [DEFAULT_PROTOCOL_VERSION, "2025-03-26", "2024-11-05"]
|
7
7
|
|
8
|
-
attr_writer :exception_reporter, :instrumentation_callback
|
8
|
+
attr_writer :exception_reporter, :instrumentation_callback
|
9
9
|
|
10
10
|
def initialize(exception_reporter: nil, instrumentation_callback: nil, protocol_version: nil,
|
11
11
|
validate_tool_call_arguments: true)
|
12
12
|
@exception_reporter = exception_reporter
|
13
13
|
@instrumentation_callback = instrumentation_callback
|
14
14
|
@protocol_version = protocol_version
|
15
|
-
if protocol_version
|
16
|
-
|
17
|
-
raise ArgumentError, message
|
18
|
-
end
|
19
|
-
unless validate_tool_call_arguments.is_a?(TrueClass) || validate_tool_call_arguments.is_a?(FalseClass)
|
20
|
-
raise ArgumentError, "validate_tool_call_arguments must be a boolean"
|
15
|
+
if protocol_version
|
16
|
+
validate_protocol_version!(protocol_version)
|
21
17
|
end
|
18
|
+
validate_value_of_validate_tool_call_arguments!(validate_tool_call_arguments)
|
19
|
+
|
20
|
+
@validate_tool_call_arguments = validate_tool_call_arguments
|
21
|
+
end
|
22
|
+
|
23
|
+
def protocol_version=(protocol_version)
|
24
|
+
validate_protocol_version!(protocol_version)
|
25
|
+
|
26
|
+
@protocol_version = protocol_version
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate_tool_call_arguments=(validate_tool_call_arguments)
|
30
|
+
validate_value_of_validate_tool_call_arguments!(validate_tool_call_arguments)
|
22
31
|
|
23
32
|
@validate_tool_call_arguments = validate_tool_call_arguments
|
24
33
|
end
|
@@ -83,6 +92,19 @@ module MCP
|
|
83
92
|
|
84
93
|
private
|
85
94
|
|
95
|
+
def validate_protocol_version!(protocol_version)
|
96
|
+
unless SUPPORTED_PROTOCOL_VERSIONS.include?(protocol_version)
|
97
|
+
message = "protocol_version must be #{SUPPORTED_PROTOCOL_VERSIONS[0...-1].join(", ")}, or #{SUPPORTED_PROTOCOL_VERSIONS[-1]}"
|
98
|
+
raise ArgumentError, message
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate_value_of_validate_tool_call_arguments!(validate_tool_call_arguments)
|
103
|
+
unless validate_tool_call_arguments.is_a?(TrueClass) || validate_tool_call_arguments.is_a?(FalseClass)
|
104
|
+
raise ArgumentError, "validate_tool_call_arguments must be a boolean"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
86
108
|
def default_exception_reporter
|
87
109
|
@default_exception_reporter ||= ->(exception, server_context) {}
|
88
110
|
end
|
data/lib/mcp/content.rb
CHANGED
data/lib/mcp/prompt/argument.rb
CHANGED
@@ -1,20 +1,24 @@
|
|
1
|
-
# typed: strict
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module MCP
|
5
4
|
class Prompt
|
6
5
|
class Argument
|
7
|
-
attr_reader :name, :
|
6
|
+
attr_reader :name, :title, :description, :required
|
8
7
|
|
9
|
-
def initialize(name:, description: nil, required: false)
|
8
|
+
def initialize(name:, title: nil, description: nil, required: false)
|
10
9
|
@name = name
|
10
|
+
@title = title
|
11
11
|
@description = description
|
12
12
|
@required = required
|
13
|
-
@arguments = arguments
|
14
13
|
end
|
15
14
|
|
16
15
|
def to_h
|
17
|
-
{
|
16
|
+
{
|
17
|
+
name: name,
|
18
|
+
title: title,
|
19
|
+
description: description,
|
20
|
+
required: required,
|
21
|
+
}.compact
|
18
22
|
end
|
19
23
|
end
|
20
24
|
end
|
data/lib/mcp/prompt/message.rb
CHANGED
data/lib/mcp/prompt/result.rb
CHANGED
data/lib/mcp/prompt.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# typed: strict
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module MCP
|
@@ -9,13 +8,20 @@ module MCP
|
|
9
8
|
attr_reader :title_value
|
10
9
|
attr_reader :description_value
|
11
10
|
attr_reader :arguments_value
|
11
|
+
attr_reader :meta_value
|
12
12
|
|
13
13
|
def template(args, server_context: nil)
|
14
14
|
raise NotImplementedError, "Subclasses must implement template"
|
15
15
|
end
|
16
16
|
|
17
17
|
def to_h
|
18
|
-
{
|
18
|
+
{
|
19
|
+
name: name_value,
|
20
|
+
title: title_value,
|
21
|
+
description: description_value,
|
22
|
+
arguments: arguments_value&.map(&:to_h),
|
23
|
+
_meta: meta_value,
|
24
|
+
}.compact
|
19
25
|
end
|
20
26
|
|
21
27
|
def inherited(subclass)
|
@@ -24,6 +30,7 @@ module MCP
|
|
24
30
|
subclass.instance_variable_set(:@title_value, nil)
|
25
31
|
subclass.instance_variable_set(:@description_value, nil)
|
26
32
|
subclass.instance_variable_set(:@arguments_value, nil)
|
33
|
+
subclass.instance_variable_set(:@meta_value, nil)
|
27
34
|
end
|
28
35
|
|
29
36
|
def prompt_name(value = NOT_SET)
|
@@ -62,7 +69,15 @@ module MCP
|
|
62
69
|
end
|
63
70
|
end
|
64
71
|
|
65
|
-
def
|
72
|
+
def meta(value = NOT_SET)
|
73
|
+
if value == NOT_SET
|
74
|
+
@meta_value
|
75
|
+
else
|
76
|
+
@meta_value = value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def define(name: nil, title: nil, description: nil, arguments: [], meta: nil, &block)
|
66
81
|
Class.new(self) do
|
67
82
|
prompt_name name
|
68
83
|
title title
|
@@ -71,6 +86,7 @@ module MCP
|
|
71
86
|
define_singleton_method(:template) do |args, server_context: nil|
|
72
87
|
instance_exec(args, server_context:, &block)
|
73
88
|
end
|
89
|
+
meta meta
|
74
90
|
end
|
75
91
|
end
|
76
92
|
|
data/lib/mcp/resource.rb
CHANGED
@@ -108,8 +108,8 @@ module MCP
|
|
108
108
|
|
109
109
|
if body["method"] == "initialize"
|
110
110
|
handle_initialization(body_string, body)
|
111
|
-
elsif body
|
112
|
-
|
111
|
+
elsif notification?(body) || response?(body)
|
112
|
+
handle_accepted
|
113
113
|
else
|
114
114
|
handle_regular_request(body_string, session_id)
|
115
115
|
end
|
@@ -168,6 +168,14 @@ module MCP
|
|
168
168
|
[400, { "Content-Type" => "application/json" }, [{ error: "Invalid JSON" }.to_json]]
|
169
169
|
end
|
170
170
|
|
171
|
+
def notification?(body)
|
172
|
+
!body["id"] && !!body["method"]
|
173
|
+
end
|
174
|
+
|
175
|
+
def response?(body)
|
176
|
+
!!body["id"] && !body["method"]
|
177
|
+
end
|
178
|
+
|
171
179
|
def handle_initialization(body_string, body)
|
172
180
|
session_id = SecureRandom.uuid
|
173
181
|
|
@@ -187,7 +195,7 @@ module MCP
|
|
187
195
|
[200, headers, [response]]
|
188
196
|
end
|
189
197
|
|
190
|
-
def
|
198
|
+
def handle_accepted
|
191
199
|
[202, {}, []]
|
192
200
|
end
|
193
201
|
|
data/lib/mcp/server.rb
CHANGED
@@ -96,8 +96,8 @@ module MCP
|
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
99
|
-
def define_tool(name: nil, title: nil, description: nil, input_schema: nil, annotations: nil, &block)
|
100
|
-
tool = Tool.define(name:, title:, description:, input_schema:, annotations:, &block)
|
99
|
+
def define_tool(name: nil, title: nil, description: nil, input_schema: nil, annotations: nil, meta: nil, &block)
|
100
|
+
tool = Tool.define(name:, title:, description:, input_schema:, annotations:, meta:, &block)
|
101
101
|
@tools[tool.name_value] = tool
|
102
102
|
|
103
103
|
validate!
|
@@ -253,7 +253,7 @@ module MCP
|
|
253
253
|
end
|
254
254
|
|
255
255
|
def list_tools(request)
|
256
|
-
@tools.map
|
256
|
+
@tools.values.map(&:to_h)
|
257
257
|
end
|
258
258
|
|
259
259
|
def call_tool(request)
|
@@ -293,7 +293,7 @@ module MCP
|
|
293
293
|
end
|
294
294
|
|
295
295
|
def list_prompts(request)
|
296
|
-
@prompts.map
|
296
|
+
@prompts.values.map(&:to_h)
|
297
297
|
end
|
298
298
|
|
299
299
|
def get_prompt(request)
|
data/lib/mcp/string_utils.rb
CHANGED
@@ -50,7 +50,10 @@ module MCP
|
|
50
50
|
accept_uri: false,
|
51
51
|
accept_file: ->(path) { path.to_s.start_with?(Gem.loaded_specs["json-schema"].full_gem_path) },
|
52
52
|
)
|
53
|
-
|
53
|
+
metaschema_path = Pathname.new(JSON::Validator.validator_for_name("draft4").metaschema)
|
54
|
+
# Converts metaschema to a file URI for cross-platform compatibility
|
55
|
+
metaschema_uri = JSON::Util::URI.file_uri(metaschema_path.expand_path.cleanpath.to_s.tr("\\", "/"))
|
56
|
+
metaschema = metaschema_uri.to_s
|
54
57
|
errors = JSON::Validator.fully_validate(metaschema, schema, schema_reader: schema_reader)
|
55
58
|
if errors.any?
|
56
59
|
raise ArgumentError, "Invalid JSON Schema: #{errors.join(", ")}"
|
@@ -7,23 +7,20 @@ module MCP
|
|
7
7
|
class OutputSchema
|
8
8
|
class ValidationError < StandardError; end
|
9
9
|
|
10
|
-
attr_reader :
|
10
|
+
attr_reader :schema
|
11
11
|
|
12
|
-
def initialize(
|
13
|
-
@
|
14
|
-
@
|
12
|
+
def initialize(schema = {})
|
13
|
+
@schema = deep_transform_keys(JSON.parse(JSON.dump(schema)), &:to_sym)
|
14
|
+
@schema[:type] ||= "object"
|
15
15
|
validate_schema!
|
16
16
|
end
|
17
17
|
|
18
18
|
def ==(other)
|
19
|
-
other.is_a?(OutputSchema) &&
|
19
|
+
other.is_a?(OutputSchema) && schema == other.schema
|
20
20
|
end
|
21
21
|
|
22
22
|
def to_h
|
23
|
-
|
24
|
-
hsh[:properties] = properties if properties.any?
|
25
|
-
hsh[:required] = required if required.any?
|
26
|
-
end
|
23
|
+
@schema
|
27
24
|
end
|
28
25
|
|
29
26
|
def validate_result(result)
|
@@ -35,32 +32,38 @@ module MCP
|
|
35
32
|
|
36
33
|
private
|
37
34
|
|
35
|
+
def deep_transform_keys(schema, &block)
|
36
|
+
case schema
|
37
|
+
when Hash
|
38
|
+
schema.each_with_object({}) do |(key, value), result|
|
39
|
+
if key.casecmp?("$ref")
|
40
|
+
raise ArgumentError, "Invalid JSON Schema: $ref is not allowed in tool output schemas"
|
41
|
+
end
|
42
|
+
|
43
|
+
result[yield(key)] = deep_transform_keys(value, &block)
|
44
|
+
end
|
45
|
+
when Array
|
46
|
+
schema.map { |e| deep_transform_keys(e, &block) }
|
47
|
+
else
|
48
|
+
schema
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
38
52
|
def validate_schema!
|
39
|
-
check_for_refs!
|
40
53
|
schema = to_h
|
41
54
|
schema_reader = JSON::Schema::Reader.new(
|
42
55
|
accept_uri: false,
|
43
56
|
accept_file: ->(path) { path.to_s.start_with?(Gem.loaded_specs["json-schema"].full_gem_path) },
|
44
57
|
)
|
45
|
-
|
58
|
+
metaschema_path = Pathname.new(JSON::Validator.validator_for_name("draft4").metaschema)
|
59
|
+
# Converts metaschema to a file URI for cross-platform compatibility
|
60
|
+
metaschema_uri = JSON::Util::URI.file_uri(metaschema_path.expand_path.cleanpath.to_s.tr("\\", "/"))
|
61
|
+
metaschema = metaschema_uri.to_s
|
46
62
|
errors = JSON::Validator.fully_validate(metaschema, schema, schema_reader: schema_reader)
|
47
63
|
if errors.any?
|
48
64
|
raise ArgumentError, "Invalid JSON Schema: #{errors.join(", ")}"
|
49
65
|
end
|
50
66
|
end
|
51
|
-
|
52
|
-
def check_for_refs!(obj = properties)
|
53
|
-
case obj
|
54
|
-
when Hash
|
55
|
-
if obj.key?("$ref") || obj.key?(:$ref)
|
56
|
-
raise ArgumentError, "Invalid JSON Schema: $ref is not allowed in tool output schemas"
|
57
|
-
end
|
58
|
-
|
59
|
-
obj.each_value { |value| check_for_refs!(value) }
|
60
|
-
when Array
|
61
|
-
obj.each { |item| check_for_refs!(item) }
|
62
|
-
end
|
63
|
-
end
|
64
67
|
end
|
65
68
|
end
|
66
69
|
end
|
data/lib/mcp/tool/response.rb
CHANGED
@@ -5,16 +5,17 @@ module MCP
|
|
5
5
|
class Response
|
6
6
|
NOT_GIVEN = Object.new.freeze
|
7
7
|
|
8
|
-
attr_reader :content
|
8
|
+
attr_reader :content, :structured_content
|
9
9
|
|
10
|
-
def initialize(content, deprecated_error = NOT_GIVEN, error: false)
|
10
|
+
def initialize(content = nil, deprecated_error = NOT_GIVEN, error: false, structured_content: 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
|
-
@content = content
|
16
|
+
@content = content || []
|
17
17
|
@error = error
|
18
|
+
@structured_content = structured_content
|
18
19
|
end
|
19
20
|
|
20
21
|
def error?
|
@@ -22,7 +23,7 @@ module MCP
|
|
22
23
|
end
|
23
24
|
|
24
25
|
def to_h
|
25
|
-
{ content:, isError: error
|
26
|
+
{ content:, isError: error?, structuredContent: @structured_content }.compact
|
26
27
|
end
|
27
28
|
end
|
28
29
|
end
|
data/lib/mcp/tool.rb
CHANGED
@@ -8,6 +8,7 @@ module MCP
|
|
8
8
|
attr_reader :title_value
|
9
9
|
attr_reader :description_value
|
10
10
|
attr_reader :annotations_value
|
11
|
+
attr_reader :meta_value
|
11
12
|
|
12
13
|
def call(*args, server_context: nil)
|
13
14
|
raise NotImplementedError, "Subclasses must implement call"
|
@@ -21,6 +22,7 @@ module MCP
|
|
21
22
|
inputSchema: input_schema_value.to_h,
|
22
23
|
outputSchema: @output_schema_value&.to_h,
|
23
24
|
annotations: annotations_value&.to_h,
|
25
|
+
_meta: meta_value,
|
24
26
|
}.compact
|
25
27
|
end
|
26
28
|
|
@@ -32,6 +34,7 @@ module MCP
|
|
32
34
|
subclass.instance_variable_set(:@input_schema_value, nil)
|
33
35
|
subclass.instance_variable_set(:@output_schema_value, nil)
|
34
36
|
subclass.instance_variable_set(:@annotations_value, nil)
|
37
|
+
subclass.instance_variable_set(:@meta_value, nil)
|
35
38
|
end
|
36
39
|
|
37
40
|
def tool_name(value = NOT_SET)
|
@@ -84,14 +87,20 @@ module MCP
|
|
84
87
|
if value == NOT_SET
|
85
88
|
output_schema_value
|
86
89
|
elsif value.is_a?(Hash)
|
87
|
-
|
88
|
-
required = value[:required] || value["required"] || []
|
89
|
-
@output_schema_value = OutputSchema.new(properties:, required:)
|
90
|
+
@output_schema_value = OutputSchema.new(value)
|
90
91
|
elsif value.is_a?(OutputSchema)
|
91
92
|
@output_schema_value = value
|
92
93
|
end
|
93
94
|
end
|
94
95
|
|
96
|
+
def meta(value = NOT_SET)
|
97
|
+
if value == NOT_SET
|
98
|
+
@meta_value
|
99
|
+
else
|
100
|
+
@meta_value = value
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
95
104
|
def annotations(hash = NOT_SET)
|
96
105
|
if hash == NOT_SET
|
97
106
|
@annotations_value
|
@@ -100,12 +109,13 @@ module MCP
|
|
100
109
|
end
|
101
110
|
end
|
102
111
|
|
103
|
-
def define(name: nil, title: nil, description: nil, input_schema: nil, output_schema: nil, annotations: nil, &block)
|
112
|
+
def define(name: nil, title: nil, description: nil, input_schema: nil, output_schema: nil, meta: nil, annotations: nil, &block)
|
104
113
|
Class.new(self) do
|
105
114
|
tool_name name
|
106
115
|
title title
|
107
116
|
description description
|
108
117
|
input_schema input_schema
|
118
|
+
meta meta
|
109
119
|
output_schema output_schema
|
110
120
|
self.annotations(annotations) if annotations
|
111
121
|
define_singleton_method(:call, &block) if block
|
data/lib/mcp/version.rb
CHANGED
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: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Model Context Protocol
|
@@ -44,12 +44,13 @@ executables: []
|
|
44
44
|
extensions: []
|
45
45
|
extra_rdoc_files: []
|
46
46
|
files:
|
47
|
-
- ".cursor/rules/release-changelogs.mdc"
|
48
47
|
- ".gitattributes"
|
48
|
+
- ".github/dependabot.yml"
|
49
49
|
- ".github/workflows/ci.yml"
|
50
50
|
- ".github/workflows/release.yml"
|
51
51
|
- ".gitignore"
|
52
52
|
- ".rubocop.yml"
|
53
|
+
- AGENTS.md
|
53
54
|
- CHANGELOG.md
|
54
55
|
- CODE_OF_CONDUCT.md
|
55
56
|
- Gemfile
|
@@ -1,17 +0,0 @@
|
|
1
|
-
---
|
2
|
-
description: Updating CHANGELOG.md before cutting a new release of the gem
|
3
|
-
globs: CHANGELOG.md
|
4
|
-
alwaysApply: false
|
5
|
-
---
|
6
|
-
|
7
|
-
- start by refreshing your knowledge on the Keep a Changelog convention by reading the format spec referenced at the top of CHANGELOG.md
|
8
|
-
- stick to Keep a Changelog
|
9
|
-
- entries should be terse and in a top-level flat list: do not nest
|
10
|
-
- follow this format for entries:
|
11
|
-
- Terse description of the change (#nnn)
|
12
|
-
- git tags are used to mark the commit that cut a new release of the gem
|
13
|
-
- the gem version is located in [version.rb](mdc:lib/mcp/version.rb)
|
14
|
-
- use the git history, especially merge commits from PRs to construct the changelog
|
15
|
-
- when necessary, look at the diff of files changed to determine the true nature of the change
|
16
|
-
- maintenance PRs that don't concern end users of the gem should not be listed in the changelog
|
17
|
-
- when checking PRs, see if there's an upstream remote, and if so, fetch PRs from upstream instead of origin
|