mcp 0.8.0 → 0.10.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +176 -5
  3. data/lib/mcp/client/stdio.rb +222 -0
  4. data/lib/mcp/client.rb +21 -3
  5. data/lib/mcp/progress.rb +22 -0
  6. data/lib/mcp/prompt.rb +4 -0
  7. data/lib/mcp/resource.rb +3 -0
  8. data/lib/mcp/server/transports/stdio_transport.rb +6 -4
  9. data/lib/mcp/server/transports/streamable_http_transport.rb +140 -31
  10. data/lib/mcp/server/transports.rb +10 -0
  11. data/lib/mcp/server.rb +71 -39
  12. data/lib/mcp/server_context.rb +44 -0
  13. data/lib/mcp/server_session.rb +79 -0
  14. data/lib/mcp/tool.rb +5 -0
  15. data/lib/mcp/transport.rb +2 -2
  16. data/lib/mcp/version.rb +1 -1
  17. data/lib/mcp.rb +11 -24
  18. metadata +8 -36
  19. data/.gitattributes +0 -4
  20. data/.github/dependabot.yml +0 -6
  21. data/.github/workflows/ci.yml +0 -54
  22. data/.github/workflows/conformance.yml +0 -29
  23. data/.github/workflows/release.yml +0 -57
  24. data/.gitignore +0 -11
  25. data/.rubocop.yml +0 -15
  26. data/AGENTS.md +0 -107
  27. data/CHANGELOG.md +0 -168
  28. data/CODE_OF_CONDUCT.md +0 -74
  29. data/Gemfile +0 -29
  30. data/RELEASE.md +0 -12
  31. data/Rakefile +0 -56
  32. data/SECURITY.md +0 -21
  33. data/bin/console +0 -15
  34. data/bin/generate-gh-pages.sh +0 -119
  35. data/bin/rake +0 -31
  36. data/bin/setup +0 -8
  37. data/conformance/README.md +0 -103
  38. data/conformance/expected_failures.yml +0 -9
  39. data/conformance/runner.rb +0 -101
  40. data/conformance/server.rb +0 -547
  41. data/dev.yml +0 -30
  42. data/docs/_config.yml +0 -6
  43. data/docs/index.md +0 -7
  44. data/docs/latest/index.html +0 -19
  45. data/examples/README.md +0 -197
  46. data/examples/http_client.rb +0 -184
  47. data/examples/http_server.rb +0 -169
  48. data/examples/stdio_server.rb +0 -94
  49. data/examples/streamable_http_client.rb +0 -207
  50. data/examples/streamable_http_server.rb +0 -172
  51. data/mcp.gemspec +0 -35
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 149cbf6f8809235111648ffae6163abb1994cb5c2a4bd2240eb696f17197e421
4
- data.tar.gz: 7c526f405b013d868032122484079ce715ef8db9629d78dc5c8635fccaf2fc6f
3
+ metadata.gz: 29a39b8c5bb27a2fcdc8d084dce2cd79dfada5981a18d156bc1de78604035b2e
4
+ data.tar.gz: d969675cb0bb08b9ee3971a9bd90891767c7b28d68fe60579cf4857a06ff3680
5
5
  SHA512:
6
- metadata.gz: b7dcdebe8faa024fe784a894ae6383d2bcd812f41a3a492c75429034ed8f062a8da49963a94a159ce2af25928cccfbb92cb629be98ead1d751d54389e3dfc9a3
7
- data.tar.gz: 9c6f1fb122e3d636da0a7585fe38bd3ea567ba98c2899d91ef87136b69e8d0c93007c3f031915b44c835d16bbcd1465875decc459e5a34aada517541b91d369f
6
+ metadata.gz: 3ff992068b54cc35acd43bc9e93edb3959f270a19eedf52540748344f2a94488829cbfec65dedb2772d67dd883f7536523af25d6419ded2cc71944359ff2d016
7
+ data.tar.gz: 0f716e3f54ca10619f95787c65dfe01fecc2356474ddf2909dfb2918f544f3fc8aa2a1a798836b2c556a9178ad842472fd79820ad83e96e855e4ca1ded4f5d8b
data/README.md CHANGED
@@ -108,13 +108,22 @@ The server supports sending notifications to clients when lists of tools, prompt
108
108
 
109
109
  #### Notification Methods
110
110
 
111
- The server provides three notification methods:
111
+ The server provides the following notification methods:
112
112
 
