Building MCP Servers
This guide walks through building a production-ready MCP server from scratch.
Project Setup
Section titled “Project Setup”-
Create a new project
Terminal window mkdir my-mcp-servercd my-mcp-serverbun init -y -
Install dependencies
Terminal window bun add futurity-mcp zodbun add -d typescript @types/bun -
Create tsconfig.json
{"compilerOptions": {"target": "ESNext","module": "ESNext","moduleResolution": "bundler","strict": true,"esModuleInterop": true,"skipLibCheck": true,"outDir": "dist"},"include": ["src/**/*"]} -
Create entry point
Create
src/index.ts:import { mcp } from "futurity-mcp";const app = mcp({name: "my-mcp-server",version: "1.0.0",});app.listen(3000);console.log("MCP server running on port 3000");
Designing Tools
Section titled “Designing Tools”Principles
Section titled “Principles”Good MCP tools follow these principles:
- Single responsibility: One tool, one job
- Clear descriptions: AI must understand when to use it
- Validated inputs: Use Zod for type safety
- Meaningful outputs: Return structured, useful data
Example: Weather Service
Section titled “Example: Weather Service”import { z } from "zod";import { mcp } from "futurity-mcp";
const app = mcp({ name: "weather-service", version: "1.0.0", instructions: ` This service provides weather information. Use get_weather for current conditions. Use get_forecast for upcoming weather. `,});
// Current weatherapp.tool("get_weather", { description: `Get current weather for a location. Returns temperature, conditions, humidity, and wind. Use this when user asks about current weather.`, input: z.object({ location: z.string().describe("City name or coordinates"), units: z.enum(["celsius", "fahrenheit"]).default("celsius"), }), handler: async ({ location, units }) => { // Call weather API const weather = await fetchWeather(location, units); return { location: weather.name, temperature: weather.temp, units, conditions: weather.conditions, humidity: weather.humidity, wind_speed: weather.wind, }; },});
// Forecastapp.tool("get_forecast", { description: `Get weather forecast for upcoming days. Returns daily forecasts with high/low temperatures. Use this when user asks about future weather.`, input: z.object({ location: z.string(), days: z.number().min(1).max(7).default(5), }), handler: async ({ location, days }) => { const forecast = await fetchForecast(location, days); return { location, forecast: forecast.map(day => ({ date: day.date, high: day.high, low: day.low, conditions: day.conditions, })), }; },});Tool Description Best Practices
Section titled “Tool Description Best Practices”// ❌ Bad: Vague descriptionapp.tool("process", { description: "Process data", // ...});
// ✅ Good: Clear and specificapp.tool("analyze_sales_data", { description: `Analyze sales data to identify trends and insights. Input: Array of sales records with date, amount, and product. Output: Summary statistics, top products, and trend analysis. Use when user wants to understand sales performance.`, // ...});Error Handling
Section titled “Error Handling”Returning Errors
Section titled “Returning Errors”app.tool("get_user", { description: "Get user by ID", input: z.object({ id: z.string() }), handler: async ({ id }) => { const user = await db.getUser(id);
if (!user) { return { error: true, message: `User with ID ${id} not found`, }; }
return { user }; },});Throwing Errors
Section titled “Throwing Errors”For unexpected errors, throw:
app.tool("risky_operation", { description: "Perform a risky operation", handler: async () => { try { const result = await externalApi.call(); return { result }; } catch (error) { throw new Error(`External API failed: ${error.message}`); } },});Working with External APIs
Section titled “Working with External APIs”HTTP Requests
Section titled “HTTP Requests”app.tool("fetch_data", { description: "Fetch data from external API", input: z.object({ endpoint: z.string() }), handler: async ({ endpoint }) => { const response = await fetch(`https://api.example.com/${endpoint}`, { headers: { "Authorization": `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json", }, });
if (!response.ok) { return { error: true, message: `API error: ${response.status}` }; }
return await response.json(); },});Database Access
Section titled “Database Access”import Database from "bun:sqlite";
const db = new Database("data.db");
app.tool("query_customers", { description: "Search customers by name", input: z.object({ name: z.string() }), handler: async ({ name }) => { const customers = db .query("SELECT * FROM customers WHERE name LIKE ?") .all(`%${name}%`); return { customers }; },});Authentication
Section titled “Authentication”Token-Based Auth
Section titled “Token-Based Auth”const app = mcp({ name: "secure-server", version: "1.0.0", auth: async (req) => { const token = req.headers.get("authorization")?.replace("Bearer ", "");
if (!token) { return false; }
// Verify token try { const payload = await verifyJwt(token); // Attach user to request context if needed return true; } catch { return false; } },});OAuth Integration
Section titled “OAuth Integration”For user-authenticated services:
const app = mcp({ name: "oauth-server", version: "1.0.0", oauth: { issuer: process.env.OAUTH_ISSUER, authorizationEndpoint: `${process.env.OAUTH_ISSUER}/authorize`, tokenEndpoint: `${process.env.OAUTH_ISSUER}/token`, scopesSupported: ["read", "write"], }, auth: async (req) => { const token = req.headers.get("authorization")?.replace("Bearer ", ""); if (!token) return false;
// Validate against OAuth provider return await validateOAuthToken(token); },});Testing
Section titled “Testing”Unit Tests
Section titled “Unit Tests”import { describe, test, expect } from "bun:test";
describe("weather tools", () => { test("get_weather returns valid structure", async () => { const result = await weatherHandler({ location: "London", units: "celsius" });
expect(result).toHaveProperty("temperature"); expect(result).toHaveProperty("conditions"); expect(result.units).toBe("celsius"); });});Integration Tests
Section titled “Integration Tests”import { describe, test, expect, beforeAll, afterAll } from "bun:test";
let server: ReturnType<typeof app.listen>;
beforeAll(async () => { server = await app.listen(3001);});
afterAll(async () => { await server.stop();});
test("MCP endpoint responds", async () => { const response = await fetch("http://localhost:3001/mcp", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", method: "tools/list", id: 1, }), });
expect(response.ok).toBe(true);});Discovery Testing
Section titled “Discovery Testing”bun run discover http://localhost:3000/mcpDeployment
Section titled “Deployment”Environment Variables
Section titled “Environment Variables”// Use environment variables for configurationconst app = mcp({ name: process.env.SERVER_NAME || "my-server", version: process.env.SERVER_VERSION || "1.0.0",});
// API keys and secretsconst API_KEY = process.env.EXTERNAL_API_KEY;if (!API_KEY) { throw new Error("EXTERNAL_API_KEY is required");}Docker
Section titled “Docker”FROM oven/bun:1
WORKDIR /app
COPY package.json bun.lockb ./RUN bun install --frozen-lockfile
COPY . .
EXPOSE 3000
CMD ["bun", "run", "src/index.ts"]Health Checks
Section titled “Health Checks”// Add a health check toolapp.tool("health", { description: "Check server health", handler: async () => { return { status: "healthy", timestamp: new Date().toISOString(), version: "1.0.0", }; },});Production Checklist
Section titled “Production Checklist”- All tools have clear descriptions
- Input validation with Zod on all tools
- Error handling for all external calls
- Authentication configured
- CORS configured for production origins
- Environment variables for all secrets
- Logging for debugging
- Health check endpoint
- Rate limiting considered
- Documentation for tool usage
Example Projects
Section titled “Example Projects”See complete examples in the repository:
bun examples/cors.ts # CORS configurationbun examples/oauth.ts # OAuth metadatabun examples/stateful.ts # Stateful serverbun examples/todo-app.ts # CRUD operationsbun examples/calculator.ts # Math toolsbun examples/filesystem.ts # Virtual filesystembun examples/weather-api.ts # External API integrationbun examples/database.ts # Database operationsbun examples/monday.ts # monday.com integration