How to Integrate 33+ CLI Tools with OmniRoute: A Complete Developer Guide

OmniRoute ships with a built‑in catalog that describes more than thirty command‑line interfaces, enabling integration through a three‑layer system that declares metadata in src/shared/constants/cliTools.ts, detects binaries via src/shared/services/cliRuntime.ts, and executes via MCP wrappers in open-sse/mcp-server/tools/.

OmniRoute is an open‑source routing platform that unifies access to AI models and developer tools behind a single API. To integrate 33+ CLI tools with OmniRoute, you work with a declarative catalog, a runtime detection service, and lightweight MCP wrappers that normalize every tool into a streaming, OpenAI‑compatible endpoint. The architecture treats each CLI as a routable resource, handling binary discovery, environment configuration, and output translation automatically.

Three‑Layer Integration Architecture

OmniRoute abstracts CLI tools through three distinct layers: a static catalog for metadata, a runtime scanner for binary discovery, and MCP wrappers for execution. Each layer is isolated, allowing you to add or modify tools without touching the core routing logic.

Catalog Definition in cliTools.ts

The catalog layer lives in src/shared/constants/cliTools.ts and exports a CLI_TOOLS object that acts as the source of truth for every integrated tool. Each entry defines the tool’s ID, human‑readable name, UI icons, vendor, default command, supported base‑URL modes, required environment variables, and optional UI guide steps. This declarative approach lets the frontend and backend agree on tool capabilities without hard‑coding logic in either place.

The catalog also exposes helper functions such as listCliTools() to enumerate available integrations at runtime.

Runtime Detection via cliRuntime.ts

The runtime layer in src/shared/services/cliRuntime.ts scans the host’s $PATH (and additional well‑known locations) for the binaries declared in the catalog. It exposes CLI_TOOL_IDS, getCliRuntimeStatus(), and getCliConfigPaths() to the rest of the application.

When getCliRuntimeStatus() is called with a tool ID, it invokes locateCommand("<binary>") to verify installation, returning the absolute binary path, version hints, and configuration file locations. This allows OmniRoute to fail fast with clear error messages if a user requests a tool that is not installed or misconfigured.

MCP Tool Wrappers

The execution layer resides under open-sse/mcp-server/tools/ (e.g., skillTools.ts). Each file implements a Zod‑validated input schema and a handler function that translates incoming MCP requests into spawned CLI processes.

