MCP
Build Your First MCP Server
Create a Model Context Protocol server in minutes. This guide walks you through installation, configuration, and building a functional MCP server that integrates with Claude Desktop.
Quick Start Steps
Prerequisites
- Node.js 18+ or Python 3.10+ installed
- Claude Desktop or MCP-compatible AI assistant
- Basic understanding of JavaScript/TypeScript or Python
- Text editor (VS Code recommended)
Install MCP SDK
- Choose your preferred language SDK
- Install via npm/pip package manager
- Verify installation with version check
- Set up development environment
Create Your First Server
- Initialize a new MCP server project
- Define server metadata and capabilities
- Implement basic tool handlers
- Test with MCP Inspector
Connect to Claude Desktop
- Configure claude_desktop_config.json
- Add your server configuration
- Restart Claude Desktop
- Verify connection and test tools
Step 1: Installation
Choose your preferred programming language and follow the installation steps:
TypeScript/JavaScript
# Create a new directory for your MCP server
mkdir my-mcp-server && cd my-mcp-server
# Initialize npm project
npm init -y
# Install MCP SDK
npm install @modelcontextprotocol/sdk
# Create your server file
touch server.jsPython
# Create a new directory for your MCP server
mkdir my-mcp-server && cd my-mcp-server
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install MCP SDK
pip install mcp
# Create your server file
touch server.pyStep 2: Create Your Server
Here's a complete example of a simple MCP server that can read and write files:
TypeScript Example
// server.js
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { promises as fs } from 'fs';
// Create server instance
const server = new Server(
{
name: 'my-first-server',
version: '1.0.0',
},
{
capabilities: {
tools: {}, // Enable tools
resources: {}, // Enable resources if needed
},
}
);
// Define available tools
server.setRequestHandler('tools/list', async () => {
return {
tools: [
{
name: 'read_file',
description: 'Read contents of a file',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Path to the file to read',
},
},
required: ['path'],
},
},
{
name: 'write_file',
description: 'Write contents to a file',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Path to the file to write',
},
content: {
type: 'string',
description: 'Content to write to the file',
},
},
required: ['path', 'content'],
},
},
],
};
});
// Handle tool execution
server.setRequestHandler('tools/call', async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case 'read_file': {
try {
const content = await fs.readFile(args.path, 'utf-8');
return {
content: [
{
type: 'text',
text: content,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error reading file: ${error.message}`,
},
],
};
}
}
case 'write_file': {
try {
await fs.writeFile(args.path, args.content, 'utf-8');
return {
content: [
{
type: 'text',
text: `Successfully wrote to ${args.path}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error writing file: ${error.message}`,
},
],
};
}
}
default:
throw new Error(`Unknown tool: ${name}`);
}
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('MCP server running on stdio');
}
main().catch((error) => {
console.error('Server error:', error);
process.exit(1);
});Python Example
# server.py
import asyncio
import json
from typing import Any
from mcp.server.models import InitializationOptions
from mcp.server import NotificationOptions, Server
from mcp.server.stdio import stdio_server
import mcp.types as types
# Create server instance
server = Server("my-first-server")
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""List available tools."""
return [
types.Tool(
name="read_file",
description="Read contents of a file",
inputSchema={
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path to the file to read",
}
},
"required": ["path"],
},
),
types.Tool(
name="write_file",
description="Write contents to a file",
inputSchema={
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path to the file to write",
},
"content": {
"type": "string",
"description": "Content to write to the file",
}
},
"required": ["path", "content"],
},
),
]
@server.call_tool()
async def handle_call_tool(
name: str, arguments: dict[str, Any]
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
"""Handle tool execution."""
if name == "read_file":
try:
with open(arguments["path"], "r") as f:
content = f.read()
return [types.TextContent(type="text", text=content)]
except Exception as e:
return [types.TextContent(type="text", text=f"Error reading file: {str(e)}")]
elif name == "write_file":
try:
with open(arguments["path"], "w") as f:
f.write(arguments["content"])
return [types.TextContent(type="text", text=f"Successfully wrote to {arguments['path']}")]
except Exception as e:
return [types.TextContent(type="text", text=f"Error writing file: {str(e)}")]
else:
raise ValueError(f"Unknown tool: {name}")
async def main():
# Run the server using stdio transport
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="my-first-server",
server_version="1.0.0",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)
if __name__ == "__main__":
asyncio.run(main())Step 3: Configure Claude Desktop
Add your server to Claude Desktop's configuration file:
- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
- Windows: %APPDATA%\Claude\claude_desktop_config.json
- Linux: ~/.config/Claude/claude_desktop_config.json
{
"mcpServers": {
"my-first-server": {
"command": "node",
"args": ["./server.js"],
"cwd": "/absolute/path/to/my-mcp-server"
}
}
}
// For Python servers:
{
"mcpServers": {
"my-first-server": {
"command": "python",
"args": ["./server.py"],
"cwd": "/absolute/path/to/my-mcp-server"
}
}
}Important Notes:
- • Use absolute paths in the configuration
- • Restart Claude Desktop after changes
- • Check developer console for connection logs
Step 4: Testing Your Server
Test with MCP Inspector
# Install MCP Inspector
npm install -g @modelcontextprotocol/inspector
# Run your server in one terminal
node server.js
# In another terminal, start the inspector
mcp-inspector
# Connect to stdio://localhost and test your toolsTest in Claude Desktop
After configuring claude_desktop_config.json:
- 1.Restart Claude Desktop completely
- 2.Open developer tools (Cmd/Ctrl + Option + I)
- 3.Check console for connection logs
- 4.Try using your tools in conversation
What's Next?
Explore Examples
Browse the official servers repository for more complex implementations and patterns.
View Examples →Advanced Features
Learn about resources, prompts, and advanced server capabilities.
Read Documentation →