toy-mcp
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
toy-mcp
Two small MCP servers for studying the Model Context Protocol on the wire, and for seeing what changed in the 2026-07-28 revision. Both are short enough to read in an afternoon.
MCP is an open protocol that lets an AI application call tools and read data that a server offers. The messages are JSON-RPC 2.0, and here they travel over HTTP.
Code | Port | Protocol | Built with | |
SDK server |
| 3333 | 2025-11-25 (the latest that v1 of the official SDK supports) | Version 1 of the official TypeScript SDK, in stateless mode |
Hand-written server |
| 3334 | 2026-07-28 | Node's |
They can run side by side, so the same kind of request can be sent to each and the answers compared.
This is a learning project. Neither server is meant for production: see Known gaps.
Quick start
Requires Node 20.19+, 22.13+ or 24+.
npm installThen start each server in its own terminal:
npm start # SDK server http://127.0.0.1:3333/mcpnpm run start:2026 # hand-written server http://127.0.0.1:3334/mcpBoth listen on 127.0.0.1 only. Set PORT to use another port, for example PORT=4000 npm start.
Each server prints one line per request. This is the hand-written server after one valid request and one request without the MCP headers:
2026-07-28 server on http://127.0.0.1:3334/mcp
server/discover -> 200
server/discover -> 400 -32020 Header mismatch: MCP-Protocol-Version header is missingTo send requests, see Try it.
Related MCP server: Tizón MCP Server
Where to start reading
The whole of src/ is about 840 lines, comments included.
This README, then docs/how-it-works.md, which walks through both servers with the responses they return.
src/sdk-server/:server.tsregisters the tools andhttp.tsis the HTTP transport.src/server-2026/handler.ts, then the functions insrc/protocol/that it calls.test/: the test names list the behaviors, one per line.docs/spec-2026-07-28.md: the parts of the specification that the hand-written server implements, with sources.
Project layout
src/
protocol/ JSON-RPC types, type guards and reply builders, error codes, per-request validation, server/discover (pure functions)
catalog/ the toy marketplace behind the SDK server's tools: items, input validation, job store
sdk-server/ the SDK-based server: tool registration and HTTP transport
server-2026/ the hand-written server: request handler and HTTP transport
test/ Vitest tests, mirroring src/
postman/ a Postman collection: ready-made requests for both servers
docs/ how it works, and the wire shapes of the 2026-07-28 specThe hand-written server keeps its protocol rules in plain functions (handleRpc, validateRequest) with the HTTP layer kept thin, so the rules can be read and tested without a network. It does not use catalog/ yet: its tools arrive in step 3.
Scripts
Script | What it does |
| Vitest: unit tests plus end-to-end tests that boot each server on a random port |
|
|
| ESLint: recommended rules, single quotes, a TSDoc comment on every exported symbol in |
| Prettier: rewrite files to the project style |
| Prettier: report files that differ from the project style, without changing them |
| typecheck, lint, formatting check and tests |
Try it
Postman
postman/toy-mcp.postman_collection.json is a Postman collection (v2.1) with 24 requests, in one folder per server. Every request has a description that says what it demonstrates and what to look for in the reply.
Start the server you want to try (see Quick start).
In Postman, select Import and choose the file. The collection needs no environment and no authorization.
Open the folder of that server and send its requests from top to bottom.
Folder | Start the server with | URL variable | Default value |
|
|
|
|
|
|
|
|
sdk and s2026 are collection variables that hold the whole URL. If you started a server with another PORT, change the port in its variable (open the collection, then its Variables tab).
The SDK server answers every request in its folder with HTTP 200 and an SSE frame: the JSON-RPC message is on the data: line. The hand-written server's requests are lettered A to L. A succeeds. B to L are each answered with an error, and the name says the HTTP status and, where there is one, the JSON-RPC error code to expect.
get_result reads the third collection variable, jobId. A script on the start_job request stores the jobId from the reply in that variable. If get_result answers No job, copy the jobId from the text field of the start_job reply and paste it into the variable.
curl
Every call is one JSON-RPC POST, so plain curl works too. Write each header out in full.
The hand-written server:
curl -X POST 'http://127.0.0.1:3334/mcp' -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -H 'MCP-Protocol-Version: 2026-07-28' -H 'Mcp-Method: server/discover' -d '{"jsonrpc":"2.0","id":"d1","method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'It replies with one line of JSON, shown here on several lines:
{
"jsonrpc": "2.0",
"id": "d1",
"result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28"],
"capabilities": {},
"_meta": {
"io.modelcontextprotocol/serverInfo": { "name": "toy-marketplace-2026", "version": "0.1.0" }
},
"instructions": "Hand-written MCP server for the 2026-07-28 revision.",
"ttlMs": 3600000,
"cacheScope": "public"
}
}The SDK server:
curl -X POST 'http://127.0.0.1:3333/mcp' -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'The SDK server needs the Accept header shown here. curl's default, Accept: */*, is answered with 406.
A client that opens with initialize (any client written for an earlier revision, including one built on v1 of the SDK) gets -32022 and the supported versions from the hand-written server. That is the intended behavior of a server that speaks 2026-07-28 only. Until step 3 adds tools, Postman and curl are the way to talk to it.
The SDK server
A small marketplace with a catalog of three fake items.
Tool | What it demonstrates |
| The minimal tool: name, description, JSON-Schema input, typed content result |
| The marketplace pattern: search, then one generic call. A validation failure names every failing field |
| A server-minted handle in place of a session. |
Behaviors worth knowing:
It is built on v1 of the official TypeScript SDK (the
@modelcontextprotocol/sdkpackage), which supports protocol 2025-11-25 at most, soserver/discoverreturns-32601. Version 2 of the SDK (the@modelcontextprotocol/serverand@modelcontextprotocol/clientpackages) implements the 2026-07-28 revision. This project does not use it: the second server is written by hand, so that every rule of the 2026-07-28 wire format is visible in the code.Stateless mode: a fresh server per request, no
Mcp-Session-Id, andtools/listworks withoutinitialize.Replies are SSE frames (
event: message,data: {...}) even for a single response.Argument-validation failures and unknown tools come back as a normal result with
isError: true. Unknown methods come back as a JSON-RPCerror.The SDK's transport refuses some requests before any tool runs:
406unlessAcceptlists bothapplication/jsonandtext/event-stream,415unlessContent-Typeisapplication/json,400for anMCP-Protocol-Versionheader that names a version it does not support (such as2026-07-28), and400with-32700for a body that is JSON but not a JSON-RPC message. It acknowledges a notification with202and no body.Jobs live in memory, so a restart loses them. A real deployment needs a shared store.
The hand-written server
Built one step at a time; each step is runnable and each rejection is visible in the server log.
Step | What it adds | Status |
1 | JSON-RPC envelope, | Done |
2 | Per-request rules: required headers, required | Done |
3 |
| Planned |
4 | The handle, idempotency and cost-cap tools from the SDK server | Planned |
5 | Multi round-trip requests: | Planned |
Errors it returns today:
Code | HTTP | Meaning | Example trigger |
| 400 | Body is not valid JSON | Body |
| 400 | Not a JSON-RPC 2.0 request; answered with | Body |
| 400 | A required header is missing, or a header contradicts the body | No |
| 400 |
| Headers present, no |
| 400 | Unsupported protocol version, with | Version |
| 404 | Method not found | Any method not implemented yet |
Outside JSON-RPC, a path other than /mcp gets 404, and a method other than POST on /mcp gets 405 with Allow: POST. Both have an empty body.
The server does not return -32021 (MissingRequiredClientCapability) yet, because nothing it implements needs a client capability. Step 5 changes that.
The specification does not give an HTTP status for -32700 and -32600, so 400 is this project's choice. The order of the checks, and answering initialize with -32022, are also implementation choices rather than spec text.
Known gaps
Both servers are for learning on a local machine. These things are missing, some on purpose and some because they are not built yet:
No authentication or authorization. Anyone who can reach the port can call every tool.
No
Origincheck. The specification requires an HTTP server to validate theOriginheader and to answer an invalid one with403, as a defense against DNS rebinding. Neither server does. Both bind to127.0.0.1only.No limit on the size of a request body, on either server.
The path must be exactly
/mcp./mcp/and/mcp?x=1get404from both servers.The hand-written server answers a notification as if it were a request. A message without an
idgets200and a reply withid: null. The specification asks for202and no body.The hand-written server does not check
AcceptorContent-Type.start_jobcompares only the idempotency key. A second call with the same key and different arguments returns the first job. A production API would refuse it.The cost cap is checked only before a job starts, and jobs live in memory.
Documentation
docs/how-it-works.md: what each part demonstrates, with real responses, and exercises.
docs/spec-2026-07-28.md: the wire shapes this project implements, with sources.
AGENTS.md: guidance for coding agents and contributors.
License
MIT. See LICENSE.
Notes
TypeScript is pinned to 6.0.x because
typescript-eslintsupports TypeScript below 6.1 only. Raise it when typescript-eslint supports a newer release.Formatting: single quotes.
.prettierrc.jsonconfigures Prettier and the ESLintquotesrule enforces it.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
A paid remote MCP for AI SDK eval dashboard, built to return verdicts, receipts, usage logs, and aud
A paid remote MCP for ZeroLang, built to return verdicts, receipts, usage logs, and audit-ready JSON
A paid remote MCP for OpenAI Codex agent coordination MCP, built to return verdicts, receipts, usage
A paid remote MCP for Skybridge, built to return verdicts, receipts, usage logs, and audit-ready JSO
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceA minimal reference implementation of an MCP server with two basic math tools (add and multiply), designed as a starting point for learning MCP protocol and deploying remote servers to cloud platforms.1-
- FlicenseNot gradedqualityDmaintenanceMCP server for operating a fictional restaurant, providing tools to consult the menu, manage active orders, view shift summaries, update dish availability, and advance order statuses. Includes a playground that teaches JSON-RPC by showing live protocol interactions.1-
- FlicenseNot gradedqualityDmaintenanceA demo MCP server that showcases the 2026-07-28 MCP protocol revision features: discover, stateless calls, multi-round-trip requests, subscriptions, and cache hints. It also maintains backward compatibility with legacy 2025-11-25 clients.-
- FlicenseNot gradedqualityCmaintenanceEnables MCP clients to pull live FX and crypto prices, store and read JSON records in a DynamoDB-backed key-value store, and run business calculations such as invoice totals, shipping estimates, and inventory-status checks. It runs over streamable HTTP on AWS Lambda with SigV4-authenticated access, or locally via stdio.-