Skip to main content
Glama
oleg494

Notion Local MCP Easy

by oleg494
README.md
# Local MCP Easy

[![CI](https://github.com/oleg494/local-mcp-easy/actions/workflows/ci.yml/badge.svg)](https://github.com/oleg494/local-mcp-easy/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/oleg494/local-mcp-easy?sort=semver)](https://github.com/oleg494/local-mcp-easy/releases/latest)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
![Python](https://img.shields.io/badge/python-3.10--3.13-blue.svg)
![Tests](https://img.shields.io/badge/tests-449%20passing-brightgreen.svg)

Локальные файловые инструменты, команды и Git через MCP — по Streamable HTTP с OAuth 2.1.

One-click MCP-сервер для Windows: агент получает безопасные инструменты для чтения, поиска и изменения файлов в выбранной рабочей папке. Отдельно включается доверенный developer-режим (Python, Git, Node) с защитным git setup-flow. Стабильный публичный адрес — через зарезервированный Serveo hostname, собственный домен за обратным прокси или self-hosted sish-туннель (см. `SISH_SETUP.md` и `REVERSE_PROXY.md`). На Linux/macOS вместо `.bat` используйте `.sh`-обёртки (`./setup.sh`, `./start.sh`, …).

Совместим с Hyperagent, Notion и другими MCP-клиентами.

## Режимы авторизации

- **dual** — Bearer token и OAuth 2.1 одновременно на одном `/mcp` (**по умолчанию** с 2.1.0);
- **oauth** — только OAuth 2.1 с Dynamic Client Registration и PKCE (`S256`) для любых OAuth MCP-клиентов;
- **legacy** — только статический Bearer token (классическое поведение 1.x).

```text
Клиент со статическим токеном ── Bearer ─┐
                                         ├─ /mcp → общий набор MCP-инструментов
OAuth-клиент ────────── OAuth 2.1 ───────┘
```

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

## Запуск за несколько минут

1. Распакуйте архив в любую папку.
2. Дважды кликните `START.bat`.
3. При первом запуске выберите рабочую папку.
4. Оставьте **trusted developer mode выключенным**, если нужны только файловые инструменты. Для написания и запуска кода его можно включить ответом `y`.
5. Подключите клиента: для статического токена (например, Notion Custom MCP) скопируйте показанные `URL` и `Bearer token`; для OAuth-клиента (например, Hyperagent) запустите `OAUTH_SETUP.bat`, выберите `dual` или `oauth` и добавьте URL сервера в клиент — дальше DCR и страница `/consent` с owner-кодом.
6. Не закрывайте окно запуска во время работы.

При последующих запусках повторная настройка не требуется. Конфигурация хранится в `%LOCALAPPDATA%\LocalMcpEasy` и не входит в архив проекта (настройки из старого каталога `NotionMcpEasy` переносятся автоматически при первом запуске 2.0). Начиная с 1.4.2 список быстрых переключений между рабочими областями хранится в `%LOCALAPPDATA%\LocalMcpEasy\connections.cfg`: при `MENU = on` сервер показывает сохранённые пути и меняет только текущий `workspace` в `config.json`, не пересоздавая токен. При stable Serveo hostname адрес MCP сохраняется; в temporary mode после перезапуска URL меняется и его нужно обновить в клиенте.

## Управление

- `START.bat` — создать локальное `.venv`, установить зависимости и запустить сервер с туннелем. Если `connections.cfg` содержит `MENU = on`, перед стартом появится меню сохранённых рабочих областей.
- `STOP.bat` — остановить только процессы этого MCP после проверки их идентичности.
- `SETUP.bat` — заново пройти мастер настройки; токен при повторном setup сохраняется, а выбранная рабочая область попадает в `connections.cfg`.
- `SHOW_CONNECTION.bat` — показать текущие URL, workspace и режимы; токен и OAuth owner code маскируются, `--full` показывает их полностью.
- `OAUTH_SETUP.bat` — выбрать режим авторизации `legacy / oauth / dual` и сгенерировать OAuth owner code.
- `REGISTER_OAUTH_CLIENT.bat` — заранее зарегистрировать OAuth-клиент для режима «Bring my own OAuth app».
- `DOCTOR.bat` / `./doctor.sh` (2.4.0) — диагностика сломанной установки одной командой: версия Python, зависимости, config, токен, workspace, режим авторизации, owner code, tunnel backend, наличие ssh и git, занят ли порт. Возвращает ненулевой код, если что-то сломано.
- `launcher.py --add-command ИМЯ` / `--remove-command ИМЯ` — безопасно изменить список разрешённых команд без ручной правки JSON (ручная правка с BOM/лишней запятой раньше сбрасывала настройку — теперь launcher останавливается с понятной ошибкой и ничего не перезаписывает).

## connections.cfg

Пользовательский файл `%LOCALAPPDATA%\LocalMcpEasy\connections.cfg` создаётся автоматически и содержит:

- `MENU = on/off` — показывать ли меню выбора рабочей области при старте;
- `PATH[1] ... PATH[9]` — стартовые слоты для сохранённых путей;
- дополнительные слоты `PATH[10]`, `PATH[11]` и дальше можно добавлять вручную или через меню, если базовые места заняты.

Когда меню включено, запуск показывает только занятые слоты и предлагает:

- выбрать сохранённую рабочую область по номеру;
- нажать `0`, чтобы задать новую папку и сохранить её в свободный слот;
- нажать `q`, чтобы отключить меню и оставить последнюю выбранную область в `config.json`.

Все подсказки во время запуска сообщают точные пути к `connections.cfg` и `config.json`. Файл `connections.example.cfg` в архиве служит только шаблоном и не содержит пользовательских путей.

## Режимы

### File-only mode — по умолчанию

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

### Trusted developer mode — опционально

Добавляет запуск разрешённых программ без `cmd.exe` и PowerShell:

```text
python, py, pip, git, node, npm, npx, pytest, ruff, make, uv
```

Это **не песочница**. Python, Node, Git hooks, npm scripts и другие инструменты могут обращаться ко всей системе и сети с правами текущего пользователя Windows. Включайте режим только для личного доверенного агента.

С 2.4.0 аргументы к `.cmd`/`.bat`-программам (на Windows это `npm` и `npx`) отклоняются, если содержат метасимволы `& | < > ^ " % !`: Windows запускает batch-файлы через `cmd.exe`, который заново разбирает командную строку, поэтому `npm` с аргументом `--version&whoami` выполнял `whoami` в обход allow-list — при `shell=False` и argv списком (BatBadBut, тот же класс, что CVE-2024-24576).

### Read-only mode — опционально (2.4.0)

`MCP_READ_ONLY=1` вообще не регистрирует изменяющие инструменты: их нет в `list_tools()`, и до них не может дойти ни один запрос. Гарантия структурная, а не «проверка scope сработает». Подходит, чтобы дать агенту посмотреть рабочую папку через туннель, ничего не рискуя.

### Аудит вызовов (2.4.0)

`MCP_AUDIT_LOG=<путь>` пишет по одной JSON-строке на каждый вызов инструмента — время, OAuth `client_id`, имя инструмента, именованные аргументы (обрезанные) и результат `ok`/`error`. Сервер, доступный из интернета и умеющий запускать команды, обязан оставлять след; больше это нигде не фиксировалось. Секреты в лог не попадают, а сбой записи никогда не ломает сам вызов.

```json
{"ts":"2026-07-26T14:02:11+00:00","client":"mcp_client_a1b2","tool":"run_command","outcome":"ok","args":{"args":"['test']","cwd":".","program":"pytest"}}
```

## Скилы и память (2.5.0)

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

Обе подсистемы едут в клиент через поле `instructions` MCP-ответа на `initialize` —
единственный штатный канал сервера в системный промпт модели. Всё остальное клиенту
пришлось бы вытягивать вызовом инструмента, о котором он должен догадаться.

### Скилы

Скил — папка с `SKILL.md`: YAML-frontmatter плюс markdown-инструкции. Формат — переносимое
ядро спецификации [agentskills.io](https://agentskills.io/specification) (`name`,
`description`, `license`, `compatibility`, `metadata`), без расширений, специфичных для
конкретного клиента, так что тот же скил читается и в Claude Code.

```
%LOCALAPPDATA%\LocalMcpEasy\skills\<имя>\SKILL.md   ← личные, доступны в любой рабочей папке
<workspace>\.mcp-skills\<имя>\SKILL.md              ← скилы проекта, только здесь
```

```markdown
---
name: release-notes
description: >
  Собирает changelog из git diff, группируя по типу изменения.
  Использовать, когда просят release notes или саммари PR для пользователей.
license: MIT
---

# Release notes

1. `git diff <base>..<head>`
2. Разложить на Added / Changed / Fixed / Removed.
3. Один пункт на логическое изменение, языком пользователя.

Полная спецификация — в [references/format.md](references/format.md).
```

Раскрытие трёхуровневое, чтобы большая библиотека не съедала контекст:

1. **Всегда** — только `имя: описание`, одна строка на скил, в `instructions` и в `skills_list()`.
2. **По требованию** — тело `SKILL.md`, через `skill_view(name)`.
3. **Отдельно** — `references/`, `scripts/`, `assets/` не инлайнятся даже на втором уровне:
   в конце тела появляется их список и подсказка `skill_view(name, file_path=...)`.

**Скил — это инструкции, которым модель следует, то есть граница доверия, а не данные.**
Отсюда два следствия, оба проверяются тестами: личный скил всегда побеждает одноимённый
скил из рабочей папки (рабочую папку может писать любой клиент с `mcp:files:write`, так что
обратный приоритет был бы повышением привилегий), а скил из рабочей папки помечается
`[workspace]` везде, где отображается. Скил ничего не исполняет: он может *попросить*
модель что-то запустить, и это пойдёт через `run_command` с его allow-list, как любая
другая команда.

### Память

Два плоских файла в `%LOCALAPPDATA%\LocalMcpEasy\memory\`: `MEMORY.md` — что агент узнал про
окружение и работу, `USER.md` — что узнал про человека. Записи разделены строкой `§`. Ни
базы, ни эмбеддингов: recall здесь означает «весь стор целиком показан модели», так что
ранжировать нечего — индекс добавил бы зависимость, шаг сборки и точку отказа ради запроса,
которого никогда не будет.

Ограничение — **в символах**, не в записях (2200 для `MEMORY.md`, 1375 для `USER.md`). Лимит
на количество позволил бы одной пространной записи вытеснить десять полезных, а символьный
управляет тем, что реально дефицитно, — местом в контексте. Запись сверх бюджета
**отклоняется** и возвращает текущее содержимое, чтобы модель сократила и повторила; молча
обрезать нельзя — так теряются записи.

Стор лежит вне рабочей папки, поэтому до него не дотягивается ни один файловый инструмент:
изменить память можно только через `memory_write`, а значит каждое изменение проходит
проверку scope.

| Инструмент | Scope | Что делает |
| --- | --- | --- |
| `skills_list()` | `mcp:files:read` | индекс скилов |
| `skill_view(name, file_path="")` | `mcp:files:read` | тело скила или его вспомогательный файл |
| `memory_read(target="all")` | `mcp:files:read` | текущая память с указанием бюджета |
| `memory_write(action, content, target, old_text)` | `mcp:files:write` | `add` / `replace` / `remove` |

Новых OAuth-scope не появилось намеренно: уже выданные токены продолжают работать, а в
`MCP_READ_ONLY=1` инструмент `memory_write` просто не регистрируется.

### Переменные окружения (выборочно)

| Переменная | По умолчанию | Назначение |
| --- | --- | --- |
| `MCP_READ_ONLY` | `0` | `1` — изменяющие инструменты не регистрируются вовсе |
| `MCP_AUDIT_LOG` | (пусто) | путь к append-only JSONL-журналу вызовов |
| `MCP_SKILLS_DIR` | `<config>\skills` | папка личных скилов |
| `MCP_MEMORY_DIR` | `<config>\memory` | папка стора памяти |
| `MCP_MEMORY` | `1` | `0` — выключить память целиком |
| `MCP_MEMORY_CHAR_LIMIT` | `2200` | бюджет `MEMORY.md` в символах |
| `MCP_USER_CHAR_LIMIT` | `1375` | бюджет `USER.md` в символах |
| `MCP_ALLOW_COMMANDS` | `0` | `1` — включить trusted developer mode |
| `MCP_ALLOWED_COMMANDS` | см. список выше | allow-list программ через запятую |
| `MCP_MAX_COMMAND_JOBS` | `4` | сколько фоновых команд может идти параллельно |
| `MCP_MAX_COPY_MOVE_BYTES` | `100 МБ` | потолок для `copy_file` / `move_file` |
| `MCP_TEMP_FILE_TTL` | `86400` | сколько живут временные файлы `@temp/` | Git через MCP теперь проходит через отдельный setup-flow: без local repo context (`agent-repo-config.local.json`) обычные git-команды блокируются, а агент должен сначала либо привязать существующий репозиторий, либо инициализировать новый, либо явно отключить git для этой папки. Если сервер собирается принять значения по умолчанию или изменить уже сохранённую git-привязку, агент обязан запросить явное подтверждение пользователя.

## Архитектура

```text
Notion ─────── static Bearer ─┐
                              ├─ /mcp → общий набор MCP-инструментов
Hyperagent ── OAuth 2.1 ──────┘
    -> HTTPS (Serveo SSH reverse tunnel)
    -> 127.0.0.1:8765
FastMCP server
    -> выбранный workspace
```

Сервер слушает только localhost. В режиме `legacy` все HTTP-маршруты, включая `/health`, требуют токен — поведение линии 1.x без изменений. В режимах `oauth`/`dual` endpoint `/mcp` защищён проверкой токена per-request (legacy и/или OAuth), discovery-маршруты OAuth публичны по спецификации, а `/health` принимает операторский токен запуска. FastMCP Host-проверка отключена намеренно: Serveo выдаёт случайное публичное имя, которое иначе приводило бы к HTTP 421; вместо неё работает собственный Host-allowlist.

Serveo — сторонний туннель. В быстром анонимном режиме URL меняется после перезапуска. Если в Serveo зарезервировать hostname и добавить SSH-ключ, мастер включает stable mode: адрес вида `https://my-name.serveousercontent.com/mcp` сохраняется после перезапусков и добавляется в клиент один раз. Для OAuth-режимов обязателен стабильный адрес: зарезервированный hostname или собственный домен через `public_url` (тогда Serveo не используется — маршрутизацию делает ваш reverse proxy).

## Universal OAuth (Hyperagent и другие MCP-клиенты)

### Режимы авторизации

```text
dual    — Bearer token И OAuth одновременно на одном /mcp (по умолчанию с 2.1.0)
oauth   — только OAuth 2.1 (Hyperagent); Bearer-токен работает лишь на /health
legacy  — только статический Bearer token (например Notion, как в линии 1.x)
```

Режим выбирается через `OAUTH_SETUP.bat` и хранится в `config.json`. **Новые установки по умолчанию используют `dual`**, и `SETUP.bat` сам генерирует и печатает OAuth owner code — открытая DCR и подтверждение на `/consent` работают «из коробки» (регистрация сама по себе ничего не даёт: каждую авторизацию нужно одобрить owner-кодом). Апгрейд не меняет существующий конфиг: конфиг без `auth_mode` остаётся `legacy`. Набор MCP-инструментов, границы workspace, chunking и git-политика общие для всех режимов — меняется только слой авторизации.

### Быстрое подключение Hyperagent

1. В `SETUP.bat` настройте **зарезервированный Serveo hostname** (обязателен для чистого `oauth`; для `dual` — крайне желателен: без него OAuth-часть нестабильна, но сервер стартует с предупреждением).
2. Запустите `OAUTH_SETUP.bat`, выберите `dual` (статический токен и OAuth одновременно) или `oauth`. Мастер сгенерирует **OAuth owner code** — код владельца для подтверждения подключений.
3. Запустите `START.bat`.
4. В Hyperagent: `Add MCP server` → Streamable HTTP → URL `https://<hostname>.serveousercontent.com/mcp`. Поле `Advanced` заполнять не нужно — сервер публикует discovery metadata.
5. Если `Bring my own OAuth app` **выключен**, Hyperagent зарегистрируется сам через Dynamic Client Registration и откроет страницу подтверждения `/consent`.
6. На странице `/consent` проверьте имя клиента и запрошенные права, введите OAuth owner code (показывается в окне запуска и через `SHOW_CONNECTION.bat --full`) и нажмите **Approve**.
7. Если `Bring my own OAuth app` **включен**, сначала выполните `REGISTER_OAUTH_CLIENT.bat`: введите redirect URL из Hyperagent, получите `client_id` (для public PKCE-клиента secret не нужен) и внесите значения в Hyperagent. Дальше тот же `/consent`-флоу.

> **Интерстициал Serveo (бесплатный аккаунт).** При **первом** заходе в браузере Serveo показывает одноразовую страницу «you are about to visit…» и при этом теряет query-параметры у ссылки `/authorize`. Если вместо `/consent` вы увидели ошибку вида `client_id: Field required` — это не сбой сервера: нажмите в браузере **«Назад»** и откройте ссылку авторизации ещё раз (или повторите *Connect* в клиенте) — предупреждение уже снято на эту сессию браузера, и откроется страница `/consent`. Сервер с 2.1.0 показывает на этот случай понятную страницу-подсказку вместо сырого JSON. Чтобы интерстициал не появлялся вовсе — используйте свой домен (`public_url`) или платный аккаунт Serveo с зарезервированным hostname.

### Что реализовано

- OAuth 2.1 Authorization Code Flow + PKCE (`S256`, единственный поддерживаемый метод);
- Dynamic Client Registration (`POST /register`) и заранее зарегистрированные клиенты;
- строгая проверка `redirect_uri` (https или локальный loopback) и передача `state`;
- короткоживущие access-токены (1 час по умолчанию) с audience-привязкой к `/mcp` (RFC 8707);
- refresh-токены с ротацией: старый refresh и связанные access-токены гаснут при каждом обновлении;
- одноразовые authorization codes: повторное использование кода отзывает выданные по нему токены;
- `POST /revoke` для отзыва токенов;
- discovery: `/.well-known/oauth-authorization-server` (RFC 8414), `/.well-known/oauth-protected-resource/mcp` (RFC 9728, плюс root-алиас) и `WWW-Authenticate` с `resource_metadata` при 401.

### Scopes

| Scope | Инструменты |
| --- | --- |
| `mcp:files:read` | `workspace_info`, `list_dir`, `file_info`, `read_file`, `tail_file`, `glob_files`, `grep_files`, `skills_list`, `skill_view`, `memory_read` |
| `mcp:files:write` | `write_file`, `append_file`, `edit_file`, `create_dir`, `delete_file`, `copy_file`, `move_file`, `memory_write` |
| `mcp:commands:run` | `run_command`, `start_command`, `get_command_status`, `cancel_command`, `list_commands` (дополнительно требуется trusted developer mode) |
| `mcp:git` | `repo_context_status`, `inspect_git_repository`, `configure_repo_context`, `setup_git_context` |

Проверка scope выполняется перед каждым вызовом инструмента (deny-by-default: инструмент без известного scope не регистрируется). Токен только с `mcp:files:read` не может изменять файлы, запускать команды или трогать git.

**Least-privilege по умолчанию.** Клиент, который регистрируется без запроса конкретных scopes (в т.ч. через DCR без поля `scope`), получает только `mcp:files:read` + `mcp:files:write`. Мощные scopes нужно запрашивать явно.

**⚠️ `mcp:commands:run` — это доступ уровня «почти вся система», а не workspace-scoped право.** В trusted developer mode `run_command` запускает Python/Git/Node с правами пользователя ОС, и эти программы могут читать и менять файлы и ходить в сеть за пределами workspace, фактически обходя ограничения `mcp:files:read/write/git`. Выдавайте этот scope только полностью доверенному клиенту.

**Легаси Bearer-токен остаётся мастер-токеном с полным доступом** — это осознанное решение для личного сервера, учитывайте его при передаче токена.

Ограниченный (или, наоборот, расширенный) клиент создаётся через `REGISTER_OAUTH_CLIENT.bat` (укажите нужный поднабор scopes) или когда клиент сам запрашивает конкретный `scope` при регистрации/авторизации.

### Стабильный URL для OAuth

При смене публичного URL меняются issuer, discovery-ссылки, redirect-конфигурация и audience уже выданных токенов, поэтому OAuth-часть требует стабильного адреса. С 2.1.0 политика такая: чистый `oauth` на нестабильном URL launcher **блокирует** (иначе рабочего способа авторизации не останется), а `dual` **стартует с предупреждением** — Bearer-токен работает сразу, а OAuth-часть станет стабильной, когда появится постоянный адрес. Разрешённые варианты дать стабильный URL:

- **зарезервированный Serveo hostname** — launcher сам поднимает стабильный туннель;
- **свой стабильный домен / reverse proxy** — задайте его в `OAUTH_SETUP.bat` (сохраняется как `public_url`). В этом режиме launcher НЕ поднимает Serveo: вы сами маршрутизируете `https://ваш-домен/mcp` на `http://127.0.0.1:<port>` своим прокси/туннелем. `START.bat` в этом случае просто запускает сервер и публикует ваш URL.

Для локальных экспериментов существует переменная `MCP_OAUTH_ALLOW_TEMPORARY_URL=1` — с ней сервер работает на `http://127.0.0.1:<port>` без туннеля.

### Защита от злоупотреблений

- **Consent без DoS на владельца.** Правильный owner code принимается всегда, поэтому неверные попытки не могут «залочить» настоящего владельца. Неверные попытки ограничиваются per-transaction (после нескольких — транзакция сгорает, клиент начинает заново) и общим самозаживающим rolling-window rate limit, без глухой блокировки всей страницы.
- **DCR не переполняет диск.** Реестр клиентов ограничен (`MCP_OAUTH_MAX_CLIENTS`, по умолчанию 100); зарегистрированные, но не завершившие авторизацию DCR-клиенты удаляются через `MCP_OAUTH_UNUSED_CLIENT_TTL` (по умолчанию 1 час); клиенты с живыми токенами и вручную зарегистрированные (BYO) не вытесняются.

### Хранение OAuth-состояния

Файл `%LOCALAPPDATA%\LocalMcpEasy\oauth_state.json` содержит зарегистрированных клиентов и **SHA-256 хеши** access/refresh-токенов — сырые значения токенов на диск не пишутся. Файл не входит ни в git, ни в release-архив. Битый или отредактированный вручную файл (null-секции, мусор, неизвестная будущая версия схемы) не роняет запуск — сервер стартует с чистым состоянием. Благодаря этому файлу клиенты и refresh-токены переживают перезапуск сервера: при stable hostname Hyperagent переподключается без повторного подтверждения.

Переменные тонкой настройки: `MCP_OAUTH_ACCESS_TTL` (сек, по умолчанию 3600), `MCP_OAUTH_REFRESH_TTL` (по умолчанию 30 дней), `MCP_OAUTH_MAX_CLIENTS` (100), `MCP_OAUTH_UNUSED_CLIENT_TTL` (3600), `MCP_OAUTH_CONSENT_MAX_ATTEMPTS` (на транзакцию, 5), `MCP_OAUTH_CONSENT_FAILURE_WINDOW_SECONDS` (60), `MCP_OAUTH_CONSENT_MAX_FAILURES` (10), `MCP_OAUTH_OWNER_GRANT_SCOPES` (single-owner override, по умолчанию пуст), `MCP_OAUTH_MAX_CLIENTS` (100), `MCP_OAUTH_UNUSED_CLIENT_TTL` (3600).

## Инструменты

- `workspace_info` — workspace, активный режим, root repo и краткий обзор nested repo;
- `repo_context_status`, `inspect_git_repository` — диагностика git и следующего безопасного шага;
- `setup_git_context`, `configure_repo_context` — инициализация, привязка, перепривязка или отключение git для конкретной папки с обязательным выбором branch policy;
- `list_dir`, `file_info`, `read_file`, `tail_file`;
- `write_file`, `append_file`, `edit_file`;
- `create_dir`, безопасное нерекурсивное `delete_file`;
- `copy_file`, `move_file` — только отдельные файлы;
- `glob_files`, ограниченный текстовый `grep_files`;
- `skills_list`, `skill_view` — библиотека многоразовых процедур (см. «Скилы и память»);
- `memory_read`, `memory_write` — память, переживающая сессию;
- `run_command` — только в trusted developer mode.

`read_file()` теперь читает длинные файлы частями: показывает диапазон строк, общее число строк и `next offset` для продолжения. Если `run_command()`, `grep_files()` или `list_dir()` возвращают слишком большой результат, MCP сохраняет полный вывод во временный файл и отдаёт первую безопасную часть с путём вида `@temp/...` для продолжения через `read_file()`.

`tail_file(path, lines)` (2.4.0) отдаёт последние N строк — для логов и длинных транскриптов, где `read_file()` пришлось бы каждый раз пересчитывать offset от начала файла.

Regex-поиск отключён, чтобы исключить зависание на патологических выражениях. Обычный регистронезависимый поиск остаётся доступен: клиентский regex — это готовый DoS-примитив (катастрофический бэктрекинг) против сервера без бюджета на запрос.

### Аннотации инструментов (2.4.0)

Все инструменты объявляют MCP-аннотации `readOnlyHint` / `destructiveHint` / `idempotentHint` / `openWorldHint`, поэтому клиент видит, что можно выполнять без подтверждения, а что требует спросить пользователя. `readOnlyHint` выводится из scope инструмента, а не задаётся вручную, — так он не может разойтись с реальностью.

## Ограничения

- текстовый файл для чтения и итогового append/edit: до 5 МБ;
- один write/append: до 2 МБ;
- `read_file()` по умолчанию выдаёт до 400 строк, но в первую очередь ограничивается безопасным бюджетом около 9 500 символов, сохраняя целые строки;
- небольшие результаты команд отдаются напрямую, а большие автоматически сохраняются во временный файл и продолжаются через `read_file()`;
- `git` через MCP запрещён, пока не завершён local setup-flow: при отсутствии `.git` агент должен спросить пользователя, создаём новый репозиторий, подключаемся к существующему или временно отключаем git;
- при настройке repo context пользователь теперь должен явно выбрать branch policy: коммит в ветку по умолчанию (`default_branch`) или в явно заданную ветку (`commit_branch`);
- после настройки MCP сверяет `remote.origin.url` с сохранённой локальной привязкой и блокирует git при несовпадении, а commit/push/merge/rebase блокирует вне выбранной ветки;
- если настройка git уже сохранена, её нельзя молча менять: для default-значений и для перепривязки требуются отдельные явные подтверждения пользователя;
- обычные mutating git-команды вроде `reset`, `checkout -B`, `tag`, `config` и `remote set-url` теперь дополнительно фильтруются политикой MCP и не должны обходить setup-flow.
- временные MCP-файлы используют путь вида `@temp/...`, лежат в `temp/` рядом с `server.py`, удаляются после финального чтения и дополнительно очищаются при старте;
- timeout команды по-прежнему останавливает дерево процесса;
- рекурсивное удаление и перемещение каталогов через MCP отсутствуют;
- `node_modules`, `.venv`, `.git` и кэши пропускаются при рекурсивном просмотре.

## Длинные операции и таймаут туннеля

`run_command` поддерживает `timeout` до 300 с, и сервер это уважает, но у связки **Streamable HTTP + Serveo** есть практический потолок: одиночный синхронный POST длиннее ~20–30 с туннель нередко обрывает (`Streamable HTTP error: Error POSTing to endpoint`). Это свойство рекомендуемого туннеля, а не логики сервера. Что делать с тяжёлыми задачами (сборка, `pip install`, полный прогон тестов):

- **Фоновые команды (рекомендуется, с 2.2.0):** `start_command(program, args, cwd, timeout)` запускает allow-list-программу в фоне и сразу возвращает `job_id`; `get_command_status(job_id)` отдаёт статус, а после завершения — полный вывод в формате `run_command`; `cancel_command(job_id)` убивает дерево процессов; `list_commands()` показывает отслеживаемые задачи. Ограничения те же (allow-list, проверка `cwd`, git-context guard); число параллельных задач задаётся `MCP_MAX_COMMAND_JOBS` (по умолчанию 4), завершённые задачи и их файлы вывода подчищаются автоматически (хранение ~10 минут). Так сборку, `pip install` или полный прогон тестов можно пережить дольше таймаута туннеля и гонять команды параллельно.
- **Демонизировать вручную и опрашивать:** запустить процесс в фоне и писать результат в файл (например, `> out.txt 2>&1` и отдельный sentinel-файл о завершении), затем читать файл через `read_file()`. Короткие интерактивные вызовы идут напрямую и стабильно.
- **Свой reverse proxy вместо Serveo:** задать стабильный домен через `public_url` (тогда Serveo не используется) и настроить keep-alive/таймауты на своей стороне — так потолок длительности снимается. Конфиги для nginx/Caddy/Traefik — в [REVERSE_PROXY.md](REVERSE_PROXY.md).

## Требования

- Windows 10/11;
- Python 3.11+ с опцией `Add Python to PATH`;
- встроенный OpenSSH Client (`ssh.exe`);
- интернет при первой установке и для Serveo.

## Проверка

```bat
.venv\Scripts\python -m unittest discover -s tests -v
.venv\Scripts\ruff check .
```

Набор из 370 тестов покрывает path traversal, allowlist, занятый порт, PID-проверку, правильный и неправильный токены, Serveo Host без HTTP 421, chunked-выдачу, repo bootstrap / disable / mismatch guard и timeout процесса. С 2.4.0 добавлены: guard на метасимволы cmd.exe для .cmd/.bat-программ (с живой проверкой на Windows, что cmd действительно переразбирает командную строку), побеги через Windows junction, резолвер виртуальных путей `@temp/`, владение фоновыми задачами по OAuth-клиенту, read-only mode, аудит-лог, редакция секретов, аннотации инструментов, `--doctor`, защита argv для ssh и default-deny для неклассифицированных git-подкоманд. OAuth-набор дополнительно проверяет discovery, DCR, PKCE (включая неверный verifier), state, consent с owner-кодом и троттлингом (правильный код всегда проходит), scope-ограничения per tool, лимиты клиентского реестра, ротацию refresh-токенов, replay authorization code, отзыв токенов, dual-режим (Bearer + X-API-Key + OAuth параллельно), переживание перезапуска сервера выданными токенами, а также устойчивость config.json и oauth_state.json к повреждению.

## Что улучшено относительно оригинала

- автоматический setup без ручного редактирования BAT-файлов;
- токен и runtime вне проекта;
- localhost-only bind и обязательная Bearer-авторизация;
- правильная проверка границ workspace;
- файловый режим безопаснее и включён по умолчанию;
- команды вынесены в явно доверенный режим;
- нет shell, фоновых команд, HTTP downloader и чтения env через MCP;
- автоматический перевод больших результатов в temp-файлы с продолжением через `read_file()` вместо попытки отправить всё модели одним ответом;
- обязательный setup-flow для git: bind existing / init new / attach existing remote / disable git with persisted local policy;
- локальная repo-привязка для Git с проверкой `origin` после перезапуска MCP;
- проверка занятого порта до создания туннеля;
- проверка идентичности PID перед остановкой;
- фиксированные зависимости, тесты, changelog и security model.

Подробная модель безопасности: `SECURITY.md`. История версий: `CHANGELOG.md`.

## Git setup-flow для агента

Если обычная git-команда вызывается впервые для этой папки, MCP больше не пытается угадывать репозиторий. Вместо этого агент должен сначала вызвать `repo_context_status()` и, при необходимости, предложить пользователю выбор:

1. `setup_git_context(mode="init_new_repo", repository_url="...", fork_status="fork|not_fork", branch_mode="default_branch|specified_branch", default_branch="main", commit_branch="stablefix")`
2. `setup_git_context(mode="attach_to_remote", repository_url="...", fork_status="fork|not_fork", branch_mode="default_branch|specified_branch", default_branch="main", commit_branch="stablefix")`
3. `setup_git_context(mode="bind_existing_repo", repository_url="...", fork_status="fork|not_fork", branch_mode="default_branch|specified_branch", default_branch="main", commit_branch="stablefix")`
4. `setup_git_context(mode="disable_git")`

Это состояние сохраняется в `agent-repo-config.local.json` в корне workspace и переживает перезапуск MCP. Вместе с repo URL там хранится branch policy: либо коммиты разрешены только в ветку по умолчанию, либо только в явно заданную ветку. Файл intentionally local-only: он исключён из Git и release-архивов.

## Сборка архива для отправки

Запустите `BUILD_RELEASE.bat`. Архив `local-mcp-easy-<версия>.zip` появится в папке `release/` внутри проекта. Эта папка создаётся автоматически, исключена из Git и не попадает в сам release-архив. Сборщик автоматически исключает `.venv`, кэши, логи, ZIP-файлы, временную папку `temp/`, папку `release/`, локальные repo-файлы и файлы конфигурации/токенов.

## Полный гайд Serveo

Подробная инструкция по временному и постоянному URL, созданию аккаунта, SSH-ключа, резервированию hostname, настройке Notion и устранению ошибок находится в `SERVEO_SETUP.md`. Если у вас свой домен и reverse proxy (вместо Serveo) — см. [REVERSE_PROXY.md](REVERSE_PROXY.md).

## Совместимые клиенты

- Hyperagent — OAuth 2.1, Streamable HTTP;
- Notion Custom MCP — статический Bearer token;
- другие Streamable HTTP MCP-клиенты — OAuth 2.1 или статический токен.

## История проекта

Local MCP Easy вырос из проекта `notion-local-mcp-easy`.

Universal-версия включает работу из трёх источников:

- оригинальный проект `notion-local-mcp-easy` (GitHub: oleg494);
- форк LEADBERG и его стабилизационные доработки;
- OAuth- и совместимостный слой, разработанный вместе с командой Opus/Fable.

Старая Notion-линия 1.x сохранена в ветке `legacy`.

## Лицензия

[MIT](LICENSE)

English overview: [README.en.md](README.en.md)