mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-11 17:52:31 +03:00
refactor(mcp): modularize Radar catalog tool
This commit is contained in:
@@ -1,5 +1,19 @@
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
|
||||
import { logToolCall } from "./audit.ts";
|
||||
import { radarCatalogInput, radarCatalogOutput } from "./schemas/radarCatalog.ts";
|
||||
import type { McpToolExtraLike } from "./scopeEnforcement.ts";
|
||||
import type { TextToolResult } from "./toolResult.ts";
|
||||
import { sanitizeErrorMessage } from "../utils/error.ts";
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
type ScopeEnforcer = (
|
||||
toolName: string,
|
||||
handler: (args: unknown, extra?: McpToolExtraLike) => Promise<TextToolResult>,
|
||||
toolScopes?: readonly string[]
|
||||
) => (args: unknown, extra?: McpToolExtraLike) => Promise<TextToolResult>;
|
||||
|
||||
export interface McpRadarCatalogArgs {
|
||||
provider?: string;
|
||||
familyId?: string;
|
||||
@@ -115,3 +129,42 @@ export async function getMcpRadarCatalog(
|
||||
|
||||
return { meta: normalizeMeta(raw.meta), models };
|
||||
}
|
||||
|
||||
async function handleRadarCatalog(args: {
|
||||
provider?: string;
|
||||
familyId?: string;
|
||||
enabledOnly: boolean;
|
||||
}): Promise<TextToolResult> {
|
||||
const start = Date.now();
|
||||
try {
|
||||
const result = radarCatalogOutput.parse(await getMcpRadarCatalog(args));
|
||||
await logToolCall(
|
||||
"omniroute_radar_catalog",
|
||||
args,
|
||||
{ modelCount: result.models.length },
|
||||
Date.now() - start,
|
||||
true
|
||||
);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
} catch (error) {
|
||||
const message = sanitizeErrorMessage(error) || "Failed to read Radar catalog";
|
||||
await logToolCall("omniroute_radar_catalog", args, null, Date.now() - start, false, message);
|
||||
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
||||
}
|
||||
}
|
||||
|
||||
export function registerRadarCatalogTool(
|
||||
server: McpServer,
|
||||
withScopeEnforcement: ScopeEnforcer
|
||||
): void {
|
||||
server.registerTool(
|
||||
"omniroute_radar_catalog",
|
||||
{
|
||||
description: "Reads the local signed Radar catalog with optional provider and family filters",
|
||||
inputSchema: radarCatalogInput,
|
||||
},
|
||||
withScopeEnforcement("omniroute_radar_catalog", (args) =>
|
||||
handleRadarCatalog(radarCatalogInput.parse(args))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,9 +35,6 @@ export {
|
||||
listModelsCatalogInput,
|
||||
listModelsCatalogOutput,
|
||||
listModelsCatalogTool,
|
||||
radarCatalogInput,
|
||||
radarCatalogOutput,
|
||||
radarCatalogTool,
|
||||
// Phase 2: Advanced tool schemas
|
||||
simulateRouteInput,
|
||||
simulateRouteOutput,
|
||||
@@ -94,6 +91,8 @@ export {
|
||||
ccrStatsTool,
|
||||
} from "./tools.ts";
|
||||
|
||||
export { radarCatalogInput, radarCatalogOutput, radarCatalogTool } from "./radarCatalog.ts";
|
||||
|
||||
// A2A schemas
|
||||
export {
|
||||
AgentCardSchema,
|
||||
|
||||
65
open-sse/mcp-server/schemas/radarCatalog.ts
Normal file
65
open-sse/mcp-server/schemas/radarCatalog.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type { McpToolDefinition } from "./toolDefinition.ts";
|
||||
|
||||
export const radarCatalogInput = z.object({
|
||||
provider: z.string().trim().min(1).max(100).optional().describe("Filter by provider id"),
|
||||
familyId: z.string().trim().min(1).max(120).optional().describe("Filter by curated family id"),
|
||||
enabledOnly: z.boolean().default(true).describe("Exclude models disabled by the Radar feed"),
|
||||
});
|
||||
|
||||
const radarLimitOutput = z.object({
|
||||
rpm: z.number().nullable(),
|
||||
rpd: z.number().nullable(),
|
||||
tpm: z.number().nullable(),
|
||||
tpd: z.number().nullable(),
|
||||
});
|
||||
|
||||
export const radarCatalogOutput = z.object({
|
||||
meta: z
|
||||
.object({
|
||||
version: z.string(),
|
||||
tier: z.string(),
|
||||
fetchedAt: z.string(),
|
||||
})
|
||||
.nullable(),
|
||||
models: z.array(
|
||||
z.object({
|
||||
provider: z.string(),
|
||||
modelId: z.string(),
|
||||
displayName: z.string(),
|
||||
familyId: z.string().nullable(),
|
||||
quota: z.object({
|
||||
monthlyTokens: z.number(),
|
||||
creditTokens: z.number(),
|
||||
freeType: z.string(),
|
||||
limits: radarLimitOutput.nullable(),
|
||||
}),
|
||||
capabilities: z
|
||||
.object({
|
||||
tools: z.boolean(),
|
||||
vision: z.boolean(),
|
||||
thinking: z.boolean(),
|
||||
})
|
||||
.nullable(),
|
||||
enabled: z.boolean(),
|
||||
origin: z.enum(["baseline", "radar", "local"]),
|
||||
disabledBy: z.literal("radar").nullable(),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export const radarCatalogTool: McpToolDefinition<
|
||||
typeof radarCatalogInput,
|
||||
typeof radarCatalogOutput
|
||||
> = {
|
||||
name: "omniroute_radar_catalog",
|
||||
description:
|
||||
"Reads the local signed Radar catalog with optional provider and curated-family filters. Never syncs or writes data.",
|
||||
inputSchema: radarCatalogInput,
|
||||
outputSchema: radarCatalogOutput,
|
||||
scopes: ["read:radar"],
|
||||
auditLevel: "none",
|
||||
phase: 1,
|
||||
sourceEndpoints: ["/api/radar/catalog"],
|
||||
};
|
||||
@@ -13,11 +13,11 @@ import { z } from "zod";
|
||||
import { toolSearchTool } from "./toolSearch.ts";
|
||||
import { pickFastestModelTool } from "./pickFastestModel.ts";
|
||||
import { CCR_MCP_TOOLS } from "./ccrTools.ts";
|
||||
import { radarCatalogTool } from "./radarCatalog.ts";
|
||||
import {
|
||||
AUTO_ROUTING_STRATEGY_VALUES,
|
||||
ROUTING_STRATEGY_VALUES,
|
||||
} from "../../../src/shared/constants/routingStrategies.ts";
|
||||
|
||||
// ============ Shared Types ============
|
||||
// AuditLevel + McpToolDefinition live in the leaf ./toolDefinition.ts so that
|
||||
// toolSearch.ts can import the type without forming a tools.ts ↔ toolSearch.ts cycle.
|
||||
@@ -26,7 +26,6 @@ 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 ============
|
||||
|
||||
// --- Tool 1: omniroute_get_health ---
|
||||
@@ -432,69 +431,6 @@ export const listModelsCatalogTool: McpToolDefinition<
|
||||
sourceEndpoints: ["/api/models/catalog", "/v1/models"],
|
||||
};
|
||||
|
||||
// --- Tool 9: omniroute_radar_catalog ---
|
||||
export const radarCatalogInput = z.object({
|
||||
provider: z.string().trim().min(1).max(100).optional().describe("Filter by provider id"),
|
||||
familyId: z.string().trim().min(1).max(120).optional().describe("Filter by curated family id"),
|
||||
enabledOnly: z.boolean().default(true).describe("Exclude models disabled by the Radar feed"),
|
||||
});
|
||||
|
||||
const radarLimitOutput = z.object({
|
||||
rpm: z.number().nullable(),
|
||||
rpd: z.number().nullable(),
|
||||
tpm: z.number().nullable(),
|
||||
tpd: z.number().nullable(),
|
||||
});
|
||||
|
||||
export const radarCatalogOutput = z.object({
|
||||
meta: z
|
||||
.object({
|
||||
version: z.string(),
|
||||
tier: z.string(),
|
||||
fetchedAt: z.string(),
|
||||
})
|
||||
.nullable(),
|
||||
models: z.array(
|
||||
z.object({
|
||||
provider: z.string(),
|
||||
modelId: z.string(),
|
||||
displayName: z.string(),
|
||||
familyId: z.string().nullable(),
|
||||
quota: z.object({
|
||||
monthlyTokens: z.number(),
|
||||
creditTokens: z.number(),
|
||||
freeType: z.string(),
|
||||
limits: radarLimitOutput.nullable(),
|
||||
}),
|
||||
capabilities: z
|
||||
.object({
|
||||
tools: z.boolean(),
|
||||
vision: z.boolean(),
|
||||
thinking: z.boolean(),
|
||||
})
|
||||
.nullable(),
|
||||
enabled: z.boolean(),
|
||||
origin: z.enum(["baseline", "radar", "local"]),
|
||||
disabledBy: z.literal("radar").nullable(),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export const radarCatalogTool: McpToolDefinition<
|
||||
typeof radarCatalogInput,
|
||||
typeof radarCatalogOutput
|
||||
> = {
|
||||
name: "omniroute_radar_catalog",
|
||||
description:
|
||||
"Reads the local signed Radar catalog with optional provider and curated-family filters. Never syncs or writes data.",
|
||||
inputSchema: radarCatalogInput,
|
||||
outputSchema: radarCatalogOutput,
|
||||
scopes: ["read:radar"],
|
||||
auditLevel: "none",
|
||||
phase: 1,
|
||||
sourceEndpoints: ["/api/radar/catalog"],
|
||||
};
|
||||
|
||||
// --- Tool 10: omniroute_web_search ---
|
||||
export const webSearchInput = z.object({
|
||||
query: z
|
||||
|
||||
@@ -17,8 +17,6 @@ import {
|
||||
routeRequestInput,
|
||||
costReportInput,
|
||||
listModelsCatalogInput,
|
||||
radarCatalogInput,
|
||||
radarCatalogOutput,
|
||||
webSearchInput,
|
||||
webFetchInput,
|
||||
simulateRouteInput,
|
||||
@@ -95,9 +93,9 @@ import { normalizeQuotaResponse } from "../../src/shared/contracts/quota.ts";
|
||||
import { resolveOmniRouteBaseUrl } from "../../src/shared/utils/resolveOmniRouteBaseUrl.ts";
|
||||
import { sanitizeErrorMessage } from "../utils/error.ts";
|
||||
import { getMcpModelsCatalog } from "./catalog.ts";
|
||||
import { getMcpRadarCatalog } from "./radarCatalog.ts";
|
||||
import { registerRadarCatalogTool } from "./radarCatalog.ts";
|
||||
import type { TextToolResult } from "./toolResult.ts";
|
||||
export { getMcpModelsCatalog } from "./catalog.ts";
|
||||
export { getMcpRadarCatalog } from "./radarCatalog.ts";
|
||||
|
||||
const OMNIROUTE_BASE_URL = resolveOmniRouteBaseUrl();
|
||||
const MCP_ENFORCE_SCOPES = process.env.OMNIROUTE_MCP_ENFORCE_SCOPES === "true";
|
||||
@@ -150,11 +148,6 @@ function readMcpAccessibilityConfig(): McpAccessibilityConfig {
|
||||
}
|
||||
}
|
||||
|
||||
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) : {};
|
||||
}
|
||||
@@ -574,29 +567,6 @@ async function handleListModelsCatalog(args: { provider?: string; capability?: s
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRadarCatalog(args: {
|
||||
provider?: string;
|
||||
familyId?: string;
|
||||
enabledOnly: boolean;
|
||||
}) {
|
||||
const start = Date.now();
|
||||
try {
|
||||
const result = radarCatalogOutput.parse(await getMcpRadarCatalog(args));
|
||||
await logToolCall(
|
||||
"omniroute_radar_catalog",
|
||||
args,
|
||||
{ modelCount: result.models.length },
|
||||
Date.now() - start,
|
||||
true
|
||||
);
|
||||
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
||||
} catch (error) {
|
||||
const message = sanitizeErrorMessage(error) || "Failed to read Radar catalog";
|
||||
await logToolCall("omniroute_radar_catalog", args, null, Date.now() - start, false, message);
|
||||
return { content: [{ type: "text" as const, text: `Error: ${message}` }], isError: true };
|
||||
}
|
||||
}
|
||||
|
||||
async function handleWebSearch(args: {
|
||||
query: string;
|
||||
max_results?: number;
|
||||
@@ -842,16 +812,7 @@ export function createMcpServer(): McpServer {
|
||||
)
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"omniroute_radar_catalog",
|
||||
{
|
||||
description: "Reads the local signed Radar catalog with optional provider and family filters",
|
||||
inputSchema: radarCatalogInput,
|
||||
},
|
||||
withScopeEnforcement("omniroute_radar_catalog", (args) =>
|
||||
handleRadarCatalog(radarCatalogInput.parse(args))
|
||||
)
|
||||
);
|
||||
registerRadarCatalogTool(server, withScopeEnforcement);
|
||||
|
||||
server.registerTool(
|
||||
"omniroute_simulate_route",
|
||||
|
||||
4
open-sse/mcp-server/toolResult.ts
Normal file
4
open-sse/mcp-server/toolResult.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export type TextToolResult = {
|
||||
content: Array<{ type: "text"; text: string }>;
|
||||
isError?: boolean;
|
||||
};
|
||||
Reference in New Issue
Block a user