Class: Insika::McpToolIngestor

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/mcp_tool_ingestor.rb

Overview

LIVE MCP ingestion (/ spec): discovers the tools of an MCP instance at RUNTIME (no hand-written manifest) and ingests them as data-tools. Given an McpStore instance + an INJECTABLE MCP client (duck-typed: #list_tools -> [{name, description, inputSchema}]), it builds a ToolManifest and REUSES the ingestion path (the:import_tools Command: batch upsert into the ToolStore + hot reload + per-tool report + partial- failure isolation R4). The ToolManifest MCP adapter (inputSchema) is reused — no schema parsing here.

GENERIC: nothing here mentions a consumer/gateway. The MCP instance is DATA in the store.

BINDING STRATEGY (this stage's choice, bounded):

Each discovered tool becomes an HTTP data-tool that makes a JSON-RPC 2.0
`tools/call` POST to the instance endpoint (url). The tool name is resolved
at INGESTION (literal in the body); the model's arguments come in as `{{param}}`
per TOP-level property of the inputSchema (with quoting by type — strings
quoted, others raw, via the DataDefinedTool :body encode). This way the tool
runs through the SAME HTTP path as the other data-tools (egress guard, secret
headers, hot reload) — no new execution code.

Each tool gets `group: "mcp:<instance>"` so the per-group gating
(tools_allow_groups) works for free.

DEFERRED / OUT-OF-SCOPE (documented — spec):

- Real MCP transport: only instances with a `url` (http transport) are ingestible;
stdio has no HTTP endpoint -> raises a clear error (later work).
- MCP session lifecycle (initialize/negotiation/session-id/notifications) and the
UNWRAP of the `tools/call` response (`{content:[{type,text}]}`) — the binding
makes a stateless POST and returns the raw body (extract body_raw).
- Credential injection (the instance `env`) as an auth header in the HTTP
binding: the `env` is consumed by a real MCP client (deferred), not mapped
to a header here.
- Tools whose name/top-level property is outside the ToolDefinition NAME_RE
(uppercase/hyphens) are ISOLATED into `errors[]` by the import (R4).

Instance Method Summary collapse

Constructor Details

#initialize(mcp_store:, import_tools:, client_factory: nil) ⇒ McpToolIngestor

Returns a new instance of McpToolIngestor.



41
42
43
44
45
46
47
# File 'lib/insika/mcp_tool_ingestor.rb', line 41

def initialize(mcp_store:, import_tools:, client_factory: nil)
  @mcp_store = mcp_store
  @import_tools = import_tools
  # Per-instance client factory (default: minimal JSON-RPC HTTP client).
  # Injectable for tests (Fake) and to swap for a real transport later.
  @client_factory = client_factory || method(:default_client)
end

Instance Method Details

#ingest(name, client: nil) ⇒ Object

Discovers + ingests the tools of instance name. client is injectable (Fake in tests); absent -> the factory builds one from the record. -> the import_tools report + instance: ({ instance:, version:, created:, updated:, errors: }).



52
53
54
55
56
# File 'lib/insika/mcp_tool_ingestor.rb', line 52

def ingest(name, client: nil)
  manifest = manifest_for(name, client: client)
  report = @import_tools.call(Insika::Command.build(:import_tools, manifest, transport: :internal))
  report.merge(instance: name.to_s)
end

#manifest_for(name, client: nil) ⇒ Object

Discovers the tools and builds the manifest Hash (without ingesting) — isolable for testing.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/insika/mcp_tool_ingestor.rb', line 59

def manifest_for(name, client: nil)
  record = @mcp_store.get_raw(name.to_s)
  raise Insika::NotFoundError, "MCP instance '#{name}' not found" if record.nil?
  raise Insika::ValidationError, "MCP instance '#{name}' is disabled" unless record["enabled"]

  url = presence(record["url"])
  if url.nil?
    raise Insika::ValidationError,
          "MCP instance '#{name}' has no url: live ingestion requires HTTP transport " \
          "(stdio is later work)"
  end

  tools = Array((client || @client_factory.call(record)).list_tools)
  build_manifest(name.to_s, url, tools)
end