Skip to main content

api-errors

McpError constructor, JsonRpcErrorCode reference, and error handling patterns for `@cyanheads/mcp-ts-core`. Use when looking up error codes, understanding where errors should be thrown vs. caught, or using ErrorHandler.tryCatch in services.

설치로 이동

소스 정보

저장소
cyanheads/secedgar-mcp-server
최근 소스 활동
2026년 9월 9일 22:29
감지된 SKILL.md 언어
영어
스타
10
포크
4

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
api-errors
description
McpError constructor, JsonRpcErrorCode reference, and error handling patterns for `@cyanheads/mcp-ts-core`. Use when looking up error codes, understanding where errors should be thrown vs. caught, or using ErrorHandler.tryCatch in services.
metadata
{"author":"cyanheads","version":"1.9","audience":"external","type":"reference"}
## Overview Error handling in `@cyanheads/mcp-ts-core` follows a strict layered pattern: tool and resource handlers throw `McpError` freely (no try/catch), the handler factory catches and normalizes all errors, and services use `ErrorHandler.tryCatch` for structured logging and wrapping. **Imports:** ```ts import { notFound, validationError, McpError, JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors'; import { ErrorHandler } from '@cyanheads/mcp-ts-core/utils'; ``` --- ## Type-Driven Error Contract (recommended) The recommended path for new tools and resources. Declare failure modes as a const tuple under `errors`; the reason union flows into the handler's `ctx.fail` and TypeScript enforces that you can only fail with a declared reason: ```ts import { tool, z } from '@cyanheads/mcp-ts-core'; import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors'; export const fetchTool = tool('fetch_articles', { description: 'Fetch articles by PMID', input: z.object({ pmids: z.array(z.string()).describe('PMIDs') }), output: z.object({ articles: z.array(z.unknown()).describe('Articles') }), errors: [ { reason: 'no_match', code: JsonRpcErrorCode.NotFound, when: 'No requested PMID returned data', recovery: 'Try pubmed_search_articles to discover valid PMIDs first.' }, { reason: 'queue_full', code: JsonRpcErrorCode.RateLimited, when: 'Local request queue is at capacity', retryable: true, recovery: 'Wait 30 seconds and retry, or reduce batch size.' }, { reason: 'ncbi_down', code: JsonRpcErrorCode.ServiceUnavailable, when: 'NCBI E-utilities unreachable after retries', retryable: true, recovery: 'NCBI is degraded; retry in a few minutes.' }, ], async handler(input, ctx) { const articles = await ncbi.fetch(input.pmids); if (articles.length === 0) { throw ctx.fail('no_match', `None of ${input.pmids.length} PMIDs returned data`); } // ctx.fail('typo') ← TypeScript error: 'typo' isn't in the contract return { articles }; }, }); ``` **What you get:** | Surface | Behavior | |:--------|:---------| | Compile time | `ctx.fail('typo')` is a TS error. Auto-completes declared reasons. | | Runtime | `ctx.fail(reason, msg?, data?, options?)` builds an `McpError(contract.code, msg, { ...data, reason }, options)` — `data.reason` is auto-populated from the contract and cannot be overridden by caller-supplied data (spread first, then `reason` written last), so observers see a stable identifier. `options` accepts `{ cause }` for ES2022 error chaining. | | Lint (devcheck) | Each `code` validated against `JsonRpcErrorCode`. Reasons validated as snake_case + unique within contract. `recovery` validated as non-empty and ≥ 5 words. Build-time only — not invoked at server startup. | | Lint (conformance) | If the handler `throw new McpError(JsonRpcErrorCode.X)` outside `ctx.fail`, conformance check warns when X isn't declared. | > **`recovery` is opt-in resolution, not auto-population.** The contract `recovery` is required metadata documenting the agent's next move when this failure mode fires (a forcing function for thoughtful guidance — placeholders like "Try again." get flagged by the linter). It does **not** automatically appear in runtime `data.recovery.hint` — the framework never injects it without an explicit signal at the throw site. Authors opt in by spreading `ctx.recoveryFor('reason')` into the `data` argument, the same way `ctx.fail('reason')` opts into resolving the contract `code`. What the author types at the throw site is what flows to the wire, with no hidden transformation; the resolver is just a typed lookup keyed by the same `reason` the author already typed. #### `ctx.recoveryFor` — opt-in contract resolution `ctx.recoveryFor(reason)` returns `{ recovery: { hint: <contract.recovery> } }` for a declared reason, ready to spread into `data`. Always available on `Context` (returns `{}` when no contract is attached or the reason is unknown — spread-safe with no optional chaining). On `HandlerContext<R>` it tightens to a typed signature constrained to the declared reason union. ```ts export const calculateTool = tool('calculate', { // ... errors: [ { reason: 'empty_expression', code: JsonRpcErrorCode.ValidationError, when: 'Expression is empty or whitespace-only.', recovery: 'Provide a non-empty mathematical expression to evaluate.' }, ], handler(input, ctx) { if (!input.expression.trim()) { // Static recovery — resolve from the contract. throw ctx.fail('empty_expression', undefined, { ...ctx.recoveryFor('empty_expression') }); } // ... }, }); ``` Same pattern works inside services that accept `ctx`: ```ts export class MathService { parse(expr: string, ctx: Context) { try { return mathjs.parse(expr); } catch (err) { throw validationError(`Parse failed: ${err.message}`, { reason: 'parse_failed', ...ctx.recoveryFor('parse_failed'), // {} if calling tool has no matching reason }); } } } ``` The contract is the single source of truth — write the recovery once, lint validates ≥5 words, the resolver carries it to every throw site that opts in. For runtime-context recovery (interpolating input values, attempted IDs, queue state), override at the throw site: ```ts throw ctx.fail('no_match', `No item ${id}`, { recovery: { hint: `No item ${id}; try IDs 1-100 instead.` }, }); ``` `ctx.recoveryFor` is the first member of a planned **family of opt-in resolution helpers**. Future contract-bound fields (`troubleshootingFor`, `userMessageFor`, …) follow the same shape: single-purpose, spreadable wire-shape, `{}` fallback when not applicable. **Skip the contract** for one-off internal tools or quick prototypes — `ctx` is plain `Context` (no `fail`) and you throw via [factories](#error-factories-fallback) directly. Behavior is identical at the wire; the contract just adds compile-time safety. > **Declare contracts inline on each tool, even when similar across tools.** The contract is part of the tool's documented public surface — reading one tool definition file should give the full picture (input, output, errors, handler, format). Don't extract a shared `errors[]` constant or contract module to deduplicate near-identical entries; per-tool repetition is the intended cost of locality, and dynamic `recovery` hints often need tool-specific runtime context anyway. If a code-cleanup pass suggests consolidating contracts, decline — the duplication is load-bearing for tool-def readability. > **Limits of the conformance lint.** The conformance and prefer-fail rules scan the handler's source text for `throw` statements. Errors thrown from called services (e.g. `await myService.fetch()` raising `RateLimited` internally) are invisible — the lint only sees what's lexically in the handler. Treat the contract as the *advertised* failure surface; bubbled-up codes still reach the client correctly via the auto-classifier, just without lint enforcement. ### Carrying contract `reason` from services Services don't receive `ctx` automatically (unlike handlers), so they can't call `ctx.fail` directly — though `ctx` can be passed as a parameter when needed. To make a service-thrown failure carry the contract's `reason` on the wire, **pass `data: { reason: 'X' }` to the factory**. The framework's auto-classifier preserves `data` unchanged, so clients see the same `error.data.reason` they'd see from `ctx.fail`: ```ts // my-service.ts throw validationError('Expression cannot be empty.', { reason: 'empty_expression' }); throw serviceUnavailable('Upstream timeout', { reason: 'evaluation_timeout' }); ``` ```ts // my-tool.tool.ts errors: [ { reason: 'empty_expression', code: JsonRpcErrorCode.ValidationError, when: 'Input is empty.', recovery: 'Provide a non-empty expression to evaluate.' }, { reason: 'evaluation_timeout', code: JsonRpcErrorCode.ServiceUnavailable, when: 'Upstream exceeded the configured timeout.', recovery: 'Simplify the expression or retry the request after a brief delay.' }, ] ``` The handler doesn't catch and re-throw — letting service errors bubble unchanged keeps "logic throws, framework catches" intact. The wire payload still carries `code` + `data.reason`, and clients can switch on reason without parsing message text. What's lost is lint-time enforcement that every reason is reachable; compensate with one wire-shape test per reason. To carry the contract `recovery` from a service throw, accept `ctx` and spread the resolver: ```ts throw validationError(message, { reason: 'parse_failed', ...ctx.recoveryFor('parse_failed'), // {} when calling tool has no matching reason }); ``` `ctx.recoveryFor` is always present on `Context` (no-op when no contract), so services don't need to know which tool called them — the spread is safe either way. --- ## When not to throw Throw when the server has authoritative classification — auth failure, rate limit, schema violation, upstream 5xx, missing required input. Don't throw when "this looks wrong" depends on intent the server can't see. For mutators, surface raw pre- and post-mutation observable state in the response and let the agent decide whether it matches intent — the server can detect that the file shrunk, but only the agent knows whether it was supposed to. Tell: defensive code justified as a free rider on other work — audit it standalone, and it usually doesn't earn its keep. --- ## Error Factories (fallback) Use when no contract entry fits — ad-hoc throws, tools without a contract, or service-layer code. Shorter than `new McpError(...)` and self-documenting. All return `McpError` instances and accept an optional `options` parameter for error chaining via `{ cause }`. ```ts throw notFound('Item not found', { itemId: '123' }); throw validationError('Missing required field: name', { field: 'name' }); throw unauthorized('Token expired'); // With cause for error chaining throw serviceUnavailable('API call failed', { url }, { cause: error }); ``` **Available factories:** | Factory | Code | |:--------|:-----| | `invalidParams(msg, data?, options?)` | InvalidParams (-32602) | | `invalidRequest(msg, data?, options?)` | InvalidRequest (-32600) | | `notFound(msg, data?, options?)` | NotFound (-32001) | | `forbidden(msg, data?, options?)` | Forbidden (-32005) | | `unauthorized(msg, data?, options?)` | Unauthorized (-32006) | | `validationError(msg, data?, options?)` | ValidationError (-32007) | | `conflict(msg, data?, options?)` | Conflict (-32002) | | `rateLimited(msg, data?, options?)` | RateLimited (-32003) | | `timeout(msg, data?, options?)` | Timeout (-32004) | | `serviceUnavailable(msg, data?, options?)` | ServiceUnavailable (-32000) | | `configurationError(msg, data?, options?)` | ConfigurationError (-32008) | | `internalError(msg, data?, options?)` | InternalError (-32603) | | `serializationError(msg, data?, options?)` | SerializationError (-32070) — JSON/XML/parser failures | | `databaseError(msg, data?, options?)` | DatabaseError (-32010) | | `requestCancelled(msg, data?, options?)` | RequestCancelled (-32011) — caller went away | `options` is `{ cause?: unknown }` — the standard ES2022 `ErrorOptions` type. --- ## McpError Constructor For codes not covered by factories (rare — `MethodNotFound`, `ParseError`, `InitializationFailed`, `UnknownError`): ```ts throw new McpError(code, message?, data?, options?) ``` - `code` — a `JsonRpcErrorCode` enum value - `message` — optional human-readable description of the failure - `data` — optional structured context (plain object) - `options` — optional `{ cause?: unknown }` for error chaining **Example:** ```ts import { McpError, JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors'; throw new McpError(JsonRpcErrorCode.DatabaseError, 'Connection pool exhausted', { pool: 'primary', }); ``` --- ## Error Codes **Standard JSON-RPC 2.0 codes:** | Code | Value | When to Use | |:-----|------:|:------------| | `ParseError` | -32700 | Malformed JSON received | | `InvalidRequest` | -32600 | Unsupported operation, missing client capability | | `MethodNotFound` | -32601 | Requested method does not exist | | `InvalidParams` | -32602 | Bad input, missing required fields, schema validation failure | | `InternalError` | -32603 | Unexpected failure, catch-all for programmer errors | **Implementation-defined codes (-32000 to -32099):** | Code | Value | When to Use | |:-----|------:|:------------| | `ServiceUnavailable` | -32000 | External dependency down, upstream failure | | `NotFound` | -32001 | Resource, entity, or record doesn't exist | | `Conflict` | -32002 | Duplicate key, version mismatch, concurrent modification | | `RateLimited` | -32003 | Rate limit exceeded | | `Timeout` | -32004 | Operation exceeded time limit | | `Forbidden` | -32005 | Authenticated but insufficient scopes/permissions | | `Unauthorized` | -32006 | No auth, invalid token, expired credentials | | `ValidationError` | -32007 | Business rule violation (not schema — use `InvalidParams` for that) | | `ConfigurationError` | -32008 | Missing env var, invalid config | | `InitializationFailed` | -32009 | Server/component startup failure | | `DatabaseError` | -32010 | Storage/persistence layer failure | | `RequestCancelled` | -32011 | Caller abandoned the request — client disconnect, external abort signal. Framework-raised; never retried, logged at `info` | | `SerializationError` | -32070 | Data serialization/deserialization failed | | `UnknownError` | -32099 | Generic fallback when no other code fits | --- ## Auto-Classification When a handler throws a plain `Error` (or any non-`McpError` value), the framework classifies it to the most specific `JsonRpcErrorCode` automatically. This matters when you don't control what a third-party library throws and can't predict its error type. Use factories or `McpError` directly when the code must be exact — auto-classification is best-effort pattern matching and not guaranteed for ambiguous messages. For errors from your own code where the code matters, be explicit. ### Resolution Order The framework applies these steps in order — first match wins:
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기