Lucas S. Vieira


Múltiplas Ferramentas e MCP


O agente se expande


Guilda de IA

Recap: Semana 07

  • Ferramenta = função Python que o agente pode chamar
  • @tool + Pydantic: schema e validação automáticos
  • Descrição clara decide SE o modelo usa — e como
  • Ciclo manual (bind_tools) e automático (create_agent)
  • ReAct completo: Thought → Action → Observation

"Semana passada ele aprendeu a agir. Hoje aprende a encadear."

Chamadas encadeadas

📓 Colab

Na S07, o agente usou uma ferramenta por vez.

Mas e quando precisa de duas ou mais?

"Quanto é 234 × 987 + 100?"

Thought: Preciso multiplicar, depois somar
Action: calculate(234, 987, "multiply") → 230958
Action: calculate(230958, 100, "add") → 231058
Answer: "O resultado é 231.058."

O create_agent repete Thought→Action→Observation automaticamente.

from langchain.agents import create_agent

agente = create_agent(llm, [calculate, get_time, get_word_length])

resultado = agente.invoke({"messages": "Quanto é 234 vezes 987 mais 100?"})
print(resultado["messages"][-1].content)
# "O resultado de 234 × 987 + 100 é 231.058."

Uma chamada. Múltiplas ferramentas encadeadas. Automático.

O problema das descrições vagas

Se duas ferramentas parecem fazer a mesma coisa:

# ❌ Vagas — LLM confunde
@tool
def search_wiki(query: str) -> str:
    """Search for information."""

@tool
def search_web(query: str) -> str:
    """Search for information."""

Search for information × 2 → modelo não sabe qual usar.

# ✅ Específicas — LLM distingue
@tool
def search_wiki(query: str) -> str:
    """Search Wikipedia for factual and historical information.
    Use for established facts about people, places, events."""

@tool
def search_web(query: str) -> str:
    """Search the web for current information.
    Use for news, prices, and real-time data."""

Regra: cada descrição explica quando usar aquela ferramenta.

Debug de ferramentas

O LLM nem sempre acerta. Problemas comuns:

  1. Ferramenta errada — descrição vaga
  2. Argumentos errados — valores possíveis não documentados
  3. ValidationError — tipo errado no argumento

Problema: LLM chama ferramenta errada

Pergunta: "Que horas são?" LLM chama: calculate em vez de get_time

# ❌ Vaga
@tool
def get_time() -> str:
    """Returns current date and time."""

# ✅ Específica
@tool
def get_time() -> str:
    """Returns the current date and time.
    Use when the user asks 'what time is it',
    'what date is today', or about the current moment."""

Problema: argumentos errados

LLM manda operation"234 vezes 987"= em vez de "multiply"

# ✅ Solução: documentar valores no docstring
@tool
def calculate(a: float, b: float, operation: str) -> float:
    """Performs a mathematical operation between two numbers.
    Use when you need to perform numeric calculations.
    Args:
        a: First number
        b: Second number
        operation: One of 'add', 'subtract', 'multiply', 'divide'
    """

Problema: ValidationError

Pydantic rejeita argumentos com tipo errado (ex.: passar texto onde espera número). O agente recebe o erro e pode corrigir automaticamente. Mas se acontece com frequência, a descrição da ferramenta precisa melhorar.

Ferramentas externas via MCP

Até agora, você escreveu as ferramentas com @tool.

E se outra pessoa escreveu?

MCP — Model Context Protocol

  • Protocolo padrão para ferramentas de IA (como HTTP para a web)
  • Um servidor MCP expõe ferramentas
  • Um cliente MCP consome essas ferramentas
  • Qualquer LLM se conecta a qualquer servidor MCP

modelcontextprotocol.io

