claude-agent-sdk 0.1.4 → 0.2.1

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: 4d8b669f4c3f02dea0763113551838bb0a3a5ec514125876f28dbc5d23ee44aa
4
- data.tar.gz: 6ce126558a2d99e3b6aba03527d5b4ce8954ff3a99a13ee25f54dd6fa0141980
3
+ metadata.gz: db20a5dbe6ebc51b229f4de595489038a26a47d8109600131c45aa2bf27c6d2e
4
+ data.tar.gz: 563e16e88a48ce331cd48c6ebce3302f0c57292ee6563ebaec7af02bed29433d
5
5
  SHA512:
6
- metadata.gz: 54e48e83a98845d56d6dc4c1843ebf8728fd74ffd3e7670e826c0fdbf3ef297359c18f5b27c2fac0757da0fab5e28d6c19ab084ac1842c5615d06cf52a0a94f8
7
- data.tar.gz: 224aca0535d196fa3b348da69af9e4cbcc46276af8d36b7b58f6e35811704f93506ff31df7449e9aae9ebd252c8b5c035fa581dc4d500a5bade82d0d15f37ddc
6
+ metadata.gz: 288868f49db2968d92dc2250fe2ca91b7692431c92dc4fce41b04a35610858c146490da45178290877988a9210d67ebdf8257363f651e058a0bab534d24222fc
7
+ data.tar.gz: 183224c382c68b916da22c20c64d2f4fd625861baee265611bae8f752c25effcc7de2159a61cc65822cd62f24344f85b485cd1e6005ceff0e88ea2c34be4c30c
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
@@ -13,7 +13,7 @@ Ruby SDK for Claude Agent. See the [Claude Agent SDK documentation](https://docs
13
13
  Add this line to your application's Gemfile:
14
14
 
15
15
  ```ruby
16
- gem 'claude-agent-sdk'
16
+ gem 'claude-agent-sdk', '~> 0.2.0'
17
17
  ```
18
18
 
19
19
  And then execute:
@@ -29,7 +29,7 @@ gem install claude-agent-sdk
29
29
  ```
30
30
 
31
31
  **Prerequisites:**
32
- - Ruby 3.0+
32
+ - Ruby 3.2+
33
33
  - Node.js
34
34
  - Claude Code 2.0.0+: `npm install -g @anthropic-ai/claude-code`
35
35
 
@@ -143,6 +143,8 @@ A **custom tool** is a Ruby proc/lambda that you can offer to Claude, for Claude
143
143
 
144
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.
145
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
+
146
148
  For a complete example, see [examples/mcp_calculator.rb](examples/mcp_calculator.rb).
147
149
 
148
150
  #### Creating a Simple Tool
@@ -292,7 +292,10 @@ module ClaudeAgentSDK
292
292
  end
293
293
 
294
294
  def handle_sdk_mcp_request(server_name, message)
295
- unless @sdk_mcp_servers.key?(server_name)
295
+ # Convert server_name to symbol if needed for hash lookup
296
+ server_key = @sdk_mcp_servers.key?(server_name) ? server_name : server_name.to_sym
297
+
298
+ unless @sdk_mcp_servers.key?(server_key)
296
299
  return {
297
300
  jsonrpc: '2.0',
298
301
  id: message[:id],
@@ -303,7 +306,7 @@ module ClaudeAgentSDK
303
306
  }
304
307
  end
305
308
 
306
- server = @sdk_mcp_servers[server_name]
309
+ server = @sdk_mcp_servers[server_key]
307
310
  method = message[:method]
308
311
  params = message[:params] || {}
309
312
 
@@ -1,18 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'mcp'
4
+
3
5
  module ClaudeAgentSDK
4
- # SDK MCP Server - runs in-process within your Ruby application
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
- # Supports:
11
- # - Tools: Executable functions that Claude can call
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
- @tool_map = tools.each_with_object({}) { |tool, hash| hash[tool.name] = tool }
24
- @resource_map = resources.each_with_object({}) { |res, hash| hash[res.uri] = res }
25
- @prompt_map = prompts.each_with_object({}) { |prompt, hash| hash[prompt.name] = prompt }
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 = @tool_map[name]
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 = @resource_map[uri]
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 = @prompt_map[name]
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,177 @@ 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 name_value
156
+ @tool_def.name
157
+ end
158
+
159
+ def description_value
160
+ @tool_def.description
161
+ end
162
+
163
+ def input_schema_value
164
+ schema = convert_schema(@tool_def.input_schema)
165
+ MCP::Tool::InputSchema.new(
166
+ properties: schema[:properties] || {},
167
+ required: schema[:required] || []
168
+ )
169
+ end
170
+
171
+ def call(server_context: nil, **args)
172
+ # Filter out server_context and pass remaining args to handler
173
+ result = @tool_def.handler.call(args)
174
+
175
+ # Convert result to MCP::Tool::Response format
176
+ content = result[:content].map do |item|
177
+ {
178
+ type: item[:type],
179
+ text: item[:text]
180
+ }
181
+ end
182
+
183
+ MCP::Tool::Response.new(content)
184
+ end
185
+
186
+ private
187
+
188
+ def convert_schema(schema)
189
+ # If it's already a proper JSON schema, return it
190
+ if schema.is_a?(Hash) && schema[:type] && schema[:properties]
191
+ return schema
192
+ end
193
+
194
+ # Simple schema: hash mapping parameter names to types
195
+ if schema.is_a?(Hash)
196
+ properties = {}
197
+ schema.each do |param_name, param_type|
198
+ properties[param_name] = type_to_json_schema(param_type)
199
+ end
200
+
201
+ return {
202
+ type: 'object',
203
+ properties: properties,
204
+ required: properties.keys.map(&:to_s)
205
+ }
206
+ end
207
+
208
+ # Default fallback
209
+ { type: 'object', properties: {} }
210
+ end
211
+
212
+ def type_to_json_schema(type)
213
+ case type
214
+ when :string, String
215
+ { type: 'string' }
216
+ when :integer, Integer
217
+ { type: 'integer' }
218
+ when :float, Float
219
+ { type: 'number' }
220
+ when :boolean, TrueClass, FalseClass
221
+ { type: 'boolean' }
222
+ when :number
223
+ { type: 'number' }
224
+ else
225
+ { type: 'string' } # Default fallback
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
232
+
233
+ # Create dynamic Resource classes from resource definitions
234
+ def create_resource_classes(resources)
235
+ resources.map do |resource_def|
236
+ # Create a new class that extends MCP::Resource
237
+ Class.new(MCP::Resource) do
238
+ @resource_def = resource_def
239
+
240
+ class << self
241
+ attr_reader :resource_def
242
+
243
+ def uri
244
+ @resource_def.uri
245
+ end
246
+
247
+ def name
248
+ @resource_def.name
249
+ end
250
+
251
+ def description
252
+ @resource_def.description
253
+ end
254
+
255
+ def mime_type
256
+ @resource_def.mime_type
257
+ end
258
+
259
+ def read
260
+ result = @resource_def.reader.call
261
+
262
+ # Convert to MCP format
263
+ result[:contents].map do |content|
264
+ MCP::ResourceContents.new(
265
+ uri: content[:uri],
266
+ mime_type: content[:mimeType] || content[:mime_type],
267
+ text: content[:text]
268
+ )
269
+ end
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end
275
+
276
+ # Create dynamic Prompt classes from prompt definitions
277
+ def create_prompt_classes(prompts)
278
+ prompts.map do |prompt_def|
279
+ # Create a new class that extends MCP::Prompt
280
+ Class.new(MCP::Prompt) do
281
+ @prompt_def = prompt_def
282
+
283
+ class << self
284
+ attr_reader :prompt_def
285
+
286
+ def name
287
+ @prompt_def.name
288
+ end
289
+
290
+ def description
291
+ @prompt_def.description
292
+ end
293
+
294
+ def arguments
295
+ @prompt_def.arguments || []
296
+ end
297
+
298
+ def get(**args)
299
+ result = @prompt_def.generator.call(args)
300
+
301
+ # Convert to MCP format
302
+ {
303
+ messages: result[:messages].map do |msg|
304
+ {
305
+ role: msg[:role],
306
+ content: msg[:content]
307
+ }
308
+ end
309
+ }
310
+ end
311
+ end
312
+ end
313
+ end
314
+ end
315
+
123
316
  def convert_input_schema(schema)
124
317
  # If it's already a proper JSON schema, return it
125
318
  if schema.is_a?(Hash) && schema[:type] && schema[:properties]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeAgentSDK
4
- VERSION = '0.1.4'
4
+ VERSION = '0.2.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: claude-agent-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Community Contributors
@@ -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.0.0
131
+ version: 3.2.0
118
132
  required_rubygems_version: !ruby/object:Gem::Requirement
119
133
  requirements:
120
134
  - - ">="