Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions docs/frameworks/MCP-SERVER.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,9 @@ lastUpdated: 2026-06-28

# OmniRoute MCP Server Documentation

> Model Context Protocol server with 94 tools across routing, cache, compression, memory, skills, proxy, pool, and context source operations.
> Model Context Protocol server with 104 tools across routing, cache, compression, memory, skills, proxy, pool, and context source operations.
>
> Source of truth: `open-sse/mcp-server/schemas/tools.ts` (34 base) + `memoryTools.ts` (3) + `skillTools.ts` (4) + `agentSkillTools.ts` (3) + `poolTools.ts` (6) + `gamificationTools.ts` (8) + `pluginTools.ts` (8) + `notionTools.ts` (6) + `obsidianTools.ts` (22) = **94** (`TOTAL_MCP_TOOL_COUNT`). Tool registration and scope wiring lives in `open-sse/mcp-server/server.ts`.

![MCP tool inventory (94 tools by category)](../diagrams/exported/mcp-tools-94.svg)

> Source: [diagrams/mcp-tools-94.mmd](../diagrams/mcp-tools-94.mmd) (regenerate via `npm run docs:render-diagrams`).
> Source of truth: `open-sse/mcp-server/server.ts` computes **104 unique tools** with `countUniqueMcpTools()`: 42 canonical definitions (including the six CCR lifecycle tools and the agent-skills trio), plus memory (3), skills (4), GitHub skills (3), pool (6), gamification (8), plugins (8), Notion (6), Obsidian (22), and two RTK-only compression tools.

## Installation

Expand Down Expand Up @@ -110,7 +106,7 @@ Cursor, Cline, and compatible MCP client setup.
| `omniroute_cache_stats` | `read:cache` | Semantic cache, prompt-cache, and idempotency stats |
| `omniroute_cache_flush` | `write:cache` | Flush cache globally or by signature/model |

## Compression Tools (5)
## Compression Tools (13)

| Tool | Scopes | Description |
| :---------------------------------- | :------------------ | :----------------------------------------------------------------------------------------------------------------------- |
Expand All @@ -119,6 +115,20 @@ Cursor, Cline, and compatible MCP client setup.
| `omniroute_set_compression_engine` | `write:compression` | Pick the active engine (off/caveman/rtk/stacked) and Caveman/RTK intensity |
| `omniroute_list_compression_combos` | `read:compression` | List named compression combos and their engine pipelines |
| `omniroute_compression_combo_stats` | `read:compression` | Analytics grouped by compression combo and engine |
| `omniroute_ccr_store` | `write:compression` | Store caller-isolated content in the bounded in-memory CCR store and return a marker plus `ccr://` reference |
| `omniroute_ccr_retrieve` | `read:compression` | Retrieve CCR content in full or with head, tail, lines, grep, and stats modes |
| `omniroute_ccr_inspect` | `read:compression` | Inspect caller-owned CCR metadata without returning content |
| `omniroute_ccr_list` | `read:compression` | List paginated metadata for caller-owned CCR blocks |
| `omniroute_ccr_delete` | `write:compression` | Delete a caller-owned CCR block |
| `omniroute_ccr_stats` | `read:compression` | Report caller-scoped memory usage, lifecycle counters, and store limits |
| `omniroute_rtk_discover` | `read:compression` | Discover recurring noise in opt-in RTK output samples |
| `omniroute_rtk_learn` | `read:compression` | Generate a reviewable RTK filter draft from opt-in samples |

CCR entries are in-memory only and disappear on restart. Each block is limited to 2 MiB, each
principal to 16 MiB, and the global store to 64 MiB. Entries default to a 24-hour TTL (maximum
seven days). Full MCP retrieval is limited to 256 KiB; larger blocks remain available through the
ranged and grep modes. Storage, retrieval, listing, inspection, deletion, and stats are isolated by
the authenticated API-key principal. Audit records contain hashes and size metadata, never content.

