mcp-project-helper
README.md
# mcp-project-helper
Минимальный MCP (Model Context Protocol) сервер, который даёт AI-ассистенту
для программирования — в частности, Claude Code — небольшой, безопасный
набор инструментов (tools) для работы с **одной конкретной директорией
проекта**: поиск по её файлам, чтение файла, поиск по локальной документации
и запуск предварительно одобренной проверки (тестов).
Проект реализован поэтапно (Stage 0 → Stage 3, история промптов — в
[PROMPTS.md](PROMPTS.md)); текущая стадия — **Stage 3: финализация**. Все
четыре tool реализованы и покрыты тестами (Stage 1), сервер подключён к
Claude Code и проверен вручную реальными запросами через Claude Code CLI
(Stage 2–3, evidence — в [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).
## Что такое 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](src/mcp_project_helper/server.py#L37-L58); каждая
тонкая обёртка, обращённая к 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](src/mcp_project_helper/tools/search_project_files.py#L41-L130).
### `read_project_file(path)`
Читает один текстовый файл по пути `path` (относительно корня проекта).
Отклоняет директории, несуществующие файлы и бинарный контент (байт NUL или
невалидный UTF-8). Содержимое ограничено значением
`config.READ_MAX_FILE_BYTES` (200 КБ) — файлы большего размера возвращаются
обрезанными, а не отклоняются.
Реализация: [tools/read_project_file.py:25-69](src/mcp_project_helper/tools/read_project_file.py#L25-L69).
### `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](src/mcp_project_helper/tools/get_docs.py#L58-L114).
### `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](src/mcp_project_helper/tools/run_project_check.py#L32-L90).
## Whitelist
```python
ALLOWED_CHECKS = {
"tests": [sys.executable, "-m", "pytest", "-q"],
}
```
Определено в [config.py:55-57](src/mcp_project_helper/config.py#L55-L57).
`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`**
```jsonc
{
"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`**
```jsonc
{
"status": "success",
"file": "demo_app/models.py",
"content": "...",
"size": 397,
"truncated": false
}
```
**`get_docs`**
```jsonc
{
"status": "success",
"query": "whitelist",
"results": [
{"file": "architecture.md", "heading": "Whitelist", "snippet": "..."}
],
"count": 1,
"truncated": false
}
```
**`run_project_check`**
```jsonc
{
"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](src/mcp_project_helper/security.py#L19-L50)) отклоняет
абсолютные пути, обход через `..` (на любую глубину), байты 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](src/mcp_project_helper/config.py#L55-L57)) *до* того,
как что-либо будет запущено; неизвестные имена отклоняются немедленно, а
сама проверка выполняется через
`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](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](src/mcp_project_helper/logging_setup.py#L20-L42).
Для отладки:
- Уровень логирования регулируется `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.
## Установка
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```
Единственная runtime-зависимость — пакет `mcp`; `pytest` — зависимость
только для разработки/тестов (обе зафиксированы в `pyproject.toml`).
## Настройка окружения
Конфигурация задаётся через переменные окружения — скопируйте
[`.env.example`](.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):
```bash
python -m mcp_project_helper.server
```
Запуск набора тестов:
```bash
pytest -q
```
Запуск собственных тестов demo-проекта напрямую (то, что по умолчанию
запускает `run_project_check("tests")`, так как `MCP_PROJECT_HELPER_ROOT` по
умолчанию указывает на `demo_project`):
```bash
cd demo_project && pytest -q
```
## Интеграция с Claude Code
Этот репозиторий содержит **project-scoped** файл
[`.mcp.json`](.mcp.json) в корне репозитория — конфигурация именно для
Claude Code (в отличие от [`.vscode/mcp.json`](.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`](.mcp.json):
```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`](.vscode/mcp.json) —
**отдельную** конфигурацию рабочей области для встроенного MCP-хоста VS
Code (используемого агентным режимом GitHub Copilot Chat):
```json
{
"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](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](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/tool-calls.log). Полная таблица —
[evidence/README.md](evidence/README.md); подробный разбор с ссылками на
код и логи — [REPORT.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 (закоммичен)
```