AIPaths Academy
CourseGuidesBlogVideosResources

Resources

  • Documentation
  • Video Library
  • Blog
AIPaths Academy

Empowering developers to build the future with AI.

Legal

  • Terms of Service
  • Privacy Policy
  • Refund Policy
  • Cookie Policy

© 2026 AIPaths Academy. All rights reserved.

    Related Content

    Videos
    How I Create Quotes for Clients with AI (Full Method)You were using AI the wrong way.
    Guides
    Molt Bot: The Autonomous AI Agent GuideSuperClaude FrameworkContext Engineering: The Evolution You Need to Understand
    Blog Posts
    5 Essential MCP Servers Every Claude Developer NeedsClaude Design: useful tool or pure hype?

    Related Content

    Videos
    How I Create Quotes for Clients with AI (Full Method)You were using AI the wrong way.
    Guides
    Molt Bot: The Autonomous AI Agent GuideSuperClaude FrameworkContext Engineering: The Evolution You Need to Understand
    Blog Posts
    5 Essential MCP Servers Every Claude Developer NeedsClaude Design: useful tool or pure hype?
    1. Home
    2. Guides
    3. Building Your First MCP Server: A Complete Guide
    •Published 10/20/2025•
    5 min read

    Building Your First MCP Server: A Complete Guide

    Learn how to build Model Context Protocol (MCP) servers to extend Claude's capabilities with custom tools and data sources.

    mcpclaudetoolsintermediate
    Table of Contents(11 sections)

    On This Page

    What is MCP?PrerequisitesUnderstanding MCP ArchitectureBuilding a Weather MCP ServerConnecting to Claude for DesktopCommon Issues & TroubleshootingBest PracticesAdvanced FeaturesPython AlternativeNext StepsAdditional Resources

    Building Your First MCP Server: A Complete Guide

    If you've been working with Claude and want to extend its capabilities with custom tools, data sources, or integrations, the Model Context Protocol (MCP) is your answer. MCP is an open protocol that standardizes how AI applications connect to external systems—think of it as a USB-C port for AI.

    In this guide, you'll learn what MCP is, how it works, and build your first MCP server from scratch. By the end, you'll have a working weather server that Claude can use to fetch real-time weather data.

    What is MCP?

    Model Context Protocol (MCP) is an open standard that enables AI applications to securely connect to external data sources, tools, and services. Created by Anthropic, MCP solves a critical problem: how to give AI models access to real-world data and functionality without rebuilding integrations for every use case.

    Why MCP Matters

    Before MCP, every AI application had to build custom integrations for each data source or tool. This meant:

    • Duplicated effort across projects
    • Inconsistent security practices
    • Hard-to-maintain integrations
    • Limited interoperability

    MCP provides:

    • Standardization: One protocol for all integrations
    • Security: Built-in authentication and authorization
    • Flexibility: Works with any data source or tool
    • Interoperability: Build once, use everywhere

    Real-World Use Cases

    • Enterprise Data Access: Connect Claude to internal databases, APIs, and knowledge bases
    • Custom Tools: Give Claude the ability to execute domain-specific functions
    • File Systems: Let Claude read and write files safely
    • External APIs: Integrate weather services, payment systems, CRMs, and more
    • Development Tools: Git operations, code analysis, testing frameworks

    Prerequisites

    Before you start, ensure you have:

    • Node.js 18+ or Python 3.8+ installed
    • Basic knowledge of JavaScript/TypeScript or Python
    • A text editor (VS Code recommended)
    • Claude for Desktop (optional, for testing)
    • 30 minutes of your time

    Skill Level: Intermediate Time Required: 30-45 minutes

    Understanding MCP Architecture

    MCP follows a client-server architecture where:

    1. MCP Host: The AI application (like Claude for Desktop)
    2. MCP Client: Manages connections to servers (runs inside the host)
    3. MCP Server: Provides resources, tools, or prompts to the AI
    ┌─────────────────────────────────────┐
    │       MCP Host (Claude)             │
    │  ┌────────────────────────────────┐ │
    │  │      MCP Client Manager        │ │
    │  │  ┌──────┐  ┌──────┐  ┌──────┐ │ │
    │  │  │Client│  │Client│  │Client│ │ │
    │  │  └───┬──┘  └───┬──┘  └───┬──┘ │ │
    │  └──────┼─────────┼─────────┼────┘ │
    └─────────┼─────────┼─────────┼──────┘
              │         │         │
              ▼         ▼         ▼
         ┌────────┐┌────────┐┌────────┐
         │Weather ││FileSystem││GitHub│
         │Server  ││Server   ││Server │
         └────────┘└────────┘└────────┘
    

    Key Concepts

    Resources: Data or content that Claude can read (files, database records, API responses)

    Tools: Functions that Claude can execute (search, calculate, API calls)

    Prompts: Pre-defined prompt templates for common tasks

    Transport: Communication channel between client and server (typically STDIO or HTTP)

    Building a Weather MCP Server

    Let's build a practical MCP server that provides weather information using the National Weather Service API.

    Step 1: Project Setup

    Create a new project directory:

    # Create project directory
    mkdir weather-mcp-server
    cd weather-mcp-server
    
    # Initialize npm project
    npm init -y
    
    # Install dependencies
    npm install @modelcontextprotocol/sdk zod
    npm install -D @types/node typescript
    
    # Create source directory
    mkdir src
    touch src/index.ts
    

    Create a tsconfig.json:

    {
      "compilerOptions": {
        "target": "ES2022",
        "module": "Node16",
        "moduleResolution": "Node16",
        "outDir": "./build",
        "rootDir": "./src",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true
      },
      "include": ["src/**/*"]
    }
    

    Step 2: Initialize the MCP Server

    Create src/index.ts and set up the server:

    import { Server } from "@modelcontextprotocol/sdk/server/index.js";
    import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
    import {
      CallToolRequestSchema,
      ListToolsRequestSchema,
    } from "@modelcontextprotocol/sdk/types.js";
    import { z } from "zod";
    
    const NWS_API_BASE = "https://api.weather.gov";
    const USER_AGENT = "weather-mcp-server/1.0";
    
    // Create server instance
    const server = new Server(
      {
        name: "weather-server",
        version: "1.0.0",
      },
      {
        capabilities: {
          tools: {},
        },
      }
    );
    
    console.error("Weather MCP Server starting...");
    

    Note: We use console.error() instead of console.log() because MCP servers communicate via STDIO, and stdout is reserved for JSON-RPC messages.

    Step 3: Define Tool Schemas

    Define the structure for our weather tools using Zod:

    // Tool input schemas
    const GetForecastSchema = z.object({
      latitude: z.number().min(-90).max(90),
      longitude: z.number().min(-180).max(180),
    });
    
    const GetAlertsSchema = z.object({
      state: z.string().length(2).describe("Two-letter state code (e.g., CA, NY)"),
    });
    
    // Helper function to make API requests
    async function fetchWeatherData(url: string) {
      const response = await fetch(url, {
        headers: {
          "User-Agent": USER_AGENT,
        },
      });
    
      if (!response.ok) {
        throw new Error(`Weather API error: ${response.statusText}`);
      }
    
      return response.json();
    }
    

    Step 4: Implement Tool Handlers

    Register tools that Claude can call:

    // Handle tool list requests
    server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: [
          {
            name: "get_forecast",
            description: "Get weather forecast for a location by latitude and longitude",
            inputSchema: {
              type: "object",
              properties: {
                latitude: {
                  type: "number",
                  description: "Latitude of the location",
                  minimum: -90,
                  maximum: 90,
                },
                longitude: {
                  type: "number",
                  description: "Longitude of the location",
                  minimum: -180,
                  maximum: 180,
                },
              },
              required: ["latitude", "longitude"],
            },
          },
          {
            name: "get_alerts",
            description: "Get active weather alerts for a US state",
            inputSchema: {
              type: "object",
              properties: {
                state: {
                  type: "string",
                  description: "Two-letter US state code (e.g., CA, NY)",
                  pattern: "^[A-Z]{2}$",
                },
              },
              required: ["state"],
            },
          },
        ],
      };
    });
    
    // Handle tool execution
    server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;
    
      try {
        if (name === "get_forecast") {
          const { latitude, longitude } = GetForecastSchema.parse(args);
    
          // Get gridpoint data
          const pointsUrl = `${NWS_API_BASE}/points/${latitude},${longitude}`;
          const pointsData = await fetchWeatherData(pointsUrl);
    
          // Get forecast
          const forecastUrl = pointsData.properties.forecast;
          const forecastData = await fetchWeatherData(forecastUrl);
    
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(forecastData.properties.periods, null, 2),
              },
            ],
          };
        }
    
        if (name === "get_alerts") {
          const { state } = GetAlertsSchema.parse(args);
    
          const alertsUrl = `${NWS_API_BASE}/alerts?area=${state}`;
          const alertsData = await fetchWeatherData(alertsUrl);
    
          const features = alertsData.features;
          if (features.length === 0) {
            return {
              content: [
                {
                  type: "text",
                  text: `No active weather alerts for ${state}`,
                },
              ],
            };
          }
    
          const alerts = features.map((feature: any) => ({
            event: feature.properties.event,
            severity: feature.properties.severity,
            description: feature.properties.description,
          }));
    
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(alerts, null, 2),
              },
            ],
          };
        }
    
        throw new Error(`Unknown tool: ${name}`);
      } catch (error) {
        if (error instanceof z.ZodError) {
          throw new Error(`Invalid arguments: ${error.message}`);
        }
        throw error;
      }
    });
    

    Step 5: Start the Server

    Add the main function to run the server:

    // Start server
    async function main() {
      const transport = new StdioServerTransport();
      await server.connect(transport);
      console.error("Weather MCP Server running on stdio");
    }
    
    main().catch((error) => {
      console.error("Fatal error:", error);
      process.exit(1);
    });
    

    Step 6: Build and Test

    Add build scripts to package.json:

    {
      "name": "weather-mcp-server",
      "version": "1.0.0",
      "type": "module",
      "scripts": {
        "build": "tsc",
        "start": "node build/index.js"
      },
      "dependencies": {
        "@modelcontextprotocol/sdk": "^1.0.0",
        "zod": "^3.22.0"
      },
      "devDependencies": {
        "@types/node": "^20.0.0",
        "typescript": "^5.0.0"
      }
    }
    

    Build the server:

    npm run build
    

    Test it using the MCP Inspector (a debugging tool):

    npx @modelcontextprotocol/inspector node build/index.js
    

    This opens a web interface where you can test your tools interactively.

    Connecting to Claude for Desktop

    To use your server with Claude for Desktop:

    1. Locate your config file:

      • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
      • Windows: %APPDATA%\Claude\claude_desktop_config.json
    2. Add your server:

    {
      "mcpServers": {
        "weather": {
          "command": "node",
          "args": ["/absolute/path/to/weather-mcp-server/build/index.js"]
        }
      }
    }
    
    1. Restart Claude for Desktop

    2. Test it:

      • Open Claude for Desktop
      • Look for the 🔌 icon indicating MCP servers are connected
      • Ask: "What's the weather forecast for Seattle?" (latitude: 47.6062, longitude: -122.3321)

    Common Issues & Troubleshooting

    Issue: "Server not connecting"

    Cause: Incorrect path or missing build files

    Solution:

    1. Ensure you've run npm run build
    2. Use absolute paths in the config file
    3. Check Claude's logs: ~/Library/Logs/Claude/mcp*.log

    Issue: "Tool not found"

    Cause: Server didn't register tools properly

    Solution:

    1. Verify ListToolsRequestSchema handler is set
    2. Check server logs for errors
    3. Test with MCP Inspector first

    Issue: "API errors"

    Cause: Invalid coordinates or network issues

    Solution:

    1. Validate latitude/longitude ranges
    2. Check your internet connection
    3. The NWS API only works for US locations

    Best Practices

    1. Always Validate Input

    Use Zod or similar libraries to validate tool arguments:

    const args = MySchema.parse(request.params.arguments);
    

    2. Provide Clear Descriptions

    Tool and parameter descriptions help Claude understand when and how to use them:

    {
      name: "search_database",
      description: "Search the customer database by email or ID. Returns customer details.",
      inputSchema: {
        // ...
      }
    }
    

    3. Handle Errors Gracefully

    Return meaningful error messages:

    try {
      // Tool logic
    } catch (error) {
      return {
        content: [{
          type: "text",
          text: `Error: ${error.message}. Please check your input.`
        }],
        isError: true,
      };
    }
    

    4. Use Logging Wisely

    Always log to stderr in STDIO servers:

    console.error("Processing request:", request.params.name);
    

    5. Implement Rate Limiting

    Protect external APIs from abuse:

    let lastRequest = 0;
    const MIN_INTERVAL = 1000; // 1 second
    
    if (Date.now() - lastRequest < MIN_INTERVAL) {
      throw new Error("Rate limit exceeded");
    }
    lastRequest = Date.now();
    

    Advanced Features

    Resources

    Expose data that Claude can read:

    server.setRequestHandler(ListResourcesRequestSchema, async () => {
      return {
        resources: [
          {
            uri: "weather://alerts",
            name: "Active Weather Alerts",
            mimeType: "application/json",
          },
        ],
      };
    });
    

    Prompts

    Provide prompt templates:

    server.setRequestHandler(ListPromptsRequestSchema, async () => {
      return {
        prompts: [
          {
            name: "weather_report",
            description: "Generate a detailed weather report",
            arguments: [
              {
                name: "location",
                description: "City name",
                required: true,
              },
            ],
          },
        ],
      };
    });
    

    Progress Notifications

    Send progress updates for long-running operations:

    server.notification({
      method: "notifications/progress",
      params: {
        progress: 50,
        total: 100,
      },
    });
    

    Python Alternative

    Prefer Python? Here's the equivalent setup:

    # Create project
    mkdir weather-mcp-server
    cd weather-mcp-server
    
    # Install dependencies
    pip install mcp
    
    # Create server
    touch server.py
    

    Basic Python server structure:

    from mcp.server import Server, Tool
    from mcp.server.stdio import stdio_server
    import httpx
    
    app = Server("weather-server")
    
    @app.tool()
    async def get_forecast(latitude: float, longitude: float) -> str:
        """Get weather forecast for coordinates"""
        async with httpx.AsyncClient() as client:
            # Fetch and return weather data
            pass
    
    async def main():
        async with stdio_server() as (read, write):
            await app.run(read, write)
    
    if __name__ == "__main__":
        import asyncio
        asyncio.run(main())
    

    Next Steps

    Now that you've built your first MCP server, explore:

    1. Build More Tools: Create servers for your specific use cases
    2. Explore Official Servers: Study @modelcontextprotocol repositories
    3. Add Authentication: Implement secure API key handling
    4. Deploy Remotely: Set up HTTP transport for remote servers
    5. Contribute: Share your servers with the community

    Popular MCP Server Examples:

    • @modelcontextprotocol/server-filesystem - File operations
    • @modelcontextprotocol/server-github - GitHub integration
    • @modelcontextprotocol/server-memory - Persistent memory

    Additional Resources

    Official Documentation

    • MCP Specification
    • MCP SDK Documentation
    • Official MCP Servers

    Tools

    • MCP Inspector - Debug MCP servers
    • Claude for Desktop - Test your servers

    Community

    • MCP GitHub Discussions
    • Anthropic Discord - #mcp channel

    Questions? Open an issue or join our community discussions!

    Was this helpful?
    Share this content
    0comments

    On This Page

    What is MCP?PrerequisitesUnderstanding MCP ArchitectureBuilding a Weather MCP ServerConnecting to Claude for DesktopCommon Issues & TroubleshootingBest PracticesAdvanced FeaturesPython AlternativeNext StepsAdditional Resources