ruby-mcp-client 1.0.1 → 2.0.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 +234 -7
- data/lib/mcp_client/auth/oauth_provider.rb +553 -50
- data/lib/mcp_client/auth.rb +47 -9
- data/lib/mcp_client/client.rb +613 -172
- data/lib/mcp_client/elicitation_validator.rb +43 -0
- data/lib/mcp_client/errors.rb +44 -1
- data/lib/mcp_client/http_transport_base.rb +232 -38
- data/lib/mcp_client/json_rpc_common.rb +140 -12
- data/lib/mcp_client/oauth_client.rb +8 -3
- data/lib/mcp_client/prompt.rb +17 -2
- data/lib/mcp_client/resource.rb +15 -2
- data/lib/mcp_client/resource_content.rb +8 -3
- data/lib/mcp_client/resource_link.rb +16 -3
- data/lib/mcp_client/resource_template.rb +15 -2
- data/lib/mcp_client/root.rb +61 -7
- data/lib/mcp_client/schema_validator.rb +285 -0
- data/lib/mcp_client/server_base.rb +139 -0
- data/lib/mcp_client/server_http/json_rpc_transport.rb +2 -1
- data/lib/mcp_client/server_http.rb +45 -29
- data/lib/mcp_client/server_sse/json_rpc_transport.rb +67 -9
- data/lib/mcp_client/server_sse/reconnect_monitor.rb +11 -0
- data/lib/mcp_client/server_sse/sse_parser.rb +51 -11
- data/lib/mcp_client/server_sse.rb +98 -57
- data/lib/mcp_client/server_stdio/json_rpc_transport.rb +42 -9
- data/lib/mcp_client/server_stdio.rb +216 -37
- data/lib/mcp_client/server_streamable_http/json_rpc_transport.rb +149 -23
- data/lib/mcp_client/server_streamable_http.rb +249 -107
- data/lib/mcp_client/task.rb +93 -61
- data/lib/mcp_client/tool.rb +63 -10
- data/lib/mcp_client/version.rb +6 -1
- data/lib/mcp_client.rb +1 -0
- metadata +19 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5687a9c54b7e5c4814e80bd2e5e97d2072dd0f994e3bebe680259a1a9db64c25
|
|
4
|
+
data.tar.gz: 05b3bce582f21e57ae07ac862daf6a74812f3ba79f6e4b806868850686c33467
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3aef7d0842eab30de7e84a5507298efd30ae78de3ea3223cc8f00bd31892e7665f55f533952df771a2d3ab38f874d3905e029b0d19b8147d07ffd030772106fe
|
|
7
|
+
data.tar.gz: 324f5121352512f827f05fa55f78917b3218b7d031c418cfa884a8718b2b06fda027b8b811df2083361200ff17682dbfea8998505db0e2932db3a545f605c35a
|
data/README.md
CHANGED
|
@@ -28,7 +28,10 @@ Built-in API conversions: `to_openai_tools()`, `to_anthropic_tools()`, `to_googl
|
|
|
28
28
|
|
|
29
29
|
## MCP Protocol Support
|
|
30
30
|
|
|
31
|
-
Implements **MCP 2025-11-25** specification
|
|
31
|
+
Implements the **MCP 2025-11-25** specification. The client negotiates the
|
|
32
|
+
protocol version during `initialize` and disconnects if the server answers
|
|
33
|
+
with a revision it cannot speak (supported: `2025-11-25`, `2025-06-18`,
|
|
34
|
+
`2025-03-26`, `2024-11-05`):
|
|
32
35
|
|
|
33
36
|
- **Tools**: list, call, streaming, annotations (hint-style), structured outputs, title
|
|
34
37
|
- **Prompts**: list, get with parameters
|
|
@@ -38,9 +41,11 @@ Implements **MCP 2025-11-25** specification:
|
|
|
38
41
|
- **Sampling**: Server-requested LLM completions with modelPreferences
|
|
39
42
|
- **Completion**: Autocomplete for prompts/resources with context
|
|
40
43
|
- **Logging**: Server log messages with level filtering
|
|
41
|
-
- **Tasks**:
|
|
44
|
+
- **Tasks**: Task-augmented `tools/call` — create with a `ttl`, poll `tasks/get`, retrieve via `tasks/result`, plus `tasks/list` and `tasks/cancel`
|
|
42
45
|
- **Audio**: Audio content type support
|
|
43
|
-
- **
|
|
46
|
+
- **Progress & Cancellation**: `progressToken` plumbing with per-call callbacks; automatic `notifications/cancelled` for abandoned requests
|
|
47
|
+
- **Metadata**: `icons`, `title` and `_meta` parsed on tools, prompts and resources
|
|
48
|
+
- **OAuth 2.1**: PKCE (S256 required), RFC 8414/9728 discovery, dynamic registration, Client ID Metadata Documents, scope step-up challenges
|
|
44
49
|
|
|
45
50
|
## Quick Connect API (Recommended)
|
|
46
51
|
|
|
@@ -107,6 +112,11 @@ end
|
|
|
107
112
|
prompts = client.list_prompts
|
|
108
113
|
result = client.get_prompt('greeting', { name: 'Alice' })
|
|
109
114
|
|
|
115
|
+
# Pagination: list_tools and list_prompts automatically follow the server's
|
|
116
|
+
# nextCursor and return the COMPLETE set across all pages (with a per-call
|
|
117
|
+
# safety bound and an identical-cursor loop guard). No manual cursor handling
|
|
118
|
+
# is required.
|
|
119
|
+
|
|
110
120
|
# Resources
|
|
111
121
|
result = client.list_resources
|
|
112
122
|
contents = client.read_resource('file:///example.txt')
|
|
@@ -124,9 +134,12 @@ end
|
|
|
124
134
|
tool = client.find_tool('delete_user')
|
|
125
135
|
|
|
126
136
|
# Hint-style annotations (MCP 2025-11-25)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
137
|
+
# Defaults follow the MCP ToolAnnotations schema: when a hint is absent the
|
|
138
|
+
# client assumes the less-safe value, so an un-annotated tool is treated as
|
|
139
|
+
# writable, potentially destructive, and open-world.
|
|
140
|
+
tool.read_only_hint? # Defaults to false; tool may modify its environment
|
|
141
|
+
tool.destructive_hint? # Defaults to true; tool may perform destructive updates
|
|
142
|
+
tool.idempotent_hint? # Defaults to false; repeated calls may have additional effects
|
|
130
143
|
tool.open_world_hint? # Defaults to true; tool may interact with external entities
|
|
131
144
|
|
|
132
145
|
# Legacy annotations
|
|
@@ -144,6 +157,25 @@ tool.output_schema # JSON Schema for output
|
|
|
144
157
|
|
|
145
158
|
result = client.call_tool('get_weather', { location: 'SF' })
|
|
146
159
|
data = result['structuredContent'] # Type-safe structured data
|
|
160
|
+
|
|
161
|
+
# Per MCP 2025-11-25, clients SHOULD validate structured results against the
|
|
162
|
+
# tool's output schema, and a tool that declares an outputSchema must return
|
|
163
|
+
# structuredContent in successful results. call_tool checks both automatically
|
|
164
|
+
# for the common JSON Schema keywords (type, properties, required, items, enum,
|
|
165
|
+
# numeric/string bounds). The full 2020-12 vocabulary ($ref/$dynamicRef/$defs,
|
|
166
|
+
# allOf/anyOf/oneOf/not, if/then/else, additionalProperties, patternProperties,
|
|
167
|
+
# propertyNames, prefixItems, contains/minContains/maxContains, uniqueItems,
|
|
168
|
+
# multipleOf, format, dependentRequired/dependentSchemas, minProperties/
|
|
169
|
+
# maxProperties, unevaluated*) is NOT evaluated: when a schema uses any of
|
|
170
|
+
# those keywords, call_tool logs a "validation is partial" warning naming them
|
|
171
|
+
# (in both modes), since data may pass this check that a full validator would
|
|
172
|
+
# reject. By default a violation (mismatch, or missing structuredContent on a
|
|
173
|
+
# successful result) logs a warning; opt in to strict mode to raise instead:
|
|
174
|
+
client = MCPClient::Client.new(
|
|
175
|
+
mcp_server_configs: [...],
|
|
176
|
+
validate_structured_content: :strict # raises MCPClient::Errors::ValidationError on violation
|
|
177
|
+
)
|
|
178
|
+
# Task-delivered results (get_task_result) are not validated yet.
|
|
147
179
|
```
|
|
148
180
|
|
|
149
181
|
### Roots
|
|
@@ -176,6 +208,81 @@ client = MCPClient.connect('http://server/mcp',
|
|
|
176
208
|
)
|
|
177
209
|
```
|
|
178
210
|
|
|
211
|
+
Sampling tool calling (SEP-1577) is opt-in: pass `sampling_supports_tools: true`
|
|
212
|
+
to declare the `sampling.tools` capability. The handler then receives the full
|
|
213
|
+
request params (including `tools`/`toolChoice`) as an optional fifth argument;
|
|
214
|
+
without the opt-in, tool-enabled sampling requests are rejected with `-32602`
|
|
215
|
+
as the spec requires:
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
client = MCPClient::Client.new(
|
|
219
|
+
mcp_server_configs: [...],
|
|
220
|
+
sampling_supports_tools: true,
|
|
221
|
+
sampling_handler: ->(messages, prefs, system_prompt, max_tokens, params = nil) {
|
|
222
|
+
tools = params && params['tools'] # ToolUseContent may be returned in content
|
|
223
|
+
# ...
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Progress Tracking
|
|
229
|
+
|
|
230
|
+
Attach a per-call progress callback — the client generates a unique
|
|
231
|
+
`progressToken`, places it in the request `_meta`, and routes matching
|
|
232
|
+
`notifications/progress` to your block while the request is active (stale
|
|
233
|
+
tokens after completion are dropped):
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
client.call_tool('long_running', args, progress: ->(progress, total, message) {
|
|
237
|
+
puts "#{message}: #{progress}/#{total}"
|
|
238
|
+
})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
A request-level `_meta` (e.g. a hand-picked `progressToken`) can also be passed
|
|
242
|
+
inside the arguments under the `'_meta'` key on every transport — it is hoisted
|
|
243
|
+
to the JSON-RPC params level on the wire, never sent as a tool argument.
|
|
244
|
+
|
|
245
|
+
### Timeouts and Cancellation
|
|
246
|
+
|
|
247
|
+
Timeouts are configurable per request in addition to the per-server
|
|
248
|
+
`read_timeout`. A timed-out request raises
|
|
249
|
+
`MCPClient::Errors::RequestTimeoutError` (a `TransportError` subclass), is
|
|
250
|
+
**never** silently re-sent by the retry layer, and a best-effort
|
|
251
|
+
`notifications/cancelled` is sent for the abandoned request (never for
|
|
252
|
+
`initialize`, and task-augmented calls use `tasks/cancel` instead):
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
client.send_rpc('tools/call', params: { name: 'slow', arguments: {} }, timeout: 300)
|
|
256
|
+
server.rpc_request('tools/list', {}, timeout: 5)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Client Identity and Server Instructions
|
|
260
|
+
|
|
261
|
+
Hosts can present their own `Implementation` info (sent as `clientInfo` during
|
|
262
|
+
initialize; `name` and `version` required — `title`, `description`,
|
|
263
|
+
`websiteUrl`, `icons` optional), and read the server's `instructions` hint
|
|
264
|
+
after connecting:
|
|
265
|
+
|
|
266
|
+
```ruby
|
|
267
|
+
client = MCPClient::Client.new(
|
|
268
|
+
mcp_server_configs: [...],
|
|
269
|
+
client_info: { 'name' => 'my-ide', 'version' => '2.0.0', 'description' => 'An MCP-powered IDE' }
|
|
270
|
+
)
|
|
271
|
+
client.servers.first.connect
|
|
272
|
+
puts client.servers.first.instructions # e.g. "Use the search tool before answering."
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Capability Gating
|
|
276
|
+
|
|
277
|
+
Optional server features (`logging/setLevel`, `resources/subscribe`,
|
|
278
|
+
`completion/complete`, `tasks/list`, `tasks/cancel`) are only sent to servers
|
|
279
|
+
that negotiated the corresponding capability; otherwise
|
|
280
|
+
`MCPClient::Errors::CapabilityError` is raised (the lifecycle forbids using
|
|
281
|
+
capabilities that were not negotiated). `Client#log_level=` skips
|
|
282
|
+
non-logging servers instead of failing. Declared *client* capabilities are
|
|
283
|
+
derived from what the host actually registered (handlers, roots), never
|
|
284
|
+
hardcoded.
|
|
285
|
+
|
|
179
286
|
### Completion (Autocomplete)
|
|
180
287
|
|
|
181
288
|
```ruby
|
|
@@ -200,6 +307,42 @@ client.on_notification do |server, method, params|
|
|
|
200
307
|
end
|
|
201
308
|
```
|
|
202
309
|
|
|
310
|
+
### Tasks (Long-running, task-augmented tools)
|
|
311
|
+
|
|
312
|
+
A task-capable server (one advertising `tasks.requests.tools.call`) can run a tool
|
|
313
|
+
whose `execution.taskSupport` is `optional` or `required` as a background task:
|
|
314
|
+
the call returns immediately with a task handle, and the result is fetched later.
|
|
315
|
+
Try it locally: `python3 examples/echo_server_streamable.py &` then
|
|
316
|
+
`./examples/tasks_example.rb` runs the full lifecycle against a task-capable
|
|
317
|
+
demo server.
|
|
318
|
+
|
|
319
|
+
```ruby
|
|
320
|
+
tool = client.find_tool('long_job')
|
|
321
|
+
tool.supports_task? # execution.taskSupport is optional/required?
|
|
322
|
+
|
|
323
|
+
# Create the task (returns immediately); ttl is the requested lifetime in ms
|
|
324
|
+
task = client.call_tool_as_task('long_job', { input: 'data' }, ttl: 60_000)
|
|
325
|
+
|
|
326
|
+
# Poll until the task reaches a terminal (or input-required) status,
|
|
327
|
+
# honoring the server's suggested poll interval
|
|
328
|
+
until task.terminal? || task.input_required?
|
|
329
|
+
sleep((task.poll_interval || 1000) / 1000.0)
|
|
330
|
+
task = client.get_task(task.task_id) # tasks/get
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Retrieve the underlying result (e.g. a CallToolResult) via tasks/result
|
|
334
|
+
result = client.get_task_result(task.task_id)
|
|
335
|
+
|
|
336
|
+
# List and cancel tasks
|
|
337
|
+
page = client.list_tasks # { tasks: [...], next_cursor: ... }
|
|
338
|
+
client.cancel_task(task.task_id) # tasks/cancel
|
|
339
|
+
|
|
340
|
+
# React to server-pushed status updates
|
|
341
|
+
client.on_notification do |server, method, params|
|
|
342
|
+
puts "Task #{params['taskId']} -> #{params['status']}" if method == 'notifications/tasks/status'
|
|
343
|
+
end
|
|
344
|
+
```
|
|
345
|
+
|
|
203
346
|
### Elicitation (Server-initiated user interactions)
|
|
204
347
|
|
|
205
348
|
```ruby
|
|
@@ -243,6 +386,18 @@ client = MCPClient.create_client(
|
|
|
243
386
|
client = MCPClient.create_client(server_definition_file: 'servers.json')
|
|
244
387
|
```
|
|
245
388
|
|
|
389
|
+
### Retries
|
|
390
|
+
|
|
391
|
+
The `retries:` option controls automatic retry with exponential backoff. Only
|
|
392
|
+
failures where the request most likely did **not** complete at the server are
|
|
393
|
+
retried: transport/network errors and HTTP **5xx** responses. Application-level
|
|
394
|
+
failures — a JSON-RPC error response or an HTTP **4xx** — are **never** retried,
|
|
395
|
+
because the server already processed or rejected the request and re-sending
|
|
396
|
+
would risk re-executing a non-idempotent `tools/call`. Retryable server failures
|
|
397
|
+
raise `MCPClient::Errors::TransientServerError`, a subclass of
|
|
398
|
+
`MCPClient::Errors::ServerError`, so existing `rescue ServerError` handlers are
|
|
399
|
+
unaffected.
|
|
400
|
+
|
|
246
401
|
### Faraday Customization
|
|
247
402
|
|
|
248
403
|
```ruby
|
|
@@ -334,9 +489,65 @@ See `examples/` for complete implementations:
|
|
|
334
489
|
- `gemini_ai_mcp.rb` - Google Vertex AI integration
|
|
335
490
|
- `ruby_llm_mcp.rb` - RubyLLM integration (OpenAI provider)
|
|
336
491
|
|
|
492
|
+
## Running the Examples
|
|
493
|
+
|
|
494
|
+
The `examples/run_all_examples.sh` harness runs every example that can run on the current machine — self-contained stdio servers, the Python/Flask/FastMCP echo and elicitation servers, `npx`-based MCP servers, and (optionally) the paid LLM integrations. It starts and tears down each server automatically and prints a `PASS`/`FAIL`/`SKIP` summary. `tasks_example.rb` is always skipped (it needs a task-capable remote server); `oauth_browser_auth.rb` is interactive and only runs when you opt in with `RUN_OAUTH=1`.
|
|
495
|
+
|
|
496
|
+
### Prerequisites
|
|
497
|
+
|
|
498
|
+
Run `bundle install` first. The script preflight-checks the following and prints a warning (it does **not** abort) for anything missing; affected examples are then skipped or fail:
|
|
499
|
+
|
|
500
|
+
- `ruby`, `bundle`, `curl`, `lsof` - on `PATH`
|
|
501
|
+
- `python3` (or `$PYTHON`) plus a separate `python` binary - on `PATH`
|
|
502
|
+
- Python packages `flask`, `fastmcp`, `mcp` - importable by `$PYTHON`
|
|
503
|
+
- `npx` (Node) - needed by the `npx`-based example (`json_input`) and by every LLM example, which spawn `npx` filesystem/Playwright servers
|
|
504
|
+
|
|
505
|
+
### Usage
|
|
506
|
+
|
|
507
|
+
```bash
|
|
508
|
+
examples/run_all_examples.sh # run everything runnable on this machine
|
|
509
|
+
RUN_AI=0 examples/run_all_examples.sh # skip the paid-LLM examples
|
|
510
|
+
RUN_NPX=0 examples/run_all_examples.sh # skip the npx-based example (json_input)
|
|
511
|
+
LOG_DIR=/path examples/run_all_examples.sh # write logs to a chosen dir
|
|
512
|
+
PYTHON=python3.12 TIMEOUT=180 examples/run_all_examples.sh # override interpreter and per-example timeout
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### Environment Knobs
|
|
516
|
+
|
|
517
|
+
| Variable | Default | Effect |
|
|
518
|
+
|----------|---------|--------|
|
|
519
|
+
| `RUN_AI` | `1` | Set to `0` to skip the LLM integrations, which make **real, paid** API calls. |
|
|
520
|
+
| `RUN_NPX` | `1` | Set to `0` (or leave `npx` off `PATH`) to skip the `npx`-based example (`json_input`). The LLM examples spawn `npx` servers too, but are gated by `RUN_AI` and their API keys instead. |
|
|
521
|
+
| `PYTHON` | `python3` | Interpreter used to launch the Python/Flask/FastMCP servers and run the import preflight checks. |
|
|
522
|
+
| `TIMEOUT` | `120` | Per-example wall-clock timeout in seconds; a timeout is reported as a `FAIL`. |
|
|
523
|
+
| `LOG_DIR` | fresh `mktemp` dir | Directory for per-example and per-server logs; the path is printed after preflight and in the summary. |
|
|
524
|
+
|
|
525
|
+
### Secrets and API Keys
|
|
526
|
+
|
|
527
|
+
Real secrets live in `examples/secrets.env`, which is **gitignored** and sourced automatically (every `KEY=value` line is exported) when present. Copy the tracked template to get started:
|
|
528
|
+
|
|
529
|
+
```bash
|
|
530
|
+
cp examples/secrets.env.example examples/secrets.env
|
|
531
|
+
# then set ZAPIER_MCP_TOKEN=... to enable the Zapier streamable-HTTP example
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Set `ZAPIER_MCP_TOKEN` (from the Zapier MCP setup page, "Option 1: Authorization header") to run `streamable_http_example.rb` and `oauth_example.rb` against Zapier; override `ZAPIER_MCP_URL` if your connect URL differs. To run the interactive `oauth_browser_auth.rb`, set `MCP_SERVER_URL` (e.g. an ngrok tunnel to your OAuth-protected MCP server) in `secrets.env` and pass `RUN_OAUTH=1`. The LLM examples each need their own credentials in the environment and are skipped without them:
|
|
535
|
+
|
|
536
|
+
- `ruby_anthropic_mcp.rb` - `ANTHROPIC_API_KEY` (+ `npx`)
|
|
537
|
+
- `openai_ruby_mcp.rb` - `OPENAI_API_KEY` (+ `npx`)
|
|
538
|
+
- `ruby_openai_mcp.rb`, `ruby_llm_mcp.rb` - `OPENAI_API_KEY` (+ `npx`, plus a Playwright MCP server on `:8931`)
|
|
539
|
+
- `gemini_ai_mcp.rb` - a Vertex service-account JSON at `VERTEX_CREDENTIALS_FILE` (default `examples/google-credentials.json`, + `npx`)
|
|
540
|
+
|
|
541
|
+
### How Pass/Fail Is Judged
|
|
542
|
+
|
|
543
|
+
Most examples print their own success/failure marks but exit `0` regardless, so the harness combines the exit code with a scan of the output rather than trusting the exit status alone. An example `FAIL`s when it exits nonzero, times out (exit `124`), prints a hard-error signature (a Ruby/Python traceback, `Connection refused`, `uninitialized constant`, and similar), prints a `❌` mark, or is missing its expected success marker; otherwise it `PASS`es. (The `❌` check is suppressed with `IGNORE_XMARK=1` for the interactive elicitation demos, where `❌` can be legitimate "declined" output.) The script exits `0` only if zero examples failed — `SKIP`s do not affect the exit status.
|
|
544
|
+
|
|
545
|
+
For deeper, per-topic walkthroughs see [`examples/README.md`](examples/README.md), [`examples/README_ECHO_SERVER.md`](examples/README_ECHO_SERVER.md), [`examples/STREAMABLE_HTTP_TESTING.md`](examples/STREAMABLE_HTTP_TESTING.md), and [`examples/elicitation/README.md`](examples/elicitation/README.md).
|
|
546
|
+
|
|
337
547
|
## OAuth 2.1 Authentication
|
|
338
548
|
|
|
339
549
|
```ruby
|
|
550
|
+
require 'mcp_client'
|
|
340
551
|
require 'mcp_client/auth/browser_oauth'
|
|
341
552
|
|
|
342
553
|
oauth = MCPClient::Auth::OAuthProvider.new(
|
|
@@ -361,6 +572,20 @@ Features: PKCE, server discovery (`.well-known`), dynamic registration, token re
|
|
|
361
572
|
|
|
362
573
|
See [OAUTH.md](OAUTH.md) for full documentation.
|
|
363
574
|
|
|
575
|
+
### OAuth Extras (2025-11-25)
|
|
576
|
+
|
|
577
|
+
- **Client ID Metadata Documents (SEP-991)** — pass
|
|
578
|
+
`client_id_metadata_url: 'https://myapp.example/oauth-client.json'` (an HTTPS
|
|
579
|
+
URL with a path, which doubles as the `client_id`); when the authorization
|
|
580
|
+
server advertises `client_id_metadata_document_supported`, dynamic client
|
|
581
|
+
registration is skipped entirely.
|
|
582
|
+
- **Scope challenges (SEP-835)** — an HTTP 403 `insufficient_scope` challenge
|
|
583
|
+
raises `MCPClient::Errors::InsufficientScopeError` (a `ConnectionError`
|
|
584
|
+
subclass) exposing `#scope` and `#error_description`; the challenged scopes
|
|
585
|
+
are treated as authoritative for the next authorization flow.
|
|
586
|
+
- **PKCE** — authorization refuses to proceed when the authorization server
|
|
587
|
+
does not advertise `code_challenge_methods_supported` including `S256`.
|
|
588
|
+
|
|
364
589
|
## Server Notifications
|
|
365
590
|
|
|
366
591
|
```ruby
|
|
@@ -383,7 +608,9 @@ Both HTTP and Streamable HTTP transports automatically handle session-based serv
|
|
|
383
608
|
- **Session capture**: Extracts `Mcp-Session-Id` from initialize response
|
|
384
609
|
- **Session persistence**: Includes session header in subsequent requests
|
|
385
610
|
- **Session termination**: Sends DELETE request during cleanup
|
|
386
|
-
- **Resumability** (Streamable HTTP):
|
|
611
|
+
- **Resumability** (Streamable HTTP, SEP-1699): tracks SSE event IDs and, when a
|
|
612
|
+
response stream is interrupted, resumes via GET with `Last-Event-ID` so the
|
|
613
|
+
server can replay missed messages — honoring the server's `retry:` directive
|
|
387
614
|
|
|
388
615
|
No configuration required - works automatically.
|
|
389
616
|
|