Главная / Статьи / Протокол контекста моделей для начинающих с FastMCP и Ollama

Протокол контекста моделей для начинающих с FastMCP и Ollama

Изучите роли MCP — хост, клиент, сервер, транспорт — затем подключите сервер инструмента для прогноза погоды к локальной модели qwen3:8b с помощью FastMCP и STDIO.

1841 слов

Model Context Protocol, обычно сокращаемый до MCP, — это общий язык для подключения крупных языковых моделей к инструментам и источникам данных, к которым они не могут обратиться самостоятельно. Термин «протокол» подчеркивает, что он стандартизирует формат взаимодействия; конкретные библиотеки затем реализуют этот стандарт, чтобы командам не приходилось самостоятельно создавать сокеты и схемы сообщений. FastMCP — одна из таких реализаций, используемая в приведённом ниже примере.

Сопутствующий репозиторий: https://github.com/harshagangari747/MCPTutorial/tree/main

Предварительные требования

Демонстрация зависит от трёх пакетов: fastmcp, ollama и langchain-community. Инференс выполняется с использованием локальной модели qwen3:8b. Запустите её следующей командой:

ollama run qwen3:8b

Подготовьте папку проекта, в которой уже находятся пустые шаблоны с именами weather_server_mcp.py и app.py, чтобы у сервера и приложения были четко определенные места хранения.

Понимание MCP

В одиночку большая языковая модель представляет собой устройство для преобразования токенов: токены поступают внутрь, токены уходят наружу. Она не подключается к API погодных сервисов, не открывает базы данных и не считывает системное время, если только кто-то внешний по отношению к модели не выполняет эти действия. Провайдеры облачных сервисов иногда добавляют к своим API собственные инструменты для запуска, что удобно в производственных условиях, но затрудняет изучение самого протокола. Запуск локальной модели с помощью Ollama позволяет сохранить эксперимент в закрытой среде.

Попробуйте задать вопрос вроде «Какая погода сегодня в Италии?» Типичный местный ответ начинается с признания отсутствия прямой передачи данных о погоде. Тем не менее в этом предложении содержатся три указания, которые системе необходимо интерпретировать: погода как тема, «сегодня» как дата и Италия как место. Модели требуется способ вычисления или получения информации о погоде, способ определения даты «сегодня» и способ связи этой погоды с Италией.

Очевидным недостатком является отсутствие точного определения даты. Веса модели не позволяют ей надежно определять текущую дату. MCP становится полезным, когда модель может предлагать инструменты и аргументы, а среда выполнения фактически запускает эти инструменты, возвращая актуальные данные, которые модель может использовать для формирования ответа.

Компоненты MCP

