UNPKG

@withpica/mcp-server

Version:

MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog

68 lines • 2.99 kB
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved. /** * Refuse unknown `sections` names on any tool whose schema declares them as a * closed list (ops dfa810f8, 2026-09-28). * * The `*_inspect` tools take `sections: string[]` with an `items.enum`, but * nothing enforced the enum at call time: every executor filters by * `sections.includes(name)`, so `pica_works_inspect { sections: ["work"] }` * matched nothing and answered a successful, empty `{}`. An agent cannot tell * that from "this work has no data", which is the confident-wrong-answer * shape canonical-model.md calls the most expensive output we produce. * * Called once, from `ToolRegistry.executeTool` (`./index.ts`), before any * executor runs — so every tool that declares a `sections` enum, and every * transport (stdio, `/api/mcp`, `pica_execute`), gets the same refusal * without each executor re-checking. A `sections` property without * `items.enum` (e.g. the object-shaped `sections` on the export tools) is left * alone. */ import { typedError } from "./error-envelope.js"; /** The closed list of section names the schema declares, or null if none. */ export function declaredSectionNames(inputSchema) { const props = inputSchema ?.properties; const sections = props?.sections; if (!sections || sections.type !== "array") return null; const names = sections.items?.enum; if (!Array.isArray(names) || names.length === 0) return null; return names.filter((n) => typeof n === "string"); } /** * Returns a VALIDATION_ERROR result naming the unknown section(s) and the * valid ones, or `null` when the call may proceed (no `sections` sent, the * tool declares no closed list, or every name is known). * * A bare string (`sections: "basic"`) is checked as a one-element list rather * than coerced, so a typo there is refused too; other non-array shapes are * left to the executor. */ export function rejectUnknownSections(toolName, inputSchema, args) { const requested = args?.sections; if (requested === undefined || requested === null) return null; const valid = declaredSectionNames(inputSchema); if (!valid) return null; const names = Array.isArray(requested) ? requested : typeof requested === "string" ? [requested] : null; if (!names) return null; const validSet = new Set(valid); const invalid = names.filter((n) => typeof n !== "string" || !validSet.has(n)); if (invalid.length === 0) return null; const shown = invalid.map((n) => JSON.stringify(n)).join(", "); return typedError("VALIDATION_ERROR", `${toolName} has no section${invalid.length === 1 ? "" : "s"} ${shown}. ` + `Valid sections: ${valid.join(", ")}. Omit sections to get the default set.`, { field: "sections", invalid_sections: invalid, valid_sections: valid, }); } //# sourceMappingURL=section-validation.js.map