Are you the author? Sign in to claim
Ultra-lightweight MCP server for Bun. Zero Express, zero ajv, zero jose. Full protocol compliance with @modelcontextprot
Ultra-lightweight MCP server for Bun. Zero Express, zero ajv, zero jose.
Full protocol compliance. ~5MB heap. Drop-in replacement for @modelcontextprotocol/sdk.
Quick Start · Why · Performance · API · Migration · Contributing
The official @modelcontextprotocol/sdk pulls in Express 5, ajv, jose, cors and 27+ transitive dependencies — adding ~150-200MB to your heap. If you're building on Bun + Hono, you don't need any of that.
mcp-lite extracts the exact same MCP protocol implementation from the official SDK, strips the bloat, and gives you:
.d.ts declarations for any consumer@modelcontextprotocol/sdk | mcp-lite | |
|---|---|---|
| Dependencies | Express 5, ajv, jose, cors, eventsource... | zod + hono (peer) |
| Heap footprint | ~150-200MB | ~5MB |
| Module load | ~190ms | ~93ms |
| Transports | stdio, SSE, Streamable HTTP | stdio, Streamable HTTP |
| Protocol version | 2025-11-25 | 2025-11-25 |
| TypeScript | Full | Full |
| Runtime | Node.js, Bun, Deno | Bun (optimized) |
| OAuth | Built-in | Not included (use Hono middleware) |
| Package size | ~2.5MB unpacked | ~50KB unpacked |
bun add mcp-lite zod
import { McpServer, StdioServerTransport } from "mcp-lite";
import { z } from "zod";
const server = new McpServer({
name: "my-server",
version: "1.0.0",
});
server.tool(
"get_weather",
"Get current weather for a city",
{ city: z.string().describe("City name") },
async ({ city }) => ({
content: [{ type: "text", text: `Weather in ${city}: Sunny, 72°F` }],
})
);
const transport = new StdioServerTransport();
await server.connect(transport);
bun run my-server.ts
That's it. Your MCP server is running over stdio.
import { McpServer, WebStandardStreamableHTTPServerTransport } from "mcp-lite";
import { Hono } from "hono";
import { z } from "zod";
const server = new McpServer({
name: "my-server",
version: "1.0.0",
});
server.tool(
"search",
"Search for items",
{ query: z.string() },
async ({ query }) => ({
content: [{ type: "text", text: `Results for: ${query}` }],
})
);
const transport = new WebStandardStreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
});
await server.connect(transport);
const app = new Hono();
app.all("/mcp", (c) => transport.handleRequest(c.req.raw));
export default app;
mcp-lite is designed for cold-start performance. Here's what happens when your server starts:
Module load: 93ms (vs 190ms official SDK)
types-base: 0.1ms (ErrorCode, McpError, type guards)
protocol: 12ms (lazy Maps, inlined isTerminal)
server: 8ms (lazy task helpers)
zod: 90ms (DEFERRED — only loads on first message)
Key optimizations:
tools/call, not at import timeisTerminal() inlined to eliminate experimental/tasks/interfaces.js chainZod.safeParse()McpServerHigh-level server with declarative registration.
const server = new McpServer({
name: "my-server", // Required
version: "1.0.0", // Required
title: "My Server", // Optional
description: "...", // Optional
instructions: "...", // Optional
});
server.tool(name, description?, schema?, annotations?, handler)Register a tool.
// Full signature
server.tool(
"get_weather",
"Get weather for a city",
{ city: z.string() },
{ readOnlyHint: true },
async ({ city }, extra) => ({
content: [{ type: "text", text: `Sunny in ${city}` }],
})
);
// Shorthand (no description)
server.tool("ping", {}, async () => ({
content: [{ type: "text", text: "pong" }],
}));
// Shorthand (no parameters)
server.tool("version", "Get version", async () => ({
content: [{ type: "text", text: "1.0.0" }],
}));
server.resource(uri, nameOrHandler?, handler?)Register a static resource.
server.resource("config://app", "App Config", async (uri) => ({
contents: [{ uri, mimeType: "application/json", text: '{"key":"value"}' }],
}));
server.resourceTemplate(uriTemplate, handler)Register a dynamic resource template.
server.resourceTemplate("users://{userId}/profile", async (uri, params) => ({
contents: [{ uri, text: `Profile for ${params.userId}` }],
}));
server.prompt(name, description?, handler)Register a prompt.
server.prompt("greeting", "Generate a greeting", async (args) => ({
messages: [{
role: "user",
content: { type: "text", text: `Hello ${args.name}!` },
}],
}));
server.sendToolListChanged();
server.sendResourceListChanged();
server.sendResourceUpdated("resource://my-resource");
server.sendPromptListChanged();
server.sendLogMessage("info", "Something happened");
ServerLow-level server with direct protocol handling.
import { Server } from "mcp-lite";
const server = new Server(
{ name: "my-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler("tools/list", async () => ({
tools: [{ name: "ping", description: "Ping pong" }],
}));
server.setRequestHandler("tools/call", async (request) => ({
content: [{ type: "text", text: "pong" }],
}));
StdioServerTransportimport { StdioServerTransport } from "mcp-lite";
const transport = new StdioServerTransport();
await server.connect(transport);
WebStandardStreamableHTTPServerTransportimport { WebStandardStreamableHTTPServerTransport } from "mcp-lite";
const transport = new WebStandardStreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
enableJsonResponse: true, // Return JSON instead of SSE
onsessioninitialized: (id) => console.log(`Session ${id}`),
onsessionclosed: (id) => console.log(`Closed ${id}`),
});
await server.connect(transport);
// In your Hono app
app.all("/mcp", (c) => transport.handleRequest(c.req.raw));
InMemoryTransportFor testing.
import { InMemoryTransport } from "mcp-lite";
const transport = new InMemoryTransport();
await server.connect(transport);
import { McpError, ErrorCode } from "mcp-lite";
throw new McpError(ErrorCode.ToolNotFound, "Tool not found: my-tool");
import {
isJSONRPCRequest,
isJSONRPCNotification,
isJSONRPCResponse,
isJSONRPCError,
isInitializeRequest,
isInitializedNotification,
isCompletable,
} from "mcp-lite";
if (isJSONRPCRequest(message)) {
// handle request
}
- import { McpServer, StdioServerTransport } from "@modelcontextprotocol/sdk";
+ import { McpServer, StdioServerTransport } from "mcp-lite";
- import express from "express";
- import cors from "cors";
+ import { Hono } from "hono";
- const transport = new StreamableHTTPServerTransport({
- sessionIdGenerator: () => crypto.randomUUID(),
- });
+ const transport = new WebStandardStreamableHTTPServerTransport({
+ sessionIdGenerator: () => crypto.randomUUID(),
+ });
- app.use("/mcp", cors());
- app.all("/mcp", express.json(), (req, res) => transport.handleRequest(req, res));
+ app.all("/mcp", (c) => transport.handleRequest(c.req.raw));
Everything else (tool registration, resource handling, prompts) works exactly the same.
Implements MCP spec 2025-11-25:
| Feature | Status |
|---|---|
Lifecycle (initialize, initialized, ping) | ✅ |
Tools (tools/list, tools/call) | ✅ |
Resources (resources/list, resources/read) | ✅ |
| Resource Templates | ✅ |
Prompts (prompts/list, prompts/get) | ✅ |
Completions (completion/complete) | ✅ |
Logging (logging/setLevel) | ✅ |
| Progress notifications | ✅ |
| Cancellation | ✅ |
| Subscriptions | ✅ |
| Pagination (cursor-based) | ✅ |
| Streamable HTTP (POST/GET/DELETE) | ✅ |
| SSE streaming | ✅ |
| Session management | ✅ |
| Tool annotations | ✅ |
mcp-lite/
├── src/
│ ├── index.js # Barrel exports (from types-base.js)
│ ├── index.d.ts # Barrel type declarations
│ ├── types-base.js # Non-Zod exports (0ms load)
│ ├── types-base.d.ts # Non-Zod type declarations
│ ├── types.js # Zod schemas (lazy-loaded)
│ ├── inMemory.js # InMemoryTransport
│ ├── server/
│ │ ├── mcp.js # McpServer (high-level)
│ │ ├── index.js # Server (low-level)
│ │ ├── stdio.js # StdioServerTransport
│ │ ├── webStandardStreamableHttp.js # HTTP transport
│ │ ├── completable.js # Completable helper
│ │ ├── zod-compat.js # Zod v3/v4 compatibility
│ │ └── zod-json-schema-compat.js # Zod → JSON Schema
│ ├── shared/
│ │ ├── protocol.js # Protocol base class
│ │ ├── transport.js # Transport interface
│ │ └── stdio.js # Shared stdio helpers
│ ├── validation/
│ │ ├── ajv-provider.js # PassthroughJsonSchemaValidator
│ │ └── index.js # Validation barrel
│ └── experimental/
│ └── tasks/ # Task-augmented execution
├── FEATURES.md # Full feature list & limitations
├── package.json
└── tsconfig.json
tools/call has ~90ms overhead while Zod loadsContributions are welcome! Please read our Contributing Guidelines before submitting a PR.
# Clone the repo
git clone https://github.com/srsergi0/mcp-lite.git
# Install dependencies
bun install
# Run typecheck
bun tsc --noEmit
# Run tests
bun test
If you discover a security vulnerability, please report it responsibly. See SECURITY.md for details.
Built with ❤️ for the MCP community.
Run Claude Code as an MCP server so any agent can delegate coding tasks to it
Browser automation using accessibility snapshots instead of screenshots
Google's universal MCP server supporting PostgreSQL, MySQL, MongoDB, Redis, and 10+ databases
Official GitHub integration for repos, issues, PRs, and CI/CD workflows