# How the MCP Server in OmniRoute Manages 94 Tools and 30 Permission Scopes

> Discover how OmniRoute's MCP server expertly manages 94 tools and 30 permission scopes through dynamic loading and layered middleware for efficient access control.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: architecture
- Published: 2026-07-14

---

**The MCP server in OmniRoute operates through a dynamic tool registry that lazily loads 94 specialized tools across 12 modules, while enforcing fine-grained access control via 30 permission scopes using a layered middleware architecture.**

The **OmniRoute** repository (`diegosouzapw/OmniRoute`) provides a production-grade **Model Context Protocol (MCP)** server implementation built on TypeScript. This system exposes nearly one hundred discrete capabilities to AI agents and external clients through a unified JSON-RPC interface, protected by granular scope-based permissions.

## Dynamic Tool Registration and the 94-Tool Ecosystem

The server consolidates 94 distinct tools into a runtime registry without static imports, keeping the startup footprint minimal while ensuring all capabilities remain available on demand.

### Modular Tool Architecture

Tool definitions are organized by functional domain across twelve separate modules under `open-sse/mcp-server/tools/`. The [`tools/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/tools/index.ts) file aggregates these capabilities through named exports:

```typescript
// open-sse/mcp-server/tools/index.ts
export * from "./advancedTools"
export * from "./agentSkillTools"
export * from "./compressionTools"
export * from "./memoryTools"
export * from "./gamificationTools"
export * from "./pluginTools"
export * from "./notionTools"
export * from "./obsidianTools"
export * from "./skillTools"
export * from "./poolTools"
export * from "./pickFastestModel"
export * from "./githubSkillTools"

```

Each module exports a `register` function that returns a typed tool definition via `defineTool`. This pattern ensures consistent schema validation using **Zod** while allowing domain-specific implementations to remain isolated.

### Lazy Loading Implementation

The `buildMcpServer` function in [`open-sse/mcp-server/server.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/mcp-server/server.ts) dynamically imports the entire tool catalog at runtime:

```typescript
export const buildMcpServer = async () => {
  const tools: MCPTool[] = []
  const toolModules = await import("@omniroute/open-sse/mcp-server/tools")
  
  for (const name of Object.keys(toolModules)) {
    const mod = (toolModules as any)[name]
    if (mod && typeof mod.register === "function") {
      const tool = await mod.register()
      tools.push(tool)
    }
  }
  
  const baseServer = createMcpServer({ tools })
  // ... middleware attachment
  return baseServer
}

```

This architecture ensures that heavy dependencies (such as Notion or GitHub API clients) are only loaded when the specific tool module is first accessed, reducing memory consumption for partially utilized deployments.

## Scope-Based Access Control with 30 Permission Scopes

Access to the 94 tools is governed by approximately **30 distinct scopes** (e.g., `tools:health`, `tools:compression`, `tools:memory`, `tools:plugin`), which are linked to API keys in the database and enforced through middleware.

### The Scope Enforcement Middleware

The `createScopeEnforcementMiddleware` function in [`open-sse/mcp-server/scopeEnforcement.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/mcp-server/scopeEnforcement.ts) intercepts every incoming request to validate permissions:

```typescript
export const createScopeEnforcementMiddleware = () => {
  return async (ctx: any, next: () => Promise<void>) => {
    const apiKey = getApiKeyFromRequest(ctx.req)
    if (!apiKey) {
      await next() // Public mode fallback
      return
    }
    const scopes = await ctx.db.getScopesForKey(apiKey)
    ctx.allowedScopes = new Set(scopes)
    await next()
  }
}

```

This middleware attaches an `allowedScopes` Set to the request context, which downstream handlers check before executing tool-specific logic. If a tool requires a scope not present in the caller's permissions, the server returns a 403 Forbidden response before reaching the handler.

### API Key Authentication Flow

The authentication chain operates in three distinct phases as defined in [`open-sse/mcp-server/catalog.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/mcp-server/catalog.ts):

1. **Scope Enforcement** (order 10): Validates tool permissions against the API key's allowed scopes
2. **Logging** (order 20): Records request metadata for audit trails
3. **Auth Validation** (order 30): Confirms the presence of the `x-api-key` header

The `MIDDLEWARE_ORDER` constants ensure deterministic execution regardless of registration sequence:

```typescript
export const MIDDLEWARE_ORDER = {
  scope: 10,
  logging: 20,
  auth: 30,
}

```

## Middleware Architecture and Request Processing

The server processes JSON-RPC requests through a layered pipeline that transforms HTTP calls into tool invocations.

### Ordered Middleware Pipeline

The `buildMcpServer` function attaches middleware using explicit ordering to prevent race conditions:

```typescript
const baseServer = createMcpServer({ tools })

const scopeMiddleware = createScopeEnforcementMiddleware()
baseServer.addMiddleware(scopeMiddleware, MIDDLEWARE_ORDER.scope)

for (const mw of MIDDLEWARE) {
  baseServer.addMiddleware(mw.handler, mw.order)
}

```

This guarantees that scope checks execute before business logic, while logging and authentication occur at predictable intervals in the request lifecycle.

### HTTP Transport Layer