При практическом внедрении MCP обычно выделяют несколько взаимодействующих элементов:

  1. Рабочий API — любой сервис, который уже отвечает на поставленные вопросы, например точка входа для получения информации о погоде из Интернета.
  2. Сервер MCP — процесс, скрывающий способ доступа к API или базе данных и предоставляющий инструменты для вызова.
  3. Хост MCP — интерфейс продукта, например интеллектуальный планировщик поездок, сочетающий логику больших языков моделирования с актуальными данными.
  4. Клиент MCP — мост, расположенный внутри хоста. Он сообщает модели, какие инструменты доступны, преобразует намерения модели в запросы MCP и преобразует ответы MCP в формат, понятный модели.
  5. Слой передачи данных — протокол JSON-RPC 2.0, передаваемый либо по HTTP/SSE при удаленном расположении компонентов, либо по STDIO, когда модель и инструменты находятся на одной машине.
  6. Большой язык моделирования — здесь qwen3:8b, предоставляемый сервисом Ollama.
  • Harness — факультативный инструмент оркестрации, который объединяет компоненты стека с меньшим использованием ручного кода; в первоначальном описании в качестве примера приводится Goose AI.
  • После определения этих ролей задача по получению информации о погоде в Италии превращается в процесс координации действий различных компонентов, а не в простой вызов одной модели.

    Аналогия

    Убедительная метафора помогает закрепить роли в системе. Намерение управлять автомобилем соответствует хост-приложению. Мозг представляет собой LLM: он анализирует обстановку на дороге и решает ускоряться, тормозить или менять передачи, но не может нажимать на педали. Конечности соответствуют серверу MCP; мышцы и кости внутри конечности — это отдельные инструменты: одна конечность управляет направлением или переключает передачи, другая тормозит или ускоряет. Нервный интерфейс между мозгом и мышцами — это клиент MCP. Нервы, передающие электрические импульсы, выполняют функцию транспортного средства. Автомобиль представляет собой внешний API. Собранное тело — это система креплений, обеспечивающая совместную работу всех компонентов.

    Сжатая схема соответствий:

    • LLM → мозг
    • Сервер MCP → конечность
    • Инструмент → действие мышцы
    • Хост MCP → намерение управления
    • Клиент MCP → нервный интерфейс
    • Транспорт → нервы
    • Рабочий API → автомобиль
    • Система креплений → сборка тела

    Этой картинки достаточно, чтобы сервер, клиент и механизм передачи данных не сливались в один расплывчатый «плагин».

    Принцип работы MCP

    Реализация основывается на определенных ролях: создание сервера, хоста, нейросети большого размера, механизма передачи данных, при необходимости — инструментов управления, а также настоящего API или сервиса. Сервер абстрагирует API и предоставляет инструменты. Каждый инструмент представляет собой отдельное действие, которое может запросить модель; сама модель никогда не выполняет HTTP-запросы. Клиент одновременно публикует каталог возможных действий и осуществляет перевод данных в обоих направлениях, что позволяет модели и серверу оставаться слабо связанными.

    Если у сервера есть функции get_todays_date() и get_weather_data(city, date), запрос вроде «Какая погода сегодня в Париже?» может быть реализован следующим образом:

    1. Модель понимает, что ей нужна дата сегодняшнего дня.
    2. Она просит клиент MCP использовать функцию get_todays_date.
    3. Клиент передает запрос на сервер.
  • Сервер выполняет его запрос.
  • Клиент преобразует ответ сервера для использования моделью.
  • Имея дату, модель всё равно нуждается в информации о погоде в Париже.
  • Она просит клиента вызвать функцию get_weather_data с указанием города и даты.
  • Клиент преобразует этот запрос в запрос к серверу.
  • Сервер обращается к API погоды и возвращает полученные данные.
  • Клиент снова преобразует эти данные для использования моделью.
  • Модель предоставляет окончательный ответ пользователю.
  • Вопросы о прошлых событиях, попадающие в период обучения, могут быть ответлены исключительно на основе запомненных данных, но суть MCP заключается в использовании текущего контекста: дат и погодных условий, которые меняются после обучения.

    Проект

    Пример делает описание погоды более конкретным. Сервер MCP содержит логику взаимодействия с внешним API погоды. Приложение-хост создаёт клиент MCP, регистрирует сервер и запрашивает данные у Ollama. Изоляция доступа к LLM в отдельном помощнике облегчает понимание структуры передачи данных.

    Сервер MCP

    # MCP Server
    # weather_server_mcp.py
    from fastmcp import FastMCP
    import requests
    
    # This is a server instance that we register in our host
    server = FastMCP("weather-mcp-server")
    
    
    # Third party api data
    WEATHER_API_KEY = "api_key_here"
    WEATHER_BASE_URL = "https://api.weatherapi.com/v1/"
    
    # Tool 1
    @server.tool()
    def get_weather_data(city: str) -> float:
        """Get current temperature in Celsius"""
        response = requests.get(
            WEATHER_BASE_URL + "current.json",
            params={"key": WEATHER_API_KEY, "q": city},
        )
        response.raise_for_status()
        return response.json()["current"]["temp_c"]
    
    # Tool 2
    @server.tool()
    def get_historical_weather_data(city: str, date: str) -> float:
        """Get max temperature for a historical date"""
        response = requests.get(
            WEATHER_BASE_URL + "history.json",
            params={"key": WEATHER_API_KEY, "q": city, "dt": date},
        )
        response.raise_for_status()
        return response.json()["forecast"]["forecastday"][0]["day"]["maxtemp_c"]
    
    
    if __name__ == "__main__":
        server.run()
    

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

    MCP Host, Client, LLM, метод передачи данных

    import asyncio
    import sys
    import json
    from pathlib import Path
    from langchain_community.llms import Ollama
    from fastmcp import Client
    from fastmcp.client.transports import StdioTransport
    
    
    async def main():
        # We mention the mcp server path.
        server_path = Path(__file__).parent / "weather_server_mcp.py"
    
        # The transport method here is STDIO
        transport = StdioTransport(
            command=sys.executable,
            args=[str(server_path)],
        )
    
        # Register the MCP Client
        mcp_client = Client(transport)
    
        # LLM via Ollama
        llm = Ollama(model="qwen3:8b", temperature=0.5)
    
        async with mcp_client:
            print("✓ Connected to MCP server!")
    
            # We can now access that tools are present in the weather server mcp now.
            mcp_tools = await mcp_client.list_tools()
            tools_info = "\n".join([f"- {t.name}: {t.description or t.name}" for t in mcp_tools])
    
            print(f"✓ Available tools:\n{tools_info}\n")
    
            # Interactive loop
            while True:
                question = input("🌤️  Ask: ").strip()
                if question.lower() == 'exit':
                    break
    
                try:
                    # Step 1: Ask LLM to decide which tool to use
                    decision_prompt = f"""Given the question: "{question}"
    
    Available tools:
    {tools_info}
    
    Respond with ONLY a JSON object (no other text):
    {{"tool": "tool_name", "params": {{"city": "city_name"}}}}
    
    For get_historical_weather_data, use: {{"tool": "get_historical_weather_data", "params": {{"city": "city_name", "date": "YYYY-MM-DD"}}}}"""
    
                    print(f"\n📍 Processing: {question}")
                    llm_response = llm.invoke(decision_prompt)
    
                    # Step 2: Parse JSON from LLM response
                    json_start = llm_response.find('{')
                    json_end = llm_response.rfind('}') + 1
    
                    if json_start == -1 or json_end == 0:
                        print("❌ LLM didn't return valid tool call")
                        continue
    
                    json_str = llm_response[json_start:json_end]
                    tool_call = json.loads(json_str)
    
                    print("Tool call: ", tool_call)
    
                    # Handle array responses
                    if isinstance(tool_call, list):
                        tool_call = tool_call[0]
    
                    tool_name = tool_call.get("tool")
                    params = tool_call.get("params", {})
    
                    print(f"🔧 Calling: {tool_name} with {params}")
    
                    # Step 3: Call MCP tool. This is where we actually call the tool.
                    result = await mcp_client.call_tool(tool_name, params)
                    answer = result.content[0].text
    
                    print(f"✓ Answer: {answer}°C\n")
    
                except json.JSONDecodeError as e:
                    print(f"❌ JSON parsing error: {e}")
                except Exception as e:
                    print(f"❌ Error: {e}\n")
    
    
    if __name__ == "__main__":
        asyncio.run(main())
    

    Что происходит?

    Необходимо определить путь к модулю сервера рядом с приложением-хостом:

    server_path = Path(__file__).parent / "weather_server_mcp.py"
    

    Создайте транспорт STDIO, который запустит указанный модуль с текущим интерпретатором Python:

      # The transport method here is STDIO
        transport = StdioTransport(
            command=sys.executable,
            args=[str(server_path)],
        )
    

    Инициализируйте клиент MCP с использованием этого транспорта:

    mcp_client = Client(transport)
    

    Теперь у хоста есть зарегистрированный путь к серверу, выбранный транспорт и клиент. Подключите модель через Ollama:

    llm = Ollama(model="qwen3:8b", temperature=0.5)
    

    Запросите у клиента каталог инструментов, опубликованный файлом weather_server_mcp.py:

    mcp_tools = await mcp_client.list_tools()
    

    Передайте этот каталог в команду и попросите модель отвечать только названием инструмента и его параметрами. После обработки выполните выбранный инструмент:

    result = await mcp_client.call_tool(tool_name, params)
    

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

    В совокупности MCP скорее представляет собой разделение труда, чем просто один вызов библиотеки. Модель предлагает решение; клиент его преобразует; сервер выполняет действия; механизм передачи данных пересылает сообщения JSON-RPC; хост отвечает за взаимодействие с пользователем. Как только эти границы становятся ясными, замена погодных данных на календари, CRM-системы или внутренние поисковые функции сводится в основном к созданию новых инструментов и их достаточно подробной документации, чтобы модель могла сделать правильный выбор. Во время работы цикла обращайте внимание на то, что выводит модель перед каждым вызовом инструмента. Нормальный отчет показывает, что модель указывает на реально существующий инструмент, передаёт аргументы, описанные в документации, и ждёт возврата данных от клиента перед тем, как сформировать сообщение для пользователя. Если модель выдумывает название инструмента, уточните запрос или улучшите описания инструментов. Если сервер выдаёт ошибку, отобразите её через клиент, чтобы модель могла попробовать снова или извиниться вместо того, чтобы генерировать ложную информацию.

    Зачитывание значений погоды. Эта дисциплина обратной связи имеет такое же значение, как и первоначальная настройка схемы.