How to Configure MCP Tools and Custom MCP Servers in Routa

Routa provides a built-in MCP coordination server and a REST API to register custom stdio, HTTP, or SSE-based MCP servers, allowing fine-grained control over which tools are exposed to agents.

Routa is an open-source AI agent orchestration framework that ships with a persistent MCP coordination server (routa-coordination) for managing agent lifecycles. Developers can extend this capability by configuring custom MCP servers through either the React-based settings UI or direct API calls to the Node.js backend, with configurations stored in Postgres or SQLite.

Understanding the MCP Architecture in Routa

Routa's MCP implementation consists of four core components that bridge the Model Context Protocol with the agent runtime:

When the backend initializes a provider-specific MCP configuration, it calls mergeCustomMcpServers() to combine built-in coordination endpoints with enabled custom servers, filtering out disabled entries before passing the final profile to the orchestration layer.

Adding Custom MCP Servers

You can register custom MCP servers through the Settings UI or programmatically via HTTP requests to the backend.

Via the Settings UI

Navigate to Settings → MCP in the Routa interface, rendered by src/client/components/settings-panel-mcp-tab.tsx. Click "Add new server" to open the configuration form:

  • ID: Unique identifier used as the query key (e.g., my-filesystem-server)
  • Name: Human-readable label displayed in the tool picker
  • Type: Select stdio for local command-based tools, http for REST gateways, or sse for Server-Sent Events streams
  • Command/Args (stdio only): Executable and arguments array (e.g., npx, ["@modelcontextprotocol/server-filesystem", "/workspace"])
  • URL (HTTP/SSE only): Endpoint address
  • Headers/Env: Optional JSON objects for authentication or environment variables

Submitting the form sends a POST request to /api/mcp-servers with a payload matching the CustomMcpServerCreateInput interface defined in src/core/store/custom-mcp-server-store.ts.

Via the REST API

For automation or headless deployments, send HTTP requests directly to the management endpoint:


# Register a stdio-based filesystem server

curl -X POST http://localhost:3210/api/mcp-servers \
  -H "Content-Type: application/json" \
  -d '{
    "id": "filesystem-mcp",
    "name": "Filesystem Tools",
    "type": "stdio",
    "command": "npx",
    "args": ["@modelcontextprotocol/server-filesystem", "/home/user/workspace"],
    "enabled": true
  }'

# Register an HTTP gateway with authentication

curl -X POST http://localhost:3210/api/mcp-servers \
  -H "Content-Type: application/json" \
  -d '{
    "id": "custom-gateway",
    "name": "Custom HTTP Gateway",
    "type": "http",
    "url": "http://localhost:9000/mcp",
    "headers": {"Authorization": "Bearer token123"},
    "enabled": true
  }'

The backend validates the payload against the schema, persists it via CustomMcpServerStore.create(), and returns the stored record with generated timestamps.

Managing Server State and Persistence

Each custom server includes an enabled boolean flag that determines availability without deleting configuration data.

Enabling and Disabling Servers

In the Settings UI, toggle the activation switch next to any server entry. This sends a PUT request to /api/mcp-servers containing only the ID and the new enabled state:

{
  "id": "filesystem-mcp",
  "enabled": false
}

Disabled servers are filtered out during the merge process in mergeCustomMcpServers(), ensuring agents cannot access tools from inactive endpoints. The underlying configuration remains in the database for quick reactivation.

How Custom Servers Merge with Built-ins

When building the final MCP profile, Routa executes mergeCustomMcpServers(builtIn, custom) from src/core/store/custom-mcp-server-store.ts:

  1. The routa-coordination built-in server always takes precedence and cannot be overridden.
  2. Enabled custom servers transform into their respective transport configurations:
    • stdio: { type: "stdio", command, args, env? }
    • http/sse: { type, url, headers?, env? }
  3. Duplicate names favor built-in definitions to prevent coordination conflicts.

The resulting merged object becomes the profile passed to RoutaMcpServer during initialization.

Configuring Which MCP Tools Are Exposed

Control tool visibility through the RoutaMcpToolManager class to limit agent capabilities or reduce token consumption.

Switching Between Essential and Full Tool Sets

The tool manager supports two predefined modes via setToolMode():

