Dev Tools

Tutorial: Como Criar um MCP Server em TypeScript (Passo a Passo)

Aprenda a construir do zero o seu próprio servidor Model Context Protocol (MCP) utilizando TypeScript e o SDK oficial da Anthropic.

08 de março de 20268 min de leituraDevThru

Após compreendermos o conceito no Guia sobre Model Context Protocol, é chegada a hora de colocarmos a mão na massa.

Seja provendo acesso ao seu banco de dados interno ou manipulando arquivos, a criação de um Servidor MCP personalizado confere "superpoderes" à sua IDE e aos seus LLMs favoritos. E a melhor maneira de iniciar hoje é por meio do Node.js com TypeScript.

Pré-requisitos e Instalação

Para construir a fundação, utilizaremos o SDK TypeScript Oficial do MCP.

Inicialize o seu projeto e instale o pacote principal:

npm init -y
npm install @modelcontextprotocol/sdk

Para um projeto moderno e escalável, recomendamos o formato ECMAScript Modules (ESM) no package.json e o TypeScript devidamente configurado (com tipos para NodeJS e ts-node ou build rápido via tsup/esbuild).

Estrutura Base de um Servidor MCP

A configuração inicial do seu arquivo server.ts envolverá injetar dados do seu projeto e lidar com os ciclos vitais (Transport). Pela nossa abordagem usaremos a conexão padrão recomendada para IDEs: o StdioServerTransport (transporte focado em terminal standard I/O).

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  ListToolsRequestSchema,
  CallToolRequestSchema,
  Tool,
} from "@modelcontextprotocol/sdk/types.js";

const server = new Server(
  {
    name: "meu-primeiro-mcp-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},    // Habilitando Ferramentas Interativas Actions
      prompts: {},  // (Opção) Habilitando Prompts e templates
      resources: {} // (Opção) Habilitando extração de resources URIs
    },
  }
);
⚠️ Dica TypeScript: No TypeScript precisamos explicitamente declarar tools: {} na propriedade capabilities, caso contrário o SDK deduzirá que o seu servidor destina-se puramente a Prompts ou Resources, ignorando os callbacks de Tools.

Definindo sua Tool (Ferramenta IA)

Digamos que você quer permitir ao modelo de Inteligência Artificial consultar o clima em real-time a partir da sua infraestrutura. Para o LLM entender, você precisa usar a estrutura rígida de JSON Schema para declarar o formato (Tool Definition):

const WEATHER_TOOL: Tool = {
  name: "get_internal_weather",
  description: "Busca o alerta de tempestades atual do sensor privado do escritório.",
  inputSchema: {
    type: "object",
    properties: {
      location: {
        type: "string",
        description: "Local que a IA quer saber o clima ex: 'São Paulo', 'Data Center'",
      },
    },
    required: ["location"],
  },
};

Registrando os Handlers

O servidor não fará nada até você amarrar as "pontas": criar o handler dizendo pro SDK retornar a lista de ferramentas declaradas (List) e criar o executor lógico delas (Call). O uso da biblioteca zod é extremamente benéfico para injetar Typesafety durante o executor, embora não seja obrigatório.


// 1. O LLM descobrirá da sua existência pedindo a lista.
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return { tools: [WEATHER_TOOL] };
});

// 2. Quando o LLM decidir 'tocar' no método get_internal_weather
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === "get_internal_weather") {
    // Garantimos e destruturamos o location do JSON injetado pela IA
    const location = String(args?.location);

    // Faça seu processamento de Backend Real, fetch na DB, etc.
    const resultado = location === 'Data Center' 
        ? "Tempestade na sala do Servidor: ALERTA CRÍTICO." 
        : `Previsão para ${location} é limpo.`

    // Devolvemos o log para o modelo na estrutura nativa obrigatória
    return {
      content: [{ type: "text", text: resultado }],
    };
  }

  throw new Error("Tool desconhecida pelo Servidor");
});

Iniciando o Transporte

A fase final consiste em atar o Stdio transport em uma coroutine (main), garantindo os logs via Error Output para você conseguir debugar o fluxo (já que a saída padrão console.log() do Stdio é sequestrada inteiramente para o tráfego JSON do MCP).

async function runServer() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  
  // Imprimiremos em sys.stderr pra IDE não quebrar
  console.error("🚀 MCP Server TypeScript inciado perfeitamente no Studio");
}

runServer().catch(console.error);

Adicionando na sua IDE (Cursor / Claude Desktop)

Faça o build ou instanciamento nativo usando TS-Node para gerar o executável final em seu ambiente local. Em seguida:

1. Para o Claude Desktop, edite o arquivo primário config: ~/Library/Application Support/Claude/claude_desktop_config.json (Mac) ou %APPDATA%/Claude/claude_desktop_config.json (Windows). 2. Adicione os comandos para a inicialização no dict mcpServers:


{
  "mcpServers": {
    "weather_office": {
      "command": "node",
      "args": [
        "C:/Seu/Projeto/build/server.js"
      ]
    }
  }
}

Reinicie o Desktop Client (ou o reload no Cursor) e você notará a get_internal_weather devidamente autorizada no chat.

A partir daqui, os seus agentes são irrestritos, possuindo total autonomia e visão analítica sobre tabelas no PostgreSQL da empresa e execuções profundas pelo File System, sob os limites da sua arquitetura Typescript.

MCPTypeScriptTutorialSDKAnthropicClaude DesktopServidor de IA