@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
127 lines • 5.02 kB
JavaScript
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved.
const NON_EXTRA_KEYS = new Set([
"success",
"error",
"code",
"message",
"details",
]);
function siblingFields(body) {
const extra = {};
for (const [key, value] of Object.entries(body)) {
if (!NON_EXTRA_KEYS.has(key))
extra[key] = value;
}
return Object.keys(extra).length > 0 ? { extra } : {};
}
/** Prefixes the SDK uses before the response body. Each captures the status. */
const PREFIXES = [
/^API request failed:\s*(\d{3})\s*/i,
/^Authentication failed:\s*(\d{3})\s*/i,
/^API\s+(\d{3})\s+[a-z ]+:\s*/i,
];
function asRecord(value) {
return value && typeof value === "object" && !Array.isArray(value)
? value
: null;
}
function asString(value) {
return typeof value === "string" ? value : undefined;
}
/**
* Recover `{status, code, message, details}` from an SDK error (or a raw
* message string). Never throws.
*/
export function parseApiErrorBody(err) {
const raw = typeof err === "string"
? err
: err instanceof Error
? err.message
: undefined;
if (!raw)
return {};
let status;
let rest = raw;
for (const prefix of PREFIXES) {
const m = rest.match(prefix);
if (m) {
status = Number(m[1]);
rest = rest.slice(m[0].length);
break;
}
}
// Widest span from the first `{` to the last `}`. Deliberately not a regex
// and deliberately not the FIRST balanced object: a route body is a single
// JSON object, so the widest span IS that object, and stopping at the first
// nested `}` would truncate every nested `details`/`error` body.
const jsonStart = rest.indexOf("{");
if (jsonStart === -1)
return status !== undefined ? { status } : {};
const jsonText = rest.slice(jsonStart, rest.lastIndexOf("}") + 1);
let parsed;
try {
parsed = JSON.parse(jsonText);
}
catch {
// ⚠️ A TRUNCATED body must not silently lose the code. The SDK caps a 404
// body at `errorText.slice(0, 200)` (mcp-sdk `EntityNotFoundError`), and
// the real RECORDING_NOT_FOUND body measures 191 characters — a nine-
// character margin. One more key in `details`, or a longer id, pushes it
// past the cap, `JSON.parse` throws, and every caller silently falls back
// to its generic branch, reverting the fix this module exists for. The
// code is the one field worth recovering from a mangled body, and it is
// always well-formed even when the object is not.
// A typed-error body carries its code as `"error"` rather than `"code"`.
const salvaged = jsonText.match(/"code"\s*:\s*"([A-Z][A-Z0-9_]*)"/) ??
jsonText.match(/"error"\s*:\s*"([A-Z][A-Z0-9_]*)"/);
return {
...(status !== undefined ? { status } : {}),
...(salvaged ? { code: salvaged[1] } : {}),
};
}
const body = asRecord(parsed);
if (!body)
return status !== undefined ? { status } : {};
// Nested shape: `error` is itself an object carrying the code.
const nested = asRecord(body.error);
if (nested) {
return {
...(status !== undefined ? { status } : {}),
...(asString(nested.code) ? { code: asString(nested.code) } : {}),
...(asString(nested.message)
? { message: asString(nested.message) }
: {}),
...(nested.details !== undefined ? { details: nested.details } : {}),
...siblingFields(body),
};
}
// Typed-error shape: `typedErrorResponse` (lib/api/typed-error-response.ts,
// called by both `handleRouteError` and `withDualAuth`) answers every
// MCPRecoverableError as `{error: <CODE>, message: <prose>}` — the
// code IS `error` and there is no `code` key. Read by the flat branch below,
// the code came back as the message and `code` as undefined, so no caller
// could branch on it.
const flatError = asString(body.error);
const flatMessage = asString(body.message);
if (body.code === undefined &&
flatError !== undefined &&
/^[A-Z][A-Z0-9_]*$/.test(flatError) &&
flatMessage !== undefined) {
return {
...(status !== undefined ? { status } : {}),
code: flatError,
message: flatMessage,
...(body.details !== undefined ? { details: body.details } : {}),
...siblingFields(body),
};
}
// Flat shape: `code` sits beside a string `error`.
return {
...(status !== undefined ? { status } : {}),
...(asString(body.code) ? { code: asString(body.code) } : {}),
...(asString(body.error) ? { message: asString(body.error) } : {}),
...(body.details !== undefined ? { details: body.details } : {}),
...siblingFields(body),
};
}
//# sourceMappingURL=api-error-body.js.map