ask-mcp 0.2.0 → 0.3.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: 01bf6655c874ece6be6c44d5864e19c844f7edd1fa88c8f90b60b836092bcd90
4
- data.tar.gz: 2cebafa8dd9d36901b5e78e81350c235ef837f1d4a010792f6c7d23982729969
3
+ metadata.gz: 90e04370e4d5b1136abb513220b68beb46a5ca2c74d8f7ca694fa59297f8f32e
4
+ data.tar.gz: 17f2accea77cefc0ea51fedcc3879e219fff47760999b0ba39e0e168d30dbfe1
5
5
  SHA512:
6
- metadata.gz: 292a26a5c625cd6e1b06d214ce5b3cd9e38a7562f8e388b8d2817ee4fc915d6f743301c23466c03d2f5e5f3399607a02ca07c2c32bc179696cb1c71386609d5c
7
- data.tar.gz: 9c6395fbae61b1013fb72013dfd3d069f76956ad25efd88c62444b3f6c33823462a01c4cb91b129f1a7001a5fef3d7580b91986391128aea41b33c253221afda
6
+ metadata.gz: d8a481b9467e8f4d6d4d305da61346855f4d27ac94de77f109ac528e704599c18aee5a9c5deb64f7edb71a71b4e446cc1801149df21891d15eefdcb441eb1d09
7
+ data.tar.gz: b78ccc9a910fbfbeaf2e273ce6169279e4195624871f89356d6564ab6cd46380dd876951544e90b2f3b3b57328ae00180c6d218e1d66afa5416090c5703e1560
data/CHANGELOG.md CHANGED
@@ -1,40 +1,37 @@
1
+ ## [0.3.0] - 2026-06-26
2
+
3
+ ### Changed
4
+ - Renamed `Adapters::AskToolServer` → `Adapters::ToolServer` — the adapter is
5
+ now duck-typed and works with any Ruby object, not just Ask::Tool instances.
6
+ - README fully rewritten: leads with a general-purpose duck-typed server example
7
+ instead of ask-tools-specific code. ask-rb integration is now a sub-section.
8
+ - Gemspec description updated to reflect general-purpose positioning.
9
+
1
10
  ## [0.2.0] - 2026-06-26
2
11
 
3
12
  ### Added
4
13
  - Server Runtime: `Ask::MCP::Server::Stdio` — run as an MCP server over stdio
5
14
  with full initialize handshake, tool discovery, and tool call dispatch.
6
- - `Ask::MCP::Adapters::AskToolServer` — converts `Ask::Tool` instances to MCP
7
- tool definitions and dispatches calls, enabling any Ask::Tool to be exposed
8
- via MCP without modification.
15
+ - `Adapters::AskToolServer` — converts tools to MCP definitions and dispatches calls.
9
16
  - `Ask::MCP::Server.start_stdio` entry point for easy one-line server setup.
10
- - `examples/` — reference usage in README "Running as an MCP Server" section.
11
17
 
12
18
  ### Changed
13
- - Server Runtime adds 56 new tests across adapter, server stdio, integration,
14
- and start_stdio entry point (170 total, up from 114).
15
- - `ask-mcp` gem description updated to reflect client + server capabilities.
16
- - Architecture section updated to include server/ and adapters/ask_tool_server.rb.
19
+ - 56 new tests across adapter, server stdio, integration, and start_stdio (170 total).
17
20
 
18
21
  ## [0.1.1] - 2026-06-25
19
22
 
20
23
  ### Changed
21
- - Major test expansion: Transport tests (SSE/Stdio/StreamableHTTP), MessagesParser, OAuth, Client(17t with cache invalidation), Server(7t), Tool(8t), Messages(17t with serialization), integration tests(7t with mock server). Infrastructure: rubocop, overcommit, CI matrix, gemspec, SimpleCov.
24
+ - Major test expansion: Transport tests, MessagesParser, OAuth, Client, Server, Tool, Messages, integration. Rubocop, overcommit, CI matrix, SimpleCov.
22
25
 
23
26
  ## [0.1.0] - 2026-06-10
24
27
 
25
28
  ### Added
26
29
  - Core MCP client with full JSON-RPC 2.0 message layer
27
- - stdio transport for local process MCP servers
28
- - SSE transport for remote Server-Sent Events MCP servers
29
- - Streamable HTTP transport for remote HTTP MCP servers
30
- - Tool, Resource, and Prompt data models with `from_h`/`to_h` serialization
31
- - Client lifecycle: initialize, capabilities discovery, session management
32
- - Tool calling, resource reading, and prompt retrieval
33
- - Token-based authentication (Bearer/Basic)
34
- - OAuth 2.1 authentication (client credentials + authorization code flows)
35
- - Ask::Tool adapter for integration with ask-agent
30
+ - stdio, SSE, and Streamable HTTP transports
31
+ - Tool, Resource, and Prompt data models
32
+ - Client lifecycle, tool calling, resource reading, prompt retrieval
33
+ - Token-based and OAuth 2.1 authentication
34
+ - Ask::Tool adapter for ask-agent integration
36
35
  - Thread-safe request/response matching with configurable timeouts