The `createMcpHttpServer` function wraps the core server with an HTTP transport that resolves authentication context from incoming requests:

```typescript
export const createMcpHttpServer = async () => {
  const server = await buildMcpServer()
  const httpTransport = createOpenSseHttpTransport({
    server,
    getAuthContext: getOpenSseHttpAuthContext,
  })
  return httpTransport
}

```

The transport extracts the API key from request headers, queries the database for associated scopes, and injects this context into the middleware chain before dispatching to the appropriate tool handler.

## Building Custom Tools and Extending the Server

Adding new capabilities requires implementing the standard registration pattern. Each tool defines its contract using Zod schemas and implements execution logic in an async handler.

### Example Tool Implementation

The following pattern from [`advancedTools.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/advancedTools.ts) demonstrates the structure for implementing health checks:

```typescript
import { defineTool } from "@omniroute/open-sse/mcp-server"
import { z } from "zod"

export const register = async () => {
  return defineTool({
    name: "get_health",
    description: "Retrieve health status and version of the OmniRoute server.",
    inputSchema: z.object({}),
    outputSchema: z.object({
      ok: z.boolean(),
      version: z.string(),
      uptime_seconds: z.number(),
    }),
    handler: async () => {
      return {
        ok: true,
        version: "3.8.47",
        uptime_seconds: Math.floor(process.uptime()),
      }
    },
  })
}

```

### Session Memory Manipulation

Tools can interact with persistent storage through the context object. The `memory_add` tool from [`memoryTools.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/memoryTools.ts) illustrates session state management:

```typescript
export const register = async () => {
  return defineTool({
    name: "memory_add",
    description: "Add a memory entry for the current session.",
    inputSchema: z.object({
      key: z.string(),
      value: z.string(),
    }),
    outputSchema: z.object({ success: z.boolean() }),
    handler: async ({ input, ctx }) => {
      ctx.memoryStore.set(input.key, input.value)
      return { success: true }
    },
  })
}

```

### Client Invocation Example

Clients interact with the server via HTTP POST requests containing JSON-RPC payloads:

```typescript
const payload = {
  jsonrpc: "2.0",
  id: 1,
  method: "memory_add",
  params: { key: "user_preference", value: "dark_mode" }
}

const response = await fetch("https://api.omniroute.example/api/mcp", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": "sk_live_xxxxxxxx"
  },
  body: JSON.stringify(payload)
})

```

## Summary

- **Dynamic Tool Loading**: The `buildMcpServer` function in [`open-sse/mcp-server/server.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/mcp-server/server.ts) lazily imports 94 tools from 12 domain modules, keeping startup overhead minimal while maintaining runtime availability.
- **Scope Enforcement**: Approximately 30 permission scopes regulate access to tool groups, enforced by `createScopeEnforcementMiddleware` before handler execution.
- **Ordered Middleware**: The `MIDDLEWARE_ORDER` constants in [`catalog.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/catalog.ts) ensure deterministic execution of scope checks (10), logging (20), and authentication (30).
- **Zod Schema Validation**: Every tool uses `defineTool` with strict input/output schemas, providing type-safe contracts between clients and the server.
- **Transport Agnostic**: The same server core supports both HTTP (`createOpenSseHttpTransport`) and stdio transports, ensuring consistent behavior across deployment environments.

## Frequently Asked Questions

### How does the OmniRoute MCP server handle the loading of 94 tools without impacting startup performance?

The server uses **dynamic imports** in `buildMcpServer` to load tool modules only when the server initializes, rather than at build time. Each module exports a `register` function that returns a tool definition; these are collected into an array and passed to `createMcpServer`. This lazy-loading pattern ensures that heavy dependencies (like Notion or GitHub API clients) are only instantiated when the specific tool is first invoked.

### What determines the order of middleware execution in the OmniRoute MCP server?

Middleware execution follows the numeric priority system defined in `MIDDLEWARE_ORDER` within [`open-sse/mcp-server/catalog.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/mcp-server/catalog.ts). Scope enforcement runs at priority 10, logging at 20, and authentication validation at 30. When `baseServer.addMiddleware()` is called, it inserts handlers into a sorted chain based on these constants, ensuring that scope permissions are checked before logging or business logic executes.

### How are the 30 permission scopes assigned to API keys in the OmniRoute system?

Scopes are stored in the database and linked to API keys through the `getScopesForKey` method (referenced in [`scopeEnforcement.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/scopeEnforcement.ts)). When a request arrives, `createScopeEnforcementMiddleware` extracts the API key using `getApiKeyFromRequest`, queries the database for associated scopes, and stores them as a `Set` in `ctx.allowedScopes`. The middleware then continues to the next layer, where individual tool handlers verify that their required scope exists within this set.

### Can developers add custom tools to the OmniRoute MCP server without modifying core files?

Yes, developers can create new tool modules under `open-sse/mcp-server/tools/` following the standard export pattern. The module must export a `register` function that returns the result of `defineTool`, including Zod schemas for validation and an async handler. Once the module is added to [`tools/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/tools/index.ts) using an export statement (e.g., `export * from "./customTools"`), the `buildMcpServer` function will automatically include it in the registry during the next server startup.