Skip to content

Tool Gateway

The Tool Gateway is the central MCP hub (speedwave_<project>_mcp_hub) and the only MCP server exposed directly to Claude. Regardless of how many integrations are enabled, Claude receives access to exactly two tools: search_tools and execute_code.

The search_tools utility discovers available tools across all active integrations, including host OS tools. The execute_code tool routes execution to the appropriate worker container, exposing each integration as a named JavaScript object (such as redmine.listIssueIds()).

Illustrative execute_code snippet
// Claude first uses search_tools to discover these integration functions.
const ids = await redmine.listIssueIds({ project: "speedwave" });
const issues = await batch(ids.map((id) => redmine.getIssue({ id })));
return issues.results;

These named objects serve as an illustrative abstraction rather than a fixed public API contract.

  1. Claude calls search_tools with a search keyword, and the hub returns matching tool definitions with descriptions.

  2. Claude constructs a brief execution script invoking the discovered tools.

  3. Claude calls execute_code with the script, whereupon the hub executes it within a secure sandbox, routes individual calls to their designated workers, and returns the result.

Tools remain hidden until discovered by search_tools, which keeps prompt token consumption low on large projects. A worker can optionally make specific tools visible upfront by setting _meta: { deferLoading: false }.

Each worker container mounts only its own service credentials in read-only mode; the central hub stores no credentials. The guarantees behind this architecture are covered in Isolation model. Host OS tools operate differently: the hub accesses them on the host via an HTTP bridge that resolves only explicitly declared service names.

flowchart LR
  Claude["Claude"] -->|search_tools| Hub
  Claude -->|execute_code| Hub["MCP hub (port 4000, zero credentials)"]
  Hub -->|"gitlab.getMrFull()"| GL["gitlab worker (/tokens:ro)"]
  Hub -->|"redmine.listIssueIds()"| RM["redmine worker (/tokens:ro)"]
  Hub -->|"os.createEvent() via HTTP bridge"| OS["host OS tools"]

The execute_code environment blocks sensitive APIs (eval, require, process, direct filesystem access, and outbound networking) before script execution begins, exposing only a strict allowlist of standard global objects such as JSON and Array.

Heavier isolation layers were evaluated and rejected: isolated-vm was in maintenance mode with binaries incompatible with the Node 24 ABI, quickjs introduced issues with batch() and Promise.allSettled across asynchronous suspensions, and the Node vm module did not provide a dependable security boundary. For these reasons, execute_code runs on an AsyncFunction wrapper with denied APIs, backed by hardened container boundaries.

Even in the event of sandbox escapes, the code remains contained within a hardened container holding no sensitive credentials, and subsequent operations are recorded in the audit log.

Before errors reach Claude, the hub sanitizes stack traces: local file paths are replaced with [file], line numbers are stripped, and message lengths are capped at 500 characters. When an unknown method is called, the hub provides a list of valid methods for that object, and identifiers matching service_method receive camelCase suggestions. Each error includes a retryable flag (set to true on timeouts). This context is maintained only within the active session; the hub retains no state between sessions.

The hub also tokenizes integration results before they reach Claude. It functions as one of two enforcement boundaries sharing the same engine: a local per-project proxy similarly scans outbound requests sent to the model provider. See Tokenization for complete details. Detections from both boundaries are logged as aggregated counters categorized by layer, category, and action in audit-proxy.jsonl and audit-hub.jsonl under ~/.speedwave/audit/<project>/. The hub independently records each execute_code call, its operation type, target integration, and passed parameters.

Built-in workers and external plugins share identical discovery mechanisms and _meta handling; there is no hardcoded list of built-in tools. If a worker is unavailable during hub initialization, a placeholder entry is created until background health checks detect its availability. See Workers for details on worker container architecture.