Servidor MCP (math_server.py)

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Math")

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers together."""
    return a + b

@mcp.tool()
def multiply(a: int, b: int) -> int:
    """Multiply two numbers together."""
    return a * b

if __name__ == "__main__":
    mcp.run(transport="stdio")

Quase idêntico ao @tool, mas @mcp.tool() e FastMCP.

Cliente: consumindo MCP no LangChain

import subprocess
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from langchain_mcp_adapters.tools import load_mcp_tools
from langchain.agents import create_agent

server_params = StdioServerParameters(
    command="python", args=["math_server.py"])

async with stdio_client(
    server_params, errlog=subprocess.DEVNULL) as (read, write):
    async with ClientSession(read, write) as session:
        await session.initialize()
        tools = await load_mcp_tools(session)
        agente = create_agent(llm, tools)
        resultado = await agente.ainvoke(
            {"messages": "Quanto é 3 + 5 multiplicado por 2?"})

Ferramentas MCP viram @tool do LangChain — o agente não sabe a diferença.

O errlog=subprocess.DEVNULL evita o bug do fileno() no Jupyter/Colab.

Servidor MCP remoto (HTTP)

# Mesmo math_server.py, mas rodando como servidor HTTP
mcp = FastMCP("Math", port=8000)

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers together."""
    return a + b

@mcp.tool()
def multiply(a: int, b: int) -> int:
    """Multiply two numbers together."""
    return a * b

# A porta fica no FastMCP (não no run) — sobe em http://localhost:8000/mcp
mcp.run(transport="streamable-http")

Cliente remoto (HTTP)

from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
from langchain_mcp_adapters.tools import load_mcp_tools
from langchain.agents import create_agent

# Aponta pra URL — pode ser localhost ou um servidor remoto
async with streamablehttp_client(
    "http://localhost:8000/mcp"
) as (read, write):
    async with ClientSession(read, write) as session:
        await session.initialize()
        tools = await load_mcp_tools(session)
        agente = create_agent(llm, tools)
        # API idêntica — só muda como conecta

Cliente com auth (headers)

Servidor que exige token — caso real: SearXNG no Raspberry Pi.

import os
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

async with streamablehttp_client(
    "http://192.168.3.8:8899/mcp",   # SearXNG no Raspberry Pi
    headers={"Authorization": f"Bearer {os.environ['SEARXNG_MCP_TOKEN']}"},
) as (read, write):
    async with ClientSession(read, write) as session:
        await session.initialize()
        # dali em diante: igual ao cliente sem auth

O token vem de $SEARXNG_MCP_TOKEN (export SEARXNG_MCP_TOKEN…= antes de rodar); o auth mora no headers, ClientSession, load_mcp_tools e o agente não mudam.

S07 vs S08

  S07 S08
Ferramentas @tool — você escreve MCP — alguém escreveu
Código bind_tools + create_agent load_mcp_tools + create_agent
Origem Seu Python Servidor externo

Pra o agente, ferramenta é ferramenta. Não importa de onde veio.

Múltiplos servidores MCP

Vários servidores ao mesmo tempo, com AsyncExitStack:

import subprocess
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from langchain_mcp_adapters.tools import load_mcp_tools

SERVIDORES = [
    StdioServerParameters(command="python", args=["math_server.py"]),
    StdioServerParameters(command="python", args=["string_server.py"]),
]
async with AsyncExitStack() as stack:
    tools = []
    for params in SERVIDORES:
        read, write = await stack.enter_async_context(
            stdio_client(params, errlog=subprocess.DEVNULL))
        session = await stack.enter_async_context(
            ClientSession(read, write))
        await session.initialize()
        tools.extend(await load_mcp_tools(session))

    agente = create_agent(llm, tools)

# Ferramentas de math E strings, num agente só

Monte um agente combinando ferramentas de fontes diferentes — sem código de integração. (MultiServerMCPClient existe, mas não aceita errlog — quebra no Colab.)

Para lembrar

"Ferramentas transformam conversa em ação. MCP transforma integração em padrão."

  • Descrições vagas = confusão
  • Descrições específicas = clareza
  • create_agent encadeia ferramentas automaticamente
  • MCP = HTTP das ferramentas de IA
  • MCP suporta stdio (local) e HTTP (remoto)
  • Na S09: Embeddings e RAG — buscar informações relevantes em documentos

Perguntas?