ask-mcp 0.2.0 → 0.4.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 +33 -28
- data/README.md +73 -23
- data/lib/ask/mcp/adapters/{ask_tool_server.rb → tool_server.rb} +16 -11
- data/lib/ask/mcp/server/stdio.rb +61 -22
- data/lib/ask/mcp/version.rb +1 -1
- data/lib/ask/mcp.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ae155e99332cb2a3fcec6b65c6d0ac20aa9b2b81b27f1e3aa3c667dc0c7a42fb
|
|
4
|
+
data.tar.gz: af84c5f5aae82c640e2c12316cde40444fe7733d6ad5aaddd6780242b98e1a1c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 62f473ff0fdc35b25adcdadf3aeb2a65c330767f7b73cc85bed204a32bff7fcc28f0348c2ee3f8e8ae1e3d7eaf3977714af67d67038c1be990d9feca2199d81f
|
|
7
|
+
data.tar.gz: e511364526282fcb429740dbd65bbce3e51d0bde06a06cb672e33a0c30b1b09e1971c663145f1b272d7e01015060349447bb7d53081b8dd9813dea02f19591e9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,40 +1,45 @@
|
|
|
1
|
-
## [0.
|
|
1
|
+
## [0.4.0] - 2026-06-26
|
|
2
2
|
|
|
3
3
|
### Added
|
|
4
|
-
-
|
|
5
|
-
|
|
6
|
-
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- `
|
|
4
|
+
- Request ID deduplication — retried `tools/call` with the same ID returns
|
|
5
|
+
cached result instead of re-executing the tool.
|
|
6
|
+
- SIGTERM/SIGHUP graceful shutdown — closes stdin to unblock the read loop
|
|
7
|
+
so the current tool call can finish before the process exits.
|
|
8
|
+
- Configurable `tool_timeout` option — prevents hung tool calls from
|
|
9
|
+
blocking the server indefinitely.
|
|
10
|
+
- `ping` handler for MCP keepalive support.
|
|
11
|
+
- 10 new tests: request dedup (3), tool timeout (2), SIGTERM (1),
|
|
12
|
+
multiline JSON safety (3), ping (1) — 180 total.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- `Server::Stdio` now echoes back the client's requested protocol version
|
|
16
|
+
instead of hardcoding a value.
|
|
17
|
+
|
|
18
|
+
## [0.3.0] - 2026-06-26
|
|
11
19
|
|
|
12
20
|
### Changed
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
21
|
+
- Renamed `Adapters::AskToolServer` → `Adapters::ToolServer` — duck-typed,
|
|
22
|
+
works with any Ruby object, not just Ask::Tool instances.
|
|
23
|
+
- README rewritten to lead with general-purpose duck-typed server example.
|
|
24
|
+
- ask-rb integration is now a documented sub-section.
|
|
25
|
+
|
|
26
|
+
## [0.2.0] - 2026-06-26
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- Server Runtime: `Ask::MCP::Server::Stdio` with full initialize handshake.
|
|
30
|
+
- `Server.start_stdio` entry point for one-line server setup.
|
|
31
|
+
- 56 new tests (170 total at time of release).
|
|
17
32
|
|
|
18
33
|
## [0.1.1] - 2026-06-25
|
|
19
34
|
|
|
20
35
|
### Changed
|
|
21
|
-
- Major test expansion
|
|
36
|
+
- Major test expansion across all modules.
|
|
22
37
|
|
|
23
38
|
## [0.1.0] - 2026-06-10
|
|
24
39
|
|
|
25
40
|
### Added
|
|
26
|
-
- Core MCP client with
|
|
27
|
-
- stdio
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
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
|
|
36
|
-
- 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
|
|
39
|
-
- Factory methods: `from_stdio`, `from_sse`, `from_http`, `connect`
|
|
40
|
-
- Ability to cache or bypass caching for tools/resources/prompts
|
|
41
|
+
- Core MCP client with JSON-RPC 2.0 message layer.
|
|
42
|
+
- stdio, SSE, and Streamable HTTP transports.
|
|
43
|
+
- Tool, Resource, Prompt data models.
|
|
44
|
+
- OAuth 2.1 and token-based authentication.
|
|
45
|
+
- ask-agent integration via AskTool adapter.
|
data/README.md
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/ask-mcp)
|
|
4
4
|
|
|
5
|
-
**Model Context Protocol (MCP) client for Ruby.** Connect to MCP
|
|
6
|
-
stdio, SSE, or Streamable HTTP transports.
|
|
7
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
`
|
|
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
|
|
56
|
-
|
|
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:
|
|
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/
|
|
78
|
+
"args": ["/path/to/your/server.rb"]
|
|
73
79
|
}
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
82
|
```
|
|
77
83
|
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
└──
|
|
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
|
|
7
|
-
# calls. This is the server-direction adapter — it takes
|
|
8
|
-
#
|
|
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
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
|
|
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
|
data/lib/ask/mcp/server/stdio.rb
CHANGED
|
@@ -1,70 +1,80 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require "timeout"
|
|
4
5
|
|
|
5
6
|
module Ask
|
|
6
7
|
module MCP
|
|
7
8
|
class Server
|
|
8
9
|
# MCP server over stdio transport.
|
|
9
|
-
#
|
|
10
|
-
# Reads JSON-RPC 2.0 messages from stdin and writes responses to stdout.
|
|
11
|
-
# Designed to be spawned as a child process by MCP clients (Codex Desktop,
|
|
12
|
-
# Claude Code, etc.) with command/args config.
|
|
13
|
-
#
|
|
14
|
-
# Usage:
|
|
15
|
-
# Server::Stdio.new(name: "my-server", tools: my_tools).start
|
|
16
|
-
#
|
|
17
10
|
class Stdio
|
|
18
|
-
|
|
11
|
+
MAX_RESULT_CACHE = 100
|
|
19
12
|
|
|
20
13
|
attr_reader :name, :tools, :capabilities, :resources, :prompts
|
|
21
14
|
|
|
22
|
-
def initialize(name:, tools: [], capabilities: {}, resources: {}, prompts: {},
|
|
15
|
+
def initialize(name:, tools: [], capabilities: {}, resources: {}, prompts: {},
|
|
16
|
+
debug: false, tool_timeout: nil)
|
|
23
17
|
@name = name
|
|
24
18
|
@capabilities = capabilities
|
|
25
19
|
@resources = resources
|
|
26
20
|
@prompts = prompts
|
|
27
21
|
@debug = debug
|
|
22
|
+
@tool_timeout = tool_timeout
|
|
28
23
|
|
|
29
|
-
@adapter = Adapters::
|
|
24
|
+
@adapter = Adapters::ToolServer.new(tools || [])
|
|
30
25
|
@initialized = false
|
|
31
26
|
@running = false
|
|
27
|
+
@shutdown_requested = false
|
|
28
|
+
@result_cache = {}
|
|
32
29
|
end
|
|
33
30
|
|
|
34
|
-
# Start the server (blocking). Processes stdin until EOF.
|
|
35
31
|
def start
|
|
36
32
|
@running = true
|
|
37
33
|
$stdout.sync = true
|
|
38
34
|
|
|
35
|
+
# Graceful shutdown: on TERM/HUP, set flag and close stdin to
|
|
36
|
+
# unblock the read loop so the current tool call can finish.
|
|
37
|
+
trap("TERM") { graceful_shutdown }
|
|
38
|
+
trap("HUP") { graceful_shutdown }
|
|
39
|
+
|
|
39
40
|
debug_log "Server starting: #{@name} (PID #{Process.pid})"
|
|
40
41
|
debug_log "Tools: #{@adapter.definitions.map { |d| d[:name] }.join(', ')}"
|
|
41
42
|
|
|
42
|
-
while @running && (line = $stdin.gets)
|
|
43
|
+
while @running && !@shutdown_requested && (line = $stdin.gets)
|
|
43
44
|
line = line.strip
|
|
44
45
|
next if line.empty?
|
|
45
|
-
|
|
46
46
|
process_line(line)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
debug_log "stdin closed — exiting"
|
|
50
50
|
rescue Errno::EBADF, IOError
|
|
51
|
-
# stdin closed externally
|
|
51
|
+
# stdin closed externally (e.g. from trap handler)
|
|
52
|
+
rescue SignalException
|
|
53
|
+
# SIGTERM or SIGHUP during blocked read
|
|
52
54
|
ensure
|
|
53
55
|
@running = false
|
|
56
|
+
@shutdown_requested = false
|
|
57
|
+
trap("TERM", "DEFAULT")
|
|
58
|
+
trap("HUP", "DEFAULT")
|
|
54
59
|
end
|
|
55
60
|
|
|
56
|
-
# Stop the server (non-blocking, closes stdin to unblock the read loop).
|
|
57
61
|
def stop
|
|
58
62
|
@running = false
|
|
59
63
|
end
|
|
60
64
|
|
|
61
|
-
# Is the server still running?
|
|
62
65
|
def running?
|
|
63
66
|
@running
|
|
64
67
|
end
|
|
65
68
|
|
|
66
69
|
private
|
|
67
70
|
|
|
71
|
+
def graceful_shutdown
|
|
72
|
+
@shutdown_requested = true
|
|
73
|
+
# Close stdin to unblock $stdin.gets so the signal handler
|
|
74
|
+
# returns promptly and the process exits cleanly.
|
|
75
|
+
$stdin.close rescue nil
|
|
76
|
+
end
|
|
77
|
+
|
|
68
78
|
def process_line(line)
|
|
69
79
|
msg = JSON.parse(line, symbolize_names: true)
|
|
70
80
|
handle_message(msg)
|
|
@@ -90,17 +100,20 @@ module Ask
|
|
|
90
100
|
when "tools/call"
|
|
91
101
|
return send_error(id, -32000, "Server not initialized") unless @initialized
|
|
92
102
|
handle_tool_call(id, params)
|
|
103
|
+
when "ping"
|
|
104
|
+
send_result(id, {}) if has_id
|
|
93
105
|
else
|
|
94
106
|
debug_log "Unknown method: #{method}"
|
|
95
107
|
send_error(id, -32601, "Method not found: #{method}") if has_id
|
|
96
108
|
end
|
|
97
109
|
end
|
|
98
110
|
|
|
99
|
-
def handle_initialize(id,
|
|
111
|
+
def handle_initialize(id, params)
|
|
100
112
|
@initialized = true
|
|
101
|
-
|
|
113
|
+
client_version = params[:protocolVersion] || PROTOCOL_VERSION
|
|
114
|
+
debug_log "Handling initialize (id=#{id.inspect}, version=#{client_version})"
|
|
102
115
|
send_result(id, {
|
|
103
|
-
protocolVersion:
|
|
116
|
+
protocolVersion: client_version,
|
|
104
117
|
capabilities: @capabilities,
|
|
105
118
|
serverInfo: {
|
|
106
119
|
name: @name,
|
|
@@ -111,20 +124,41 @@ module Ask
|
|
|
111
124
|
end
|
|
112
125
|
|
|
113
126
|
def handle_tools_list(id)
|
|
114
|
-
debug_log "Handling tools/list (id=#{id.inspect})"
|
|
115
127
|
defs = @adapter.definitions
|
|
116
128
|
debug_log "tools/list returning #{defs.length} tool definitions"
|
|
117
129
|
send_result(id, { tools: defs })
|
|
118
130
|
end
|
|
119
131
|
|
|
120
132
|
def handle_tool_call(id, params)
|
|
133
|
+
cache_key = id.to_s
|
|
134
|
+
|
|
135
|
+
# Return cached result for retried requests (same ID, already processed)
|
|
136
|
+
if @result_cache.key?(cache_key)
|
|
137
|
+
debug_log "Returning cached result for id=#{id}"
|
|
138
|
+
return send_result(id, @result_cache[cache_key])
|
|
139
|
+
end
|
|
140
|
+
|
|
121
141
|
tool_name = params[:name].to_s
|
|
122
142
|
arguments = params[:arguments] || {}
|
|
123
143
|
|
|
124
144
|
debug_log "Handling tools/call: #{tool_name} (id=#{id.inspect})"
|
|
125
145
|
|
|
126
|
-
result = @
|
|
146
|
+
result = if @tool_timeout
|
|
147
|
+
Timeout.timeout(@tool_timeout) { @adapter.call(tool_name, arguments) }
|
|
148
|
+
else
|
|
149
|
+
@adapter.call(tool_name, arguments)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
@result_cache[cache_key] = result
|
|
153
|
+
trim_cache
|
|
154
|
+
|
|
127
155
|
send_result(id, result)
|
|
156
|
+
rescue Timeout::Error
|
|
157
|
+
debug_log "Tool call timed out: #{tool_name}"
|
|
158
|
+
send_result(id, {
|
|
159
|
+
content: [{ type: "text", text: "Tool call timed out: #{tool_name}" }],
|
|
160
|
+
isError: true
|
|
161
|
+
})
|
|
128
162
|
end
|
|
129
163
|
|
|
130
164
|
def send_result(id, result)
|
|
@@ -140,6 +174,11 @@ module Ask
|
|
|
140
174
|
ts = Time.now.strftime("%H:%M:%S.%L")
|
|
141
175
|
$stderr.puts "[#{ts}] [ask-mcp] #{msg}"
|
|
142
176
|
end
|
|
177
|
+
|
|
178
|
+
def trim_cache
|
|
179
|
+
return if @result_cache.size <= MAX_RESULT_CACHE
|
|
180
|
+
@result_cache.shift(@result_cache.size - MAX_RESULT_CACHE)
|
|
181
|
+
end
|
|
143
182
|
end
|
|
144
183
|
end
|
|
145
184
|
end
|
data/lib/ask/mcp/version.rb
CHANGED
data/lib/ask/mcp.rb
CHANGED
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.
|
|
4
|
+
version: 0.4.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
|
|
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/
|
|
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
|