113
113
  - `notify_tools_list_changed` - Send a notification when the tools list changes
114
114
  - `notify_prompts_list_changed` - Send a notification when the prompts list changes
115
115
  - `notify_resources_list_changed` - Send a notification when the resources list changes
116
116
  - `notify_log_message` - Send a structured logging notification message
117
117
 
118
+ #### Session Scoping
119
+
120
+ When using Streamable HTTP transport with multiple clients, each client connection gets its own session. Notifications are scoped as follows:
121
+
122
+ - **`report_progress`** and **`notify_log_message`** called via `server_context` inside a tool handler are automatically sent only to the requesting client.
123
+ No extra configuration is needed.
124
+ - **`notify_tools_list_changed`**, **`notify_prompts_list_changed`**, and **`notify_resources_list_changed`** are always broadcast to all connected clients,
125
+ as they represent server-wide state changes. These should be called on the `server` instance directly.
126
+
118
127
  #### Notification Format
119
128
 
120
129
  Notifications follow the JSON-RPC 2.0 specification and use these method names:
@@ -122,8 +131,58 @@ Notifications follow the JSON-RPC 2.0 specification and use these method names:
122
131
  - `notifications/tools/list_changed`
123
132
  - `notifications/prompts/list_changed`
124
133
  - `notifications/resources/list_changed`
134
+ - `notifications/progress`
125
135
  - `notifications/message`
126
136
 
137
+ ### Progress
138
+
139
+ The MCP Ruby SDK supports progress tracking for long-running tool operations,
140
+ following the [MCP Progress specification](https://modelcontextprotocol.io/specification/latest/server/utilities/progress).
141
+
142
+ #### How Progress Works
143
+
144
+ 1. **Client Request**: The client sends a `progressToken` in the `_meta` field when calling a tool
145
+ 2. **Server Notification**: The server sends `notifications/progress` messages back to the client during tool execution
146
+ 3. **Tool Integration**: Tools call `server_context.report_progress` to report incremental progress
147
+
148
+ #### Server-Side: Tool with Progress
149
+
150
+ Tools that accept a `server_context:` parameter can call `report_progress` on it.
151
+ The server automatically wraps the context in an `MCP::ServerContext` instance that provides this method:
152
+
153
+ ```ruby
154
+ class LongRunningTool < MCP::Tool
155
+ description "A tool that reports progress during execution"
156
+ input_schema(
157
+ properties: {
158
+ count: { type: "integer" },
159
+ },
160
+ required: ["count"]
161
+ )
162
+
163
+ def self.call(count:, server_context:)
164
+ count.times do |i|
165
+ # Do work here.
166
+ server_context.report_progress(i + 1, total: count, message: "Processing item #{i + 1}")
167
+ end
168
+
169
+ MCP::Tool::Response.new([{ type: "text", text: "Done" }])
170
+ end
171
+ end
172
+ ```
173
+
174
+ The `server_context.report_progress` method accepts:
175
+
176
+ - `progress` (required) — current progress value (numeric)
177
+ - `total:` (optional) — total expected value, so clients can display a percentage
178
+ - `message:` (optional) — human-readable status message
179
+
180
+ **Key Features:**
181
+
182
+ - Tools report progress via `server_context.report_progress`
183
+ - `report_progress` is a no-op when no `progressToken` was provided by the client
184
+ - Supports both numeric and string progress tokens
185
+
127
186
  ### Logging
128
187
 
129
188
  The MCP Ruby SDK supports structured logging through the `notify_log_message` method, following the [MCP Logging specification](https://modelcontextprotocol.io/specification/latest/server/utilities/logging).
@@ -228,6 +287,14 @@ Set `stateless: true` in `MCP::Server::Transports::StreamableHTTPTransport.new`
228
287
  transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, stateless: true)
229
288
  ```
230
289
 
290
+ By default, sessions do not expire. To mitigate session hijacking risks, you can set a `session_idle_timeout` (in seconds).
291
+ When configured, sessions that receive no HTTP requests for this duration are automatically expired and cleaned up:
292
+
293
+ ```ruby
294
+ # Session timeout of 30 minutes
295
+ transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, session_idle_timeout: 1800)
296
+ ```
297
+
231
298
  ### Unsupported Features (to be implemented in future versions)
232
299
 
233
300
  - Resource subscriptions
@@ -242,11 +309,12 @@ When added to a Rails controller on a route that handles POST requests, your ser
242
309
  [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports#streamable-http) transport
243
310
  requests.
244
311
 
245
- You can use the `Server#handle_json` method to handle requests.
312
+ You can use `StreamableHTTPTransport#handle_request` to handle requests with proper HTTP
313
+ status codes (e.g., 202 Accepted for notifications).
246
314
 
