gkerensky/mcpinabox
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.
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_weathertool 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
| Step | What You'll Do | Time |
|---|---|---|
| 1 | Set up the project | 5 min |
| 2 | Build the MCP server | 10 min |
| 3 | Test locally | 5 min |
| 4 | Connect to OpenAI | 10 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_weatherwith 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:
- Connect to your MCP server
- Discover the
get_weathertool - Call it with
city: "Seattle" - 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
- Sign up for a free API key
- Use
fetch()to call the API - 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 installto install dependencies - Check that
package.jsonhas"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 ngrokor download from ngrok.com - Run
ngrok authtoken YOUR_TOKENif 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
- Read OpenAI's MCP Documentation: https://developers.openai.com/apps-sdk/build/mcp-server/
- Explore the MCP Specification: https://modelcontextprotocol.io/
- Build a ChatGPT App: Add a UI component using the Apps SDK
- Add Authentication: Implement OAuth for user-specific data
Resources
Congratulations! 🎉 You've built your first MCP server. Now go build something amazing!