mcp 1.1.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 356017978ae1c4a455d50d37bd9a178aba8989ee5e001bed8ab309977071f71f
4
- data.tar.gz: cc62cd7e64421b3a45c49d60709610cf0bf552183b4a2f08c2adef9e0bde2e29
3
+ metadata.gz: 69ef263cadd9221259fedfea19655d64cdecd7175deca3139439577b48fb10b2
4
+ data.tar.gz: 4580aafd0866efce3f102a701d244dd67af7b8881b19306d4b81ab2701a7350a
5
5
  SHA512:
6
- metadata.gz: 5464fcaa9535b838cd7b3bccffa7c9d4f461cb4ca91ac08067d9f9fa97bde4124bd9214c57c6d7928ce4315135dcabcfff445d48b24f325f0acaace0b8af5fc9
7
- data.tar.gz: 118dba080f17a6464892b4d65a57e147ffe405539457940a7f25184ead416af03accf4ac27809e5b4c0fb2d1a6942e58edcc8f73b71c88fc364bf658861353f0
6
+ metadata.gz: a5aae701572da5725edf8e02ba8da55debfc702183b123813aba22ca005bf3ee1e61193543975c111065b49e5eddac520600fc775a026fc04d5b28050d22313b
7
+ data.tar.gz: bd8a9bce3c67b2f4db31bd0bb58b85e9417c997be075f07a9e597a2819facc922c8cea9796fe9ed543b498ec9e3d080fa24b9ce0246e660d5dc875c94b995c0e
data/README.md CHANGED
@@ -46,8 +46,38 @@ It implements the Model Context Protocol specification, handling model context r
46
46
  ### Supported Methods
47
47
 
48
48
  - `initialize` - Initializes the protocol and returns server capabilities
49
- - `server/discover` - Sessionless capability discovery (MCP 2026-07-28 draft, SEP-2575): returns `supportedVersions`, `capabilities`, `serverInfo`,
50
- and `instructions`, and responds before `initialize` and without an `Mcp-Session-Id`
49
+ - `server/discover` - Sessionless capability discovery (MCP 2026-07-28, SEP-2575): returns the modern `supportedVersions`,
50
+ `capabilities`, `instructions`, the required `ttlMs`/`cacheScope` cache hints, and the server identity as the optional
51
+ `io.modelcontextprotocol/serverInfo` stamp in the result `_meta`, and responds before `initialize`
52
+ and without an `Mcp-Session-Id`. The server also serves the full stateless modern lifecycle: requests carrying the SEP-2575 `_meta` envelope
53
+ (`io.modelcontextprotocol/protocolVersion`, `clientInfo`, and `clientCapabilities`) are validated per request,
54
+ and the Streamable HTTP transport serves them on a sessionless single-exchange path. On the client, `MCP::Client#connect` negotiates
55
+ the lifecycle automatically by default (probe `server/discover`, fall back to the `initialize` handshake), `connect(mode: :modern)` skips
56
+ the handshake entirely, `connect(mode: :legacy)` forces the classic handshake, and `MCP::Client#discover` exposes the raw discovery result
57
+ - `subscriptions/listen` - Long-lived notification subscription stream (MCP 2026-07-28, SEP-2575), replacing the legacy HTTP GET listening stream:
58
+ the client opts in via the `notifications` filter (`toolsListChanged` / `promptsListChanged` / `resourcesListChanged` / `resourceSubscriptions`),
59
+ the server acknowledges the honored subset with `notifications/subscriptions/acknowledged` as the first stream message,
60
+ and every delivered notification carries the correlating `io.modelcontextprotocol/subscriptionId` in `_meta`. Served on the Streamable HTTP modern path;
61
+ stdio answers `-32601`. Concurrent streams are capped by `max_listen_subscriptions:` (default 1000), and each stream receives an SSE keepalive
62
+ comment frame every `listen_keepalive_interval:` seconds (default 15) so a dropped connection frees its slot; pass `listen_keepalive_interval: nil`
63
+ when an upstream proxy already keeps the stream alive
64
+ - Multi round-trip `input_required` results (MCP 2026-07-28, SEP-2322): a `tools/call`, `prompts/get`, or `resources/read` handler that
65
+ opts in to `server_context:` may return `MCP::Server::InputRequiredResult.new(input_requests:, request_state:)` to ask the client for
66
+ additional input (`elicitation/create`, `sampling/createMessage`, or `roots/list` shapes) instead of performing a server-initiated request,
67
+ which the modern lifecycle forbids. On the retried request the handler re-runs from the start and reads the answers via
68
+ `server_context.input_responses` / `server_context.input_response(key)` and the echoed opaque `server_context.request_state`
69
+ (deterministic replay; the server holds no memory between rounds). The SDK rejects issuance on legacy requests and returns `-32021`
70
+ when an embedded request needs a client capability the request did not declare. The echoed `requestState` arrives as
71
+ client-controlled input: pass `MCP::Server::RequestStateSecurity.new(key:)` (a 32-byte key) via `Server.new(request_state_security:)` to
72
+ have it sealed with AES-256-GCM and bound to a TTL plus the originating method, target, and arguments, all transparently to handlers.
73
+ Multi-process deployments must share the key across workers; without `request_state_security:` the state crosses the wire exactly as
74
+ the handler wrote it and protecting it is the handler author's responsibility. On the client, register handlers with
75
+ `on_elicitation` / `on_sampling` / `on_roots` - the same registrations that answer a real server-to-client request - and
76
+ declare the matching capabilities on `connect` (a server embeds only the request kinds the client declared);
77
+ `call_tool` / `get_prompt` / `read_resource` then resume `input_required` results automatically: each embedded request is fulfilled by
78
+ the matching handler and the original request is re-issued with `inputResponses` plus the echoed `requestState`
79
+ (with exponential backoff for `requestState`-only load-shedding legs). Without a matching handler they raise `MCP::Client::InputRequiredError`,
80
+ and the `input_responses:` / `request_state:` keyword arguments support manual driving
51
81
  - `ping` - Simple health check
