Build a small Model Context Protocol server that gives a support-triage assistant a knowledge base and a routing tool, using the real Python MCP SDK.
Quando construí meu primeiro painel interno de suporte, a parte mais difícil nunca foi o modelo. Foi conectar o modelo às coisas de que ele realmente precisava: as anotações que minha equipe mantinha sobre problemas comuns e as filas em que um chamado precisava cair. Um modelo por si só consegue escrever uma resposta amigável, mas não consegue ler sua base de conhecimento nem mover um chamado — não a menos que você lhe dê uma forma limpa e segura de alcançar essas coisas.
Essa forma limpa e segura é o Model Context Protocol (MCP). Neste módulo vou construir o lado servidor do assistente de triagem de suporte de-identificado que vimos desenvolvendo ao longo desta trilha. O assistente recebe uma mensagem de cliente, consulta o que já sabemos e decide para onde o chamado deve ir. O servidor MCP é o que permite que ele faça a consulta e o roteamento sem que eu codifique à mão uma ponte frágil a cada vez.
Pense em um servidor MCP como a janela de serviço de uma cozinha movimentada. Os cozinheiros (seus dados e ações) ficam nos fundos, onde é seguro. A janela expõe exatamente duas coisas ao exterior: um cardápio que você pode ler e uma campainha que você pode tocar para fazer um pedido. O assistente nunca entra na cozinha — ele só usa a janela. Essa fronteira é o ponto inteiro.
Ideia-chave: um servidor MCP não torna seu assistente mais inteligente. Ele torna seu assistente capaz — é a porta controlada entre um modelo de linguagem e seus sistemas reais.
Ao final deste módulo você terá um servidor em execução ao qual o assistente pode se conectar, expondo uma consulta à base de conhecimento e uma ação de roteamento.
O MCP traça uma linha em que iniciantes tropeçam, então deixe-me torná-la concreta antes de qualquer código. Um servidor oferece dois tipos de coisas, e elas não são intercambiáveis.
Um recurso é algo que o assistente pode ler. Não tem efeitos colaterais. Pedir um recurso duas vezes não muda nada. No nosso assistente de triagem, a base de conhecimento de suporte é um recurso — o assistente lê a nota de "export-failing" do mesmo jeito que você lê um rótulo numa prateleira.
Uma ferramenta é algo que o assistente pode fazer. Tem efeitos colaterais, ou ao menos toma uma decisão que importa. Encaminhar um chamado para a fila de cobrança é uma ferramenta — chamá-la é uma ação, não uma consulta.
Aqui está o modelo mental que eu mantenho: um recurso é uma prateleira somente-leitura; uma ferramenta é uma tomada de energia. Você olha a prateleira com a frequência que quiser. Você se conecta à tomada deliberadamente, porque algo acontece quando você o faz.
Armadilha comum: iniciantes expõem tudo como ferramenta, inclusive consultas somente-leitura. Isso dá ao modelo a capacidade de agir quando tudo de que ele precisava era a capacidade de olhar. Mantenha leituras como recursos — é mais seguro e mais fácil de raciocinar.
Todo servidor MCP também tem uma terceira peça — o núcleo do servidor que fala o protocolo — mas o SDK lhe dá isso de graça. Seu trabalho é declarar os recursos e as ferramentas.
Antes de escrever código, eu sempre escrevo o contrato em português claro. Para o servidor de triagem ele é curto:
export-failing, retorne a nota que minha equipe escreveu sobre ele. Somente-leitura.Note o que não está aqui. O servidor não chama um modelo de linguagem e não tenta ser esperto. A inteligência — decidir quando consultar algo e quando encaminhar — vive no assistente (o agente que construímos no próximo módulo). O servidor apenas oferece capacidades honestas e previsíveis. Manter essa divisão limpa é o que torna o sistema inteiro depurável depois.
Por que isso importa: um servidor determinístico que você pode prever totalmente é uma feature, não uma limitação. Quando o assistente se comporta mal, você quer saber instantaneamente se o bug está no julgamento do modelo ou no comportamento da ferramenta. Misturar os dois é como você acaba incapaz de depurar qualquer coisa.
O SDK Python MCP traz uma interface de alto nível chamada FastMCP que cuida do encanamento do protocolo. Instale-a primeiro:
Agora o menor servidor possível. Isto ainda não faz nada útil — apenas inicia e fala o protocolo — mas é real e roda:
FastMCP("support-triage") cria o servidor e o nomeia. mcp.run() o inicia e, por padrão, conversa via entrada e saída padrão — que é exatamente como um assistente local se conecta a ele. Tudo o que adicionamos a seguir pende desse objeto mcp.
Agora damos ao servidor algo a oferecer. Primeiro o recurso — a base de conhecimento somente-leitura. Nós o anexamos com o decorador @mcp.resource(...), e o marcador {topic} na URI vira o argumento da função:
Quando o assistente lê kb://export-failing, o SDK chama support_note("export-failing") e devolve a string. Sem efeitos colaterais, sem decisões — uma consulta pura, exatamente o que um recurso deve ser.
Agora a ferramenta — a ação de roteamento. Usamos @mcp.tool(), e por ser uma ferramenta, o assistente a chama de propósito para fazer algo acontecer:
Esta ferramenta é deliberadamente um simples casamento de palavras-chave — e tudo bem. É um auxiliar determinístico, não o cérebro da operação. O assistente no próximo módulo decide se chama route_ticket; a ferramenta apenas dá uma resposta rápida e previsível quando solicitada.
Armadilha comum: não recorra a um modelo de linguagem dentro de uma ferramenta como esta. Uma ferramenta de roteamento que silenciosamente chama um LLM é lenta, custa dinheiro a cada chamado e é difícil de testar. Mantenha ferramentas baratas e previsíveis; deixe o agente fornecer o julgamento.
Antes de seguir em frente, preveja a resposta: se o assistente precisa ler o histórico de pedidos passados de um cliente, você deveria expô-lo como um recurso ou uma ferramenta? Por quê? (Dica: ler o histórico de pedidos muda alguma coisa?) Raciocinar sobre essa distinção é a habilidade que este módulo inteiro está ensinando.
O SDK traz um inspetor que lança seu servidor e permite que você manuseie seus recursos e ferramentas à mão. Aponte-o para o seu arquivo:
Isso abre uma UI local onde você pode ler kb://export-failing e chamar route_ticket com uma mensagem de exemplo — a forma mais rápida de confirmar que o servidor se comporta antes de conectar um assistente real.
Para uma verificação rápida em código, lembre-se de que as funções decoradas ainda são Python comum. Você pode chamá-las diretamente:
Bom padrão: teste as funções puras isoladamente primeiro, depois teste-as pelo inspetor, depois conecte o assistente. Cada camada elimina uma classe de bug, então quando algo quebra você sabe exatamente onde olhar.
Quando construí a versão real de automação de suporte disto, esse hábito em camadas foi o que me manteve são — eu nunca precisei me perguntar se uma falha era culpa do modelo ou do servidor, porque eu já tinha provado que o servidor funcionava por conta própria.
Application-level questions on MCP servers, resources, and tools.
1. The assistant needs to read a customer's past order history. Should that be exposed as a resource or a tool?
Correct Answer: B
Reading order history is a pure lookup with no side effects, so it belongs as a resource (the read-only shelf), not a tool (the power outlet).
2. Why does the module keep route_ticket as simple keyword matching instead of calling a language model inside it?
Correct Answer: C
Tools should be cheap and predictable so failures are easy to localize. The agent supplies the judgment about when to call the tool; the tool just answers fast.
3. Given the routing tool in the module, what does route_ticket("I want a refund for last month's charge") return?
Correct Answer: C
The message contains both 'refund' and 'charge', so the first branch sets queue to 'billing', and needs_human is True because queue == 'billing'.
4. What does mcp.run() do by default in a FastMCP server?
Correct Answer: B
By default FastMCP communicates over stdio, which is exactly how a local assistant connects to the server.
5. An MCP server has resources, tools, and a third piece. What is it, and who writes it?
Correct Answer: C
The server core handles the protocol plumbing. FastMCP gives it to you, so your job is only to declare the resources and tools.
6. What does support_note("billing-dispute") return, given the knowledge base in the module?
Correct Answer: B
billing-dispute is not a key in NOTES, so the .get() fallback returns the not-found message instead of raising.