`omniroute_compression_status` reports MCP description compression separately under
`analytics.mcpDescriptionCompression`. Those values are metadata-size estimates for MCP listable
Expand All @@ -127,7 +137,7 @@ receipts and are marked with `source: "mcp_metadata_estimate"`.

### MCP Accessibility Tree Filter (v3.8.0)

Separate from the 5 compression tools above, OmniRoute includes a post-execution filter that
Separate from the compression tools above, OmniRoute includes a post-execution filter that
compresses the **tool results** of MCP browser/accessibility tools before they are returned to the
agent. This filter is not itself a tool — it runs transparently on any tool result that contains
verbose accessibility-tree or browser-snapshot text (≥2000 chars).
Expand Down Expand Up @@ -217,7 +227,7 @@ See [AGENT-SKILLS.md](./AGENT-SKILLS.md) for the full catalog and how external a

## Related Frameworks (v3.8.0)

The MCP tool inventory above (94 tools = 34 core + 3 memory + 4 skills + 3 agent-skills + 6 pool + 8 gamification + 8 plugins + 6 notion + 22 obsidian) is intentionally
The MCP tool inventory above (104 unique tools, computed by `countUniqueMcpTools()`) is intentionally
scoped to runtime routing/cache/compression/memory/skills/proxy/context-source operations. Two adjacent
frameworks ship alongside the MCP server in v3.8.0 and are documented separately:

Expand Down Expand Up @@ -331,7 +341,7 @@ MCP tool, prompt, and resource registries can compress descriptions at registrat

Description compression shrinks each tool's metadata; **tool-cardinality reduction** goes one step further by reducing _how many_ tools are announced at all. Advertising fewer tools in the `tools/list` manifest cuts the per-request token cost the client's model pays for the tool catalog ("layer 5" compression). The implementation is a pure, stateless filter in `open-sse/mcp-server/toolCardinality.ts` (`reduceToolManifest`), wired into the registration loop in `createMcpServer()` (`open-sse/mcp-server/server.ts`).

**Opt-in, off by default.** The filter only runs when at least one of two environment variables is set; with neither set, all 94 tools are announced unchanged.
**Opt-in, off by default.** The filter only runs when at least one of two environment variables is set; with neither set, all 104 tools are announced unchanged.

| Variable | Mode |
| :--------------- | :-------------------------------------------------------------------------------------- |
Expand Down
16 changes: 13 additions & 3 deletions open-sse/mcp-server/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# OmniRoute MCP Server

> **Model Context Protocol server** that exposes OmniRoute's gateway intelligence as **37 tools** for AI agents.
> **Model Context Protocol server** that exposes OmniRoute's gateway intelligence as **104 tools** for AI agents.
>
> **Source of truth for the full tool catalog and REST surface:** [`docs/frameworks/MCP-SERVER.md`](../../docs/MCP-SERVER.md). This README focuses on architecture, configuration, and integration examples; the catalog below is a summary subset.
> **Source of truth for the full tool catalog and REST surface:** [`docs/frameworks/MCP-SERVER.md`](../../docs/frameworks/MCP-SERVER.md). This README focuses on architecture, configuration, and integration examples; the catalog below is a summary subset.

The MCP Server allows any AI agent (Claude Desktop, Cursor, VS Code Copilot, custom agents) to **monitor, control, and optimize** the OmniRoute AI gateway programmatically.

