OmniRoute MCP Server Implementation: 104 Tools, IO4 Transports, and Scope Security Explained
The OmniRoute MCP server implements 104 tools through a unified RPC interface with four transport modes (HTTP, SSE, StdIO, and Streamable HTTP), enforcing fine-grained scope-based access control at the tool level.
This deep dive examines the Multi-Channel Protocol (MCP) server architecture in the diegosouzapw/OmniRoute repository. The server provides a central hub for tool execution, with every tool declaring required scopes and every request validated against caller permissions before execution.
MCP Server Architecture Overview
The implementation spans three logical layers under open-sse/mcp-server/:
| Layer | File | Responsibility |
|---|---|---|
| Transport Layer | httpTransport.ts |
IO4 transports: HTTP, SSE, StdIO, Streamable HTTP |
| Core Server | server.ts |
Tool registration, RPC dispatch, middleware wiring |
| Scope Enforcement | scopeEnforcement.ts |
Permission validation before tool execution |
All transports share a common McpCallerIdentity object containing callerId and scopes extracted from the authentication token. This identity flows through every request to enable consistent access control regardless of transport mode.
The IO4 Transport System
The "IO4" designation refers to the four supported I/O modes implemented in httpTransport.ts:
| Transport | Factory Function | Endpoint/Mechanism | Use Case |
|---|---|---|---|
| HTTP | createHttpTransport() |
POST to /api/mcp/http |
Standard REST clients |
| SSE | createSseTransport() |
EventSource stream | Real-time updates |
| StdIO | createStdioTransport() |
process.stdin/process.stdout |
CLI integration |
| Streamable HTTP | createStreamTransport() |
ReadableStream response | Large payload transfers |
Each transport extracts scopes from the incoming token and constructs the McpCallerIdentity passed to the core server. The server then invokes scopeEnforcement.ts before any tool handler executes.
Source: [open-sse/mcp-server/server.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/open-sse/mcp-server/server.ts) and [open-sse/mcp-server/httpTransport.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/open-sse/mcp-server/httpTransport.ts)
Tool Registration and Manifest Structure
The 104 tools reside in open-sse/mcp-server/tools/ and follow a standardized manifest pattern. During server initialization, registerTools() in server.ts dynamically imports all tool files and builds two lookup structures:
toolMap– direct name-to-manifest lookup for RPC dispatchtoolCardinality– filtered view based on caller scope intersection
Every tool exports a manifest matching this TypeScript interface:
{
name: string,
description: string,
scopes: readonly string[], // required permission scopes
inputSchema: z.ZodSchema, // runtime input validation
handler: (args, ctx) => Promise<any>
}
Tools without a scopes declaration are treated as public and require no special permissions.
Source: [open-sse/mcp-server/toolCardinality.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/open-sse/mcp-server/toolCardinality.ts)
Scope Enforcement Implementation
The scopeEnforcement.ts middleware implements a strict intersection check for every tool invocation:
// From scopeEnforcement.ts
if (!scopeIntersects(tool.scopes, callerIdentity.scopes)) {
throw new McpError(403, `Insufficient MCP scopes for ${toolName}.`);
}
The enforcement flow:
- Transport layer extracts
allowScopesfrom JWT/API key - Core server receives
McpCallerIdentitywith validated scopes - Before handler execution, scopes are compared using intersection logic
- Mismatch triggers immediate 403 rejection with descriptive message
This design allows horizontal scope filtering (which tools are visible) and runtime enforcement (authorization at invocation time).
Source: [open-sse/mcp-server/scopeEnforcement.ts](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/open-sse/mcp-server/scopeEnforcement.ts)
Complete Scope Inventory
The 104 tools declare permissions across 12 functional categories. All scopes follow the pattern {action}:{resource}:
| Category | Available Scopes | Representative Tool Files |
|---|---|---|
| Skills | read:skills, write:skills, execute:skills |
skillTools.ts (lines 28-98) |
| Memory | read:memory, write:memory |
memoryTools.ts |
| Plugins | read:plugins, write:plugins |
pluginTools.ts (lines 39-206) |
| Obsidian | read:obsidian, write:obsidian |
obsidianTools.ts (lines 44-315) |
| Notion | read:notion, write:notion |
notionTools.ts (lines 15-95) |
| Health/Resilience | read:health, write:resilience |
poolTools.ts (lines 161-201) |
| Local Corpus | read:local-corpus |
corpus indexing tools |
| Compression | read:compression, write:compression |
compressionTools.ts (lines 550-649) |
| Gamification | read:gamification, write:gamification |
achievement/leaderboard tools |
| System | read:system, admin:system |
server configuration tools |
Tools may declare multiple scopes, requiring caller possession of any (not necessarily all) matching scope through the intersection logic.
MCP Server Startup Patterns
HTTP/SSE Server
import { createMcpServer } from './server';
import { createHttpTransport } from './httpTransport';
const server = createMcpServer(); // registers all 104 tools
createHttpTransport(server).listen(3000); // exposes HTTP + SSE endpoints
StdIO Transport (CLI Mode)
# Start MCP server on stdin/stdout for Claude Desktop or similar
omniroute --mcp
The CLI uses createStdioTransport() for JSON-RPC line-delimited communication.
Programmatic Scope Restriction
import { createMcpServer } from './server';
// Restrict this server instance to read-only memory operations
const limitedServer = createMcpServer({
callerIdentity: {
callerId: 'sandbox-client',
scopes: ['read:memory']
}
});
Practical Usage Examples
Calling Tools via HTTP Transport
import fetch from 'node-fetch';
const response = await fetch('http://localhost:3000/api/mcp/http', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer <token-with-read-plugins>'
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'list_plugins',
params: {},
id: 1
})
});
const { result } = await response.json();
// result: array of installed plugin manifests
StdIO Transport for CLI Scripts
echo '{"jsonrpc":"2.0","method":"read_health","params":{},"id":1}' \
| omniroute --mcp \
| jq '.result'
Scope Violation Handling
When a caller lacks required scopes, the server returns a structured JSON-RPC error:
{
"jsonrpc": "2.0",
"error": {
"code": 403,
"message": "Insufficient MCP scopes for write_memory."
},
"id": 2
}
Key Implementation Files
| File Path | Function |
|---|---|
open-sse/mcp-server/server.ts |
Core server factory, tool registration, RPC routing |
open-sse/mcp-server/scopeEnforcement.ts |
Permission validation middleware |
open-sse/mcp-server/toolCardinality.ts |
Scope-based tool filtering logic |
open-sse/mcp-server/httpTransport.ts |
Transport implementations (IO4) |
open-sse/mcp-server/index.ts |
Public API exports |
open-sse/mcp-server/tools/*.ts |
Individual tool implementations with scope declarations |
Summary
- Four transports (HTTP, SSE, StdIO, Streamable HTTP) provide flexible connectivity options for the MCP server
- 104 tools are registered dynamically from
open-sse/mcp-server/tools/with standardized Zod-validated manifests - Scope enforcement operates at two levels: cardinality filtering (which tools are visible) and runtime authorization (pre-handler validation)
- 12 scope categories cover skills, memory, plugins, Obsidian, Notion, health, compression, gamification, and system functions
- No special scope required for public tools; all others fail fast with 403 errors for insufficient permissions
Frequently Asked Questions
How do I determine which scopes my API token needs for specific MCP tools?
Inspect the tool manifest directly in the source file. Each tool in open-sse/mcp-server/tools/ exports a scopes array. For example, obsidianTools.ts lines 44-315 shows Obsidian tools require read:obsidian or write:obsidian. Alternatively, call the get_tool_catalog method with a valid token to receive only tools matching your scopes.
Can I run the MCP server without scope enforcement for local development?
The server requires a callerIdentity object but accepts an empty scopes array. Pass { callerId: 'dev', scopes: [] } to createMcpServer() — this grants access only to tools without scope requirements. For full access, include all relevant scopes in the array or use a wildcard scope if your deployment supports it.
What is the difference between transport-layer and tool-layer scope checking?
Transport scope checking (toolCardinality.ts) filters the tool list exposed to a caller, affecting discovery and catalog operations. Tool-layer checking (scopeEnforcement.ts) validates permissions immediately before handler execution. Both use the same intersection logic, but the cardinality layer optimizes performance while enforcement provides defense in depth against direct RPC calls.
How does the SSE transport handle scope validation for long-lived connections?
The SSE transport validates scopes at connection establishment using the initial request's authentication token. The McpCallerIdentity is bound to the connection context and reused for all subsequent tool invocations on that stream. To change scopes, a client must establish a new SSE connection with a different token.
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 →