What Does Implementing MCP in TypeScript Involve?

Filed as AFR/MCP-18 · task class Code modification · all MCP topics

Where the type system stops being any help

There is no TypeScript dialect of MCP. A server is a process that speaks the protocol, and writing one in TypeScript changes the engineering around the conversation rather than the conversation itself: which runtime executes the process, how arguments are checked once the type system is gone, how the package is built and started, and how a failure surfaces inside an event loop.

Types are a compile-time artefact and they are gone before any message arrives, so the interesting boundary is the point where an incoming argument object becomes a typed value. A runtime validation library does that work, and usually emits the published schema from the same declaration — so what a caller reads and what the handler enforces cannot drift apart. An operation that is typed but not validated accepts whatever actually arrives.

One consequence of the local channel catches almost everyone once, and it is worth stating plainly because the failure it produces is unreadable. When a host launches a server as a subprocess, the process’s standard output IS the channel the protocol messages travel on. A stray line of diagnostic output written there is not a log line; it is a corrupt message in the middle of a session. Diagnostics belong on the error stream.

Two failure paths have to be decided explicitly, because the language does not decide them the way the protocol expects. An exception raised inside a handler is not an answer to anything: it has to become a result the caller can read, saying that the operation failed and why, kept distinct from the separate case where the request itself was not valid. And a rejection nobody handles does not fail one request — it can end the process, which takes the session down with it and reaches the caller as a connection that stopped existing rather than as anything it could act on.

What the host launches is not what you wrote

A locally launched server is started by a command, and that command runs the built output: an entry point resolving its imports from wherever the package was installed, under whichever runtime the host can find. Source that runs happily under a project’s own tooling is no evidence that the command a host will use resolves to anything at all.

Types shared between a server and a client written in the same language are a convenience for whoever owns both, and nothing more. What crosses a connection is JSON described by a schema, and a caller written elsewhere — or never written against this server at all — sees exactly what the schema says and not one field beyond it. A field that exists only in a shared interface does not exist for anybody else.

A caller cannot tell what answered it

None of this reaches the protocol. The roles, the message contract, the primitives a server may publish and the access it is given are identical whichever language is chosen, which is also why a server can be replaced by one written in another language without any caller noticing.

How the Registry classifies implementing a server in TypeScript

Writing a server and running it against a client is code work — new code, a new process, and a surface another program will depend on — which the code modification class covers. On the order form's task class field, select Code modification, which covers writing, editing, refactoring, and testing code. That class has its own page at /tasks/code.

The Registry files this topic under one canonical task, fixed in advance and published here rather than generated per visit:

Implement an MCP server in TypeScript that exposes a build-status lookup and run it against a client.

That string, hashed once, is c3fc0a82199a694b… — the first sixteen of sixty-four hex characters. An assessment ordered against a task of this kind is computed from the class, the wording submitted, and the permanent chart the Registry derives for the model configuration named on the order: its ascendant, its ruling planet, and the composite harmony that constrains the range of verdicts available to it. The whole derivation is published at /method.

The risk here is the gap between compile time and run time: a value the type system promised, and a payload that arrives over a connection without having been checked by anything. 4 risk factors are tracked for code modification tasks generally; an assessment reports 3 of them, selected by the submitted task's own seed rather than chosen. Their names, severities and descriptions are certificate content and are not published on a free page.

Get Assessment

What this page does not claim

The Registry makes no representation that an assessment predicts the outcome of any task, and no assessment is validated against real-world results.

This page defines a term and states a classification. It publishes no verdict: the Registry does not test implementations, does not measure how often anything works, and has never run the canonical task above against any model. Which runtime executes the process, which validation library declares the schemas, and where the built output lives are decisions the canonical brief leaves open.

What is sold is a permanent, numbered assessment for one submitted task at one model configuration — a verdict on a seven-point scale, a recommended execution window, and, above the first tier, the risk factors the class tracks. EUR 1.90 Standard, EUR 4.90 Extended, and EUR 14.90 Full Chart, which adds the permanent chart for the configuration itself. Assessments from EUR 1.90; machine-readable at /pricing.json.

Related surfaces

← All MCP topics · The AFR-1 method · Code modification task class

Related topics: MCP in Python · Building a server · Tools

Questions about a TypeScript implementation

Does MCP have a TypeScript-specific protocol?
No. The protocol is language-independent, and a caller cannot tell from the other end what a server was written in. TypeScript decides the runtime, the validation and the build — never the message contract.
Why is runtime validation needed if the code is already typed?
Because the types are erased when the code compiles. An argument object arriving over a connection has passed through no check the language performs, so a runtime validator is what stands between the schema a server published and whatever actually turned up.
Why must a locally launched server keep its logs off standard output?
Because standard output is the channel the protocol messages travel on when a host launches the server as a subprocess. A diagnostic line written there arrives as a malformed message. Diagnostics go to the error stream instead.
What happens if a handler throws?
Whatever the code does with it. A raised exception is not an answer to a request: it has to be turned into a result the caller can read, and kept distinct from the case where the request itself was invalid. An unhandled rejection can end the process, which a caller sees as a connection that disappeared.