{"version":3,"sources":["../src/bridge/mcp-error.ts","../src/index.ts"],"names":["BRIDGE_VERSION"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * A machine-readable classification for an MCP call that failed, carried across the host/plugin\n * bridge alongside the human-readable message.\n *\n * ## Why this exists\n *\n * The bridge used to reduce every failure to `err.message`, a bare string. A plugin therefore could\n * not tell \"you lack permission for this tool\" from \"the host is unreachable\" without matching on\n * host error prose, and the practical consequence was that plugins swallowed *all* failures into a\n * successful empty result to keep a permission denial from looking like a crash. That turns a\n * transient outage into \"you have no connectors\" - a false statement about the user's data, and one\n * that also suppresses the query layer's retry.\n *\n * A closed vocabulary lets the plugin branch on the one case it wants to tolerate and rethrow the\n * rest.\n *\n * ## Deliberately small, and deliberately not HTTP\n *\n * These are the distinctions a *caller* acts on differently, not a mirror of any status enum. Codes\n * that would prompt the same handling are folded together, because a vocabulary nobody can apply is\n * just a wider surface to get wrong. `unavailable` and `timeout` stay separate only because retry\n * policy differs between them.\n */\nexport type McpErrorCode =\n    /** The caller is not authenticated, or its token expired. Re-authentication may fix it. */\n    | \"unauthorized\"\n    /** Authenticated, but not permitted this tool or resource. Retrying will not help. */\n    | \"forbidden\"\n    /** The tool, resource, or addressed entity does not exist. */\n    | \"not_found\"\n    /** The arguments were rejected. A caller bug, or stale client-side validation. */\n    | \"invalid_request\"\n    /** A concurrency or state conflict - a row version, or a duplicate. */\n    | \"conflict\"\n    /** Throttled. Retry later, with backoff. */\n    | \"rate_limited\"\n    /** The call did not complete in time. Safe to retry only if the operation is idempotent. */\n    | \"timeout\"\n    /** The host or an upstream dependency is down. Retryable. */\n    | \"unavailable\"\n    /** Anything else, including an unclassifiable failure. The default - never a claim. */\n    | \"internal\";\n\n/**\n * Every valid {@link McpErrorCode}. Used to validate a code arriving over the wire: an unknown\n * string is downgraded rather than trusted, so a newer host cannot make an older plugin branch on a\n * code it has never heard of.\n */\nexport const MCP_ERROR_CODES: readonly McpErrorCode[] = [\n    \"unauthorized\",\n    \"forbidden\",\n    \"not_found\",\n    \"invalid_request\",\n    \"conflict\",\n    \"rate_limited\",\n    \"timeout\",\n    \"unavailable\",\n    \"internal\",\n];\n\n/** True when `value` is a code this build understands. */\nexport function isMcpErrorCode(value: unknown): value is McpErrorCode\n{\n    return typeof value === \"string\"\n        && (MCP_ERROR_CODES as readonly string[]).includes(value);\n}\n\n/**\n * Codes a caller can retry without changing anything about the request. Exposed so retry policy is\n * decided once here rather than re-derived, subtly differently, at each call site.\n *\n * `unauthorized` is absent on purpose: a retry only helps once something else has refreshed the\n * token, which is a different action from retrying.\n */\nconst RETRYABLE: readonly McpErrorCode[] = [\"rate_limited\", \"timeout\", \"unavailable\"];\n\n/** True when the failure is worth retrying as-is. */\nexport function isRetryableMcpErrorCode(code: McpErrorCode): boolean\n{\n    return RETRYABLE.includes(code);\n}\n\n/**\n * An MCP call rejected by the host, carrying its {@link McpErrorCode}.\n *\n * `name` stays `\"McpHostError\"` - the string the bridge has always set - so code that matches on the\n * name keeps working. Prefer {@link isMcpToolError}, which survives a name change and works across\n * realm boundaries where `instanceof` does not.\n */\nexport class McpToolError extends Error\n{\n    /** Machine-readable classification. `internal` when the host sent none. */\n    public readonly code: McpErrorCode;\n\n    /**\n     * Marks the instance for {@link isMcpToolError}. A structural marker rather than a prototype\n     * check because the error can be constructed in one bundle and inspected in another, where\n     * `instanceof` compares two different class objects and answers false.\n     */\n    public readonly isMcpToolError = true as const;\n\n    public constructor(message: string, code: McpErrorCode = \"internal\")\n    {\n        super(message);\n        this.name = \"McpHostError\";\n        this.code = code;\n    }\n\n    /** True when this failure is worth retrying unchanged. */\n    public get retryable(): boolean\n    {\n        return isRetryableMcpErrorCode(this.code);\n    }\n}\n\n/** True when `value` is an {@link McpToolError}, including one from another bundle. */\nexport function isMcpToolError(value: unknown): value is McpToolError\n{\n    return value instanceof McpToolError\n        || (value !== null\n            && typeof value === \"object\"\n            && (value as { isMcpToolError?: unknown }).isMcpToolError === true\n            && isMcpErrorCode((value as { code?: unknown }).code));\n}\n\n/**\n * Maps an HTTP status onto a code. Split out because host MCP clients are commonly HTTP clients, so\n * a status is the classification most of them already have.\n *\n * Unrecognised statuses (including every 2xx and 3xx, which should not be reaching an error path)\n * yield `internal` rather than a guess.\n */\nexport function mcpErrorCodeFromHttpStatus(status: number): McpErrorCode\n{\n    switch (status)\n    {\n        case 401:\n            return \"unauthorized\";\n        case 403:\n            return \"forbidden\";\n        case 404:\n            return \"not_found\";\n        case 400:\n        case 422:\n            return \"invalid_request\";\n        case 409:\n        case 412:\n            return \"conflict\";\n        case 429:\n            return \"rate_limited\";\n        case 408:\n        case 504:\n            return \"timeout\";\n        case 502:\n        case 503:\n            return \"unavailable\";\n        default:\n            // Everything unmapped, 500 included. `internal` is the honest answer for a status this\n            // vocabulary has no distinct handling for - inventing a closer-looking code would tell\n            // the caller something the status does not actually say.\n            return \"internal\";\n    }\n}\n\n/**\n * Derives a code from a host MCP client's RETURNED failure, as opposed to a thrown one.\n *\n * Both paths exist and both have to be covered. A client that throws is handled by\n * {@link classifyHostError}; a client that reports failure as `{ ok: false, error, status }` - the\n * common shape for anything wrapping HTTP - comes through here. Covering only the throw path leaves\n * the majority of real failures arriving as `internal`, which is the same blindness the code was\n * added to remove.\n *\n * Returns `undefined` for a successful response, so a caller can spread it without putting a\n * meaningless code on the happy path.\n */\nexport function classifyHostResponse(response: {\n    readonly ok: boolean;\n    readonly status?: number;\n    readonly mcpErrorCode?: unknown;\n}): McpErrorCode | undefined\n{\n    if (response.ok)\n    {\n        return undefined;\n    }\n\n    // Same precedence as the thrown path: an explicit code from the client beats the transport's\n    // status, and neither is ever inferred from the message.\n    if (isMcpErrorCode(response.mcpErrorCode))\n    {\n        return response.mcpErrorCode;\n    }\n\n    return typeof response.status === \"number\"\n        ? mcpErrorCodeFromHttpStatus(response.status)\n        : \"internal\";\n}\n\n/**\n * Derives a code from an arbitrary thrown value, for the host side of the bridge.\n *\n * Precedence, most explicit first:\n *\n * 1. An `mcpErrorCode` property holding a known code. The intended contract: a host MCP client that\n *    knows why a call failed says so directly.\n * 2. A numeric `status` / `statusCode`, mapped by {@link mcpErrorCodeFromHttpStatus}. Covers the\n *    HTTP clients that already carry one without asking every host to adopt the field above.\n * 3. `name === \"AbortError\"` / `\"TimeoutError\"`, which is how the platform's own aborts surface.\n * 4. `internal`.\n *\n * **Never infers from the message.** Matching prose would make the classification depend on wording\n * nobody treats as a contract, and it would silently reclassify itself the day someone improves an\n * error string. An unclassifiable failure is `internal`, which is honest.\n */\nexport function classifyHostError(err: unknown): McpErrorCode\n{\n    if (err === null || typeof err !== \"object\")\n    {\n        return \"internal\";\n    }\n\n    const candidate = err as {\n        mcpErrorCode?: unknown;\n        status?: unknown;\n        statusCode?: unknown;\n        name?: unknown;\n    };\n\n    if (isMcpErrorCode(candidate.mcpErrorCode))\n    {\n        return candidate.mcpErrorCode;\n    }\n\n    const status = typeof candidate.status === \"number\"\n        ? candidate.status\n        : typeof candidate.statusCode === \"number\" ? candidate.statusCode : undefined;\n\n    if (status !== undefined)\n    {\n        return mcpErrorCodeFromHttpStatus(status);\n    }\n\n    if (candidate.name === \"AbortError\" || candidate.name === \"TimeoutError\")\n    {\n        return \"timeout\";\n    }\n\n    return \"internal\";\n}\n","import { BRIDGE_VERSION } from \"@ethisyscore/protocol\";\n\nexport const EXTENSION_RUNTIME_PACKAGE = \"@ethisyscore/extension-runtime\";\nexport const BRIDGE = BRIDGE_VERSION;\n\n// Re-export the canonical protocol constant under its original name so plugin\n// authors can `import { BRIDGE_VERSION } from \"@ethisyscore/extension-runtime\"`\n// without taking a direct dependency on `@ethisyscore/protocol`. The runtime\n// is the public API surface for plugins; protocol is an internal dep.\nexport { BRIDGE_VERSION };\n\n// MCP failure classification. Exported from the plugin entry point because the plugin is the side\n// that branches on it, and from the host entry point because a host MCP client can attach\n// `mcpErrorCode` to the errors it throws to control what the plugin sees.\nexport {\n    McpToolError,\n    isMcpToolError,\n    isMcpErrorCode,\n    isRetryableMcpErrorCode,\n    mcpErrorCodeFromHttpStatus,\n    classifyHostError,\n    classifyHostResponse,\n    MCP_ERROR_CODES,\n    type McpErrorCode,\n} from \"./bridge/mcp-error\";\n"]}