mcpinabox

gkerensky/mcpinabox

3.2

If you are the rightful owner of mcpinabox and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.

The Model Context Protocol (MCP) server is a framework designed to facilitate the integration of AI models with external tools and APIs, enabling seamless communication and data exchange.

Tools
2
Resources
0
Prompts
0

MCP in a Box 📦

Build Your First Custom MCP Server in Under 30 Minutes

A hands-on lab for learning the Model Context Protocol (MCP) by building a working weather dashboard server that connects to OpenAI's API.


What You'll Build

A fully functional MCP server that:

  • Exposes a get_weather tool that returns mock weather data
  • Works with OpenAI's Responses API
  • Can be extended to connect to real APIs

By the end, you'll understand how MCP servers work and how to build your own.


Prerequisites

Knowledge Required

  • Basic JavaScript/Node.js (variables, functions, async/await)
  • Comfortable using the terminal/command line
  • Basic understanding of APIs (what they are, how requests work)

Tools Required

  • Node.js v18 or higher (download)
  • npm (comes with Node.js)
  • A code editor (VS Code recommended)
  • ngrok for tunneling (free tier works) (download)
  • OpenAI API key (for testing with Responses API)

Check Your Setup

Run these commands to verify you're ready:

node --version    # Should show v18.x.x or higher
npm --version     # Should show 9.x.x or higher

Lab Overview

StepWhat You'll DoTime
1Set up the project5 min
2Build the MCP server10 min
3Test locally5 min
4Connect to OpenAI10 min

Total time: ~30 minutes


Step 1: Set Up the Project

1.1 Create Your Project Directory

mkdir mcp-weather-server
cd mcp-weather-server

1.2 Initialize the Project

npm init -y

1.3 Configure for ES Modules

Open package.json and replace its contents with:

{
  "name": "mcp-weather-server",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.20.2",
    "zod": "^3.25.76"
  }
}

1.4 Install Dependencies

npm install

You should see the packages install successfully.


Step 2: Build the MCP Server

2.1 Understanding the Architecture

Before we code, here's what we're building:

┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│  OpenAI API     │────▶│  Your MCP Server │────▶│  Weather Data   │
│  (or ChatGPT)   │◀────│  (Node.js)       │◀────│  (mock/real)    │
└─────────────────┘     └──────────────────┘     └─────────────────┘

Key concepts:

  • Tools: Functions your server exposes (like get_weather)
  • Input Schema: What parameters the tool accepts (validated with Zod)
  • Transport: How requests/responses flow (we use Streamable HTTP)

2.2 Create the Server File

Create a new file called server.js and paste the following code:

// server.js - MCP Weather Server
// A simple MCP server that provides weather information

import { createServer } from "node:http";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";

// ============================================
// WEATHER DATA (Mock data for the lab)
// In production, replace with real API calls
// ============================================

const weatherDatabase = {
  "new york": { temp: 72, condition: "Partly Cloudy", humidity: 65, wind: 8 },
  "los angeles": { temp: 78, condition: "Sunny", humidity: 45, wind: 5 },
  "chicago": { temp: 68, condition: "Windy", humidity: 55, wind: 15 },
  "miami": { temp: 85, condition: "Humid", humidity: 80, wind: 10 },
  "seattle": { temp: 62, condition: "Rainy", humidity: 75, wind: 7 },
  "denver": { temp: 70, condition: "Clear", humidity: 30, wind: 6 },
  "boston": { temp: 65, condition: "Cloudy", humidity: 60, wind: 12 },
  "san francisco": { temp: 64, condition: "Foggy", humidity: 70, wind: 9 },
};

// Helper function to get weather data
function getWeatherData(city) {
  const normalizedCity = city.toLowerCase().trim();
  const weather = weatherDatabase[normalizedCity];
  
  if (!weather) {
    return {
      found: false,
      city: city,
      message: `Weather data not available for "${city}". Available cities: ${Object.keys(weatherDatabase).join(", ")}`,
    };
  }
  
  return {
    found: true,
    city: city,
    temperature: weather.temp,
    temperatureUnit: "°F",
    condition: weather.condition,
    humidity: weather.humidity,
    humidityUnit: "%",
    windSpeed: weather.wind,
    windUnit: "mph",
    timestamp: new Date().toISOString(),
  };
}

// ============================================
// MCP SERVER SETUP
// ============================================

