Múltiplas Ferramentas e MCP: O Agente Se Expande

Chamadas encadeadas

Na semana passada, o agente usava uma ferramenta por vez. Mas e quando a pergunta precisa de duas ou mais?

Exemplo: "Quanto é 234 × 987 + 100?"

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

O create_agent faz isso automaticamente. Ele repete o ciclo Thought→Action→Observation até ter a resposta final.

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."

O agente decide sozinho: preciso de duas chamadas a calculate? Então faço duas.

O problema das descrições vagas

Se duas ferramentas parecem fazer a mesma coisa, o modelo confunde.

Exemplo de conflito:

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

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

Duas ferramentas com a mesma descrição genérica. O LLM não sabe qual usar.

Solução: descrições específicas

@tool
def search_wiki(query: str) -> str:
    """Search Wikipedia for factual, historical, and encyclopedic information.
    Use for established facts about people, places, events, and concepts.
    """
    ...

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

Agora o modelo distingue: fato histórico → search_wiki, notícia atual → search_web.

Regra prática: cada ferramenta precisa de uma descrição exclusiva que explique quando usar.

Debug de ferramentas

O LLM nem sempre acerta. Eis os problemas mais comuns e como resolver.

Problema 1: LLM chama a ferramenta errada

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

Causa: descrição de calculate muito vaga ("Faz cálculos") ou descrição de get_time não menciona "que horas são".

Solução: melhorar a descrição da ferramenta correta.

# ❌ 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 any question about the current moment.
    """

Problema 2: argumentos errados

LLM manda: calculate(a=234, b=987, operation"234 vezes 987")= Esperado: operation deve ser "multiply", não "234 vezes 987"

Causa: descrição do parâmetro operation não lista os valores possíveis claramente.

Solução: documentar valores permitidos 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 3: ValidationError

Se o LLM manda um argumento com tipo errado (ex: passar texto onde espera número), Pydantic gera um ValidationError com contexto claro.

# ValidationError: Input should be a valid number,
# unable to parse string as a number (field: a)

Isso é bom — o agente recebe o erro e pode corrigir. Mas se acontece muito, a descrição do parâmetro pode estar confusa.

Ferramentas externas via MCP

Até agora, você escreveu as ferramentas. Mas e se outra pessoa escreveu a ferramenta e você só quer usar?

O MCP (Model Context Protocol) é um protocolo padrão para ferramentas. Pense nele como o HTTP das ferramentas de IA — assim como HTTP padronizou a web, MCP padroniza como ferramentas se conectam a agentes.

Conceito

Model Context Protocol (criado pela Anthropic, open-source):

  • Um servidor MCP expõe ferramentas
  • Um cliente MCP consome essas ferramentas
  • Protocolo padrão — qualquer LLM pode se conectar a qualquer servidor MCP

Na S06, você escreveu as ferramentas com @tool. Na S07, você consome ferramentas que alguém escreveu, via MCP.

Servidor MCP com FastMCP

Vamos criar um servidor MCP simples — depois vamos consumi-lo como cliente.

# math_server.py — Servidor MCP com duas ferramentas
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")

Repare: é quase idêntico ao @tool do LangChain, mas com @mcp.tool() e FastMCP em vez de langchain_core.tools.

O servidor usa transport="stdio" — roda no mesmo processo, sem servidor externo.

Cliente: consumindo MCP no LangChain

Instale: pip install langchain-mcp-adapters

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) as (read, write):
    async with ClientSession(read, write) as session:
        await session.initialize()
        tools = await load_mcp_tools(session)
        # tools agora são @tool do LangChain — usa igual!
        agente = create_agent(llm, tools)
        resultado = await agente.ainvoke(
            {"messages": "Quanto é 3 + 5 multiplicado por 2?"}
        )
        print(resultado["messages"][-1].content)

O que aconteceu:

  1. stdio_client iniciou o servidor math_server.py em background
  2. ClientSession fez o handshake MCP
  3. load_mcp_tools converteu as ferramentas MCP em @tool do LangChain
  4. create_agent usou as ferramentas normalmente — igual à S06

Servidor MCP remoto (HTTP)

O exemplo acima usa transport="stdio" — o cliente spawn o servidor localmente. Mas o MCP também suporta transporte HTTP, permitindo conectar a servidores remotos.

Para rodar o mesmo servidor como HTTP:

# math_server_http.py — Mesmo servidor, mas acessível via HTTP
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

# Roda em http://localhost:8000 (ou qualquer host remoto)
mcp.run(transport="streamable-http")

O código do servidor é idêntico — só muda a última linha. O servidor fica disponível em http://localhost:8000/mcp e pode ser acessado de qualquer máquina com conectividade.

Cliente remoto (HTTP)

No cliente, em vez de stdio_client, usamos streamablehttp_client com uma URL:

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

A API do agente é idêntica. A única diferença é como o cliente se conecta ao servidor: stdio spawn um processo local, streamable-http conecta a uma URL. Servidores MCP públicos (Slack, GitHub, Notion) funcionam assim — rodam remotamente e você só precisa da URL.

S06 vs S07 — resumo

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

O agente não sabe a diferença. Pra ele, ferramenta é ferramenta.

Múltiplos servidores MCP

Você pode conectar vários servidores MCP ao mesmo agente:

from langchain_mcp_adapters.client import MultiServerMCPClient

client = MultiServerMCPClient({
    "math": {
        "command": "python",
        "args": ["math_server.py"],
        "transport": "stdio",
    },
    "weather": {
        "url": "http://localhost:8000/mcp",
        "transport": "http",
    }
})

tools = await client.get_tools()
agente = create_agent(llm, tools)
# Agora o agente tem ferramentas de math E weather

Isso é poderoso: você monta um agente combinando ferramentas de fontes diferentes sem escrever código de integração.

Referências Importantes

Paper: Gorilla

Título: "Gorilla: Large Language Model Connected to Massive APIs" Autores: Shishir Patil, Tianjun Zhang, Xin Wang, Joseph E. Gonzalez Ano: 2023 Link: arXiv:2305.15334

Paper: ReAct

Título: "ReAct: Synergistic Reasoning and Acting in Language Models" Autores: Shunyu Yao, et al. Ano: 2022 Link: arXiv:2210.03629

MCP — Model Context Protocol

Site: modelcontextprotocol.io Biblioteca: langchain-mcp-adapters v0.2.2 (MIT, Mar 2026) GitHub: langchain-ai/langchain-mcp-adapters

Exercícios Práticos

Exercício 1: Descrições em Conflito

Crie duas ferramentas com descrições parecidas e teste o que acontece. Depois, corrija as descrições e veja a diferença.

📝 Gabarito

from langchain_core.tools import tool

# ❌ Descrições vagas — LLM confunde
@tool
def find_word_meaning(word: str) -> str:
    """Search for information about a word."""
    # (implementação simplificada)
    return f"Definition of {word}: ..."

@tool
def find_word_rhymes(word: str) -> str:
    """Search for information about a word."""
    # (implementação simplificada)
    return f"Rhymes of {word}: ..."

# Teste: "What does 'serendipity' mean?"
# Possível erro: LLM escolhe find_word_rhymes em vez de find_word_meaning

# ✅ Descrições específicas
@tool
def find_word_meaning(word: str) -> str:
    """Look up the definition and meaning of a word in a dictionary.
    Use when the user asks 'what does X mean' or 'definition of X'.
    """
    return f"Definition of {word}: ..."

@tool
def find_word_rhymes(word: str) -> str:
    """Find words that rhyme with a given word.
    Use when the user asks for rhymes, similar-sounding words, or poetry help.
    """
    return f"Rhymes of {word}: ..."

Exercício 2: Debug de Descrição

O agente abaixo chama a ferramenta errada. Corrija a descrição.

@tool
def calculate(a: float, b: float, operation: str) -> float:
    """Do math."""
    ops = {'add': a+b, 'subtract': a-b,
           'multiply': a*b, 'divide': a/b}
    return ops[operation]

@tool
def get_time() -> str:
    """Gives info."""
    from datetime import datetime
    return datetime.now().strftime("%d/%m/%Y %H:%M:%S")

# Teste: "Que horas são?"
# Resultado: LLM chama calculate em vez de get_time
# Por quê? "Gives info" e "Do math" são vagas demais

📝 Gabarito

@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 like addition,
    subtraction, multiplication, or division.
    Args:
        a: First number
        b: Second number
        operation: One of 'add', 'subtract', 'multiply', 'divide'
    """
    ops = {'add': a+b, 'subtract': a-b,
           'multiply': a*b, 'divide': a/b}
    return ops[operation]

