Skip to main content
Glama

mcp-project-helper

Минимальный MCP (Model Context Protocol) сервер, который даёт AI-ассистенту для программирования — в частности, Claude Code — небольшой, безопасный набор инструментов (tools) для работы с одной конкретной директорией проекта: поиск по её файлам, чтение файла, поиск по локальной документации и запуск предварительно одобренной проверки (тестов).

Проект реализован поэтапно (Stage 0 → Stage 3, история промптов — в PROMPTS.md); текущая стадия — Stage 3: финализация. Все четыре tool реализованы и покрыты тестами (Stage 1), сервер подключён к Claude Code и проверен вручную реальными запросами через Claude Code CLI (Stage 2–3, evidence — в evidence/).

О проекте

Вместо того чтобы давать ассистенту прямой доступ к shell или неограниченный доступ к файловой системе, проект предоставляет узкую, легко аудируемую поверхность из четырёх tools:

  • search_project_files — поиск текста, ограниченный корнем проекта.

  • read_project_file — чтение одного файла, ограниченное корнем проекта.

  • get_docs — поиск по локальной документации в docs/ этого репозитория.

  • run_project_check — запуск проверки из белого списка (сейчас это tests), но никогда произвольной shell-команды.

Это учебный проект (домашнее задание) — цель которого не покрыть все возможные случаи использования, а показать сквозной, honestly-документированный пример MCP-сервера: от каркаса и примитивов безопасности (Stage 0), через реальную реализацию tools (Stage 1), до интеграции с IDE-агентом и воспроизводимых evidence реальных вызовов (Stage 2–3).

Related MCP server: GPT Commander

Что такое MCP и как работает подключение агента

MCP (Model Context Protocol) — открытый протокол на основе JSON-RPC, описывающий, как AI-ассистент (клиент/хост, например Claude Code) обнаруживает и вызывает внешние инструменты (tools), предоставляемые отдельным процессом (MCP-сервером), без того чтобы ассистент имел прямой доступ к shell, сети или файловой системе хоста.

В этом проекте используется stdio-транспорт — самый простой и самый распространённый способ для локальных инструментов:

  1. Хост (Claude Code) читает свою MCP-конфигурацию (.mcp.json) и запускает сервер как обычный локальный подпроцесс, с указанной командой/аргументами и переменными окружения.

  2. Хост и сервер обмениваются JSON-RPC-сообщениями через stdin/stdout этого подпроцесса (отсюда требование, что stdout зарезервирован только под протокол — см. раздел "Логирование и отладка").

  3. Хост вызывает initialize() — сервер отвечает своим именем/версией (mcp-project-helper 0.1.0) и возможностями.

  4. Хост вызывает list_tools() — сервер возвращает список зарегистрированных tools с их именем, описанием и JSON Schema входных параметров (inputSchema), сгенерированной MCP SDK из сигнатуры функции.

  5. Когда пользователь (или сама модель) решает вызвать один из tools, хост отправляет call_tool(name, arguments); сервер выполняет соответствующую Python-функцию и возвращает структурированный результат (см. "Контракт вывода tools" ниже) либо ошибку уровня tool.

  6. Никакого сетевого порта не открывается: жизненный цикл сервера полностью привязан к подпроцессу, запущенному хостом — если хост закрывает подключение, подпроцесс завершается.

Здесь нет обращений ни к одному LLM/AI API (OpenAI, Anthropic и т. д.): этот сервер только предоставляет tools, которые вызывает клиент (Claude Code); "поиск" в get_docs — простое детерминированное совпадение подстроки по секциям Markdown, без embeddings/векторной БД. Для запуска сервера не нужен никакой API-ключ.

Что считается tool в данном сервере

Tool — это обычная функция Python, декорированная @mcp.tool(), принимающая JSON-сериализуемые аргументы и возвращающая dict[str, Any]. MCP SDK автоматически:

  • генерирует inputSchema (JSON Schema) из сигнатуры и аннотаций типов аргументов функции — отдельно эту схему нигде не приходится описывать руками;

  • превращает аннотацию возвращаемого значения -> dict[str, Any] в структурированный вывод tool (outputSchema/structuredContent), см. "Контракт вывода tools" ниже;

  • превращает необработанное исключение Python внутри tool-функции в структурированный результат с ошибкой на уровне tool (CallToolResult.is_error = True), не роняя саму MCP-сессию.

