Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsSome links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Build an MCP HTTP server in TypeScript by creating an McpServer, registering tools (and, when useful, resources and prompts), attaching a Streamable HTTP transport, and calling await server.connect(transport). For a remote Node deployment, Streamable HTTP is the modern transport; use stateful sessions when you need session IDs and resumability-related behavior, or stateless mode for a simpler API-style service.
What you are building
Model Context Protocol (MCP) separates your server’s capabilities from the wire protocol. Your TypeScript code defines tools, resources and prompts. A transport receives MCP requests and sends responses to a client such as an AI application. The smallest useful server therefore has four parts:
- An MCP SDK package and Zod for input validation.
- An
McpServerwith a name and version. - One or more registered tools, resources or prompts.
- A Streamable HTTP transport connected with
server.connect(transport).
Expose that transport at a stable endpoint such as /mcp. Keep health checks, authentication and your application routes separate from the MCP endpoint.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Pin the SDK generation before writing code
The TypeScript SDK has two documented package lines. The v1 quick start installs @modelcontextprotocol/sdk and zod. The v2 documentation uses split packages such as @modelcontextprotocol/server and related adapters, and describes the 2026-07-28 specification era. Do not mix import paths from different generations. Pin the major version in package.json, then use the matching SDK guide for transport imports.
#1 Best Overall
v1-style installation
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript tsx @types/node
npx tsc --init
The example below follows the v1-style McpServer import and the Node Streamable HTTP transport. If your pinned SDK exposes the transport from a different adapter module, keep the server logic unchanged and update only that import and its constructor options.
A complete stateless Streamable HTTP server
Stateless mode creates no MCP session ID. Each request can be handled independently, which is useful for API-like services and makes horizontal deployment simpler. This example validates a URL, calls a local function, and returns structured text.
import { createServer } from 'node:http';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/node.js';
import { z } from 'zod';
const mcp = new McpServer({
name: 'typescript-http-example',
version: '1.0.0'
});
mcp.tool(
'describe_url',
'Validate a URL and return its origin.',
{ url: z.string().url() },
async ({ url }) => {
const parsed = new URL(url);
return {
content: [
{
type: 'text',
text: JSON.stringify({
href: parsed.href,
origin: parsed.origin,
protocol: parsed.protocol
})
}
]
};
}
);
const transport = new NodeStreamableHTTPServerTransport({
// No sessionIdGenerator means stateless operation.
enableJsonResponse: true
});
await mcp.connect(transport);
const httpServer = createServer(async (req, res) => {
const requestUrl = new URL(req.url ?? '/', 'http://localhost');
if (requestUrl.pathname === '/healthz' && req.method === 'GET') {
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({ ok: true }));
return;
}
if (requestUrl.pathname !== '/mcp') {
res.writeHead(404);
res.end('Not found');
return;
}
try {
await transport.handleRequest(req, res);
} catch (error) {
console.error(error);
if (!res.headersSent) {
res.writeHead(500, { 'content-type': 'application/json' });
res.end(JSON.stringify({ error: 'Internal MCP error' }));
}
}
});
httpServer.listen(Number(process.env.PORT ?? 3000), '127.0.0.1', () => {
console.log('MCP server listening on http://127.0.0.1:3000/mcp');
});
const shutdown = async () => {
httpServer.close();
await transport.close();
await mcp.close();
};
process.once('SIGINT', shutdown);
process.once('SIGTERM', shutdown);
Run it with npx tsx src/server.ts. A client should target http://127.0.0.1:3000/mcp. The enableJsonResponse: true option selects JSON responses instead of requiring an event stream for every response. Remove that option when your client specifically requires streaming behavior.
Stateful sessions, stateless requests and legacy SSE
| Choice | Use it when | Operational consequence |
|---|---|---|
| Streamable HTTP, stateful | A client needs a session ID, resumability-related behavior or conversation-scoped state. | Create a transport with a session ID generator and route subsequent requests to the same session. |
| Streamable HTTP, stateless | Your service behaves like an ordinary authenticated API and each request contains all required context. | Omit the session ID generator; load balancing is simpler because requests do not depend on in-memory session state. |
| HTTP plus SSE | Only when you must support an older client that cannot use Streamable HTTP. | It is a legacy compatibility transport rather than the preferred design for new remote servers. |
| stdio | A local integration launches your server as a child process. | There is no public HTTP endpoint; use the client’s process-spawn configuration. |
Adding a session ID generator
import { randomUUID } from 'node:crypto';
const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: randomUUID,
enableJsonResponse: true
});
await mcp.connect(transport);
That option makes the transport stateful. In a multi-instance deployment, do not assume an arbitrary request can reach any process: use session-aware routing or an external session strategy appropriate for your infrastructure. A single process with in-memory state is suitable for development, while a horizontally scaled service needs an explicit routing design.
Register tools, resources and prompts deliberately
Tools
Give every tool a stable name, a description that explains when an agent should use it, and a Zod schema that rejects malformed input before your business logic runs. Return MCP content blocks rather than unstructured console output. Keep secrets, authorization checks and rate limits inside the handler or a middleware layer; never rely on a tool description as an access-control mechanism.
Rank #2
- TypeScript implements a superset of syntax for strictly typed development, facilitating deep static analysis and enhanced development environment integration. The compiler translates source into standard script formats, ensuring parity across any runtime.
- TypeScript is ideal for front-end developers, full-stack engineers, and software architects who build large-scale web applications. It serves those looking to improve code excellence, reduce bugs through static checking, and maintain complex projects more.
- Lightweight, Classic fit, Double-needle sleeve and bottom hem
Resources
Resources expose discoverable context such as documentation, configuration or records. Use a predictable URI scheme and return only data the authenticated caller is allowed to read. If the data changes frequently, document whether the client should refetch it.
Prompts
Prompts are reusable, named message templates. They are useful when a client needs a consistent workflow around your tools. Keep user-controlled values as explicit arguments and validate them just as you validate tool inputs.
Mounting the endpoint in a Node framework
The transport can be mounted behind a framework adapter instead of Node’s built-in http module. The framework must preserve the request method, headers and body, and must not consume a streaming response before the MCP transport writes it. Mount one route, such as POST /mcp plus any methods required by your pinned SDK, and leave framework JSON-body limits high enough for legitimate MCP messages.
For a JSON-only service, keep enableJsonResponse: true. For clients that expect server-sent streaming, use the transport’s streaming response mode and configure the reverse proxy to avoid buffering that response.
Security for local and public servers
Host and origin protection
A localhost server can be abused through DNS rebinding if it accepts arbitrary Host or Origin headers. Validate allowed hosts and origins, especially when binding to a loopback address. Do not treat a browser-supplied Origin as proof of identity; combine origin checks with authentication.
Authentication and authorization
Terminate authentication at the MCP route or a trusted proxy, then pass the authenticated identity into tool handlers. Check authorization for every tool and resource. If a tool can fetch URLs, write files or execute commands, apply an allowlist and resource limits rather than exposing unrestricted capabilities.
CORS and proxies
Allow only the origins that actually use your server. Forward the MCP session header unchanged when running statefully. Verify that your proxy supports long-lived responses, does not rewrite /mcp, and applies timeouts longer than your slowest legitimate tool.
Graceful shutdown and reliability
Close the HTTP listener, transports and MCP server on SIGINT and SIGTERM, as shown in the example. The official guidance notes that in-flight tool handlers are not automatically drained when the process exits. If a tool mutates data, add your own cancellation, completion tracking or deployment drain period before terminating the process.
There are no independent throughput, latency or cost benchmarks established for this implementation. Measure your own handlers with realistic payloads. The dominant factors are normally the work performed by tools, remote services they call, response size, proxy buffering and whether stateful requests are routed consistently.
Deployment checklist
- Pin one SDK generation and commit the lockfile.
- Expose a stable HTTPS URL ending in a documented MCP path such as
/mcp. - Choose stateful or stateless mode before selecting load-balancer behavior.
- Configure authentication, CORS and Host/Origin validation.
- Set proxy read, write and idle timeouts for your longest tool.
- Keep a separate
/healthzendpoint that does not invoke MCP tools. - Log request IDs, tool names, duration and failure class without logging secrets or full sensitive arguments.
- Test shutdown while a tool is running and decide how that operation is recovered.
Common errors and fixes
Import or constructor is not found
Cause: v1 and v2 package names or adapter paths were mixed. Fix: inspect the installed major version, use its matching guide, and pin that version. Do not copy a v2 import into a v1 project.
The client receives 404 at /mcp
Cause: the proxy changed the path, the server is listening on another port, or the framework route was not mounted. Fix: test the local URL first, confirm the public base path, and forward the path without a trailing-slash rewrite.
Requests hang or time out
Cause: a proxy is buffering a stream, a tool never resolves, or the timeout is shorter than the tool’s work. Fix: use JSON responses when streaming is unnecessary, disable response buffering for streaming mode, add tool-level timeouts, and raise proxy timeouts deliberately.
Stateful requests lose their session
Cause: the session ID is not forwarded or later requests reach a process that does not own the session. Fix: preserve the session header and use sticky or shared session routing. Stateless mode avoids this class of failure when session semantics are not needed.
Browser requests are rejected on localhost
Cause: Host or Origin protection correctly rejected an unexpected value, or a development origin was omitted from the allowlist. Fix: explicitly allow the exact development origin and host you use; do not disable validation broadly.
Recommended Free Tools
Shutdown drops work
Cause: process termination does not automatically drain in-flight handlers. Fix: stop accepting new requests, track active operations, allow a bounded drain period, and make mutating tools idempotent where possible.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If your MCP project needs screenshots of documentation, dashboards or test pages, ScreenshotNeo provides a single HTTP call instead of maintaining a browser stack. It accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and whether it was billed.
Use the API documentation at https://screenshotneo.com/docs/ for all options. A minimal call is:
Best Value
curl -G 'https://api.screenshotneo.com/v1/shot' -d access_key=YOUR_API_KEY --data-urlencode url=https://screenshotneo.com/docs/ -o shot.webp
Python
import requests
r = requests.get('https://api.screenshotneo.com/v1/shot', params={'access_key': 'YOUR_API_KEY', 'url': 'https://screenshotneo.com/docs/'}, timeout=90)
open('shot.webp', 'wb').write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://screenshotneo.com/docs/' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo also exposes an MCP server with take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. Every plan includes its features; 1,000 screenshots per month are free with no card, and paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Final implementation decision
For a new remote TypeScript service, choose Streamable HTTP, start stateless unless your workflow genuinely needs session semantics, and add a session ID generator when it does. Keep the MCP endpoint narrow, validate every input, protect localhost and public deployments against hostile origins, and treat shutdown and proxy behavior as part of the server design rather than deployment afterthoughts.
Frequently Asked Questions
Can one process expose both an MCP endpoint and ordinary REST routes?
Yes. Mount MCP at a dedicated path such as /mcp and keep health or REST routes separate so their authentication, limits and monitoring policies remain independent.
When should a tool return JSON instead of a stream?
Use the transport’s JSON response option when clients only need a complete response. Keep streaming enabled when the client or tool benefits from incremental output.
Does the SDK provide performance guarantees?
No benchmark or throughput guarantee is established here. Measure your own handlers, dependencies, proxy settings and payload sizes under the traffic pattern you expect.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