function createWeatherServer() {
  // Initialize the MCP server with name and version
  const server = new McpServer({
    name: "weather-server",
    version: "1.0.0",
  });

  // ----------------------------------------
  // TOOL: get_weather
  // This is the main tool our server exposes
  // ----------------------------------------
  server.registerTool(
    "get_weather",  // Tool name (what the model calls)
    {
      title: "Get Weather",
      description: "Get current weather information for a city. Returns temperature, conditions, humidity, and wind speed.",
      
      // Input schema using Zod for validation
      inputSchema: {
        city: z.string().min(1).describe("The city name to get weather for"),
      },
      
      // Metadata for OpenAI integration
      _meta: {
        "openai/toolInvocation/invoking": "Checking weather...",
        "openai/toolInvocation/invoked": "Weather retrieved!",
      },
    },
    
    // The handler function - runs when the tool is called
    async ({ city }) => {
      console.log(`[get_weather] Request for city: ${city}`);
      
      const weatherData = getWeatherData(city);
      
      if (!weatherData.found) {
        return {
          structuredContent: weatherData,
          content: [{ type: "text", text: weatherData.message }],
        };
      }
      
      // Return structured data for the model to use
      return {
        structuredContent: weatherData,
        content: [
          {
            type: "text",
            text: `Weather in ${city}: ${weatherData.temperature}${weatherData.temperatureUnit}, ${weatherData.condition}`,
          },
        ],
      };
    }
  );

  // ----------------------------------------
  // TOOL: list_cities
  // Bonus tool to show available cities
  // ----------------------------------------
  server.registerTool(
    "list_cities",
    {
      title: "List Available Cities",
      description: "Get a list of all cities that have weather data available.",
      inputSchema: {},  // No input required
    },
    async () => {
      const cities = Object.keys(weatherDatabase);
      return {
        structuredContent: { cities, count: cities.length },
        content: [
          {
            type: "text",
            text: `Available cities: ${cities.join(", ")}`,
          },
        ],
      };
    }
  );

  return server;
}

// ============================================
// HTTP SERVER
// Handles incoming requests and routes to MCP
// ============================================

const PORT = process.env.PORT || 3000;
const MCP_PATH = "/mcp";

const httpServer = createServer(async (req, res) => {
  const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
  
  // Log all incoming requests
  console.log(`[${new Date().toISOString()}] ${req.method} ${url.pathname}`);

  // Handle CORS preflight requests
  if (req.method === "OPTIONS") {
    res.writeHead(204, {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "POST, GET, DELETE, OPTIONS",
      "Access-Control-Allow-Headers": "content-type, mcp-session-id",
      "Access-Control-Expose-Headers": "Mcp-Session-Id",
    });
    res.end();
    return;
  }

  // Health check endpoint
  if (req.method === "GET" && url.pathname === "/") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({
      status: "ok",
      server: "MCP Weather Server",
      version: "1.0.0",
      mcpEndpoint: MCP_PATH,
    }));
    return;
  }

  // MCP endpoint - handle all MCP requests
  const MCP_METHODS = new Set(["POST", "GET", "DELETE"]);
  if (url.pathname === MCP_PATH && MCP_METHODS.has(req.method || "")) {
    // Set CORS headers
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id");

    // Create a new server instance for this request
    const server = createWeatherServer();
    
    // Create the transport layer
    const transport = new StreamableHTTPServerTransport({
      sessionIdGenerator: undefined,  // Stateless mode
      enableJsonResponse: true,
    });

    // Clean up when connection closes
    res.on("close", () => {
      transport.close();
      server.close();
    });

    try {
      await server.connect(transport);
      await transport.handleRequest(req, res);
    } catch (error) {
      console.error("[MCP Error]", error);
      if (!res.headersSent) {
        res.writeHead(500, { "Content-Type": "application/json" });
        res.end(JSON.stringify({ error: "Internal server error" }));
      }
    }
    return;
  }

  // 404 for unknown routes
  res.writeHead(404, { "Content-Type": "application/json" });
  res.end(JSON.stringify({ error: "Not found" }));
});

// Start the server
httpServer.listen(PORT, () => {
  console.log(`
╔════════════════════════════════════════════════╗
║     MCP Weather Server is Running! 🌤️          ║
╠════════════════════════════════════════════════╣
║  Local:    http://localhost:${PORT}║  MCP:      http://localhost:${PORT}${MCP_PATH}╠════════════════════════════════════════════════╣
║  Next steps:                                   ║
║  1. Test: curl http://localhost:${PORT}║  2. Tunnel: ngrok http ${PORT}║  3. Connect to OpenAI Responses API            ║
╚════════════════════════════════════════════════╝
  `);
});

2.3 Understanding the Code

Let's break down the key parts:

1. Tool Registration