Все четыре регистрации находятся рядом в server.py:37-58; каждая тонкая обёртка, обращённая к MCP (docstring которой становится описанием tool, видимым модели), делегирует вызов настоящей реализации в tools/*.py, отделяя сигнатуру уровня протокола от логики.

Стек

  • Python 3.14 (requires-python = ">=3.10" в pyproject.toml — это реальная нижняя граница используемого MCP SDK, а не утверждение, что работает только 3.14).

  • Официальный MCP Python SDK (пакет mcp, установленная версия 2.0.0) — предоставляет фреймворк сервера (mcp.server.MCPServer), регистрацию tools (@mcp.tool()) и stdio-транспорт (mcp.run(transport="stdio")).

  • pytest — единственная dev-зависимость, для набора тестов.

  • Нет интеграции с LLM/AI API и нет сетевого транспорта (HTTP/SSE не настроен) — см. предыдущий раздел.

Архитектура

src/mcp_project_helper/
  server.py        точка входа: создаёт MCPServer, регистрирует tools, запускает stdio
  config.py        корень проекта / корень docs / настройки логирования / whitelist проверок / лимиты
  security.py      resolve_within_root() — единый шлюз ограничения путей
  logging_setup.py логирование в stderr (+ опционально файл), не затрагивая stdout
  tools/
    search_project_files.py   поиск текста в пределах корня проекта
    read_project_file.py      чтение одного файла в пределах корня проекта
    get_docs.py                поиск по секциям markdown в docs/
    run_project_check.py       запуск подпроцесса из белого списка

Каждый tool, работающий с файлами, проходит через security.resolve_within_root(root, relative_path) прежде, чем открыть какой-либо путь. config.py определяет корень проекта из переменной окружения MCP_PROJECT_HELPER_ROOT (по умолчанию ./demo_project), поэтому сервер можно направить на любой проект без изменения кода.

Реализованные MCP tools

search_project_files(query, path=".", max_results=50)

Рекурсивно ищет по текстовым файлам под path (относительно корня проекта; по умолчанию — весь корень) точное совпадение подстроки query. Пропускает директории из config.IGNORED_DIR_NAMES (.git, .venv, __pycache__, node_modules, ...) и любые директории *.egg-info. Файлы проверяются на бинарный контент (байт NUL или невалидный UTF-8 в первых 4 КБ) и молча пропускаются, а не приводят к ошибке. Никогда не идёт по символическим ссылкам на директории или файлы за пределами корня — каждый путь-кандидат дополнительно проверяется через resolve_within_root в дополнение к стандартному поведению os.walk, не следующему по символическим ссылкам на директории.

max_results ограничен сверху значением config.SEARCH_RESULTS_CAP (200); совпавшие строки длиннее config.SEARCH_MAX_LINE_CHARS (300) обрезаются; файлы больше config.SEARCH_MAX_FILE_BYTES (2 МБ) пропускаются, а не сканируются.

Реализация: tools/search_project_files.py:41-130.

read_project_file(path)

Читает один текстовый файл по пути path (относительно корня проекта). Отклоняет директории, несуществующие файлы и бинарный контент (байт NUL или невалидный UTF-8). Содержимое ограничено значением config.READ_MAX_FILE_BYTES (200 КБ) — файлы большего размера возвращаются обрезанными, а не отклоняются.

Реализация: tools/read_project_file.py:25-69.

get_docs(query=None, max_results=10)

Ищет по docs/*.md (рекурсивно), разбитым на секции по заголовкам Markdown. При наличии query возвращает секции, чей заголовок или тело содержат искомую подстроку (без учёта регистра), каждая с указанием исходного файла и заголовка. Без query возвращает список из одной секции на файл — перечень того, какая документация существует. Ограничено только config.get_docs_root() — никогда не корнем проекта.

max_results ограничен сверху значением config.DOCS_RESULTS_CAP (50); фрагменты (snippets) ограничены значением config.DOCS_MAX_SNIPPET_CHARS (800 символов).

Реализация: tools/get_docs.py:58-114.

run_project_check(check_name)

Запускает проверку из белого списка. check_name ищется в config.ALLOWED_CHECKS до того, как что-либо запускается — неизвестное имя немедленно вызывает ошибку, подпроцесс при этом никогда не запускается. Argv из белого списка выполняется через subprocess.run(argv, shell=False, cwd=<корень проекта>, timeout=...): без shell, с фиксированной рабочей директорией, и ничего от вызывающей стороны не добавляется в командную строку.

Реализация: tools/run_project_check.py:32-90.

Whitelist

ALLOWED_CHECKS = {
    "tests": [sys.executable, "-m", "pytest", "-q"],
}

Определено в config.py:55-57. sys.executable (а не просто строка "pytest") используется, чтобы проверка всегда выполнялась с тем же интерпретатором/окружением, что и сам сервер, независимо от того, что первым стоит в PATH. Здесь намеренно нет записи lint: в этом репозитории нет зависимости или конфигурации ruff, поэтому подключение проверки "lint" было бы либо фикцией, либо обманом. Добавить её можно позже (config.ALLOWED_CHECKS["lint"] = [sys.executable, "-m", "ruff", "check", "."]), когда ruff станет настоящей зависимостью проекта с реальной конфигурацией — механизм whitelist уже поддерживает это без каких-либо других изменений кода.

Таймаут (config.CHECK_TIMEOUT_SECONDS, по умолчанию 60 с) и ограничение объёма вывода (config.CHECK_MAX_OUTPUT_CHARS, по умолчанию 20 000 символов на поток) применяются к каждому запуску проверки.

Tool outputs contract

Каждый tool возвращает обычный Python dict из функции с аннотацией -> dict[str, Any]; MCP SDK автоматически распознаёт это как структурированный вывод tool (заполняет CallToolResult.structured_content и выводит outputSchema) — здесь нигде вручную не сериализуются результаты в JSON-строку. Ошибочные ситуации (некорректный ввод, выход за пределы пути, неизвестная проверка, файл не найден, бинарный контент и т. д.) вызывают исключение Python, а не возврат dict; SDK автоматически превращает это в результат с ошибкой на уровне tool (CallToolResult.is_error = True). Единственное исключение — таймаут проверки: это легитимный результат выполнения успешно запущенной проверки, а не ошибка входных данных, поэтому он возвращается как структурированный dict {"status": "error", ...}, а не вызывает исключение.

search_project_files

{
  "status": "success",
  "query": "apply_discount",
  "path": ".",
  "matches": [
    {"file": "demo_app/services.py", "line": 12, "text": "def apply_discount(order: Order, percent: float) -> float:"}
  ],
  "count": 4,
  "truncated": false
}

read_project_file

{
  "status": "success",
  "file": "demo_app/models.py",
  "content": "...",
  "size": 397,
  "truncated": false
}

get_docs

{
  "status": "success",
  "query": "whitelist",
  "results": [
    {"file": "architecture.md", "heading": "Whitelist", "snippet": "..."}
  ],
  "count": 1,
  "truncated": false
}

run_project_check

{
  "status": "success",
  "check_name": "tests",
  "exit_code": 0,
  "stdout": "...",
  "stderr": "",
  "truncated": false
}

При таймауте: {"status": "error", "check_name": ..., "error": "check timed out after 60s", "exit_code": null, "stdout": "...", "stderr": "...", "truncated": ...}.

Названия полей и конвенции status/count/truncated выше считаются устойчивым контрактом на будущее, а не деталью реализации.

Ограничения безопасности

  • Ограничение путей: security.resolve_within_root (security.py:19-50) отклоняет абсолютные пути, обход через .. (на любую глубину), байты NUL и символические ссылки, ведущие за пределы настроенного корня. Используется в read_project_file и search_project_files относительно корня проекта, а также повторно в search_project_files для каждого файла-кандидата во время обхода. Покрыто unit-тестами в tests/test_security.py и подтверждено вручную реальным негативным тестом через Claude Code (см. "Результаты проверки" ниже, тест 6).

  • Отсутствие выхода за пределы через символические ссылки при обходе: search_project_files и get_docs никогда не идут по символическим ссылкам на директории (поведение os.walk по умолчанию) и полностью пропускают символические ссылки на файлы.

  • Отсутствие произвольных shell-команд: run_project_check проверяет запрошенное имя проверки по config.ALLOWED_CHECKS (config.py:55-57) до того, как что-либо будет запущено; неизвестные имена отклоняются немедленно, а сама проверка выполняется через subprocess.run(argv, shell=False, ...) с фиксированным cwd и без каких-либо аргументов, добавленных вызывающей стороной.

  • Ограниченный вывод везде: каждый tool ограничивает объём возвращаемых данных — max_results + жёсткие пределы для поиска и docs, ограничение по байтам для чтения файлов, ограничение по символам + таймаут для вывода проверки — так что ни один вызов не может вернуть неограниченный объём данных или работать бесконечно.

  • stdout остаётся чистым: всё логирование идёт через logging_setup.py в stderr (и опционально в файл лога); ничто в сервере не пишет в stdout, который зарезервирован для JSON-RPC framing протокола MCP.

  • Отсутствие секретов в логах: сервер вообще не принимает никаких API-ключей или учётных данных. Каждый реальный вызов tool логирует имя tool, его безопасные входные параметры (строки запроса, пути, имена проверок, количество/размер результатов — но никогда содержимое файла) и финальный status=success/status=error.

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

Каждый реальный вызов tool логирует одну строку через общий логгер mcp_project_helper (stderr, плюс опциональный файл через MCP_PROJECT_HELPER_LOG_FILE), например (реальные строки из evidence/tool-calls.log):

INFO mcp_project_helper: tool=search_project_files query='apply_discount' path='.' max_results=50 matches=4 truncated=False status=success
INFO mcp_project_helper: tool=read_project_file path='demo_app/models.py' size=397 truncated=False status=success
INFO mcp_project_helper: tool=run_project_check check_name='tests' exit_code=0 status=success
INFO mcp_project_helper: tool=read_project_file path='../../../../etc/passwd' status=error

Содержимое файлов никогда не логируется — только метаданные о вызове (пути, строки запроса, размеры, количество, коды завершения). Настройка логирования — logging_setup.py:20-42.

Для отладки:

  • Уровень логирования регулируется MCP_PROJECT_HELPER_LOG_LEVEL (DEBUG, INFO, WARNING, ERROR, CRITICAL; по умолчанию INFO).

  • Файл лога задаётся MCP_PROJECT_HELPER_LOG_FILE; по умолчанию (без этой переменной) пишется только в stderr. При запуске из Claude Code (.mcp.json) он указывает на evidence/tool-calls.log.

  • Никогда не используйте print() в коде сервера — stdout зарезервирован под JSON-RPC-протокол; любой лишний вывод в stdout ломает stdio-транспорт.

  • Чтобы посмотреть, какие вызовы реально произошли в текущей сессии Claude Code, откройте файл, на который указывает MCP_PROJECT_HELPER_LOG_FILE (evidence/tool-calls.log), либо запустите сервер вручную (python -m mcp_project_helper.server) и смотрите stderr.

Установка

python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

Единственная runtime-зависимость — пакет mcp; pytest — зависимость только для разработки/тестов (обе зафиксированы в pyproject.toml).

Настройка окружения

Конфигурация задаётся через переменные окружения — скопируйте .env.example в .env и при необходимости измените значения:

Переменная

Назначение

Значение по умолчанию

MCP_PROJECT_HELPER_ROOT

Единственная директория, к которой имеют доступ файловые tools (search_project_files, read_project_file, get_docs — только к её docs/, get_docs работает от корня репозитория, а не MCP_PROJECT_HELPER_ROOT).

./demo_project

MCP_PROJECT_HELPER_LOG_FILE

Путь к файлу лога (см. "Логирование и отладка"). Логи всегда также идут в stderr.

не задано (только stderr)

MCP_PROJECT_HELPER_LOG_LEVEL

Один из DEBUG/INFO/WARNING/ERROR/CRITICAL.

INFO

Секреты (API-ключи, токены) серверу не нужны — .env.example содержит только безопасные примеры путей и уровня логирования, а .env игнорируется git'ом (см. "Структура проекта" ниже).

Запуск MCP-сервера

Запуск сервера напрямую (он будет ожидать клиента на stdin — это нормально для MCP-серверов со stdio-транспортом; выход через Ctrl+C):

python -m mcp_project_helper.server

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

pytest -q

Запуск собственных тестов demo-проекта напрямую (то, что по умолчанию запускает run_project_check("tests"), так как MCP_PROJECT_HELPER_ROOT по умолчанию указывает на demo_project):

cd demo_project && pytest -q

Интеграция с Claude Code

Этот репозиторий содержит project-scoped файл .mcp.json в корне репозитория — конфигурация именно для Claude Code (в отличие от .vscode/mcp.json — отдельной конфигурации native MCP host VS Code; см. подробное сравнение ниже).

Claude Code обнаруживает .mcp.json при открытии папки проекта, запускает сервер как дочерний процесс и общается с ним по JSON-RPC через stdio — тот же транспорт, что используется во всех автоматизированных тестах этого проекта, просто запущенный самим Claude Code, а не тестовым harness'ом.

Подтверждённый end-to-end сценарий: Claude Code CLI → MCP server → custom tools. Все 6 проверочных запросов (см. "Результаты проверки" ниже) были реально выполнены через Claude Code CLI с этим сервером, подключённым по .mcp.json — не только сконфигурированы, а фактически вызваны, с реальными скриншотами и записями в server-side логе.

Конфигурация для Claude Code

.mcp.json:

{
  "mcpServers": {
    "mcp-project-helper": {
      "command": "${CLAUDE_PROJECT_DIR:-.}/.venv/bin/python",
      "args": ["-m", "mcp_project_helper.server"],
      "env": {
        "MCP_PROJECT_HELPER_ROOT": "${CLAUDE_PROJECT_DIR:-.}/demo_project",
        "MCP_PROJECT_HELPER_LOG_FILE": "${CLAUDE_PROJECT_DIR:-.}/evidence/tool-calls.log"
      }
    }
  }
}

${CLAUDE_PROJECT_DIR} разворачивается самим Claude Code в абсолютный путь той директории, в которую был клонирован репозиторий, поэтому файл не содержит специфичных для конкретной машины путей и не требует правок после git clone. Используется именно форма с fallback-значением ${CLAUDE_PROJECT_DIR:-.}, а не голое ${CLAUDE_PROJECT_DIR}: без :-. переменная не разворачивалась, и Claude Code пытался буквально запустить ${CLAUDE_PROJECT_DIR}/.venv/bin/python как путь к исполняемому файлу (эта ошибка была реально замечена при первой версии конфигурации Stage 2, см. REPORT.md). MCP_PROJECT_HELPER_ROOT задан явно как ${CLAUDE_PROJECT_DIR:-.}/demo_project, чтобы корень проекта, передаваемый серверу, был однозначным независимо от собственного значения по умолчанию в config.py.

Замечание по платформам: .venv/bin/python — это структура venv для Unix (macOS/Linux), используемая во всём этом проекте. В Windows эквивалентный путь — .venv\Scripts\python.exe; чтобы поддержать эту платформу тоже, .mcp.json потребовал бы второй, специфичной для Windows записи (или скрипта-обёртки) — этого не сделано, так как проект разрабатывался и проверялся только на macOS.

Конфигурация для VS Code

Этот репозиторий также содержит .vscode/mcp.json — отдельную конфигурацию рабочей области для встроенного MCP-хоста VS Code (используемого агентным режимом GitHub Copilot Chat):

{
  "servers": {
    "mcp-project-helper": {
      "type": "stdio",
      "command": "${workspaceFolder}/.venv/bin/python",
      "args": ["-m", "mcp_project_helper.server"],
      "env": {
        "MCP_PROJECT_HELPER_ROOT": "${workspaceFolder}/demo_project",
        "MCP_PROJECT_HELPER_LOG_FILE": "${workspaceFolder}/evidence/tool-calls.log"
      }
    }
  }
}

Тот же stdio-сервер mcp-project-helper, с MCP_PROJECT_HELPER_ROOT, заданным как ${workspaceFolder}/demo_project, и MCP_PROJECT_HELPER_LOG_FILE, заданным как ${workspaceFolder}/evidence/tool-calls.log.

Почему два файла, а не один: .mcp.json и .vscode/mcp.json следуют разным, несовместимым схемам, а их переменные подстановки путей не взаимозаменяемы между хостами:

  • .mcp.json (конфигурация Claude Code) использует верхнеуровневый ключ mcpServers и разворачивает ${CLAUDE_PROJECT_DIR:-.} в корень репозитория.

  • .vscode/mcp.json (конфигурация native MCP host VS Code) использует верхнеуровневый ключ servers, явное поле "type": "stdio" и разворачивает вместо этого ${workspaceFolder} в путь открытой папки. MCP-хост VS Code не понимает ${CLAUDE_PROJECT_DIR} — при попытке открыть .mcp.json напрямую из VS Code переменная передаётся буквально, и сервер не может запуститься (spawn ${CLAUDE_PROJECT_DIR}/.venv/bin/python ENOENT) — это реально наблюдавшаяся ошибка, из-за которой и появился отдельный .vscode/mcp.json. Хранение конфигурации каждого хоста в своём файле, со своей переменной, избегает этой ошибки и позволяет использовать оба инструмента с одним и тем же клоном без того, чтобы один конфиг шёл на компромисс с синтаксисом другого.

.vscode/mcp.json — единственное исключение из общего правила игнорирования .vscode/* в .gitignore; остальное локальное состояние VS Code (settings.local.json и т. п.) не отслеживается.

Статус проверки VS Code: .vscode/mcp.json синтаксически и семантически корректен (тот же сервер, та же команда/переменные окружения, что и рабочая конфигурация Claude Code) и был провалидирован как JSON. Дополнительно подтверждён реальным скриншотом evidence/vscode_mcp_server_connected.png факт, что встроенный native MCP host VS Code реально запускает сервер по этой конфигурации: Starting server mcp-project-helper → Connection state: Running → Discovered 4 tools, с подтверждающей строкой из собственного stderr-лога процесса mcp_project_helper в этом же выводе. Это не то же самое, что подтверждение вызова custom tools через интерфейс VS Code — ни один пользовательский сценарий (search_project_files и т. д.) через этот интерфейс не выполнялся и не заявляется как проверенный. Единственная IDE-интеграция, подтверждённая вплоть до реальных вызовов tools пользователем (скриншоты + server-side логи для всех 6 сценариев) — Claude Code CLI, см. "Результаты проверки" ниже. Отдельно от обоих: интеграция через Claude Code Desktop / расширение Claude Code внутри VS Code не проверялась в этой сессии вообще — не путать ни с native MCP host VS Code (этот раздел), ни с Claude Code CLI.

Как включить MCP

Кратко (подробности — в подразделах выше):

Claude Code:

  1. Создайте venv и установите зависимости (раздел "Установка").

  2. Откройте корень репозитория в Claude Code (claude из корня репозитория).

  3. Claude Code обнаруживает .mcp.json и один раз предлагает подтвердить доверие рабочей области для сервера mcp-project-helper — подтвердите.

  4. Выполните /mcp (или claude mcp list в терминале) и убедитесь, что mcp-project-helper подключён с 4 tools.

VS Code (native MCP host, агентный режим Copilot Chat):

  1. Создайте venv так же, как для Claude Code — .vscode/mcp.json ожидает тот же .venv/bin/python.

  2. Откройте корень репозитория как папку в VS Code.

  3. VS Code обнаруживает .vscode/mcp.json и предлагает запустить сервер — запустите/подтвердите доверие.

  4. Проверьте состояние через MCP: List Servers.

Оба варианта предполагают Unix-структуру venv (.venv/bin/python); в Windows — .venv\Scripts\python.exe (не настроено, см. выше).

Проверочные запросы

Шесть сценариев, реально выполненных через Claude Code CLI для подтверждения интеграции (полная таблица с результатами — в evidence/README.md):

  1. Найди через MCP все места использования функции apply_discount в demo_project → ожидается search_project_files.

  2. Прочитай через MCP файл demo_app/models.py и кратко объясни, какие модели там определены → ожидается read_project_file.

  3. Используя MCP-документацию проекта, расскажи, какие ограничения безопасности есть у MCP-сервера → ожидается get_docs.

  4. Проверь через MCP-инструмент, проходят ли тесты demo_project → ожидается run_project_check.

  5. Используя только MCP-инструменты, найди в demo_project реализацию apply_discount, затем прочитай файл, где она определена, и объясни её параметры/возврат/расчёт скидки → ожидается цепочка из двух tools: search_project_files, затем read_project_file.

  6. (негативный / security-тест) Попробуй через MCP прочитать файл ../../../../etc/passwd → ожидается отказ read_project_file со структурированной ошибкой (путь выходит за пределы разрешённого корня).

Результаты проверки

Все 6 из 6 запросов выполнены успешно (в тесте 5 — оба ожидаемых tool, в правильном порядке; в тесте 6 успехом является ожидаемый отказ). Каждая строка подтверждена и реальным скриншотом, и независимой строкой в evidence/tool-calls.log. Полная таблица — evidence/README.md; подробный разбор с ссылками на код и логи — REPORT.md.

№

Tool

Итог

1

search_project_files

Успех, 4 совпадения

2

read_project_file

Успех, size=397

3

get_docs

Успех, найден раздел «Безопасность»

4

run_project_check

Успех, exit_code=0, 2/2 тестов пройдено

5

search_project_files → read_project_file

Успех, цепочка из двух tools

6

read_project_file

Успешный отказ (path traversal заблокирован)

Автоматизированные проверки (не заменяют, а дополняют ручное IDE-тестирование выше):

  • pytest -q из корня репозитория — 44 passed.

  • pytest -q внутри demo_project/ — 2 passed.

  • Программный stdio-хендшейк (initialize() + list_tools()) — сервер сообщает mcp-project-helper 0.1.0 и ровно 4 tools: get_docs, read_project_file, run_project_check, search_project_files.

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

mcp-project-helper/
  .mcp.json                конфигурация MCP для Claude Code (project-scoped)
  .vscode/mcp.json          конфигурация MCP для native MCP host VS Code
  .env.example              безопасные примеры переменных окружения (без секретов)
  pyproject.toml            зависимости, entry point, конфигурация pytest
  README.md                 этот файл
  REPORT.md                 итоговый отчёт по всем стадиям, со ссылками файл:строки
  PROMPTS.md                история фактически использованных промптов (Этапы 0-3)
  docs/
    architecture.md          документация, которую обслуживает get_docs
  src/mcp_project_helper/
    server.py                 точка входа: MCPServer, регистрация tools, stdio
    config.py                  корень проекта/docs, лимиты, whitelist проверок
    security.py                resolve_within_root() — ограничение путей
    logging_setup.py           логирование в stderr (+ опционально файл)
    tools/
      search_project_files.py
      read_project_file.py
      get_docs.py
      run_project_check.py
  tests/                     unit- и интеграционные тесты mcp_project_helper (44 теста)
  demo_project/              демонстрационный проект — цель для файловых tools
    demo_app/
      models.py                Product, Order
      services.py               apply_discount, OrderBuilder
      tests/test_services.py    2 теста, запускаемые run_project_check("tests")
  evidence/                  реальные доказательства ручного тестирования через Claude Code и VS Code
    README.md                  реестр всех 6 тестов с результатами + доп. evidence по VS Code
    tool-calls.log              реальный server-side лог всех 6 тестов (закоммичен)
    tool-calls.log.example      формат строки лога (шаблон)
    test1_search_project_files.png … test6_path_traversal.png   скриншоты 6 тестов Claude Code CLI (закоммичены)
    vscode_mcp_server_connected.png   доп. скриншот: native MCP host VS Code подключился, 4 tools (закоммичен)

Available Tools

4 tools
get_docsC

Search local documentation from the repo's docs/ directory.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryNo
max_resultsNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

C2.6/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. 'Search' implies read-only behavior, but the description does not state read-only safety, permissions, rate limits, or other constraints, and only discloses the local docs/ scope.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with no filler. It is appropriately brief, though it omits useful structure for parameters and usage context.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-parameter search tool with no annotations and 0% schema descriptions, the description leaves core parameter behavior and usage context unspecified. The output schema reduces return-value burden, but invocation guidance remains largely missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, and the description never mentions either parameter. It does not explain that query is optional or what null means, nor what max_results controls.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Search') and resource ('local documentation') with scope ('repo's docs/ directory'). It distinguishes itself by directory scope, but does not explicitly differentiate from sibling tools such as search_project_files or read_project_file.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No when-to-use guidance, exclusions, or alternatives are provided. The description implies documentation search, but gives no routing advice for choosing this over search_project_files or read_project_file.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_project_fileB

Read one text file inside the allowed project root.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

B3.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full behavioral burden. It does disclose two useful traits: only 'text' files are handled and paths are confined to the 'allowed project root', implying rejection of out-of-root or binary inputs. It omits error behavior, encoding, and size limits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One efficient sentence with the resource and the key constraint front-loaded; nothing is wasted. It is arguably under-specified for a tool with no annotations, but that is a completeness issue rather than verbosity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple (one required param, output schema present so return values need not be explained), and the description covers the basic contract. However, with no annotations it leaves error handling, path resolution rules, and file-type restrictions implicit — the minimum viable level.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The single 'path' parameter has no schema description (0% coverage), so the description must compensate. It implies the path is relative to the allowed project root and must point to a text file, but adds no syntax, format, or relative-vs-absolute guidance.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (Read) and resource (one text file) plus a scoping constraint (inside the allowed project root), so the agent knows it is a single-file read rather than a search. It does not name the sibling search_project_files or get_docs, so differentiation is implied by 'one text file' rather than explicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no when-to-use or when-not-to-use guidance and no mention of the alternatives (search_project_files, get_docs). The 'allowed project root' phrasing hints at a boundary, but the agent must infer when this tool is preferable to its siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

run_project_checkC

Run a whitelisted project check (e.g. 'tests').

ParametersJSON Schema
NameRequiredDescriptionDefault
check_nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

C2.9/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden, and it says nothing about side effects, permissions, runtime, or failure behavior for what is an execution-style tool. The word 'whitelisted' hints at a restricted input set but never explains enforcement or consequences.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with no filler, which is structurally efficient. The brevity is under-specification rather than waste, so it doesn't reach a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values needn't be described, but for an execution tool with no annotations the description should still cover side effects and the allowed input values. Neither is present, leaving the definition materially incomplete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% and there are no enums, so the description must compensate; it does add a sample value ('tests') and implies a fixed allowed set via 'whitelisted'. But the actual list of valid check names — the single most useful piece of information — is absent.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (Run) and resource (project check), which clearly distinguishes it from the read-oriented siblings (get_docs, search_project_files, read_project_file). However, 'whitelisted project check' remains somewhat abstract — the only concrete anchor is the parenthetical example 'tests'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to invoke this versus the sibling read tools, no prerequisites, and no mention of the whitelist constraint's practical implications (e.g. what happens if the name isn't whitelisted). The agent must infer usage entirely.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_project_filesC

Search for text inside files under the allowed project root.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathNo.
queryYes
max_resultsNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

C2.7/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden, and it discloses only that searching is confined to "the allowed project root." It says nothing about case sensitivity, regex vs literal matching, traversal depth, or result cap behavior, and does not hint that it is a non-destructive read.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single tight sentence with the scope constraint front-loaded and no filler. It is efficiently structured, though its brevity reflects under-specification elsewhere rather than optimal density.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described, but for a 3-parameter tool with zero schema coverage and no annotations, the description omits parameter meaning and usage context. An agent would call this only by guessing how query, path, and max_results interact.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate for path, query, and max_results, and it does not. Only broad implications appear ("text" for query, "project root" for path), leaving the default of "." and the meaning/limits of max_results unexplained.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ("Search for text") and resource ("files under the allowed project root"), which is enough to distinguish it from get_docs, run_project_check, and read_project_file. It is clear but never explicitly contrasts itself with the siblings, so it falls short of a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No when-to-use guidance is given, and no alternatives are named. An agent must infer on its own that this is for locating text across many files rather than reading a single file via read_project_file.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.1.0
    • First observedget_docs
    • First observedread_project_file
    • First observedrun_project_check
    • First observedsearch_project_files

TDQS

B3.2/5.0

Scored across 4 tools

Disambiguation4/5

get_docs and search_project_files both search text, but get_docs is scoped to docs/ while search_project_files covers the entire project root. run_project_check and read_project_file are clearly distinct, so only minor overlap exists.

Naming Consistency5/5

All four tools follow a consistent snake_case verb_noun pattern: get_docs, run_project_check, search_project_files, read_project_file. The convention is predictable and easy to parse.

Tool Count4/5

Four tools is a reasonable, well-scoped set for a project helper, though it sits at the lower end of the typical 3–15 range. Each tool has a clear purpose, but the set feels slightly thin for broader project assistance.

Completeness3/5

The surface is read-only and lacks file listing, write/edit operations, and general command execution. Core read/search/check operations are present, but notable gaps remain for a server named 'project helper'.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Agent-safe code retrieval MCP server that indexes repositories and provides semantic search, file navigation, call graph analysis, and bounded file reading tools for coding agents.
    4,964,314 npm
    3
    AGPL 3.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    A lightweight local coding MCP server that exposes a single project directory to ChatGPT via Streamable HTTP, enabling file operations, command execution, search, and web fetching without authentication.
    30 npm
    25
    MIT