52
82
  - `logging/setLevel` - Configures the minimum log level for the server
53
83
  - `tools/list` - Lists all registered tools and their schemas
@@ -346,7 +376,8 @@ server = MCP::Server.new(
346
376
  ```
347
377
 
348
378
  This hash is then passed as the `server_context` keyword argument to tool and prompt calls.
349
- Note that exception and instrumentation callbacks do not receive this user-defined hash.
379
+ Note that the exception reporter does not receive this user-defined hash, and instrumentation
380
+ callbacks omit it unless you opt in with `instrument_server_context`.
350
381
  See the relevant sections below for the arguments they receive.
351
382
 
352
383
  #### Request-specific `_meta` Parameter
@@ -460,10 +491,31 @@ around_request = ->(data, &request_handler) { request_handler.call }
460
491
 
461
492
  **`data` availability by timing:**
462
493
 
463
- - Before `request_handler.call`: `method`
494
+ - Before `request_handler.call`: `method`, and `server_context` when `instrument_server_context` is enabled
464
495
  - After `request_handler.call`: `tool_name`, `tool_arguments`, `prompt_name`, `resource_uri`, `error`, `client`
465
496
  - Not available inside `around_request`: `duration` (added after `around_request` returns)
466
497
 
498
+ **Exposing the user-defined `server_context` (opt in):**
499
+
500
+ `data` omits the user-defined `server_context` by default, because that hash is
501
+ application-supplied and may hold values a tracing backend should not receive.
502
+ Enable it when you need to tag spans with the request's subject:
503
+
504
+ ```ruby
505
+ MCP.configure do |config|
506
+ config.instrument_server_context = true
507
+
508
+ config.around_request = ->(data, &request_handler) {
509
+ Sentry.set_user(id: data.dig(:server_context, :user_id))
510
+ request_handler.call
511
+ }
512
+ end
513
+ ```
514
+
515
+ `data[:server_context]` is the hash passed to `Server.new` — `nil` when the host
516
+ set none. It is not the exception reporter's context argument, which describes
517
+ where a failure occurred rather than who made the request.
518
+
467
519
  > [!NOTE]
468
520
  > `tool_name`, `prompt_name` and `resource_uri` may only be populated for the corresponding request methods
469
521
  > (`tools/call`, `prompts/get`, `resources/read`), and may not be set depending on how the request is handled
@@ -515,6 +567,8 @@ It receives a hash with the following possible keys:
515
567
  - `error`: (String, optional) Error code if a lookup failed
516
568
  - `duration`: (Float) Duration of the call in seconds
517
569
  - `client`: (Hash, optional) Client information with `name` and `version` keys, from the initialize request
570
+ - `server_context`: (Any, optional) The user-defined hash passed to `Server.new`, present only when
571
+ `instrument_server_context` is enabled
518
572
 
519
573
  **Signature:**
520
574
 
@@ -531,8 +585,7 @@ configuration = MCP::Configuration.new(protocol_version: "2024-11-05")
531
585
  MCP::Server.new(name: "test_server", configuration: configuration)
532
586
  ```
533
587
 
534
- If no protocol version is specified, the latest stable version will be applied by default.
535
- The latest stable version includes new features from the [draft version](https://modelcontextprotocol.io/specification/draft).
588
+ If no protocol version is specified, the latest handshake version (`2025-11-25`) is applied by default.
536
589
 
537
590
  This will make all new server instances use the specified protocol version instead of the default version. The protocol version can be reset to the default by setting it to `nil`:
538
591
 
@@ -542,6 +595,11 @@ MCP::Configuration.new(protocol_version: nil)
542
595
 
543
596
  If an invalid `protocol_version` value is set, an `ArgumentError` is raised.
544
597
 
598
+ The pin scopes the `initialize` handshake, so it accepts handshake versions (`2025-11-25` and earlier) only. Per the SEP-2575 era model,
599
+ `2026-07-28` carries its version on every request and has no handshake at all, so there is nothing for a pin to configure there and setting it raises `ArgumentError`;
600
+ a client asking `initialize` for a modern version is counter-offered the pinned version (or the latest handshake version), matching the TypeScript and Python SDKs.
601
+ Clients reach `2026-07-28` through `server/discover` and the per-request `_meta` envelope, which the bundled transports serve alongside the handshake with no configuration needed.
602
+
545
603
  Be sure to check the [MCP spec](https://modelcontextprotocol.io/specification/versioning) for the protocol version to understand the supported features for the version being set.
546
604
 
547
605
  ### Exception Reporting
@@ -1361,6 +1419,22 @@ Roots define the boundaries of where a server can operate, providing a list of d
1361
1419
  > automatically and routes the request onto the originating POST stream on the Streamable HTTP transport. Calling the corresponding
1362
1420
  > `ServerSession` methods without `related_request_id:` still works but emits a deprecation warning.
1363
1421
 
1422
+ **Timeouts:** every server-to-client request is bounded, so a client that never answers cannot park the handler's thread indefinitely.
1423
+ `MCP::Server::Transports::StreamableHTTPTransport` waits `server_to_client_request_timeout:` seconds (600 by default), then tells
1424
+ the client the request was abandoned and raises `MCP::Server::RequestTimeoutError`. Individual calls override the deadline with `timeout:`,
1425
+ which is the knob to reach for when a prompt legitimately waits on a person:
1426
+
1427
+ ```ruby
1428
+ server_context.create_form_elicitation(
1429
+ message: "Approve this deployment?",
1430
+ requested_schema: { type: "object", properties: { approved: { type: "boolean" } } },
1431
+ timeout: 3600, # This one waits up to an hour.
1432
+ )
1433
+ ```
1434
+
1435
+ `StdioTransport` is not bounded and ignores `timeout:`: it owns the client process, so a client that stops answering
1436
+ surfaces as end-of-file rather than as a wait that never ends.
1437
+
1364
1438
  **Using Roots in Tools:**
1365
1439
 
1366
1440
  Tools that accept a `server_context:` parameter can call `list_roots` on it.
@@ -1970,6 +2044,64 @@ server.define_tool(name: "configure_deploy", description: "Configure a deploymen
1970
2044
  end
1971
2045
  ```
1972
2046
 
2047
+ #### Enum Schemas
2048
+
2049
+ For enumerated choices, use `MCP::Elicitation::EnumSchema` to construct the canonical schema shapes per
2050
+ [SEP-1330](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330) instead of building
2051
+ the underlying Hash by hand. The five class methods cover titled and untitled, single-select and multi-select,
2052
+ plus the legacy `enumNames` form retained for backward compatibility:
2053
+
2054
+ ```ruby
2055
+ size_schema = MCP::Elicitation::EnumSchema.titled_single_select(
2056
+ options: [
2057
+ { value: "s", title: "Small" },
2058
+ { value: "m", title: "Medium" },
2059
+ { value: "l", title: "Large" },
2060
+ ],
2061
+ default: "m",
2062
+ )
2063
+
2064
+ tags_schema = MCP::Elicitation::EnumSchema.untitled_multi_select(
2065
+ values: ["urgent", "billing", "feedback"],
2066
+ )
2067
+
2068
+ result = server_context.create_form_elicitation(
2069
+ message: "Tell us about your order",
2070
+ requested_schema: {
2071
+ type: "object",
2072
+ properties: {
2073
+ size: size_schema.to_h,
2074
+ tags: tags_schema.to_h,
2075
+ },
2076
+ required: ["size"],
2077
+ },
2078
+ )
2079
+ ```
2080
+
2081
+ The available builders are `untitled_single_select`, `titled_single_select`, `untitled_multi_select`, `titled_multi_select`,
2082
+ and `legacy_titled`. Each accepts optional `default:`, `title:`, and `description:`.
2083
+
2084
+ The same builders produce the `requestedSchema` of an `elicitation/create` request embedded in a SEP-2322 `input_required` result,
2085
+ which is how elicitation reaches clients on the stateless 2026-07-28 lifecycle:
2086
+
2087
+ ```ruby
2088
+ MCP::Server::InputRequiredResult.new(
2089
+ input_requests: {
2090
+ "size" => {
2091
+ method: "elicitation/create",
2092
+ params: {
2093
+ message: "Pick a size",
2094
+ requestedSchema: {
2095
+ type: "object",
2096
+ properties: { size: size_schema.to_h },
2097
+ required: ["size"],
2098
+ },
2099
+ },
2100
+ },
2101
+ },
2102
+ )
2103
+ ```
2104
+
1973
2105
  #### URL Mode
1974
2106
 
1975
2107
  URL mode directs the user to an external URL for out-of-band interactions such as OAuth flows:
@@ -2098,6 +2230,18 @@ transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
2098
2230
  # When tools change, notify clients
2099
2231
  server.define_tool(name: "new_tool") { |**args| { result: "ok" } }
2100
2232
  server.notify_tools_list_changed
2233
+
2234
+ # When prompts change, notify clients
2235
+ server.define_prompt(name: "new_prompt") do |args, server_context:|
2236
+ MCP::Prompt::Result.new(messages: [])
2237
+ end
2238
+ server.notify_prompts_list_changed
2239
+
2240
+ # When resources change, notify clients
2241
+ server.define_resource(uri: "resource://new", name: "new_resource", mime_type: "text/plain") do
2242
+ [MCP::Resource::TextContents.new(uri: "resource://new", mime_type: "text/plain", text: "contents")]
2243
+ end
2244
+ server.notify_resources_list_changed
2101
2245
  ```
2102
2246
 
2103
2247
  You can use Stateless Streamable HTTP, where notifications are not supported and all calls are request/response interactions.
@@ -2258,13 +2402,18 @@ If your application needs the complete collection regardless of how the server i
2258
2402
  `client.tools`, `client.resources`, `client.resource_templates`, and `client.prompts` auto-iterate
2259
2403
  through all pages and return a plain array of items, guaranteeing the full collection regardless
2260
2404
  of the server's `page_size` setting. When a server paginates, they issue multiple JSON-RPC round
2261
- trips per call and break out of the pagination loop if the server returns the same `nextCursor`
2262
- twice in a row as a safety measure.
2405
+ trips per call. Two guards keep that loop finite: it stops when the server returns a `nextCursor`
2406
+ it has already sent, and it stops after `max_pages` pages.
2263
2407
 
2264
2408
  ```ruby
2265
2409
  tools = client.tools # => Array<MCP::Client::Tool> of every tool on the server.
2266
2410
  ```
2267
2411
 
2412
+ `MCP::Client.new` accepts an optional `max_pages:` keyword that caps how many pages these methods
2413
+ will walk. It defaults to `1_000`; a server that keeps offering a fresh `nextCursor` past that
2414
+ point raises `MCP::Client::PaginationLimitError` rather than being followed indefinitely. Raise it
2415
+ if you legitimately expect more pages than that.
2416
+
2268
2417
  Use these when you want the complete list; use `list_tools(cursor:)` etc. when you need
2269
2418
  fine-grained iteration (e.g. to stream-process pages without loading everything into memory).
2270
2419
 
@@ -2274,8 +2423,11 @@ Per SEP-2549, list and read results can carry cache hints telling clients how lo
2274
2423
  `0` means do not cache) and whether shared intermediaries may cache it (`cacheScope`: `"public"` or `"private"`).
2275
2424
 
2276
2425
  Emission is opt-in: pass `ttl_ms:` and/or `cache_scope:` to `MCP::Server.new` and both fields are added to `tools/list`, `prompts/list`, `resources/list`,
2277
- `resources/templates/list`, and `resources/read` results (a missing field is filled with the defaults `ttlMs: 0` / `cacheScope: "public"`).
2426
+ `resources/templates/list`, and `resources/read` results (a missing field is filled with the defaults `ttlMs: 0` / `cacheScope: "private"`,
2427
+ the scope that keeps a potentially user-dependent result out of shared caches).
2278
2428
  When neither is set, responses are serialized exactly as before.
2429
+ The 2026-07-28 revision makes both hints required on these results, so on requests carrying the modern `_meta` envelope
2430
+ the server always emits them, filling unset values with the same defaults; stable protocol versions keep the opt-in behavior.
2279
2431
 
2280
2432
  ```ruby
2281
2433
  server = MCP::Server.new(
@@ -2376,6 +2528,55 @@ This class supports:
2376
2528
  Clients are initialized with a transport layer instance that handles the low-level communication mechanics.
2377
2529
  Authorization is handled by the transport layer.
2378
2530
 
2531
+ ### Lifecycle Negotiation (SEP-2575)
2532
+
2533
+ `MCP::Client#connect` selects the protocol lifecycle automatically by default: on the bundled
2534
+ `MCP::Client::HTTP` and `MCP::Client::Stdio` transports it probes `server/discover` first and adopts
2535
+ the stateless modern lifecycle (MCP 2026-07-28) when the server serves it, falling back to
2536
+ the classic `initialize` handshake otherwise. Custom transports whose `connect` does not declare
2537
+ a `mode:` keyword always receive the classic call shape, unchanged.
2538
+
2539
+ ```ruby
2540
+ client.connect # negotiate automatically (default)
2541
+ client.connect(mode: :legacy) # force the classic initialize handshake
2542
+ client.connect(mode: :modern) # require the modern lifecycle; fails on legacy-only servers
2543
+ client.connect(protocol_version: "2025-11-25") # an explicit legacy version pins the handshake, no probe
2544
+ ```
2545
+
2546
+ Prefer `mode: :legacy` for spawn-per-invocation CLI tools (the probe adds a round trip per process)
2547
+ and when using server-initiated requests (`on_elicitation` / `on_sampling`), which exist only on
2548
+ the legacy lifecycle.
2549
+
2550
+ Because the raw `connect` return value and `MCP::Client#server_info` mirror the wire result,
2551
+ their shape depends on the negotiated lifecycle: `InitializeResult` (`protocolVersion`,
2552
+ top-level `serverInfo`) on legacy, `DiscoverResult` (`supportedVersions`, `ttlMs`/`cacheScope`)
2553
+ on modern. Code that should work against both lifecycles can use the era-independent readers instead:
2554
+
2555
+ ```ruby
2556
+ client.protocol_version # negotiated or adopted version, either lifecycle
2557
+ client.server_capabilities # capabilities Hash, either lifecycle
2558
+ client.instructions # instructions text, either lifecycle
2559
+ client.server_implementation # server name/version; nil when a modern server does not identify itself
2560
+ ```
2561
+
2562
+ Troubleshooting: if `server_info["protocolVersion"]` starts returning `nil` after a server you connect to was upgraded,
2563
+ the server now serves the modern lifecycle and the automatic negotiation adopted it.
2564
+ Pass `mode: :legacy` for an immediate return to the previous behavior, or switch to the readers above for a permanent fix.
2565
+
2566
+ ### Custom Headers from Tool Parameters (SEP-2243)
2567
+
2568
+ On a modern `MCP::Client::HTTP` connection, `tools/call` mirrors arguments whose `inputSchema` property carries
2569
+ an `x-mcp-header` annotation into `Mcp-Param-{Name}` request headers, so intermediaries can route
2570
+ on the values without parsing bodies. The declarations are learned from `tools/list` responses:
2571
+ list the tools before calling one to enable the mirroring. Values that cannot ride as plain ASCII header values
2572
+ (non-ASCII, control characters, edge whitespace, empty strings) are wrapped as `=?base64?...?=`,
2573
+ and a `null` or absent argument omits its header.
2574
+
2575
+ Per the specification, a tool definition whose `x-mcp-header` annotations are invalid (empty or non-token names,
2576
+ duplicate names, non-primitive properties, annotations outside a chain of `properties` keys) is excluded from
2577
+ `tools/list` results on modern connections, with a warning naming the tool.
2578
+ Legacy connections are unaffected: nothing is learned, mirrored, or excluded.
2579
+
2379
2580
  ## Transport Layer Interface
2380
2581
 
2381
2582
  If the transport layer you need is not included in the gem, you can build and pass your own instances so long as they conform to the following interface:
@@ -2499,6 +2700,13 @@ The server will send `notifications/progress` back to the client during executio
2499
2700
  an SSE event or a JSON response body. A message that reaches this limit before completing is rejected as a transport error, preventing unbounded memory growth from
2500
2701
  a server that never terminates an SSE event. It defaults to `4 * 1024 * 1024` (4 MiB); raise it if your server returns larger responses.
2501
2702
 
2703
+ `MCP::Client::HTTP.new` also accepts `max_reconnection_wait:`, a budget in seconds for resuming a closed SSE stream. It gates every wait between reconnection attempts,
2704
+ and what is left of it becomes the read timeout of each resumed stream. The server chooses that wait through the SSE `retry:` field, and resuming happens on the calling thread,
2705
+ so without a budget a server answering with a large `retry:` parks a thread of your application for as long as it likes. It defaults to `300` (5 minutes).
2706
+ The server's `retry:` is never shortened: when honoring it would run past the budget, the client stops trying to resume and raises instead,
2707
+ the same thing it already does once the reconnection attempts are used up. A floor of 100ms applies to each wait, so a `retry: 0` cannot spin
2708
+ the listening stream's reconnect loop; waiting longer than the server asked for is explicitly allowed by the SSE reconnection algorithm the spec points at.
2709
+
2502
2710
  #### Server-to-Client Requests (Elicitation)
2503
2711
 
2504
2712
  Servers can send requests back to the client while one of the client's own requests is in flight - for example,
@@ -2772,6 +2980,28 @@ whether the swap was triggered by
2772
2980
  `instance_variable_set(:@url, ...)`, by a Faraday customizer rewriting `url_prefix`, or by a custom middleware rewriting `env.url` (including just `env.url.query`) at request time,
2773
2981
  and whether the new URL is `http://` *or* `https://` to a different host or tenant.
2774
2982
 
2983
+ ##### Discovery URL Destinations
2984
+
2985
+ The scheme rules above say how a URL is contacted, not where it points. Discovery URLs arrive from the network, so the SDK also constrains their destinations.
2986
+ Both checks run before the request is sent, and neither is configurable.
2987
+
2988
+ - The `resource_metadata` URL in a `WWW-Authenticate` challenge must be on the MCP server's own origin. Protected Resource Metadata describes that server,
2989
+ so a real deployment publishes it there; requiring it means a `401` cannot aim the first request of the flow at an unrelated host. This is stricter than RFC 9728,
2990
+ which does not require it.
2991
+ - The PRM `authorization_servers` entry and the `authorization_endpoint`, `token_endpoint`, and `registration_endpoint` from Authorization Server metadata must not be
2992
+ IP literals in a private, loopback, link-local, or unique-local range, per the SSRF precaution in [RFC 9728 Section 7.7](https://www.rfc-editor.org/rfc/rfc9728#section-7.7).
2993
+ The blocked ranges are `0.0.0.0/8`, `10.0.0.0/8`, `100.64.0.0/10`, `127.0.0.0/8`, `169.254.0.0/16`, `172.16.0.0/12`, `192.168.0.0/16`, `::/96`, `fc00::/7`,
2994
+ and `fe80::/10`, along with the IPv4-mapped IPv6 spellings of each and the `localhost` name.
2995
+ - That range check is skipped when the MCP server URL you configured is itself on such an address. Pointing the client at a private network is a deliberate act,
2996
+ and the authorization server for it usually lives on the same network, so `http://localhost` development and deployments that never leave a corporate network keep working.
2997
+
2998
+ The range check compares IP literals and does not resolve hostnames, so it cannot recognize an internal service that is named rather than addressed,
2999
+ such as `https://vault.corp.internal/`. Resolving names here would not close that gap either, because the address the SDK looked up need not be the one
3000
+ the HTTP client connects to a moment later. The same-origin rule is what protects the `resource_metadata` URL, which is the only one of these a server supplies directly.
3001
+
3002
+ If you replace the OAuth HTTP client through `MCP::Client::OAuth::Flow.new(http_client_factory:)`, do not add redirect-following middleware. Every check above runs against
3003
+ the URL as written, so a connection that follows a `3xx` on its own would reach hosts these rules just refused.
3004
+
2775
3005
  #### Customizing the Faraday Connection
2776
3006
 
2777
3007
  You can pass a block to `MCP::Client::HTTP.new` to customize the underlying Faraday connection.
@@ -2802,6 +3032,18 @@ The Ruby client recognizes such results and raises `MCP::Client::InputRequiredEr
2802
3032
  and the raw `result`; automatic resumption is not implemented yet, so callers respond manually if they opt into the draft flow. `MCP::ResultType::COMPLETE` and `MCP::ResultType::INPUT_REQUIRED`
2803
3033
  are provided for forward compatibility. Servers on stable protocol versions never send `resultType`, so existing behavior is unchanged.
2804
3034
 
3035
+ SEP-2322 also makes `resultType` a required member of every result a 2026-07-28 server returns. The server stamps `resultType: "complete"` on all results of requests carrying
3036
+ the modern `_meta` envelope (and on `server/discover` results), while results that already carry a discriminator (`"input_required"`, the tasks extension's `"task"`) keep it.
3037
+ Legacy results stay unstamped, and clients treat an absent `resultType` as `"complete"` per the spec.
3038
+
3039
+ #### Dual-era authoring (legacy fulfilment shim)
3040
+
3041
+ Handlers written in the 2026 style serve pre-2026 clients too: when a `tools/call`, `prompts/get`, or `resources/read` handler returns an `InputRequiredResult` on the legacy wire,
3042
+ the server fulfills it in place of the client's driver. Each `inputRequests` entry is sent as the equivalent real server-to-client request
3043
+ (`elicitation/create`, `sampling/createMessage`, `roots/list`), associated with the originating request per SEP-2260; the answers are collected under the same keys,
3044
+ and the handler re-runs with `server_context.input_responses` populated and the raw `requestState` echoed, the same deterministic replay contract the modern client driver follows.
3045
+ The shim is on by default (matching the TypeScript SDK) and capped at 8 rounds; `MCP::Server.new(input_required_legacy_shim: false)` restores the strict rejection of `input_required` results on legacy requests.
3046
+
2805
3047
  ## Conformance Testing
2806
3048
 
2807
3049
  The `conformance/` directory contains a test server and runner that validate the SDK against the MCP specification using [`@modelcontextprotocol/conformance`](https://github.com/modelcontextprotocol/conformance).
@@ -135,11 +135,12 @@ module JsonRpcHandler
135
135
  success_response(id: id, result: result)
136
136
  rescue MCP::Server::RequestHandlerError => e
137
137
  handle_request_error(e, id, id_validation_pattern)
138
- rescue StandardError => e
138
+ rescue StandardError
139
+ # The exception message is deliberately not echoed into `data`: it can carry
140
+ # internals that must not reach untrusted clients (CWE-209).
139
141
  error_response(id: id, id_validation_pattern: id_validation_pattern, error: {
140
142
  code: ErrorCode::INTERNAL_ERROR,
141
143
  message: "Internal error",
142
- data: e.message,
143
144
  })
144
145
  end
145
146
  end