foobara-mcp-connector 0.0.3 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7f2399bed1c8f29efdfd89bf12b38251d74eece5ece41708e5ce04bc8838fd3
4
- data.tar.gz: e73a8e7afecf4792a62c87097b78488d52f7913e752c7d63ccc37ede25a2505d
3
+ metadata.gz: 907ef0b728ab96d109ef5db1a5938a322537dc97b442d36386fcdbb8826fbcee
4
+ data.tar.gz: afef5e6b376f2f50cc630b535d0edc0f110d4421c5540ce33322b55e4ffc5c89
5
5
  SHA512:
6
- metadata.gz: 6f007511b3ea56495751b0d184b79190d6a07efc19e142bfa6e557f5ea039523762fb6ac0da55e67a89ffde52be40317e44a3b7a9c17ce97121122fd98139498
7
- data.tar.gz: b2deac186cd83fd33268ca054f3d022c8ae47e60849d3f1f9347e608c709645d85a33107c4210e5b0b7dde9e07c8b610759841d1f87496fcd5f208f095063351
6
+ metadata.gz: a43ce9d80d6f3540b4cb62e52c616758d1fb322d43d418405e90bc2226ac30406663659171c2f87e43483d4d20118569777cf3b0ff04cf3f5d29ba278917fc6a
7
+ data.tar.gz: c8a6234d60d32123cd97cc7fd6abfe8f98cf183b43e627b079094806f84ff4a159772da5002d0f895dd88b56e32d12b88103546e1cd3de165c8441b408ee05d0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [0.0.5] - 2025-05-03
2
+
3
+ - Handle changes to CommandConnector interface
4
+
5
+ ## [0.0.4] - 2025-04-14
6
+
7
+ - Do not log requests/responses/errors by default
8
+
1
9
  ## [0.0.3] - 2025-04-14
2
10
 
3
11
  - Grab version from gemspec instead of version.rb
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
@@ -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 < StandardError; end
6
- class InvalidJsonrpcMethodError < StandardError; end
7
- class InvalidJsonrpcParamsError < StandardError; end
8
- class InvalidJsonrpcRequestStructureError < StandardError; end
9
- class EmptyBatchError < StandardError; end
10
- class InvalidJsonError < StandardError; end
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("Could not parse request: #{e.message}")
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("Unsupported jsonrpc version: #{parsed_request_body["jsonrpc"]}")
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("An empty array/batch is not allowed")
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
@@ -32,57 +32,49 @@ module Foobara
32
32
  "This is a Foobara MCP command connector which exposes Foobara commands to you as tools that you can invoke."
33
33
  end
34
34
 
35
- # TODO: how to stream content out instead of buffering it up?
36
35
  def run(*args, **opts, &)
37
- File.open("mcp-connector.log", "a") do |f|
38
- f.puts("#{Time.now}: Request: #{args.first.strip}")
39
- f.flush
40
- end
41
- super.body.tap do |response|
42
- File.open("mcp-connector.log", "a") do |f|
43
- f.puts("#{Time.now}: Response: #{response&.strip}")
44
- f.flush
45
- end
46
- end
47
- rescue => e
48
- File.open("mcp-connector.log", "a") do |f|
49
- f.puts("#{Time.now}: Error: #{e}")
50
- f.flush
51
- end
52
- raise
36
+ super.body
53
37
  end
54
38
 
55
39
  def run_stdio_server(io_in: $stdin, io_out: $stdout, io_err: $stderr)
56
40
  StdioRunner.new(self).run(io_in: io_in, io_out: io_out, io_err: io_err)
57
41
  end
58
42
 
59
- def request_to_command(request)
60
- action = request.action
61
-
43
+ def request_to_command_class(request)
62
44
  return if request.error?
63
45
 
64
- case action
65
- when "initialize"
66
- command_class = find_builtin_command_class("Initialize")
67
- when "notifications/initialized", "notifications/cancelled", "notifications/progress",
68
- "notifications/roots/list_changed"
69
- command_class = find_builtin_command_class("Noop")
70
- else
71
- return super
72
- end
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
73
55
 
74
- full_command_name = command_class.full_command_name
75
- inputs = (request.params || {}).merge(request:)
56
+ command_class = find_builtin_command_class(builtin_command_class_name)
76
57
 
77
- transformed_command_class = transformed_command_from_name(full_command_name) ||
78
- transform_command_class(command_class)
58
+ full_command_name = command_class.full_command_name
79
59
 
80
- transformed_command_class.new(inputs)
60
+ transformed_command_from_name(full_command_name) || transform_command_class(command_class)
81
61
  rescue CommandConnector::NoCommandFoundError => e
82
62
  request.error = e
83
63
  nil
84
64
  end
85
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
+
86
78
  # TODO: figure out how to support multiple sessions
87
79
  def session_created(session)
88
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 < StandardError; end
7
- class MethodNotYetSupportedError < StandardError; end
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, "#{method} not yet supported!"
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.3
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: 1980-01-02 00:00:00.000000000 Z
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.7
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)