These wrappers use the paths supplied by cliRuntime.ts to invoke the correct binary, inject environment variables (such as OPENCODE_BASE_URL), and pipe stdout/stderr into OmniRoute’s Server‑Sent Events (SSE) pipeline. The wrappers also normalize tool‑specific output so that translators in open-sse/translator/* can convert payloads back into standard OpenAI‑compatible JSON.

Request Lifecycle: From API to CLI Process

When a client sends a request to POST /v1/chat/completions, the flow through open-sse/handlers/chatCore.ts follows these steps:

  1. Signal Detection: If the request body contains model: "opencode" (or similar tool identifiers), the handler recognizes it as a CLI tool call rather than a standard LLM request.
  2. Catalog Lookup: The handler queries the CLI_TOOLS catalog to retrieve metadata, default arguments, and required environment variables.
  3. Runtime Verification: It calls getCliRuntimeStatus() from cliRuntime.ts to confirm the binary exists and fetch its absolute path.
  4. MCP Execution: The request is delegated to the appropriate MCP wrapper in open-sse/mcp-server/tools/, which spawns the process using cross-spawn and streams output.
  5. Response Translation: The translator layer converts CLI‑specific exit codes and text streams into the standard chat‑completion schema before returning to the client.

Throughout this pipeline, input validation runs through Zod schemas, error sanitization flows through open-sse/utils/error.ts, and per‑tool circuit‑breakers prevent transient CLI crashes from destabilizing the platform.

Step‑by‑Step: Adding a New CLI Tool

Extending OmniRoute to support an additional tool follows a deterministic, five‑step sequence:

  1. Extend the Catalog: Add an entry to CLI_TOOLS in src/shared/constants/cliTools.ts specifying the tool ID, command, environment variables, and UI metadata.
  2. Update Runtime Detection: If the binary name differs from the catalog ID, add a detection rule in src/shared/services/cliRuntime.ts (typically a locateCommand("<binary>") call).
  3. Create an MCP Wrapper: Implement a new file under open-sse/mcp-server/tools/ that defines the Zod input schema and a handler spawning the binary using paths from cliRuntime.
  4. Expose the API Route: Most tools are reachable through the generic POST /v1/cli-tools/{id} route, which forwards requests to the MCP wrapper via the internal cliRuntimeProviderMap.
  5. Add Tests: Write unit tests that verify the catalog entry passes CliCatalogEntrySchema, confirm the runtime detects the binary, and ensure the MCP wrapper produces correctly shaped responses. Reference tests/unit/t40-opencode-cli-tools-integration.test.ts and tests/unit/cli-tools.test.ts for patterns.

Implementation Examples

Listing Available CLI Tools

// Client‑side enumeration of the 33+ tool catalog
import { listCliTools } from "@/shared/constants/cliTools";

const tools = listCliTools();
console.log(tools.map(t => t.id)); 
// => ['claude', 'codex', 'opencode', …]

Executing a Tool via the OmniRoute API

import fetch from "node-fetch";

await fetch("https://router.mycompany.com/api/mcp/execute", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    tool: "opencode",                 // matches a CLI_TOOLS entry
    args: ["run", "implement feature X"],
    config: { baseUrl: "https://my.custom.host/v1" }  // optional override
  }),
});

Inside the MCP Wrapper

export const opencodeTool = {
  input: z.object({ 
    args: z.array(z.string()), 
    config: z.object({}).optional() 
  }),
  
  async handler({ args, config }) {
    const { getCliRuntimeStatus } = await import("@/shared/services/cliRuntime");
    const status = await getCliRuntimeStatus("opencode");
    
    if (!status?.executable) {
      throw new Error("Opencode CLI not found");
    }

    const spawn = require("cross-spawn");
    const child = spawn(
      status.executable, 
      args, 
      { 
        env: { 
          ...process.env, 
          OPENCODE_BASE_URL: config?.baseUrl 
        } 
      }
    );

    // Pipe stdout/stderr into OmniRoute's SSE response stream
    return child;
  },
};

Summary

  • Catalog Layer: Define tool metadata in src/shared/constants/cliTools.ts using the CLI_TOOLS object.
  • Runtime Layer: Detect installed binaries and config paths via src/shared/services/cliRuntime.ts using getCliRuntimeStatus() and locateCommand().
  • Execution Layer: Implement MCP wrappers in open-sse/mcp-server/tools/ to spawn processes and normalize I/O.
  • Integration Flow: Requests hit chatCore.ts, resolve through the catalog and runtime, execute via MCP, and return through translators in open-sse/translator/*.
  • Security: Zod validation, error sanitization through open-sse/utils/error.ts, and circuit‑breaker handling protect against malformed input and flaky CLI processes.
  • Testing: Validate new tools with schema tests (cli-tools.test.ts) and end‑to‑end integration tests (t40-opencode-cli-tools-integration.test.ts).

Frequently Asked Questions

How does OmniRoute detect if a CLI tool is installed?

The cliRuntime.ts service exposes getCliRuntimeStatus(), which scans the host’s $PATH and well‑known installation directories using locateCommand(). It returns an object indicating whether the binary exists, its absolute path, and where its configuration files reside, allowing the platform to fail fast with actionable error messages when a tool is missing.

Can I override default commands or base URLs for a specific tool?

Yes. Each catalog entry in CLI_TOOLS defines default commands and supported base‑URL modes. When calling the tool through the API, you can pass a config object in the request payload to override these defaults at runtime. The MCP wrapper merges this config into the spawn environment (for example, setting OPENCODE_BASE_URL) before executing the binary.

What testing strategy ensures CLI integrations remain stable?

The repository requires three levels of testing: (1) schema validation tests that verify catalog entries against CliCatalogEntrySchema in tests/unit/cli-tools.test.ts; (2) runtime detection tests confirming cliRuntime.ts correctly locates binaries; and (3) end‑to‑end integration tests like tests/unit/t40-opencode-cli-tools-integration.test.ts that execute real CLI processes and verify the OpenAI‑compatible response shape.

How does OmniRoute handle errors from external CLI processes?

Errors are sanitized through open-sse/utils/error.ts to prevent leakage of sensitive host information. Additionally, per‑tool circuit‑breaker logic wraps each MCP execution, ensuring that transient failures (such as a crashed CLI or network timeout) do not cascade into the broader routing platform or exhaust system resources.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →