Phase 13 - Lesson 07

Construindo um Servidor MCP — SDKs Python + TypeScript

This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.

A maioria dos tutoriais de MCP mostra apenas hello-worlds em stdio. Um servidor real expõe ferramentas, recursos e prompts, lida com negociação de capacidades, emite erros estruturados e funciona da mesma forma em diferentes SDKs. Esta lição constrói um servidor de notas de ponta a ponta: transporte stdio da biblioteca padrão (stdlib), despacho JSON-RPC, as três primitivas de servidor e um estilo de função pura aplicável tanto no FastMCP do SDK do Python quanto no SDK do TypeScript após a transição.

Tipo: Build Linguagens: Python (servidor MCP stdio com stdlib) Pré-requisitos: Phase 13 · 06 (fundamentos do MCP) Tempo: ~75 minutos

Objetivos de Aprendizado

  • Implementar os métodos initialize, tools/list, tools/call, resources/list, resources/read, prompts/list e prompts/get.
  • Escrever um loop de despacho (dispatch loop) que lê mensagens JSON-RPC da stdin e escreve respostas na stdout.
  • Emitir respostas de erro estruturadas de acordo com a especificação do JSON-RPC 2.0 e os códigos adicionais do MCP.
  • Migrar uma implementação com stdlib para o FastMCP (SDK Python) ou para o SDK TypeScript sem reescrever a lógica das ferramentas.

O Problema

Antes de poder usar um transporte remoto (Phase 13 · 09) ou uma camada de autenticação (Phase 13 · 16), você precisa de um servidor local limpo. Local significa stdio: o servidor é iniciado pelo cliente como um processo filho, e as mensagens fluem delimitadas por quebra de linha através de stdin/stdout.

A especificação de 25-11-2025 prescreve que as mensagens stdio sejam codificadas como objetos JSON com um separador \n explícito. Sem SSE aqui; SSE era o modo remoto antigo e será removido em meados de 2026 (o servidor Rovo MCP da Atlassian o depreciou em 30 de junho de 2026; o Keboola em 1º de abril de 2026). Para stdio, um objeto JSON por linha é todo o formato de transmissão.

