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.