Expand All @@ -20,7 +20,7 @@ The MCP Server allows any AI agent (Claude Desktop, Cursor, VS Code Copilot, cus
┌──────────────────────────────────────────────────────────────────┐
│ OmniRoute MCP Server │
│ ┌──────────────┐ ┌─────────────────┐ ┌────────────────────┐ │
│ │ Scope │ │ 37 MCP Tools │ │ Audit Logger │ │
│ │ Scope │ │ 104 MCP Tools │ │ Audit Logger │ │
│ │ Enforcement │──│ (core + memory │──│ (SHA-256/SQLite) │ │
│ │ │ │ + skills + …) │ │ │ │
│ └──────────────┘ └────────┬────────┘ └────────────────────┘ │
Expand Down Expand Up @@ -157,6 +157,16 @@ omniroute --mcp
| 25 | `omniroute_set_compression_engine` | `write:compression` | Set Caveman, RTK, or stacked compression mode and pipeline |
| 26 | `omniroute_list_compression_combos` | `read:compression` | List named compression combos and routing assignments |
| 27 | `omniroute_compression_combo_stats` | `read:compression` | Read analytics grouped by compression combo and engine |
| 28 | `omniroute_ccr_store` | `write:compression` | Store content in the caller-isolated in-memory CCR store |
| 29 | `omniroute_ccr_retrieve` | `read:compression` | Retrieve full or ranged caller-owned CCR content |
| 30 | `omniroute_ccr_inspect` | `read:compression` | Inspect CCR metadata without returning content |
| 31 | `omniroute_ccr_list` | `read:compression` | List paginated caller-owned CCR metadata |
| 32 | `omniroute_ccr_delete` | `write:compression` | Delete a caller-owned CCR block |
| 33 | `omniroute_ccr_stats` | `read:compression` | Report caller usage, bounded-store limits, and lifecycle counters |

CCR storage is bounded and in-memory only: 2 MiB per block, 16 MiB per principal, 64 MiB global,
with a 24-hour default TTL. Full MCP retrieval is capped at 256 KiB; larger blocks use ranged or
grep retrieval. All lifecycle operations are isolated by the authenticated caller principal.

MCP listable metadata descriptions are compressed at registration/list time when description
compression is enabled. `omniroute_compression_status` exposes those savings separately as
Expand Down
9 changes: 4 additions & 5 deletions open-sse/mcp-server/__tests__/toolSearch.catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ describe("getAllToolDefinitions", () => {
const names = all.map((t) => t.name);
expect(new Set(names).size).toBe(names.length);
});
it("includes compressionTools-only entries (omniroute_ccr_retrieve, not in MCP_TOOLS)", () => {
// Regression: compressionTools carries omniroute_ccr_retrieve, which is absent from
// MCP_TOOLS — if the collection is dropped from the catalog, tool_search can never
// surface it. Guards against the catalog omission caught in core review.
expect(all.find((t) => t.name === "omniroute_ccr_retrieve")).toBeTruthy();
it("includes every canonical CCR lifecycle tool", () => {
for (const name of ["store", "retrieve", "inspect", "list", "delete", "stats"]) {
expect(all.find((tool) => tool.name === `omniroute_ccr_${name}`)).toBeTruthy();
}
});
});
202 changes: 202 additions & 0 deletions open-sse/mcp-server/schemas/ccrTools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { z } from "zod";

import type { McpToolDefinition } from "./toolDefinition.ts";

const ccrHash = z
.string()
.regex(/^[a-f0-9]{24}$/i)
.describe("24-hex content hash from a CCR marker or ccr:// URI");

export const ccrEntryMetadataOutput = z.object({
hash: ccrHash,
bytes: z.number().int().nonnegative(),
chars: z.number().int().nonnegative(),
lines: z.number().int().nonnegative(),
contentType: z.string(),
source: z.enum(["compression", "mcp", "ionizer", "session-dedup"]),
createdAt: z.number().int().nonnegative(),
lastAccessedAt: z.number().int().nonnegative(),
expiresAt: z.number().int().nonnegative(),
retrievalCount: z.number().int().nonnegative(),
});

export const ccrReferenceOutput = z.object({
hash: ccrHash,
uri: z.string().startsWith("ccr://"),
marker: z.string(),
});

export const ccrStoreInput = z.object({
content: z
.string()
.min(1)
.refine((content) => Buffer.byteLength(content, "utf8") <= 2 * 1024 * 1024, {
message: "Content exceeds the 2 MiB UTF-8 CCR block limit",
})
.describe("Verbatim content to keep in the in-memory CCR store (maximum 2 MiB UTF-8)"),
contentType: z.string().trim().min(1).max(128).optional(),
ttlSeconds: z
.number()
.int()
.min(60)
.max(7 * 24 * 60 * 60)
.optional(),
});

export const ccrStoreOutput = z.union([
z.object({
stored: z.literal(true),
reference: ccrReferenceOutput,
metadata: ccrEntryMetadataOutput,
}),
z.object({
stored: z.literal(false),
reason: z.enum(["block_too_large", "principal_budget_exceeded", "global_budget_exceeded"]),
}),
]);

export const ccrStoreTool: McpToolDefinition<typeof ccrStoreInput, typeof ccrStoreOutput> = {
name: "omniroute_ccr_store",
description:
"Store verbatim content in the caller-isolated in-memory CCR store and return a ccr:// reference plus the compatible CCR marker. Entries expire automatically and are not persisted across restarts.",
inputSchema: ccrStoreInput,
outputSchema: ccrStoreOutput,
scopes: ["write:compression"],
auditLevel: "basic",
phase: 2,
sourceEndpoints: [],
};

export const ccrRetrieveInput = z.object({
hash: ccrHash,
mode: z.enum(["full", "head", "tail", "lines", "grep", "stats"]).optional(),
n: z.number().int().positive().max(10_000).optional(),
start: z.number().int().positive().optional(),
end: z.number().int().positive().optional(),
pattern: z.string().max(512).optional(),
unique: z.boolean().optional(),
});

export const ccrRetrieveOutput = z.union([
z.object({
found: z.literal(false),
error: z.string(),
}),
z.object({
found: z.literal(true),
metadata: ccrEntryMetadataOutput,
content: z.string().optional(),
tooLargeForFull: z.boolean().optional(),
suggestedModes: z.array(z.enum(["head", "tail", "lines", "grep", "stats"])).optional(),
error: z.string().optional(),
}),
]);

export const ccrRetrieveTool: McpToolDefinition<typeof ccrRetrieveInput, typeof ccrRetrieveOutput> =
{
name: "omniroute_ccr_retrieve",
description:
"Retrieve caller-owned CCR content by hash. Full MCP responses are capped at 256 KiB; use head, tail, lines, grep, or stats for larger blocks.",
inputSchema: ccrRetrieveInput,
outputSchema: ccrRetrieveOutput,
scopes: ["read:compression"],
auditLevel: "basic",
phase: 2,
sourceEndpoints: ["/api/compression/retrieve"],
};

export const ccrInspectInput = z.object({ hash: ccrHash });
export const ccrInspectOutput = z.union([
z.object({ found: z.literal(false) }),
z.object({
found: z.literal(true),
reference: ccrReferenceOutput,
metadata: ccrEntryMetadataOutput,
}),
]);
export const ccrInspectTool: McpToolDefinition<typeof ccrInspectInput, typeof ccrInspectOutput> = {
name: "omniroute_ccr_inspect",
description: "Inspect metadata for a caller-owned CCR block without returning its content.",
inputSchema: ccrInspectInput,
outputSchema: ccrInspectOutput,
scopes: ["read:compression"],
auditLevel: "basic",
phase: 2,
sourceEndpoints: [],
};

export const ccrListInput = z.object({
offset: z.number().int().nonnegative().optional(),
limit: z.number().int().min(1).max(100).optional(),
});
export const ccrListOutput = z.object({
entries: z.array(z.object({ reference: ccrReferenceOutput, metadata: ccrEntryMetadataOutput })),
total: z.number().int().nonnegative(),
offset: z.number().int().nonnegative(),
limit: z.number().int().positive(),
hasMore: z.boolean(),
});
export const ccrListTool: McpToolDefinition<typeof ccrListInput, typeof ccrListOutput> = {
name: "omniroute_ccr_list",
description: "List paginated metadata for CCR blocks owned by the current caller.",
inputSchema: ccrListInput,
outputSchema: ccrListOutput,
scopes: ["read:compression"],
auditLevel: "basic",
phase: 2,
sourceEndpoints: [],
};

export const ccrDeleteInput = z.object({ hash: ccrHash });
export const ccrDeleteOutput = z.object({ deleted: z.boolean() });
export const ccrDeleteTool: McpToolDefinition<typeof ccrDeleteInput, typeof ccrDeleteOutput> = {
name: "omniroute_ccr_delete",
description: "Delete a caller-owned block from the in-memory CCR store.",
inputSchema: ccrDeleteInput,
outputSchema: ccrDeleteOutput,
scopes: ["write:compression"],
auditLevel: "basic",
phase: 2,
sourceEndpoints: [],
};

export const ccrStatsInput = z.object({});
export const ccrStatsOutput = z.object({
storage: z.literal("memory"),
entries: z.number().int().nonnegative(),
bytes: z.number().int().nonnegative(),
limits: z.object({
maxEntries: z.number().int().positive(),
maxBlockBytes: z.number().int().positive(),
maxPrincipalBytes: z.number().int().positive(),
maxGlobalBytes: z.number().int().positive(),
defaultTtlSeconds: z.number().int().positive(),
maxTtlSeconds: z.number().int().positive(),
maxMcpFullBytes: z.number().int().positive(),
}),
lifecycle: z.object({
expiredEvictions: z.number().int().nonnegative(),
capacityEvictions: z.number().int().nonnegative(),
rejectedStores: z.number().int().nonnegative(),
}),
});
export const ccrStatsTool: McpToolDefinition<typeof ccrStatsInput, typeof ccrStatsOutput> = {
name: "omniroute_ccr_stats",
description:
"Return caller-scoped CCR entry and byte usage, lifecycle counters, and in-memory store limits.",
inputSchema: ccrStatsInput,
outputSchema: ccrStatsOutput,
scopes: ["read:compression"],
auditLevel: "basic",
phase: 2,
sourceEndpoints: [],
};

export const CCR_MCP_TOOLS = [
ccrStoreTool,
ccrRetrieveTool,
ccrInspectTool,
ccrListTool,
ccrDeleteTool,
ccrStatsTool,
] as const;
20 changes: 20 additions & 0 deletions open-sse/mcp-server/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,26 @@ export {
cacheFlushInput,
cacheFlushOutput,
cacheFlushTool,
ccrEntryMetadataOutput,
ccrReferenceOutput,
ccrStoreInput,
ccrStoreOutput,
ccrStoreTool,
ccrRetrieveInput,
ccrRetrieveOutput,
ccrRetrieveTool,
ccrInspectInput,
ccrInspectOutput,
ccrInspectTool,
ccrListInput,
ccrListOutput,
ccrListTool,
ccrDeleteInput,
ccrDeleteOutput,
ccrDeleteTool,
ccrStatsInput,
ccrStatsOutput,
ccrStatsTool,
} from "./tools.ts";

// A2A schemas
Expand Down
3 changes: 3 additions & 0 deletions open-sse/mcp-server/schemas/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import { z } from "zod";
import { toolSearchTool } from "./toolSearch.ts";
import { pickFastestModelTool } from "./pickFastestModel.ts";
import { CCR_MCP_TOOLS } from "./ccrTools.ts";
import {
AUTO_ROUTING_STRATEGY_VALUES,
ROUTING_STRATEGY_VALUES,
Expand All @@ -24,6 +25,7 @@ import {
export type { AuditLevel, McpToolDefinition } from "./toolDefinition.ts";
import type { McpToolDefinition } from "./toolDefinition.ts";
export { pickFastestModelInput, pickFastestModelOutput } from "./pickFastestModel.ts";
export * from "./ccrTools.ts";

// ============ Phase 1: Essential Tools (8) ============

Expand Down Expand Up @@ -1462,6 +1464,7 @@ export const MCP_TOOLS = [
setCompressionEngineTool,
listCompressionCombosTool,
compressionComboStatsTool,
...CCR_MCP_TOOLS,
oneproxyFetchTool,
oneproxyRotateTool,
oneproxyStatsTool,
Expand Down
1 change: 1 addition & 0 deletions open-sse/mcp-server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ const TOTAL_MCP_TOOL_COUNT = countUniqueMcpTools({
pluginTools,
notionTools,
obsidianTools,
compressionTools,
});

type JsonRecord = Record<string, unknown>;
Expand Down
Loading
Loading