Um servidor de notas é um bom formato porque exercita todas as três primitivas de servidor. As ferramentas realizam mutações (notes_create). Os recursos expõem dados (notes://{id}). Os prompts fornecem templates (review_note). O formato desta lição se generaliza para qualquer domínio.

O Conceito

Loop de despacho

loop:
  line = stdin.readline()
  msg = json.loads(line)
  if has id:
    handle request -> write response
  else:
    handle notification -> no response

Três regras:

  • Não imprima nada na stdout que não seja um envelope JSON-RPC. Logs de depuração (debug) vão para a stderr.
  • Toda requisição DEVE ter uma resposta correspondente que carregue o mesmo id.
  • Notificações NÃO DEVEM ser respondidas.

Implementando initialize

def initialize(params):
    return {
        "protocolVersion": "2025-11-25",
        "capabilities": {
            "tools": {"listChanged": True},
            "resources": {"listChanged": True, "subscribe": False},
            "prompts": {"listChanged": False},
        },
        "serverInfo": {"name": "notes", "version": "1.0.0"},
    }

Declare apenas o que você suporta. O cliente depende do conjunto de capacidades (capabilities) para habilitar ou restringir funcionalidades.

Implementando tools/list e tools/call

tools/list retorna {tools: [...]} com cada entrada contendo name, description e inputSchema. tools/call recebe {name, arguments} e retorna {content: [blocks], isError: bool}.

Os blocos de conteúdo (content blocks) são tipados. Os mais comuns:

{"type": "text", "text": "Found 2 notes"}
{"type": "resource", "resource": {"uri": "notes://14", "text": "..."}}
{"type": "image", "data": "<base64>", "mimeType": "image/png"}

Os erros de ferramentas vêm em dois formatos. Erros no nível do protocolo (método desconhecido, parâmetros inválidos) são erros JSON-RPC. Erros no nível da ferramenta (chamada válida, mas a ferramenta falhou) são retornados como {content: [...], isError: true}. Isso permite que o modelo veja a falha em seu próprio contexto.

Implementando recursos

Os recursos são somente leitura por design. resources/list retorna um manifesto; resources/read retorna o conteúdo. As URIs podem ser file://..., http://... ou um esquema customizado como notes://.

Quando você expõe dados como um recurso em vez de uma ferramenta:

  • O modelo não o "chama"; o cliente pode injetá-lo no contexto a pedido do usuário.
  • As inscrições (subscriptions) permitem que o servidor envie atualizações quando o recurso é alterado (Phase 13 · 10).
  • A Phase 13 · 14 estende isso com ui:// para recursos interativos.

Implementando prompts

Os prompts são templates com argumentos nomeados. O host os exibe como comandos de barra (slash-commands). Um prompt review_note pode receber um argumento note_id e produzir um template de prompt com várias mensagens que o cliente fornece ao seu modelo.

Sutilezas do transporte stdio

  • JSON delimitado por quebra de linha. Sem enquadramento prefixado por tamanho (length-prefixed framing).
  • Não use buffer. Chame sys.stdout.flush() após cada escrita.
  • O cliente controla o tempo de vida. Quando a stdin fecha (EOF), saia de forma limpa.
  • Não trate o sinal SIGPIPE silenciosamente; registre o log e saia.

Anotações

Cada ferramenta pode carregar annotations (anotações) que descrevem propriedades de segurança:

  • readOnlyHint: true — leitura pura, segura para repetir.
  • destructiveHint: true — efeitos colaterais irreversíveis; o cliente deve confirmar.
  • idempotentHint: true — as mesmas entradas produzem as mesmas saídas.
  • openWorldHint: true — interage com sistemas externos.

O cliente usa isso para decidir a experiência do usuário UX (diálogos de confirmação, indicadores de status) e roteamento (Phase 13 · 17).

Caminho de transição (Graduation path)

O servidor stdlib em code/main.py tem cerca de 180 linhas. O FastMCP (Python) simplifica essa mesma lógica em um estilo baseado em decoradores:

from fastmcp import FastMCP
app = FastMCP("notes")

@app.tool()
def notes_search(query: str, limit: int = 10) -> list[dict]:
    ...

O SDK do TypeScript tem um formato equivalente. O caminho de transição é direto e substituto quando você estiver pronto; os conceitos (capacidades, despacho, blocos de conteúdo) são os mesmos.

Como Usar

code/main.py é um servidor MCP de notas completo sobre stdio, usando apenas a biblioteca padrão (stdlib). Ele lida com initialize, tools/list, tools/call para três ferramentas (notes_list, notes_search, notes_create), resources/list e resources/read para cada nota, além de um prompt review_note. Você pode executá-lo enviando mensagens JSON-RPC via pipe:

echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | python main.py

O que observar:

  • O despachador (dispatcher) é um dict[str, Callable] que tem o nome do método como chave.
  • Cada executor de ferramenta retorna uma lista de blocos de conteúdo (content blocks), não uma string pura.
  • isError: true é definido quando o executor lança uma exceção.

Entregue

Esta lição produz outputs/skill-mcp-server-scaffolder.md. Dado um domínio (notas, chamados, arquivos, banco de dados), a skill gera a estrutura (scaffolds) de um servidor MCP com a divisão correta de ferramentas / recursos / prompts e o caminho de transição de SDK correspondente.

Exercícios

  1. Execute code/main.py e interaja com ele usando mensagens JSON-RPC criadas manualmente. Teste notes_create e, em seguida, resources/read para recuperar a nova nota.

  2. Adicione uma ferramenta notes_delete com annotations: {destructiveHint: true}. Verifique se o cliente exibiria uma caixa de diálogo de confirmação (isso requer um host real; o Claude Desktop funciona).

  3. Implemente resources/subscribe para que o servidor envie notificações de resources/updated sempre que uma nota for modificada. Adicione uma tarefa de manutenção de conexão (keepalive).

  4. Porte o servidor para FastMCP. O arquivo Python deve encolher para menos de 80 linhas. O comportamento na transmissão de rede deve ser idêntico; verifique com a mesma ferramenta de teste JSON-RPC.

  5. Leia a seção server/tools da especificação e identifique um campo de definição de ferramenta não implementado no servidor desta lição. (Dica: existem vários; escolha um e adicione-o).

Termos-Chave

Termo O que dizem O que realmente significa
Servidor MCP "A coisa que expõe ferramentas" Processo que fala MCP JSON-RPC via stdio ou HTTP
transporte stdio "Modelo de processo filho" O servidor é iniciado pelo cliente; comunica-se via stdin/stdout
Despachador (Dispatcher) "Roteador de métodos" Mapeamento do nome do método JSON-RPC para a função manipuladora
Bloco de conteúdo (Content block) "Pedaço de resultado da ferramenta" Elemento tipado no array content de uma resposta de ferramenta
isError "Falha no nível da ferramenta" Sinaliza que a ferramenta falhou; diferencia do erro JSON-RPC
Annotations "Dicas de segurança" Flags readOnly / destructive / idempotent / openWorld
FastMCP "SDK do Python" Framework de alto nível baseado em decoradores sobre o protocolo MCP
URI do Recurso (Resource URI) "Dados endereçáveis" file://, db:// ou esquema customizado que identifica um recurso
Template de Prompt (Prompt template) "Resumo do comando de barra" Template fornecido pelo servidor com espaços para argumentos em interfaces de hosts
Declaração de capacidades (Capability declaration) "Alternância de recursos" Flags por primitiva declaradas em initialize

Leituras Adicionais

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).