mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-18 13:14:56 +03:00
* fix(mcp): dynamically generate web search provider enum from registry * test(mcp): add contract test for dynamic web search provider enum Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(mcp): restore search.ts eslint suppression, type builder maps, fix firecrawl searchType arg The enum-dynamic refactor dropped search.ts's no-explicit-any suppression while a new Record<string,any> map re-introduced anys, and the response normalizer map swapped the firecrawl searchType argument with query. Type both maps explicitly, restore the base suppression (33 pre-existing anys), and pass searchType (not query) to normalizeFirecrawlSearchResponse. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com> Co-authored-by: sadSanta-07 <sadSanta-07@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
74 lines
3.4 KiB
TypeScript
74 lines
3.4 KiB
TypeScript
// #10209 — contract test: the MCP `omniroute_web_search` `provider` enum is now
|
|
// generated dynamically from the search registry (`getActiveSearchProviders`).
|
|
// This pins the invariant that the dynamic enum does not silently break the Zod
|
|
// schema exposed to MCP clients (or the tool's scope), regardless of future
|
|
// registry additions/removals or `disabled` flags. If the registry drifts, this
|
|
// test turns red before any client sees a broken/widened contract.
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const { getActiveSearchProviders } = await import("../../open-sse/mcp-server/schemas/providerEnums.ts");
|
|
const { SEARCH_PROVIDERS } = await import("../../open-sse/config/searchRegistry.ts");
|
|
const { webSearchInput, webSearchTool } = await import("../../open-sse/mcp-server/schemas/tools.ts");
|
|
|
|
// The provider set the tool historically exposed to MCP clients (the hardcoded
|
|
// enum this PR replaced). Every one of these MUST keep parsing so existing
|
|
// clients are never broken by the dynamic enum.
|
|
const LEGACY_CONTRACT_PROVIDERS = [
|
|
"serper-search",
|
|
"brave-search",
|
|
"perplexity-search",
|
|
"exa-search",
|
|
"tavily-search",
|
|
"google-pse-search",
|
|
"linkup-search",
|
|
"searchapi-search",
|
|
"searxng-search",
|
|
] as const;
|
|
|
|
function activeRegistryIds(): string[] {
|
|
return Object.values(SEARCH_PROVIDERS)
|
|
.filter((p) => !p.disabled)
|
|
.map((p) => p.id)
|
|
.sort();
|
|
}
|
|
|
|
test("getActiveSearchProviders() is a non-empty tuple of active registry providers", () => {
|
|
const active = getActiveSearchProviders();
|
|
assert.ok(Array.isArray(active), "should return an array (Zod enum tuple)");
|
|
assert.ok(active.length > 0, "dynamic enum must never be empty");
|
|
assert.deepEqual([...active].sort(), activeRegistryIds());
|
|
});
|
|
|
|
test("web_search inputSchema provider enum equals the active provider set", () => {
|
|
const shape = webSearchInput.shape as { provider: { unwrap: () => unknown } };
|
|
const providerManager = shape.provider; // ZodOptional around the ZodEnum
|
|
const unwrapped = providerManager.unwrap() as { options: string[] };
|
|
const options = unwrapped.options;
|
|
assert.ok(Array.isArray(options), "provider field should be an enum");
|
|
assert.ok(options.length > 0, "provider enum should have at least one value");
|
|
assert.deepEqual([...options].sort(), activeRegistryIds());
|
|
});
|
|
|
|
test("dynamic enum is a superset of the legacy contract (existing clients not broken)", () => {
|
|
for (const id of LEGACY_CONTRACT_PROVIDERS) {
|
|
const result = webSearchInput.safeParse({ query: "test", provider: id });
|
|
assert.ok(result.success, `legacy provider "${id}" must still parse`);
|
|
}
|
|
// Every active registry provider must also be selectable by an MCP client.
|
|
for (const id of activeRegistryIds()) {
|
|
const result = webSearchInput.safeParse({ query: "test", provider: id });
|
|
assert.ok(result.success, `active registry provider "${id}" must parse`);
|
|
}
|
|
});
|
|
|
|
test("unknown provider values are rejected (contract stays tight)", () => {
|
|
const result = webSearchInput.safeParse({ query: "test", provider: "no-such-search-provider" });
|
|
assert.equal(result.success, false);
|
|
});
|
|
|
|
test("tool registration + scope are unaffected by the dynamic enum", () => {
|
|
assert.equal(webSearchTool.name, "omniroute_web_search");
|
|
assert.equal(webSearchTool.inputSchema, webSearchInput);
|
|
assert.ok(webSearchTool.scopes.includes("execute:search"), "web search tool scope must be retained");
|
|
}); |