Como criar um servidor MCP local e conectá-lo ao Claude (com código funcional)

Crie um servidor MCP local a partir de uma pasta vazia: instale o SDK do TypeScript, registre duas ferramentas que funcionam, teste-as no MCP Inspector e depois conecte o servidor ao Claude Desktop e ao Claude Code. Inclui arquivos de configuração, correções de caminhos no Windows e uma lista de verificação para as falhas que quebram a maioria das configurações.

Como criar um servidor MCP local e conectá-lo ao Claude (com código funcional)
Cristian Da Conceicao
Fundador do Picasso IA

A maioria dos tutoriais de MCP para no "hello world" e deixa você olhando para um selo vermelho Disconnected. Este termina com um servidor rodando na sua própria máquina, o Claude chamando as ferramentas dele e uma lista de verificação curta para as falhas que atrapalham a maioria das pessoas. Você vai escrever cerca de 70 linhas de TypeScript, testá-las em um inspetor no navegador e conectar o resultado tanto ao Claude Desktop quanto ao Claude Code.

O servidor é uma pequena ferramenta de anotações: o Claude pode salvar uma nota em um arquivo JSON no seu disco e pesquisá-la depois. Ele é propositalmente simples, porque a infraestrutura é a mesma seja qual for o trabalho das suas ferramentas, seja ler um arquivo de notas, consultar um banco de dados ou chamar um modelo de imagem. Compilei e executei o arquivo do servidor abaixo com a versão 1.32 do SDK do TypeScript, então o código compila exatamente como mostrado.

Desenvolvedor digitando em um terminal de notebook sobre uma mesa de carvalho à luz da manhã

O que você está realmente construindo

MCP em dois parágrafos

O Model Context Protocol (MCP) é um padrão aberto, apresentado pela Anthropic em novembro de 2024, que permite a um aplicativo de IA conversar com ferramentas externas de um jeito único e consistente. Em vez de cada aplicativo inventar seu próprio formato de plugin, um servidor MCP expõe capacidades, e um cliente MCP, como o Claude Desktop ou o Claude Code, as encontra e as chama. As mensagens são JSON-RPC 2.0 simples, então um servidor pode ser escrito em qualquer linguagem.

Um servidor pode oferecer três tipos de coisas:

  • Tools (ferramentas): funções que o modelo pode chamar, como "salvar uma nota" ou "executar uma consulta".
  • Resources (recursos): dados somente leitura que o aplicativo pode carregar como contexto, como um arquivo ou um registro de banco de dados.
  • Prompts: modelos de prompt reutilizáveis que o usuário aciona de propósito.

Este tutorial se atém a ferramentas, porque são as mais simples de testar e as mais úteis já no primeiro dia.

Vista de cima de um caderno com um diagrama desenhado à mão de três caixas unidas por setas

Por que rodar localmente

Um servidor local roda como um processo filho do cliente, na sua máquina, com seus arquivos e suas permissões. Nada fica exposto à internet, não há conta de hospedagem e a iteração é rápida: edite um arquivo, recompile, reinicie. O transporte usado é o stdio: o cliente inicia seu programa e se comunica com ele pela entrada e saída padrão.

stdio (local)Streamable HTTP (remoto)
Onde rodaProcesso filho no seu computadorUm servidor web que você ou outra pessoa hospeda
Quem pode acessarSomente o aplicativo que o iniciouQualquer um com a URL e as credenciais
AutenticaçãoNenhuma, ele herda sua conta de usuárioObrigatória (OAuth ou tokens)
Ideal paraFerramentas pessoais, acesso a arquivos, desenvolvimentoFerramentas compartilhadas de equipe, integrações SaaS

💡 Bom saber: O Streamable HTTP substituiu o antigo transporte HTTP+SSE na revisão 2025-03-26 da especificação. E, como um navegador não consegue iniciar um processo no seu computador, um servidor stdio não pode ser adicionado ao claude.ai no navegador. Só servidores remotos funcionam ali.

