claude_agent 0.7.14 → 0.7.16
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/.claude/rules/conventions.md +66 -16
- data/CHANGELOG.md +20 -0
- data/CLAUDE.md +24 -4
- data/README.md +52 -1529
- data/SPEC.md +56 -29
- data/docs/architecture.md +339 -0
- data/docs/client.md +526 -0
- data/docs/configuration.md +571 -0
- data/docs/conversations.md +461 -0
- data/docs/errors.md +127 -0
- data/docs/events.md +225 -0
- data/docs/getting-started.md +310 -0
- data/docs/hooks.md +380 -0
- data/docs/logging.md +96 -0
- data/docs/mcp.md +308 -0
- data/docs/messages.md +871 -0
- data/docs/permissions.md +611 -0
- data/docs/queries.md +227 -0
- data/docs/sessions.md +335 -0
- data/lib/claude_agent/abort_controller.rb +24 -0
- data/lib/claude_agent/client/commands.rb +32 -0
- data/lib/claude_agent/client.rb +10 -4
- data/lib/claude_agent/configuration.rb +129 -0
- data/lib/claude_agent/control_protocol/commands.rb +28 -0
- data/lib/claude_agent/conversation.rb +37 -4
- data/lib/claude_agent/errors.rb +21 -4
- data/lib/claude_agent/event_handler.rb +14 -0
- data/lib/claude_agent/fork_session.rb +117 -0
- data/lib/claude_agent/hook_registry.rb +110 -0
- data/lib/claude_agent/hooks.rb +4 -0
- data/lib/claude_agent/mcp/server.rb +22 -0
- data/lib/claude_agent/mcp/tool.rb +24 -3
- data/lib/claude_agent/message.rb +93 -0
- data/lib/claude_agent/messages/streaming.rb +37 -0
- data/lib/claude_agent/options.rb +10 -0
- data/lib/claude_agent/permission_policy.rb +174 -0
- data/lib/claude_agent/permission_request.rb +17 -0
- data/lib/claude_agent/session.rb +100 -11
- data/lib/claude_agent/session_paths.rb +5 -2
- data/lib/claude_agent/turn_result.rb +20 -2
- data/lib/claude_agent/types/sessions.rb +8 -0
- data/lib/claude_agent/version.rb +1 -1
- data/lib/claude_agent.rb +187 -0
- data/sig/claude_agent.rbs +38 -1
- metadata +20 -1
data/docs/mcp.md
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# MCP Tools
|
|
2
|
+
|
|
3
|
+
The ClaudeAgent Ruby SDK lets you define MCP (Model Context Protocol) tool servers that run in-process -- in the same Ruby process as your application. Unlike external MCP servers that launch as subprocesses communicating over stdio, SDK servers handle tool calls directly via method dispatch. This means faster execution, shared state with your application, and straightforward debugging.
|
|
4
|
+
|
|
5
|
+
## Block DSL (Recommended)
|
|
6
|
+
|
|
7
|
+
The most concise way to define an MCP server is the block DSL. Pass a block to `Server.new` and call `tool` to register each tool inline:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
server = ClaudeAgent::MCP::Server.new(name: "calculator") do |s|
|
|
11
|
+
s.tool("add", "Add two numbers", { a: :number, b: :number }) do |args|
|
|
12
|
+
(args[:a] + args[:b]).to_s
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
s.tool("multiply", "Multiply two numbers", { a: :number, b: :number }) do |args|
|
|
16
|
+
(args[:a] * args[:b]).to_s
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Each call to `s.tool(name, description, schema, &handler)` creates a `ClaudeAgent::MCP::Tool` and registers it on the server. The handler block receives a **symbol-keyed** Hash of the arguments Claude provides.
|
|
22
|
+
|
|
23
|
+
## Traditional Approach
|
|
24
|
+
|
|
25
|
+
Create `Tool` objects first, then pass them to the server constructor:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
add_tool = ClaudeAgent::MCP::Tool.new(
|
|
29
|
+
name: "add",
|
|
30
|
+
description: "Add two numbers",
|
|
31
|
+
schema: { a: Float, b: Float }
|
|
32
|
+
) { |args| (args[:a] + args[:b]).to_s }
|
|
33
|
+
|
|
34
|
+
subtract_tool = ClaudeAgent::MCP::Tool.new(
|
|
35
|
+
name: "subtract",
|
|
36
|
+
description: "Subtract two numbers",
|
|
37
|
+
schema: { a: Float, b: Float }
|
|
38
|
+
) { |args| (args[:a] - args[:b]).to_s }
|
|
39
|
+
|
|
40
|
+
server = ClaudeAgent::MCP::Server.new(
|
|
41
|
+
name: "calculator",
|
|
42
|
+
tools: [add_tool, subtract_tool]
|
|
43
|
+
)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can also add and remove tools after construction:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
server.add_tool(new_tool)
|
|
50
|
+
server.remove_tool("old_tool")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Convenience Methods
|
|
54
|
+
|
|
55
|
+
`ClaudeAgent::MCP` provides module-level shortcuts for creating tools and servers without fully qualifying the class names:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
tool = ClaudeAgent::MCP.tool("greet", "Greet someone", { name: String }) do |args|
|
|
59
|
+
"Hello, #{args[:name]}!"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
server = ClaudeAgent::MCP.create_server(name: "greeter", tools: [tool])
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Tool Schema
|
|
66
|
+
|
|
67
|
+
The schema defines the JSON Schema for the tool's input parameters. Three formats are supported.
|
|
68
|
+
|
|
69
|
+
### Ruby class mapping
|
|
70
|
+
|
|
71
|
+
Pass a Hash mapping parameter names to Ruby classes. All parameters are marked as required:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
schema: { name: String, age: Integer, score: Float }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
| Ruby Class | JSON Schema type |
|
|
78
|
+
|---------------------------|------------------|
|
|
79
|
+
| `String` | `string` |
|
|
80
|
+
| `Integer` | `integer` |
|
|
81
|
+
| `Float`, `Numeric` | `number` |
|
|
82
|
+
| `TrueClass`, `FalseClass` | `boolean` |
|
|
83
|
+
| `Array` | `array` |
|
|
84
|
+
| `Hash` | `object` |
|
|
85
|
+
|
|
86
|
+
### Symbol shortcuts
|
|
87
|
+
|
|
88
|
+
Use symbols instead of classes for a more compact definition:
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
schema: { name: :string, count: :integer, ratio: :number, active: :boolean }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| Symbol | JSON Schema type |
|
|
95
|
+
|---------------------------------|------------------|
|
|
96
|
+
| `:string`, `:str` | `string` |
|
|
97
|
+
| `:integer`, `:int` | `integer` |
|
|
98
|
+
| `:number`, `:float`, `:numeric` | `number` |
|
|
99
|
+
| `:boolean`, `:bool` | `boolean` |
|
|
100
|
+
| `:array` | `array` |
|
|
101
|
+
| `:object`, `:hash` | `object` |
|
|
102
|
+
|
|
103
|
+
### Raw JSON Schema
|
|
104
|
+
|
|
105
|
+
For full control (enums, optional fields, nested objects), pass a standard JSON Schema Hash directly. The SDK detects raw schemas by the presence of a `type` or `properties` key and passes them through unchanged:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
schema: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
operation: { type: "string", enum: ["add", "subtract", "multiply"] },
|
|
112
|
+
a: { type: "number" },
|
|
113
|
+
b: { type: "number" }
|
|
114
|
+
},
|
|
115
|
+
required: ["operation", "a", "b"]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Tool Annotations
|
|
120
|
+
|
|
121
|
+
MCP tool annotations provide hints to the model about tool behavior. Pass an `annotations` Hash when creating a tool:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
server = ClaudeAgent::MCP::Server.new(name: "files") do |s|
|
|
125
|
+
s.tool("read_file", "Read a file", { path: :string },
|
|
126
|
+
annotations: {
|
|
127
|
+
readOnlyHint: true,
|
|
128
|
+
destructiveHint: false,
|
|
129
|
+
idempotentHint: true,
|
|
130
|
+
openWorldHint: false,
|
|
131
|
+
title: "Read File"
|
|
132
|
+
}
|
|
133
|
+
) do |args|
|
|
134
|
+
File.read(args[:path])
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
| Annotation | Type | Description |
|
|
140
|
+
|-------------------|---------|---------------------------------------------------|
|
|
141
|
+
| `readOnlyHint` | Boolean | Tool does not modify state |
|
|
142
|
+
| `destructiveHint` | Boolean | Tool may perform destructive operations |
|
|
143
|
+
| `idempotentHint` | Boolean | Repeated calls with same args produce same result |
|
|
144
|
+
| `openWorldHint` | Boolean | Tool interacts with external systems |
|
|
145
|
+
| `title` | String | Human-readable display name |
|
|
146
|
+
|
|
147
|
+
Annotations are omitted from the MCP definition when `nil` or empty.
|
|
148
|
+
|
|
149
|
+
## Tool Return Values
|
|
150
|
+
|
|
151
|
+
The handler block's return value is automatically formatted into the MCP response structure. Several return types are supported:
|
|
152
|
+
|
|
153
|
+
**String** -- wrapped in a text content block:
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
s.tool("greet", "Greet", { name: :string }) do |args|
|
|
157
|
+
"Hello, #{args[:name]}!"
|
|
158
|
+
end
|
|
159
|
+
# => { content: [{ type: "text", text: "Hello, World!" }], isError: false }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Numeric or other non-String** -- converted via `to_s` and wrapped in a text content block:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
s.tool("add", "Add", { a: :number, b: :number }) do |args|
|
|
166
|
+
args[:a] + args[:b]
|
|
167
|
+
end
|
|
168
|
+
# => { content: [{ type: "text", text: "7.5" }], isError: false }
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Hash with `:content` key** -- used as-is, letting you return custom content blocks:
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
s.tool("image", "Generate image", {}) do |args|
|
|
175
|
+
{ content: [{ type: "image", data: base64_data, mimeType: "image/png" }] }
|
|
176
|
+
end
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Array** -- treated as a pre-built content block array:
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
s.tool("multi", "Multiple outputs", {}) do |args|
|
|
183
|
+
[
|
|
184
|
+
{ type: "text", text: "Part 1" },
|
|
185
|
+
{ type: "text", text: "Part 2" }
|
|
186
|
+
]
|
|
187
|
+
end
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Exceptions** -- if the handler raises, the error is caught and returned as an error response:
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
s.tool("divide", "Divide", { a: :number, b: :number }) do |args|
|
|
194
|
+
raise "Division by zero" if args[:b] == 0
|
|
195
|
+
(args[:a] / args[:b]).to_s
|
|
196
|
+
end
|
|
197
|
+
# When b=0: { content: [{ type: "text", text: "Error: Division by zero" }], isError: true }
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Using with Options
|
|
201
|
+
|
|
202
|
+
To attach an MCP server to a query, pass it via the `mcp_servers` option. Each server entry is keyed by name and must include `type: "sdk"` and `instance:` pointing to the server object:
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
server = ClaudeAgent::MCP::Server.new(name: "calculator") do |s|
|
|
206
|
+
s.tool("add", "Add two numbers", { a: :number, b: :number }) do |args|
|
|
207
|
+
(args[:a] + args[:b]).to_s
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
options = ClaudeAgent::Options.new(
|
|
212
|
+
mcp_servers: {
|
|
213
|
+
"calculator" => { type: "sdk", instance: server }
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
turn = ClaudeAgent.ask("What is 2 + 3?", options: options)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The server also provides a `to_config` shortcut that builds the config Hash for you:
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
options = ClaudeAgent::Options.new(
|
|
224
|
+
mcp_servers: {
|
|
225
|
+
"calculator" => server.to_config
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Global Registration
|
|
231
|
+
|
|
232
|
+
Register an MCP server globally so it is available to all queries without passing it in every `Options`:
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
server = ClaudeAgent::MCP::Server.new(name: "calculator") do |s|
|
|
236
|
+
s.tool("add", "Add two numbers", { a: :number, b: :number }) do |args|
|
|
237
|
+
(args[:a] + args[:b]).to_s
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
ClaudeAgent.register_mcp_server(server)
|
|
242
|
+
|
|
243
|
+
# Now every query can use the calculator tools
|
|
244
|
+
turn = ClaudeAgent.ask("What is 2 + 3?")
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Globally registered servers are stored in `ClaudeAgent.config.default_mcp_servers` and merged into the effective `Options` for each request.
|
|
248
|
+
|
|
249
|
+
## External MCP Servers
|
|
250
|
+
|
|
251
|
+
In addition to in-process SDK servers, you can configure external MCP servers that run as subprocesses. These use the `stdio` transport type and are passed directly to the Claude Code CLI:
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
options = ClaudeAgent::Options.new(
|
|
255
|
+
mcp_servers: {
|
|
256
|
+
"filesystem" => {
|
|
257
|
+
type: "stdio",
|
|
258
|
+
command: "npx",
|
|
259
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
260
|
+
},
|
|
261
|
+
"database" => {
|
|
262
|
+
type: "stdio",
|
|
263
|
+
command: "python",
|
|
264
|
+
args: ["-m", "mcp_server_sqlite", "--db", "app.db"]
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
You can mix SDK and external servers in the same `mcp_servers` Hash. The SDK filters out external servers and passes their configuration to the CLI's `--mcp-config` flag, while SDK servers are handled in-process.
|
|
271
|
+
|
|
272
|
+
## MCP Elicitation
|
|
273
|
+
|
|
274
|
+
MCP elicitation allows an MCP server to request additional input from the user (or your application) during a tool call -- for example, to handle OAuth consent or collect form data.
|
|
275
|
+
|
|
276
|
+
Set the `on_elicitation` callback in your `Options`. The callback receives a request Hash and must return a response Hash:
|
|
277
|
+
|
|
278
|
+
```ruby
|
|
279
|
+
options = ClaudeAgent::Options.new(
|
|
280
|
+
on_elicitation: ->(request, signal:) {
|
|
281
|
+
# request keys:
|
|
282
|
+
# :server_name - which MCP server triggered this
|
|
283
|
+
# :message - display message from the server
|
|
284
|
+
# :mode - elicitation mode
|
|
285
|
+
# :url - URL for OAuth flows (if applicable)
|
|
286
|
+
# :elicitation_id - unique ID for this elicitation
|
|
287
|
+
# :requested_schema - schema describing expected input
|
|
288
|
+
|
|
289
|
+
case request[:mode]
|
|
290
|
+
when "oauth"
|
|
291
|
+
# Handle OAuth flow, return approval
|
|
292
|
+
{ action: "approve", content: { token: "..." } }
|
|
293
|
+
else
|
|
294
|
+
# Decline unknown elicitation types
|
|
295
|
+
{ action: "decline" }
|
|
296
|
+
end
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
The callback must return a Hash with an `action` key. Supported actions:
|
|
302
|
+
|
|
303
|
+
| Action | Description |
|
|
304
|
+
|-------------|--------------------------------------------------------------------|
|
|
305
|
+
| `"approve"` | Accept the elicitation and optionally provide `content` |
|
|
306
|
+
| `"decline"` | Reject the elicitation (this is the default if no callback is set) |
|
|
307
|
+
|
|
308
|
+
If no `on_elicitation` callback is configured, all elicitation requests are declined automatically.
|