Install the official package @modelcontextprotocol/server and Zod, create an McpServer with a name and version, register each tool with registerTool, a description and a Zod input schema, then connect a StdioServerTransport. Run npx @modelcontextprotocol/inspector to call your tools by hand before any AI application touches them.
| Piece | Where it comes from | What it does |
|---|---|---|
| McpServer | @modelcontextprotocol/server | Holds your tools and answers protocol requests |
| registerTool | Method on McpServer | Adds one tool: name, description, input schema, handler |
| StdioServerTransport | @modelcontextprotocol/server/stdio | Talks to the host over standard input and output |
| z.object(...) | zod/v4 | Describes and validates the tool's arguments |
| Inspector | npx @modelcontextprotocol/inspector | Lets you list and call tools by hand |
Updated
Tick off each step as you go. The checklist keeps your place, and every step has the exact command or file you need. The finished server searches a small list of notes, which is enough to see every moving part without a database.
mkdir notes-server && cd notes-servernpm install @modelcontextprotocol/server zod
npm install --save-dev tsx typescriptserver.registerTool('notes_search', { ... }, handler)npx tsx server.tsnpx @modelcontextprotocol/inspectortools/list, then tools/call with { "query": "invoice" }Two files. Change the notes array to whatever your server should search.
type module lets the ES import syntax run directly; the start script uses tsx so you skip a build step.
{
"name": "notes-server",
"version": "1.0.0",
"type": "module",
"scripts": { "start": "tsx server.ts" }
}One tool with a Zod schema, a plain search function and a stdio connection. Logging goes to stderr so it never mixes with protocol messages.
import { McpServer } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
const notes = [
{ title: 'Invoice March', body: 'Sent to the printer, paid on the 12th' },
{ title: 'Invoice April', body: 'Waiting on the client' },
{ title: 'Team lunch', body: 'Friday at noon' },
];
function search(query: string) {
const q = query.toLowerCase();
return notes.filter((n) => (n.title + ' ' + n.body).toLowerCase().includes(q));
}
const server = new McpServer({ name: 'notes-server', version: '1.0.0' });
server.registerTool(
'notes_search',
{
description: 'Find notes whose title or text contains a word. Use it before answering questions about the notes.',
inputSchema: z.object({ query: z.string().min(1) }),
},
async ({ query }) => {
const hits = search(query);
const text = hits.length
? `${hits.length} notes match: ${hits.map((n) => n.title).join(', ')}`
: `No notes contain "${query}".`;
return { content: [{ type: 'text', text }] };
},
);
async function main() {
await server.connect(new StdioServerTransport());
console.error('notes-server running on stdio');
}
main();The Inspector has three modes. The web UI is the easiest place to start; the CLI mode suits scripts and CI.
In the web UI, connect with the command that starts your server (npx tsx server.ts), then open the tools list.
npx @modelcontextprotocol/inspector # web UI
npx @modelcontextprotocol/inspector --cli # command line
npx @modelcontextprotocol/inspector --tui # terminal UI
A stdio server is just a program the host starts. There is no port, no hosting and no login, which makes it the quickest route to a working tool you can test on your own machine.
Once the tools behave, moving to Streamable HTTP changes the transport line and the hosting, not the tool code. That separation is the main reason to keep business logic in plain functions the handlers call.
A handler returns an object with a content array. Text items are the common case, and short, specific text works best: say what matched and how many, rather than dumping a whole record set.
When nothing matches, return a sentence that says so. An empty string leaves the model guessing whether the call failed or the search was simply empty.
For SDK version 2, install @modelcontextprotocol/server for servers and @modelcontextprotocol/client for clients. The older v1 package, @modelcontextprotocol/sdk, lives on a v1.x branch and gets bug and security fixes for at least six months after the v2 release.
The v2 README uses Zod v4 for input schemas and imports it from zod/v4, and it says other Standard Schema libraries such as Valibot and ArkType also work. Install whichever you choose alongside the SDK.
The SDK README says it runs on Node.js, Bun and Deno. The Inspector is a separate Node tool and its README asks for Node 22.19.0 or newer.
Swap the stdio transport for Streamable HTTP and host the process somewhere reachable. Add authentication before you expose any tool that changes data, since anyone who can reach the URL can send requests.
Read your description and schema as if you knew nothing else about the project. If a stranger could not tell when to call the tool and what to pass, a model will guess too. The Inspector shows exactly what a host will see.
Related: What is an MCP server?, How to build an AI agent, JSON Schema generator, Install Node and npm on a Mac, TypeScript vs JavaScript.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.