Class: Foobara::McpConnector

Inherits:
CommandConnector show all
Defined in:
foobara-mcp-connector-0.0.4/src/mcp_connector.rb,
foobara-mcp-connector-0.0.4/src/request.rb,
foobara-mcp-connector-0.0.4/src/session.rb,
foobara-mcp-connector-0.0.4/src/stdio_runner.rb,
foobara-mcp-connector-0.0.4/src/commands/noop.rb,
foobara-mcp-connector-0.0.4/src/jsonrpc_request.rb,
foobara-mcp-connector-0.0.4/src/commands/initialize.rb,
foobara-mcp-connector-0.0.4/src/commands/list_commands.rb

Overview

TODO: somehow give a way to specify an association depth when registering commands?

Defined Under Namespace

Modules: Commands Classes: JsonrpcRequest, Request, Session, StdioRunner

Instance Attribute Summary collapse

Attributes inherited from CommandConnector

#authenticator, #capture_unknown_error, #command_registry

Instance Method Summary collapse

Methods inherited from CommandConnector

#all_exposed_commands, allowed_rules_to_register, #authenticate, authenticator_registry, #build_command, #build_response, #connect, #connect_delayed, #delayed_connections, find_builtin_command_class, #find_builtin_command_class, #foobara_manifest, #lookup_command, #mutate_response, #normalize_manifest, #patch_up_broken_parents_for_errors_with_missing_command_parents, #process_delayed_connections, register_allowed_rule, register_authenticator, #request_to_response, #run_command, to_authenticator, #transform_command_class, #type_from_name

Constructor Details

#initialize(capture_unknown_error: true, server_name: default_server_name, server_version: default_server_version, instructions: default_instructions, default_serializers: Foobara::CommandConnectors::Serializers::AtomicSerializer) ⇒ McpConnector

Returns a new instance of McpConnector.



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 6

def initialize(
  *,
  capture_unknown_error: true,
  server_name: default_server_name,
  server_version: default_server_version,
  instructions: default_instructions,
  default_serializers: Foobara::CommandConnectors::Serializers::AtomicSerializer,
  **,
  &
)
  self.server_name = server_name
  self.server_version = server_version
  self.instructions = instructions

  super(*, capture_unknown_error:, default_serializers:, **, &)
end

Instance Attribute Details

#current_sessionObject

Returns the value of attribute current_session.



4
5
6
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 4

def current_session
  @current_session
end

#instructionsObject

Returns the value of attribute instructions.



4
5
6
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 4

def instructions
  @instructions
end

#server_nameObject

Returns the value of attribute server_name.



4
5
6
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 4

def server_name
  @server_name
end

#server_versionObject

Returns the value of attribute server_version.



4
5
6
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 4

def server_version
  @server_version
end

Instance Method Details

#build_requestObject



87
88
89
90
91
92
93
94
95
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 87

def build_request(*, **, &)
  request = super

  # We add a serializer to the top-level request but not to the children of a batch requests

  request.serializers = [*request.serializers, json_serializer]

  request
end

#default_instructionsObject



31
32
33
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 31

def default_instructions
  "This is a Foobara MCP command connector which exposes Foobara commands to you as tools that you can invoke."
end

#default_server_nameObject



23
24
25
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 23

def default_server_name
  "Foobara MCP Command connector"
end

#default_server_versionObject



27
28
29
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 27

def default_server_version
  Gem.loaded_specs["foobara-mcp-connector"].version.to_s
end

#json_serializerObject



97
98
99
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 97

def json_serializer
  @json_serializer ||= Foobara::CommandConnectors::Serializers::JsonSerializer.new
end

#request_to_command(request) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 43

def request_to_command(request)
  action = request.action

  return if request.error?

  case action
  when "initialize"
    command_class = find_builtin_command_class("Initialize")
  when "notifications/initialized", "notifications/cancelled", "notifications/progress",
    "notifications/roots/list_changed"
    command_class = find_builtin_command_class("Noop")
  else
    return super
  end

  full_command_name = command_class.full_command_name
  inputs = (request.params || {}).merge(request:)

  transformed_command_class = transformed_command_from_name(full_command_name) ||
                              transform_command_class(command_class)

  transformed_command_class.new(inputs)
rescue CommandConnector::NoCommandFoundError => e
  request.error = e
  nil
end

#run(*args, **opts) ⇒ Object



35
36
37
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 35

def run(*args, **opts, &)
  super.body
end

#run_request(request) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 75

def run_request(request)
  if request.batch? && !request.error
    request.batch.each do |batch_request|
      super(batch_request)
    end

    build_response(request)
  else
    super
  end
end

#run_stdio_server(io_in: $stdin, io_out: $stdout, io_err: $stderr) ⇒ Object



39
40
41
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 39

def run_stdio_server(io_in: $stdin, io_out: $stdout, io_err: $stderr)
  StdioRunner.new(self).run(io_in: io_in, io_out: io_out, io_err: io_err)
end

#serialize_response_body(response) ⇒ Object

TODO: feels awkward needing to override this for such basic/typical behavior TODO: fix this interface/pattern higher-up



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 103

def serialize_response_body(response)
  super

  request = response.request

  unless request.notification?
    # TODO: total hack, clean this up somehow...
    if request.tool_call? && request.success?
      result = response.body[:result]

      result = json_serializer.serialize(result)

      response.body[:result] = { content: [{ type: "text", text: result }] }
    end

    if request.serializer
      response.body = request.serializer.process_value!(response.body)
    end
  end
end

#session_created(session) ⇒ Object

TODO: figure out how to support multiple sessions



71
72
73
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 71

def session_created(session)
  self.current_session = session
end

#set_response_body(response) ⇒ Object

TODO: Should this be implemented on response object instead??



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 125

def set_response_body(response)
  request = response.request

  return if request.notification?

  response.body = if request.valid_batch?
                    request.batch.map do |batched_request|
                      batched_request.response.body
                    end.compact
                  else
                    body = { jsonrpc: "2.0", id: request.request_id }

                    if request.error
                      body.merge(error: { message: request.error.message, code: response.status })
                    else
                      outcome = request.outcome

                      if outcome.success?
                        body.merge(result: outcome.result)
                      else
                        body.merge(error: {
                                     message: outcome.errors_sentence,
                                     data: outcome.errors_hash,
                                     code: response.status
                                   })
                      end
                    end
                  end
end

#set_response_status(response) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'foobara-mcp-connector-0.0.4/src/mcp_connector.rb', line 155

def set_response_status(response)
  return if response.request.valid_batch?

  request = response.request

  response.status = if request.error
                      case request.error
                      when Request::InvalidJsonrpcVersionError, Request::InvalidJsonrpcMethodError,
                        Request::InvalidJsonrpcParamsError, Request::EmptyBatchError,
                        Request::InvalidJsonrpcRequestStructureError
                        -32_600
                      when Request::InvalidJsonError
                        -32_700
                      when CommandConnector::NotFoundError
                        -32_601
                      when Request::FoobaraCommandsDoNotAcceptArraysError
                        -32_602
                      else
                        -32_603
                      end
                    elsif request.success?
                      0
                    else
                      errors = request.error_collection.error_array
                      error = errors.first

                      # Going to steal some http codes to be less confusing
                      case error
                      when Foobara::Entity::NotFoundError
                        # TODO: we should not be coupled to Entities here...
                        # :nocov:
                        404
                        # :nocov:
                      when CommandConnector::UnauthenticatedError
                        # :nocov:
                        401
                        # :nocov:
                      when CommandConnector::NotAllowedError
                        # :nocov:
                        403
                        # :nocov:
                      when CommandConnector::UnknownError
                        -32_603
                      when Foobara::DataError
                        -32_602
                      end || -32_600
                    end
end