How OmniRoute MCP Implements Scope-Based Access Control

OmniRoute MCP enforces scope-based access control by resolving caller scopes from JWT tokens or request metadata, evaluating them against tool-specific requirements with wildcard support, and wrapping every handler in an enforcement layer that rejects unauthorized requests with detailed audit logs.

The diegosouzapw/OmniRoute repository provides a Model Context Protocol (MCP) server that secures tool invocations using OAuth-style scope validation. This mechanism ensures that only clients with explicit permissions can execute sensitive operations, with enforcement centralized in the open-sse/mcp-server directory.

Resolving Caller Scope Context

When an MCP request arrives, the system first determines what permissions the caller possesses. The resolveCallerScopeContext function in open-sse/mcp-server/scopeEnforcement.ts (lines 72-96) inspects the request's extra data (McpToolExtraLike) and extracts scopes from three potential sources in order of precedence:

  1. AuthInfo — JWT scopes embedded in extra.authInfo.scopes (typically containing the client ID's authorized permissions)
  2. Meta — Scope arrays found in scopes or auth.scopes fields within the _meta payload
  3. Environment fallback — A static list defined in the MCP_ALLOWED_SCOPES environment variable when no explicit scopes are provided

The function normalizes all extracted scopes by trimming whitespace and deduplicating entries to prevent accidental mismatches due to formatting inconsistencies.

Evaluating Required vs. Provided Scopes

Once caller scopes are resolved, evaluateToolScopes (lines 99-135 in open-sse/mcp-server/scopeEnforcement.ts) performs the authorization check. This function receives the tool name, caller scopes, the global MCP_ENFORCE_SCOPES flag, and optional inline-scope overrides.

The evaluation process:

  • Retrieves the tool's declared scopes from MCP_TOOL_MAP, a centralized registry defined in open-sse/mcp-server/schemas/tools.ts (lines 15-54), or uses the inline scopes provided as arguments
  • Supports wildcard matching: a caller with * (full access) or prefix wildcards like read:* can satisfy multiple specific requirements (e.g., read:health, read:metrics)
  • Returns a boolean indicating whether the caller possesses all required permissions

If a tool has no declared scopes in the registry, the enforcement layer treats the invocation as explicitly disallowed and returns a tool_definition_missing error.

Enforcing Scope Restrictions at Runtime

The withScopeEnforcement wrapper, implemented in open-sse/mcp-server/server.ts (lines 24-71), protects every MCP tool handler. This middleware:

  1. Invokes resolveCallerScopeContext to gather caller permissions
  2. Passes these to evaluateToolScopes along with the tool's requirements
  3. Short-circuits the request with a descriptive error if validation fails, including the missing scopes, caller ID, and source
  4. Logs denied attempts for auditability

When enforcement fails, the client receives an error message formatted as: Insufficient MCP scopes for [tool_name]. Missing: [scope_list]. Caller=[id], source=[source].

Declaring Tool Scopes in the Registry

Tool permissions are defined centrally in open-sse/mcp-server/schemas/tools.ts where the MCP_TOOLS array constructs MCP_TOOL_MAP. Each tool entry includes a scopes property declaring its requirements:

// From open-sse/mcp-server/schemas/tools.ts
{
  name: "omniroute_get_health",
  handler: getHealthHandler,
  scopes: ["read:health"]
},
{
  name: "omniroute_set_routing_strategy",
  handler: setRoutingHandler,
  scopes: ["write:combos"]
}

This registry ensures the enforcement layer can look up required permissions at runtime without hardcoding logic in individual handlers.

Practical Implementation Examples

A client with appropriate permissions can invoke tools by including scope data in the request extras:

import { invokeMcpTool } from "@omniroute/open-sse/mcp-server/client";

// Caller presents JWT with "read:health" scope
const result = await invokeMcpTool("omniroute_get_health", {}, {
  authInfo: { clientId: "service-a", scopes: ["read:health"] },
});

The enforcement wrapper can also apply inline scope overrides for specific handler instances:

const protectedHandler = withScopeEnforcement(
  "omniroute_set_routing_strategy",
  setRoutingStrategyHandler,
  ["write:combos"]  // inline requirement override
);

If a caller lacking the write:combos scope attempts the above, the wrapper rejects the call before the handler executes:

// Attempt with insufficient permissions
await protectedHandler({ authInfo: { scopes: ["read:health"] } });
// Throws: Error: Insufficient MCP scopes for omniroute_set_routing_strategy. 
// Missing: write:combos. Caller=anonymous, source=none.

Summary

  • Triple-resolution strategy: Caller scopes resolve from JWT AuthInfo, metadata fields, or the MCP_ALLOWED_SCOPES environment variable
  • Flexible matching: Supports exact matches, universal wildcards (*), and prefix wildcards (read:*) for hierarchical permissions
  • Centralized registry: Tool scopes are declared in MCP_TOOL_MAP (open-sse/mcp-server/schemas/tools.ts) and evaluated by evaluateToolScopes
  • Middleware enforcement: withScopeEnforcement wraps all handlers in open-sse/mcp-server/server.ts to provide consistent access control and audit logging
  • Fail-closed design: Missing tool definitions result in explicit tool_definition_missing errors rather than silent acceptance

Frequently Asked Questions

What happens if a caller provides no scopes in OmniRoute MCP?

If the incoming request lacks scopes in AuthInfo or Meta fields, resolveCallerScopeContext falls back to the MCP_ALLOWED_SCOPES environment variable. If no fallback is configured, the caller is treated as having an empty scope set, which will fail any tool requiring specific permissions.

How does OmniRoute MCP handle wildcard permissions?

The scope evaluator in open-sse/mcp-server/scopeEnforcement.ts recognizes two wildcard patterns: a universal asterisk (*) that grants all permissions, and prefix wildcards like read:* that satisfy any scope starting with that prefix. This allows broad permissions to satisfy multiple specific requirements without listing each individually.

What error is returned when scope validation fails?

When evaluateToolScopes determines a caller lacks required permissions, withScopeEnforcement throws an error formatted as: Insufficient MCP scopes for [tool_name]. Missing: [scope_list]. Caller=[caller_id], source=[source]. Additionally, if a tool has no scope definition in MCP_TOOL_MAP, the system returns tool_definition_missing.

Where are tool scopes defined in the OmniRoute codebase?

Tool scopes are defined in open-sse/mcp-server/schemas/tools.ts within the MCP_TOOLS array, where each tool object includes a scopes property. These definitions are compiled into MCP_TOOL_MAP and exported via open-sse/mcp-server/schemas/index.ts for runtime lookup by the enforcement layer.

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 →