How to Configure MCP Server Tools and Scope Enforcement in OmniRoute

OmniRoute's MCP server enforces fine-grained access control by validating caller scopes against each tool's requirements configured in the tool definition, using environment variables and middleware wrappers.

OmniRoute (diegosouzapw/OmniRoute) exposes AI-agent capabilities through a Model Context Protocol (MCP) server that validates permissions before executing tool calls. Configuring MCP server tools and scope enforcement requires defining required scopes in the tool schema and enabling runtime checks via environment variables.

Core Architecture of MCP Scope Enforcement

OmniRoute implements scope enforcement through a middleware pipeline that intercepts tool invocations. The system extracts caller identity and scopes from request metadata, then evaluates them against the tool's declared requirements.

Key Components

The enforcement mechanism relies on four primary source files:

  • open-sse/mcp-server/scopeEnforcement.ts: Contains resolveCallerScopeContext() to extract the caller's identity (clientId or sessionId) and scopes from authInfo or _meta payloads. It also exports evaluateToolScopes() which checks if required tool scopes are satisfied.

  • open-sse/mcp-server/schemas/tools.ts: Declares the MCP_TOOL_MAP and every tool's Zod schema alongside its scopes array (e.g., ["read:health"]). This map drives the scope validation logic.

  • open-sse/mcp-server/server.ts: Bootstraps the MCP server via createMcpServer(). At lines 95-101, it reads OMNIROUTE_MCP_ENFORCE_SCOPES and OMNIROUTE_MCP_SCOPES from the environment. Each tool handler is wrapped with withScopeEnforcement before registration via server.registerTool.

  • open-sse/mcp-server/httpAuthContext.ts: Propagates internal authentication headers (X-MCP-Auth-ClientId, X-MCP-Auth-Scopes) when the MCP server calls the OmniRoute HTTP API, ensuring scope context persists across service boundaries.

Request Processing Flow

When an AI agent invokes a tool, OmniRoute processes the request through this pipeline:

  1. The incoming JSON-RPC request hits server.registerTool, triggering the withScopeEnforcement wrapper.

  2. The wrapper calls resolveCallerScopeContext(extra, fallbackScopes), where extra contains forwarded authInfo from the client's API-key metadata.

  3. evaluateToolScopes(toolName, callerScopes, MCP_ENFORCE_SCOPES, toolScopes) validates permissions:

    • If enforcement is disabled, the call proceeds immediately.
    • If enabled and scopes are missing, the call rejects with an error and logs via logToolCall.
  4. Upon successful validation, the original handler (e.g., handleGetHealth) executes and returns results.

Configuring Scope Enforcement Environment Variables

The server respects two environment variables read at startup (see lines 95-101 of server.ts):

const MCP_ENFORCE_SCOPES = process.env.OMNIROUTE_MCP_ENFORCE_SCOPES === "true";
const MCP_ALLOWED_SCOPES = new Set(
  (process.env.OMNIROUTE_MCP_SCOPES || "")
    .split(",")
    .map((s) => s.trim())
    .filter(Boolean)
);

Enabling Strict Scope Checking

Set OMNIROUTE_MCP_ENFORCE_SCOPES to "true" to activate runtime validation:

export OMNIROUTE_MCP_ENFORCE_SCOPES=true

When enabled, any tool call lacking the required scopes defined in tools.ts receives an error response indicating the missing permissions.

Setting Fallback Scopes

OMNIROUTE_MCP_SCOPES defines comma-separated fallback scopes for unauthenticated callers:

export OMNIROUTE_MCP_SCOPES=read:health,read:models

These scopes apply when a caller does not provide its own scopes via authInfo headers, evaluated by resolveCallerScopeContext as the final fallback tier.

Defining and Modifying Tool Scopes

All tool definitions reside in open-sse/mcp-server/schemas/tools.ts. Each tool exports a definition containing a scopes array:

export const getHealthTool: McpToolDefinition<typeof getHealthInput, typeof getHealthOutput> = {
  name: "omniroute_get_health",
  description: "...",
  inputSchema: getHealthInput,
  outputSchema: getHealthOutput,
  scopes: ["read:health"],
  auditLevel: "basic",
  phase: 1,
  sourceEndpoints: [...]
};

