Guides
Build your first MCP server#
This page turns the beginner concepts into a runnable local server. Keep the first pass deliberately small: one stdio server, one read-only tool, one structured result, and one Inspector call before touching host installs or agent-connector surfaces.
Reference baseline#
The implementation below follows the current official TypeScript SDK style: McpServer, registerTool, Zod-backed input schemas, optional outputSchema, and structuredContent beside text content. It also keeps the first transport local with stdio.
| Reference | Use it for |
|---|---|
| Build an MCP server | The official quickstart for the TypeScript SDK and stdio server shape. |
| Tools spec | Tool metadata, input schemas, optional output schemas, and result content. |
| MCP Inspector | A protocol-aware test client. Use it before debugging a real host UI. |
1. Create the project#
Use a new folder so you can tell package errors apart from host errors. The SDK version below was current when this guide was refreshed; re-run npm view @modelcontextprotocol/sdk version before changing a published tutorial package.
mkdir acme-db-mcp
cd acme-db-mcp
npm init -y
npm install @modelcontextprotocol/sdk@^1.29.0 zod@^32. Write one read-only tool#
This example returns both text and structured JSON. Text is useful for the model's answer; structuredContent is useful when the result has fields that should remain machine-readable.
// my-mcp-server.mjs
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({ name: "acme-db", version: "0.1.0" });
server.registerTool(
"schema_summary",
{
title: "Schema summary",
description: "Return a short, read-only summary of the database schema.",
inputSchema: {
table: z.string().min(1).optional().describe("Optional table name"),
},
outputSchema: {
summary: z.string(),
table: z.string().optional(),
},
},
async ({ table }) => {
const structuredContent = {
table,
summary: table
? `Schema summary for ${table}: id, email, created_at`
: "Schema summary for all tables: users, orders, invoices",
};
return {
structuredContent,
content: [{ type: "text", text: structuredContent.summary }],
};
},
);
await server.connect(new StdioServerTransport());Why not start with resources, prompts, sampling, or elicitation?
They are important MCP features, but a first server needs one tight loop: advertise a capability, receive arguments, validate them, and return a predictable result. Add other protocol surfaces after this loop is boring.3. Run with MCP Inspector#
A stdio MCP server waits for JSON-RPC messages on stdin, so running node my-mcp-server.mjs directly can look idle. Inspector starts the process as a host would and lets you list and call tools.
npx -y @modelcontextprotocol/inspector node ./my-mcp-server.mjs
# In Inspector:
# 1. Connect to the stdio server.
# 2. Open Tools.
# 3. Call schema_summary with:
# { "table": "users" }4. What success looks like#
| Check | Expected result |
|---|---|
| Tool list | Inspector shows schema_summary with the title, description, and input schema. |
| Valid call | Calling with { "table": "users" } returns a short text result and structured JSON. |
| Invalid call | Bad input returns a clear validation error instead of corrupting the stdio stream. |
After these checks pass, continue to Connect your first host.