import { RoutaMcpToolManager } from "./core/mcp/routa-mcp-tool-manager";

const manager = new RoutaMcpToolManager(agentTools, workspaceId);

// Expose only 12 core coordination tools (create_task, list_agents, etc.)
manager.setToolMode("essential");

// Expose all 34 available tools including advanced workspace operations
manager.setToolMode("full");

When registerTools(server) executes, it checks this.toolMode and registers only the appropriate subset. The "essential" mode is recommended for restricted environments or when minimizing context window usage.

Creating Custom Tool Whitelists

For granular control, use setAllowedTools() with a Set<string> of specific tool names:

const allowedTools = new Set(["create_task", "read_note", "search_files"]);
manager.setAllowedTools(allowedTools);

The manager filters the registration to include only tools present in this whitelist, ignoring all others regardless of the current tool mode.

Complete Implementation Example

Combine API configuration with programmatic initialization to deploy a fully customized MCP stack:

// Step 1: Register a custom HTTP server via API
await fetch("/api/mcp-servers", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    id: "brave-search",
    name": "Brave Search MCP",
    type: "http",
    url: "http://localhost:8080/sse",
    headers: { "X-API-Key": process.env.BRAVE_API_KEY },
    enabled: true
  })
});

// Step 2: Initialize the MCP server with merged configuration
import { getCustomMcpServerStore, mergeCustomMcpServers } from "./core/store/custom-mcp-server-store";
import { RoutaMcpServer } from "./core/mcp/routa-mcp-server";

const store = getCustomMcpServerStore(); // Auto-detects Postgres or SQLite
const customServers = await store?.listEnabled() ?? [];

const builtIn = {
  "routa-coordination": {
    type: "http",
    url: "http://127.0.0.1:3210/mcp"
  }
};

const mergedProfile = mergeCustomMcpServers(builtIn, customServers);

const mcpServer = new RoutaMcpServer({
  profile: mergedProfile,
  port: 3001,
  transport: "stdio"
});

await mcpServer.start();

This configuration exposes both the built-in coordination tools and the Brave Search MCP capabilities to connected agents.

Summary

  • Routa's architecture separates server persistence (custom-mcp-server-store.ts), tool registration (routa-mcp-tool-manager.ts), and HTTP management (route.ts) into distinct layers.
  • Custom servers support three transport types (stdio, http, sse) and store configurations in Postgres or SQLite via the /api/mcp-servers endpoint.
  • Tool visibility is controlled through setToolMode("essential" | "full") or setAllowedTools(Set<string>) in the RoutaMcpToolManager class.
  • Built-in protection ensures the routa-coordination server always takes precedence over custom definitions with identical names.

Frequently Asked Questions

What is the difference between stdio and HTTP/SSE MCP servers in Routa?

stdio servers spawn a local subprocess (e.g., npx @modelcontextprotocol/server-filesystem) and communicate over standard input/output, ideal for local tools without network exposure. HTTP/SSE servers connect to remote endpoints via HTTP requests or Server-Sent Events streams, suitable for cloud-hosted gateways or microservices. Both types persist in src/core/store/custom-mcp-server-store.ts but serialize into different transport configurations during the merge process.

How do I prevent custom MCP servers from conflicting with the built-in routa-coordination server?

The mergeCustomMcpServers() function in src/core/store/custom-mcp-server-store.ts automatically prioritizes built-in definitions. If a custom server shares an ID with routa-coordination, the built-in configuration wins and the custom entry is discarded, protecting critical orchestration functionality from accidental overrides.

Can I restrict which tools are available to specific agents or workspaces?

Yes. Instantiate RoutaMcpToolManager with a specific workspaceId and call setAllowedTools() with a whitelist of tool names before registering tools. This creates a filtered view of available capabilities per workspace or agent session, though you must implement the workspace-specific instantiation logic in your orchestration layer.

Where are custom MCP server configurations stored?

Configurations persist in either Postgres (production) or SQLite (development) through Prisma ORM, handled by CustomMcpServerStore in src/core/store/custom-mcp-server-store.ts. The store provides type-safe CRUD operations and validates the CustomMcpServerConfig schema before writing to the database.

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 →