To modify a tool's requirements:

  1. Edit the scopes array in the tool definition within tools.ts.
  2. Rebuild the application or restart the MCP server to regenerate MCP_TOOL_MAP (lines 1500-1505).

The withScopeEnforcement wrapper automatically reads updated requirements from the regenerated map on subsequent requests.

Practical Implementation Examples

Starting the Server with Enforcement Enabled

Create a .env file or export variables before launching:


# .env configuration

OMNIROUTE_MCP_ENFORCE_SCOPES=true
OMNIROUTE_MCP_SCOPES=read:health,read:models

# Launch via CLI

omniroute --mcp

The server emits a startup banner ([MCP] OmniRoute MCP Server starting…) and reports the registered tool count via TOTAL_MCP_TOOL_COUNT.

Calling Tools with Authenticated Scopes

Use an API key with sufficient scopes:

import { McpClient } from "@modelcontextprotocol/sdk/client/mcp.js";

const client = new McpClient({
  apiKey: "sk_abcdef123456", // API key possessing `read:health` scope
});

async function getHealth() {
  const result = await client.callTool("omniroute_get_health", {});
  console.log(result);
}

Missing scopes produce an error: Insufficient MCP scopes for omniroute_get_health. Missing: read:health. Caller=anonymous, source=env.

Overriding Scopes Per Request

Bypass global fallback scopes by providing explicit authInfo:

const result = await client.callTool(
  "omniroute_get_models_catalog",
  { provider: "openai" },
  {
    authInfo: {
      clientId: "my-temp-key",
      scopes: ["read:models"]
    }
  }
);

The resolveCallerScopeContext function prioritizes these explicit scopes over OMNIROUTE_MCP_SCOPES.

Disabling Enforcement for Development

Unset or clear the enforcement variable to allow all calls:

export OMNIROUTE_MCP_ENFORCE_SCOPES=
omniroute --mcp

In this mode, evaluateToolScopes logs tool calls via logToolCall but never rejects requests based on scope permissions.

Summary

  • Scope enforcement validates caller permissions against tool requirements using middleware in server.ts and logic in scopeEnforcement.ts.
  • Configuration relies on two environment variables: OMNIROUTE_MCP_ENFORCE_SCOPES (boolean toggle) and OMNIROUTE_MCP_SCOPES (fallback list).
  • Tool definitions declare required scopes in open-sse/mcp-server/schemas/tools.ts via the scopes array property.
  • Caller identity propagates through authInfo metadata or X-MCP-Auth-* HTTP headers, parsed by resolveCallerScopeContext.
  • Deployment requires restarting the MCP server after modifying tool scopes or environment variables for changes to take effect.

Frequently Asked Questions

What happens if a caller lacks the required scopes for an MCP tool?

The evaluateToolScopes function rejects the call with an error message indicating the missing scope (e.g., Missing: read:health), logs the attempt via logToolCall, and returns the error to the client without executing the tool handler.

How do I add a new scope to an existing MCP tool in OmniRoute?

Edit the tool's definition in open-sse/mcp-server/schemas/tools.ts to include the new scope in the scopes array (e.g., scopes: ["read:health", "admin:config"]). Restart the server to regenerate the MCP_TOOL_MAP and apply the new requirements immediately.

Can I disable scope enforcement temporarily for testing?

Yes. Set OMNIROUTE_MCP_ENFORCE_SCOPES to any value other than "true" or unset it entirely. This disables the enforcement check in evaluateToolScopes, allowing all tool calls to proceed regardless of the caller's scopes while still logging access attempts.

Where does the MCP server read the caller's scopes from?

The server extracts scopes from multiple sources in priority order: first from the JSON-RPC request's authInfo metadata (populated via API-key policies), then from the _meta payload, and finally falls back to the comma-separated list in the OMNIROUTE_MCP_SCOPES environment variable. Internally, httpAuthContext.ts translates these into X-MCP-Auth-Scopes headers for downstream HTTP calls.

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 →