$ sudo teach IT

Каждый новый сервис, который вы хотите дать агенту, до сих пор означал новую обёртку: функция с @tool, запрос к API, обработка ошибок. Для трёх сервисов это терпимо. Но если агенту нужны трекер задач, файлы, календарь и корпоративная вики, а таких агентов в компании пять, одинаковые обёртки начинают писать снова и снова в разных проектах.

MCP (Model Context Protocol) решает эту проблему стандартом. Сервис один раз оформляется как MCP-сервер, и подключить его может любое приложение, которое говорит на этом протоколе: ваш агент на LangChain, редактор кода, настольный ассистент. Многие компании уже публикуют готовые MCP-серверы для своих продуктов. После урока вы сможете написать собственный MCP-сервер на Python, подключить к агенту локальные и удалённые серверы и понимать, каким из них можно доверять.

Что уже умеем

Вы создавали инструменты через @tool и знаете, что модель выбирает их по имени, описанию и аргументам. В прошлом уроке вы подключали к главному агенту целых агентов как инструменты. Сегодня инструменты придут к агенту из отдельной программы — MCP-сервера, а LangChain сам превратит их в привычные инструменты.

Что вы узнаете в рамках статьи

  • что такое MCP и из каких ролей он состоит;
  • как написать MCP-сервер на Python с несколькими инструментами;
  • чем отличаются транспорты stdio и Streamable HTTP;
  • как получить инструменты сервера через MCPAdapter и отдать их агенту;
  • почему агента с MCP-инструментами запускают асинхронно и как выбирать надёжные серверы.

Ключевые термины

  • MCP (Model Context Protocol) — открытый протокол, по которому AI-приложения получают инструменты и данные от внешних программ.
  • MCP-сервер — программа, которая объявляет инструменты и выполняет их по запросу.
  • MCP-клиент — часть приложения, которая подключается к серверу, получает список инструментов и вызывает их.
  • Транспорт — способ связи клиента и сервера: stdio для локального процесса или Streamable HTTP для сервера в сети.
  • Асинхронный код — функции с async def, которые вызываются через await и не блокируют программу во время ожидания ответа.

Как устроен MCP

В MCP две стороны. Сервер знает, как работать с конкретным сервисом, и публикует список своих инструментов: имя, описание, схему аргументов. Клиент подключается к серверу, запрашивает этот список и по запросу вызывает инструменты. Агент при этом ничего не знает о внутреннем устройстве сервиса: для него это те же инструменты, что и созданные через @tool.

Главная выгода — повторное использование. Без стандарта пять приложений и четыре сервиса требуют до двадцати отдельных интеграций. С MCP каждый сервис оформляется сервером один раз, а каждое приложение один раз учится быть клиентом. Кроме инструментов MCP-сервер может отдавать ресурсы (данные для чтения) и готовые шаблоны промптов, но для агентов главное — инструменты.

Пишем свой MCP-сервер

Для серверов есть официальный Python SDK: pip install "mcp[cli]". Сервер — это обычный скрипт, в котором функции помечаются декоратором @mcp.tool(). Подсказки типов становятся схемой аргументов, а докстринг — описанием инструмента.

# notes_server.py
from mcp.server import MCPServer

mcp = MCPServer("Notes")
notes: dict[str, str] = {}

@mcp.tool()
def add_note(title: str, text: str) -> str:
    """Сохраняет заметку под заданным заголовком."""
    notes[title] = text
    return f"Заметка «{title}» сохранена"

@mcp.tool()
def list_notes() -> list[str]:
    """Возвращает заголовки всех сохранённых заметок."""
    return list(notes)

if __name__ == "__main__":
    mcp.run()  # без аргументов используется транспорт stdio

Если запустить скрипт напрямую, в консоли ничего не появится: сервер ждёт сообщений клиента на стандартном вводе. Так и должно быть — запускать его будет клиент. Проверить сервер вручную можно командой mcp dev notes_server.py, которая откроет инспектор с формами для вызова инструментов. Обратите внимание: в старых руководствах класс назывался FastMCP и импортировался из mcp.server.fastmcp. Во второй версии SDK этот модуль удалён, используйте MCPServer.

Транспорты: stdio и Streamable HTTP

Транспорт stdio подходит для локальных серверов. Клиент сам запускает скрипт сервера как дочерний процесс и обменивается с ним сообщениями через стандартный ввод и вывод. Ничего не нужно разворачивать, сервер живёт ровно столько, сколько клиент.

