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

> Integrate 33+ CLI tools with OmniRoute using our developer guide. Learn how OmniRoute's catalog, detection, and execution layers streamline your workflow.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: how-to-guide
- Published: 2026-08-24

---

**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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/constants/cliTools.ts), detects binaries via [`src/shared/services/cliRuntime.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/cliTools.ts)

The **catalog layer** lives in [`src/shared/constants/cliTools.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/cliRuntime.ts)

The **runtime layer** in [`src/shared/services/cliRuntime.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/tests/unit/t40-opencode-cli-tools-integration.test.ts) and [`tests/unit/cli-tools.test.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/tests/unit/cli-tools.test.ts) for patterns.

## Implementation Examples

### Listing Available CLI Tools

```typescript
// 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

```typescript
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

```typescript
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/constants/cliTools.ts) using the `CLI_TOOLS` object.
- **Runtime Layer**: Detect installed binaries and config paths via [`src/shared/services/cliRuntime.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/cli-tools.test.ts)) and end‑to‑end integration tests ([`t40-opencode-cli-tools-integration.test.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/t40-opencode-cli-tools-integration.test.ts)).

## Frequently Asked Questions

### How does OmniRoute detect if a CLI tool is installed?

The [`cliRuntime.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/tests/unit/cli-tools.test.ts); (2) runtime detection tests confirming [`cliRuntime.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/cliRuntime.ts) correctly locates binaries; and (3) end‑to‑end integration tests like [`tests/unit/t40-opencode-cli-tools-integration.test.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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.