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:
- AuthInfo — JWT scopes embedded in
extra.authInfo.scopes(typically containing the client ID's authorized permissions) - Meta — Scope arrays found in
scopesorauth.scopesfields within the_metapayload - Environment fallback — A static list defined in the
MCP_ALLOWED_SCOPESenvironment 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 inopen-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 likeread:*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:
- Invokes
resolveCallerScopeContextto gather caller permissions - Passes these to
evaluateToolScopesalong with the tool's requirements - Short-circuits the request with a descriptive error if validation fails, including the missing scopes, caller ID, and source
- 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_SCOPESenvironment 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 byevaluateToolScopes - Middleware enforcement:
withScopeEnforcementwraps all handlers inopen-sse/mcp-server/server.tsto provide consistent access control and audit logging - Fail-closed design: Missing tool definitions result in explicit
tool_definition_missingerrors 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →