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/listeprompts/get. - Escrever um loop de despacho (dispatch loop) que lê mensagens JSON-RPC da
stdine escreve respostas nastdout. - 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
stdinfecha (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
Execute
code/main.pye interaja com ele usando mensagens JSON-RPC criadas manualmente. Testenotes_createe, em seguida,resources/readpara recuperar a nova nota.Adicione uma ferramenta
notes_deletecomannotations: {destructiveHint: true}. Verifique se o cliente exibiria uma caixa de diálogo de confirmação (isso requer um host real; o Claude Desktop funciona).Implemente
resources/subscribepara que o servidor envie notificações deresources/updatedsempre que uma nota for modificada. Adicione uma tarefa de manutenção de conexão (keepalive).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.
Leia a seção
server/toolsda 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
- Model Context Protocol — Python SDK — a implementação de referência em Python
- Model Context Protocol — TypeScript SDK — implementação paralela em TS
- FastMCP — server framework — API Python em estilo de decoradores para servidores MCP
- MCP — Quickstart server guide — tutorial de ponta a ponta usando qualquer um dos SDKs
- MCP — Server tools spec — referência completa para mensagens
tools/*