server.registerTool(
  "get_weather",           // Name the model uses to call it
  {
    title: "Get Weather",  // Human-readable title
    description: "...",    // Helps the model know when to use it
    inputSchema: { ... },  // Zod schema for validation
  },
  async ({ city }) => { }  // Handler function
);

2. Response Structure

return {
  structuredContent: { ... },  // JSON data the model can reason about
  content: [{ type: "text", text: "..." }],  // Human-readable response
};

3. HTTP Transport The server uses StreamableHTTPServerTransport which is the recommended transport for production MCP servers.


Step 3: Test Locally

3.1 Start the Server

npm start

You should see:

╔════════════════════════════════════════════════╗
║     MCP Weather Server is Running! 🌤️          ║
...

3.2 Test the Health Endpoint

Open a new terminal and run:

curl http://localhost:3000

Expected response:

{"status":"ok","server":"MCP Weather Server","version":"1.0.0","mcpEndpoint":"/mcp"}

3.3 Test with MCP Inspector (Optional but Recommended)

The MCP Inspector lets you test your tools interactively:

npx @modelcontextprotocol/inspector@latest http://localhost:3000/mcp

This opens a browser window where you can:

  • See your registered tools
  • Call get_weather with different cities
  • View the responses

Step 4: Connect to OpenAI

4.1 Expose Your Server with ngrok

In a new terminal (keep your server running):

ngrok http 3000

ngrok will show you a public URL like:

Forwarding  https://abc123.ngrok.app -> http://localhost:3000

Copy the HTTPS URL (e.g., https://abc123.ngrok.app)

4.2 Test via OpenAI Responses API

Create a file called test-openai.sh:

#!/bin/bash

# Replace with your values
OPENAI_API_KEY="your-api-key-here"
MCP_SERVER_URL="https://your-ngrok-url.ngrok.app/mcp"

curl https://api.openai.com/v1/responses \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "gpt-4.1",
    "tools": [
      {
        "type": "mcp",
        "server_label": "weather",
        "server_url": "'"$MCP_SERVER_URL"'",
        "require_approval": "never"
      }
    ],
    "input": "What is the weather like in Seattle?"
  }'

Run it:

chmod +x test-openai.sh
./test-openai.sh

4.3 Expected Response

The API will:

  1. Connect to your MCP server
  2. Discover the get_weather tool
  3. Call it with city: "Seattle"
  4. Return the weather data in the response

You should see your server log:

[get_weather] Request for city: Seattle

Challenges: Extend Your Server

Now that you have a working MCP server, try these challenges:

Challenge 1: Add a Forecast Tool (Easy)

Add a get_forecast tool that returns a 3-day forecast.

Hint

Create mock forecast data and register a new tool:

server.registerTool("get_forecast", { ... }, async ({ city }) => { ... });

Challenge 2: Connect to a Real Weather API (Medium)

Replace the mock data with calls to a real weather API like OpenWeatherMap.

Hint
  1. Sign up for a free API key
  2. Use fetch() to call the API
  3. Transform the response to your structured format

Challenge 3: Add Temperature Unit Conversion (Easy)

Add a parameter to return temperature in Celsius or Fahrenheit.

Hint

Update the input schema:

inputSchema: {
  city: z.string(),
  unit: z.enum(["celsius", "fahrenheit"]).optional().default("fahrenheit"),
}

Troubleshooting

Server Won't Start

Error: Cannot find module

  • Run npm install to install dependencies
  • Check that package.json has "type": "module"

Error: Port already in use

  • Change the port: PORT=3001 npm start
  • Or kill the process using port 3000

ngrok Issues

ngrok not found

  • Install ngrok: npm install -g ngrok or download from ngrok.com
  • Run ngrok authtoken YOUR_TOKEN if required

Tunnel disconnects

  • Free tier tunnels expire. Just restart ngrok.

OpenAI API Errors

401 Unauthorized

  • Check your API key is correct
  • Ensure you have Responses API access

MCP connection failed

  • Verify ngrok is running
  • Check the MCP endpoint URL ends with /mcp
  • Look at your server logs for errors

What You Learned

✅ How MCP servers are structured
✅ How to register tools with input schemas
✅ How to return structured content
✅ How to expose your server for external access
✅ How to connect to OpenAI's Responses API


Next Steps

  1. Read OpenAI's MCP Documentation: https://developers.openai.com/apps-sdk/build/mcp-server/
  2. Explore the MCP Specification: https://modelcontextprotocol.io/
  3. Build a ChatGPT App: Add a UI component using the Apps SDK
  4. Add Authentication: Implement OAuth for user-specific data

Resources


Congratulations! 🎉 You've built your first MCP server. Now go build something amazing!