@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
68 lines • 2.99 kB
JavaScript
// 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