servemcp
v0.1.0
Published
Biblioteca leve e rápida para criar e implantar servidores MCP (Model Context Protocol) em TypeScript.
Maintainers
Readme
servemcp
Biblioteca leve e rápida para criar e implantar servidores MCP (Model
Context Protocol) em TypeScript. É uma camada fina sobre o SDK oficial
(@modelcontextprotocol/sdk) que remove boilerplate comum — registrar
tools/resources/prompts, tratar erros e subir um transporte — sem esconder o
SDK: o servidor bruto continua acessível via .raw para qualquer coisa
avançada.
Por que
- Leve: zero dependências além do SDK oficial e do
zod; o transporte HTTP usanode:httppuro, sem Express. - Rápida: build ESM único via
tsup, sem camadas extras em runtime. - Segura por padrão: erros lançados dentro de uma tool viram um
isErrorresult em vez de derrubar a conexão; logs sempre vão parastderr(nunca poluem ostdout, que no transporte stdio é reservado para mensagens JSON-RPC); o HTTP escuta em127.0.0.1por padrão. - stdio e HTTP prontos:
serveStdio()para servidores lançados por CLI (Claude Desktop, Claude Code, etc.) eserveHttp()para servir Streamable HTTP com sessões multiusuário isoladas corretamente.
Instalação
npm install servemcp zodUso rápido
import { createServer, z } from "servemcp";
const server = createServer({ name: "weather-server", version: "1.0.0" });
server.tool(
"get_weather",
{
description: "Retorna a previsão do tempo para uma cidade.",
input: { city: z.string().describe("Nome da cidade") },
},
async ({ city }) => ({
content: [{ type: "text", text: `Ensolarado em ${city}, 24°C.` }],
}),
);
await server.serveStdio();Isso já é um servidor MCP completo, pronto para ser referenciado no
claude_desktop_config.json (ou equivalente) apontando para este script.
Registrando tools, resources e prompts
Tools
import { toolError } from "servemcp";
server.tool(
"divide",
{ description: "Divide a por b.", input: { a: z.number(), b: z.number() } },
async ({ a, b }) => {
if (b === 0) return toolError("Divisão por zero não é permitida.");
return { content: [{ type: "text", text: String(a / b) }] };
},
);Exceções não tratadas dentro do handler são capturadas automaticamente e
retornadas como isError: true — o modelo vê a falha e pode reagir, em vez
da conexão simplesmente cair.
Use os helpers text(...) e json(value) para montar resultados comuns:
import { text, json } from "servemcp";
server.tool("ping", {}, async () => text("pong"));
server.tool("status", {}, async () => json({ ok: true, uptime: process.uptime() }));Resources
server.resource(
"config",
"config://app",
{ title: "Configuração da app", mimeType: "application/json" },
() => ({ text: JSON.stringify({ debug: false }) }),
);Prompts
server.prompt(
"resumir",
{ description: "Gera um prompt para resumir um texto.", args: { texto: z.string() } },
({ texto }) => ({
messages: [
{ role: "user", content: { type: "text", text: `Resuma o texto a seguir:\n\n${texto}` } },
],
}),
);server.tool(), server.resource() e server.prompt() retornam a própria
instância (this), então dá para encadear:
createServer({ name: "x", version: "1.0.0" })
.tool("a", { input: {} }, async () => text("a"))
.tool("b", { input: {} }, async () => text("b"))
.serveStdio();Transportes
stdio (padrão para clientes desktop/CLI)
await server.serveStdio();Conecta em stdin/stdout e trata SIGINT/SIGTERM para fechar a conexão de
forma limpa.
HTTP (Streamable HTTP)
const handle = await server.serveHttp({ port: 3000, cors: true });
console.error(`ouvindo em ${handle.url}`);
// depois, para desligar:
await handle.close();Opções de serveHttp:
| Opção | Padrão | Descrição |
|---|---|---|
| port | 3000 (ou process.env.PORT) | Porta TCP |
| host | 127.0.0.1 | Interface de rede. Use 0.0.0.0 só com allowedHosts configurado |
| path | /mcp | Caminho do endpoint |
| mode | "stateful" | "stateful" mantém sessão por cliente (Mcp-Session-Id) com stream SSE via GET; "stateless" cria uma sessão nova a cada POST |
| cors | false | Responde CORS permissivo para clientes de navegador |
| allowedHosts | — | Lista de Host permitidos (proteção contra DNS rebinding ao expor além de localhost) |
Internamente, cada sessão HTTP recebe sua própria instância do servidor MCP subjacente (o SDK permite apenas um transporte ativo por instância) — isso é feito automaticamente reaplicando os registros de tools/resources/prompts, então múltiplos clientes concorrentes nunca interferem uns nos outros.
Escape hatch
Qualquer recurso do SDK que não tenha um atalho aqui continua acessível via
server.raw, que é a instância padrão de McpServer:
server.raw.sendLoggingMessage({ level: "info", data: "oi" });Exemplos
Veja examples/stdio-server.js e examples/http-server.js. Rode com:
npm run build
npm run example:stdio # sobe um servidor via stdio (aguarda um cliente MCP)
npm run example:http # sobe um servidor HTTP em http://127.0.0.1:3000/mcpDesenvolvimento
npm run typecheck
npm run build