refactor(mcp): modularize Radar catalog tool

This commit is contained in:
diegosouzapw
2026-08-08 23:12:00 -03:00
parent 648e81416b
commit 2ffee220bf
6 changed files with 128 additions and 110 deletions

View File

@@ -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))
)
);
}

View File

@@ -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,

View 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"],
};

View File

@@ -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

View File

@@ -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",

View File

@@ -0,0 +1,4 @@
export type TextToolResult = {
content: Array<{ type: "text"; text: string }>;
isError?: boolean;
};