MCP server with in-memory tools: working versions (tested 2026-10-17)
Build an McpServer from @modelcontextprotocol/sdk that registers two zod-typed tools and one resource, then exercise it offline through a Client linked by InMemoryTransport.createLinkedPair().
passed4 passed · 0 failed2026-10-17 05:58 UTC- Status
- passed
- Last passed
- 2026-10-17 05:58 UTC
- Tests
- 4 / 4
- Language
- TypeScript
- Runtime
- node v22.23.3
npm install --no-audit --no-fund --ignore-scripts=false npm test
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 39008163431
The code
Exactly the files that passed. Your agent can pull them with get_recipe("mcp-sdk-inmemory-tools") (install), or see them on GitHub.
{
"name": "mcp-sdk-inmemory-tools",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.32.1",
"zod": "4.6.7"
},
"devDependencies": {
"@types/node": "26.6.5",
"tsx": "4.23.15"
}
}
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export function createServer(): McpServer {
const server = new McpServer({ name: "inmemory-tools", version: "1.0.0" });
server.registerTool(
"add",
{
title: "Add",
description: "Add two numbers together",
inputSchema: { a: z.number(), b: z.number() },
},
async ({ a, b }) => ({
content: [{ type: "text", text: `${a + b}` }],
}),
);
server.registerTool(
"greet",
{
title: "Greet",
description: "Greet a person by name",
inputSchema: { name: z.string() },
},
async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name}!` }],
}),
);
server.registerResource(
"greeting",
"greeting://hello",
{
title: "Greeting",
description: "A static greeting resource",
mimeType: "text/plain",
},
async (uri) => ({
contents: [{ uri: uri.href, text: "Hello from the resource" }],
}),
);
return server;
}
import { test } from "node:test";
import assert from "node:assert/strict";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { createServer } from "../src/server.ts";
async function connect() {
const server = createServer();
const [clientTransport, serverTransport] =
InMemoryTransport.createLinkedPair();
const client = new Client({ name: "test-client", version: "1.0.0" });
await server.connect(serverTransport);
await client.connect(clientTransport);
return { client, server };
}
test("lists the two registered tools", async () => {
const { client, server } = await connect();
const { tools } = await client.listTools();
const names = tools.map((tool) => tool.name).sort();
assert.deepEqual(names, ["add", "greet"]);
await client.close();
await server.close();
});
test("callTool returns text content", async () => {
const { client, server } = await connect();
const result = await client.callTool({
name: "add",
arguments: { a: 2, b: 3 },
});
assert.ok(!result.isError);
const [first] = result.content as Array<{ type: string; text: string }>;
assert.equal(first.type, "text");
assert.equal(first.text, "5");
await client.close();
await server.close();
});
test("readResource returns the registered resource", async () => {
const { client, server } = await connect();
const result = await client.readResource({ uri: "greeting://hello" });
const [first] = result.contents as Array<{ uri: string; text?: string }>;
assert.equal(first.uri, "greeting://hello");
assert.equal(first.text, "Hello from the resource");
await client.close();
await server.close();
});
test("callTool reports an error result for invalid input", async () => {
const { client, server } = await connect();
const result = await client.callTool({
name: "greet",
arguments: { name: 123 },
});
assert.equal(result.isError, true);
await client.close();
await server.close();
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
Anthropic TypeScript SDK tool use loop, tested with a mocked fetch
Run a Claude tool_use loop with the official Anthropic SDK and Zod-validated tool inputs, tested offline with an injected fetch.
better-sqlite3 transactions with Zod-validated inputs
A small ledger on better-sqlite3 using db.transaction for atomic transfers, nested transactions as savepoints for all-or-nothing batches, and Zod 4 to validate inputs and rows, tested on in-memory SQLite.
Cheerio HTML scraping with Zod-validated records and saved fixtures
Scrape a product listing and a spec table with Cheerio, normalise prices and relative URLs, and validate every record with Zod 4 so broken cards are reported, tested offline against saved HTML fixtures.
Commander CLI with Zod option validation, tested in-process
A Commander subcommand CLI with variadic arguments, repeatable options, choices and Zod-backed argument parsers, made testable with exitOverride and configureOutput so node:test runs it in-process without spawning.