37
- - Server-side notifications handling (tools/resources/prompts list changed)
38
- - Comprehensive test suite with mock MCP server
36
+ - Server-side notifications handling
39
37
  - Factory methods: `from_stdio`, `from_sse`, `from_http`, `connect`
40
- - Ability to cache or bypass caching for tools/resources/prompts
data/README.md CHANGED
@@ -2,9 +2,10 @@
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 for Ruby.** Connect to MCP servers via
6
- stdio, SSE, or Streamable HTTP transports. Discover tools, resources, and
7
- prompts. Supports the full MCP protocol with OAuth 2.1 authentication.
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.
8
9
 
9
10
  MCP is the industry standard for LLM tool discovery — the same protocol used by
10
11
  Claude Code, Codex, Cursor, and GitHub Copilot.
@@ -21,44 +22,49 @@ Or add to your Gemfile:
21
22
  gem "ask-mcp", "~> 0.1.0"
22
23
  ```
23
24
 
24
- ## Quick Start
25
+ ## Quick Start — Client
26
+
27
+ Connect to any MCP server and call its tools:
25
28
 
26
29
  ```ruby
27
30
  require "ask/mcp"
28
31
 
29
- # Connect to a local MCP server via stdio
30
32
  client = Ask::MCP.from_stdio("npx", ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"])
31
33
  client.start
32
34
 
33
- # List available tools
34
35
  client.tools.each { |name, tool| puts "#{name}: #{tool.description}" }
35
36
 
36
- # Call a tool
37
37
  result = client.call_tool("read_file", path: "/tmp/test.txt")
38
38
  puts result
39
39
 
40
- # Clean up
41
40
  client.stop
42
41
  ```
43
42
 
43
+ ## Quick Start — Server
44
44
 
45
- ## Running as an MCP Server
46
-
47
- `ask-mcp` can run as a standalone MCP server over stdio, exposing any
48
- `Ask::Tool` instances to MCP-compatible clients (Codex Desktop, Claude Code, etc.).
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:
49
48
 
50
49
  ```ruby
51
50
  require "ask/mcp"
52
- require "ask-tools-shell"
53
- require "ask-web-search"
54
51
 
55
- tools = Ask::Tools::Shell::TOOLS.map(&:new)
56
- tools << Ask::Tools::WebSearch.new
52
+ # Define your tools no base class needed, just duck typing
53
+ class Greeter
54
+ def name; "greet" end
55
+ def description; "Greets someone by name" end
56
+ def params_schema
57
+ { type: "object", properties: { "name" => { "type" => "string" } }, required: ["name"] }
58
+ end
59
+ def call(args = {})
60
+ "Hello, #{args['name']}!"
61
+ end
62
+ end
57
63
 
64
+ # Start the server (blocking — runs until stdin closes)
58
65
  Ask::MCP::Server.start_stdio(
59
66
  name: "my-server",
60
- tools: tools,
61
- capabilities: { tools: {} }
67
+ tools: [Greeter.new]
62
68
  )
63
69
  ```
64
70
 
@@ -69,14 +75,58 @@ Configure your MCP client:
69
75
  "mcpServers": {
70
76
  "my-server": {
71
77
  "command": "ruby",
72
- "args": ["path/to/your/script.rb"]
78
+ "args": ["/path/to/your/server.rb"]
73
79
  }
74
80
  }
75
81
  }
76
82
  ```
77
83
 
78
- For a production example, see [llm-proxy](https://github.com/ask-rb/llm-proxy)
79
- which embeds the same server and adds rate limiting, auth, and monitoring.
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?
88
+
89
+ The result object from `call(args)` should respond to `ok?` (or `ok`) and
90
+ `output` / `error_message`. Use `OpenStruct` for simple cases:
91
+
92
+ ```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
+ )
129
+ ```
80
130
 
81
131
  ## Transports
82
132
 
@@ -183,7 +233,6 @@ wrapped = Ask::MCP::Adapters::AskTool.wrap(client.tools)
183
233
  wrapped.each { |name, adapter| agent.register_tool(adapter.to_ask_tool) }
184
234
  ```
185
235
 
186
-
187
236
  ## Architecture
188
237
 
189
238
  ```
@@ -205,8 +254,9 @@ ask-mcp/
205
254
  │ └── token.rb # Token-based auth
206
255
  └── lib/ask/mcp/adapters/
207
256
  ├── ask_tool.rb # MCP::Tool → Ask::Tool adapter
208
- └── ask_tool_server.rb # Ask::Tool → MCP server adapter (reverse)
257
+ └── tool_server.rb # Duck-typed tools → MCP server adapter
209
258
  ```
259
+
210
260
  ## Development
211
261
 
212
262
  ```bash
@@ -3,19 +3,26 @@
3
3
  module Ask
4
4
  module MCP
5
5
  module Adapters
6
- # Converts Ask::Tool instances into MCP tool definitions and dispatches
7
- # calls. This is the server-direction adapter — it takes Ask::Tool objects
8
- # (from tool packs like ask-tools-shell) and makes them available over MCP.
6
+ # Converts duck-typed tool objects into MCP tool definitions and dispatches
7
+ # calls. This is the server-direction adapter — it takes any objects that
8
+ # respond to +name+, +description+, +params_schema+, and +call(args)+ and
9
+ # exposes them over MCP.
9
10
  #
10
11
  # Usage:
11
- # adapter = AskToolServer.new(Ask::Tools::Shell.all)
12
- # adapter.definitions # => [{ name: "bash", description: "...", inputSchema: {...} }]
13
- # adapter.call("bash", { command: "echo hi" }) # => { content: [...], isError: ... }
14
- class AskToolServer
12
+ # class MyTool
13
+ # def name; "hello" end
14
+ # def description; "Says hello" end
15
+ # def params_schema; nil end
16
+ # def call(args = {}); OpenStruct.new(ok?: true, output: "Hello!") end
17
+ # end
18
+ #
19
+ # adapter = ToolServer.new([MyTool.new])
20
+ # adapter.definitions # => [{ name: "hello", ... }]
21
+ # adapter.call("hello", {}) # => { content: [...], isError: false }
22
+ class ToolServer
15
23
  attr_reader :tools
16
24
 
17
- # @param tools [Array<#call, #name, #description, #params_schema>] tool instances to expose.
18
- # Each tool must respond to name, description, params_schema, and call(args).
25
+ # @param tools [Array<#call, #name, #description, #params_schema>] tool instances to expose
19
26
  def initialize(tools = [])
20
27
  @tools = tools
21
28
  @tool_map = tools.each_with_object({}) { |t, h| h[t.name] = t }
@@ -44,12 +51,10 @@ module Ask
44
51
  return error_result("Tool not found: #{name}")
45
52
  end
46
53
 
47
- # Stringify keys — Ask::Tool subclasses expect string keys
48
54
  normalized = deep_stringify_keys(arguments)
49
55
  result = tool.call(normalized)
50
56
  wrap_result(result)
51
57
  rescue StandardError => e
52
- # If the tool raised Ask::Tool::Halt, treat it as a success
53
58
  if defined?(Ask::Tool::Halt) && e.is_a?(Ask::Tool::Halt)
54
59
  return { content: [{ type: "text", text: e.content.to_s }], isError: false }
55
60
  end
@@ -26,7 +26,7 @@ module Ask
26
26
  @prompts = prompts
27
27
  @debug = debug
28
28
 
29
- @adapter = Adapters::AskToolServer.new(tools || [])
29
+ @adapter = Adapters::ToolServer.new(tools || [])
30
30
  @initialized = false
31
31
  @running = false
32
32
  end
@@ -1,5 +1,5 @@
1
1
  module Ask
2
2
  module MCP
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
data/lib/ask/mcp.rb CHANGED
@@ -28,7 +28,7 @@ module Ask
28
28
 
29
29
  module Adapters
30
30
  autoload :AskTool, "ask/mcp/adapters/ask_tool"
31
- autoload :AskToolServer, "ask/mcp/adapters/ask_tool_server"
31
+ autoload :ToolServer, "ask/mcp/adapters/tool_server"
32
32
  end
33
33
 
34
34
  class Error < StandardError; end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto
@@ -80,8 +80,8 @@ dependencies:
80
80
  - !ruby/object:Gem::Version
81
81
  version: '13.0'
82
82
  description: Connect to MCP servers via stdio, SSE, and Streamable HTTP transports.
83
- Run as an MCP server to expose Ask::Tool instances. Discover tools, resources, and
84
- prompts. OAuth 2.1 authentication.
83
+ Run as an MCP server exposing any Ruby object as a tool. Discover tools, resources,
84
+ and prompts. OAuth 2.1 authentication.
85
85
  email:
86
86
  - kaka@myrrlabs.com
87
87
  executables: []
@@ -95,7 +95,7 @@ files:
95
95
  - lib/ask-mcp.rb
96
96
  - lib/ask/mcp.rb
97
97
  - lib/ask/mcp/adapters/ask_tool.rb
98
- - lib/ask/mcp/adapters/ask_tool_server.rb
98
+ - lib/ask/mcp/adapters/tool_server.rb
99
99
  - lib/ask/mcp/auth/oauth.rb
100
100
  - lib/ask/mcp/auth/token.rb
101
101
  - lib/ask/mcp/client.rb