mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 23:02:10 +03:00
846 lines
25 KiB
TypeScript
846 lines
25 KiB
TypeScript
/**
|
|
* MCP Tool Schemas — Contracts for all 16 OmniRoute MCP tools.
|
|
*
|
|
* Defines input/output Zod schemas, descriptions, scopes, and audit levels
|
|
* for both essential (Phase 1) and advanced (Phase 3) MCP tools.
|
|
*
|
|
* Each tool wraps existing OmniRoute API endpoints and exposes them through
|
|
* the Model Context Protocol, enabling AI agents in IDEs (VS Code, Cursor,
|
|
* Copilot, Claude Desktop) to intelligently query gateway state.
|
|
*/
|
|
|
|
import { z } from "zod";
|
|
|
|
// ============ Shared Types ============
|
|
|
|
export type AuditLevel = "none" | "basic" | "full";
|
|
|
|
export interface McpToolDefinition<TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny> {
|
|
/** Tool name (MCP identifier) */
|
|
name: string;
|
|
/** Human-readable description for AI agents */
|
|
description: string;
|
|
/** Zod schema for input validation */
|
|
inputSchema: TInput;
|
|
/** Zod schema for output validation */
|
|
outputSchema: TOutput;
|
|
/** Required API key scopes */
|
|
scopes: readonly string[];
|
|
/** Audit logging level */
|
|
auditLevel: AuditLevel;
|
|
/** Phase: 1 = essential, 2 = advanced */
|
|
phase: 1 | 2;
|
|
/** Source endpoints on OmniRoute that this tool wraps */
|
|
sourceEndpoints: readonly string[];
|
|
}
|
|
|
|
// ============ Phase 1: Essential Tools (8) ============
|
|
|
|
// --- Tool 1: omniroute_get_health ---
|
|
export const getHealthInput = z.object({}).describe("No parameters required");
|
|
|
|
export const getHealthOutput = z.object({
|
|
uptime: z.string(),
|
|
version: z.string(),
|
|
memoryUsage: z.object({
|
|
heapUsed: z.number(),
|
|
heapTotal: z.number(),
|
|
}),
|
|
circuitBreakers: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
state: z.enum(["CLOSED", "OPEN", "HALF_OPEN"]),
|
|
failureCount: z.number(),
|
|
lastFailure: z.string().nullable(),
|
|
})
|
|
),
|
|
rateLimits: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
rpm: z.number(),
|
|
currentUsage: z.number(),
|
|
isLimited: z.boolean(),
|
|
})
|
|
),
|
|
cacheStats: z
|
|
.object({
|
|
hits: z.number(),
|
|
misses: z.number(),
|
|
hitRate: z.number(),
|
|
})
|
|
.optional(),
|
|
});
|
|
|
|
export const getHealthTool: McpToolDefinition<typeof getHealthInput, typeof getHealthOutput> = {
|
|
name: "omniroute_get_health",
|
|
description:
|
|
"Returns the current health status of OmniRoute including uptime, memory usage, circuit breaker states for all providers, rate limit status, and cache statistics.",
|
|
inputSchema: getHealthInput,
|
|
outputSchema: getHealthOutput,
|
|
scopes: ["read:health"],
|
|
auditLevel: "basic",
|
|
phase: 1,
|
|
sourceEndpoints: ["/api/monitoring/health", "/api/resilience", "/api/rate-limits"],
|
|
};
|
|
|
|
// --- Tool 2: omniroute_list_combos ---
|
|
export const listCombosInput = z.object({
|
|
includeMetrics: z
|
|
.boolean()
|
|
.optional()
|
|
.describe("Include request count, success rate, latency, and cost metrics per combo"),
|
|
});
|
|
|
|
export const listCombosOutput = z.object({
|
|
combos: z.array(
|
|
z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
models: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
model: z.string(),
|
|
priority: z.number(),
|
|
})
|
|
),
|
|
strategy: z.enum([
|
|
"priority",
|
|
"weighted",
|
|
"round-robin",
|
|
"strict-random",
|
|
"random",
|
|
"least-used",
|
|
"cost-optimized",
|
|
"auto",
|
|
]),
|
|
enabled: z.boolean(),
|
|
metrics: z
|
|
.object({
|
|
requestCount: z.number(),
|
|
successRate: z.number(),
|
|
avgLatencyMs: z.number(),
|
|
totalCost: z.number(),
|
|
})
|
|
.optional(),
|
|
})
|
|
),
|
|
});
|
|
|
|
export const listCombosTool: McpToolDefinition<typeof listCombosInput, typeof listCombosOutput> = {
|
|
name: "omniroute_list_combos",
|
|
description:
|
|
"Lists all configured combos (model chains) with their strategies and optionally includes performance metrics. Combos define how requests are routed across multiple providers.",
|
|
inputSchema: listCombosInput,
|
|
outputSchema: listCombosOutput,
|
|
scopes: ["read:combos"],
|
|
auditLevel: "basic",
|
|
phase: 1,
|
|
sourceEndpoints: ["/api/combos", "/api/combos/metrics"],
|
|
};
|
|
|
|
// --- Tool 3: omniroute_get_combo_metrics ---
|
|
export const getComboMetricsInput = z.object({
|
|
comboId: z.string().describe("ID of the combo to get metrics for"),
|
|
});
|
|
|
|
export const getComboMetricsOutput = z.object({
|
|
requests: z.number(),
|
|
successRate: z.number(),
|
|
avgLatency: z.number(),
|
|
costTotal: z.number(),
|
|
fallbackCount: z.number(),
|
|
byProvider: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
requests: z.number(),
|
|
successRate: z.number(),
|
|
avgLatency: z.number(),
|
|
})
|
|
),
|
|
});
|
|
|
|
export const getComboMetricsTool: McpToolDefinition<
|
|
typeof getComboMetricsInput,
|
|
typeof getComboMetricsOutput
|
|
> = {
|
|
name: "omniroute_get_combo_metrics",
|
|
description:
|
|
"Returns detailed performance metrics for a specific combo including request count, success rate, average latency, total cost, and per-provider breakdowns.",
|
|
inputSchema: getComboMetricsInput,
|
|
outputSchema: getComboMetricsOutput,
|
|
scopes: ["read:combos"],
|
|
auditLevel: "basic",
|
|
phase: 1,
|
|
sourceEndpoints: ["/api/combos/metrics"],
|
|
};
|
|
|
|
// --- Tool 4: omniroute_switch_combo ---
|
|
export const switchComboInput = z.object({
|
|
comboId: z.string().describe("ID of the combo to activate/deactivate"),
|
|
active: z.boolean().describe("Whether to enable or disable the combo"),
|
|
});
|
|
|
|
export const switchComboOutput = z.object({
|
|
success: z.boolean(),
|
|
combo: z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
enabled: z.boolean(),
|
|
}),
|
|
});
|
|
|
|
export const switchComboTool: McpToolDefinition<typeof switchComboInput, typeof switchComboOutput> =
|
|
{
|
|
name: "omniroute_switch_combo",
|
|
description:
|
|
"Activates or deactivates a combo. When deactivated, requests will not be routed through this combo. Use to toggle between different routing strategies.",
|
|
inputSchema: switchComboInput,
|
|
outputSchema: switchComboOutput,
|
|
scopes: ["write:combos"],
|
|
auditLevel: "full",
|
|
phase: 1,
|
|
sourceEndpoints: ["/api/combos"],
|
|
};
|
|
|
|
// --- Tool 5: omniroute_check_quota ---
|
|
export const checkQuotaInput = z.object({
|
|
provider: z
|
|
.string()
|
|
.optional()
|
|
.describe(
|
|
"Filter by provider name (e.g., 'claude', 'gemini'). If omitted, returns all providers."
|
|
),
|
|
connectionId: z.string().optional().describe("Filter by specific connection ID"),
|
|
});
|
|
|
|
export const checkQuotaOutput = z.object({
|
|
providers: z.array(
|
|
z.object({
|
|
name: z.string(),
|
|
provider: z.string(),
|
|
connectionId: z.string(),
|
|
quotaUsed: z.number(),
|
|
quotaTotal: z.number().nullable(),
|
|
percentRemaining: z.number(),
|
|
resetAt: z.string().nullable(),
|
|
tokenStatus: z.enum(["valid", "expiring", "expired", "refreshing"]),
|
|
})
|
|
),
|
|
meta: z
|
|
.object({
|
|
generatedAt: z.string(),
|
|
filters: z.object({
|
|
provider: z.string().nullable(),
|
|
connectionId: z.string().nullable(),
|
|
}),
|
|
totalProviders: z.number(),
|
|
})
|
|
.optional(),
|
|
});
|
|
|
|
export const checkQuotaTool: McpToolDefinition<typeof checkQuotaInput, typeof checkQuotaOutput> = {
|
|
name: "omniroute_check_quota",
|
|
description:
|
|
"Checks the remaining API quota for one or all providers. Returns quota used/total, percentage remaining, reset time, and token health status.",
|
|
inputSchema: checkQuotaInput,
|
|
outputSchema: checkQuotaOutput,
|
|
scopes: ["read:quota"],
|
|
auditLevel: "basic",
|
|
phase: 1,
|
|
sourceEndpoints: ["/api/usage/quota", "/api/token-health", "/api/rate-limits"],
|
|
};
|
|
|
|
// --- Tool 6: omniroute_route_request ---
|
|
export const routeRequestInput = z.object({
|
|
model: z.string().describe("Model identifier (e.g., 'claude-sonnet-4', 'gpt-4o')"),
|
|
messages: z
|
|
.array(
|
|
z.object({
|
|
role: z.string(),
|
|
content: z.string(),
|
|
})
|
|
)
|
|
.describe("Chat messages in OpenAI format"),
|
|
combo: z.string().optional().describe("Specific combo to route through"),
|
|
budget: z.number().optional().describe("Maximum cost in USD for this request"),
|
|
role: z
|
|
.enum(["coding", "review", "planning", "analysis"])
|
|
.optional()
|
|
.describe("Task role hint for intelligent routing"),
|
|
stream: z.boolean().optional().default(false).describe("Whether to stream the response"),
|
|
});
|
|
|
|
export const routeRequestOutput = z.object({
|
|
response: z.object({
|
|
content: z.string(),
|
|
model: z.string(),
|
|
tokens: z.object({
|
|
prompt: z.number(),
|
|
completion: z.number(),
|
|
}),
|
|
}),
|
|
routing: z.object({
|
|
provider: z.string(),
|
|
combo: z.string().nullable(),
|
|
fallbacksTriggered: z.number(),
|
|
cost: z.number(),
|
|
latencyMs: z.number(),
|
|
routingExplanation: z.string(),
|
|
}),
|
|
});
|
|
|
|
export const routeRequestTool: McpToolDefinition<
|
|
typeof routeRequestInput,
|
|
typeof routeRequestOutput
|
|
> = {
|
|
name: "omniroute_route_request",
|
|
description:
|
|
"Sends a chat completion request through OmniRoute's intelligent routing pipeline. Supports combo selection, budget limits, and task role hints for optimal provider matching.",
|
|
inputSchema: routeRequestInput,
|
|
outputSchema: routeRequestOutput,
|
|
scopes: ["execute:completions"],
|
|
auditLevel: "full",
|
|
phase: 1,
|
|
sourceEndpoints: ["/v1/chat/completions", "/v1/responses"],
|
|
};
|
|
|
|
// --- Tool 7: omniroute_cost_report ---
|
|
export const costReportInput = z.object({
|
|
period: z
|
|
.enum(["session", "day", "week", "month"])
|
|
.optional()
|
|
.default("session")
|
|
.describe("Time period for the cost report"),
|
|
});
|
|
|
|
export const costReportOutput = z.object({
|
|
period: z.string(),
|
|
totalCost: z.number(),
|
|
requestCount: z.number(),
|
|
tokenCount: z.object({
|
|
prompt: z.number(),
|
|
completion: z.number(),
|
|
}),
|
|
byProvider: z.array(
|
|
z.object({
|
|
name: z.string(),
|
|
cost: z.number(),
|
|
requests: z.number(),
|
|
})
|
|
),
|
|
byModel: z.array(
|
|
z.object({
|
|
model: z.string(),
|
|
cost: z.number(),
|
|
requests: z.number(),
|
|
})
|
|
),
|
|
budget: z.object({
|
|
limit: z.number().nullable(),
|
|
remaining: z.number().nullable(),
|
|
}),
|
|
});
|
|
|
|
export const costReportTool: McpToolDefinition<typeof costReportInput, typeof costReportOutput> = {
|
|
name: "omniroute_cost_report",
|
|
description:
|
|
"Generates a cost report for the specified period showing total cost, request count, token usage, and breakdowns by provider and model. Also shows budget status if configured.",
|
|
inputSchema: costReportInput,
|
|
outputSchema: costReportOutput,
|
|
scopes: ["read:usage"],
|
|
auditLevel: "basic",
|
|
phase: 1,
|
|
sourceEndpoints: ["/api/usage/analytics", "/api/usage/budget"],
|
|
};
|
|
|
|
// --- Tool 8: omniroute_list_models_catalog ---
|
|
export const listModelsCatalogInput = z.object({
|
|
provider: z.string().optional().describe("Filter by provider name"),
|
|
capability: z
|
|
.enum(["chat", "embedding", "image", "audio", "video", "rerank", "moderation"])
|
|
.optional()
|
|
.describe("Filter by model capability"),
|
|
});
|
|
|
|
export const listModelsCatalogOutput = z.object({
|
|
models: z.array(
|
|
z.object({
|
|
id: z.string(),
|
|
provider: z.string(),
|
|
capabilities: z.array(z.string()),
|
|
status: z.enum(["available", "degraded", "unavailable"]),
|
|
pricing: z
|
|
.object({
|
|
inputPerMillion: z.number().nullable(),
|
|
outputPerMillion: z.number().nullable(),
|
|
})
|
|
.optional(),
|
|
})
|
|
),
|
|
});
|
|
|
|
export const listModelsCatalogTool: McpToolDefinition<
|
|
typeof listModelsCatalogInput,
|
|
typeof listModelsCatalogOutput
|
|
> = {
|
|
name: "omniroute_list_models_catalog",
|
|
description:
|
|
"Lists all available AI models across all providers with their capabilities, current status, and pricing information.",
|
|
inputSchema: listModelsCatalogInput,
|
|
outputSchema: listModelsCatalogOutput,
|
|
scopes: ["read:models"],
|
|
auditLevel: "none",
|
|
phase: 1,
|
|
sourceEndpoints: ["/api/models/catalog", "/v1/models"],
|
|
};
|
|
|
|
// ============ Phase 2: Advanced Tools (8) ============
|
|
|
|
// --- Tool 9: omniroute_simulate_route ---
|
|
export const simulateRouteInput = z.object({
|
|
model: z.string().describe("Target model for simulation"),
|
|
promptTokenEstimate: z.number().describe("Estimated prompt token count"),
|
|
combo: z.string().optional().describe("Specific combo to simulate (default: active combo)"),
|
|
});
|
|
|
|
export const simulateRouteOutput = z.object({
|
|
simulatedPath: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
model: z.string(),
|
|
probability: z.number(),
|
|
estimatedCost: z.number(),
|
|
healthStatus: z.enum(["CLOSED", "OPEN", "HALF_OPEN"]),
|
|
quotaAvailable: z.number(),
|
|
})
|
|
),
|
|
fallbackTree: z.object({
|
|
primary: z.string(),
|
|
fallbacks: z.array(z.string()),
|
|
worstCaseCost: z.number(),
|
|
bestCaseCost: z.number(),
|
|
}),
|
|
});
|
|
|
|
export const simulateRouteTool: McpToolDefinition<
|
|
typeof simulateRouteInput,
|
|
typeof simulateRouteOutput
|
|
> = {
|
|
name: "omniroute_simulate_route",
|
|
description:
|
|
"Simulates (dry-run) the routing path a request would take without actually executing it. Shows the fallback tree, provider probabilities, estimated costs, and health status.",
|
|
inputSchema: simulateRouteInput,
|
|
outputSchema: simulateRouteOutput,
|
|
scopes: ["read:health", "read:combos"],
|
|
auditLevel: "basic",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/combos", "/api/monitoring/health", "/api/resilience"],
|
|
};
|
|
|
|
// --- Tool 10: omniroute_set_budget_guard ---
|
|
export const setBudgetGuardInput = z.object({
|
|
maxCost: z.number().describe("Maximum cost in USD for this session"),
|
|
action: z.enum(["degrade", "block", "alert"]).describe("Action when budget is exceeded"),
|
|
degradeToTier: z
|
|
.enum(["cheap", "free"])
|
|
.optional()
|
|
.describe("If action=degrade, which tier to fall back to"),
|
|
});
|
|
|
|
export const setBudgetGuardOutput = z.object({
|
|
sessionId: z.string(),
|
|
budgetTotal: z.number(),
|
|
budgetSpent: z.number(),
|
|
budgetRemaining: z.number(),
|
|
action: z.string(),
|
|
status: z.enum(["active", "warning", "exceeded"]),
|
|
});
|
|
|
|
export const setBudgetGuardTool: McpToolDefinition<
|
|
typeof setBudgetGuardInput,
|
|
typeof setBudgetGuardOutput
|
|
> = {
|
|
name: "omniroute_set_budget_guard",
|
|
description:
|
|
"Sets a budget guard that limits spending for the current session. When the budget is reached, it can degrade to cheaper models, block requests, or send alerts.",
|
|
inputSchema: setBudgetGuardInput,
|
|
outputSchema: setBudgetGuardOutput,
|
|
scopes: ["write:budget"],
|
|
auditLevel: "full",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/usage/budget"],
|
|
};
|
|
|
|
// --- Tool 11: omniroute_set_routing_strategy ---
|
|
export const setRoutingStrategyInput = z.object({
|
|
comboId: z.string().describe("Combo ID or name to update"),
|
|
strategy: z
|
|
.enum([
|
|
"priority",
|
|
"weighted",
|
|
"round-robin",
|
|
"strict-random",
|
|
"random",
|
|
"least-used",
|
|
"cost-optimized",
|
|
"auto",
|
|
])
|
|
.describe("Routing strategy to apply"),
|
|
autoRoutingStrategy: z
|
|
.enum(["rules", "cost", "eco", "latency", "fast"])
|
|
.optional()
|
|
.describe("Optional strategy used by auto mode (only used when strategy='auto')"),
|
|
});
|
|
|
|
export const setRoutingStrategyOutput = z.object({
|
|
success: z.boolean(),
|
|
combo: z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
strategy: z.string(),
|
|
autoRoutingStrategy: z.string().nullable(),
|
|
}),
|
|
});
|
|
|
|
export const setRoutingStrategyTool: McpToolDefinition<
|
|
typeof setRoutingStrategyInput,
|
|
typeof setRoutingStrategyOutput
|
|
> = {
|
|
name: "omniroute_set_routing_strategy",
|
|
description:
|
|
"Updates a combo routing strategy (priority/weighted/auto/etc.) at runtime. Supports selecting the sub-strategy used by auto mode (rules/cost/latency).",
|
|
inputSchema: setRoutingStrategyInput,
|
|
outputSchema: setRoutingStrategyOutput,
|
|
scopes: ["write:combos"],
|
|
auditLevel: "full",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/combos", "/api/combos/{id}"],
|
|
};
|
|
|
|
// --- Tool 12: omniroute_set_resilience_profile ---
|
|
export const setResilienceProfileInput = z.object({
|
|
profile: z
|
|
.enum(["aggressive", "balanced", "conservative"])
|
|
.describe("Resilience profile to apply"),
|
|
});
|
|
|
|
export const setResilienceProfileOutput = z.object({
|
|
applied: z.boolean(),
|
|
settings: z.object({
|
|
circuitBreakerThreshold: z.number(),
|
|
retryCount: z.number(),
|
|
timeoutMs: z.number(),
|
|
fallbackDepth: z.number(),
|
|
}),
|
|
});
|
|
|
|
export const setResilienceProfileTool: McpToolDefinition<
|
|
typeof setResilienceProfileInput,
|
|
typeof setResilienceProfileOutput
|
|
> = {
|
|
name: "omniroute_set_resilience_profile",
|
|
description:
|
|
"Applies a resilience profile that adjusts circuit breaker thresholds, retry counts, timeouts, and fallback depth. 'aggressive' = fast fail, 'conservative' = max retries.",
|
|
inputSchema: setResilienceProfileInput,
|
|
outputSchema: setResilienceProfileOutput,
|
|
scopes: ["write:resilience"],
|
|
auditLevel: "full",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/resilience"],
|
|
};
|
|
|
|
// --- Tool 13: omniroute_test_combo ---
|
|
export const testComboInput = z.object({
|
|
comboId: z.string().describe("ID of the combo to test"),
|
|
testPrompt: z.string().max(500).describe("Short test prompt (max 500 chars)"),
|
|
});
|
|
|
|
export const testComboOutput = z.object({
|
|
results: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
model: z.string(),
|
|
success: z.boolean(),
|
|
latencyMs: z.number(),
|
|
cost: z.number(),
|
|
tokenCount: z.number(),
|
|
error: z.string().optional(),
|
|
})
|
|
),
|
|
summary: z.object({
|
|
totalProviders: z.number(),
|
|
successful: z.number(),
|
|
fastestProvider: z.string(),
|
|
cheapestProvider: z.string(),
|
|
}),
|
|
});
|
|
|
|
export const testComboTool: McpToolDefinition<typeof testComboInput, typeof testComboOutput> = {
|
|
name: "omniroute_test_combo",
|
|
description:
|
|
"Tests a combo by sending a short test prompt to each provider in the combo and reporting individual results including latency, cost, and success status.",
|
|
inputSchema: testComboInput,
|
|
outputSchema: testComboOutput,
|
|
scopes: ["execute:completions", "read:combos"],
|
|
auditLevel: "full",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/combos/test", "/v1/chat/completions"],
|
|
};
|
|
|
|
// --- Tool 14: omniroute_get_provider_metrics ---
|
|
export const getProviderMetricsInput = z.object({
|
|
provider: z.string().describe("Provider name (e.g., 'claude', 'gemini-cli', 'codex')"),
|
|
});
|
|
|
|
export const getProviderMetricsOutput = z.object({
|
|
provider: z.string(),
|
|
successRate: z.number(),
|
|
requestCount: z.number(),
|
|
avgLatencyMs: z.number(),
|
|
p50LatencyMs: z.number(),
|
|
p95LatencyMs: z.number(),
|
|
p99LatencyMs: z.number(),
|
|
errorRate: z.number(),
|
|
lastError: z
|
|
.object({
|
|
message: z.string(),
|
|
timestamp: z.string(),
|
|
})
|
|
.nullable(),
|
|
circuitBreakerState: z.enum(["CLOSED", "OPEN", "HALF_OPEN"]),
|
|
quotaInfo: z.object({
|
|
used: z.number(),
|
|
total: z.number().nullable(),
|
|
resetAt: z.string().nullable(),
|
|
}),
|
|
});
|
|
|
|
export const getProviderMetricsTool: McpToolDefinition<
|
|
typeof getProviderMetricsInput,
|
|
typeof getProviderMetricsOutput
|
|
> = {
|
|
name: "omniroute_get_provider_metrics",
|
|
description:
|
|
"Returns detailed performance metrics for a specific provider including success/error rates, latency percentiles (p50/p95/p99), circuit breaker state, and quota information.",
|
|
inputSchema: getProviderMetricsInput,
|
|
outputSchema: getProviderMetricsOutput,
|
|
scopes: ["read:health"],
|
|
auditLevel: "basic",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/provider-metrics", "/api/resilience"],
|
|
};
|
|
|
|
// --- Tool 15: omniroute_best_combo_for_task ---
|
|
export const bestComboForTaskInput = z.object({
|
|
taskType: z
|
|
.enum(["coding", "review", "planning", "analysis", "debugging", "documentation"])
|
|
.describe("Type of task to find the best combo for"),
|
|
budgetConstraint: z.number().optional().describe("Maximum cost in USD"),
|
|
latencyConstraint: z.number().optional().describe("Maximum acceptable latency in ms"),
|
|
});
|
|
|
|
export const bestComboForTaskOutput = z.object({
|
|
recommendedCombo: z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
reason: z.string(),
|
|
}),
|
|
alternatives: z.array(
|
|
z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
tradeoff: z.string(),
|
|
})
|
|
),
|
|
freeAlternative: z
|
|
.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
})
|
|
.nullable(),
|
|
});
|
|
|
|
export const bestComboForTaskTool: McpToolDefinition<
|
|
typeof bestComboForTaskInput,
|
|
typeof bestComboForTaskOutput
|
|
> = {
|
|
name: "omniroute_best_combo_for_task",
|
|
description:
|
|
"Recommends the best combo for a given task type (coding, review, planning, etc.) considering budget and latency constraints. Also suggests alternatives and free options.",
|
|
inputSchema: bestComboForTaskInput,
|
|
outputSchema: bestComboForTaskOutput,
|
|
scopes: ["read:combos", "read:health"],
|
|
auditLevel: "basic",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/combos", "/api/combos/metrics", "/api/monitoring/health"],
|
|
};
|
|
|
|
// --- Tool 16: omniroute_explain_route ---
|
|
export const explainRouteInput = z.object({
|
|
requestId: z.string().describe("Request ID from the X-Request-Id header"),
|
|
});
|
|
|
|
export const explainRouteOutput = z.object({
|
|
requestId: z.string(),
|
|
decision: z.object({
|
|
comboUsed: z.string(),
|
|
providerSelected: z.string(),
|
|
modelUsed: z.string(),
|
|
score: z.number(),
|
|
factors: z.array(
|
|
z.object({
|
|
name: z.string(),
|
|
value: z.number(),
|
|
weight: z.number(),
|
|
contribution: z.number(),
|
|
})
|
|
),
|
|
fallbacksTriggered: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
reason: z.string(),
|
|
})
|
|
),
|
|
costActual: z.number(),
|
|
latencyActual: z.number(),
|
|
}),
|
|
});
|
|
|
|
export const explainRouteTool: McpToolDefinition<
|
|
typeof explainRouteInput,
|
|
typeof explainRouteOutput
|
|
> = {
|
|
name: "omniroute_explain_route",
|
|
description:
|
|
"Explains why a specific request was routed to a particular provider. Shows the scoring factors, weights, fallbacks triggered, actual cost, and latency.",
|
|
inputSchema: explainRouteInput,
|
|
outputSchema: explainRouteOutput,
|
|
scopes: ["read:health", "read:usage"],
|
|
auditLevel: "basic",
|
|
phase: 2,
|
|
sourceEndpoints: [],
|
|
};
|
|
|
|
// --- Tool 17: omniroute_get_session_snapshot ---
|
|
export const getSessionSnapshotInput = z.object({}).describe("No parameters required");
|
|
|
|
export const getSessionSnapshotOutput = z.object({
|
|
sessionStart: z.string(),
|
|
duration: z.string(),
|
|
requestCount: z.number(),
|
|
costTotal: z.number(),
|
|
tokenCount: z.object({
|
|
prompt: z.number(),
|
|
completion: z.number(),
|
|
}),
|
|
topModels: z.array(
|
|
z.object({
|
|
model: z.string(),
|
|
count: z.number(),
|
|
})
|
|
),
|
|
topProviders: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
count: z.number(),
|
|
})
|
|
),
|
|
errors: z.number(),
|
|
fallbacks: z.number(),
|
|
budgetGuard: z
|
|
.object({
|
|
active: z.boolean(),
|
|
remaining: z.number(),
|
|
})
|
|
.nullable(),
|
|
});
|
|
|
|
export const getSessionSnapshotTool: McpToolDefinition<
|
|
typeof getSessionSnapshotInput,
|
|
typeof getSessionSnapshotOutput
|
|
> = {
|
|
name: "omniroute_get_session_snapshot",
|
|
description:
|
|
"Returns a snapshot of the current working session including duration, request count, total cost, top models/providers used, error count, and budget guard status.",
|
|
inputSchema: getSessionSnapshotInput,
|
|
outputSchema: getSessionSnapshotOutput,
|
|
scopes: ["read:usage"],
|
|
auditLevel: "none",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/usage/analytics", "/api/telemetry/summary"],
|
|
};
|
|
|
|
// --- Tool 18: omniroute_sync_pricing ---
|
|
export const syncPricingInput = z.object({
|
|
sources: z
|
|
.array(z.string())
|
|
.optional()
|
|
.describe("External pricing sources to sync from (default: ['litellm'])"),
|
|
dryRun: z
|
|
.boolean()
|
|
.optional()
|
|
.describe("If true, preview sync results without saving to database"),
|
|
});
|
|
|
|
export const syncPricingOutput = z.object({
|
|
success: z.boolean(),
|
|
modelCount: z.number(),
|
|
providerCount: z.number(),
|
|
source: z.string(),
|
|
dryRun: z.boolean(),
|
|
error: z.string().optional(),
|
|
warnings: z.array(z.string()).optional(),
|
|
data: z.record(z.string(), z.record(z.string(), z.unknown())).optional(),
|
|
});
|
|
|
|
export const syncPricingTool: McpToolDefinition<typeof syncPricingInput, typeof syncPricingOutput> =
|
|
{
|
|
name: "omniroute_sync_pricing",
|
|
description:
|
|
"Syncs pricing data from external sources (LiteLLM) into OmniRoute. Synced pricing fills gaps not covered by hardcoded defaults without overwriting user-set prices. Use dryRun=true to preview.",
|
|
inputSchema: syncPricingInput,
|
|
outputSchema: syncPricingOutput,
|
|
scopes: ["pricing:write"],
|
|
auditLevel: "full",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/pricing/sync"],
|
|
};
|
|
|
|
// ============ Tool Registry ============
|
|
|
|
/** All MCP tool definitions, ordered by phase then name */
|
|
export const MCP_TOOLS = [
|
|
// Phase 1: Essential
|
|
getHealthTool,
|
|
listCombosTool,
|
|
getComboMetricsTool,
|
|
switchComboTool,
|
|
checkQuotaTool,
|
|
routeRequestTool,
|
|
costReportTool,
|
|
listModelsCatalogTool,
|
|
// Phase 2: Advanced
|
|
simulateRouteTool,
|
|
setBudgetGuardTool,
|
|
setRoutingStrategyTool,
|
|
setResilienceProfileTool,
|
|
testComboTool,
|
|
getProviderMetricsTool,
|
|
bestComboForTaskTool,
|
|
explainRouteTool,
|
|
getSessionSnapshotTool,
|
|
syncPricingTool,
|
|
] as const;
|
|
|
|
/** Essential tools only (Phase 1) */
|
|
export const MCP_ESSENTIAL_TOOLS = MCP_TOOLS.filter((t) => t.phase === 1);
|
|
|
|
/** Advanced tools only (Phase 2) */
|
|
export const MCP_ADVANCED_TOOLS = MCP_TOOLS.filter((t) => t.phase === 2);
|
|
|
|
/** Map of tool name → tool definition */
|
|
export const MCP_TOOL_MAP = Object.fromEntries(MCP_TOOLS.map((t) => [t.name, t])) as Record<
|
|
string,
|
|
(typeof MCP_TOOLS)[number]
|
|
>;
|