07-01-04 — Construindo um MCP Server

⏱ 15 minFontes validadas em: 2026-04-29

TL;DR

Construir um MCP server é surpreendentemente simples com os SDKs oficiais. Em Python: pip install mcp; em TypeScript: npm install @modelcontextprotocol/sdk. O exemplo deste tópico é um server de notas: ferramentas para criar, ler e listar notas, com um resource que expõe o conteúdo de uma nota específica. Leva menos de 50 linhas de código funcional em cada linguagem.

Os SDKs oficiais

A Anthropic mantém SDKs oficiais para Python e TypeScript. Ambos abstraem o protocolo JSON-RPC e o transporte, expondo uma API declarativa para registrar tools, resources e prompts.

Exemplo completo em Python: Notes API

Vamos construir um MCP server que expõe uma API simples de notas. O server terá três tools (create_note, list_notes, delete_note) e um resource (note://{id}) para ler o conteúdo de uma nota.

Instalação

pip install mcp

Código completo (Python)

from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, Resource, TextContent
import json

# Estado em memória (em produção: banco de dados)
notes: dict[str, str] = {}

# Criar o server
server = Server("notes-server")

# ---- TOOLS ----

@server.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="create_note",
            description="Cria uma nova nota com título e conteúdo.",
            inputSchema={
                "type": "object",
                "properties": {
                    "title": {"type": "string", "description": "Título da nota"},
                    "content": {"type": "string", "description": "Conteúdo da nota"}
                },
                "required": ["title", "content"]
            }
        ),
        Tool(
            name="list_notes",
            description="Lista todas as notas disponíveis.",
            inputSchema={"type": "object", "properties": {}}
        ),
        Tool(
            name="delete_note",
            description="Remove uma nota pelo título.",
            inputSchema={
                "type": "object",
                "properties": {
                    "title": {"type": "string", "description": "Título da nota a remover"}
                },
                "required": ["title"]
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    if name == "create_note":
        title = arguments["title"]
        notes[title] = arguments["content"]
        return [TextContent(type="text", text=f"Nota '{title}' criada com sucesso.")]
    
    elif name == "list_notes":
        if not notes:
            return [TextContent(type="text", text="Nenhuma nota encontrada.")]
        listing = "\n".join(f"- {title}" for title in notes.keys())
        return [TextContent(type="text", text=f"Notas disponíveis:\n{listing}")]
    
    elif name == "delete_note":
        title = arguments["title"]
        if title in notes:
            del notes[title]
            return [TextContent(type="text", text=f"Nota '{title}' removida.")]
        return [TextContent(type="text", text=f"Nota '{title}' não encontrada.")]
    
    raise ValueError(f"Tool desconhecida: {name}")

# ---- RESOURCES ----

@server.list_resources()
async def list_resources():
    from mcp.types import Resource as McpResource
    return [
        McpResource(
            uri=f"note://{title}",
            name=f"Nota: {title}",
            mimeType="text/plain"
        )
        for title in notes.keys()
    ]

@server.read_resource()
async def read_resource(uri: str) -> str:
    title = uri.replace("note://", "")
    if title in notes:
        return notes[title]
    raise ValueError(f"Recurso não encontrado: {uri}")

# ---- ENTRY POINT ----

if __name__ == "__main__":
    import asyncio
    asyncio.run(stdio_server(server))
💡 stdio vs HTTP

O exemplo usa stdio_server — ideal para rodar localmente como processo filho do host (Claude Desktop, VS Code). Para expor via HTTP (ex: Azure Functions), substitua por streamable_http_server da mesma biblioteca.

Exemplo completo em TypeScript

Instalação

npm install @modelcontextprotocol/sdk

Código completo (TypeScript)

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

// Estado em memória
const notes = new Map();

// Criar o server
const server = new Server(
  { name: "notes-server", version: "1.0.0" },
  { capabilities: { tools: {}, resources: {} } }
);

// ---- TOOLS ----

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "create_note",
      description: "Cria uma nova nota com título e conteúdo.",
      inputSchema: {
        type: "object",
        properties: {
          title: { type: "string", description: "Título da nota" },
          content: { type: "string", description: "Conteúdo da nota" },
        },
        required: ["title", "content"],
      },
    },
    {
      name: "list_notes",
      description: "Lista todas as notas disponíveis.",
      inputSchema: { type: "object", properties: {} },
    },
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === "create_note") {
    notes.set(args!.title as string, args!.content as string);
    return {
      content: [{ type: "text", text: `Nota '${args!.title}' criada com sucesso.` }],
    };
  }

  if (name === "list_notes") {
    if (notes.size === 0) {
      return { content: [{ type: "text", text: "Nenhuma nota encontrada." }] };
    }
    const listing = Array.from(notes.keys()).map((t) => `- ${t}`).join("\n");
    return { content: [{ type: "text", text: `Notas:\n${listing}` }] };
  }

  throw new Error(`Tool desconhecida: ${name}`);
});

// ---- RESOURCES ----

server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources: Array.from(notes.keys()).map((title) => ({
    uri: `note://${title}`,
    name: `Nota: ${title}`,
    mimeType: "text/plain",
  })),
}));

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const title = request.params.uri.replace("note://", "");
  const content = notes.get(title);
  if (!content) throw new Error(`Recurso não encontrado: ${request.params.uri}`);
  return { contents: [{ uri: request.params.uri, mimeType: "text/plain", text: content }] };
});

// ---- ENTRY POINT ----

const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Notes MCP server rodando via stdio");

Configurando no Claude Desktop para testar

// claude_desktop_config.json
{
  "mcpServers": {
    "notes": {
      "command": "python",
      "args": ["/caminho/para/notes_server.py"]
    }
  }
}
// Ou para TypeScript:
// "command": "node",
// "args": ["/caminho/para/notes_server.js"]
⚠️ Debugging

Erros no MCP server costumam aparecer silenciosamente. Use console.error() (TypeScript) ou print(..., file=sys.stderr) (Python) para logs — o stdout é reservado para o protocolo JSON-RPC. Para debugging avançado, use o MCP Inspector: npx @modelcontextprotocol/inspector python notes_server.py

Boas práticas para produção

  • Descriptions importam: o LLM decide quando chamar uma tool baseado na description. Seja preciso e inclua exemplos de quando usar.
  • Schemas rigorosos: defina required corretamente — o LLM tentará preencher todos os campos obrigatórios.
  • Tratamento de erros: retorne mensagens de erro informativas no content da tool — o LLM vai usar essa informação para tomar decisões.
  • Idempotência: tools invocadas por LLMs podem ser chamadas múltiplas vezes. Projete para isso.
  • Autenticação: para servers remotos, use OAuth 2.0 ou API keys via headers — nunca exponha endpoints MCP sem autenticação.

Como isso se conecta

  • 07-01-01: o protocolo que esses SDKs implementam
  • 07-01-03: como hospedar seu server no Azure Functions
  • 07-03-02: desafio prático — aplique esse conhecimento para SharePoint

Fontes

  1. MCP Quickstart — Building a Server (modelcontextprotocol.io)
  2. MCP Python SDK — GitHub (github.com/modelcontextprotocol)
  3. MCP TypeScript SDK — GitHub (github.com/modelcontextprotocol)
  4. MCP Inspector — Debugging Tool (modelcontextprotocol.io)