foobara-mcp-connector 0.0.4 → 0.0.5
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 +4 -0
- data/README.md +8 -0
- data/src/jsonrpc_request.rb +33 -10
- data/src/mcp_connector.rb +25 -17
- data/src/request.rb +20 -6
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 907ef0b728ab96d109ef5db1a5938a322537dc97b442d36386fcdbb8826fbcee
|
4
|
+
data.tar.gz: afef5e6b376f2f50cc630b535d0edc0f110d4421c5540ce33322b55e4ffc5c89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a43ce9d80d6f3540b4cb62e52c616758d1fb322d43d418405e90bc2226ac30406663659171c2f87e43483d4d20118569777cf3b0ff04cf3f5d29ba278917fc6a
|
7
|
+
data.tar.gz: c8a6234d60d32123cd97cc7fd6abfe8f98cf183b43e627b079094806f84ff4a159772da5002d0f895dd88b56e32d12b88103546e1cd3de165c8441b408ee05d0
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -6,6 +6,8 @@ Exposes Foobara commands according to the Model Context Protocol (MCP) specifica
|
|
6
6
|
* [Foobara::McpConnector](#foobaramcpconnector)
|
7
7
|
* [Installation](#installation)
|
8
8
|
* [Usage](#usage)
|
9
|
+
* [Code demo video!](#code-demo-video)
|
10
|
+
* [Code examples](#code-examples)
|
9
11
|
* [Super basic example](#super-basic-example)
|
10
12
|
* [An example with entities](#an-example-with-entities)
|
11
13
|
* [A destructive example](#a-destructive-example)
|
@@ -21,6 +23,12 @@ Typical stuff: add `gem "foobara-mcp-connector` to your Gemfile or .gemspec file
|
|
21
23
|
|
22
24
|
## Usage
|
23
25
|
|
26
|
+
### Code demo video!
|
27
|
+
|
28
|
+
You can watch a code demo here: https://youtu.be/_w3ZHdiJEGU
|
29
|
+
|
30
|
+
### Code examples
|
31
|
+
|
24
32
|
You can find examples in `examples/`
|
25
33
|
|
26
34
|
### Super basic example
|
data/src/jsonrpc_request.rb
CHANGED
@@ -2,12 +2,29 @@ module Foobara
|
|
2
2
|
class McpConnector < CommandConnector
|
3
3
|
# Isolating jsonrpc specific logic to this abstract class
|
4
4
|
class JsonrpcRequest < CommandConnector::Request
|
5
|
-
class InvalidJsonrpcVersionError <
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
class
|
10
|
-
|
5
|
+
class InvalidJsonrpcVersionError < Foobara::Error
|
6
|
+
context({})
|
7
|
+
end
|
8
|
+
|
9
|
+
class InvalidJsonrpcMethodError < Foobara::Error
|
10
|
+
context({})
|
11
|
+
end
|
12
|
+
|
13
|
+
class InvalidJsonrpcParamsError < Foobara::Error
|
14
|
+
context({})
|
15
|
+
end
|
16
|
+
|
17
|
+
class InvalidJsonrpcRequestStructureError < Foobara::Error
|
18
|
+
context({})
|
19
|
+
end
|
20
|
+
|
21
|
+
class EmptyBatchError < Foobara::Error
|
22
|
+
context({})
|
23
|
+
end
|
24
|
+
|
25
|
+
class InvalidJsonError < Foobara::Error
|
26
|
+
context({})
|
27
|
+
end
|
11
28
|
|
12
29
|
# TODO: push response into base class?
|
13
30
|
attr_accessor :raw_request_json, :parsed_request_body, :request_id, :batch, :is_batch_child
|
@@ -45,7 +62,9 @@ module Foobara
|
|
45
62
|
begin
|
46
63
|
JSON.parse(raw_request_json)
|
47
64
|
rescue => e
|
48
|
-
self.error = InvalidJsonError.new(
|
65
|
+
self.error = InvalidJsonError.new(
|
66
|
+
message: "Could not parse request: #{e.message}"
|
67
|
+
)
|
49
68
|
error.set_backtrace(caller)
|
50
69
|
nil
|
51
70
|
end
|
@@ -62,7 +81,9 @@ module Foobara
|
|
62
81
|
|
63
82
|
def verify_jsonrpc_version
|
64
83
|
if parsed_request_body["jsonrpc"] != "2.0"
|
65
|
-
self.error = InvalidJsonrpcVersionError.new(
|
84
|
+
self.error = InvalidJsonrpcVersionError.new(
|
85
|
+
message: "Unsupported jsonrpc version: #{parsed_request_body["jsonrpc"]}"
|
86
|
+
)
|
66
87
|
error.set_backtrace(caller)
|
67
88
|
end
|
68
89
|
end
|
@@ -93,7 +114,9 @@ module Foobara
|
|
93
114
|
|
94
115
|
def validate_batch_not_empty
|
95
116
|
if batch? && batch.empty?
|
96
|
-
self.error = EmptyBatchError.new(
|
117
|
+
self.error = EmptyBatchError.new(
|
118
|
+
message: "An empty array/batch is not allowed"
|
119
|
+
)
|
97
120
|
error.set_backtrace(caller)
|
98
121
|
end
|
99
122
|
end
|
@@ -101,7 +124,7 @@ module Foobara
|
|
101
124
|
def validate_request_structure
|
102
125
|
unless parsed_request_body.is_a?(::Hash)
|
103
126
|
self.error = InvalidJsonrpcRequestStructureError.new(
|
104
|
-
"Invalid jsonrpc request structure. Expected a hash but got a #{parsed_request_body.class}"
|
127
|
+
message: "Invalid jsonrpc request structure. Expected a hash but got a #{parsed_request_body.class}"
|
105
128
|
)
|
106
129
|
error.set_backtrace(caller)
|
107
130
|
end
|
data/src/mcp_connector.rb
CHANGED
@@ -40,33 +40,41 @@ module Foobara
|
|
40
40
|
StdioRunner.new(self).run(io_in: io_in, io_out: io_out, io_err: io_err)
|
41
41
|
end
|
42
42
|
|
43
|
-
def
|
44
|
-
action = request.action
|
45
|
-
|
43
|
+
def request_to_command_class(request)
|
46
44
|
return if request.error?
|
47
45
|
|
48
|
-
case action
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
46
|
+
builtin_command_class_name = case request.action
|
47
|
+
when "initialize"
|
48
|
+
"Initialize"
|
49
|
+
when "notifications/initialized", "notifications/cancelled",
|
50
|
+
"notifications/progress", "notifications/roots/list_changed"
|
51
|
+
"Noop"
|
52
|
+
else
|
53
|
+
return super
|
54
|
+
end
|
57
55
|
|
58
|
-
|
59
|
-
inputs = (request.params || {}).merge(request:)
|
56
|
+
command_class = find_builtin_command_class(builtin_command_class_name)
|
60
57
|
|
61
|
-
|
62
|
-
transform_command_class(command_class)
|
58
|
+
full_command_name = command_class.full_command_name
|
63
59
|
|
64
|
-
|
60
|
+
transformed_command_from_name(full_command_name) || transform_command_class(command_class)
|
65
61
|
rescue CommandConnector::NoCommandFoundError => e
|
66
62
|
request.error = e
|
67
63
|
nil
|
68
64
|
end
|
69
65
|
|
66
|
+
def request_to_command_inputs(request)
|
67
|
+
return if request.error?
|
68
|
+
|
69
|
+
case request.action
|
70
|
+
when "initialize", "notifications/initialized", "notifications/cancelled", "notifications/progress",
|
71
|
+
"notifications/roots/list_changed"
|
72
|
+
(request.params || {}).merge(request:)
|
73
|
+
else
|
74
|
+
super
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
70
78
|
# TODO: figure out how to support multiple sessions
|
71
79
|
def session_created(session)
|
72
80
|
self.current_session = session
|
data/src/request.rb
CHANGED
@@ -3,8 +3,15 @@ require_relative "jsonrpc_request"
|
|
3
3
|
module Foobara
|
4
4
|
class McpConnector < CommandConnector
|
5
5
|
class Request < JsonrpcRequest
|
6
|
-
class FoobaraCommandsDoNotAcceptArraysError <
|
7
|
-
|
6
|
+
class FoobaraCommandsDoNotAcceptArraysError < Foobara::Error
|
7
|
+
context({})
|
8
|
+
end
|
9
|
+
|
10
|
+
class MethodNotYetSupportedError < Foobara::Error
|
11
|
+
context({})
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :action_loaded
|
8
15
|
|
9
16
|
def full_command_name
|
10
17
|
return if error || batch?
|
@@ -24,11 +31,11 @@ module Foobara
|
|
24
31
|
unless inputs.is_a?(::Hash)
|
25
32
|
self.error = if inputs.is_a?(::Array)
|
26
33
|
FoobaraCommandsDoNotAcceptArraysError.new(
|
27
|
-
"Foobara commands do not accept arrays as inputs"
|
34
|
+
message: "Foobara commands do not accept arrays as inputs"
|
28
35
|
)
|
29
36
|
else
|
30
37
|
InvalidJsonrpcParamsError.new(
|
31
|
-
"Invalid MCP arguments structure. Expected a hash got a #{inputs.class}"
|
38
|
+
message: "Invalid MCP arguments structure. Expected a hash got a #{inputs.class}"
|
32
39
|
)
|
33
40
|
end
|
34
41
|
|
@@ -46,7 +53,14 @@ module Foobara
|
|
46
53
|
!error && super
|
47
54
|
end
|
48
55
|
|
56
|
+
def error?
|
57
|
+
action unless action_loaded
|
58
|
+
super
|
59
|
+
end
|
60
|
+
|
49
61
|
def action
|
62
|
+
self.action_loaded = true
|
63
|
+
|
50
64
|
case method
|
51
65
|
when "tools/call"
|
52
66
|
"run"
|
@@ -58,9 +72,9 @@ module Foobara
|
|
58
72
|
method
|
59
73
|
when "completion/complete", "logging/setLevel", "prompts/get", "prompts/list",
|
60
74
|
"resources/list", "resources/read", "resources/subscribe", "resources/unsubscribe"
|
61
|
-
raise MethodNotYetSupportedError
|
75
|
+
raise MethodNotYetSupportedError.new(message: "#{method} not yet supported!")
|
62
76
|
else
|
63
|
-
self.error = InvalidJsonrpcMethodError.new("Unknown method: #{method}")
|
77
|
+
self.error = InvalidJsonrpcMethodError.new(message: "Unknown method: #{method}")
|
64
78
|
error.set_backtrace(caller)
|
65
79
|
nil
|
66
80
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foobara-mcp-connector
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miles Georgi
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 2025-05-03 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: foobara
|
@@ -78,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
78
|
- !ruby/object:Gem::Version
|
79
79
|
version: '0'
|
80
80
|
requirements: []
|
81
|
-
rubygems_version: 3.6.
|
81
|
+
rubygems_version: 3.6.2
|
82
82
|
specification_version: 4
|
83
83
|
summary: Gives an easy way to expose your Foobara commands to tools like Claude Code
|
84
84
|
via the Model Context Protocol (MCP)
|