Vista em ângulo baixo de um mini PC cinza-grafite ao lado de um notebook prateado, unidos por um cabo USB-C trançado

Prepare o projeto

O que você precisa ter instalado

FerramentaVersãoVerifique com
Node.js20 LTS ou mais recentenode --version
npmVem com o Nodenpm --version
Claude DesktopMais recente, macOS ou WindowsConfigurações, depois Developer
Claude Code (opcional)Mais recenteclaude --version

O Claude Desktop está disponível para macOS e Windows. No Linux, use a alternativa do Claude Code na seção de conexão abaixo; o servidor em si é idêntico.

Crie e configure o projeto

mkdir local-notes-mcp && cd local-notes-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
mkdir src

Abra package.json e adicione três coisas ao lado das dependências que o npm criou: a flag de módulo ES e dois scripts.

{
  "name": "local-notes-mcp",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node build/index.js"
  }
}

Depois crie tsconfig.json na raiz do projeto:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "build",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "types": ["node"]
  },
  "include": ["src"]
}

⚠️ Atenção: O TypeScript 7, a versão que o npm instala hoje, não carrega mais pacotes @types sozinho. Sem a linha "types": ["node"], você recebe Cannot find name 'process' e erros semelhantes em todo import do Node.

Close-up das mãos de um desenvolvedor pousadas no meio da digitação, sobre uma mesa iluminada por uma lâmpada quente

Escreva suas primeiras duas ferramentas

O arquivo completo do servidor

Salve isto como src/index.ts. Ele expõe save_note e search_notes, e guarda tudo em um único arquivo JSON.

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { promises as fs } from "node:fs";
import os from "node:os";
import path from "node:path";

const NOTES_DIR = process.env.NOTES_DIR ?? path.join(os.homedir(), "mcp-notes");
const NOTES_FILE = path.join(NOTES_DIR, "notes.json");

type Note = { id: number; title: string; body: string; createdAt: string };

async function readNotes(): Promise<Note[]> {
  try {
    return JSON.parse(await fs.readFile(NOTES_FILE, "utf8"));
  } catch {
    return [];
  }
}

const server = new McpServer({ name: "local-notes", version: "1.0.0" });

server.registerTool(
  "save_note",
  {
    title: "Save note",
    description: "Save a short note with a title and a body to the local notes file.",
    inputSchema: {
      title: z.string().min(1).max(120).describe("Short title for the note"),
      body: z.string().min(1).describe("The text of the note"),
    },
  },
  async ({ title, body }) => {
    const notes = await readNotes();
    const note: Note = {
      id: notes.length + 1,
      title,
      body,
      createdAt: new Date().toISOString(),
    };
    await fs.mkdir(NOTES_DIR, { recursive: true });
    await fs.writeFile(NOTES_FILE, JSON.stringify([...notes, note], null, 2));
    return { content: [{ type: "text", text: `Saved note #${note.id}: ${title}` }] };
  }
);

