Errors
Purpose and Scope
Server error handling in the TypeScript SDK starts with one important distinction: a tool failure can still be a successful protocol response, while a protocol failure is a JSON-RPC error response. A tool error is returned to the model as normal tool-call content with an error marker, so the model can read the message, infer what went wrong, and try a better call. A protocol error instead says that the request itself was invalid, unsupported, or impossible to satisfy through that method, and the model does not receive it as tool output.
Sources: docs/servers/errors.md
This distinction is practical rather than cosmetic. If a user asks for a note that does not exist, the best result is often a readable message that lists known identifiers or explains how to recover. If a resource URI has invalid syntax, a prompt request is malformed, or a completion request cannot be interpreted, the server should reject the request at the protocol layer. Keeping these paths separate prevents recoverable application failures from being treated as transport or JSON-RPC failures, while still giving strict callbacks a precise way to report invalid parameters.
Sources: docs/servers/errors.md, docs/servers/completion.md
Relevant Source Files
- docs/servers/errors.md - Defines the primary server error model, including tool errors with isError, thrown exceptions from tool handlers, ProtocolError, ProtocolErrorCode, ResourceNotFoundError, and the note examples used to show client-visible shapes.
- docs/troubleshooting.md - Provides operational failure modes that often look like server errors to a host, including stdio JSON parsing failures, duplicate Zod type recursion, missing Web Crypto, and protocol era negotiation failures.
- docs/servers/completion.md - Shows that completion callbacks are registered for prompt arguments and resource template variables and return suggestion results, which helps explain why completion errors use protocol failure paths rather than tool-result error content.
- docs/servers/elicitation.md - Describes server-to-client elicitation from tool handlers, accepted and declined actions, schema validation for accepted content, and the 2026-07-28 behavior change that redirects readers toward input_required.
- docs/servers/input-required.md - Documents input_required results, capability checks, trusted re-entry patterns, acceptedContent validation, and the missing-capability rejection code used when embedded requests cannot be sent.
- docs/servers/logging-progress-cancellation.md - Shows request-scoped context helpers for progress, logging, and cancellation-adjacent long-running handler behavior, including progress tokens and deprecated MCP logging guidance.
Error Model for Tools
For tool handlers, prefer a tool error when the model can do something useful with the failure. The documented pattern returns ordinary content plus the error marker. In the note example, a missing identifier returns a text part saying that the requested note was absent and includes the known identifiers. The resulting tools call is still an ordinary result, so the client receives content and the model can choose a different argument. Put recovery hints in the text, because that content is what the model has available for planning the next step.
Sources: docs/servers/errors.md
A thrown exception inside a tool handler is also converted by the SDK into the same visible tool-error shape. That makes simple handlers easy to write: a missing record can throw a normal Error and the message becomes the text content of the tool result. Returning the tool error explicitly is still preferable when you need structured wording, multiple content parts, or a carefully designed retry hint. The documentation also notes an important validation consequence: an error result marked this way skips output schema validation, so error payloads do not need to match the success output contract.
Sources: docs/servers/errors.md
Protocol Errors and Typed Error Subclasses
Resource, prompt, and completion callbacks do not have the same tool-result error channel. When the request itself is wrong, throw ProtocolError with an appropriate ProtocolErrorCode, optional message, and optional data. The resource example validates that note identifiers contain lowercase letters and throws an invalid-parameters protocol error when the URI variable breaks that contract. When a syntactically valid resource is simply absent, the documented ResourceNotFoundError helper communicates that specific failure without inventing a tool-style result for a non-tool method.
Sources: docs/servers/errors.md
Completion follows the same boundary. Completion is autocomplete for prompt arguments and resource template variables: the client sends a partial value and the server returns matching suggestions. The docs emphasize that a completable field registers the completion handler and advertises the completions capability automatically, and that result lists are shaped as suggestion values with total and has-more information. Since this method is not a tool call, failures in interpreting the request should be represented as protocol errors, while successful empty matches should be returned as valid completion results rather than error content.
Sources: docs/servers/completion.md, docs/servers/errors.md
Validation, Input Required, and Elicitation Edge Cases
Validation failures can appear at several layers, so handlers should be explicit about which layer owns the problem. Tool input schemas validate the arguments before the handler logic depends on them, while accepted input from later user interactions should still be treated as untrusted. The input_required guide shows acceptedContent reading responses from ctx.mcpReq.inputResponses and optionally applying a Zod schema so the handler receives a validated, typed value on re-entry. If the user declines an embedded elicitation, the example returns a tool error so the model understands that the operation was cancelled by the operator.
Sources: docs/servers/input-required.md
The input_required helper itself has protocol-level constraints. It throws a TypeError when the specification carries neither embedded input requests nor request state, and each embedded request is checked against the capabilities declared by the connected client. A missing capability rejects the call with code minus 32021 before anything reaches the wire. That behavior is useful when diagnosing failures: the operation did not fail because business logic rejected it; it failed because the client could not support the requested mid-call interaction surface.
Sources: docs/servers/input-required.md
Elicitation adds another version-sensitive edge case. In the 2025-era flow, a tool handler can call ctx.mcpReq.elicitInput, the connected client answers through its elicitation create handler, and accepted content is validated against the requested schema before the promise resolves. On a 2026-07-28 connection, the docs state that elicitInput throws and handlers should use input_required instead. Treat that as a migration boundary when you see an apparent server error after upgrading protocol support: the handler may be using a push-style request on a connection that expects return-and-retry semantics.
Sources: docs/servers/elicitation.md, docs/servers/input-required.md
Operational Troubleshooting Signals
Some failures reported by hosts are not application errors at all. On stdio, standard output is the wire protocol, so every line written to stdout must be JSON-RPC. A stray console log from your code or a dependency can produce a host-side JSON parse error such as an unexpected token message. The documented fix is to log to stderr instead, because serveStdio owns stdout while console.error remains safe for process diagnostics. This is especially important when debugging error handlers: a diagnostic print can break the transport before your intended error response is delivered.
Sources: docs/troubleshooting.md
Runtime and dependency issues can also masquerade as server failures. The troubleshooting guide ties excessive TypeScript type instantiation to multiple installed copies of Zod, because the SDK derives tool, prompt, and resource types from Zod version four schemas. It also ties missing crypto to older Node runtimes when OAuth helpers expect Web Crypto on globalThis.crypto. Finally, SdkError ERA_NEGOTIATION_FAILED points at protocol-era negotiation rather than a handler bug: pinning a version the server does not offer, or removing the legacy fallback in automatic mode, prevents connection setup before method calls begin.
Sources: docs/troubleshooting.md
Long-Running Handlers, Progress, and Logging
Errors should not be the only feedback path for long-running work. The request context exposes progress and logging helpers on ctx.mcpReq. A client that wants progress supplies a progress token through request metadata, and the server can send notifications as each unit of work completes. If no progress token is present, the handler should simply skip progress notifications and return the final result. This separation keeps slow but healthy operations from looking like failures, and it gives clients a way to render progress without changing the final success or error shape.
Sources: docs/servers/logging-progress-cancellation.md
Logging has its own compatibility caution. The docs warn that MCP logging is deprecated as of the 2026-07-28 protocol version and recommend stderr for stdio servers or OpenTelemetry for production observability. If a handler logs to the client with ctx.mcpReq.log, the server must have declared the logging capability, and the data may be any JSON value. For new error diagnostics, prefer transport-safe process logging and observability tooling rather than relying on protocol logging as the primary path.
Sources: docs/servers/logging-progress-cancellation.md, docs/troubleshooting.md
Client-Visible Shapes and Next Steps
When designing a server API, decide first whether the caller should retry through the model or whether the request should be rejected by the protocol. For recoverable tool-domain problems, return content with a clear explanation and mark the result as an error. For invalid resource, prompt, or completion requests, throw a typed protocol error. For mid-call user decisions, model accept, decline, and cancel explicitly, validate any returned content, and use input_required on newer protocol connections. Then test the client-visible result shapes so hosts see the intended distinction rather than a generic failure.
Sources: docs/servers/errors.md, docs/servers/input-required.md, docs/servers/elicitation.md
Related pages to read next: Tools for handler result contracts, Resources for resource-not-found behavior, Completion for autocomplete callbacks, Input Required for return-and-retry interactions, Logging Progress and Cancellation for long-running work, Troubleshooting for host-facing failure messages, and Protocol Versions for 2026-07-28 migration boundaries.