Streamable HTTP нужен, когда сервер работает отдельно: на другой машине, в облаке или как общий сервис для нескольких команд. Сервер запускается заранее, а клиент подключается к нему по адресу.

if __name__ == "__main__":
    mcp.run(transport="streamable-http", port=8000)

Что увидим в консоли: сообщение о запуске сервера, после чего он доступен по адресу http://127.0.0.1:8000/mcp. Путь /mcp используется по умолчанию.

Подключаем MCP-серверы к агенту

Клиентская часть встроена в LangChain: pip install "langchain[mcp]". Класс MCPAdapter подключается к серверу и превращает его инструменты в инструменты LangChain. Цель подключения определяет транспорт: объект Path запускает локальный скрипт через stdio, строка с адресом http(s) подключается по Streamable HTTP. Для нескольких серверов передаётся словарь конфигурации.

import asyncio
from langchain.agents import create_agent
from langchain.mcp import MCPAdapter

config = {
    "mcpServers": {
        "notes": {"command": "python", "args": ["/home/anna/mcp/notes_server.py"]},
        "weather": {"url": "<http://127.0.0.1:8000/mcp>"},
    }
}

async def main():
    async with MCPAdapter(config) as adapter:
        tools = await adapter.list_tools()
        print([t.name for t in tools])

        agent = create_agent(model="openai:gpt-6-luna", tools=tools)
        result = await agent.ainvoke({"messages": [{
            "role": "user",
            "content": "Запиши заметку «Купить» с текстом «молоко, хлеб» и покажи все заметки",
        }]})
        print(result["messages"][-1].content)

asyncio.run(main())

Что увидим: сначала список вроде ['notes_add_note', 'notes_list_notes', 'weather_get_forecast'] — при нескольких серверах к имени инструмента добавляется ключ сервера, чтобы одинаковые имена не конфликтовали. Затем ответ агента: «Заметка сохранена. Сейчас у вас одна заметка: Купить».

Почему всё асинхронно? Общение с сервером — это ожидание ответа процесса или сети, и MCP-инструменты в LangChain создаются асинхронными. Поэтому код помещают в async def, ждут результатов через await, агента вызывают через ainvoke, а запускают всё через asyncio.run(main()). Блок async with открывает соединение с серверами и закрывает его по выходе. Пространство имён langchain.mcp пока помечено как бета, поэтому при первом импорте вы можете увидеть предупреждение — это нормально.

К MCP-инструментам применимо всё, что вы уже знаете: их можно передать субагенту, а опасные из них поставить под подтверждение через HumanInTheLoopMiddleware.

Частые ошибки

  • Относительный путь к скрипту сервера. Клиент запускает процесс из своей рабочей папки, и относительный путь может не найтись. Указывайте полный путь.
  • Вызов invoke вместо ainvoke. MCP-инструменты асинхронные; запускайте агента через await agent.ainvoke(...) внутри async def.
  • Подключение непроверенных серверов. Описания инструментов попадают прямо в промпт модели, а сам сервер выполняет действия от вашего имени. Подключайте серверы только из надёжных источников.
  • Подключить всё сразу. Каждый инструмент добавляет своё описание в каждый запрос к модели. Десятки лишних инструментов увеличивают стоимость и путают модель.

Наиболее частые вопросы

  • Чем MCP-инструмент отличается от @tool? — Для агента ничем: после загрузки это обычные инструменты LangChain. Разница в том, что код живёт в отдельном сервере, который можно переиспользовать в разных приложениях.
  • Можно ли писать сервер не на Python? — Да, протокол не зависит от языка. Агенту на Python всё равно, на чём написан сервер.
  • Что такое MultiServerMCPClient из старых примеров? — Это клиент из пакета langchain-mcp-adapters, который больше не развивается. Его заменил MCPAdapter из langchain.mcp.
  • Нужен ли интернет для stdio-сервера? — Нет, сервер работает на вашей машине. Интернет нужен, только если сам сервер обращается к внешнему API.

Итоги

Поздравляем, вы освоили стандартный способ расширять возможности агентов! Теперь вы понимаете, как устроен MCP, умеете написать собственный сервер с инструментами на MCPServer, запустить его через stdio или Streamable HTTP и подключить к агенту один или несколько серверов через MCPAdapter. Вы знаете, зачем здесь асинхронный код и почему к выбору серверов нужно подходить так же внимательно, как к установке любых сторонних программ.

Агент стал мощнее, а значит, тем важнее понимать, что происходит у него внутри. В следующем уроке вы научитесь отслеживать каждый шаг агента и измерять качество его ответов на наборе тестовых вопросов.

На следующий урок