2025, Oct 31 17:46

Как починить интеграцию python-a2a с FastMCP в LangChain

Почему to_langchain_tool в python-a2a падает с 404/406 при работе с FastMCP: несоответствие GET /mcp/tools vs POST /mcp. Пошаговые советы и рабочий обход.

Преобразовать локальный MCP‑сервер в инструмент LangChain через python-a2a на первый взгляд просто, но на деле это может обернуться MCPToolConversionError и 404. Проблема не в самом FastMCP‑сервере: суть в том, как python-a2a пытается получить инструменты с MCP‑эндпоинта.

Минимальная конфигурация, воспроизводящая проблему

Вызов адаптера для LangChain предельно простой. Он указывает на MCP‑эндпоинт и запрашивает конкретный инструмент.

from python_a2a.langchain import to_langchain_tool
lc_tool_obj = to_langchain_tool("http://localhost:8080/mcp", "add")
print(lc_tool_obj)

Базовый MCP‑сервер на FastMCP может выглядеть так.

from fastmcp import FastMCP
svc = FastMCP("Demo", stateless_http=True)
@svc.tool
def add(a: int, b: int) -> int:
    total = a + b
    print(total)
    return total
if __name__ == "__main__":
    svc.run()

Обращение к серверу через fastmcp.client проходит успешно: список инструментов выводится, а add вызывается как надо. Но код должен выполняться в асинхронной функции и запускаться через asyncio.run.

import asyncio
from fastmcp.client import Client
async def main():
    http_client = Client("http://localhost:8080/mcp")
    async with http_client:
        tool_list = await http_client.list_tools()
        print(tool_list)
        outcome = await http_client.call_tool("add", arguments={"a": 10, "b": 20})
        print(outcome.content)
if __name__ == "__main__":
    asyncio.run(main())

Тем не менее адаптер python-a2a завершается с 404.

Что на самом деле идет не так

MCP‑эндпоинт, который поднимает FastMCP, отвечает на POST‑запросы по пути /mcp. Когда клиент fastmcp запрашивает список инструментов, в логах сервера видно POST и редирект, за которым следует 200 OK.

INFO:     127.0.0.1:43026 - "POST /mcp HTTP/1.1" 307 Temporary Redirect
INFO:     127.0.0.1:43026 - "POST /mcp/ HTTP/1.1" 200 OK

Напротив, python-a2a v0.5.9 обращается к другому URL и использует неверный HTTP‑метод. Его to_langchain_tool() бьется в GET‑эндпоинт, который FastMCP не предоставляет для обнаружения инструментов, из‑за чего получается 406 или 404 в зависимости от ветки обработки на стороне сервера.

INFO:     127.0.0.1:47460 - "GET /mcp/tools HTTP/1.1" 406 Not Acceptable

Такое поведение следует напрямую из исходников адаптера, где формируется GET к пути /tools.

tools_response = requests.get(f"{mcp_url}/tools")

Поскольку сервер ожидает POST на /mcp, а не GET на /mcp/tools, адаптер падает с MCPToolConversionError и сообщает 404.

Что можно поправить уже сейчас

С вашей стороны нужны два шага, хотя сами по себе они не устранят проблему адаптера. Во‑первых, убедитесь, что FastMCP‑сервер доступен по HTTP, а не через stdio, выбрав подходящий транспорт. Во‑вторых, запускайте клиента из асинхронной точки входа через asyncio.run — это обязательно для корректной работы.

Вот конфигурация сервера с использованием HTTP‑транспорта.

from fastmcp import FastMCP
svc = FastMCP("Demo", transport="streamable-http")
@svc.tool
def add(a: int, b: int) -> int:
    total = a + b
    print(total)
    return total
if __name__ == "__main__":
    svc.run()

И пример асинхронного вызова клиента, который уже корректно работает с FastMCP.

import asyncio
from fastmcp.client import Client
async def main():
    http_client = Client("http://localhost:8080/mcp")
    async with http_client:
        tool_list = await http_client.list_tools()
        print(tool_list)
        outcome = await http_client.call_tool("add", arguments={"a": 10, "b": 20})
        print(outcome.content)
if __name__ == "__main__":
    asyncio.run(main())

Ключевой момент остается прежним: даже при корректном HTTP‑транспорте и правильном асинхронном клиенте to_langchain_tool() в python-a2a продолжает слать GET /mcp/tools и падает. Несоответствие находится в самом адаптере и требует исправления на стороне проекта. Проблема уже сообщена мейнтейнерам python-a2a: https://github.com/themanojdesai/python-a2a/issues/74.

Почему это важно для вашего стека

Конвертация MCP‑серверов в инструменты LangChain — полезный путь интеграции, но тонкие несостыковки протокола могут все сорвать. Понимание того, какие запросы ожидает сервер (POST на /mcp) и что фактически отправляет адаптер (GET на /mcp/tools), помогает быстро отличать ошибки конфигурации от дефектов на уровне адаптера. Это также убережет от поисков причин 404 и 406 не там, где нужно.

Итоги и практические советы

Если вам нужна работа “здесь и сейчас”, общайтесь с MCP‑сервером через fastmcp.client и асинхронную точку входа. Запускайте сервер с HTTP‑транспортом, например streamable-http, чтобы он был доступен по HTTP. Для конвертации в инструмент LangChain дождитесь исправления в to_langchain_tool() библиотеки python-a2a: текущая реализация выполняет GET к пути /tools, который FastMCP не использует для обнаружения инструментов. Следите за апстрим‑иссью и не полагайтесь на to_langchain_tool() в продакшене, пока адаптер не будет согласован с HTTP‑потоком MCP.

Статья основана на вопросе на StackOverflow от JayantSeth и ответе от furas.