Files
2026-07-13 13:39:12 +08:00

1371 lines
48 KiB
TypeScript

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
getComboModelProvider,
getComboModelString,
getComboStepTarget,
} from "../../src/lib/combos/steps.ts";
import { registerToolSearchTool } from "./toolSearch/register.ts";
import {
MCP_TOOLS,
getHealthInput,
listCombosInput,
getComboMetricsInput,
switchComboInput,
checkQuotaInput,
routeRequestInput,
costReportInput,
listModelsCatalogInput,
webSearchInput,
webFetchInput,
simulateRouteInput,
setBudgetGuardInput,
setRoutingStrategyInput,
setResilienceProfileInput,
testComboInput,
getProviderMetricsInput,
bestComboForTaskInput,
explainRouteInput,
pickFastestModelInput,
getSessionSnapshotInput,
dbHealthCheckInput,
syncPricingInput,
cacheStatsInput,
cacheFlushInput,
oneproxyFetchInput,
oneproxyRotateInput,
oneproxyStatsInput,
} from "./schemas/tools.ts";
import { startMcpHeartbeat } from "./runtimeHeartbeat.ts";
import { z } from "zod";
import { closeAuditDb, logToolCall } from "./audit.ts";
import {
evaluateToolScopes,
resolveCallerScopeContext,
type McpToolExtraLike,
} from "./scopeEnforcement.ts";
import { getMcpHttpAuthHeadersForInternalFetch } from "./httpAuthContext.ts";
import {
handleSimulateRoute,
handleSetBudgetGuard,
handleSetRoutingStrategy,
handleSetResilienceProfile,
handleTestCombo,
handleGetProviderMetrics,
handleBestComboForTask,
handleExplainRoute,
handleGetSessionSnapshot,
handleDbHealthCheck,
handleSyncPricing,
handleCacheStats,
handleCacheFlush,
handleOneproxyFetch,
handleOneproxyRotate,
handleOneproxyStats,
} from "./tools/advancedTools.ts";
import { handlePickFastestModel } from "./tools/pickFastestModel.ts";
import { memoryTools } from "./tools/memoryTools.ts";
import { skillTools } from "./tools/skillTools.ts";
import { agentSkillTools } from "./tools/agentSkillTools.ts";
import { githubSkillTools } from "./tools/githubSkillTools.ts";
import { skillRegistry } from "../../src/lib/skills/registry.ts";
import { skillExecutor } from "../../src/lib/skills/executor.ts";
import { pluginTools } from "./tools/pluginTools.ts";
import { compressionTools } from "./tools/compressionTools.ts";
import { poolTools } from "./tools/poolTools.ts";
import { gamificationTools } from "./tools/gamificationTools.ts";
import { notionTools } from "./tools/notionTools.ts";
import { obsidianTools } from "./tools/obsidianTools.ts";
import { compressMcpRegistryMetadata } from "./descriptionCompressor.ts";
import { reduceToolManifest, readMcpToolProfileFromEnv } from "./toolCardinality.ts";
import { smartFilterText } from "../services/compression/engines/mcpAccessibility/index.ts";
import {
DEFAULT_MCP_ACCESSIBILITY_CONFIG,
clampMcpAccessibilityConfig,
type McpAccessibilityConfig,
} from "../services/compression/engines/mcpAccessibility/constants.ts";
import { getDbInstance } from "../../src/lib/db/core.ts";
import { normalizeQuotaResponse } from "../../src/shared/contracts/quota.ts";
import { resolveOmniRouteBaseUrl } from "../../src/shared/utils/resolveOmniRouteBaseUrl.ts";
import { getMcpModelsCatalog } from "./catalog.ts";
export { getMcpModelsCatalog } from "./catalog.ts";
const OMNIROUTE_BASE_URL = resolveOmniRouteBaseUrl();
const MCP_ENFORCE_SCOPES = process.env.OMNIROUTE_MCP_ENFORCE_SCOPES === "true";
const MCP_ALLOWED_SCOPES = new Set(
(process.env.OMNIROUTE_MCP_SCOPES || "")
.split(",")
.map((s) => s.trim())
.filter(Boolean)
);
const TOTAL_MCP_TOOL_COUNT =
MCP_TOOLS.length +
Object.keys(memoryTools).length +
Object.keys(skillTools).length +
Object.keys(agentSkillTools).length +
Object.keys(githubSkillTools).length +
Object.keys(poolTools).length +
gamificationTools.length +
pluginTools.length +
notionTools.length +
obsidianTools.length;
type JsonRecord = Record<string, unknown>;
function readMcpDescriptionCompressionEnabled(): boolean {
try {
const row = getDbInstance()
.prepare("SELECT value FROM key_value WHERE namespace = ? AND key = ?")
.get("compression", "mcpDescriptionCompressionEnabled") as { value?: string } | undefined;
if (!row?.value) return true;
return JSON.parse(row.value) !== false;
} catch {
return true;
}
}
function readMcpAccessibilityConfig(): McpAccessibilityConfig {
try {
const row = getDbInstance()
.prepare("SELECT value FROM key_value WHERE namespace = ? AND key = ?")
.get("compression", "mcpAccessibility") as { value?: string } | undefined;
if (!row?.value) return { ...DEFAULT_MCP_ACCESSIBILITY_CONFIG };
// clampMcpAccessibilityConfig bounds every field (and folds in the non-object guard), so a
// persisted out-of-range maxTextChars can't make smartFilterText truncate the whole text.
return clampMcpAccessibilityConfig(JSON.parse(row.value));
} catch {
return { ...DEFAULT_MCP_ACCESSIBILITY_CONFIG };
}
}
type TextToolResult = {
content: Array<{ type: "text"; text: string }>;
isError?: boolean;
};
function toRecord(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
}
function toArray(value: unknown): unknown[] {
return Array.isArray(value) ? value : [];
}
function toString(value: unknown, fallback = ""): string {
return typeof value === "string" ? value : fallback;
}
function toNumber(value: unknown, fallback = 0): number {
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
}
function toStringArray(value: unknown, fallback: string[] = []): string[] {
const values = toArray(value).filter((entry): entry is string => typeof entry === "string");
return values.length > 0 ? values : fallback;
}
function normalizeComboModels(
rawModels: unknown
): Array<{ provider: string; model: string; priority: number }> {
return toArray(rawModels).map((rawModel, index) => {
const modelRecord = toRecord(rawModel);
const modelString = getComboModelString(rawModel);
const target = getComboStepTarget(rawModel);
const provider =
getComboModelProvider(rawModel) ||
(modelString ? "unknown" : target ? "combo" : toString(modelRecord.provider, "unknown"));
return {
provider,
model: modelString || target || toString(modelRecord.model, "unknown"),
priority: toNumber(modelRecord.priority, index + 1),
};
});
}
function getOmniRouteApiKey(): string {
return process.env.OMNIROUTE_API_KEY || "";
}
export async function omniRouteFetch(path: string, options: RequestInit = {}): Promise<unknown> {
const url = `${OMNIROUTE_BASE_URL}${path}`;
const apiKey = getOmniRouteApiKey();
const headers: Record<string, string> = {
"Content-Type": "application/json",
// Static env key is only a fallback; the per-caller MCP identity forwarded via
// withMcpHttpAuthContext must win over it (#5819).
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
...getMcpHttpAuthHeadersForInternalFetch(),
...((options.headers as Record<string, string>) || {}),
};
const signal = options.signal || AbortSignal.timeout(10000);
const response = await fetch(url, { ...options, headers, signal });
if (!response.ok) {
const errorText = await response.text().catch(() => "Unknown error");
throw new Error(`OmniRoute API error [${response.status}]: ${errorText}`);
}
return response.json();
}
function withScopeEnforcement(
toolName: string,
handler: (args: unknown, extra?: McpToolExtraLike) => Promise<TextToolResult>,
toolScopes?: readonly string[]
) {
return async (args: unknown, extra?: McpToolExtraLike): Promise<TextToolResult> => {
const scopeContext = resolveCallerScopeContext(extra, Array.from(MCP_ALLOWED_SCOPES));
const scopeCheck = evaluateToolScopes(
toolName,
scopeContext.scopes,
MCP_ENFORCE_SCOPES,
toolScopes
);
if (!scopeCheck.allowed) {
const missingScopes =
scopeCheck.missing.length > 0 ? scopeCheck.missing.join(", ") : "unavailable";
const reason = scopeCheck.reason || "scope_check_failed";
const msg =
`Insufficient MCP scopes for ${toolName}. ` +
`Missing: ${missingScopes}. ` +
`Caller=${scopeContext.callerId}, source=${scopeContext.source}.`;
const safeArgs = args && typeof args === "object" ? toRecord(args) : { rawArgs: args };
await logToolCall(
toolName,
{
...safeArgs,
_scopeCheck: {
callerId: scopeContext.callerId,
source: scopeContext.source,
required: scopeCheck.required,
provided: scopeCheck.provided,
missing: scopeCheck.missing,
},
},
null,
0,
false,
`scope_denied:${reason}`
);
return {
content: [{ type: "text" as const, text: `Error: ${msg}` }],
isError: true,
};
}
return handler(args, extra);
};
}
async function handleGetHealth() {
const start = Date.now();
try {
const [healthRaw, resilienceRaw, rateLimitsRaw] = await Promise.allSettled([
omniRouteFetch("/api/monitoring/health"),
omniRouteFetch("/api/resilience"),
omniRouteFetch("/api/rate-limits"),
]);
const health = healthRaw.status === "fulfilled" ? toRecord(healthRaw.value) : {};
const resilience = resilienceRaw.status === "fulfilled" ? toRecord(resilienceRaw.value) : {};
const rateLimits = rateLimitsRaw.status === "fulfilled" ? toRecord(rateLimitsRaw.value) : {};
const memoryUsageRaw = toRecord(health.memoryUsage);
const cacheStatsRaw = toRecord(health.cacheStats);
const resilienceCircuitBreakers = toArray(resilience.circuitBreakers);
const rateLimitEntries = toArray(rateLimits.limits);
const result = {
uptime: toString(health.uptime, "unknown"),
version: toString(health.version, "unknown"),
memoryUsage: {
heapUsed: toNumber(memoryUsageRaw.heapUsed, 0),
heapTotal: toNumber(memoryUsageRaw.heapTotal, 0),
},
circuitBreakers: resilienceCircuitBreakers,
rateLimits: rateLimitEntries,
cacheStats:
Object.keys(cacheStatsRaw).length > 0
? {
hits: toNumber(cacheStatsRaw.hits, 0),
misses: toNumber(cacheStatsRaw.misses, 0),
hitRate: toNumber(cacheStatsRaw.hitRate, 0),
}
: undefined,
cryptography: health.cryptography
? {
status: toString(toRecord(health.cryptography).status, "missing_or_invalid"),
provider: toString(toRecord(health.cryptography).provider, "unknown"),
}
: undefined,
};
await logToolCall("omniroute_get_health", {}, result, Date.now() - start, true);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
await logToolCall("omniroute_get_health", {}, null, Date.now() - start, false, msg);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
}
async function handleListCombos(args: { includeMetrics?: boolean }) {
const start = Date.now();
try {
const combosRaw = await omniRouteFetch("/api/combos");
const combosRecord = toRecord(combosRaw);
const combos = Array.isArray(combosRecord.combos)
? combosRecord.combos
: Array.isArray(combosRaw)
? combosRaw
: [];
let metrics: JsonRecord = {};
if (args.includeMetrics) {
metrics = toRecord(await omniRouteFetch("/api/combos/metrics").catch(() => ({})));
}
const result = {
combos: toArray(combos).map((rawCombo) => {
const combo = toRecord(rawCombo);
const comboData = toRecord(combo.data);
const comboId = toString(combo.id, "");
const modelsSource =
Array.isArray(combo.models) && combo.models.length > 0 ? combo.models : comboData.models;
return {
id: comboId,
name: toString(combo.name, comboId || "unnamed"),
models: normalizeComboModels(modelsSource),
strategy: toString(combo.strategy, toString(comboData.strategy, "priority")),
enabled: combo.enabled !== false,
...(args.includeMetrics ? { metrics: metrics[comboId] ?? null } : {}),
};
}),
};
await logToolCall("omniroute_list_combos", args, result, Date.now() - start, true);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
await logToolCall("omniroute_list_combos", args, null, Date.now() - start, false, msg);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
}
async function handleGetComboMetrics(args: { comboId: string }) {
const start = Date.now();
try {
const result = await omniRouteFetch(
`/api/combos/metrics?comboId=${encodeURIComponent(args.comboId)}`
);
await logToolCall("omniroute_get_combo_metrics", args, result, Date.now() - start, true);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
await logToolCall("omniroute_get_combo_metrics", args, null, Date.now() - start, false, msg);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
}
async function handleSwitchCombo(args: { comboId: string; active: boolean }) {
const start = Date.now();
try {
const result = await omniRouteFetch(`/api/combos/${encodeURIComponent(args.comboId)}`, {
method: "PUT",
body: JSON.stringify({ isActive: args.active }),
});
await logToolCall("omniroute_switch_combo", args, result, Date.now() - start, true);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
await logToolCall("omniroute_switch_combo", args, null, Date.now() - start, false, msg);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
}
async function handleCheckQuota(args: { provider?: string; connectionId?: string }) {
const start = Date.now();
try {
let path = "/api/usage/quota";
if (args.connectionId) path += `?connectionId=${encodeURIComponent(args.connectionId)}`;
else if (args.provider) path += `?provider=${encodeURIComponent(args.provider)}`;
const result = normalizeQuotaResponse(await omniRouteFetch(path), {
provider: args.provider || null,
connectionId: args.connectionId || null,
});
await logToolCall("omniroute_check_quota", args, result, Date.now() - start, true);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
await logToolCall("omniroute_check_quota", args, null, Date.now() - start, false, msg);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
}
async function handleRouteRequest(args: {
model: string;
messages: Array<{ role: string; content: string }>;
combo?: string;
budget?: number;
role?: string;
stream?: boolean;
}) {
const start = Date.now();
try {
const body: Record<string, unknown> = {
model: args.model,
messages: args.messages,
stream: false, // MCP tool always returns non-streaming
};
if (args.combo) {
body["x-combo"] = args.combo;
}
const raw = (await omniRouteFetch("/v1/chat/completions", {
method: "POST",
body: JSON.stringify(body),
})) as JsonRecord;
const choices = toArray(raw.choices);
const firstChoice = toRecord(choices[0]);
const firstMessage = toRecord(firstChoice.message);
const usage = toRecord(raw.usage);
const result = {
response: {
content: toString(firstMessage.content, ""),
model: toString(raw.model, args.model),
tokens: {
prompt: toNumber(usage.prompt_tokens, 0),
completion: toNumber(usage.completion_tokens, 0),
},
},
routing: {
provider: toString(raw.provider, "unknown"),
combo: raw.combo ?? null,
fallbacksTriggered: toNumber(raw.fallbacksTriggered, 0),
cost: toNumber(raw.cost, 0),
latencyMs: Date.now() - start,
routingExplanation: toString(
raw.routingExplanation,
"Request routed through primary provider"
),
},
};
await logToolCall(
"omniroute_route_request",
{ model: args.model, messageCount: args.messages.length },
result.routing,
Date.now() - start,
true
);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
await logToolCall(
"omniroute_route_request",
{ model: args.model },
null,
Date.now() - start,
false,
msg
);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
}
async function handleCostReport(args: { period?: string }) {
const start = Date.now();
try {
const period = args.period || "session";
const rangeMap: Record<string, string> = {
session: "1d",
day: "1d",
week: "7d",
month: "30d",
};
const range = rangeMap[period] || "30d";
const raw = toRecord(
await omniRouteFetch(`/api/usage/analytics?range=${encodeURIComponent(range)}`)
);
const tokenCount = toRecord(raw.tokenCount);
const budget = toRecord(raw.budget);
const result = {
period,
totalCost: toNumber(raw.totalCost, 0),
requestCount: toNumber(raw.requestCount, 0),
tokenCount: {
prompt: toNumber(tokenCount.prompt, 0),
completion: toNumber(tokenCount.completion, 0),
},
byProvider: toArray(raw.byProvider),
byModel: toArray(raw.byModel),
budget: {
limit: budget.limit ?? null,
remaining: budget.remaining ?? null,
},
};
await logToolCall("omniroute_cost_report", args, result, Date.now() - start, true);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
await logToolCall("omniroute_cost_report", args, null, Date.now() - start, false, msg);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
}
async function handleListModelsCatalog(args: { provider?: string; capability?: string }) {
const start = Date.now();
try {
const result = await getMcpModelsCatalog(args);
await logToolCall(
"omniroute_list_models_catalog",
args,
{ modelCount: result.models.length },
Date.now() - start,
true
);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
await logToolCall("omniroute_list_models_catalog", args, null, Date.now() - start, false, msg);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
}
async function handleWebSearch(args: {
query: string;
max_results?: number;
search_type?: "web" | "news";
provider?:
| "serper-search"
| "brave-search"
| "perplexity-search"
| "exa-search"
| "tavily-search"
| "google-pse-search"
| "linkup-search"
| "searchapi-search"
| "searxng-search";
}) {
const start = Date.now();
try {
const body: Record<string, unknown> = {
query: args.query,
max_results: args.max_results ?? 5,
search_type: args.search_type ?? "web",
};
if (args.provider) body.provider = args.provider;
const result = await omniRouteFetch("/v1/search", {
method: "POST",
body: JSON.stringify(body),
signal: AbortSignal.timeout(60000),
});
await logToolCall("omniroute_web_search", args, result, Date.now() - start, true);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
await logToolCall("omniroute_web_search", args, null, Date.now() - start, false, msg);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
}
async function handleWebFetch(args: {
url: string;
provider?: "firecrawl" | "jina-reader" | "tavily-search" | "tinyfish";
format?: "markdown" | "html" | "links" | "screenshot";
include_metadata?: boolean;
depth?: number;
wait_for_selector?: string;
}) {
const start = Date.now();
try {
const body: Record<string, unknown> = {
url: args.url,
format: args.format ?? "markdown",
include_metadata: args.include_metadata ?? false,
};
if (args.provider) body.provider = args.provider;
if (args.depth !== undefined) body.depth = args.depth;
if (args.wait_for_selector) body.wait_for_selector = args.wait_for_selector;
const result = await omniRouteFetch("/v1/web/fetch", {
method: "POST",
body: JSON.stringify(body),
signal: AbortSignal.timeout(60000),
});
await logToolCall("omniroute_web_fetch", args, result, Date.now() - start, true);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
await logToolCall("omniroute_web_fetch", args, null, Date.now() - start, false, msg);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
}
export function createMcpServer(): McpServer {
const server = new McpServer({
name: "omniroute",
version: process.env.npm_package_version || "1.8.1",
});
const mcpDescriptionCompressionEnabled = readMcpDescriptionCompressionEnabled();
const mcpAccessibilityConfig = readMcpAccessibilityConfig();
// F4.3 tool-cardinality: opt-in tool profile (MCP_TOOL_DENY / MCP_TOOL_ALLOW). null = no filter.
const toolProfile = readMcpToolProfileFromEnv(process.env);
const registerTool = server.registerTool.bind(server);
server.registerTool = ((name: string, config: Record<string, unknown>, handler: unknown) => {
const metadata = compressMcpRegistryMetadata(config, {
enabled: mcpDescriptionCompressionEnabled,
});
const filteredHandler = mcpAccessibilityConfig.enabled
? async (args: unknown, extra?: unknown) => {
const result = await (handler as (a: unknown, e?: unknown) => Promise<TextToolResult>)(
args,
extra
);
if (Array.isArray(result?.content)) {
for (const block of result.content) {
if (block && block.type === "text" && typeof block.text === "string") {
block.text = smartFilterText(block.text, mcpAccessibilityConfig);
}
}
}
return result;
}
: handler;
const registered = registerTool(name, metadata, filteredHandler as never);
if (toolProfile && reduceToolManifest([{ name, scopes: [] }], toolProfile).length === 0) {
// Denied by the cardinality profile: keep the registration valid but disable it so the tool
// is not announced in tools/list (token savings). The default profile never reaches here.
const disablable = registered as unknown as { disable?: () => void };
if (typeof disablable?.disable === "function") disablable.disable();
}
return registered;
}) as typeof server.registerTool;
const registerPrompt = server.registerPrompt.bind(server);
server.registerPrompt = ((name: string, config: Record<string, unknown>, handler: unknown) => {
const metadata = compressMcpRegistryMetadata(config, {
enabled: mcpDescriptionCompressionEnabled,
});
return registerPrompt(name, metadata as never, handler as never);
}) as typeof server.registerPrompt;
const registerResource = server.registerResource.bind(server);
server.registerResource = ((
name: string,
uriOrTemplate: unknown,
config: Record<string, unknown>,
readCallback: unknown
) => {
const metadata = compressMcpRegistryMetadata(config, {
enabled: mcpDescriptionCompressionEnabled,
});
return registerResource(name, uriOrTemplate as never, metadata as never, readCallback as never);
}) as typeof server.registerResource;
const RESERVED_MCP_NAMES = new Set([
...MCP_TOOLS.map((t) => t.name),
...Object.keys(memoryTools),
...Object.keys(skillTools),
...Object.keys(compressionTools),
...Object.keys(poolTools),
...pluginTools.map((t) => t.name),
...gamificationTools.map((t) => t.name),
...obsidianTools.map((t) => t.name),
...notionTools.map((t) => t.name),
]);
server.registerTool(
"omniroute_get_health",
{
description:
"Returns OmniRoute health status including uptime, memory, circuit breakers, rate limits, and cache stats",
inputSchema: getHealthInput,
},
withScopeEnforcement("omniroute_get_health", async (args) => {
getHealthInput.parse(args ?? {});
return handleGetHealth();
})
);
server.registerTool(
"omniroute_list_combos",
{
description:
"Lists all configured combos (model chains) with strategies and optional metrics",
inputSchema: listCombosInput,
},
withScopeEnforcement("omniroute_list_combos", (args) =>
handleListCombos(listCombosInput.parse(args))
)
);
server.registerTool(
"omniroute_get_combo_metrics",
{
description: "Returns detailed performance metrics for a specific combo",
inputSchema: getComboMetricsInput,
},
withScopeEnforcement("omniroute_get_combo_metrics", (args) =>
handleGetComboMetrics(getComboMetricsInput.parse(args))
)
);
server.registerTool(
"omniroute_switch_combo",
{
description: "Activates or deactivates a combo for routing",
inputSchema: switchComboInput,
},
withScopeEnforcement("omniroute_switch_combo", (args) =>
handleSwitchCombo(switchComboInput.parse(args))
)
);
server.registerTool(
"omniroute_check_quota",
{
description: "Checks remaining API quota for one or all providers",
inputSchema: checkQuotaInput,
},
withScopeEnforcement("omniroute_check_quota", (args) =>
handleCheckQuota(checkQuotaInput.parse(args))
)
);
server.registerTool(
"omniroute_route_request",
{
description: "Sends a chat completion request through OmniRoute intelligent routing",
inputSchema: routeRequestInput,
},
withScopeEnforcement("omniroute_route_request", (args) =>
handleRouteRequest(routeRequestInput.parse(args))
)
);
server.registerTool(
"omniroute_cost_report",
{
description: "Generates a cost report for the specified period",
inputSchema: costReportInput,
},
withScopeEnforcement("omniroute_cost_report", (args) =>
handleCostReport(costReportInput.parse(args))
)
);
server.registerTool(
"omniroute_list_models_catalog",
{
description: "Lists all available AI models across providers with capabilities and pricing",
inputSchema: listModelsCatalogInput,
},
withScopeEnforcement("omniroute_list_models_catalog", (args) =>
handleListModelsCatalog(listModelsCatalogInput.parse(args))
)
);
server.registerTool(
"omniroute_simulate_route",
{
description: "Simulates the routing path a request would take without executing it (dry-run)",
inputSchema: simulateRouteInput,
},
withScopeEnforcement("omniroute_simulate_route", (args) =>
handleSimulateRoute(simulateRouteInput.parse(args))
)
);
server.registerTool(
"omniroute_set_budget_guard",
{
description:
"Sets a session budget limit with configurable action when exceeded (degrade/block/alert)",
inputSchema: setBudgetGuardInput,
},
withScopeEnforcement("omniroute_set_budget_guard", (args) =>
handleSetBudgetGuard(setBudgetGuardInput.parse(args))
)
);
server.registerTool(
"omniroute_set_routing_strategy",
{
description:
"Updates combo routing strategy at runtime (priority/weighted/round-robin/auto/etc.)",
inputSchema: setRoutingStrategyInput,
},
withScopeEnforcement("omniroute_set_routing_strategy", (args) =>
handleSetRoutingStrategy(setRoutingStrategyInput.parse(args))
)
);
server.registerTool(
"omniroute_set_resilience_profile",
{
description:
"Applies a resilience profile controlling circuit breakers, retries, timeouts, and fallback depth",
inputSchema: setResilienceProfileInput,
},
withScopeEnforcement("omniroute_set_resilience_profile", (args) =>
handleSetResilienceProfile(setResilienceProfileInput.parse(args))
)
);
server.registerTool(
"omniroute_test_combo",
{
description:
"Tests each provider in a combo with a real prompt, reporting latency, cost, and success per provider",
inputSchema: testComboInput,
},
withScopeEnforcement("omniroute_test_combo", (args) =>
handleTestCombo(testComboInput.parse(args))
)
);
server.registerTool(
"omniroute_get_provider_metrics",
{
description:
"Returns detailed metrics for a specific provider including latency percentiles and circuit breaker state",
inputSchema: getProviderMetricsInput,
},
withScopeEnforcement("omniroute_get_provider_metrics", (args) =>
handleGetProviderMetrics(getProviderMetricsInput.parse(args))
)
);
server.registerTool(
"omniroute_best_combo_for_task",
{
description:
"Recommends the best combo for a task type based on provider fitness and constraints",
inputSchema: bestComboForTaskInput,
},
withScopeEnforcement("omniroute_best_combo_for_task", (args) =>
handleBestComboForTask(bestComboForTaskInput.parse(args))
)
);
server.registerTool(
"omniroute_explain_route",
{
description:
"Explains why a request was routed to a specific provider, showing scoring factors and fallbacks",
inputSchema: explainRouteInput,
},
withScopeEnforcement("omniroute_explain_route", (args) =>
handleExplainRoute(explainRouteInput.parse(args))
)
);
server.registerTool(
"omniroute_pick_fastest_model",
{
description: "Picks the fastest reliable provider-model pair from live telemetry.",
inputSchema: pickFastestModelInput,
},
withScopeEnforcement("omniroute_pick_fastest_model", (args) =>
handlePickFastestModel(pickFastestModelInput.parse(args))
)
);
server.registerTool(
"omniroute_get_session_snapshot",
{
description:
"Returns a full snapshot of the current working session: cost, tokens, top models, errors, budget status",
inputSchema: getSessionSnapshotInput,
},
withScopeEnforcement("omniroute_get_session_snapshot", async (args) => {
getSessionSnapshotInput.parse(args ?? {});
return handleGetSessionSnapshot();
})
);
server.registerTool(
"omniroute_db_health_check",
{
description:
"Diagnoses or repairs OmniRoute database drift, including broken combo references and orphan quota/domain rows",
inputSchema: dbHealthCheckInput,
},
withScopeEnforcement("omniroute_db_health_check", (args) =>
handleDbHealthCheck(dbHealthCheckInput.parse(args ?? {}))
)
);
server.registerTool(
"omniroute_sync_pricing",
{
description:
"Syncs pricing data from external sources (LiteLLM) into OmniRoute without overwriting user-set prices",
inputSchema: syncPricingInput,
},
withScopeEnforcement("omniroute_sync_pricing", (args) =>
handleSyncPricing(syncPricingInput.parse(args))
)
);
server.registerTool(
"omniroute_web_search",
{
description:
"Performs a web search using OmniRoute's search gateway. Supports multiple providers (Serper, Brave, Perplexity, Exa, Tavily) with automatic failover. Returns search results with titles, URLs, snippets, and position data.",
inputSchema: webSearchInput,
},
withScopeEnforcement("omniroute_web_search", (args) =>
handleWebSearch(webSearchInput.parse(args))
)
);
server.registerTool(
"omniroute_web_fetch",
{
description:
"Fetches and extracts content from a URL using OmniRoute's web fetch gateway. Supports multiple providers (Firecrawl, Jina Reader, Tavily) with automatic failover. Returns the page content as markdown, HTML, links, or screenshot, along with metadata.",
inputSchema: webFetchInput,
},
withScopeEnforcement("omniroute_web_fetch", (args) => handleWebFetch(webFetchInput.parse(args)))
);
server.registerTool(
"omniroute_cache_stats",
{
description:
"Returns cache statistics including semantic cache hit rate, prompt cache metrics by provider, and idempotency layer stats.",
inputSchema: cacheStatsInput,
},
withScopeEnforcement("omniroute_cache_stats", () => handleCacheStats())
);
server.registerTool(
"omniroute_cache_flush",
{
description:
"Flush cache entries. Provide signature to invalidate a single entry, model to invalidate all entries for a model, or omit both to clear all.",
inputSchema: cacheFlushInput,
},
withScopeEnforcement("omniroute_cache_flush", (args) =>
handleCacheFlush(cacheFlushInput.parse(args))
)
);
server.registerTool(
"omniroute_oneproxy_fetch",
{
description:
"Fetch free proxies from the 1proxy marketplace with optional filters for protocol, country, and quality. Returns validated proxies with quality scores.",
inputSchema: oneproxyFetchInput,
},
withScopeEnforcement("omniroute_oneproxy_fetch", (args) =>
handleOneproxyFetch(oneproxyFetchInput.parse(args))
)
);
server.registerTool(
"omniroute_oneproxy_rotate",
{
description:
"Get the next available free proxy from the 1proxy pool using the specified rotation strategy.",
inputSchema: oneproxyRotateInput,
},
withScopeEnforcement("omniroute_oneproxy_rotate", (args) =>
handleOneproxyRotate(oneproxyRotateInput.parse(args))
)
);
server.registerTool(
"omniroute_oneproxy_stats",
{
description:
"Returns 1proxy sync status and statistics: total proxies, average quality, sync history, and distribution by protocol and country.",
inputSchema: oneproxyStatsInput,
},
withScopeEnforcement("omniroute_oneproxy_stats", (args) =>
handleOneproxyStats(oneproxyStatsInput.parse(args))
)
);
registerToolSearchTool(server, withScopeEnforcement);
// ── Memory Tools ──────────────────────────────
Object.values(memoryTools).forEach((toolDef: any) => {
server.registerTool(
toolDef.name,
{
description: toolDef.description,
// @ts-ignore: dynamic zod access
inputSchema: toolDef.inputSchema,
},
withScopeEnforcement(
toolDef.name,
async (args, extra) => {
try {
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
// @ts-ignore - handler type lost through dynamic Object.values() access
const result = await toolDef.handler(parsedArgs, extra);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
},
toolDef.scopes
)
);
});
// ── Skill Tools ──────────────────────────────
Object.values(skillTools).forEach((toolDef: any) => {
server.registerTool(
toolDef.name,
{
description: toolDef.description,
// @ts-ignore: dynamic zod access
inputSchema: toolDef.inputSchema,
},
withScopeEnforcement(
toolDef.name,
async (args, extra) => {
try {
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
// @ts-ignore - handler type lost through dynamic Object.values() access
const result = await toolDef.handler(parsedArgs, extra);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
},
toolDef.scopes
)
);
});
// ── Agent Skill Tools ─────────────────────────
Object.values(agentSkillTools).forEach((toolDef) => {
server.registerTool(
toolDef.name,
{
description: toolDef.description,
// @ts-ignore: dynamic zod access
inputSchema: toolDef.inputSchema,
},
withScopeEnforcement(toolDef.name, async (args, extra) => {
try {
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
// @ts-expect-error - handler type lost through dynamic Object.values() access
const result = await toolDef.handler(parsedArgs, extra);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
})
);
});
// ── GitHub Skill Tools ──────────────────────────
Object.values(githubSkillTools).forEach((toolDef) => {
server.registerTool(
toolDef.name,
{
description: toolDef.description,
// @ts-ignore: dynamic zod access
inputSchema: toolDef.inputSchema,
},
withScopeEnforcement(
toolDef.name,
async (args) => {
try {
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
// @ts-expect-error - handler type lost through dynamic Object.values() access
const result = await toolDef.handler(parsedArgs);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
},
toolDef.scopes
)
);
});
// ── Plugin Tools ──────────────────────────────
pluginTools.forEach((toolDef) => {
server.registerTool(
toolDef.name,
{
description: toolDef.description,
// @ts-ignore: dynamic zod access
inputSchema: toolDef.inputSchema,
},
withScopeEnforcement(
toolDef.name,
async (args, extra) => {
try {
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
// @ts-ignore: handler expected specific object
const result = await toolDef.handler(parsedArgs, extra);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
},
toolDef.scopes
)
);
});
// ── Compression Tools ─────────────────────────
Object.values(compressionTools).forEach((toolDef: any) => {
server.registerTool(
toolDef.name,
{
description: toolDef.description,
// @ts-ignore: dynamic zod access
inputSchema: toolDef.inputSchema,
},
withScopeEnforcement(
toolDef.name,
async (args, extra) => {
try {
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
// @ts-ignore - handler type lost through dynamic Object.values() access
const result = await toolDef.handler(parsedArgs, extra);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
},
toolDef.scopes
)
);
});
// ── Web-Session Pool Tools (#3368 observability) ─
// Typed structurally (not `any`) — the shape is pinned by
// tests/unit/mcp-tool-collections-shape.test.ts, so the loop can stay strict.
Object.values(poolTools).forEach(
(toolDef: {
name: string;
description: string;
scopes: readonly string[];
inputSchema: { parse: (input: unknown) => unknown };
handler: (parsedArgs: unknown, extra?: unknown) => Promise<unknown>;
}) => {
server.registerTool(
toolDef.name,
{
description: toolDef.description,
// @ts-ignore: dynamic zod access
inputSchema: toolDef.inputSchema,
},
withScopeEnforcement(
toolDef.name,
async (args, extra) => {
try {
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
const result = await toolDef.handler(parsedArgs, extra);
return {
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
},
toolDef.scopes
)
);
}
);
// ── Gamification Tools ────────────────────────
gamificationTools.forEach((toolDef) => {
server.registerTool(
toolDef.name,
{
description: toolDef.description,
// @ts-ignore: dynamic zod access
inputSchema: toolDef.inputSchema,
},
withScopeEnforcement(
toolDef.name,
async (args, extra) => {
try {
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
// @ts-ignore: handler expected specific object
const result = await toolDef.handler(parsedArgs, extra);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
},
toolDef.scopes
)
);
});
// ── Notion Context Source Tools ───────────────
notionTools.forEach((toolDef) => {
server.registerTool(
toolDef.name,
{
description: toolDef.description,
// @ts-ignore: dynamic zod access
inputSchema: toolDef.inputSchema,
},
withScopeEnforcement(
toolDef.name,
async (args, extra) => {
try {
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
// @ts-ignore: handler expected specific object
const result = await toolDef.handler(parsedArgs, extra);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
},
toolDef.scopes
)
);
});
// ── Obsidian Context Source Tools ─────────────
obsidianTools.forEach((toolDef) => {
server.registerTool(
toolDef.name,
{
description: toolDef.description,
// @ts-ignore: dynamic zod access
inputSchema: toolDef.inputSchema,
},
withScopeEnforcement(
toolDef.name,
async (args, extra) => {
try {
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
// @ts-ignore: handler expected specific object
const result = await toolDef.handler(parsedArgs, extra);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
},
toolDef.scopes
)
);
});
// ── Dynamic Skill Tools (from skills table) ──
const skillToMcpToolName = (skill: { name: string }) =>
`skill_${skill.name.replace(/[^a-z0-9_-]/gi, "_")}`;
try {
const enabledSkills = skillRegistry.list().filter((s) => s.enabled);
for (const skill of enabledSkills) {
const toolName = skillToMcpToolName(skill);
if (RESERVED_MCP_NAMES.has(toolName)) continue;
server.registerTool(
toolName,
{
description: skill.description,
inputSchema: z.object({}).passthrough(),
},
withScopeEnforcement(
toolName,
async (args, extra) => {
const scopeContext = resolveCallerScopeContext(extra, Array.from(MCP_ALLOWED_SCOPES));
const apiKeyId = scopeContext.callerId || "mcp";
try {
const execution = await skillExecutor.execute(
skill.name,
(args ?? {}) as Record<string, unknown>,
{ apiKeyId }
);
return {
content: [
{ type: "text" as const, text: JSON.stringify(execution.output, null, 2) },
],
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return {
content: [{ type: "text" as const, text: `Error: ${msg}` }],
isError: true,
};
}
},
["execute:skills"]
)
);
}
} catch {
// Skills not loaded yet — skip dynamic registration until next reconnect
}
return server;
}
// ============ Main Entry Point (stdio) ============
/**
* Start the MCP server with stdio transport.
* Called when `omniroute --mcp` is used.
*/
export async function startMcpStdio(): Promise<void> {
const server = createMcpServer();
const transport = new StdioServerTransport();
const version = process.env.npm_package_version || "1.8.1";
const stopHeartbeat = startMcpHeartbeat({
version,
scopesEnforced: MCP_ENFORCE_SCOPES,
allowedScopes: Array.from(MCP_ALLOWED_SCOPES),
toolCount: TOTAL_MCP_TOOL_COUNT,
});
const stopHeartbeatOnce = () => {
stopHeartbeat();
};
process.once("exit", stopHeartbeatOnce);
process.once("SIGINT", stopHeartbeatOnce);
process.once("SIGTERM", stopHeartbeatOnce);
console.error("[MCP] OmniRoute MCP Server starting (stdio transport)...");
try {
await server.connect(transport);
console.error("[MCP] OmniRoute MCP Server connected and ready.");
} finally {
if (closeAuditDb()) {
console.error("[MCP] Audit database checkpointed and closed.");
}
stopHeartbeatOnce();
process.off("exit", stopHeartbeatOnce);
process.off("SIGINT", stopHeartbeatOnce);
process.off("SIGTERM", stopHeartbeatOnce);
}
}
// If this file is run directly, start stdio server
if (process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/"))) {
startMcpStdio().catch((err) => {
console.error("[MCP] Fatal error:", err);
process.exit(1);
});
}