| name | mcp-builder |
| description | Build MCP (Model Context Protocol) servers with TypeScript and Bun. Use when the user wants to create an MCP server, add tools to Claude/Codex, or integrate external services. |
MCP Server Builder
MCP servers expose tools, resources, and prompts through a standard protocol. In this project, build them as Bun/TypeScript programs.
Project Setup
mkdir my-mcp-server
cd my-mcp-server
bun init -y
bun add @modelcontextprotocol/sdk
bun add -d @types/bun typescript
Basic Server
#!/usr/bin/env bun
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
const server = new Server(
{ name: "my-server", version: "1.0.0" },
{ capabilities: { tools: {} } },
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "hello",
description: "Say hello to someone.",
inputSchema: {
type: "object",
properties: { name: { type: "string" } },
required: ["name"],
},
},
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const args = request.params.arguments ?? {};
if (request.params.name === "hello") {
return {
content: [{ type: "text", text: `Hello, ${String(args.name ?? "world")}!` }],
};
}
throw new Error(`Unknown tool: ${request.params.name}`);
});
await server.connect(new StdioServerTransport());
Register The Server
{
"mcpServers": {
"my-server": {
"command": "bun",
"args": ["/absolute/path/to/my-mcp-server/src/index.ts"]
}
}
}
External API Tool
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== "weather") {
throw new Error(`Unknown tool: ${request.params.name}`);
}
const city = String(request.params.arguments?.city ?? "");
const url = new URL("https://api.weatherapi.com/v1/current.json");
url.searchParams.set("key", process.env.WEATHER_API_KEY ?? "");
url.searchParams.set("q", city);
const response = await fetch(url);
if (!response.ok) {
return { content: [{ type: "text", text: `Error: ${response.status}` }], isError: true };
}
const data = await response.json();
return { content: [{ type: "text", text: `${city}: ${data.current.temp_c}C` }] };
});
SQLite Resource With Bun
import { Database } from "bun:sqlite";
import { ReadResourceRequestSchema, ListResourcesRequestSchema } from "@modelcontextprotocol/sdk/types.js";
const db = new Database("data.db", { readonly: true });
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [{ uri: "sqlite://users", name: "Users table", mimeType: "application/json" }],
}));
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
if (request.params.uri !== "sqlite://users") throw new Error("Unknown resource");
const rows = db.query("select id, name from users limit 20").all();
return {
contents: [{ uri: request.params.uri, mimeType: "application/json", text: JSON.stringify(rows, null, 2) }],
};
});
Testing
bunx @modelcontextprotocol/inspector bun src/index.ts
printf '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}\n' | bun src/index.ts
Best Practices
- Give every tool a narrow, explicit input schema.
- Validate input before hitting files, shells, or external services.
- Prefer
fetch, Bun.file, Bun.write, Bun.spawn, bun:sqlite, and Bun.sql.
- Return structured errors with
isError: true when a tool fails.
- Keep secrets in the environment; Bun loads
.env automatically.
- Make tool calls idempotent when possible.