server.registerTool(
  "search_notes",
  {
    title: "Search notes",
    description: "Find saved notes whose title or body contains a word or phrase.",
    inputSchema: {
      query: z.string().min(1).describe("Word or phrase to look for"),
    },
  },
  async ({ query }) => {
    const q = query.toLowerCase();
    const hits = (await readNotes()).filter((n) =>
      `${n.title} ${n.body}`.toLowerCase().includes(q)
    );
    if (hits.length === 0) {
      return { content: [{ type: "text", text: `No notes match "${query}".` }] };
    }
    const text = hits.map((n) => `#${n.id} ${n.title}\n${n.body}`).join("\n\n");
    return { content: [{ type: "text", text }] };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);
console.error("local-notes MCP server running on stdio");

O que cada parte faz

  • McpServer é a classe de alto nível. O name e a version que você passa aparecem na lista de servidores e nos logs do cliente.
  • registerTool recebe um nome de ferramenta, um objeto de configuração (title, description, inputSchema) e um handler assíncrono.
  • O schema Zod é validado antes de o seu handler rodar e depois convertido em JSON Schema para que o Claude possa lê-lo. Cada string .describe() chega ao modelo.
  • O valor de retorno é sempre { content: [...] }. Texto simples é o tipo de conteúdo mais simples; imagens e links de recursos também são aceitos.
  • StdioServerTransport lê as requisições do stdin e escreve as respostas no stdout.

💡 Dica: Escreva as descrições para o modelo, não para humanos. "Encontre notas salvas cujo título ou corpo contenha uma palavra ou frase" diz ao Claude quando chamar a ferramenta. "Pesquisar" não diz. O Claude escolhe as ferramentas principalmente pelos nomes e descrições.

Como search_notes nunca altera nada, marque isso na configuração dela: annotations: { readOnlyHint: true }. As dicas são apenas orientativas, e os clientes decidem por conta própria o quanto confiar nelas, mas elas permitem que clientes bem-comportados tratem ferramentas somente leitura com mais leveza.

Desenvolvedora em pé diante de uma mesa ajustável, revisando código em um monitor largo

Nunca imprima no stdout

Com stdio, o stdout é o canal do protocolo. Um único console.log("started") esquecido envia uma linha que não é JSON para o fluxo, e a maioria dos clientes vai derrubar a conexão ou mostrar o servidor como falho. Lembre-se desta regra e você evita a falha mais comum do primeiro dia:

  • Faça: console.error("message"), que escreve no stderr, onde os clientes coletam os logs.
  • Não faça: console.log(...) ou process.stdout.write(...) em qualquer parte do servidor, inclusive dentro de bibliotecas que você importa.

Teste antes que o Claude teste

Rode o MCP Inspector

O MCP Inspector é a ferramenta oficial de depuração. Ele inicia o seu servidor do mesmo jeito que um cliente faria e oferece botões no lugar de prompts.

npm run build
npx @modelcontextprotocol/inspector node build/index.js

Uma página abre no navegador. Clique em Connect, abra a aba Tools e pressione List Tools. Você deve ver save_note e search_notes com seus schemas. Execute save_note com um título e um corpo, depois execute search_notes com uma palavra desse corpo. A primeira chamada retorna Saved note #1: Standup, e o arquivo de notas aparece em uma pasta mcp-notes dentro do seu diretório pessoal (ou em NOTES_DIR, se você tiver definido outro local).

Dois colegas inclinados em direção à tela de um notebook sobre uma mesa de madeira compartilhada

Envie mensagens JSON-RPC brutas

Se você quiser ver o próprio protocolo, o stdio usa uma mensagem JSON por linha. Coloque estas três linhas em requests.jsonl:

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"0.0.1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}

Depois envie-as para o servidor e mantenha o stdin aberto por um momento para que as respostas sejam escritas:

(cat requests.jsonl; sleep 2) | node build/index.js

A primeira resposta confirma o handshake, com a versão do protocolo que o servidor aceitou e o seu serverInfo:

{"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":true}},"serverInfo":{"name":"local-notes","version":"1.0.0"}},"jsonrpc":"2.0","id":1}

A segunda resposta lista as duas ferramentas com seus JSON Schemas. Essa troca é tudo o que o Claude faz ao se conectar: handshake, listar ferramentas, chamar ferramentas.

Conecte ao Claude

Edite a configuração do Claude Desktop

No Claude Desktop, abra Configurações, depois Developer, depois Edit Config. Isso revela claude_desktop_config.json:

SistemaLocal do arquivo de configuração
Windows%APPDATA%\Claude\claude_desktop_config.json
macOS~/Library/Application Support/Claude/claude_desktop_config.json

Adicione o seu servidor em mcpServers. Use caminhos absolutos, porque o Claude Desktop inicia o processo a partir do próprio diretório de trabalho, não da pasta do projeto.

