UNPKG

@withpica/mcp-server

Version:

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

127 lines • 5.02 kB
// 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