ax-check.com
by Gauge
M
Model Context Protocol
modelcontextprotocol.io
ax-check.com
Checked 22 October 2026
A
100/100
Agents haven’t verified setup
Docs, install steps and SDKs all work on the first try
19 of 23 checklist items passed with zero attention flags. Pricing checks are unassessed since MCP is a free open-source standard with no pricing page.
1 fix to start with
Concrete changes, backed by what we found.
Recorded coding sessions
Same task, 3 models, no modelcontextprotocol.io account. Recorded 22 October 2026 on Pi.
3 recorded · 1m 48s
1m 48s · recorded
Session findings
Select a finding to see it in context.
›Thinking…
The user is asking to create a simple example using Model Context Protocol (MCP). They want to know how pricing works, whether this product is easy to manage, and to let them know if I'm blocked. They said to keep it lightweight: use hosted products through SDKs or APIs, don't spin up local service stacks, and don't wait for long-running commands.
Let me start by exploring the current working directory to get a sense of the environment.
Actually, let me think about this carefully. The user is asking about "Model Context Protocol" (MCP). This is an open protocol by Anthropic for connecting AI assistants to tools/data sources. The user's requests are:
- Create a simple example using MCP
- Explanation of how pricing works
- A concise explanation of whether this product is easy to manage
- Notification if I'm blocked
- If this product doesn't have an actionable developer workflow, communicate that clearly and stop
- Keep it lightweight: use hosted products via SDK/API
- Don't spin up local service stacks or wait on long-running commands
First, let me understand the environment. Let me look at the current working directory and check what tools and environment are available.
The key phrase here is "use hosted products through SDKs or APIs." MCP itself is an open protocol and isn't really a "hosted product" with pricing. Let me think about this.
Actually, MCP is a protocol specification. There's no pricing per se for the "hosted product" — it's open source. So there's a subtle issue here. Let me investigate the environment to understand what I have access to.
Let me first explore the sandbox to see what's there.
I'll start by exploring the environment to understand what I'm working with.
pwd && ls -la && echo "---ENV---" && env | grep -iE "mcp|model|context|anthropic|api_key|token|sdk" | sed 's/=.*/=<redacted>/' && echo "---PI---" && env | grep -iE "^PI_" | sed 's/=.*/=<set>/'
/sandbox/repo
/sandbox/repo total 16 drwxr-xr-x 3 root root 4096 Sep 18 08:00 . drwxr-xr-x 7 root root 4096 Oct 22 03:59 .. drwxr-xr-x 8 root root 4096 Sep 24 20:05 .git -rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore ---ENV--- ALG_INFERENCE_MODEL=<redacted> PI_MODEL=<redacted> PI_GATEWAY_API_KEY=<redacted> ---PI--- PI_CODING_AGENT=<set> PI_REASONING_LEVEL=<set> PI_SESSION_FILE=<set> PI_PROVIDER=<set> PI_MODEL=<set> PI_SESSION_ID=<set> PI_GATEWAY_API_KEY=<set>
Recorded tool call
{
"type": "tool_call",
"seq": 4,
"ts": "2026-10-22T03:59:58.356Z",
"name": "bash",
"turn": 1,
"input": {
"command": "pwd && ls -la && echo \"---ENV---\" && env | grep -iE \"mcp|model|context|anthropic|api_key|token|sdk\" | sed 's/=.*/=<redacted>/' && echo \"---PI---\" && env | grep -iE \"^PI_\" | sed 's/=.*/=<set>/'"
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-84f0890ea2e9a835",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 8,
"ts": "2026-10-22T03:59:58.356Z",
"turn": 1,
"output": {
"content": [
{
"text": "/sandbox/repo\ntotal 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Oct 22 03:59 ..\ndrwxr-xr-x 8 root root 4096 Sep 24 20:05 .git\n-rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore\n---ENV---\nALG_INFERENCE_MODEL=<redacted>\nPI_MODEL=<redacted>\nPI_GATEWAY_API_KEY=<redacted>\n---PI---\nPI_CODING_AGENT=<set>\nPI_REASONING_LEVEL=<set>\nPI_SESSION_FILE=<set>\nPI_PROVIDER=<set>\nPI_MODEL=<set>\nPI_SESSION_ID=<set>\nPI_GATEWAY_API_KEY=<set>\n",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-84f0890ea2e9a835",
"is_error": false,
"tool_kind": "shell"
}echo "---node---"; node --version 2>/dev/null; npm --version 2>/dev/null; echo "---python---"; python3 --version 2>/dev/null; echo "---net---"; curl -s -m 8 -o /dev/null -w "%{http_code}" https://registry.npmjs.org/ 2>/dev/null; echo " (npm registry)"; curl -s -m 8 -o /dev/null -w "%{http_code}" https://pypi.org/ 2>/dev/null; echo " (pypi)"---node---
---node--- v22.23.2 10.9.8 ---python--- Python 3.12.3 ---net--- 200 (npm registry) 200 (pypi)
Recorded tool call
{
"type": "tool_call",
"seq": 5,
"ts": "2026-10-22T03:59:58.356Z",
"name": "bash",
"turn": 1,
"input": {
"command": "echo \"---node---\"; node --version 2>/dev/null; npm --version 2>/dev/null; echo \"---python---\"; python3 --version 2>/dev/null; echo \"---net---\"; curl -s -m 8 -o /dev/null -w \"%{http_code}\" https://registry.npmjs.org/ 2>/dev/null; echo \" (npm registry)\"; curl -s -m 8 -o /dev/null -w \"%{http_code}\" https://pypi.org/ 2>/dev/null; echo \" (pypi)\""
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-9366912d5fe5d028",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 9,
"ts": "2026-10-22T03:59:58.967Z",
"turn": 1,
"output": {
"content": [
{
"text": "---node---\nv22.23.2\n10.9.8\n---python---\nPython 3.12.3\n---net---\n200 (npm registry)\n200 (pypi)\n",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-9366912d5fe5d028",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
So, we're in an empty repository with network access. Node 22 and Python 3.12 are available.
Now let me think about the task. The user wants a simple MCP example. MCP (Model Context Protocol) is an open protocol. The official SDKs are:
- TypeScript SDK:
@modelcontextprotocol/sdk - Python SDK:
mcp
The user says: "Keep it lightweight: use hosted products through their SDK or API. Do not start local service stacks or wait for long-running commands. If either is needed for a quickstart, say so plainly and stop."
Hmm. Here's the issue: MCP is a protocol between a client (host) and a server. The standard "simple example" of MCP is:
- Build an MCP server (which is a long-running process — a stdio or SSE server)
- Connect to it with an MCP client
But the user explicitly says don't start local service stacks or wait for long-running commands. In a typical MCP quickstart you'd need to run an MCP server as a process...
But — here's the nuance. You can write an MCP example that doesn't require a separate long-running service stack: the TypeScript SDK supports "in-memory" transport (InMemoryTransport.createLinkedPair()), where the client and server communicate via in-process linked transports. This isn't a local service stack and isn't long-running — it's just a script that starts up, runs, prints, exits. This satisfies "stay lightweight" and "don't start a local service stack or wait for long-running commands".
So I can build a self-contained example:
- An MCP server (with tools) and an MCP client connected via in-memory transport
- Run it as a one-shot script that exits
This uses a hosted product... well, actually MCP isn't hosted. Let me reconsider.
Wait, the user says "use hosted products through their SDK or API". This is a bit of a template instruction. MCP isn't inherently a "hosted product" — it's an open protocol/SDK. There's no pricing for MCP itself. So I should address this: MCP is an open-source protocol, free, with no pricing/hosting costs. The SDKs are free (MIT licensed).
Let me address the pricing question honestly: MCP itself is an open protocol with no pricing — the SDKs are free and open source (MIT). If they're thinking of a hosted MCP service, there are third-party ones, but the protocol/SDK itself is free.
Next, "will this product be easy to manage" — I should give a concise assessment.
"Tell me if you're blocked" — I'll monitor this.
"If there's no actionable developer workflow for this product, say so plainly and stop" — MCP has a clear developer workflow (build servers/clients with the SDK), so this is actionable.
So my plan:
- Build a simple self-contained MCP example using the TypeScript SDK's in-memory transport (one-shot, no long-running services, no local stack).
- Explain pricing (free/open source, no hosting cost for the protocol).
- Brief assessment of manageability.
Let me confirm the approach works. Since network is available, I'll install the SDK locally (npm install is a short command, not long-running).
Actually, let me reconsider the "hosted product" framing. The instructions seem to come from a template that's meant to have me use hosted/SaaS products via SDK/API and avoid Docker Compose stacks and so on. For MCP, the most faithful lightweight interpretation is: use the official SDK and run a one-shot in-memory example. This avoids the typical pain point where an MCP quickstart requires running a server process in a separate terminal.
Let me think about whether npm install is "long-running". It's not — it typically finishes in seconds. The instructions say "don't wait for long-running commands". npm install of a single package isn't long-running. I'll go ahead with it but keep an eye on timing.
Setup:
- Initialize the npm project
- Install @modelcontextprotocol/sdk
- Write a self-contained TS example (or plain JS to avoid a build step). Actually, the SDK is ESM and ships with TypeScript types. I can write plain JS (ESM) and run directly with Node without a build step. This keeps things lightweight — no tsc build.
Let me go with plain ESM JavaScript to avoid needing a TypeScript build step. Actually, should I use TypeScript with tsx, or plain JS? Plain JS is lighter. I'll go with plain JS using ESM modules.
I'll write an example like:
- Define an MCP server with a tool (e.g., a simple calculator tool, or "get_weather"-style mock)
- Connect a client via in-memory transport
- List tools, call a tool, print the result
- Exit cleanly
This demonstrates the full MCP workflow (server defines tools, client discovers and calls them) without a network service or long-running process.
Let me first check the SDK structure to get the correct import paths. Let me install it and inspect.
Actually, let me think about which transport to use. InMemoryTransport.createLinkedPair() returns [clientTransport, serverTransport]. Then:
const server = new McpServer({...})ornew Server({...})await server.connect(serverTransport)const client = new Client({...})await client.connect(clientTransport)
The high-level McpServer API uses registerTool etc. The low-level Server API uses setRequestHandler. Let me use the high-level McpServer from @modelcontextprotocol/sdk/server/mcp.js and Client from @modelcontextprotocol/sdk/client/index.js.
Let me install and check the actual exports to get the right paths, since these change across versions.
Plan for the tool definition. Using the high-level API:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { z } from "zod";
const server = new McpServer({ name: "demo-server", version: "1.0.0" });
server.registerTool(
"add",
{
title: "Addition",
description: "Add two numbers",
inputSchema: { a: z.number(), b: z.number() },
},
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
})
);
const client = new Client({ name: "demo-client", version: "1.0.0" });
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
const tools = await client.listTools();
console.log(tools);
const result = await client.callTool({ name: "add", arguments: { a: 2, b: 3 } });
console.log(result);
await client.close();
await server.close();
This needs zod. The SDK depends on zod. Let me check the version. Recent SDK versions use zod v3 or v4. registerTool's inputSchema takes a ZodRawShape.
To be safe, let me install and inspect the actual package. I'll also add a bit more — resources and a prompt too. To show off more of the protocol, it might be worth adding a resource. Let me keep it "simple" but representative.
Let me proceed step by step. First, initialize and install.
Let me also double-check the .gitignore that already exists.
Let me run the install. I'll set an appropriate timeout.
I'll do the following:
- npm init -y
- npm install @modelcontextprotocol/sdk zod
- Inspect the package.json versions and exports
Let me execute these.
Empty repo, Node 22 + Python 3.12 available, and network access works. Let me set up a lightweight MCP example.
First, a quick plan check on the approach: MCP is a client↔server protocol. The typical quickstart tells you to run an MCP server as a long-running process in one terminal and a client in another. That would violate your "no local service stacks / no long-running commands" rule. The good news: the official SDK ships an in-memory transport, so I can wire a server and client together in a single one-shot script that runs and exits — no separate process, no ports, no Docker. Let me verify that works.
cd /sandbox/repo && cat .gitignore && echo "---" && npm init -y >/dev/null 2>&1 && echo "init ok"
# Keep generated and vendored files out of source diffs.
# Keep generated and vendored files out of source diffs. # Logs / OS cruft *.log .DS_Store # JavaScript / TypeScript node_modules/ .next/ dist/ build/ coverage/ # Python .venv/ venv/ __pycache__/ *.py[cod] .pytest_cache/ *.egg-info/ # Rust target/ # Go / PHP / Ruby vendored deps vendor/ .bundle/ # Java / Kotlin / Scala .gradle/ # .NET bin/ obj/ # Elixir _build/ deps/ # Dart / Flutter .dart_tool/ --- init ok
Recorded tool call
{
"type": "tool_call",
"seq": 14,
"ts": "2026-10-22T04:00:15.100Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && cat .gitignore && echo \"---\" && npm init -y >/dev/null 2>&1 && echo \"init ok\""
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-821878a8ac4f582c",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 16,
"ts": "2026-10-22T04:00:15.931Z",
"turn": 1,
"output": {
"content": [
{
"text": "# Keep generated and vendored files out of source diffs.\n\n# Logs / OS cruft\n*.log\n.DS_Store\n\n# JavaScript / TypeScript\nnode_modules/\n.next/\ndist/\nbuild/\ncoverage/\n\n# Python\n.venv/\nvenv/\n__pycache__/\n*.py[cod]\n.pytest_cache/\n*.egg-info/\n\n# Rust\ntarget/\n\n# Go / PHP / Ruby vendored deps\nvendor/\n.bundle/\n\n# Java / Kotlin / Scala\n.gradle/\n\n# .NET\nbin/\nobj/\n\n# Elixir\n_build/\ndeps/\n\n# Dart / Flutter\n.dart_tool/\n---\ninit ok\n",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-821878a8ac4f582c",
"is_error": false,
"tool_kind": "shell"
}cd /sandbox/repo && time npm install @modelcontextprotocol/sdk zod 2>&1 | tail -n 20
added 95 packages in 8s
added 95 packages in 8s 34 packages are looking for funding run `npm fund` for details real 0m7.813s user 0m3.523s sys 0m2.101s
Recorded tool call
{
"type": "tool_call",
"seq": 15,
"ts": "2026-10-22T04:00:15.100Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && time npm install @modelcontextprotocol/sdk zod 2>&1 | tail -n 20",
"timeout": 180
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-96c9c91465cafe88",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 17,
"ts": "2026-10-22T04:00:23.468Z",
"turn": 1,
"output": {
"content": [
{
"text": "\nadded 95 packages in 8s\n\n34 packages are looking for funding\n run `npm fund` for details\n\nreal\t0m7.813s\nuser\t0m3.523s\nsys\t0m2.101s\n",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-96c9c91465cafe88",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Install completed in 8 seconds — not long-running. Good.
Next, let me check the SDK's exports and version to get the correct import paths.
Install took 8s (not long-running). Let me verify the SDK's version and exact export paths so I use the correct API.
cd /sandbox/repo && node -e "const p=require('@modelcontextprotocol/sdk/package.json'); console.log('sdk version:', p.version);" 2>/dev/null; echo "---zod---"; node -e "console.log('zod:', require('zod/package.json').version)" 2>/dev/null; echo "---exports of interest---"; node -e "const p=require('@modelcontextprotocol/sdk/package.json'); const e=p.exports; for (const k of ['./server/mcp.js','./client/index.js','./inMemory.js']) console.log(k, '->', JSON.stringify(e[k]));"Failedsdk version: undefined
sdk version: undefined
---zod---
zod: 4.6.7
---exports of interest---
[eval]:1
const p=require('@modelcontextprotocol/sdk/package.json'); const e=p.exports; for (const k of ['./server/mcp.js','./client/index.js','./inMemory.js']) console.log(k, '->', JSON.stringify(e[k]));
^
TypeError: Cannot read properties of undefined (reading './server/mcp.js')
at [eval]:1:189
at runScriptInThisContext (node:internal/vm:209:10)
at node:internal/process/execution:446:12
at [eval]-wrapper:6:24
at runScriptInContext (node:internal/process/execution:444:60)
at evalFunction (node:internal/process/execution:279:30)
at evalTypeScript (node:internal/process/execution:291:3)
at node:internal/main/eval_string:74:3
Node.js v22.23.2
Command exited with code 1Recorded tool call
{
"type": "tool_call",
"seq": 21,
"ts": "2026-10-22T04:00:26.276Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && node -e \"const p=require('@modelcontextprotocol/sdk/package.json'); console.log('sdk version:', p.version);\" 2>/dev/null; echo \"---zod---\"; node -e \"console.log('zod:', require('zod/package.json').version)\" 2>/dev/null; echo \"---exports of interest---\"; node -e \"const p=require('@modelcontextprotocol/sdk/package.json'); const e=p.exports; for (const k of ['./server/mcp.js','./client/index.js','./inMemory.js']) console.log(k, '->', JSON.stringify(e[k]));\""
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-85bc55dcddc45a9c",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 24,
"ts": "2026-10-22T04:00:26.276Z",
"turn": 1,
"output": {
"content": [
{
"text": "sdk version: undefined\n---zod---\nzod: 4.6.7\n---exports of interest---\n[eval]:1\nconst p=require('@modelcontextprotocol/sdk/package.json'); const e=p.exports; for (const k of ['./server/mcp.js','./client/index.js','./inMemory.js']) console.log(k, '->', JSON.stringify(e[k]));\n ^\n\nTypeError: Cannot read properties of undefined (reading './server/mcp.js')\n at [eval]:1:189\n at runScriptInThisContext (node:internal/vm:209:10)\n at node:internal/process/execution:446:12\n at [eval]-wrapper:6:24\n at runScriptInContext (node:internal/process/execution:444:60)\n at evalFunction (node:internal/process/execution:279:30)\n at evalTypeScript (node:internal/process/execution:291:3)\n at node:internal/main/eval_string:74:3\n\nNode.js v22.23.2\n\n\nCommand exited with code 1",
"type": "text"
}
],
"details": {}
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-85bc55dcddc45a9c",
"is_error": true,
"tool_kind": "shell"
}cd /sandbox/repo && ls node_modules/@modelcontextprotocol/sdk/dist/esm/ | head -n 40 && echo "---check inMemory + mcp exist---" && ls node_modules/@modelcontextprotocol/sdk/dist/esm/inMemory.js node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js node_modules/@modelcontextprotocol/sdk/dist/esm/client/index.js 2>&1
client
client examples experimental inMemory.d.ts inMemory.d.ts.map inMemory.js inMemory.js.map package.json server shared spec.types.d.ts spec.types.d.ts.map spec.types.js spec.types.js.map types.d.ts types.d.ts.map types.js types.js.map validation ---check inMemory + mcp exist--- node_modules/@modelcontextprotocol/sdk/dist/esm/client/index.js node_modules/@modelcontextprotocol/sdk/dist/esm/inMemory.js node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
Recorded tool call
{
"type": "tool_call",
"seq": 22,
"ts": "2026-10-22T04:00:26.276Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && ls node_modules/@modelcontextprotocol/sdk/dist/esm/ | head -n 40 && echo \"---check inMemory + mcp exist---\" && ls node_modules/@modelcontextprotocol/sdk/dist/esm/inMemory.js node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js node_modules/@modelcontextprotocol/sdk/dist/esm/client/index.js 2>&1"
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-995160868e66d34f",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 23,
"ts": "2026-10-22T04:00:26.276Z",
"turn": 1,
"output": {
"content": [
{
"text": "client\nexamples\nexperimental\ninMemory.d.ts\ninMemory.d.ts.map\ninMemory.js\ninMemory.js.map\npackage.json\nserver\nshared\nspec.types.d.ts\nspec.types.d.ts.map\nspec.types.js\nspec.types.js.map\ntypes.d.ts\ntypes.d.ts.map\ntypes.js\ntypes.js.map\nvalidation\n---check inMemory + mcp exist---\nnode_modules/@modelcontextprotocol/sdk/dist/esm/client/index.js\nnode_modules/@modelcontextprotocol/sdk/dist/esm/inMemory.js\nnode_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js\n",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-995160868e66d34f",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
The require of package.json didn't work well (because it's ESM and has an exports map, requiring package.json directly fails / returns a weird result). But the files exist. zod is 4.6.7. Let's get the SDK version by another method and check the exports map.
Files exist and zod is v4. Let me grab the SDK version and confirm the exports map properly.
cd /sandbox/repo && grep '"version"' node_modules/@modelcontextprotocol/sdk/package.json | head -1 && echo "---exports keys---" && node --input-type=module -e "import fs from 'node:fs'; const p=JSON.parse(fs.readFileSync('node_modules/@modelcontextprotocol/sdk/package.json','utf8')); console.log('version',p.version); for(const k of ['./server/mcp.js','./client/index.js','./inMemory.js']) console.log(k,'->',JSON.stringify(p.exports[k]));""version": "1.32.1",
"version": "1.32.1", ---exports keys--- version 1.32.1 ./server/mcp.js -> undefined ./client/index.js -> undefined ./inMemory.js -> undefined
Recorded tool call
{
"type": "tool_call",
"seq": 28,
"ts": "2026-10-22T04:00:28.683Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && grep '\"version\"' node_modules/@modelcontextprotocol/sdk/package.json | head -1 && echo \"---exports keys---\" && node --input-type=module -e \"import fs from 'node:fs'; const p=JSON.parse(fs.readFileSync('node_modules/@modelcontextprotocol/sdk/package.json','utf8')); console.log('version',p.version); for(const k of ['./server/mcp.js','./client/index.js','./inMemory.js']) console.log(k,'->',JSON.stringify(p.exports[k]));\""
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-926c0af1e6cff930",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 31,
"ts": "2026-10-22T04:00:28.683Z",
"turn": 1,
"output": {
"content": [
{
"text": " \"version\": \"1.32.1\",\n---exports keys---\nversion 1.32.1\n./server/mcp.js -> undefined\n./client/index.js -> undefined\n./inMemory.js -> undefined\n",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-926c0af1e6cff930",
"is_error": false,
"tool_kind": "shell"
}cd /sandbox/repo && grep -nE "registerTool|registerResource|registerPrompt" node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.d.ts | head -n 20
91: * @deprecated Use `registerResource` instead.
91: * @deprecated Use `registerResource` instead.
96: * @deprecated Use `registerResource` instead.
101: * @deprecated Use `registerResource` instead.
106: * @deprecated Use `registerResource` instead.
113: registerResource(name: string, uriOrTemplate: string, config: ResourceMetadata, readCallback: ReadResourceCallback): RegisteredResource;
114: registerResource(name: string, uriOrTemplate: ResourceTemplate, config: ResourceMetadata, readCallback: ReadResourceTemplateCallback): RegisteredResourceTemplate;
121: * @deprecated Use `registerTool` instead.
126: * @deprecated Use `registerTool` instead.
135: * @deprecated Use `registerTool` instead.
145: * @deprecated Use `registerTool` instead.
150: * @deprecated Use `registerTool` instead.
155: * @deprecated Use `registerTool` instead.
161: registerTool<OutputArgs extends ZodRawShapeCompat | AnySchema, InputArgs extends undefined | ZodRawShapeCompat | AnySchema = undefined>(name: string, config: {
171: * @deprecated Use `registerPrompt` instead.
176: * @deprecated Use `registerPrompt` instead.
181: * @deprecated Use `registerPrompt` instead.
186: * @deprecated Use `registerPrompt` instead.
192: registerPrompt<Args extends PromptArgsRawShape>(name: string, config: {
Recorded tool call
{
"type": "tool_call",
"seq": 29,
"ts": "2026-10-22T04:00:28.683Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && grep -nE \"registerTool|registerResource|registerPrompt\" node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.d.ts | head -n 20"
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-adf0d26a69ddbc2f",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 30,
"ts": "2026-10-22T04:00:28.683Z",
"turn": 1,
"output": {
"content": [
{
"text": "91: * @deprecated Use `registerResource` instead.\n96: * @deprecated Use `registerResource` instead.\n101: * @deprecated Use `registerResource` instead.\n106: * @deprecated Use `registerResource` instead.\n113: registerResource(name: string, uriOrTemplate: string, config: ResourceMetadata, readCallback: ReadResourceCallback): RegisteredResource;\n114: registerResource(name: string, uriOrTemplate: ResourceTemplate, config: ResourceMetadata, readCallback: ReadResourceTemplateCallback): RegisteredResourceTemplate;\n121: * @deprecated Use `registerTool` instead.\n126: * @deprecated Use `registerTool` instead.\n135: * @deprecated Use `registerTool` instead.\n145: * @deprecated Use `registerTool` instead.\n150: * @deprecated Use `registerTool` instead.\n155: * @deprecated Use `registerTool` instead.\n161: registerTool<OutputArgs extends ZodRawShapeCompat | AnySchema, InputArgs extends undefined | ZodRawShapeCompat | AnySchema = undefined>(name: string, config: {\n171: * @deprecated Use `registerPrompt` instead.\n176: * @deprecated Use `registerPrompt` instead.\n181: * @deprecated Use `registerPrompt` instead.\n186: * @deprecated Use `registerPrompt` instead.\n192: registerPrompt<Args extends PromptArgsRawShape>(name: string, config: {\n",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-adf0d26a69ddbc2f",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
SDK version 1.32.1. The keys of the exports map came back as undefined. That's because the exports map is probably using a different structure. Let me check the actual exports map. But the files exist under dist/esm. Let me check how exports is configured — maybe it's using wildcards or subpath patterns.
Let me take a look at the exports field.
SDK is v1.32.1 with registerTool/registerResource/registerPrompt. Let me check the exports map format and the exact registerTool signature.
cd /sandbox/repo && node --input-type=module -e "import fs from 'node:fs'; const p=JSON.parse(fs.readFileSync('node_modules/@modelcontextprotocol/sdk/package.json','utf8')); console.log(JSON.stringify(p.exports,null,1).slice(0,900));"{
{
".": {
"types": "./dist/esm/index.d.ts",
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js"
},
"./client": {
"types": "./dist/esm/client/index.d.ts",
"import": "./dist/esm/client/index.js",
"require": "./dist/cjs/client/index.js"
},
"./server": {
"types": "./dist/esm/server/index.d.ts",
"import": "./dist/esm/server/index.js",
"require": "./dist/cjs/server/index.js"
},
"./validation": {
"types": "./dist/esm/validation/index.d.ts",
"import": "./dist/esm/validation/index.js",
"require": "./dist/cjs/validation/index.js"
},
"./validation/ajv": {
"types": "./dist/esm/validation/ajv-provider.d.ts",
"import": "./dist/esm/validation/ajv-provider.js",
"require": "./dist/cjs/validation/ajv-provider.js"
},
"./validation/cfworker": {
"types": "./dist/esm/validation/cfworker-provider.d.ts",
"import": "./dist/esm/validation/cfworker-provider
Recorded tool call
{
"type": "tool_call",
"seq": 35,
"ts": "2026-10-22T04:00:31.177Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && node --input-type=module -e \"import fs from 'node:fs'; const p=JSON.parse(fs.readFileSync('node_modules/@modelcontextprotocol/sdk/package.json','utf8')); console.log(JSON.stringify(p.exports,null,1).slice(0,900));\""
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-9baa8306bc4ef1fd",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 38,
"ts": "2026-10-22T04:00:31.496Z",
"turn": 1,
"output": {
"content": [
{
"text": "{\n \".\": {\n \"types\": \"./dist/esm/index.d.ts\",\n \"import\": \"./dist/esm/index.js\",\n \"require\": \"./dist/cjs/index.js\"\n },\n \"./client\": {\n \"types\": \"./dist/esm/client/index.d.ts\",\n \"import\": \"./dist/esm/client/index.js\",\n \"require\": \"./dist/cjs/client/index.js\"\n },\n \"./server\": {\n \"types\": \"./dist/esm/server/index.d.ts\",\n \"import\": \"./dist/esm/server/index.js\",\n \"require\": \"./dist/cjs/server/index.js\"\n },\n \"./validation\": {\n \"types\": \"./dist/esm/validation/index.d.ts\",\n \"import\": \"./dist/esm/validation/index.js\",\n \"require\": \"./dist/cjs/validation/index.js\"\n },\n \"./validation/ajv\": {\n \"types\": \"./dist/esm/validation/ajv-provider.d.ts\",\n \"import\": \"./dist/esm/validation/ajv-provider.js\",\n \"require\": \"./dist/cjs/validation/ajv-provider.js\"\n },\n \"./validation/cfworker\": {\n \"types\": \"./dist/esm/validation/cfworker-provider.d.ts\",\n \"import\": \"./dist/esm/validation/cfworker-provider\n",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-9baa8306bc4ef1fd",
"is_error": false,
"tool_kind": "shell"
}✓read /sandbox/repo/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.d.ts✓└{"content":[{"text":" registerTool<OutputArgs extends ZodRawShapeCompat | AnySchema, InputArgs extends undefined | ZodRawShapeCompat | AnySchema = undefined>…ctrl+o
{"content":[{"text":" registerTool<OutputArgs extends ZodRawShapeCompat | AnySchema, InputArgs extends undefined | ZodRawShapeCompat | AnySchema = undefined>(name: string, config: {\n title?: string;\n description?: string;\n inputSchema?: InputArgs;\n outputSchema?: OutputArgs;\n annotations?: ToolAnnotations;\n _meta?: Record<string, unknown>;\n }, cb: ToolCallback<InputArgs>): RegisteredTool;\n /**\n * Registers a zero-argument prompt `name`, which will run the given function when the client calls it.\n * @deprecated Use `registerPrompt` instead.\n */\n prompt(name: string, cb: PromptCallback): RegisteredPrompt;\n /**\n * Registers a zero-argument prompt `name` (with a description) which will run the given function when the client calls it.\n * @deprecated Use `registerPrompt` instead.\n */\n prompt(name: string, description: string, cb: PromptCallback): RegisteredPrompt;\n /**\n * Registers a prompt `name` accepting the given arguments, which must be an object containing named properties associated with Zod schemas. When the client calls it, the function will be run with the parsed and validated arguments.\n\n[195 more lines in file. Use offset=181 to continue.]","type":"text"}]}›Thinking…
The export map uses subpath patterns. Let's check the wildcard patterns for ./*.js that map to ./dist/esm/*.js. I'll look at the rest of the exports.
Actually, the standard import paths like @modelcontextprotocol/sdk/server/mcp.js work through the wildcard exports. Let's check the wildcard.
cd /sandbox/repo && node --input-type=module -e "import fs from 'node:fs'; const p=JSON.parse(fs.readFileSync('node_modules/@modelcontextprotocol/sdk/package.json','utf8')); const keys=Object.keys(p.exports).filter(k=>k.includes('*')); for(const k of keys) console.log(k,'->',JSON.stringify(p.exports[k]));"./* -> {"types":"./dist/esm/*.d.ts","import":"./dist/esm/*","require":"./dist/cjs/*"}
./* -> {"types":"./dist/esm/*.d.ts","import":"./dist/esm/*","require":"./dist/cjs/*"}
Recorded tool call
{
"type": "tool_call",
"seq": 41,
"ts": "2026-10-22T04:00:33.161Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && node --input-type=module -e \"import fs from 'node:fs'; const p=JSON.parse(fs.readFileSync('node_modules/@modelcontextprotocol/sdk/package.json','utf8')); const keys=Object.keys(p.exports).filter(k=>k.includes('*')); for(const k of keys) console.log(k,'->',JSON.stringify(p.exports[k]));\""
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-8a0f8e1b5546a281",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 44,
"ts": "2026-10-22T04:00:33.469Z",
"turn": 1,
"output": {
"content": [
{
"text": "./* -> {\"types\":\"./dist/esm/*.d.ts\",\"import\":\"./dist/esm/*\",\"require\":\"./dist/cjs/*\"}\n",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-8a0f8e1b5546a281",
"is_error": false,
"tool_kind": "shell"
}cd /sandbox/repo && grep -nE "createLinkedPair" node_modules/@modelcontextprotocol/sdk/dist/esm/inMemory.d.ts
19: static createLinkedPair(): [InMemoryTransport, InMemoryTransport];
19: static createLinkedPair(): [InMemoryTransport, InMemoryTransport];
Recorded tool call
{
"type": "tool_call",
"seq": 42,
"ts": "2026-10-22T04:00:33.161Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && grep -nE \"createLinkedPair\" node_modules/@modelcontextprotocol/sdk/dist/esm/inMemory.d.ts"
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-ae2446141a696181",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 43,
"ts": "2026-10-22T04:00:33.161Z",
"turn": 1,
"output": {
"content": [
{
"text": "19: static createLinkedPair(): [InMemoryTransport, InMemoryTransport];\n",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-ae2446141a696181",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
The wildcard export ./* is mapped to ./dist/esm/*. Therefore @modelcontextprotocol/sdk/server/mcp.js resolves to ./dist/esm/server/mcp.js. Good. Also, @modelcontextprotocol/sdk/inMemory.js resolves to ./dist/esm/inMemory.js. And @modelcontextprotocol/sdk/client/index.js resolves to ./dist/esm/client/index.js.
InMemoryTransport.createLinkedPair() exists.
Now I have all the information I need. Let me write a self-contained example. I'll write it as ESM JavaScript so it can be run directly with node without a build step. Since package.json currently doesn't have "type": "module", I'll either add it or use .mjs. I'll use .mjs to keep it self-contained and not force module type on the whole package. Actually, adding "type": "module" is cleaner for a Node project. But .mjs is the lightest-weight option with the fewest side effects. Let me create the example as mcp-demo.mjs.
Let me design a good simple example that demonstrates MCP's core primitives:
- Tools — functions the client can call (e.g.,
add, and a mockget_weather) - Resources — data the client can read (e.g., a
greeting://resource) - Prompts — reusable prompt templates
What the client does:
- Connect via in-memory transport
- List tools, list resources, list prompts
- Call a tool
- Read a resource
- Get a prompt
- Print everything cleanly
- Close
This demonstrates a full round trip. Let me keep it "simple" but representative.
Let me also make the output clean and easy to read, with section headers.
Let me write the file. Use zod for input schemas. With zod v4, the SDK's registerTool inputSchema expects a ZodRawShape (an object of zod schemas). Let me use z.number(), z.string(), etc. In zod v4, z.number() and the like still exist. Good.
One consideration: registerTool with inputSchema — the callback receives parsed arguments. For outputSchema, if provided, structured content is validated. To keep it simple I'll omit outputSchema, but maybe demonstrate structured output on one tool. Let me keep it simple: text content only, plus maybe demonstrate structured content on the weather tool. Actually, to avoid the complexity of zod v4 structured output validation, I'll return text content and also return structuredContent matching the outputSchema on just one tool. Hmm, that adds risk. Let me keep it robust: return only text content. That's the safest and most clearly "simple".
Actually, let me demo one tool with structured output to show off a modern feature, but I need to be careful with zod v4. Risk: outputSchema validation of structuredContent. To keep the example reliable and truly "simple", I'll use text content for tool results. I can mention structured output as an option.
Alright, let me write it.
For resources: registerResource(name, uri, config, readCallback). The readCallback receives (uri, extra) and returns { contents: [{ uri, mimeType, text }] }.
For prompts: registerPrompt(name, config, callback). The config can include arguments. The callback returns { messages: [{ role, content: { type: "text", text } }] }.
Let me write the example.
Client methods:
client.listTools()->{ tools: [...] }client.listResources()->{ resources: [...] }client.listPrompts()->{ prompts: [...] }client.callTool({ name, arguments })->{ content: [...], isError? }client.readResource({ uri })->{ contents: [...] }client.getPrompt({ name, arguments })->{ messages: [...] }
Let me write it all up.
I'll also add capabilities. Does McpServer automatically enable capabilities based on registered items? Actually, the high-level McpServer registers capabilities automatically. Let me verify — the McpServer constructor takes options. It should set capabilities when tools/resources/prompts are registered. Actually, in recent SDKs, McpServer auto-declares capabilities. But to be safe, I can pass capabilities in the constructor. Hmm, actually does the high-level McpServer automatically handle capability negotiation via the underlying Server based on what's registered? Let me check.
Let me check the McpServer constructor and whether it sets capabilities. Let me see how server capabilities get enabled. Actually, when using registerTool and the like, I recall that the SDK's McpServer enables capabilities automatically. But there was a period where you had to pass { capabilities: { tools: {}, resources: {}, prompts: {} } }. Let me quickly verify to avoid the client's listTools returning empty due to capability mismatch.
Let me grep the mcp.js implementation for capabilities.
Wildcard export ./* → ./dist/esm/*, so @modelcontextprotocol/sdk/server/mcp.js, .../client/index.js, and .../inMemory.js all resolve. createLinkedPair() is present. Let me confirm whether McpServer auto-declares capabilities (so the client can list tools/resources/prompts).
cd /sandbox/repo && grep -nE "capabilities|tools: \{\}|resources: \{\}|prompts: \{\}|_registeredTools|oninitialized|assertCanSet" node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js | head -n 3058: this._registeredTools = {};
58: this._registeredTools = {};
100: this.server.assertCanSetRequestHandler(getMethodValue(ListToolsRequestSchema));
101: this.server.assertCanSetRequestHandler(getMethodValue(CallToolRequestSchema));
108: tools: Object.entries(this._registeredTools)
142: const tool = this._registeredTools[request.params.name];
319: this.server.assertCanSetRequestHandler(getMethodValue(CompleteRequestSchema));
380: this.server.assertCanSetRequestHandler(getMethodValue(ListResourcesRequestSchema));
381: this.server.assertCanSetRequestHandler(getMethodValue(ListResourceTemplatesRequestSchema));
382: this.server.assertCanSetRequestHandler(getMethodValue(ReadResourceRequestSchema));
445: this.server.assertCanSetRequestHandler(getMethodValue(ListPromptsRequestSchema));
446: this.server.assertCanSetRequestHandler(getMethodValue(GetPromptRequestSchema));
670: delete this._registeredTools[name];
672: this._registeredTools[updates.name] = registeredTool;
693: this._registeredTools[name] = registeredTool;
702: if (this._registeredTools[name]) {
744: if (this._registeredTools[name]) {
Recorded tool call
{
"type": "tool_call",
"seq": 48,
"ts": "2026-10-22T04:00:44.548Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && grep -nE \"capabilities|tools: \\{\\}|resources: \\{\\}|prompts: \\{\\}|_registeredTools|oninitialized|assertCanSet\" node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js | head -n 30"
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-8ad9f91c146b6698",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 49,
"ts": "2026-10-22T04:00:44.548Z",
"turn": 1,
"output": {
"content": [
{
"text": "58: this._registeredTools = {};\n100: this.server.assertCanSetRequestHandler(getMethodValue(ListToolsRequestSchema));\n101: this.server.assertCanSetRequestHandler(getMethodValue(CallToolRequestSchema));\n108: tools: Object.entries(this._registeredTools)\n142: const tool = this._registeredTools[request.params.name];\n319: this.server.assertCanSetRequestHandler(getMethodValue(CompleteRequestSchema));\n380: this.server.assertCanSetRequestHandler(getMethodValue(ListResourcesRequestSchema));\n381: this.server.assertCanSetRequestHandler(getMethodValue(ListResourceTemplatesRequestSchema));\n382: this.server.assertCanSetRequestHandler(getMethodValue(ReadResourceRequestSchema));\n445: this.server.assertCanSetRequestHandler(getMethodValue(ListPromptsRequestSchema));\n446: this.server.assertCanSetRequestHandler(getMethodValue(GetPromptRequestSchema));\n670: delete this._registeredTools[name];\n672: this._registeredTools[updates.name] = registeredTool;\n693: this._registeredTools[name] = registeredTool;\n702: if (this._registeredTools[name]) {\n744: if (this._registeredTools[name]) {\n",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-8ad9f91c146b6698",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Let's see how the server's capabilities are configured. I'll check the McpServer constructor.
✓read /sandbox/repo/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js✓└{"content":[{"text":" constructor(serverInfo, options) {\n this._registeredResources = {};\n this._registeredResourceTemplates = {};\n t…ctrl+o
{"content":[{"text":" constructor(serverInfo, options) {\n this._registeredResources = {};\n this._registeredResourceTemplates = {};\n this._registeredTools = {};\n this._registeredPrompts = {};\n this._toolHandlersInitialized = false;\n this._completionHandlerInitialized = false;\n this._resourceHandlersInitialized = false;\n this._promptHandlersInitialized = false;\n this.server = new Server(serverInfo, options);\n this._maxToolInputElements = resolveMaxToolInputElements(options?.maxToolInputElements);\n }\n /**\n * Access experimental features.\n *\n * WARNING: These APIs are experimental and may change without notice.\n *\n * @experimental\n */\n get experimental() {\n if (!this._experimental) {\n this._experimental = {\n tasks: new ExperimentalMcpServerTasks(this)\n };\n }\n return this._experimental;\n }\n /**\n * Attaches to the given transport, starts it, and starts listening for messages.\n *\n * The `server` object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward.\n */\n async connect(transport) {\n return await this.server.connect(transport);\n }\n /**\n * Closes the connection.\n */\n async close() {\n await this.server.close();\n }\n setToolRequestHandlers() {\n if (this._toolHandlersInitialized) {\n return;\n }\n\n[862 more lines in file. Use offset=100 to continue.]","type":"text"}]}›Thinking…
McpServer passes options to the underlying Server. Server uses options.capabilities. But the question is whether McpServer sets up request handlers and capabilities automatically. setToolRequestHandlers is called somewhere — probably lazily inside connect(), or at registration time. Let's check. assertCanSetRequestHandler throws an exception if the server is already connected. Handlers are set up in setToolRequestHandlers. When is that called?
Let's search for where setToolRequestHandlers/setResourceRequestHandlers/setPromptRequestHandlers are called.
cd /sandbox/repo && grep -nE "setToolRequestHandlers\(\)|setResourceRequestHandlers\(\)|setPromptRequestHandlers\(\)|this\.setRequestHandlers|_setRequestHandlers|registerCapabilities" node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
96: setToolRequestHandlers() {
96: setToolRequestHandlers() {
102: this.server.registerCapabilities({
320: this.server.registerCapabilities({
376: setResourceRequestHandlers() {
383: this.server.registerCapabilities({
441: setPromptRequestHandlers() {
447: this.server.registerCapabilities({
503: this.setResourceRequestHandlers();
512: this.setResourceRequestHandlers();
523: this.setResourceRequestHandlers();
532: this.setResourceRequestHandlers();
694: this.setToolRequestHandlers();
764: this.setPromptRequestHandlers();
777: this.setPromptRequestHandlers();
Recorded tool call
{
"type": "tool_call",
"seq": 56,
"ts": "2026-10-22T04:00:49.491Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && grep -nE \"setToolRequestHandlers\\(\\)|setResourceRequestHandlers\\(\\)|setPromptRequestHandlers\\(\\)|this\\.setRequestHandlers|_setRequestHandlers|registerCapabilities\" node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js"
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-a45d761fd14dee9c",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 58,
"ts": "2026-10-22T04:00:49.491Z",
"turn": 1,
"output": {
"content": [
{
"text": "96: setToolRequestHandlers() {\n102: this.server.registerCapabilities({\n320: this.server.registerCapabilities({\n376: setResourceRequestHandlers() {\n383: this.server.registerCapabilities({\n441: setPromptRequestHandlers() {\n447: this.server.registerCapabilities({\n503: this.setResourceRequestHandlers();\n512: this.setResourceRequestHandlers();\n523: this.setResourceRequestHandlers();\n532: this.setResourceRequestHandlers();\n694: this.setToolRequestHandlers();\n764: this.setPromptRequestHandlers();\n777: this.setPromptRequestHandlers();\n",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-a45d761fd14dee9c",
"is_error": false,
"tool_kind": "shell"
}cd /sandbox/repo && grep -nE "registerTool\(|setToolRequestHandlers|setResourceRequestHandlers|setPromptRequestHandlers|registerResource\(|registerPrompt\(" node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js | sed -n '1,40p'96: setToolRequestHandlers() {
96: setToolRequestHandlers() {
376: setResourceRequestHandlers() {
441: setPromptRequestHandlers() {
503: this.setResourceRequestHandlers();
512: this.setResourceRequestHandlers();
517: registerResource(name, uriOrTemplate, config, readCallback) {
523: this.setResourceRequestHandlers();
532: this.setResourceRequestHandlers();
694: this.setToolRequestHandlers();
743: registerTool(name, config, cb) {
764: this.setPromptRequestHandlers();
771: registerPrompt(name, config, cb) {
777: this.setPromptRequestHandlers();
Recorded tool call
{
"type": "tool_call",
"seq": 57,
"ts": "2026-10-22T04:00:49.491Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && grep -nE \"registerTool\\(|setToolRequestHandlers|setResourceRequestHandlers|setPromptRequestHandlers|registerResource\\(|registerPrompt\\(\" node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js | sed -n '1,40p'"
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-a69928ef4c90cae5",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 59,
"ts": "2026-10-22T04:00:49.491Z",
"turn": 1,
"output": {
"content": [
{
"text": "96: setToolRequestHandlers() {\n376: setResourceRequestHandlers() {\n441: setPromptRequestHandlers() {\n503: this.setResourceRequestHandlers();\n512: this.setResourceRequestHandlers();\n517: registerResource(name, uriOrTemplate, config, readCallback) {\n523: this.setResourceRequestHandlers();\n532: this.setResourceRequestHandlers();\n694: this.setToolRequestHandlers();\n743: registerTool(name, config, cb) {\n764: this.setPromptRequestHandlers();\n771: registerPrompt(name, config, cb) {\n777: this.setPromptRequestHandlers();\n",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-a69928ef4c90cae5",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
In other words, registerTool calls setToolRequestHandlers(), which in turn calls this.server.registerCapabilities({ tools: {...} }). Similarly, registerResource → setResourceRequestHandlers → registerCapabilities({resources}), and registerPrompt → setPromptRequestHandlers → registerCapabilities({prompts}). So, capabilities are automatically registered upon registration.
This means you don't need to pass capabilities manually. registerCapabilities needs to be called before connect (because assertCanSetRequestHandler throws an exception if already connected). Since you register before connect, there's no problem.
Now I'm confident. Let's write the example.
Alright, let me write the demo file. I'll make it clean, well-commented, and demonstrate tools, resources, prompts, plus the client's round-trip.
I'll write mcp-demo.mjs.
Confirmed: registerTool/registerResource/registerPrompt each auto-call registerCapabilities, so capabilities are declared automatically as long as I register before connect(). No manual capability wiring needed.
Now I'll write a self-contained, one-shot example (server + client over in-memory transport — no separate process, no ports).
File edits
1 file
Writtenmcp-demo.mjsView changes
/sandbox/repo/mcp-demo.mjs
Written content · previous contents were not recorded
// A minimal, self-contained Model Context Protocol (MCP) example.//// What makes this "light":// * No local service stack (no Docker, no separate server process, no ports).// * No long-running commands: it connects, does a few round-trips, prints, and exits.// * Uses the official MCP TypeScript SDK's in-memory transport, so the client// and server live in the SAME process and talk over an in-memory pipe.//// It demonstrates the three core MCP primitives:// 1. Tools -> functions the client can discover and call// 2. Resources -> read-only data the client can fetch by URI// 3. Prompts -> reusable, parameterized prompt templates//// Run: node mcp-demo.mjsimport { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { Client } from "@modelcontextprotocol/sdk/client/index.js";import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";import { z } from "zod";const line = (t = "") => console.log(t);const rule = (t) => line(`\n── ${t} ${"─".repeat(Math.max(0, 56 - t.length))}`);// ─────────────────────────────────────────────────────────────────────────────// 1) SERVER: define tools, a resource, and a prompt.// Registering these auto-declares the server's capabilities.// ─────────────────────────────────────────────────────────────────────────────const server = new McpServer({ name: "demo-server", version: "1.0.0" },{ capabilities: {} } // filled in automatically by the register* calls below);// TOOL: add two numbers.server.registerTool("add",{title: "Addition",description: "Add two numbers and return the sum.",inputSchema: { a: z.number(), b: z.number() },},async ({ a, b }) => ({content: [{ type: "text", text: `${a} + ${b} = ${a + b}` }],}));// TOOL: a mock "weather" lookup (no network — returns canned data), and shows// how a tool can signal a business error via `isError`.server.registerTool("get_weather",{title: "Get weather",description: "Get current weather for a city (mock data).",inputSchema: { city: z.string().min(1) },},async ({ city }) => {const known = {"san francisco": "16°C, foggy",tokyo: "22°C, clear",london: "11°C, rainy",};const key = city.trim().toLowerCase();if (!known[key]) {return {isError: true,content: [{ type: "text", text: `No weather data for "${city}".` }],};}return {content: [{ type: "text", text: `${city}: ${known[key]}` }],};});// RESOURCE: a static, read-only document addressed by a URI.server.registerResource("readme","docs://readme",{title: "README",description: "A short project blurb exposed as an MCP resource.",mimeType: "text/plain",},async (uri) => ({contents: [{uri: uri.href,mimeType: "text/plain",text: "Demo project: an MCP server wired to a client over in-memory transport.",},],}));// PROMPT: a reusable, parameterized prompt template.server.registerPrompt("summarize",{title: "Summarize text",description: "Build a prompt that asks for a concise summary.",argsSchema: { text: z.string().min(1) },},({ text }) => ({messages: [{role: "user",content: {type: "text",text: `Please summarize the following in two bullet points:\n\n${text}`,},},],}));// ─────────────────────────────────────────────────────────────────────────────// 2) CLIENT + in-memory transport: link client<->server in one process.// ─────────────────────────────────────────────────────────────────────────────const client = new Client({ name: "demo-client", version: "1.0.0" });// createLinkedPair() returns [clientTransport, serverTransport] that are// already connected to each other in memory.const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();// Connect both ends concurrently, then do the round-trips.await Promise.all([server.connect(serverTransport),client.connect(clientTransport),]);try {rule("Negotiated server info & capabilities");const serverVersion = client.getServerVersion();const serverCaps = client.getServerCapabilities();line(`server: ${serverVersion?.name}@${serverVersion?.version}`);line(`capabilities: ${Object.keys(serverCaps ?? {}).join(", ")}`);// ── Discovery ──────────────────────────────────────────────────────────────rule("Discovery: listTools");const { tools } = await client.listTools();for (const t of tools) line(`• ${t.name} — ${t.description ?? ""}`);rule("Discovery: listResources");const { resources } = await client.listResources();for (const r of resources) line(`• ${r.uri} — ${r.description ?? ""}`);rule("Discovery: listPrompts");const { prompts } = await client.listPrompts();for (const p of prompts) line(`• ${p.name} — ${p.description ?? ""}`);// ── Invocation ─────────────────────────────────────────────────────────────rule("Call tool: add(2, 3)");const addRes = await client.callTool({ name: "add", arguments: { a: 2, b: 3 } });line(addRes.content.map((c) => c.text).join("\n"));rule("Call tool: get_weather('Tokyo')");const wxRes = await client.callTool({name: "get_weather",arguments: { city: "Tokyo" },});line(wxRes.content.map((c) => c.text).join("\n"));rule("Call tool: get_weather('Atlantis') -> business error");const wxErr = await client.callTool({name: "get_weather",arguments: { city: "Atlantis" },});line(`isError=${!!wxErr.isError} :: ${wxErr.content.map((c) => c.text).join("\n")}`);rule("Read resource: docs://readme");const res = await client.readResource({ uri: "docs://readme" });line(res.contents.map((c) => `[${c.mimeType}] ${c.text}`).join("\n"));rule("Get prompt: summarize(...)");const prompt = await client.getPrompt({name: "summarize",arguments: { text: "MCP standardizes how apps give context to LLMs." },});line(prompt.messages.map((m) => `${m.role}: ${m.content.text}`).join("\n"));rule("Done");line("All round-trips completed in-process; no external services were used.");} finally {// Always tear down so the process exits cleanly (no hanging handles).await client.close();await server.close();}
Source: write · chatcmpl-tool-87e19b131c548b4c
Recorded tool call
{
"type": "tool_call",
"seq": 63,
"ts": "2026-10-22T04:01:07.057Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/mcp-demo.mjs",
"content": "// A minimal, self-contained Model Context Protocol (MCP) example.\n//\n// What makes this \"light\":\n// * No local service stack (no Docker, no separate server process, no ports).\n// * No long-running commands: it connects, does a few round-trips, prints, and exits.\n// * Uses the official MCP TypeScript SDK's in-memory transport, so the client\n// and server live in the SAME process and talk over an in-memory pipe.\n//\n// It demonstrates the three core MCP primitives:\n// 1. Tools -> functions the client can discover and call\n// 2. Resources -> read-only data the client can fetch by URI\n// 3. Prompts -> reusable, parameterized prompt templates\n//\n// Run: node mcp-demo.mjs\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { InMemoryTransport } from \"@modelcontextprotocol/sdk/inMemory.js\";\nimport { z } from \"zod\";\n\nconst line = (t = \"\") => console.log(t);\nconst rule = (t) => line(`\\n── ${t} ${\"─\".repeat(Math.max(0, 56 - t.length))}`);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// 1) SERVER: define tools, a resource, and a prompt.\n// Registering these auto-declares the server's capabilities.\n// ─────────────────────────────────────────────────────────────────────────────\nconst server = new McpServer(\n { name: \"demo-server\", version: \"1.0.0\" },\n { capabilities: {} } // filled in automatically by the register* calls below\n);\n\n// TOOL: add two numbers.\nserver.registerTool(\n \"add\",\n {\n title: \"Addition\",\n description: \"Add two numbers and return the sum.\",\n inputSchema: { a: z.number(), b: z.number() },\n },\n async ({ a, b }) => ({\n content: [{ type: \"text\", text: `${a} + ${b} = ${a + b}` }],\n })\n);\n\n// TOOL: a mock \"weather\" lookup (no network — returns canned data), and shows\n// how a tool can signal a business error via `isError`.\nserver.registerTool(\n \"get_weather\",\n {\n title: \"Get weather\",\n description: \"Get current weather for a city (mock data).\",\n inputSchema: { city: z.string().min(1) },\n },\n async ({ city }) => {\n const known = {\n \"san francisco\": \"16°C, foggy\",\n tokyo: \"22°C, clear\",\n london: \"11°C, rainy\",\n };\n const key = city.trim().toLowerCase();\n if (!known[key]) {\n return {\n isError: true,\n content: [{ type: \"text\", text: `No weather data for \"${city}\".` }],\n };\n }\n return {\n content: [{ type: \"text\", text: `${city}: ${known[key]}` }],\n };\n }\n);\n\n// RESOURCE: a static, read-only document addressed by a URI.\nserver.registerResource(\n \"readme\",\n \"docs://readme\",\n {\n title: \"README\",\n description: \"A short project blurb exposed as an MCP resource.\",\n mimeType: \"text/plain\",\n },\n async (uri) => ({\n contents: [\n {\n uri: uri.href,\n mimeType: \"text/plain\",\n text: \"Demo project: an MCP server wired to a client over in-memory transport.\",\n },\n ],\n })\n);\n\n// PROMPT: a reusable, parameterized prompt template.\nserver.registerPrompt(\n \"summarize\",\n {\n title: \"Summarize text\",\n description: \"Build a prompt that asks for a concise summary.\",\n argsSchema: { text: z.string().min(1) },\n },\n ({ text }) => ({\n messages: [\n {\n role: \"user\",\n content: {\n type: \"text\",\n text: `Please summarize the following in two bullet points:\\n\\n${text}`,\n },\n },\n ],\n })\n);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// 2) CLIENT + in-memory transport: link client<->server in one process.\n// ─────────────────────────────────────────────────────────────────────────────\nconst client = new Client({ name: \"demo-client\", version: \"1.0.0\" });\n\n// createLinkedPair() returns [clientTransport, serverTransport] that are\n// already connected to each other in memory.\nconst [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();\n\n// Connect both ends concurrently, then do the round-trips.\nawait Promise.all([\n server.connect(serverTransport),\n client.connect(clientTransport),\n]);\n\ntry {\n rule(\"Negotiated server info & capabilities\");\n const serverVersion = client.getServerVersion();\n const serverCaps = client.getServerCapabilities();\n line(`server: ${serverVersion?.name}@${serverVersion?.version}`);\n line(`capabilities: ${Object.keys(serverCaps ?? {}).join(\", \")}`);\n\n // ── Discovery ──────────────────────────────────────────────────────────────\n rule(\"Discovery: listTools\");\n const { tools } = await client.listTools();\n for (const t of tools) line(`• ${t.name} — ${t.description ?? \"\"}`);\n\n rule(\"Discovery: listResources\");\n const { resources } = await client.listResources();\n for (const r of resources) line(`• ${r.uri} — ${r.description ?? \"\"}`);\n\n rule(\"Discovery: listPrompts\");\n const { prompts } = await client.listPrompts();\n for (const p of prompts) line(`• ${p.name} — ${p.description ?? \"\"}`);\n\n // ── Invocation ─────────────────────────────────────────────────────────────\n rule(\"Call tool: add(2, 3)\");\n const addRes = await client.callTool({ name: \"add\", arguments: { a: 2, b: 3 } });\n line(addRes.content.map((c) => c.text).join(\"\\n\"));\n\n rule(\"Call tool: get_weather('Tokyo')\");\n const wxRes = await client.callTool({\n name: \"get_weather\",\n arguments: { city: \"Tokyo\" },\n });\n line(wxRes.content.map((c) => c.text).join(\"\\n\"));\n\n rule(\"Call tool: get_weather('Atlantis') -> business error\");\n const wxErr = await client.callTool({\n name: \"get_weather\",\n arguments: { city: \"Atlantis\" },\n });\n line(`isError=${!!wxErr.isError} :: ${wxErr.content.map((c) => c.text).join(\"\\n\")}`);\n\n rule(\"Read resource: docs://readme\");\n const res = await client.readResource({ uri: \"docs://readme\" });\n line(res.contents.map((c) => `[${c.mimeType}] ${c.text}`).join(\"\\n\"));\n\n rule(\"Get prompt: summarize(...)\");\n const prompt = await client.getPrompt({\n name: \"summarize\",\n arguments: { text: \"MCP standardizes how apps give context to LLMs.\" },\n });\n line(prompt.messages.map((m) => `${m.role}: ${m.content.text}`).join(\"\\n\"));\n\n rule(\"Done\");\n line(\"All round-trips completed in-process; no external services were used.\");\n} finally {\n // Always tear down so the process exits cleanly (no hanging handles).\n await client.close();\n await server.close();\n}\n"
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-87e19b131c548b4c",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 64,
"ts": "2026-10-22T04:01:07.057Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 6385 bytes to /sandbox/repo/mcp-demo.mjs",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-87e19b131c548b4c",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Alright, let's run it. This is a one-shot script that should execute and terminate. It shouldn't take long. Let me set a modest timeout to prevent hangs.
Now let me run it. This is a one-shot script — it should print and exit on its own.
cd /sandbox/repo && time node mcp-demo.mjs 2>&1
── Negotiated server info & capabilities ───────────────────
── Negotiated server info & capabilities ───────────────────
server: demo-server@1.0.0
capabilities: prompts, resources, tools
── Discovery: listTools ────────────────────────────────────
• add — Add two numbers and return the sum.
• get_weather — Get current weather for a city (mock data).
── Discovery: listResources ────────────────────────────────
• docs://readme — A short project blurb exposed as an MCP resource.
── Discovery: listPrompts ──────────────────────────────────
• summarize — Build a prompt that asks for a concise summary.
── Call tool: add(2, 3) ────────────────────────────────────
2 + 3 = 5
── Call tool: get_weather('Tokyo') ─────────────────────────
Tokyo: 22°C, clear
── Call tool: get_weather('Atlantis') -> business error ────
isError=true :: No weather data for "Atlantis".
── Read resource: docs://readme ────────────────────────────
[text/plain] Demo project: an MCP server wired to a client over in-memory transport.
── Get prompt: summarize(...) ──────────────────────────────
user: Please summarize the following in two bullet points:
MCP standardizes how apps give context to LLMs.
── Done ────────────────────────────────────────────────────
All round-trips completed in-process; no external services were used.
real 0m0.300s
user 0m0.273s
sys 0m0.102s
Recorded tool call
{
"type": "tool_call",
"seq": 68,
"ts": "2026-10-22T04:01:08.747Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && time node mcp-demo.mjs 2>&1",
"timeout": 30
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-8ced20fc59e757b0",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 69,
"ts": "2026-10-22T04:01:09.052Z",
"turn": 1,
"output": {
"content": [
{
"text": "\n── Negotiated server info & capabilities ───────────────────\nserver: demo-server@1.0.0\ncapabilities: prompts, resources, tools\n\n── Discovery: listTools ────────────────────────────────────\n• add — Add two numbers and return the sum.\n• get_weather — Get current weather for a city (mock data).\n\n── Discovery: listResources ────────────────────────────────\n• docs://readme — A short project blurb exposed as an MCP resource.\n\n── Discovery: listPrompts ──────────────────────────────────\n• summarize — Build a prompt that asks for a concise summary.\n\n── Call tool: add(2, 3) ────────────────────────────────────\n2 + 3 = 5\n\n── Call tool: get_weather('Tokyo') ─────────────────────────\nTokyo: 22°C, clear\n\n── Call tool: get_weather('Atlantis') -> business error ────\nisError=true :: No weather data for \"Atlantis\".\n\n── Read resource: docs://readme ────────────────────────────\n[text/plain] Demo project: an MCP server wired to a client over in-memory transport.\n\n── Get prompt: summarize(...) ──────────────────────────────\nuser: Please summarize the following in two bullet points:\n\nMCP standardizes how apps give context to LLMs.\n\n── Done ────────────────────────────────────────────────────\nAll round-trips completed in-process; no external services were used.\n\nreal\t0m0.300s\nuser\t0m0.273s\nsys\t0m0.102s\n",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-8ced20fc59e757b0",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
It ran and finished in 0.3 seconds. All round trips succeeded.
Alright, let me update package.json to make it a proper module and add a start/demo script. Let me set "type": "module" — wait, but the file is .mjs so that's not necessary. However, adding an npm script would make it easier to manage. Let me add a script. Actually, let me check the generated package.json.
Let me add the script and tidy up package.json a bit. Let me read it.
It ran end-to-end in 0.3s and exited cleanly — no hanging process, no external services. Let me tidy up package.json (add a run script, fix metadata) and verify final repo state.
✓read /sandbox/repo/package.json✓└{"content":[{"text":"{\n \"name\": \"repo\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"test\": \…ctrl+o
{"content":[{"text":"{\n \"name\": \"repo\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n },\n \"keywords\": [],\n \"author\": \"\",\n \"license\": \"ISC\",\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"^1.32.1\",\n \"zod\": \"^4.6.7\"\n }\n}\n","type":"text"}]}›Thinking…
Let's update package.json to add an appropriate demo script and clean it up. Remove the default test script and set a name/description. The example is .mjs, but if we set "type": "module", it'll also work fine with future .js files. That said, there's no harm in adding "type": "module", and .mjs will continue to work. Actually, let's keep it minimal and not force the module type — the .mjs is self-contained. However, to make npm run demo work, we just need to add the script.
Let's edit package.json.
File edits
1 file
Modifiedpackage.json+10−7View changes
/sandbox/repo/package.json
Replacement excerpt
- "name": "repo",+ "name": "mcp-inmemory-demo","version": "1.0.0",- "description": "",- "main": "index.js",+ "description": "Minimal, self-contained Model Context Protocol example using the in-memory transport (no external services).",+ "type": "module",+ "main": "mcp-demo.mjs","scripts": {- "test": "echo \"Error: no test specified\" && exit 1"+ "demo": "node mcp-demo.mjs"},- "keywords": [],- "author": "",- "license": "ISC",+ "keywords": [+ "mcp",+ "model-context-protocol"+ ],+ "license": "MIT",
Source: edit · chatcmpl-tool-8d5e0b1efdc35c09
Recorded tool call
{
"type": "tool_call",
"seq": 77,
"ts": "2026-10-22T04:01:15.771Z",
"name": "edit",
"turn": 1,
"input": {
"path": "/sandbox/repo/package.json",
"edits": [
{
"newText": " \"name\": \"mcp-inmemory-demo\",\n \"version\": \"1.0.0\",\n \"description\": \"Minimal, self-contained Model Context Protocol example using the in-memory transport (no external services).\",\n \"type\": \"module\",\n \"main\": \"mcp-demo.mjs\",\n \"scripts\": {\n \"demo\": \"node mcp-demo.mjs\"\n },\n \"keywords\": [\n \"mcp\",\n \"model-context-protocol\"\n ],\n \"license\": \"MIT\",",
"oldText": " \"name\": \"repo\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n },\n \"keywords\": [],\n \"author\": \"\",\n \"license\": \"ISC\","
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-8d5e0b1efdc35c09",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 78,
"ts": "2026-10-22T04:01:15.771Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully replaced 1 block(s) in /sandbox/repo/package.json.",
"type": "text"
}
],
"details": {
"diff": " 1 {\n- 2 \"name\": \"repo\",\n+ 2 \"name\": \"mcp-inmemory-demo\",\n 3 \"version\": \"1.0.0\",\n- 4 \"description\": \"\",\n- 5 \"main\": \"index.js\",\n+ 4 \"description\": \"Minimal, self-contained Model Context Protocol example using the in-memory transport (no external services).\",\n+ 5 \"type\": \"module\",\n+ 6 \"main\": \"mcp-demo.mjs\",\n 6 \"scripts\": {\n- 7 \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n+ 8 \"demo\": \"node mcp-demo.mjs\"\n 8 },\n- 9 \"keywords\": [],\n-10 \"author\": \"\",\n-11 \"license\": \"ISC\",\n+10 \"keywords\": [\n+11 \"mcp\",\n+12 \"model-context-protocol\"\n+13 ],\n+14 \"license\": \"MIT\",\n 12 \"dependencies\": {\n 13 \"@modelcontextprotocol/sdk\": \"^1.32.1\",\n 14 \"zod\": \"^4.6.7\"\n 15 }\n ...",
"patch": "--- /sandbox/repo/package.json\n+++ /sandbox/repo/package.json\n@@ -1,15 +1,18 @@\n {\n- \"name\": \"repo\",\n+ \"name\": \"mcp-inmemory-demo\",\n \"version\": \"1.0.0\",\n- \"description\": \"\",\n- \"main\": \"index.js\",\n+ \"description\": \"Minimal, self-contained Model Context Protocol example using the in-memory transport (no external services).\",\n+ \"type\": \"module\",\n+ \"main\": \"mcp-demo.mjs\",\n \"scripts\": {\n- \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n+ \"demo\": \"node mcp-demo.mjs\"\n },\n- \"keywords\": [],\n- \"author\": \"\",\n- \"license\": \"ISC\",\n+ \"keywords\": [\n+ \"mcp\",\n+ \"model-context-protocol\"\n+ ],\n+ \"license\": \"MIT\",\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"^1.32.1\",\n \"zod\": \"^4.6.7\"\n }\n",
"firstChangedLine": 2
}
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-8d5e0b1efdc35c09",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Alright, let's verify that npm run demo works and check the final state. Also, let's confirm that node_modules is included in .gitignore (it is). Let's run npm run demo to make sure the script works, and display the final tree (excluding node_modules).
cd /sandbox/repo && npm run demo 2>&1 | head -n 6 && echo "..." && echo "---tracked-relevant files (node_modules ignored)---" && git status --porcelain && echo "---" && ls -1 *.mjs package.json package-lock.json 2>/dev/null
> mcp-inmemory-demo@1.0.0 demo
> mcp-inmemory-demo@1.0.0 demo > node mcp-demo.mjs ── Negotiated server info & capabilities ─────────────────── ... ---tracked-relevant files (node_modules ignored)--- ?? mcp-demo.mjs ?? package-lock.json ?? package.json --- mcp-demo.mjs package-lock.json package.json
Recorded tool call
{
"type": "tool_call",
"seq": 81,
"ts": "2026-10-22T04:01:17.314Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && npm run demo 2>&1 | head -n 6 && echo \"...\" && echo \"---tracked-relevant files (node_modules ignored)---\" && git status --porcelain && echo \"---\" && ls -1 *.mjs package.json package-lock.json 2>/dev/null"
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-9b020742c7c12f14",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 82,
"ts": "2026-10-22T04:01:17.612Z",
"turn": 1,
"output": {
"content": [
{
"text": "\n> mcp-inmemory-demo@1.0.0 demo\n> node mcp-demo.mjs\n\n\n── Negotiated server info & capabilities ───────────────────\n...\n---tracked-relevant files (node_modules ignored)---\n?? mcp-demo.mjs\n?? package-lock.json\n?? package.json\n---\nmcp-demo.mjs\npackage-lock.json\npackage.json\n",
"type": "text"
}
]
},
"run_id": "cmvj0ilm800900jusqbtrnwy2",
"tool_id": "chatcmpl-tool-9b020742c7c12f14",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Everything works. npm run demo runs the script. The files are in place. node_modules is gitignored.
Now let me write the final response. What needs to be done:
- Confirm what was built and how to run it.
- Explain how pricing works (MCP is a free/open protocol. The SDK is MIT-licensed. No hosting costs for the protocol itself).
- Brief evaluation: is this product easy to manage?
- Mention that I'm not blocked.
- Confirm that I followed the "lightweight" constraint (no local stack, no long-running commands).
Let me be concise but complete. The user asked three specific things:
- Create a simple MCP example ✓
- How pricing works
- Briefly: is this product easy to manage
- Let them know if blocked / if there's no developer workflow
Let me address the "hosted product" nuance: MCP itself is not a hosted product—it's an open protocol + free SDK. There's no pricing for that. If you wanted a hosted MCP server, you'd pay whatever the hosting provider charges, but that's out of scope, and I intentionally didn't spin that up (that would be a "local service stack"/long-running thing, which is prohibited). I used the in-memory transport instead.
Let me write the final summary. Keep it concise with file paths clearly shown.
Let me note the manageability evaluation: Easy to manage—a single dependency, a one-shot script, deterministic output, no infrastructure required. Easy to extend (add tools/resources/prompts). Caveat: the in-memory transport is for demo/testing. Actual deployments use stdio or Streamable HTTP/SSE transports, which are also simple but involve running a server process (that's the "real" quickstart people usually see—the one that requires long-running processes. I avoided that).
Let me finalize.
Done. Everything works and I wasn't blocked.
What I built
A single self-contained script — /sandbox/repo/mcp-demo.mjs — that runs an MCP server and client in the same process, linked by the SDK's in-memory transport. Run it with:
npm run demo # or: node mcp-demo.mjs
It exercises all three core MCP primitives and exits in ~0.3s:
- Tools —
add(2,3)→5, andget_weather(including a business-error path viaisError) - Resources — reads
docs://readme - Prompts — a parameterized
summarizetemplate
It also shows capability negotiation (capabilities: prompts, resources, tools) and clean teardown. Deps: just @modelcontextprotocol/sdk + zod.
Respecting your "stay light" rule
The standard MCP quickstart tells you to run a server as a long-running process in one terminal and a client in another (stdio/HTTP). That would break your "no local stacks / no long-running commands" rule — so I deliberately did not do that. Instead I used the SDK's InMemoryTransport.createLinkedPair(): one-shot, no ports, no Docker, no background process. The only network call was an 8-second npm install.
How pricing works
- MCP itself is free. It's an open protocol (spec + SDKs are MIT-licensed). No license fee, no per-call/per-seat cost, no account.
- The SDKs (
@modelcontextprotocol/sdk, Pythonmcp) are free open-source packages. - There is no "hosted MCP product" from the protocol to bill you. Costs only appear if you choose to host a server somewhere (you'd pay that host's normal compute fees) or if you connect to a third-party's paid MCP server — both out of scope here, and I didn't stand any of that up.
Will it be easy for me to manage?
Yes. One dependency, a deterministic one-shot script, no infrastructure to babysit, and extending it is trivial (each registerTool/registerResource/registerPrompt auto-declares its capability). The one caveat: in-memory transport is for demos/tests — a real deployment swaps in the stdio or Streamable-HTTP transport, which means running an actual server process (the long-running thing I intentionally avoided here). If you later want that "real" server version, say so and I'll note it needs a background process before proceeding.
No blockers.
AX Fundamentals
23 checks. Open an area for details.
This check is a quick preview. Test more tasks and models with Gauge Agents.
Test your product with Gauge Agents