{
  "mcpServers": {
    "local-notes": {
      "command": "node",
      "args": ["C:/Users/you/projects/local-notes-mcp/build/index.js"],
      "env": {
        "NOTES_DIR": "C:/Users/you/mcp-notes"
      }
    }
  }
}

No macOS, o caminho fica parecido com /Users/you/projects/local-notes-mcp/build/index.js. Salve o arquivo, depois feche completamente o Claude Desktop (no Windows, pela bandeja do sistema, não apenas pelo botão de fechar da janela) e abra-o de novo. Suas ferramentas aparecem no menu de ferramentas do campo de entrada do chat, e o Claude pede permissão antes de executar qualquer uma delas.

Desenvolvedor trabalhando em uma mesa de café de mármore com um notebook prateado e um flat white

Adicione ao Claude Code

O Claude Code não exige edição de arquivos. Um único comando registra o servidor:

claude mcp add --transport stdio --env NOTES_DIR=/home/you/mcp-notes local-notes -- node /home/you/projects/local-notes-mcp/build/index.js

Tudo depois do duplo hífen é o comando que inicia o seu servidor. Verifique com claude mcp list, ou digite /mcp dentro de uma sessão para ver o status. Uma flag de escopo decide quem recebe o servidor:

EscopoArmazenado emQuem vê
local (padrão)Suas configurações privadas para este projetoSomente você, neste projeto
project (--scope project).mcp.json no repositórioTodos que clonarem, depois de aprovarem
user (--scope user)Sua configuração de usuárioSomente você, em todos os projetos

Experimente um prompt real

Peça ao Claude algo que force uma chamada de ferramenta:

Salve uma nota com o título "Standup" que diz "Publicar o post sobre MCP na sexta". Depois pesquise nas minhas notas por "sexta".

O Claude chama save_note, depois search_notes, e devolve o resultado citado. Abra notes.json para confirmar que os dados foram gravados no seu disco. Se foram, você tem um servidor MCP local funcionando.

Corrija falhas e proteja o servidor

Corrija as falhas comuns

SintomaCausa provávelCorreção
Servidor aparece como falho ou desconectadoSaída no stdout, ou um erro ao iniciarExecute node build/index.js manualmente e leia o stderr; remova todo console.log
Nenhuma ferramenta após editar a configuraçãoCliente ainda aberto, ou JSON inválidoFeche completamente; procure vírgulas sobrando
spawn node ENOENTO aplicativo não encontra node no seu PATHUse o caminho absoluto para o binário node como command
Funciona no Inspector, falha no ClaudeCaminhos relativos ou variáveis de ambiente ausentesCaminhos absolutos, e coloque as variáveis em env
Alterações no código não têm efeitoVocê não recompilou ou reiniciouExecute npm run build e depois reinicie o cliente

Dois detalhes do Windows causam metade dos problemas restantes. Barras invertidas dentro de strings JSON precisam ser duplicadas (C:\\Users\\you\\...), ou você pode simplesmente usar barras normais, como no exemplo acima. E, no Windows nativo, servidores iniciados por npx geralmente precisam de um wrapper cmd /c em command; um comando node simples não funciona.

Quando algo ainda falhar, leia os logs. O Claude Desktop grava um log por servidor, em ~/Library/Logs/Claude no macOS e em %APPDATA%\Claude\logs no Windows. Suas próprias linhas console.error acabam ali.

Engenheiro recostado na cadeira de escritório com um sorriso aliviado sob a luz quente de uma lâmpada de latão

Padrões seguros que vale manter

Um servidor stdio herda as suas permissões, então trate cada ferramenta como código que pode agir em seu nome.

  • Limite o raio de impacto. Mantenha o acesso a arquivos dentro de uma única pasta. Se uma ferramenta aceitar um caminho, resolva-o e rejeite qualquer coisa fora do diretório permitido.
  • Valide cada entrada. As regras min, max e enum do Zod não custam nada e bloqueiam chamadas malformadas antes de o seu handler rodar.
  • Mantenha segredos fora do código. Coloque os tokens no bloco env da sua configuração, e mantenha esse arquivo fora do controle de versão.
  • Leia antes de instalar. Adicione apenas servidores de terceiros cujo código-fonte você tenha examinado. Eles rodam com o acesso da sua conta.
  • Trate a saída das ferramentas como não confiável. Textos que sua ferramenta busca em páginas web ou e-mails podem conter instruções direcionadas ao modelo. Devolva-os como dados e mantenha as ações de escrita atrás de confirmação.

