ask-mcp 0.4.0 → 0.4.2
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/CHANGELOG.md +119 -0
- data/README.md +104 -175
- data/docs/SPEC_COMPLIANCE.md +184 -0
- data/lib/ask/mcp/adapters/tool_server.rb +33 -27
- data/lib/ask/mcp/auth/client_id_metadata_document.rb +74 -0
- data/lib/ask/mcp/auth/oauth.rb +52 -4
- data/lib/ask/mcp/client.rb +237 -23
- data/lib/ask/mcp/native/messages.rb +23 -2
- data/lib/ask/mcp/prompt.rb +9 -3
- data/lib/ask/mcp/resource.rb +9 -3
- data/lib/ask/mcp/server/stdio.rb +194 -4
- data/lib/ask/mcp/server.rb +7 -2
- data/lib/ask/mcp/tool.rb +11 -4
- data/lib/ask/mcp/trace_context.rb +39 -0
- data/lib/ask/mcp/transport/sse.rb +5 -1
- data/lib/ask/mcp/transport/stdio.rb +1 -1
- data/lib/ask/mcp/transport/streamable_http.rb +108 -26
- data/lib/ask/mcp/validator.rb +19 -1
- data/lib/ask/mcp/version.rb +1 -1
- data/lib/ask/mcp/x_mcp_header.rb +95 -0
- data/lib/ask/mcp.rb +15 -0
- metadata +19 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d73da1e33a97a8bd6825f2eb3e0f9c770ffbbc8b1a9502db612b49e71fc87193
|
|
4
|
+
data.tar.gz: 5f7cf6b4cb501db18cf5a8367215292c923cf97c078acb226e7546d86e3218a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c932454bfccb278fa718290ea84cb3e721296db46ab449c941005f363f404e5342a3836354660d525604c441088b556e938403f99c1d6c80b9066a68906ed3b0
|
|
7
|
+
data.tar.gz: c8a46f82f4f4907aed57bb1394856f9894a8d9d4bf727fc011604f0b8f68fd621ba2884d72cd194f7e30b9b4545804aba99e412ab55d0a3cbb2498f6708587a1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,122 @@
|
|
|
1
|
+
## [0.4.2] - 2026-08-04
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **2026-07-28 stateless protocol core (dual-mode)** — new
|
|
6
|
+
`Ask::MCP::LATEST_PROTOCOL_VERSION` (`"2026-07-28"`) and
|
|
7
|
+
`SUPPORTED_PROTOCOL_VERSIONS` constants. The client probes
|
|
8
|
+
`server/discover` and, for a 2026-07-28 peer, skips the `initialize`
|
|
9
|
+
handshake and sends every request with `_meta`
|
|
10
|
+
(`io.modelcontextprotocol/protocolVersion`, `clientCapabilities`,
|
|
11
|
+
`clientInfo`). The server answers `server/discover`, detects stateless
|
|
12
|
+
requests from `_meta`, and keeps the legacy handshake for older peers. A
|
|
13
|
+
server that supports only ≤ 2025-11-25 is detected and still handshakes
|
|
14
|
+
with the negotiated version.
|
|
15
|
+
- **`resultType` on server results** — `"complete"` for stateless peers
|
|
16
|
+
(`"input_required"` via MRTR).
|
|
17
|
+
- **MRTR (Multi Round-Trip Requests)** — the client resolves
|
|
18
|
+
`inputRequests` through the `on_elicitation`/`on_sampling`/`on_request`
|
|
19
|
+
handlers, retries the original request with `inputResponses`, echoes
|
|
20
|
+
`requestState` verbatim, uses a fresh JSON-RPC id per retry, and caps round
|
|
21
|
+
trips (`MAX_MRTR_ROUND_TRIPS`).
|
|
22
|
+
- **Server-initiated request framework** — `Client#on_request` plus
|
|
23
|
+
`on_elicitation` and `on_sampling` conveniences; the client declares
|
|
24
|
+
support via `client_capabilities`.
|
|
25
|
+
- **Streamable HTTP rework (2026-07-28 shape)** — one POST per message with
|
|
26
|
+
`MCP-Protocol-Version`, `Mcp-Method`, `Mcp-Name` headers (Base64 sentinel
|
|
27
|
+
value encoding); per-response Content-Type decides single JSON vs SSE
|
|
28
|
+
stream; SSE keep-alive comments ignored; `subscriptions/listen` long-lived
|
|
29
|
+
streams via `Transport::StreamableHTTP#listen` / `Client#listen`; x-mcp-
|
|
30
|
+
header tool parameters mirrored into `Mcp-Param-{Name}` headers. Sessions,
|
|
31
|
+
the GET endpoint, and `Last-Event-ID` were removed upstream and are not
|
|
32
|
+
implemented. `base64` added as a runtime dependency (Ruby 3.4+).
|
|
33
|
+
- **x-mcp-header validation (SEP-2243)** — `Ask::MCP::XMcpHeader` validates
|
|
34
|
+
annotations (empty names, invalid characters, non-primitive types,
|
|
35
|
+
case-insensitive duplicates, non-`properties`-reachable placement);
|
|
36
|
+
Streamable HTTP clients exclude invalid tool definitions from `tools/list`
|
|
37
|
+
with a warning; out-of-range integers are omitted from headers.
|
|
38
|
+
- **OTel trace context (SEP-414)** — `Ask::MCP::TraceContext` extracts
|
|
39
|
+
`traceparent`/`tracestate`/`baggage` from headers (incl. Rack `HTTP_*`) and
|
|
40
|
+
`_meta`; clients propagate configured `_meta` fields via `meta:` option.
|
|
41
|
+
- **Server list_changed notifications** — `Server::Stdio#notify_tools_
|
|
42
|
+
list_changed`, `#notify_resources_list_changed`, `#notify_prompts_
|
|
43
|
+
list_changed` emit the change notifications (deterministically ordered
|
|
44
|
+
before the triggering response); the client invalidates its caches on
|
|
45
|
+
receipt. Verified end-to-end with a dedicated notify test server.
|
|
46
|
+
- **Client ID Metadata Documents (SEP-991)** — `Auth::ClientIdMetadataDocument`
|
|
47
|
+
builds and validates the self-hosted client metadata document (required
|
|
48
|
+
fields, https URL client_id with path, URL/client_id match,
|
|
49
|
+
`application_type` native/web) — the 2026-07-28 recommended replacement
|
|
50
|
+
for the deprecated Dynamic Client Registration.
|
|
51
|
+
- **RFC 9207 `iss` validation** — `Auth::OAuth#validate_iss!` rejects an
|
|
52
|
+
`iss` that does not match the recorded issuer.
|
|
53
|
+
- **`CacheableResult`** — `ttlMs` + `cacheScope` (configurable via
|
|
54
|
+
`cache_ttl_ms`/`cache_scope`) emitted on list/read results for stateless
|
|
55
|
+
peers; deterministic `tools/list` ordering locked by test.
|
|
56
|
+
- **`ping` removed for 2026-07-28 peers** (Method-not-found); legacy clients
|
|
57
|
+
still get `{}`.
|
|
58
|
+
- **Error-code allocation policy** — `HEADER_MISMATCH` `-32020`,
|
|
59
|
+
`MISSING_REQUIRED_CLIENT_CAPABILITY` `-32021`,
|
|
60
|
+
`UNSUPPORTED_PROTOCOL_VERSION` `-32022`; resource-not-found renumbered to
|
|
61
|
+
`-32602` for stateless peers.
|
|
62
|
+
- **OIDC Discovery 1.0 (2025-11-25)** — `Auth::OAuth#discover!` resolves
|
|
63
|
+
endpoints from `/.well-known/openid-configuration` (issuer-derived or
|
|
64
|
+
explicit URL); `token_url` is now optional at construction.
|
|
65
|
+
- **JSON Schema 2020-12 dialect** — `Validator` accepts schemas declaring the
|
|
66
|
+
2020-12 metaschema (strips it and retries on the json-schema gem, which
|
|
67
|
+
lacks 2020-12); `const` and other shared keywords enforced.
|
|
68
|
+
- **2025-11-25 display metadata (SEP-973)** — `Tool`, `Resource`, and
|
|
69
|
+
`Prompt` now parse (`from_h`) and emit (`to_h`) the optional `title` and
|
|
70
|
+
`icons` (array of `{src, mimeType, sizes}`) fields, and
|
|
71
|
+
`Adapters::ToolServer` passes them through in `tools/list` definitions
|
|
72
|
+
when the tool object provides them. Clients connecting to 2025-11-25
|
|
73
|
+
servers no longer drop these fields.
|
|
74
|
+
- **Server now serves resources & prompts** — `Server::Stdio` implements
|
|
75
|
+
`resources/list`, `resources/read`, `resources/templates/list`,
|
|
76
|
+
`prompts/list`, and `prompts/get` (previously only tools were served).
|
|
77
|
+
Duck-typed resource/prompt/template objects are serialized from their
|
|
78
|
+
accessors, and `Server.start_stdio` accepts `resources`, `prompts`, and
|
|
79
|
+
`resource_templates` keyword args.
|
|
80
|
+
- `RecordingTransport` test helper (`test/support/recording_transport.rb`) —
|
|
81
|
+
in-memory transport for asserting what `Client` puts on the wire without
|
|
82
|
+
spawning a subprocess.
|
|
83
|
+
- `docs/SPEC_COMPLIANCE.md` — full compliance status across all three
|
|
84
|
+
revisions (2025-06-18 / 2025-11-25 / 2026-07-28).
|
|
85
|
+
|
|
86
|
+
### Changed
|
|
87
|
+
|
|
88
|
+
- HTTP+SSE transport marked deprecated (2026-07-28 feature lifecycle);
|
|
89
|
+
still functional for legacy servers. Prefer Streamable HTTP.
|
|
90
|
+
- `send` on transports now accepts an optional extra-headers argument
|
|
91
|
+
(used for `Mcp-Param-*` mirroring; ignored by stdio/SSE).
|
|
92
|
+
|
|
93
|
+
### Fixed
|
|
94
|
+
|
|
95
|
+
- **Client advertised a bogus protocol version** — `Ask::MCP::Client` sent
|
|
96
|
+
`protocolVersion: "0.1.0"` in `initialize` while `Server::Stdio` pinned
|
|
97
|
+
`2025-06-18`. The advertised MCP revision is now a single source of truth,
|
|
98
|
+
`Ask::MCP::PROTOCOL_VERSION` (`"2025-06-18"`), referenced by both client and
|
|
99
|
+
server; the old per-class constants remain as deprecated aliases.
|
|
100
|
+
- **Flaky subprocess timing tests** — `test_transport_send_and_receive` and
|
|
101
|
+
`test_sigterm_triggers_shutdown` failed intermittently under CI load.
|
|
102
|
+
Replaced fixed `sleep`s with a shared `wait_until` poll-until-deadline
|
|
103
|
+
helper (`test/test_helper.rb`); `test_spawn_and_communicate` now does a
|
|
104
|
+
real round-trip via `cat` instead of an assertion that could never fail.
|
|
105
|
+
|
|
106
|
+
## [0.4.1] - 2026-07-31
|
|
107
|
+
|
|
108
|
+
### Changed
|
|
109
|
+
|
|
110
|
+
- **`ToolServer` result wrapping** — plain `String` results are always treated
|
|
111
|
+
as success; result-like objects (`Ask::Result`, OpenStruct, etc.) use
|
|
112
|
+
`ok?`/`ok` and `output`/`error_message`; any other value is treated as
|
|
113
|
+
success with `to_s` used for the response.
|
|
114
|
+
|
|
115
|
+
### Fixed
|
|
116
|
+
|
|
117
|
+
- **Stdio server protocol version** — pins `PROTOCOL_VERSION` to
|
|
118
|
+
`2025-06-18` and echoes it back in the `initialize` response.
|
|
119
|
+
|
|
1
120
|
## [0.4.0] - 2026-06-26
|
|
2
121
|
|
|
3
122
|
### Added
|
data/README.md
CHANGED
|
@@ -2,13 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/ask-mcp)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
servers
|
|
7
|
-
to expose your own tools to any MCP client
|
|
8
|
-
implement a couple of duck-typed
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
Model Context Protocol (MCP) client and server for Ruby. Connect to MCP
|
|
6
|
+
servers over stdio, SSE, or Streamable HTTP transports, or run as an MCP
|
|
7
|
+
server to expose your own tools to any MCP client (Claude Code, Codex, Cursor,
|
|
8
|
+
GitHub Copilot). No framework lock-in: implement a couple of duck-typed
|
|
9
|
+
methods and you are done.
|
|
10
|
+
|
|
11
|
+
Speaks the protocol **dual-mode**: legacy `initialize`-handshake revisions
|
|
12
|
+
(`2025-06-18`, `2025-11-25`) and the stateless `2026-07-28` revision
|
|
13
|
+
(`server/discover`, per-request `_meta`, MRTR). Clients negotiate
|
|
14
|
+
automatically; servers answer both eras on one connection. See
|
|
15
|
+
[docs/SPEC_COMPLIANCE.md](docs/SPEC_COMPLIANCE.md) for the full status across
|
|
16
|
+
all three revisions.
|
|
12
17
|
|
|
13
18
|
## Installation
|
|
14
19
|
|
|
@@ -16,13 +21,7 @@ Claude Code, Codex, Cursor, and GitHub Copilot.
|
|
|
16
21
|
gem "ask-mcp"
|
|
17
22
|
```
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
```ruby
|
|
22
|
-
gem "ask-mcp", "~> 0.1.0"
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Quick Start — Client
|
|
24
|
+
## Quick Start: Client
|
|
26
25
|
|
|
27
26
|
Connect to any MCP server and call its tools:
|
|
28
27
|
|
|
@@ -40,16 +39,37 @@ puts result
|
|
|
40
39
|
client.stop
|
|
41
40
|
```
|
|
42
41
|
|
|
43
|
-
|
|
42
|
+
`start` probes `server/discover` first: a `2026-07-28` server is used
|
|
43
|
+
statelessly (no handshake; every request carries `_meta`), and older servers
|
|
44
|
+
fall back to the `initialize` handshake automatically.
|
|
45
|
+
|
|
46
|
+
### Server-initiated requests (elicitation, sampling)
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
(
|
|
47
|
-
|
|
48
|
+
Servers can ask the client for input — user answers (elicitation) or LLM
|
|
49
|
+
completions (sampling). Register handlers; the client answers the server and,
|
|
50
|
+
in the stateless revision, completes the request via
|
|
51
|
+
[MRTR](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr):
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
client.on_elicitation { |params| { message: "42" } }
|
|
55
|
+
client.on_sampling do |params|
|
|
56
|
+
# params includes `tools` / `toolChoice` when the server offers tool calling
|
|
57
|
+
{ role: "assistant", content: { type: "text", text: "Paris" } }
|
|
58
|
+
end
|
|
59
|
+
# any on_request("method") { |params| ... } works for custom server requests
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Declare support so servers know they can ask:
|
|
63
|
+
`Ask::MCP::Client.new(transport, client_capabilities: { elicitation: {}, sampling: {} })`.
|
|
64
|
+
|
|
65
|
+
## Quick Start: Server
|
|
66
|
+
|
|
67
|
+
Run as a standalone MCP server. Any object that responds to `name`,
|
|
68
|
+
`description`, `params_schema`, and `call(args)` works:
|
|
48
69
|
|
|
49
70
|
```ruby
|
|
50
71
|
require "ask/mcp"
|
|
51
72
|
|
|
52
|
-
# Define your tools — no base class needed, just duck typing
|
|
53
73
|
class Greeter
|
|
54
74
|
def name; "greet" end
|
|
55
75
|
def description; "Greets someone by name" end
|
|
@@ -61,14 +81,17 @@ class Greeter
|
|
|
61
81
|
end
|
|
62
82
|
end
|
|
63
83
|
|
|
64
|
-
#
|
|
84
|
+
# Blocking: runs until stdin closes
|
|
65
85
|
Ask::MCP::Server.start_stdio(
|
|
66
86
|
name: "my-server",
|
|
67
|
-
tools: [Greeter.new]
|
|
87
|
+
tools: [Greeter.new],
|
|
88
|
+
resources: { "greeting://world" => GreetingResource.new }, # uri → object
|
|
89
|
+
prompts: { "greet" => GreetPrompt.new }, # name → object
|
|
90
|
+
resource_templates: { "file:///{path}" => FileTemplate.new }
|
|
68
91
|
)
|
|
69
92
|
```
|
|
70
93
|
|
|
71
|
-
|
|
94
|
+
Point any MCP client at it:
|
|
72
95
|
|
|
73
96
|
```json
|
|
74
97
|
{
|
|
@@ -81,198 +104,104 @@ Configure your MCP client:
|
|
|
81
104
|
}
|
|
82
105
|
```
|
|
83
106
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
107
|
+
The result of `call(args)` may be a plain value, or an object responding to
|
|
108
|
+
`ok?` and `output` / `error_message` (for example an `OpenStruct`). The server
|
|
109
|
+
wraps results into MCP's `content` array format automatically. Resources and
|
|
110
|
+
prompts are duck-typed: objects with `to_h` are serialized directly, otherwise
|
|
111
|
+
accessors (`title`, `description`, `mime_type`, `icons`, `arguments`) are
|
|
112
|
+
collected; `resources/read` and `prompts/get` pull contents from `content` /
|
|
113
|
+
`read` / `messages`.
|
|
88
114
|
|
|
89
|
-
|
|
90
|
-
`output` / `error_message`. Use `OpenStruct` for simple cases:
|
|
115
|
+
When your tool/resource/prompt sets change at runtime, tell clients:
|
|
91
116
|
|
|
92
117
|
```ruby
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def name; "bash" end
|
|
97
|
-
def description; "Run a shell command" end
|
|
98
|
-
def params_schema
|
|
99
|
-
{ type: "object", properties: { "command" => { "type" => "string" } }, required: ["command"] }
|
|
100
|
-
end
|
|
101
|
-
def call(args = {})
|
|
102
|
-
output = `#{args['command']} 2>&1`
|
|
103
|
-
OpenStruct.new(ok?: true, output: output)
|
|
104
|
-
rescue => e
|
|
105
|
-
OpenStruct.new(ok?: false, error_message: e.message)
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
For a production example with shell tools, file ops, and web search,
|
|
111
|
-
see [llm-proxy](https://github.com/ask-rb/llm-proxy).
|
|
112
|
-
|
|
113
|
-
### Using with ask-tools
|
|
114
|
-
|
|
115
|
-
If you use the ask-rb ecosystem, you can expose Ask::Tool subclasses directly:
|
|
116
|
-
|
|
117
|
-
```ruby
|
|
118
|
-
require "ask/mcp"
|
|
119
|
-
require "ask-tools-shell"
|
|
120
|
-
require "ask-web-search"
|
|
121
|
-
|
|
122
|
-
tools = Ask::Tools::Shell::TOOLS.map(&:new) + [Ask::Tools::WebSearch.new]
|
|
123
|
-
|
|
124
|
-
Ask::MCP::Server.start_stdio(
|
|
125
|
-
name: "my-server",
|
|
126
|
-
tools: tools,
|
|
127
|
-
capabilities: { tools: {} }
|
|
128
|
-
)
|
|
118
|
+
server.notify_tools_list_changed
|
|
119
|
+
server.notify_resources_list_changed
|
|
120
|
+
server.notify_prompts_list_changed
|
|
129
121
|
```
|
|
130
122
|
|
|
131
123
|
## Transports
|
|
132
124
|
|
|
133
125
|
```ruby
|
|
134
|
-
# stdio
|
|
126
|
+
# stdio: local processes
|
|
135
127
|
Ask::MCP.from_stdio("npx", ["-y", "@modelcontextprotocol/server-github"])
|
|
136
128
|
|
|
137
|
-
# SSE
|
|
129
|
+
# SSE: remote servers with Server-Sent Events (deprecated upstream — prefer Streamable HTTP)
|
|
138
130
|
Ask::MCP.from_sse("https://mcp.example.com/sse")
|
|
139
131
|
|
|
140
|
-
# Streamable HTTP
|
|
132
|
+
# Streamable HTTP: remote servers
|
|
141
133
|
Ask::MCP.from_http("https://mcp.example.com/mcp")
|
|
142
134
|
```
|
|
143
135
|
|
|
144
|
-
|
|
136
|
+
Each factory returns a client backed by `Ask::MCP::Transport::Stdio`,
|
|
137
|
+
`Ask::MCP::Transport::SSE`, or `Ask::MCP::Transport::StreamableHTTP`.
|
|
145
138
|
|
|
146
|
-
|
|
139
|
+
The Streamable HTTP transport implements the `2026-07-28` shape: one POST per
|
|
140
|
+
message with `MCP-Protocol-Version` / `Mcp-Method` / `Mcp-Name` headers
|
|
141
|
+
(Base64-sentinel value encoding), per-response JSON-or-SSE handling, and
|
|
142
|
+
`subscriptions/listen` long-lived notification streams:
|
|
147
143
|
|
|
148
144
|
```ruby
|
|
149
|
-
|
|
150
|
-
transport = Ask::MCP::Transport::Stdio.new("ruby", ["server.rb"])
|
|
151
|
-
client = Ask::MCP::Client.new(transport, timeout: 30)
|
|
152
|
-
|
|
153
|
-
# Start the session (sends initialize + receives capabilities)
|
|
154
|
-
client.start
|
|
155
|
-
|
|
156
|
-
# Use the client
|
|
157
|
-
client.tools # => { "tool_name" => #<Ask::MCP::Tool> }
|
|
158
|
-
client.resources # => { "resource_uri" => #<Ask::MCP::Resource> }
|
|
159
|
-
client.prompts # => { "prompt_name" => #<Ask::MCP::Prompt> }
|
|
160
|
-
client.call_tool("tool_name", arg1: "value")
|
|
161
|
-
client.read_resource("file:///path")
|
|
162
|
-
client.get_prompt("prompt_name", arg1: "value")
|
|
163
|
-
|
|
164
|
-
# Stop the session
|
|
165
|
-
client.stop
|
|
145
|
+
client.listen(toolsListChanged: true, resourceSubscriptions: ["file:///x"])
|
|
166
146
|
```
|
|
167
147
|
|
|
168
|
-
|
|
148
|
+
Tool parameters annotated with `x-mcp-header` in the server's `inputSchema`
|
|
149
|
+
are mirrored into `Mcp-Param-{Name}` headers; tool definitions with invalid
|
|
150
|
+
annotations are excluded from `tools/list` on HTTP transports.
|
|
151
|
+
|
|
152
|
+
## Essential API
|
|
153
|
+
|
|
154
|
+
| Entry point | Purpose |
|
|
155
|
+
|---|---|
|
|
156
|
+
| `client.start` / `client.stop` | Negotiate the protocol (discover or handshake) and shut down |
|
|
157
|
+
| `client.tools` / `client.resources` / `client.prompts` | Indexed lists exposed by the server (title/icons preserved) |
|
|
158
|
+
| `client.call_tool(name, args)` | Invoke a tool; also `read_resource(uri)` and `get_prompt(name, args)` |
|
|
159
|
+
| `client.on_request(method)` / `on_elicitation` / `on_sampling` | Answer server-initiated requests (MRTR) |
|
|
160
|
+
| `client.listen(notifications)` | Open a `subscriptions/listen` notification stream |
|
|
161
|
+
| `server.notify_*_list_changed` | Emit change notifications to clients |
|
|
162
|
+
| `Ask::MCP::Adapters::AskTool.wrap(tools_hash)` | Adapter from MCP tools to `Ask::Tool` instances for ask-agent |
|
|
163
|
+
| `Ask::MCP::Adapters::ToolServer` | Adapter from duck-typed tools to MCP server tools |
|
|
164
|
+
| `Ask::MCP::Auth::Token.new(token)` | Token-based auth (`apply(headers)`) |
|
|
165
|
+
| `Ask::MCP::Auth::OAuth.new(client_id:, ...)` | OAuth for MCP; `discover!` (OIDC), `authenticate!`, `validate_iss!`, `apply(headers)` |
|
|
166
|
+
| `Ask::MCP::Auth::ClientIdMetadataDocument` | Build/validate Client ID Metadata Documents (2026-07-28 client registration) |
|
|
167
|
+
| `Ask::MCP::TraceContext` | OpenTelemetry `traceparent`/`tracestate`/`baggage` extraction for `_meta` |
|
|
168
|
+
| `Ask::MCP::XMcpHeader` | Validation of `x-mcp-header` tool annotations |
|
|
169
|
+
|
|
170
|
+
OAuth endpoints can be discovered instead of configured:
|
|
169
171
|
|
|
170
172
|
```ruby
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
input_schema: {
|
|
176
|
-
type: "object",
|
|
177
|
-
properties: { path: { type: "string" } },
|
|
178
|
-
required: ["path"]
|
|
179
|
-
}
|
|
180
|
-
)
|
|
181
|
-
tool.name # => "read_file"
|
|
182
|
-
tool.description # => "Read a file from disk"
|
|
183
|
-
tool.input_schema # => { type: "object", ... }
|
|
184
|
-
|
|
185
|
-
# Resource
|
|
186
|
-
resource = Ask::MCP::Resource.new(
|
|
187
|
-
uri: "file:///tmp/test.txt",
|
|
188
|
-
name: "Test File",
|
|
189
|
-
mime_type: "text/plain"
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
# Prompt
|
|
193
|
-
prompt = Ask::MCP::Prompt.new(
|
|
194
|
-
name: "greet",
|
|
195
|
-
description: "Generate a greeting",
|
|
196
|
-
arguments: [{ name: "name", description: "Name to greet", required: true }]
|
|
197
|
-
)
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### Authentication
|
|
201
|
-
|
|
202
|
-
```ruby
|
|
203
|
-
# Token-based auth
|
|
204
|
-
token = Ask::MCP::Auth::Token.new("my-api-token")
|
|
205
|
-
headers = token.apply({}) # => { "Authorization" => "Bearer my-api-token" }
|
|
206
|
-
|
|
207
|
-
# OAuth 2.1
|
|
208
|
-
oauth = Ask::MCP::Auth::OAuth.new(
|
|
209
|
-
client_id: "my-client",
|
|
210
|
-
client_secret: "my-secret",
|
|
211
|
-
token_url: "https://auth.example.com/token",
|
|
212
|
-
scopes: ["mcp"]
|
|
213
|
-
)
|
|
214
|
-
oauth.authenticate!
|
|
215
|
-
headers = oauth.apply({})
|
|
173
|
+
oauth = Ask::MCP::Auth::OAuth.new(client_id: "my-client", issuer: "https://auth.example.com")
|
|
174
|
+
oauth.discover! # fetches /.well-known/openid-configuration
|
|
175
|
+
oauth.authenticate! # client-credentials or authorization-code flow
|
|
176
|
+
client = Ask::MCP::Client.new(transport, auth: oauth)
|
|
216
177
|
```
|
|
217
178
|
|
|
218
179
|
### With ask-agent
|
|
219
180
|
|
|
220
181
|
```ruby
|
|
221
|
-
require "ask/mcp"
|
|
222
|
-
|
|
223
182
|
client = Ask::MCP.from_stdio("npx", ["-y", "@modelcontextprotocol/server-github"])
|
|
224
183
|
client.start
|
|
225
184
|
|
|
226
|
-
# Convert MCP tools to Ask::Tool instances for use with Ask::Agent
|
|
227
|
-
client.tools.each do |name, mcp_tool|
|
|
228
|
-
agent.register_tool(mcp_tool.to_ask_tool)
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
# Or use the adapter directly
|
|
232
185
|
wrapped = Ask::MCP::Adapters::AskTool.wrap(client.tools)
|
|
233
186
|
wrapped.each { |name, adapter| agent.register_tool(adapter.to_ask_tool) }
|
|
234
187
|
```
|
|
235
188
|
|
|
236
|
-
|
|
189
|
+
Expose `Ask::Tool` subclasses as an MCP server with `Ask::MCP::Server.start_stdio(name:, tools:, capabilities: { tools: {} })`; the `ToolServer` adapter handles them.
|
|
237
190
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
├── lib/ask/mcp/prompt.rb # MCP prompt representation
|
|
247
|
-
├── lib/ask/mcp/native/messages.rb # JSON-RPC message layer
|
|
248
|
-
├── lib/ask/mcp/transport/
|
|
249
|
-
│ ├── stdio.rb # stdio transport (client direction)
|
|
250
|
-
│ ├── sse.rb # Server-Sent Events transport
|
|
251
|
-
│ └── streamable_http.rb # Streamable HTTP transport
|
|
252
|
-
├── lib/ask/mcp/auth/
|
|
253
|
-
│ ├── oauth.rb # OAuth 2.1 for MCP
|
|
254
|
-
│ └── token.rb # Token-based auth
|
|
255
|
-
└── lib/ask/mcp/adapters/
|
|
256
|
-
├── ask_tool.rb # MCP::Tool → Ask::Tool adapter
|
|
257
|
-
└── tool_server.rb # Duck-typed tools → MCP server adapter
|
|
258
|
-
```
|
|
191
|
+
## Full documentation
|
|
192
|
+
|
|
193
|
+
The full ask-rb documentation lives at https://ask-rb.github.io/ask-docs.
|
|
194
|
+
https://ask-rb.github.io/ask-docs/core/mcp covers ask-mcp in depth, including
|
|
195
|
+
tool, resource, and prompt objects, protocol details, and auth. See also the
|
|
196
|
+
[Auth Setup Guide](docs/auth-setup.md) for token and OAuth 2.1 setup with
|
|
197
|
+
ask-auth, and [docs/SPEC_COMPLIANCE.md](docs/SPEC_COMPLIANCE.md) for the
|
|
198
|
+
protocol compliance status. API reference: https://ask-rb.github.io/ask-docs/reference/api.
|
|
259
199
|
|
|
260
200
|
## Development
|
|
261
201
|
|
|
262
|
-
|
|
263
|
-
#
|
|
264
|
-
bundle exec rake test
|
|
265
|
-
|
|
266
|
-
# Run specific tests
|
|
267
|
-
bundle exec ruby -Itest test/messages_test.rb
|
|
268
|
-
bundle exec ruby -Itest test/stdio_integration_test.rb
|
|
269
|
-
```
|
|
202
|
+
bundle install
|
|
203
|
+
bundle exec rake test # full suite + self-contained conformance (both protocol eras)
|
|
270
204
|
|
|
271
205
|
## License
|
|
272
206
|
|
|
273
207
|
MIT
|
|
274
|
-
|
|
275
|
-
## Authentication
|
|
276
|
-
|
|
277
|
-
See the [Auth Setup Guide](docs/auth-setup.md) for detailed documentation on
|
|
278
|
-
token-based and OAuth 2.1 authentication, including ask-auth integration.
|