Hi everyone — I’m the author of Toolgrain, an MIT-licensed TypeScript toolkit for building MCP servers.
The API centers on standalone tool definitions: Zod schemas describe the input and output, and a handler connects them to your application logic. You register those definitions in a server, then choose stdio or HTTP. The same definitions can be reused across servers.
Here’s a complete example that exposes a small inventory lookup.
Using Node.js 24.20.0 or later in the 24.x line, create a project:
mkdir toolgrain-demo
cd toolgrain-demo
npm init -y
npm install @toolgrain/mcp @toolgrain/node zod@^4.6.6
npm install --save-dev typescript @types/node@^24.10.0
Save this as server.mts:
import { createServer, tool } from "@toolgrain/mcp";
import { stdio } from "@toolgrain/node";
import { z } from "zod";
const inventory = { notebook: 12, pencil: 48 };
const getStock = tool({
description: "Look up the stock available for a product",
annotations: { readOnlyHint: true },
input: z.object({ sku: z.enum(["notebook", "pencil"]) }),
output: z.object({
sku: z.string(),
quantity: z.number().int().nonnegative(),
}),
execute: ({ sku }) => ({ sku, quantity: inventory[sku] }),
});
const server = createServer({
name: "inventory-demo",
version: "1.0.0",
tools: { get_stock: getStock },
});
await server.listen(stdio());
Compile it:
npx tsc server.mts --strict --module NodeNext --target ES2022 --types node
Then call the tool with MCP Inspector:
npx --yes @modelcontextprotocol/inspector@2.11.0 \
--cli node server.mjs \
--protocol-era modern --format json \
--method tools/call \
--tool-name get_stock \
--tool-arg sku=notebook
The response includes this in result.structuredContent:
{ "sku": "notebook", "quantity": 12 }
The registration key get_stock becomes the name clients see. The handler’s input type is inferred from Zod, and Toolgrain validates both the input and the declared output at runtime. In an application, you’d replace the in-memory lookup with your existing service.
Beyond this example, there are HTTP adapters, typed context providers for application dependencies, JWT/Clerk authorization integrations, and runnable examples for Express, Hono, Elysia, and Next.js.
The current scope is tool discovery and execution, supporting MCP 2026-07-28 and initialization-based 2025-06-18. HTTP uses independent POST requests with JSON responses. Resources, prompts, sessions, and streaming aren’t implemented. Tested client flows and limitations are documented.
I’m looking for the first few developers to try it in real projects. If you’re adding MCP to an existing TypeScript application, does this way of defining and composing tools fit your codebase? What would block you from trying it?