Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Тестирование и отладка
testing_and_debugging

Тестирование и отладка

Unit-тесты инструментов, интеграционное тестирование через MCP-клиент, отладка.

Тестирование и отладка

Тестирование MCP-сервера включает два уровня: unit-тесты (логика инструментов) и интеграционные тесты (весь MCP-стек). В этой теме разберём оба подхода и инструменты отладки.

Unit-тесты проверяют бизнес-логику. Интеграционные тесты проверяют, что MCP-протокол работает корректно.

#Unit-тесты инструментов

Декоратор @mcp.tool() не меняет функцию — она остаётся обычной Python-функцией. Это значит, что unit-тест вызывается напрямую:

# server.py from fastmcp import FastMCP, ErrorTool mcp = FastMCP("MathServer") @mcp.tool() def divide(a: int, b: int) -> float: """Делит a на b.""" if b == 0: return ErrorTool("Деление на ноль недопустимо") return a / b
# test_server.py import pytest from server import divide from fastmcp import ErrorTool class TestDivide: def test_divide_positive(self): """Корректное деление.""" result = divide(10, 2) assert result == 5.0 def test_divide_negative(self): """Деление отрицательных чисел.""" result = divide(-10, 2) assert result == -5.0 def test_divide_by_zero(self): """Деление на ноль возвращает ErrorTool.""" result = divide(10, 0) assert isinstance(result, ErrorTool) assert "деление на ноль" in str(result).lower() def test_divide_floats(self): """Деление с плавающей точкой.""" result = divide(7, 2) assert result == 3.5

Unit-тесты быстрые — не нужно запускать сервер, подключать клиент, сериализовать данные. Только бизнес-логика.

#Unit-тесты с моками

Когда инструмент зависит от внешних систем (БД, API), мокируем зависимости:

# server.py import requests @mcp.tool() def get_user_by_email(email: str) -> str: """Ищет пользователя по email через внешний API.""" response = requests.get(f"https://api.example.com/users", params={"email": email}) response.raise_for_status() data = response.json() if not data: return ErrorTool(f"Пользователь с email {email} не найден") return str(data[0])
# test_server.py from unittest.mock import patch, MagicMock from server import get_user_by_email from fastmcp import ErrorTool class TestGetUserByEmail: @patch("server.requests.get") def test_user_found(self, mock_get): """Пользователь найден — возвращаем данные.""" mock_response = MagicMock() mock_response.json.return_value = [{"id": 1, "name": "Алиса", "email": "alice@test.com"}] mock_response.raise_for_status = MagicMock() mock_get.return_value = mock_response result = get_user_by_email("alice@test.com") assert "Алиса" in result mock_get.assert_called_once_with( "https://api.example.com/users", params={"email": "alice@test.com"} ) @patch("server.requests.get") def test_user_not_found(self, mock_get): """Пользователь не найден — ErrorTool.""" mock_response = MagicMock() mock_response.json.return_value = [] mock_response.raise_for_status = MagicMock() mock_get.return_value = mock_response result = get_user_by_email("nobody@test.com") assert isinstance(result, ErrorTool) @patch("server.requests.get") def test_api_error(self, mock_get): """Ошибка API — выбрасывает исключение.""" mock_response = MagicMock() mock_response.raise_for_status.side_effect = requests.HTTPError("500 Server Error") mock_get.return_value = mock_response with pytest.raises(requests.HTTPError): get_user_by_email("alice@test.com")

Мокирование позволяет тестировать все сценарии: успех, не найдено, ошибка сервера, таймаут — без реального API.

#Интеграционное тестирование

Интеграционный тест запускает сервер и подключается через MCP-клиент. Это проверяет весь стек: сериализацию, транспорт, десериализацию:

import pytest import pytest_asyncio from fastmcp import FastMCP from fastmcp.client import Client # Создаём тестовый сервер test_mcp = FastMCP("TestServer") @test_mcp.tool() def add(a: int, b: int) -> int: """Складывает два числа.""" return a + b @test_mcp.tool() def greet(name: str) -> str: """Приветствует пользователя.""" return f"Hello, {name}!" @pytest_asyncio.fixture async def mcp_client(): """Фикстура: запускает сервер и подключает клиент.""" client = Client(test_mcp) async with client: yield client @pytest.mark.asyncio async def test_add_tool(mcp_client): """Интеграционный тест: инструмент add через MCP-протокол.""" result = await mcp_client.call_tool("add", {"a": 3, "b": 5}) assert result == 8 @pytest.mark.asyncio async def test_greet_tool(mcp_client): """Интеграционный тест: инструмент greet через MCP-протокол.""" result = await mcp_client.call_tool("greet", {"name": "Мир"}) assert result == "Hello, Мир!" @pytest.mark.asyncio async def test_tools_list(mcp_client): """Интеграционный тест: список инструментов.""" tools = await mcp_client.list_tools() tool_names = [t.name for t in tools] assert "add" in tool_names assert "greet" in tool_names

Интеграционный тест медленнее unit-теста (запуск сервера, транспорт), но проверяет, что MCP-протокол работает корректно.

#Отладка с MCP Inspector

MCP Inspector — веб-интерфейс для отладки MCP-серверов. Запуск:

npx @modelcontextprotocol/inspector python server.py

Inspector открывает веб-страницу, где можно:

  1. Посмотреть все зарегистрированные инструменты, ресурсы, промпты
  2. Вызвать инструмент с произвольными аргументами
  3. Увидеть raw JSON-RPC-запрос и ответ
  4. Прочитать ресурсы по URI

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

#Логирование для отладки

Включите подробное логирование FastMCP для диагностики проблем:

import logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger("fastmcp") logger.setLevel(logging.DEBUG)

Логи покажут:

  • Какие JSON-RPC-сообщения приходят от клиента
  • Какие инструменты вызываются и с какими параметрами
  • Какие ошибки возникают при выполнении
  • Время выполнения каждого запроса

#Типичные проблемы и решения

#Инструмент не отображается в tools/list

Проверьте, что декоратор применён до вызова mcp.run():

# Неправильно — run() вызван до декоратора mcp.run() @mcp.tool() def my_tool(): pass # Правильно — декоратор до run() @mcp.tool() def my_tool(): pass mcp.run()

#Параметры не валидируются

Убедитесь, что аннотации типов указаны корректно. Без аннотаций FastMCP не может сгенерировать JSON Schema:

# Неправильно — нет аннотаций @mcp.tool() def add(a, b): return a + b # Правильно — аннотации типов @mcp.tool() def add(a: int, b: int) -> int: return a + b

#Сервер зависает в SSE-режиме

Проверьте, что порт свободен и хост корректен:

# Неправильно — порт может быть занят mcp.run(transport="sse", port=8000) # Правильно — обработка ошибки try: mcp.run(transport="sse", host="127.0.0.1", port=8765) except OSError as e: logger.error("Не удалось запустить SSE-сервер: %s", e)

Далее: Паттерны деплоя