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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ae155e99332cb2a3fcec6b65c6d0ac20aa9b2b81b27f1e3aa3c667dc0c7a42fb
4
- data.tar.gz: af84c5f5aae82c640e2c12316cde40444fe7733d6ad5aaddd6780242b98e1a1c
3
+ metadata.gz: d73da1e33a97a8bd6825f2eb3e0f9c770ffbbc8b1a9502db612b49e71fc87193
4
+ data.tar.gz: 5f7cf6b4cb501db18cf5a8367215292c923cf97c078acb226e7546d86e3218a8
5
5
  SHA512:
6
- metadata.gz: 62f473ff0fdc35b25adcdadf3aeb2a65c330767f7b73cc85bed204a32bff7fcc28f0348c2ee3f8e8ae1e3d7eaf3977714af67d67038c1be990d9feca2199d81f
7
- data.tar.gz: e511364526282fcb429740dbd65bbce3e51d0bde06a06cb672e33a0c30b1b09e1971c663145f1b272d7e01015060349447bb7d53081b8dd9813dea02f19591e9
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
  [![Gem Version](https://badge.fury.io/rb/ask-mcp.svg)](https://badge.fury.io/rb/ask-mcp)
4
4
 
5
- **Model Context Protocol (MCP) client and server for Ruby.** Connect to MCP
6
- servers via stdio, SSE, or Streamable HTTP transports. Run as an MCP server
7
- to expose your own tools to any MCP client. No framework lock-in — just
8
- implement a couple of duck-typed methods and you're done.
9
-
10
- MCP is the industry standard for LLM tool discovery — the same protocol used by
11
- Claude Code, Codex, Cursor, and GitHub Copilot.
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
- Or add to your Gemfile:
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
- ## Quick Start Server
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
- Run as a standalone MCP server exposing your own tools to any MCP client
46
- (Codex, Claude Code, Cursor, etc.). Any object that responds to `name`,
47
- `description`, `params_schema`, and `call(args)` will work:
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
- # Start the server (blocking — runs until stdin closes)
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
- Configure your MCP client:
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
- Tools can return any value the client can use. The server automatically wraps
85
- the result into MCP's `content` array format.
86
-
87
- ### What about complex tools?
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
- The result object from `call(args)` should respond to `ok?` (or `ok`) and
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
- require "ostruct"
94
-
95
- class BashTool
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 local processes
126
+ # stdio: local processes
135
127
  Ask::MCP.from_stdio("npx", ["-y", "@modelcontextprotocol/server-github"])
136
128
 
137
- # SSE remote servers with Server-Sent Events
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 remote servers
132
+ # Streamable HTTP: remote servers
141
133
  Ask::MCP.from_http("https://mcp.example.com/mcp")
142
134
  ```
143
135
 
144
- ## API
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
- ### Client Lifecycle
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
- # Create a client with any transport
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
- ### Tool, Resource, Prompt Objects
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
- # Tool
172
- tool = Ask::MCP::Tool.new(
173
- name: "read_file",
174
- description: "Read a file from disk",
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
- ## Architecture
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
- ask-mcp/
240
- ├── lib/ask/mcp.rb # Entry point, factory methods
241
- ├── lib/ask/mcp/client.rb # MCP client (connect, call_tool, etc.)
242
- ├── lib/ask/mcp/server.rb # MCP server representation + Server.start_stdio entry point
243
- ├── lib/ask/mcp/server/stdio.rb # MCP server stdio runtime (run as server)
244
- ├── lib/ask/mcp/tool.rb # MCP tool representation
245
- ├── lib/ask/mcp/resource.rb # MCP resource representation
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
- ```bash
263
- # Run tests
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.