Building Your First MCP Server in TypeScript or Python
You need a real MCP server you can install in a host, not a protocol essay.
Search across all documentation pages
You need a real MCP server you can install in a host, not a protocol essay.
This recipe scaffolds a minimal stdio server in Python (FastMCP) or TypeScript (@modelcontextprotocol/sdk), registers tools with schemas, and connects a desktop or IDE client.
Pick one language, install the official SDK, register one or two tools with clear descriptions, run over stdio with stderr-only logging, then point a host config at the absolute command path and verify tools appear.
mcp[cli] or Node 18+ with @modelcontextprotocol/sdk and Zod 3.uv/pip or npm).name and version (TS) or FastMCP name (Python).echo/ping for CI.# server.py - verify package APIs at build
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("first-server")
@mcp.tool()
def add(a: float, b: float) -> str:
"""Add two numbers and return a short explanation."""
return f"{a} + {b} = {a + b}"
@mcp.tool()
async def fetch_status(service: str) -> str:
"""Return a fake health status for a named service."""
# Replace with real HTTP later; keep side effects explicit.
return f"{service}: ok"
if __name__ == "__main__":
mcp.run(transport="stdio")pip install "mcp[cli]"
python server.pyHost config sketch:
{
"mcpServers": {
"first-server": {
"command": "python",
"args": ["/ABSOLUTE/PATH/TO/server.py"]
}
}
}// src/index.ts - verify import paths at build
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: "first-server",
version: "1.0.0",
});
server.registerTool(
"add",
{
description: "Add two numbers and return a short explanation",
inputSchema: {
a: z.number().describe("First addend"),
b: z.number().describe("Second addend"),
},
},
async ({ a, b }) => ({
content: [{ type: "text", text: `${a} + ${b} = ${a + b}` }],
}),
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("first-server listening on stdio");
}
main().catch((err) => {
console.error(err);
process.exit(1);
});npm init -y
npm install @modelcontextprotocol/sdk zod@3
npm install -D typescript @types/node
# set "type": "module", build with tsc to build/index.js
node build/index.jsBoth stacks implement the same protocol roles: server identity, tool registry, schema, transport.
Python FastMCP optimizes for decorator ergonomics and docstring-driven descriptions.
TypeScript optimizes for explicit registerTool metadata and Zod validation at the edge.
Agents and hosts only care that tools/list and tools/call behave.
first-server/
server.py # or src/index.ts
pyproject.toml # or package.json + tsconfig.json
README.md # how to run + host config snippet
tests/ # client harness smoke testsKeep handlers thin. Put HTTP clients, DB access, and policy in modules the tools call.
Write tool descriptions as instructions to a junior teammate: what it does, when to use it, what not to pass.
Field-level .describe(...) / docstring Args sections reduce invalid calls more than retry loops do.
Many hosts execute node path/to/build/index.js.
A forgotten npm run build is a frequent "server won't connect" report.
Pin Node and SDK versions in CI the same way you pin agent frameworks.
stdio servers inherit env from the host config (env map in many clients).
Do not hardcode API keys in source. Document required variables in README.
Ship when: tools list, one call succeeds, logs stay on stderr, and a smoke test is green.
Add resources, prompts, HTTP, and OAuth only after that baseline.
print / console.log on stdio corrupts JSON-RPC.mcp installed.build/index.js.| Approach | When it wins | When it loses |
|---|---|---|
| Python FastMCP | Fast prototypes, data/ML stacks | Team standard is Node |
| TypeScript SDK | Same language as web agents | Python-only ops |
| Low-level Server API | Max control over capabilities | More boilerplate |
| Existing third-party MCP server | Zero build | Wrong domain or trust model |
| Plain REST + custom tool wrappers | Already have OpenAPI agents | No shared host ecosystem |
Pick the language your team already ships. Protocol interoperability means a Python server still works with a TypeScript host.
Use async when the tool awaits network or disk. Sync is fine for pure CPU-light helpers, but avoid long blocks.
Bump version in TS server metadata and tag releases. Mention schema changes in the changelog so hosts re-test tool lists.
Yes. Keep registration code shared; swap only the transport entrypoint when you deploy remotely.
One health/echo tool for connectivity plus one real domain tool. Connectivity-only servers never get exercised by agents.
Run the server under your smoke client, check stderr logs, and confirm absolute paths and env vars in the host config.
No for a first server. Tools alone are enough to prove the stack.
In plain modules imported by tool handlers. Avoid stuffing DB sessions into transport setup beyond shared factories.
Related: Building & Deploying MCP Basics
Related: Anatomy of an MCP Server: Handlers, Schemas, and Transport
Related: Exposing Resources and Prompts, Not Just Tools, from an MCP Server
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.
Reviewed by Chris St. John·Last updated Jul 16, 2026