Migrando do stdio para HTTP

Quando colegas de equipe precisarem das mesmas ferramentas, troque o transporte. O SDK traz StreamableHTTPServerTransport, que serve as mesmas McpServer via HTTP atrás da sua própria autenticação. Suas chamadas registerTool não mudam. Só o transporte e o registro no cliente mudam, por exemplo claude mcp add --transport http notes https://your-host/mcp.

Você já pode ver esse padrão na prática. O conector PicassoIA no claude.ai é um servidor MCP remoto que lista geração de imagens, edição de imagens e geração de vídeo como ferramentas, e o Claude as chama sem nenhum processo local.

Close-up de um rack de servidores preto fosco com cabos ethernet cinza bem organizados

Rascunhe especificações de ferramentas no PicassoIA

Boas ferramentas começam com bons nomes e boas descrições, e um modelo de linguagem é uma forma rápida de rascunhá-las antes de escrever código. O PicassoIA hospeda 75 modelos de texto na categoria Large Language Models, incluindo o Claude Sonnet 5, que foi feito para tarefas de programação. Veja como usá-lo para desenhar ferramentas.

Como usar o Claude Sonnet 5 no PicassoIA

  1. Abra a página do Claude Sonnet 5 no PicassoIA.
  2. Descreva a ferramenta em linguagem simples: o que ela faz, o que recebe, o que devolve e se altera alguma coisa.
  3. Peça um formato de saída fixo: um nome de ferramenta em snake_case, uma descrição de no máximo duas frases escrita para um modelo, um schema Zod com .describe() em cada campo e três casos-limite que devem falhar na validação.
  4. Cole o resultado em uma chamada registerTool, recompile e teste no Inspector.
  5. Ajuste a descrição, não o schema, quando o Claude escolher a ferramenta errada. Geralmente o problema é o texto.

Um prompt que funciona bem:

Estou criando uma ferramenta MCP chamada list_overdue_tasks. Ela lê o tasks.json, retorna as tarefas cujo dueDate é anterior a hoje e não altera nada. Escreva o nome da ferramenta, uma descrição de duas frases para um modelo de IA, um schema de entrada Zod com um describe() em cada campo e três entradas inválidas que ela deve rejeitar.

Para refatorações maiores, como dividir um servidor de 600 linhas em módulos, experimente o Claude Fable 5 ou o Claude Opus 4.7 com o seu arquivo inteiro colado.

Crie sua primeira imagem no PicassoIA

Seu servidor de notas é um modelo. Troque o arquivo JSON por uma chamada a um modelo de imagem, e o Claude pode renderizar imagens sob demanda. Você não precisa construir isso primeiro para ver o resultado. Cada foto deste artigo foi gerada com o P-Image, um dos modelos de texto para imagem no Picasso IA.

Abra o Picasso IA, digite uma frase descrevendo uma cena e gere. Depois teste três experimentos:

  • Mude a lente. Reescreva o mesmo prompt com "35mm" e depois com "85mm" e compare o enquadramento.
  • Mude a luz. Troque "morning window light" por "warm desk lamp" e veja o clima mudar.
  • Mude o ângulo. Peça uma vista de cima e depois uma vista em ângulo baixo do mesmo assunto.

Crie uma ferramenta que você gostaria que o Claude tivesse, conecte-a com os passos acima e depois passe dez minutos no Picasso IA criando as imagens do seu projeto. O servidor leva uma tarde. As imagens levam segundos.

Compartilhe este artigo

Escolha seu idioma