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: ContainsresolveCallerScopeContext()to extract the caller's identity (clientIdorsessionId) and scopes fromauthInfoor_metapayloads. It also exportsevaluateToolScopes()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 itsscopesarray (e.g.,["read:health"]). This map drives the scope validation logic. -
open-sse/mcp-server/server.ts: Bootstraps the MCP server viacreateMcpServer(). At lines 95-101, it readsOMNIROUTE_MCP_ENFORCE_SCOPESandOMNIROUTE_MCP_SCOPESfrom the environment. Each tool handler is wrapped withwithScopeEnforcementbefore registration viaserver.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:
-
The incoming JSON-RPC request hits
server.registerTool, triggering thewithScopeEnforcementwrapper. -
The wrapper calls
resolveCallerScopeContext(extra, fallbackScopes), whereextracontains forwardedauthInfofrom the client's API-key metadata. -
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.
-
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:
- Edit the
scopesarray in the tool definition withintools.ts. - 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.tsand logic inscopeEnforcement.ts. - Configuration relies on two environment variables:
OMNIROUTE_MCP_ENFORCE_SCOPES(boolean toggle) andOMNIROUTE_MCP_SCOPES(fallback list). - Tool definitions declare required scopes in
open-sse/mcp-server/schemas/tools.tsvia thescopesarray property. - Caller identity propagates through
authInfometadata orX-MCP-Auth-*HTTP headers, parsed byresolveCallerScopeContext. - 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →