claude-agent-sdk 0.1.4 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d8b669f4c3f02dea0763113551838bb0a3a5ec514125876f28dbc5d23ee44aa
4
- data.tar.gz: 6ce126558a2d99e3b6aba03527d5b4ce8954ff3a99a13ee25f54dd6fa0141980
3
+ metadata.gz: fe94c46bcf37e8f5fd38dcc5ccaa17e3490d70f79b083ffd4f753711e8df38ab
4
+ data.tar.gz: ff8fd7d82daa4acac453b15c727b96c03edf8fa2f1d52c772b75b24cd0df2792
5
5
  SHA512:
6
- metadata.gz: 54e48e83a98845d56d6dc4c1843ebf8728fd74ffd3e7670e826c0fdbf3ef297359c18f5b27c2fac0757da0fab5e28d6c19ab084ac1842c5615d06cf52a0a94f8
7
- data.tar.gz: 224aca0535d196fa3b348da69af9e4cbcc46276af8d36b7b58f6e35811704f93506ff31df7449e9aae9ebd252c8b5c035fa581dc4d500a5bade82d0d15f37ddc
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.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
@@ -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,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]
@@ -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.0'
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.0
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
  - - ">="