About In A Box
In A Box is an MCP server published by pavlinhristov in the Developer Tools category: mCP server: In A Box. It has been installed 0 times through Conduid.
The repository has 7 stars and 3 forks, with the last commit 9 months ago. Six months or more without a commit doesn't mean the server is broken, but check the open issues (0) before depending on it in production.
Install
npx mcp-in-a-boxThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about In A Box
Powered by Claude · Grounded in docs
Security checks
- ·README presentNot checked yet.
- ·License declaredNot checked yet.
- ·Tests presentNot checked yet.
- ·Dependencies pinnedNot checked yet.
- ·No dynamic code executionNot checked yet.
- !Scoped permissionsDoesn't declare a permission scope. Assume it can do anything its process can.
README
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.
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.
- 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.
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!
README mirrored from the source repository 2 days ago. The original is authoritative.