How to Build Your First MCP Server: A Step-by-Step Tutorial
Learn how to create your own MCP server from scratch using TypeScript. This hands-on tutorial walks you through building a functional MCP server.
Building your first MCP server is easier than you might think. In this tutorial, we'll create a simple but functional MCP server that exposes tools for AI applications to use.
If you're new to MCP, start with our introduction to the Model Context Protocol first.
Prerequisites
Before we start, make sure you have:
Step 1: Project Setup
First, create a new directory and initialize your project:
mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
Create a tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"outDir": "./dist"
}
}
Step 2: Create the Server
Create src/index.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";// Create the MCP server
const server = new McpServer({
name: "my-first-server",
version: "1.0.0",
});
// Define a simple tool
server.tool(
"greet",
"Greet someone by name",
{
name: z.string().describe("The name to greet"),
},
async ({ name }) => {
return {
content: [
{
type: "text",
text: Hello, ${name}! Welcome to MCP.,
},
],
};
}
);
// Define a calculation tool
server.tool(
"calculate",
"Perform basic math operations",
{
operation: z.enum(["add", "subtract", "multiply", "divide"]),
a: z.number().describe("First number"),
b: z.number().describe("Second number"),
},
async ({ operation, a, b }) => {
let result: number;
switch (operation) {
case "add":
result = a + b;
break;
case "subtract":
result = a - b;
break;
case "multiply":
result = a * b;
break;
case "divide":
result = b !== 0 ? a / b : NaN;
break;
}
return {
content: [
{
type: "text",
text: ${a} ${operation} ${b} = ${result},
},
],
};
}
);
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Server running on stdio");
}
main().catch(console.error);
Step 3: Understanding the Code
Let's break down what we just wrote:
Server Initialization:
const server = new McpServer({
name: "my-first-server",
version: "1.0.0",
});
This creates a new MCP server with a name and version that clients can identify.
Tool Definition:
server.tool(
"greet", // Tool name
"Greet someone", // Description
{ name: z.string() }, // Input schema using Zod
async ({ name }) => { / handler / }
);
Tools are functions that AI can call. We define the name, description, input schema, and handler function.
Transport:
const transport = new StdioServerTransport();
STDIO transport means communication happens through standard input/output — perfect for local servers.
Step 4: Test Your Server
Add a script to package.json:
{
"scripts": {
"start": "tsx src/index.ts"
}
}
You can test the server using the MCP Inspector:
npx @modelcontextprotocol/inspector tsx src/index.ts
This opens a web interface where you can interact with your server and test your tools.
Step 5: Connect to Claude Desktop
To use your server with Claude Desktop, add it to your configuration. On macOS, edit ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"my-first-server": {
"command": "npx",
"args": ["tsx", "/path/to/my-mcp-server/src/index.ts"]
}
}
}
Restart Claude Desktop, and your tools will be available!
Adding Resources
MCP servers can also expose resources — read-only data that provides context:
server.resource(
"config",
"config://app",
async (uri) => ({
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify({
version: "1.0.0",
environment: "development",
}),
},
],
})
);
Adding Prompts
Prompts are reusable templates for common interactions:
server.prompt(
"code-review",
"Template for code review requests",
{ language: z.string() },
({ language }) => ({
messages: [
{
role: "user",
content: {
type: "text",
text: Please review the following ${language} code...,
},
},
],
})
);
Best Practices
1. Validate all inputs: Use Zod schemas to ensure type safety
2. Write clear descriptions: AI uses these to understand when to call your tools
3. Handle errors gracefully: Return meaningful error messages
4. Keep tools focused: Each tool should do one thing well
5. Log for debugging: Use console.error (not console.log) for debug output
Next Steps
Now that you've built your first MCP server, explore these resources:
Prefer Python? Check out Getting Started with MCP in Python.
The MCP ecosystem is growing rapidly, and your server could be the next great integration!