247
315
  ```ruby
248
- class ApplicationController < ActionController::Base
249
- def index
316
+ class McpController < ActionController::Base
317
+ def create
250
318
  server = MCP::Server.new(
251
319
  name: "my_server",
252
320
  title: "Example Server Display Name",
@@ -256,7 +324,11 @@ class ApplicationController < ActionController::Base
256
324
  prompts: [MyPrompt],
257
325
  server_context: { user_id: current_user.id },
258
326
  )
259
- render(json: server.handle_json(request.body.read))
327
+ transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
328
+ server.transport = transport
329
+ status, headers, body = transport.handle_request(request)
330
+
331
+ render(json: body.first, status: status, headers: headers)
260
332
  end
261
333
  end
262
334
  ```
@@ -375,6 +447,50 @@ server = MCP::Server.new(
375
447
 
376
448
  This hash is then passed as the `server_context` argument to tool and prompt calls, and is included in exception and instrumentation callbacks.
377
449
 
450
+ #### Request-specific `_meta` Parameter
451
+
452
+ The MCP protocol supports a special [`_meta` parameter](https://modelcontextprotocol.io/specification/2025-06-18/basic#general-fields) in requests that allows clients to pass request-specific metadata. The server automatically extracts this parameter and makes it available to tools and prompts as a nested field within the `server_context`.
453
+
454
+ **Access Pattern:**
455
+
456
+ When a client includes `_meta` in the request params, it becomes available as `server_context[:_meta]`:
457
+
458
+ ```ruby
459
+ class MyTool < MCP::Tool
460
+ def self.call(message:, server_context:)
461
+ # Access provider-specific metadata
462
+ session_id = server_context.dig(:_meta, :session_id)
463
+ request_id = server_context.dig(:_meta, :request_id)
464
+
465
+ # Access server's original context
466
+ user_id = server_context.dig(:user_id)
467
+
468
+ MCP::Tool::Response.new([{
469
+ type: "text",
470
+ text: "Processing for user #{user_id} in session #{session_id}"
471
+ }])
472
+ end
473
+ end
474
+ ```
475
+
476
+ **Client Request Example:**
477
+
478
+ ```json
479
+ {
480
+ "jsonrpc": "2.0",
481
+ "id": 1,
482
+ "method": "tools/call",
483
+ "params": {
484
+ "name": "my_tool",
485
+ "arguments": { "message": "Hello" },
486
+ "_meta": {
487
+ "session_id": "abc123",
488
+ "request_id": "req_456"
489
+ }
490
+ }
491
+ }
492
+ ```
493
+
378
494
  #### Configuration Block Data
379
495
 
380
496
  ##### Exception Reporter
@@ -968,6 +1084,52 @@ class CustomTransport
968
1084
  end
969
1085
  ```
970
1086
 
1087
+ ### Stdio Transport Layer
1088
+
1089
+ Use the `MCP::Client::Stdio` transport to interact with MCP servers running as subprocesses over standard input/output.
1090
+
1091
+ `MCP::Client::Stdio.new` accepts the following keyword arguments:
1092
+
1093
+ | Parameter | Required | Description |
1094
+ |---|---|---|
1095
+ | `command:` | Yes | The command to spawn the server process (e.g., `"ruby"`, `"bundle"`, `"npx"`). |
1096
+ | `args:` | No | An array of arguments passed to the command. Defaults to `[]`. |
1097
+ | `env:` | No | A hash of environment variables to set for the server process. Defaults to `nil`. |
1098
+ | `read_timeout:` | No | Timeout in seconds for waiting for a server response. Defaults to `nil` (no timeout). |
1099
+
1100
+ Example usage:
1101
+
1102
+ ```ruby
1103
+ stdio_transport = MCP::Client::Stdio.new(
1104
+ command: "bundle",
1105
+ args: ["exec", "ruby", "path/to/server.rb"],
1106
+ env: { "API_KEY" => "my_secret_key" },
1107
+ read_timeout: 30
1108
+ )
1109
+ client = MCP::Client.new(transport: stdio_transport)
1110
+
1111
+ # List available tools.
1112
+ tools = client.tools
1113
+ tools.each do |tool|
1114
+ puts "Tool: #{tool.name} - #{tool.description}"
1115
+ end
1116
+
1117
+ # Call a specific tool.
1118
+ response = client.call_tool(
1119
+ tool: tools.first,
1120
+ arguments: { message: "Hello, world!" }
1121
+ )
1122
+
1123
+ # Close the transport when done.
1124
+ stdio_transport.close
1125
+ ```
1126
+
1127
+ The stdio transport automatically handles:
1128
+
1129
+ - Spawning the server process with `Open3.popen3`
1130
+ - MCP protocol initialization handshake (`initialize` request + `notifications/initialized`)
1131
+ - JSON-RPC 2.0 message framing over newline-delimited JSON
1132
+
971
1133
  ### HTTP Transport Layer
972
1134
 
973
1135
  Use the `MCP::Client::HTTP` transport to interact with MCP servers using simple HTTP requests.
@@ -1000,8 +1162,17 @@ response = client.call_tool(
1000
1162
  tool: tools.first,
1001
1163
  arguments: { message: "Hello, world!" }
1002
1164
  )
1165
+
1166
+ # Call a tool with progress tracking.
1167
+ response = client.call_tool(
1168
+ tool: tools.first,
1169
+ arguments: { count: 10 },
1170
+ progress_token: "my-progress-token"
1171
+ )
1003
1172
  ```
1004
1173
 
1174
+ The server will send `notifications/progress` back to the client during execution.
1175
+
1005
1176
  #### HTTP Authorization
1006
1177
 
1007
1178
  By default, the HTTP transport layer provides no authentication to the server, but you can provide custom headers if you need authentication. For example, to use Bearer token authentication:
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "open3"
5
+ require "securerandom"
6
+ require "timeout"
7
+ require_relative "../../json_rpc_handler"
8
+ require_relative "../configuration"
9
+ require_relative "../methods"
10
+ require_relative "../version"
11
+
12
+ module MCP
13
+ class Client
14
+ class Stdio
15
+ # Seconds to wait for the server process to exit before sending SIGTERM.
16
+ # Matches the Python and TypeScript SDKs' shutdown timeout:
17
+ # https://github.com/modelcontextprotocol/python-sdk/blob/v1.26.0/src/mcp/client/stdio/__init__.py#L48
18
+ # https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.27.1/src/client/stdio.ts#L221
19
+ CLOSE_TIMEOUT = 2
20
+ STDERR_READ_SIZE = 4096
21
+
22
+ attr_reader :command, :args, :env
23
+
24
+ def initialize(command:, args: [], env: nil, read_timeout: nil)
25
+ @command = command
26
+ @args = args
27
+ @env = env
28
+ @read_timeout = read_timeout
29
+ @stdin = nil
30
+ @stdout = nil
31
+ @stderr = nil
32
+ @wait_thread = nil
33
+ @stderr_thread = nil
34
+ @started = false
35
+ @initialized = false
36
+ end
37
+
38
+ def send_request(request:)
39
+ start unless @started
40
+ initialize_session unless @initialized
41
+
42
+ write_message(request)
43
+ read_response(request)
44
+ end
45
+
46
+ def start
47
+ raise "MCP::Client::Stdio already started" if @started
48
+
49
+ spawn_env = @env || {}
50
+ @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(spawn_env, @command, *@args)
51
+ @stdout.set_encoding("UTF-8")
52
+ @stdin.set_encoding("UTF-8")
53
+
54
+ # Drain stderr in the background to prevent the pipe buffer from filling up,
55
+ # which would cause the server process to block and deadlock.
56
+ @stderr_thread = Thread.new do
57
+ loop do
58
+ @stderr.readpartial(STDERR_READ_SIZE)
59
+ end
60
+ rescue IOError
61
+ nil
62
+ end
63
+
64
+ @started = true
65
+ rescue Errno::ENOENT, Errno::EACCES, Errno::ENOEXEC => e
66
+ raise RequestHandlerError.new(
67
+ "Failed to spawn server process: #{e.message}",
68
+ {},
69
+ error_type: :internal_error,
70
+ original_error: e,
71
+ )
72
+ end
73
+
74
+ def close
75
+ return unless @started
76
+
77
+ @stdin.close
78
+ @stdout.close
79
+ @stderr.close
80
+
81
+ begin
82
+ Timeout.timeout(CLOSE_TIMEOUT) { @wait_thread.value }
83
+ rescue Timeout::Error
84
+ begin
85
+ Process.kill("TERM", @wait_thread.pid)
86
+ Timeout.timeout(CLOSE_TIMEOUT) { @wait_thread.value }
87
+ rescue Timeout::Error
88
+ begin
89
+ Process.kill("KILL", @wait_thread.pid)
90
+ rescue Errno::ESRCH
91
+ nil
92
+ end
93
+ rescue Errno::ESRCH
94
+ nil
95
+ end
96
+ end
97
+
98
+ @stderr_thread.join(CLOSE_TIMEOUT)
99
+ @started = false
100
+ @initialized = false
101
+ end
102
+
103
+ private
104
+
105
+ # The client MUST send a protocol version it supports. This SHOULD be the latest version.
106
+ # https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#version-negotiation
107
+ #
108
+ # Always sends `LATEST_STABLE_PROTOCOL_VERSION`, matching the Python and TypeScript SDKs:
109
+ # https://github.com/modelcontextprotocol/python-sdk/blob/v1.26.0/src/mcp/client/session.py#L175
110
+ # https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.27.1/src/client/index.ts#L495
111
+ def initialize_session
112
+ init_request = {
113
+ jsonrpc: JsonRpcHandler::Version::V2_0,
114
+ id: SecureRandom.uuid,
115
+ method: MCP::Methods::INITIALIZE,
116
+ params: {
117
+ protocolVersion: MCP::Configuration::LATEST_STABLE_PROTOCOL_VERSION,
118
+ capabilities: {},
119
+ clientInfo: { name: "mcp-ruby-client", version: MCP::VERSION },
120
+ },
121
+ }
122
+
123
+ write_message(init_request)
124
+ response = read_response(init_request)
125
+
126
+ if response.key?("error")
127
+ error = response["error"]
128
+ raise RequestHandlerError.new(
129
+ "Server initialization failed: #{error["message"]}",
130
+ { method: MCP::Methods::INITIALIZE },
131
+ error_type: :internal_error,
132
+ )
133
+ end
134
+
135
+ unless response.key?("result")
136
+ raise RequestHandlerError.new(
137
+ "Server initialization failed: missing result in response",
138
+ { method: MCP::Methods::INITIALIZE },
139
+ error_type: :internal_error,
140
+ )
141
+ end
142
+
143
+ notification = {
144
+ jsonrpc: JsonRpcHandler::Version::V2_0,
145
+ method: MCP::Methods::NOTIFICATIONS_INITIALIZED,
146
+ }
147
+ write_message(notification)
148
+
149
+ @initialized = true
150
+ end
151
+
152
+ def write_message(message)
153
+ ensure_running!
154
+ json = JSON.generate(message)
155
+ @stdin.puts(json)
156
+ @stdin.flush
157
+ rescue IOError, Errno::EPIPE => e
158
+ raise RequestHandlerError.new(
159
+ "Failed to write to server process",
160
+ {},
161
+ error_type: :internal_error,
162
+ original_error: e,
163
+ )
164
+ end
165
+
166
+ def read_response(request)
167
+ request_id = request[:id] || request["id"]
168
+ method = request[:method] || request["method"]
169
+ params = request[:params] || request["params"]
170
+
171
+ loop do
172
+ ensure_running!
173
+ wait_for_readable!(method, params) if @read_timeout
174
+ line = @stdout.gets
175
+ raise_connection_error!(method, params) if line.nil?
176
+
177
+ parsed = JSON.parse(line.strip)
178
+
179
+ next unless parsed.key?("id")
180
+
181
+ return parsed if parsed["id"] == request_id
182
+ end
183
+ rescue JSON::ParserError => e
184
+ raise RequestHandlerError.new(
185
+ "Failed to parse server response",
186
+ { method: method, params: params },
187
+ error_type: :internal_error,
188
+ original_error: e,
189
+ )
190
+ end
191
+
192
+ def ensure_running!
193
+ return if @wait_thread.alive?
194
+
195
+ raise RequestHandlerError.new(
196
+ "Server process has exited",
197
+ {},
198
+ error_type: :internal_error,
199
+ )
200
+ end
201
+
202
+ def wait_for_readable!(method, params)
203
+ ready = @stdout.wait_readable(@read_timeout)
204
+ return if ready
205
+
206
+ raise RequestHandlerError.new(
207
+ "Timed out waiting for server response",
208
+ { method: method, params: params },
209
+ error_type: :internal_error,
210
+ )
211
+ end
212
+
213
+ def raise_connection_error!(method, params)
214
+ raise RequestHandlerError.new(
215
+ "Server process closed stdout unexpectedly",
216
+ { method: method, params: params },
217
+ error_type: :internal_error,
218
+ )
219
+ end
220
+ end
221
+ end
222
+ end
data/lib/mcp/client.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "client/stdio"
4
+ require_relative "client/http"
5
+ require_relative "client/tool"
6
+
3
7
  module MCP
4
8
  class Client
5
9
  # Initializes a new MCP::Client instance.
@@ -88,11 +92,17 @@ module MCP
88
92
 
89
93
  # Calls a tool via the transport layer and returns the full response from the server.
90
94
  #
95
+ # @param name [String] The name of the tool to call.
91
96
  # @param tool [MCP::Client::Tool] The tool to be called.
92
97
  # @param arguments [Object, nil] The arguments to pass to the tool.
98
+ # @param progress_token [String, Integer, nil] A token to request progress notifications from the server during tool execution.
93
99
  # @return [Hash] The full JSON-RPC response from the transport.
94
100
  #
95
- # @example
101
+ # @example Call by name
102
+ # response = client.call_tool(name: "my_tool", arguments: { foo: "bar" })
103
+ # content = response.dig("result", "content")
104
+ #
105
+ # @example Call with a tool object
96
106
  # tool = client.tools.first
97
107
  # response = client.call_tool(tool: tool, arguments: { foo: "bar" })
98
108
  # structured_content = response.dig("result", "structuredContent")
@@ -100,12 +110,20 @@ module MCP
100
110
  # @note
101
111
  # The exact requirements for `arguments` are determined by the transport layer in use.
102
112
  # Consult the documentation for your transport (e.g., MCP::Client::HTTP) for details.
103
- def call_tool(tool:, arguments: nil)
113
+ def call_tool(name: nil, tool: nil, arguments: nil, progress_token: nil)
114
+ tool_name = name || tool&.name
115
+ raise ArgumentError, "Either `name:` or `tool:` must be provided." unless tool_name
116
+
117
+ params = { name: tool_name, arguments: arguments }
118
+ if progress_token
119
+ params[:_meta] = { progressToken: progress_token }
120
+ end
121
+
104
122
  transport.send_request(request: {
105
123
  jsonrpc: JsonRpcHandler::Version::V2_0,
106
124
  id: request_id,
107
125
  method: "tools/call",
108
- params: { name: tool.name, arguments: arguments },
126
+ params: params,
109
127
  })
110
128
  end
111
129
 
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MCP
4
+ class Progress
5
+ def initialize(notification_target:, progress_token:)
6
+ @notification_target = notification_target
7
+ @progress_token = progress_token
8
+ end
9
+
10
+ def report(progress, total: nil, message: nil)
11
+ return unless @progress_token
12
+ return unless @notification_target
13
+
14
+ @notification_target.notify_progress(
15
+ progress_token: @progress_token,
16
+ progress: progress,
17
+ total: total,
18
+ message: message,
19
+ )
20
+ end
21
+ end
22
+ end
data/lib/mcp/prompt.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "prompt/argument"
4
+ require_relative "prompt/message"
5
+ require_relative "prompt/result"
6
+
3
7
  module MCP
4
8
  class Prompt
5
9
  class << self
data/lib/mcp/resource.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "resource/contents"
4
+ require_relative "resource/embedded"
5
+
3
6
  module MCP
4
7
  class Resource
5
8
  attr_reader :uri, :name, :title, :description, :icons, :mime_type
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../../transport"
4
3
  require "json"
4
+ require_relative "../../transport"
5
5
 
6
6
  module MCP
7
7
  class Server
@@ -10,17 +10,19 @@ module MCP
10
10
  STATUS_INTERRUPTED = Signal.list["INT"] + 128
11
11
 
12
12
  def initialize(server)
13
- @server = server
13
+ super(server)
14
14
  @open = false
15
+ @session = nil
15
16
  $stdin.set_encoding("UTF-8")
16
17
  $stdout.set_encoding("UTF-8")
17
- super
18
18
  end
19
19
 
20
20
  def open
21
21
  @open = true
22
+ @session = ServerSession.new(server: @server, transport: self)
22
23
  while @open && (line = $stdin.gets)
23
- handle_json_request(line.strip)
24
+ response = @session.handle_json(line.strip)
25
+ send_response(response) if response
24
26
  end
25
27
  rescue Interrupt
26
28
  warn("\nExiting...")