claude-agent-sdk 0.1.3 → 0.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 +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +9 -2
- data/lib/claude_agent_sdk/sdk_mcp_server.rb +206 -18
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +6 -3
- metadata +17 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fe94c46bcf37e8f5fd38dcc5ccaa17e3490d70f79b083ffd4f753711e8df38ab
|
|
4
|
+
data.tar.gz: ff8fd7d82daa4acac453b15c727b96c03edf8fa2f1d52c772b75b24cd0df2792
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 597dd01120488fdd74c18c2ac0623bcb7d9f0f9af784cc868a291e3075a25a999023258acf338210e7e0f2d0d2ed3de5512cce8e5c72fb0b29142c665d8ab2ac
|
|
7
|
+
data.tar.gz: a3f2c72a4beac29ba34471bbf7195e71f92720afc220d2c52250421f49da4bedc3ce8be113e8a1c75fb9a370418528d7912f71343fe7acebe81be26abbc9d32f
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.0] - 2025-10-17
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **BREAKING:** Updated minimum Ruby version from 3.0+ to 3.2+ (required by official MCP SDK)
|
|
12
|
+
- **Major refactoring:** SDK MCP server now uses official Ruby MCP SDK (`mcp` gem v0.4) internally
|
|
13
|
+
- Internal implementation migrated from custom MCP to wrapping official `MCP::Server`
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- Official Ruby MCP SDK (`mcp` gem) as runtime dependency
|
|
17
|
+
- Full MCP protocol compliance via official SDK
|
|
18
|
+
- `handle_json` method for protocol-compliant JSON-RPC handling
|
|
19
|
+
|
|
20
|
+
### Improved
|
|
21
|
+
- Better long-term maintenance by leveraging official SDK updates
|
|
22
|
+
- Aligned with Python SDK implementation pattern (using official MCP library)
|
|
23
|
+
- All 86 tests passing with full backward compatibility maintained
|
|
24
|
+
|
|
25
|
+
### Technical Details
|
|
26
|
+
- Creates dynamic `MCP::Tool`, `MCP::Resource`, and `MCP::Prompt` classes from block-based definitions
|
|
27
|
+
- User-facing API remains unchanged - no breaking changes for Ruby 3.2+ users
|
|
28
|
+
- Maintains backward-compatible methods (`list_tools`, `call_tool`, etc.)
|
|
29
|
+
|
|
8
30
|
## [0.1.3] - 2025-10-15
|
|
9
31
|
|
|
10
32
|
### Added
|
data/README.md
CHANGED
|
@@ -29,7 +29,7 @@ gem install claude-agent-sdk
|
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
**Prerequisites:**
|
|
32
|
-
- Ruby 3.
|
|
32
|
+
- Ruby 3.2+
|
|
33
33
|
- Node.js
|
|
34
34
|
- Claude Code 2.0.0+: `npm install -g @anthropic-ai/claude-code`
|
|
35
35
|
|
|
@@ -131,7 +131,11 @@ For a complete example, see [examples/streaming_input_example.rb](examples/strea
|
|
|
131
131
|
|
|
132
132
|
## Client
|
|
133
133
|
|
|
134
|
-
`ClaudeAgentSDK::Client` supports bidirectional, interactive conversations with Claude Code. Unlike `query()`, `Client` enables **custom tools**, **hooks**, and **permission callbacks**, all of which can be defined as Ruby procs/lambdas.
|
|
134
|
+
`ClaudeAgentSDK::Client` supports bidirectional, interactive conversations with Claude Code. Unlike `query()`, `Client` enables **custom tools**, **hooks**, and **permission callbacks**, all of which can be defined as Ruby procs/lambdas.
|
|
135
|
+
|
|
136
|
+
**The Client class automatically uses streaming mode** for bidirectional communication, allowing you to send multiple queries dynamically during a single session without closing the connection. This matches the Python SDK's `ClaudeSDKClient` behavior.
|
|
137
|
+
|
|
138
|
+
See [lib/claude_agent_sdk.rb](lib/claude_agent_sdk.rb) for implementation details.
|
|
135
139
|
|
|
136
140
|
### Custom Tools (SDK MCP Servers)
|
|
137
141
|
|
|
@@ -139,6 +143,8 @@ A **custom tool** is a Ruby proc/lambda that you can offer to Claude, for Claude
|
|
|
139
143
|
|
|
140
144
|
Custom tools are implemented as in-process MCP servers that run directly within your Ruby application, eliminating the need for separate processes that regular MCP servers require.
|
|
141
145
|
|
|
146
|
+
**Implementation**: This SDK uses the [official Ruby MCP SDK](https://github.com/modelcontextprotocol/ruby-sdk) (`mcp` gem) internally, providing full protocol compliance while offering a simpler block-based API for tool definition.
|
|
147
|
+
|
|
142
148
|
For a complete example, see [examples/mcp_calculator.rb](examples/mcp_calculator.rb).
|
|
143
149
|
|
|
144
150
|
#### Creating a Simple Tool
|
|
@@ -292,6 +298,7 @@ Async do
|
|
|
292
298
|
client = ClaudeAgentSDK::Client.new
|
|
293
299
|
|
|
294
300
|
begin
|
|
301
|
+
# Connect automatically uses streaming mode for bidirectional communication
|
|
295
302
|
client.connect
|
|
296
303
|
|
|
297
304
|
# Send a query
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'mcp'
|
|
4
|
+
|
|
3
5
|
module ClaudeAgentSDK
|
|
4
|
-
# SDK MCP Server -
|
|
6
|
+
# SDK MCP Server - wraps official MCP::Server with block-based API
|
|
5
7
|
#
|
|
6
8
|
# Unlike external MCP servers that run as separate processes, SDK MCP servers
|
|
7
9
|
# run directly in your application's process, providing better performance
|
|
8
10
|
# and simpler deployment.
|
|
9
11
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# - Resources: Data sources that can be read (files, databases, APIs, etc.)
|
|
13
|
-
# - Prompts: Reusable prompt templates with arguments
|
|
12
|
+
# This class wraps the official MCP Ruby SDK and provides a simpler block-based
|
|
13
|
+
# API for defining tools, resources, and prompts.
|
|
14
14
|
class SdkMcpServer
|
|
15
|
-
attr_reader :name, :version, :tools, :resources, :prompts
|
|
15
|
+
attr_reader :name, :version, :tools, :resources, :prompts, :mcp_server
|
|
16
16
|
|
|
17
17
|
def initialize(name:, version: '1.0.0', tools: [], resources: [], prompts: [])
|
|
18
18
|
@name = name
|
|
@@ -20,12 +20,34 @@ module ClaudeAgentSDK
|
|
|
20
20
|
@tools = tools
|
|
21
21
|
@resources = resources
|
|
22
22
|
@prompts = prompts
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
|
|
24
|
+
# Create dynamic Tool classes from tool definitions
|
|
25
|
+
tool_classes = create_tool_classes(tools)
|
|
26
|
+
|
|
27
|
+
# Create dynamic Resource classes from resource definitions
|
|
28
|
+
resource_classes = create_resource_classes(resources)
|
|
29
|
+
|
|
30
|
+
# Create dynamic Prompt classes from prompt definitions
|
|
31
|
+
prompt_classes = create_prompt_classes(prompts)
|
|
32
|
+
|
|
33
|
+
# Create the official MCP::Server instance
|
|
34
|
+
@mcp_server = MCP::Server.new(
|
|
35
|
+
name: name,
|
|
36
|
+
version: version,
|
|
37
|
+
tools: tool_classes,
|
|
38
|
+
resources: resource_classes,
|
|
39
|
+
prompts: prompt_classes
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Handle a JSON-RPC request
|
|
44
|
+
# @param json_string [String] JSON-RPC request
|
|
45
|
+
# @return [String] JSON-RPC response
|
|
46
|
+
def handle_json(json_string)
|
|
47
|
+
@mcp_server.handle_json(json_string)
|
|
26
48
|
end
|
|
27
49
|
|
|
28
|
-
# List all available tools
|
|
50
|
+
# List all available tools (for backward compatibility)
|
|
29
51
|
# @return [Array<Hash>] Array of tool definitions
|
|
30
52
|
def list_tools
|
|
31
53
|
@tools.map do |tool|
|
|
@@ -37,12 +59,12 @@ module ClaudeAgentSDK
|
|
|
37
59
|
end
|
|
38
60
|
end
|
|
39
61
|
|
|
40
|
-
# Execute a tool by name
|
|
62
|
+
# Execute a tool by name (for backward compatibility)
|
|
41
63
|
# @param name [String] Tool name
|
|
42
64
|
# @param arguments [Hash] Tool arguments
|
|
43
65
|
# @return [Hash] Tool result
|
|
44
66
|
def call_tool(name, arguments)
|
|
45
|
-
tool = @
|
|
67
|
+
tool = @tools.find { |t| t.name == name }
|
|
46
68
|
raise "Tool '#{name}' not found" unless tool
|
|
47
69
|
|
|
48
70
|
# Call the tool's handler
|
|
@@ -56,7 +78,7 @@ module ClaudeAgentSDK
|
|
|
56
78
|
result
|
|
57
79
|
end
|
|
58
80
|
|
|
59
|
-
# List all available resources
|
|
81
|
+
# List all available resources (for backward compatibility)
|
|
60
82
|
# @return [Array<Hash>] Array of resource definitions
|
|
61
83
|
def list_resources
|
|
62
84
|
@resources.map do |resource|
|
|
@@ -69,11 +91,11 @@ module ClaudeAgentSDK
|
|
|
69
91
|
end
|
|
70
92
|
end
|
|
71
93
|
|
|
72
|
-
# Read a resource by URI
|
|
94
|
+
# Read a resource by URI (for backward compatibility)
|
|
73
95
|
# @param uri [String] Resource URI
|
|
74
96
|
# @return [Hash] Resource content
|
|
75
97
|
def read_resource(uri)
|
|
76
|
-
resource = @
|
|
98
|
+
resource = @resources.find { |r| r.uri == uri }
|
|
77
99
|
raise "Resource '#{uri}' not found" unless resource
|
|
78
100
|
|
|
79
101
|
# Call the resource's reader
|
|
@@ -87,7 +109,7 @@ module ClaudeAgentSDK
|
|
|
87
109
|
content
|
|
88
110
|
end
|
|
89
111
|
|
|
90
|
-
# List all available prompts
|
|
112
|
+
# List all available prompts (for backward compatibility)
|
|
91
113
|
# @return [Array<Hash>] Array of prompt definitions
|
|
92
114
|
def list_prompts
|
|
93
115
|
@prompts.map do |prompt|
|
|
@@ -99,12 +121,12 @@ module ClaudeAgentSDK
|
|
|
99
121
|
end
|
|
100
122
|
end
|
|
101
123
|
|
|
102
|
-
# Get a prompt by name
|
|
124
|
+
# Get a prompt by name (for backward compatibility)
|
|
103
125
|
# @param name [String] Prompt name
|
|
104
126
|
# @param arguments [Hash] Arguments to fill in the prompt template
|
|
105
127
|
# @return [Hash] Prompt with filled-in arguments
|
|
106
128
|
def get_prompt(name, arguments = {})
|
|
107
|
-
prompt = @
|
|
129
|
+
prompt = @prompts.find { |p| p.name == name }
|
|
108
130
|
raise "Prompt '#{name}' not found" unless prompt
|
|
109
131
|
|
|
110
132
|
# Call the prompt's generator
|
|
@@ -120,6 +142,172 @@ module ClaudeAgentSDK
|
|
|
120
142
|
|
|
121
143
|
private
|
|
122
144
|
|
|
145
|
+
# Create dynamic Tool classes from tool definitions
|
|
146
|
+
def create_tool_classes(tools)
|
|
147
|
+
tools.map do |tool_def|
|
|
148
|
+
# Create a new class that extends MCP::Tool
|
|
149
|
+
Class.new(MCP::Tool) do
|
|
150
|
+
@tool_def = tool_def
|
|
151
|
+
|
|
152
|
+
class << self
|
|
153
|
+
attr_reader :tool_def
|
|
154
|
+
|
|
155
|
+
def description
|
|
156
|
+
@tool_def.description
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def input_schema
|
|
160
|
+
convert_schema(@tool_def.input_schema)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def call(server_context: nil, **args)
|
|
164
|
+
# Filter out server_context and pass remaining args to handler
|
|
165
|
+
result = @tool_def.handler.call(args)
|
|
166
|
+
|
|
167
|
+
# Convert result to MCP::Tool::Response format
|
|
168
|
+
content = result[:content].map do |item|
|
|
169
|
+
{
|
|
170
|
+
type: item[:type],
|
|
171
|
+
text: item[:text]
|
|
172
|
+
}
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
MCP::Tool::Response.new(content)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
|
|
180
|
+
def convert_schema(schema)
|
|
181
|
+
# If it's already a proper JSON schema, return it
|
|
182
|
+
if schema.is_a?(Hash) && schema[:type] && schema[:properties]
|
|
183
|
+
return schema
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Simple schema: hash mapping parameter names to types
|
|
187
|
+
if schema.is_a?(Hash)
|
|
188
|
+
properties = {}
|
|
189
|
+
schema.each do |param_name, param_type|
|
|
190
|
+
properties[param_name] = type_to_json_schema(param_type)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
type: 'object',
|
|
195
|
+
properties: properties,
|
|
196
|
+
required: properties.keys.map(&:to_s)
|
|
197
|
+
}
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Default fallback
|
|
201
|
+
{ type: 'object', properties: {} }
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def type_to_json_schema(type)
|
|
205
|
+
case type
|
|
206
|
+
when :string, String
|
|
207
|
+
{ type: 'string' }
|
|
208
|
+
when :integer, Integer
|
|
209
|
+
{ type: 'integer' }
|
|
210
|
+
when :float, Float
|
|
211
|
+
{ type: 'number' }
|
|
212
|
+
when :boolean, TrueClass, FalseClass
|
|
213
|
+
{ type: 'boolean' }
|
|
214
|
+
when :number
|
|
215
|
+
{ type: 'number' }
|
|
216
|
+
else
|
|
217
|
+
{ type: 'string' } # Default fallback
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Set the tool name
|
|
223
|
+
define_singleton_method(:name) { @tool_def.name }
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Create dynamic Resource classes from resource definitions
|
|
229
|
+
def create_resource_classes(resources)
|
|
230
|
+
resources.map do |resource_def|
|
|
231
|
+
# Create a new class that extends MCP::Resource
|
|
232
|
+
Class.new(MCP::Resource) do
|
|
233
|
+
@resource_def = resource_def
|
|
234
|
+
|
|
235
|
+
class << self
|
|
236
|
+
attr_reader :resource_def
|
|
237
|
+
|
|
238
|
+
def uri
|
|
239
|
+
@resource_def.uri
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def name
|
|
243
|
+
@resource_def.name
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def description
|
|
247
|
+
@resource_def.description
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def mime_type
|
|
251
|
+
@resource_def.mime_type
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def read
|
|
255
|
+
result = @resource_def.reader.call
|
|
256
|
+
|
|
257
|
+
# Convert to MCP format
|
|
258
|
+
result[:contents].map do |content|
|
|
259
|
+
MCP::ResourceContents.new(
|
|
260
|
+
uri: content[:uri],
|
|
261
|
+
mime_type: content[:mimeType] || content[:mime_type],
|
|
262
|
+
text: content[:text]
|
|
263
|
+
)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Create dynamic Prompt classes from prompt definitions
|
|
272
|
+
def create_prompt_classes(prompts)
|
|
273
|
+
prompts.map do |prompt_def|
|
|
274
|
+
# Create a new class that extends MCP::Prompt
|
|
275
|
+
Class.new(MCP::Prompt) do
|
|
276
|
+
@prompt_def = prompt_def
|
|
277
|
+
|
|
278
|
+
class << self
|
|
279
|
+
attr_reader :prompt_def
|
|
280
|
+
|
|
281
|
+
def name
|
|
282
|
+
@prompt_def.name
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def description
|
|
286
|
+
@prompt_def.description
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def arguments
|
|
290
|
+
@prompt_def.arguments || []
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def get(**args)
|
|
294
|
+
result = @prompt_def.generator.call(args)
|
|
295
|
+
|
|
296
|
+
# Convert to MCP format
|
|
297
|
+
{
|
|
298
|
+
messages: result[:messages].map do |msg|
|
|
299
|
+
{
|
|
300
|
+
role: msg[:role],
|
|
301
|
+
content: msg[:content]
|
|
302
|
+
}
|
|
303
|
+
end
|
|
304
|
+
}
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
123
311
|
def convert_input_schema(schema)
|
|
124
312
|
# If it's already a proper JSON schema, return it
|
|
125
313
|
if schema.is_a?(Hash) && schema[:type] && schema[:properties]
|
data/lib/claude_agent_sdk.rb
CHANGED
|
@@ -86,11 +86,12 @@ module ClaudeAgentSDK
|
|
|
86
86
|
#
|
|
87
87
|
# This client provides full control over the conversation flow with support
|
|
88
88
|
# for streaming, hooks, permission callbacks, and dynamic message sending.
|
|
89
|
+
# The Client class always uses streaming mode for bidirectional communication.
|
|
89
90
|
#
|
|
90
91
|
# @example Basic usage
|
|
91
92
|
# Async do
|
|
92
93
|
# client = ClaudeAgentSDK::Client.new
|
|
93
|
-
# client.connect
|
|
94
|
+
# client.connect # No arguments needed - automatically uses streaming mode
|
|
94
95
|
#
|
|
95
96
|
# client.query("What is the capital of France?")
|
|
96
97
|
# client.receive_response do |msg|
|
|
@@ -150,8 +151,10 @@ module ClaudeAgentSDK
|
|
|
150
151
|
configured_options = @options.dup_with(permission_prompt_tool_name: 'stdio')
|
|
151
152
|
end
|
|
152
153
|
|
|
153
|
-
#
|
|
154
|
-
|
|
154
|
+
# Auto-connect with empty enumerator if no prompt is provided
|
|
155
|
+
# This matches the Python SDK pattern where ClaudeSDKClient always uses streaming mode
|
|
156
|
+
# An empty enumerator keeps stdin open for bidirectional communication
|
|
157
|
+
actual_prompt = prompt || [].to_enum
|
|
155
158
|
@transport = SubprocessCLITransport.new(actual_prompt, configured_options)
|
|
156
159
|
@transport.connect
|
|
157
160
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: claude-agent-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Community Contributors
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-10-
|
|
10
|
+
date: 2025-10-17 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: async
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: mcp
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.4'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.4'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: bundler
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -114,7 +128,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
114
128
|
requirements:
|
|
115
129
|
- - ">="
|
|
116
130
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: 3.
|
|
131
|
+
version: 3.2.0
|
|
118
132
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
133
|
requirements:
|
|
120
134
|
- - ">="
|