How the MCP Server Implements Tool Scope Enforcement Across 30 Scopes in OmniRoute
The OmniRoute MCP server enforces tool scope enforcement across 30 scopes by validating API key permissions against tool-specific scope requirements before every tool invocation.
The OmniRoute repository implements a robust scope-based access control system for its Model Context Protocol (MCP) server. This architecture ensures that API keys can only invoke tools matching their explicitly granted permissions, with 30 distinct scopes governing access to different tool categories like memory, plugin, skill, and advanced operations.
Scope Definition and Enumeration
All 30 available scopes are centrally defined in OMNIROUTE_MCP_SCOPES within [src/shared/constants/mcpScopes.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/shared/constants/mcpScopes.ts). Each entry represents a logical grouping:
- memory – Memory storage and retrieval tools
- skill – Skill execution and management
- plugin – Plugin installation and configuration
- notion – Notion integration tools
- obsidian – Obsidian vault operations
- compression – Data compression utilities
- agentSkill – Agent-specific capabilities
- advanced – Extended tool functionality
The constant exports a complete enumeration that both the registration system and enforcement middleware reference at runtime.
API Key to Scope Mapping
When an API key is created, the server persists scope assignments in the SQLite database via [src/lib/db/apiKeys.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/lib/db/apiKeys.ts). The scopes column stores a JSON-encoded array of scope names:
// Example API key record structure
{
key_id: "key_abc123",
name: "Production Memory Worker",
scopes: ["memory", "skill"], // JSON array
created_at: "2024-01-15T10:30:00Z"
}
This database layer provides the foundation for runtime permission verification.
Request Authentication and Context Attachment
Every incoming MCP request flows through [open-sse/mcp-server/httpAuthContext.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/open-sse/mcp-server/httpAuthContext.ts). This module:
- Extracts the bearer token from the
Authorizationheader - Queries the
api_keystable for the corresponding record - Parses the JSON
scopesarray - Attaches a request-context object containing
allowedScopesto the request object
// Conceptual flow in httpAuthContext.ts
const apiKey = extractBearerToken(request.headers.authorization);
const keyRecord = await db.apiKeys.findByKey(apiKey);
request.context = {
apiKeyId: keyRecord.key_id,
allowedScopes: JSON.parse(keyRecord.scopes), // ["memory", "skill"]
authenticatedAt: Date.now()
};
This context becomes available to all downstream middleware and tool handlers.
Scope Enforcement Middleware
The critical enforcement logic resides in [open-sse/mcp-server/scopeEnforcement.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/open-sse/mcp-server/scopeEnforcement.ts). This middleware receives:
- The request context (
allowedScopesarray) - The tool definition (
ToolDefinitionwithrequiredScope: string)
It performs a subset check: allowedScopes must contain tool.requiredScope.
// Scope check logic in scopeEnforcement.ts
function enforceScope(
context: RequestContext,
tool: ToolDefinition
): void {
const hasScope = context.allowedScopes.includes(tool.requiredScope);
if (!hasScope) {
throw new MCPError(
403,
`Insufficient scope for tool ${tool.name}. ` +
`Required: "${tool.requiredScope}". ` +
`Allowed: [${context.allowedScopes.join(", ")}]`
);
}
}
If the check fails, the server immediately returns 403 Forbidden without executing the tool handler, preventing any unauthorized access.
Tool Registration with Scope Assignment
Each tool registers its definition via [open-sse/mcp-server/toolSearch/register.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/open-sse/mcp-server/toolSearch/register.ts). The registration includes a mandatory scope property:
// Example from memoryTools.ts
registerTool({
name: "memory_add",
description: "Store a value in memory",
scope: "memory", // One of the 30 OMNIROUTE_MCP_SCOPES
handler: async (args) => { /* implementation */ }
});
// Example from pluginTools.ts
registerTool({
name: "plugin_install",
description: "Install a new plugin",
scope: "plugin",
handler: async (args) => { /* implementation */ }
});
The registration system maintains an internal map of tool names to their definitions, enabling efficient scope lookup during request processing.
Runtime Enforcement Flow
When a client invokes a tool via the HTTP transport in [open-sse/mcp-server/httpTransport.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/open-sse/mcp-server/httpTransport.ts), the server executes this sequence:
- Parse request – Extract tool name and arguments
- Resolve tool – Look up
ToolDefinitionin the registration map - Enforce scope – Call
scopeEnforcementwith context and tool definition - Execute or reject – Run handler if scope check passes; return 403 if it fails
This guarantees that knowing a tool's name provides no capability without proper scope authorization.
Audit Logging for Security Traceability
Every tool invocation—successful or blocked—is recorded by [open-sse/mcp-server/audit.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/open-sse/mcp-server/audit.ts). Audit entries include:
{
timestamp: "2024-01-15T10:35:22Z",
apiKeyId: "key_abc123",
toolName: "memory_add",
scopeCheck: {
required: "memory",
allowed: ["memory", "skill"],
result: "granted" // or "denied"
},
executionTimeMs: 12
}
This logging supports security reviews, compliance reporting, and incident investigation.
Calling Tools with Proper Scope Authorization
Authorized Memory Tool Call
import fetch from "node-fetch";
const response = await fetch("https://my.omniroute.dev/api/mcp/sse", {
method: "POST",
headers: {
"Authorization": "Bearer key_with_memory_scope",
"Content-Type": "application/json",
},
body: JSON.stringify({
tool: "memory_add",
args: { key: "session1", value: "Hello world!" },
}),
});
// Response: 200 OK with result
The API key includes the "memory" scope, so memory_add executes successfully.
Blocked Plugin Tool Call
const response = await fetch("https://my.omniroute.dev/api/mcp/sse", {
method: "POST",
headers: {
"Authorization": "Bearer key_without_plugin_scope",
"Content-Type": "application/json",
},
body: JSON.stringify({
tool: "plugin_install",
args: { name: "unauthorized-plugin" },
}),
});
// Response: 403 Forbidden
// Body: { "error": "Insufficient scope for tool plugin_install. Required: \"plugin\". Allowed: [\"memory\", \"skill\"]" }
The server rejects the request because the key lacks the required "plugin" scope, even though the tool name is valid.
Key Implementation Files
Summary
- 30 scopes are defined centrally in
mcpScopes.tscovering all tool categories - API keys store granted scopes as JSON arrays in the SQLite database
httpAuthContext.tsextracts and attachesallowedScopesto every requestscopeEnforcement.tsvalidates thatallowedScopescontains the tool'srequiredScoperegister.tsbinds each tool to exactly one scope during registration- 403 Forbidden responses prevent unauthorized tool execution without revealing sensitive information
- Audit logging provides complete traceability of scope-based access decisions
Frequently Asked Questions
What happens when an API key has no scopes assigned?
An API key with an empty scopes array cannot invoke any MCP tools. Every tool call returns 403 Forbidden because no scope requirements can be satisfied. The OmniRoute system requires at least one scope for functional access.
Can a single tool require multiple scopes?
No. According to the OmniRoute implementation, each tool definition specifies exactly one scope property. If multi-scope requirements are needed, you create separate tools or implement composite checks in custom middleware layers.
How do I add a new scope to the 30 existing ones?
Add the scope identifier to the OMNIROUTE_MCP_SCOPES constant in src/shared/constants/mcpScopes.ts, then assign it to tools during registration. Existing API keys will not automatically receive the new scope; administrators must update key records in the database to grant it.
Is scope enforcement bypassable through SSE transport directly?
No. The open-sse/mcp-server/httpTransport.ts channel routes all requests through the same httpAuthContext and scopeEnforcement pipeline. Whether clients use HTTP POST or SSE connections, the scope validation executes identically before any tool handler runs.
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 →