Module: Cuboid::MCP::CoreTools
- Defined in:
- lib/cuboid/mcp/core_tools.rb
Overview
Framework-level MCP tools — instance management. Mounted by ‘Cuboid::MCP::Server::Dispatcher` at the top-level `/mcp` endpoint so an MCP-only client has a way to spawn / list / kill engine instances without going through the REST surface.
Per-instance per-service tools (the application-gem-supplied ones registered via ‘mcp_service_for`) live at `/instances/:instance/<service>` and pick up where these leave off: the typical client lifecycle is
spawn_instance → returns instance_id
POST /instances/<instance_id>/<service> { tools/call ... }
...
kill_instance(id: instance_id)
Defined Under Namespace
Classes: KillInstance, ListInstances, SpawnInstance
Constant Summary collapse
- TOOLS =
[ ListInstances, SpawnInstance, KillInstance ].freeze
Class Method Summary collapse
-
.extract_session_id(server_context) ⇒ Object
‘MCP::ServerContext` doesn’t expose its ‘notification_target` publicly — the only readers are inside the gem.
-
.inject_live_plugin(options, instance_id:, server_context:) ⇒ Object
Mutate (a copy of) the user’s ‘options` so the engine subprocess loads the `live` plugin pointed at this MCP server’s ‘/mcp/live/<token>` route.
-
.instances ⇒ Object
Direct access to the shared in-memory map populated by REST POST /instances + the scheduler-sync flow + spawn_instance below.
-
.instrumented_call ⇒ Object
Wraps a tool body.
- .tools ⇒ Object
Class Method Details
.extract_session_id(server_context) ⇒ Object
‘MCP::ServerContext` doesn’t expose its ‘notification_target` publicly — the only readers are inside the gem. Reach through to the wrapped session for its session_id; if anything in this chain isn’t there (stateless transport, bare invocation) we bail and the live injection is skipped.
221 222 223 224 225 226 |
# File 'lib/cuboid/mcp/core_tools.rb', line 221 def self.extract_session_id( server_context ) return nil if server_context.nil? target = server_context.instance_variable_get( :@notification_target ) return nil if target.nil? || !target.respond_to?( :session_id ) target.session_id end |
.inject_live_plugin(options, instance_id:, server_context:) ⇒ Object
Mutate (a copy of) the user’s ‘options` so the engine subprocess loads the `live` plugin pointed at this MCP server’s ‘/mcp/live/<token>` route. Honors anything the user explicitly set under `plugins.live` (metadata, serializer); only the `url` is auto-injected. Skips the injection silently if the call didn’t arrive over a session that can receive notifications —‘live: true` over a stateless / non-MCP transport has nowhere to send events, so we let the spawn proceed without it.
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/cuboid/mcp/core_tools.rb', line 188 def self.inject_live_plugin( , instance_id:, server_context: ) session_id = extract_session_id( server_context ) return if !session_id return if !::Cuboid::MCP::Live.configured? token = ::Cuboid::MCP::Live.register( session_id: session_id, instance_id: instance_id ) = ( || {}).dup # Normalise plugins to Hash{String => Hash} so the merge below # is well-defined regardless of whether the caller supplied # Hash, Array, or nothing. plugins = case ['plugins'] when Hash then ['plugins'].dup when Array then ['plugins'].each_with_object({}) { |n, h| h[n.to_s] = {} } else {} end live_opts = (plugins['live'] || {}).dup live_opts['url'] = ::Cuboid::MCP::Live.url_for( token ) plugins['live'] = live_opts ['plugins'] = plugins end |
.instances ⇒ Object
Direct access to the shared in-memory map populated by REST POST /instances + the scheduler-sync flow + spawn_instance below. Module-level so tools don’t have to mix in the InstanceHelpers context (which carries Sinatra-helper assumptions like ‘session`).
29 30 31 |
# File 'lib/cuboid/mcp/core_tools.rb', line 29 def self.instances ::Cuboid::Server::InstanceHelpers.instances end |
.instrumented_call ⇒ Object
Wraps a tool body. Returns an MCP::Tool::Response that always carries a JSON-encoded ‘text` content for clients that don’t yet consume ‘structuredContent`, and — when the result is structured (Hash/Array) — also a `structuredContent` block matching the tool’s ‘output_schema`. A raised exception is captured and returned as an MCP error response so the MCP server itself stays up for the next call.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/cuboid/mcp/core_tools.rb', line 40 def self.instrumented_call result = yield if result.is_a?( String ) ::MCP::Tool::Response.new( [{ type: 'text', text: result }] ) else ::MCP::Tool::Response.new( [{ type: 'text', text: JSON.pretty_generate( result ) }], structured_content: result ) end rescue => e ::MCP::Tool::Response.new( [{ type: 'text', text: "error: #{e.class}: #{e.}" }], error: true ) end |