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.js

Python

# 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.py

Step 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 tools

Test in Claude Desktop

After configuring claude_desktop_config.json:

  1. 1.Restart Claude Desktop completely
  2. 2.Open developer tools (Cmd/Ctrl + Option + I)
  3. 3.Check console for connection logs
  4. 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 →

Join Community

Get help, share your servers, and connect with other developers.

Join Discord →

Common Next Steps