@tool
def get_time() -> str:
    """Returns the current date and time.
    Use when the user asks about the current time, today's date,
    or 'what time is it'.
    """
    from datetime import datetime
    return datetime.now().strftime("%d/%m/%Y %H:%M:%S")

Exercício 3: Servidor MCP

Crie um servidor MCP com uma ferramenta reverse_string que inverte uma string, depois escreva um cliente que a consome.

📝 Gabarito

Servidor (reverse_server.py):

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Reverse")

@mcp.tool()
def reverse_string(text: str) -> str:
    """Reverses a string. Use when the user wants to flip or reverse text."""
    return text[::-1]

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

Cliente (Colab):

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
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="lfm2.5:8b",
    base_url="http://localhost:11434/v1",
    api_key="nao_precisa",
    temperature=0,
)

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

async with stdio_client(server_params) 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": "Reverse the word 'Guilda'"}
        )
        print(resultado["messages"][-1].content)
        # "The reverse of 'Guilda' is 'adliuG'."

Exercício 4: Agente com Ferramentas Mistas

Combine duas ferramentas @tool (que você escreveu) com ferramentas MCP de um servidor externo em um único agente.

📝 Gabarito

from langchain_core.tools import tool
from langchain.agents import create_agent
from langchain_mcp_adapters.client import MultiServerMCPClient

# Suas ferramentas @tool
@tool
def calculate(a: float, b: float, operation: str) -> float:
    """Performs a mathematical operation between two numbers.
    Args:
        a: First number
        b: Second number
        operation: One of 'add', 'subtract', 'multiply', 'divide'
    """
    ops = {'add': a+b, 'subtract': a-b,
           'multiply': a*b, 'divide': a/b}
    return ops[operation]

# Ferramentas MCP
client = MultiServerMCPClient({
    "math": {
        "command": "python",
        "args": ["math_server.py"],
        "transport": "stdio",
    },
})

mcp_tools = await client.get_tools()

# Combina tudo
all_tools = [calculate] + mcp_tools
agente = create_agent(llm, all_tools)

resultado = await agente.ainvoke(
    {"messages": "Quanto é 10 + 5 multiplicado por 3?"}
)
print(resultado["messages"][-1].content)

← Voltar para Guilda de IA