mcp-local-access
by BigTolly
README.md
**Languages:** [Русский](README.md) | [English](README-EN.md)
# MCP (Local Access)
MCP-сервер позволяет облачным сервисам с поддержкой MCP, например Notion AI.
Предоставлять доступ к **одной папке** на вашем компьютере: читать файлы,
искать по ним, править, создавать и удалять, а также запускать разрешённые команды.
MCP сервер — это «руки», облачный сервис — «мозг».
Вместо копирования кода в чат и обратно, MCP позволяет работать с файлами на ПК.
**Зачем это нужно**
- Чат с ИИ видит реальный проект, а не пересказ: точные файлы, точные строки.
- Правки применяются сразу в файлы, с диффом в ответе.
- Рамки задаёте вы: одна корневая папка, список запрещённых путей, белый список
команд, при желании — режим «только чтение».
**Статус.** Версия разработана для Windows и MacOS, но реально протестирована
только на Windows; ветки для macOS/Linux написаны, но не проверены.

---
## Содержание
1. [Как это работает](#1-как-это-работает)
2. [Структура проекта](#2-структура-проекта)
3. [Требования](#3-требования)
4. [Установка](#4-установка)
5. [Настройка](#5-настройка)
6. [Запуск и публикация](#6-запуск-и-публикация)
7. [Подключение клиента](#7-подключение-клиента)
8. [Настройка через JSON на примере accio.com](#8-настройка-через-json-на-примере-acciocom)
9. [Инструменты](#9-инструменты)
- [Диалоговое окно](#Диалоговое-окно)
- [Управление браузером (Browser Control)](#управление-браузером-browser-control)
10. [Rules.md — краткое описание вашего проекта](#10-rulesmd--краткое-описание-вашего-проекта)
11. [Права доступа](#11-права-доступа)
12. [Лимиты](#12-лимиты)
13. [Безопасность](#13-безопасность)
14. [Диагностика](#14-диагностика)
15. [FAQ](#15-faq)
---
## 1. Как это работает
```
Notion AI / claude.ai / chatgpt
│ HTTPS + Bearer-токен
▼
cloudflared-туннель или свой web сервер с TLS
│ HTTP внутри локальной сети
▼
server.py (слушает host:port, отдаёт /mcp)
│ все пути проверяются: песочница + права
▼
rootDir + явно разрешённые внешние папки Read/Write
```
Стек: Python + MCP SDK для Python (FastMCP) + uvicorn, транспорт Streamable HTTP
на пути `/mcp`, авторизация Bearer-токеном. Сервер ничего не знает о способе
публикации: он просто слушает `host:port`, а как этот порт оказался в интернете —
дело туннеля или прокси.
## 2. Структура проекта
| Путь | Что это |
| --- | --- |
| `server.py` | MCP-сервер: конфиг, песочница путей, права, регистрация инструментов, авторизация |
| `dialog-box.py` | диалог Ask me: интерфейс, звук и ожидание ответа пользователя |
| `config.json` | файл настроек, создайте его из `config.example.json` |
| `config.example.json` | шаблон настроек |
| `.env` | файл хранит токен `MCP_TOKEN`, который генерируется при первом запуске `server.py` |
| `requirements.txt` | четыре зависимости: `mcp`, `uvicorn`, `python-dotenv`, `websockets` |
| `README.md` | этот файл |
| `Rules.md` | базовые инструкции для ИИ, чтобы ИИ знал минимальный контекст по вашему проекту |
| `logs/` (внутри `rootDir`) | `cmd-NNN.log` — вывод команд, `audit.log` — журнал действий, `processes.json` — реестр процессов |
## 3. Требования
- Python 3.10 или новее (`python --version`).
- Способ отдать локальный порт наружу по HTTPS — одно из двух:
- **cloudflared**, если своего веб сервера нет:
- Windows: `winget install Cloudflare.cloudflared`
- macOS: `brew install cloudflared`
- **свой обратный прокси с TLS** (на базе Nginx, Caddy, Traefik) перед этим портом.
При первом запуске `server.py`, сам генерирует токен, установка для cloudflare
для этой задачи не нужна, он сам сам cloudflared.
## 4. Установка
```bash
cd local-access
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate
pip install -r requirements.txt
```
## 5. Настройка
Все настройки лежат в `config.json`. Скопируйте шаблон и поправьте:
```bash
# Windows
copy config.example.json config.json
# macOS / Linux
cp config.example.json config.json
```
```json
{
"rootDir": "C:/usr/mcp/local-access",
"host": "0.0.0.0",
"port": 8000,
"readOnly": false,
"jsonResponse": false,
"rulesFile": "Rules.md",
"logDir": "logs",
"audit": true,
"maxCommandTimeout": 600,
"permissions": { "...": "см. раздел 11" }
}
```
| Ключ | Значение |
| --- | --- |
| `rootDir` | Основная папка файловой песочницы. Всё вне неё закрыто, кроме явно подключённых абсолютными правилами `Read`/`Write` папок. Обязательный параметр. |
| `host` | Интерфейс для прослушивания. По умолчанию `0.0.0.0` — годится и для туннеля на этой машине, и для прокси на другом хосте. `127.0.0.1` — принимать только локальные подключения. |
| `port` | Локальный порт; туннель или прокси смотрит сюда. По умолчанию 8000. |
| `readOnly` | `true` отключает `edit_file`, `multi_edit`, `create`, `delete`, `move` и `run_command`. |
| `jsonResponse` | `true` нужен только клиенту, который не умеет SSE-ответы. |
| `rulesFile` | Путь к файлу-путеводителю для модели. Необязательный, см. раздел 10. |
| `logDir` | Папка внутри `rootDir` для логов команд и журнала действий. По умолчанию `logs`. |
| `audit` | `false` выключает `logs/audit.log`. По умолчанию `true`. |
| `maxCommandTimeout` | Верхняя граница `timeout` для `run_command`, в секундах. По умолчанию 600. |
| `permissions` | Правила по путям и командам, см. раздел 11. |
| `browser` | Мост к расширению Browser MCP, см. «Браузер (Browser MCP)» в разделе 9. Необязательный. |
### Несколько файлов настроек
По умолчанию читается `config.json`. Другой файл задаётся ключом `--config`
(короткая форма `-c`). Путь указывать не нужно: файл всегда ищется в папке, где
лежит `server.py`.
```bat
rem start.cmd
start "MCP Server" cmd /k python C:\usr\mcp\local-access\server.py --config new.json
```
- Порядок выбора: `--config`, затем переменная `CONFIG_FILE`, затем `config.json`.
- Файл, указанный через `--config`, обязан существовать — иначе сервер не
стартует с ошибкой `Config file not found`. Отсутствие `config.json` по
умолчанию, как и раньше, не ошибка.
- Активный файл настроек всегда закрыт для модели, как бы он ни назывался:
правило `*(**/config.json)` не покрыло бы `new.json`. Неактивные запасные
конфиги защищайте сами, например `"*(**/new.json)"` в `deny`.
- Имя активного файла сервер печатает при старте в строке `config`.
В `.env` — только секретный токен:
```bash
# Windows
copy .env.example .env
# macOS / Linux
cp .env.example .env
```
Оставьте `MCP_TOKEN` пустым: при первом запуске сервер сгенерирует токен и
запишет его в эту же строку. Чтобы сменить токен, очистите значение и
перезапустите сервер.
## 6. Запуск и публикация
Нужны два терминала.
**Терминал 1 — сервер:**
```bash
python server.py
```
Он печатает эндпоинт, токен и действующие правила:
```
local-mcp v0.1
root : C:\usr\mcp\my-project
endpoint : http://0.0.0.0:8000/mcp
token : 3f8c1d...
read-only : no
rules : C:\usr\mcp\local-mcp-v0.1\Rules.md
logs : C:\usr\mcp\my-project\logs (audit.log: last 300 lines)
processes : adopted 1, cleaned 4 old log(s)
config : C:\usr\mcp\local-mcp-v0.1\config.json
perms : defaultMode allow
allow : List(**), Read(**), Edit(**), Create(**), Delete(**)
deny : *(**/.env), *(**/.env.*), *(**/config.json), *(**/secrets/**)
run : Run(npm run dev), Run(npm run build), Run(git status)
browser : ws://localhost:9009 - press 'Connect' in the Browser MCP extension
WARNING: the server is listening on the whole local network.
For a tighter setup set "host": "127.0.0.1" in config.json.
```
### Вариант A — быстрый туннель cloudflared
**Терминал 2:**
```bash
cloudflared tunnel --url http://localhost:8000 --protocol http2
```
После запуска в терминале можно увидеть публичный адрес:
Requesting new quick Tunnel on trycloudflare.com...
+--------------------------------------------------------------------------------------------+
| Your quick Tunnel has been created! Visit it at (it may take some time to be reachable): |
| https://verbal-univ-silent-bracelet.trycloudflare.com |
+--------------------------------------------------------------------------------------------+
В данном случае это `https://verbal-univ-silent-bracelet.trycloudflare.com`.
Обратите внимание, что после каждого запуска url адрес меняется.
- `--protocol http2` заставляет туннель работать по TCP вместо QUIC/UDP. Без
этого флага соединение может отваливаться с `timeout: no recent network
activity`, а запросы — падать с ошибкой Cloudflare 1033.
- Туннель не нужно перезапускать вместе с сервером: он пробрасывает
`localhost:8000`, и пока порт тот же, публичный адрес продолжает работать.
Перезапуск нужен, только если вы поменяли `port` или туннель сам умер.
- Адрес быстрого туннеля меняется при каждом старте cloudflared — значит,
URL в клиенте придётся обновлять.
### Вариант B — использование веб сервер перед сервером (постоянный адрес)
Для этого варианта нужно иметь:
- статический внешний IP-адрес
- веб сервер с TLS, на котором можно настроить обратный прокси
- домен, при этом для mcp можно использовать домен третьего уровня
- получить сертификат для работы https
В `config.json`:
- `"host": "0.0.0.0"` — значение по умолчанию и то, что нужно прокси: на
`127.0.0.1` сервер доступен только с этой машины.
- `"port": 8000` — или любой свободный порт, на который смотрит прокси.
- После правки файла перезапустите `server.py` и проверьте строку `endpoint`.
На хосте с прокси нужен только `location /mcp` — публичный путь это дело прокси,
сервер всегда отдаёт `/mcp`.
Пример настройки обратного прокси на Nginx:
```nginx
server {
listen 443 ssl;
server_name mcp.example.com;
location /mcp {
proxy_pass http://192.168.1.50:8000/mcp; # машина, где запущен server.py
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_buffering off; # SSE должен стримиться, а не буферизоваться
proxy_request_buffering off;
proxy_read_timeout 3600s;
chunked_transfer_encoding on;
}
}
```
- Ключевые строки — `proxy_buffering off` и `proxy_request_buffering off`:
транспорт стримит SSE, и буферизующий прокси подвешивает каждый ответ до
таймаута.
- Заголовок `Authorization` проходит без изменений, токен доезжает как есть.
- Сервер сам переписывает заголовок `Host`, так что защита SDK от DNS-rebinding
не требует дополнительных заголовков (ошибки 421 не будет).
- TLS терминируется на прокси, до сервера идёт обычный HTTP внутри локальной сети.
Адрес после этого постоянный: `https://mcp.example.com/mcp` вводится в клиент
один раз и переживает перезапуски и сервера, и прокси.
## 7. Подключение клиента
**Notion** (Settings → Connections → добавить MCP-сервер):
- URL: `https://ваш-адрес/mcp` — суффикс `/mcp` обязателен
- Аутентификация: Bearer-токен, префикс `Bearer`, сам токен — из терминала 1
## 8. Настройка через JSON на примере accio.com
Некоторые сервисы позволяют добавить MCP-сервер не по полям формы, а одним
блоком JSON. Например, в accio.com: Settings → MCP → секция **Custom** → кнопка
**Add custom MCP** (или «+ Add») → диалог **Add Custom MCP Server**. В диалоге
есть четыре вкладки Configuration Mode: `JSON | Stdio (Local) | HTTP | SSE`.
Откройте вкладку **JSON** и вставьте этот текст:
```json
{
"mcpServers": {
"Local Access": {
"type": "http",
"url": "https://your-host.example.com/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}
```
Затем замените значения по умолчанию на свои:
- `url` — адрес вашего туннеля или домена, суффикс `/mcp` обязателен
- `Authorization` — префикс `Bearer` и ваш токен (`MCP_TOKEN` из `.env`, сервер
печатает его при запуске)
Ключ `Local Access` — это просто имя подключения в интерфейсе, его можно
поменять. Тот же JSON подходит любому клиенту, который принимает конфигурацию
в формате `mcpServers`.
## 9. Инструменты
Пути внутри проекта задаются относительно `rootDir`. Для явно разрешённых
внешних папок доступны абсолютные пути и ссылки из проекта. Остальные внешние
пути и ссылки блокируются. См. «Внешние папки и сетевые пути» в разделе 11.
### Путеводитель
| Инструмент | Что делает |
| --- | --- |
| `project_guide()` | Возвращает `Rules.md`: контекст проекта и правила работы |
Тот же текст автоматически дописывается к первому результату инструмента в
диалоге, так что обычно вызывать его не нужно.
### Файлы
| Инструмент | Что делает |
| --- | --- |
| `list_files(path, recursive, max_depth)` | Листинг папки; `recursive` включает вложенные |
| `read_file(path, offset, limit)` | Файл с номерами строк, постранично для больших файлов |
| `search_text(pattern, path, glob, regex, case_sensitive, context, max_results)` | Поиск по проекту, отдаёт `путь:строка: текст` |
| `file_info(path)` | Размер, число строк, время изменения, текст или бинарник |
| `edit_file(path, old_string, new_string, replace_all)` | Точная замена, возвращает дифф |
| `multi_edit(path, edits, regex)` | Несколько замен в одном файле, всё или ничего |
| `create(path, type, content, overwrite)` | Новый файл или папка |
| `move(source, destination, overwrite)` | Переименование или перемещение |
| `delete(path, recursive)` | Безвозвратное удаление файла или папки |
- Шумные папки (`node_modules`, `.git`, `dist`, `build`, `.venv`, `venv`,
`__pycache__`, `.next`, `.idea`) при листинге и поиске пропускаются всегда.
- У `delete` нет корзины: непустая папка требует `recursive=true`, сам корень
удалить нельзя, а папка, внутри которой лежит запрещённый правилами файл,
не удаляется целиком.
- `move` требует прав `Delete` на источник и `Create` на приёмник, поэтому
защищённый файл нельзя вынести из-под защиты переносом.
- Один файл не стоит править двумя вызовами одновременно: `edit_file` и
`multi_edit` читают и пишут файл целиком, параллельные вызовы затирают друг
друга.
### Команды
| Инструмент | Что делает |
| --- | --- |
| `run_command(command, cwd, timeout, background, wait_for, idle_timeout, restart)` | Запускает команду внутри `rootDir` |
| `list_processes()` | Всё, что запущено в этой сессии: живое и завершённое |
| `check_process(id, lines, wait, wait_for)` | Состояние и свежий вывод одного процесса |
| `stop_process(id, stop_all)` | Убивает процесс вместе с детьми |
- Команды идут через системный шелл, поэтому `&&`, `||`, `|` и `;` работают.
Каждое звено цепочки проверяется по правилам `Run(...)` отдельно, команда без
подходящего правила отклоняется.
- Команда должна быть одной строкой: перевод строки — разделитель команд, так что
многострочная строка выполнилась бы лишь частично. Склеивайте шаги через `&&`
или положите их в скрипт.
- `background=false` (по умолчанию) — для команд, которые завершаются: вызов
возвращает код выхода и вывод, процесс после этого гарантированно мёртв. По
таймауту убивается всё дерево процессов.
- `background=true` — для долгоживущих команд вроде `npm run dev`. Процесс живёт
после вызова; когда вернуть управление, решают `wait_for` (регулярное
выражение) или `idle_timeout`, например `wait_for: "ready in|listening on"`.
- Вывод стримится в `logs/cmd-NNN.log`, а не держится в памяти, так что шумная
сборка не разорвёт ответ. Длинный вывод возвращается началом и хвостом.
- Каждый запущенный процесс отслеживается, и короткий блок `[processes]`
дописывается к **любому** результату инструмента, пока что-то работает или
только что завершилось:
```
[processes]
#1 running npm run dev (pid 24188, 96s, log logs/cmd-001.log)
#2 exited (exit 0) npm run build (log logs/cmd-002.log)
```
Так модель остаётся в курсе терминала: MCP-сервер не может сам прислать
уведомление внутрь хода модели, поэтому состояние заново подкладывается
контекстом при следующем вызове.
- Запуск команды, которая уже работает, отклоняется со ссылкой на её номер;
`restart=true` сначала останавливает старый процесс.
- Вывод принудительно переводится в UTF-8 (`chcp 65001` на Windows,
`PYTHONIOENCODING`, `NO_COLOR`), чтобы логи не превращались в кракозябры.
- Процессы убиваются деревом (`taskkill /T` на Windows, целая группа процессов
на macOS/Linux, сначала `SIGTERM`, потом `SIGKILL`), потому что порт держит не
`npm`, а его дочерний `node`.
- При выходе сервера все запущенные процессы останавливаются, так что `Ctrl+C`
не оставляет висящих dev-серверов.
- Реестр процессов дублируется в `logs/processes.json`. Жёсткое убийство сервера
(закрытая консоль, диспетчер задач) не выполняет очистку и оставляет детей
живыми, поэтому при старте каждая запись сверяется с ОС: живой pid
подхватывается обратно в реестр и показывается как `running (adopted)`,
остальное считается завершённым, а их `cmd-NNN.log` удаляются. Номера
продолжают расти, поэтому имена логов не конфликтуют. Подхваченный процесс
можно смотреть и останавливать как обычно — теряется только точный код
выхода, потому что сервер больше не владеет его хэндлом.
### Диалоговое окно
Инструмент `show-dialog-box()` открывает диалоговое окно **Ask me**, которое позволяет входить в зацикленный диалог с агентом.

**Сценарий работы**
1. При работе с MCP Local Access в инструкциях агента добавляется правило: после завершения задачи или уточняющего вопроса в чате вызвать `show-dialog-box`.
2. Введите следующий запрос в появившемся окне и нажмите **OK**. Пустой ответ - завершают диалог.
3. Если пользователь не ответил в течение 50 сек (таймаут можно изменить), то инструмент перезапускает себя, если этого не делать, то инструмент завершится по таймауту.
**Настройка**
В файле `config.json` есть параметры, которые настраивают работу с этим инструментом, пример:
```json
"dialog": {
"width": 500,
"height": 300,
"sound": true,
"autoPrompt": true,
"waitTimeoutSeconds": 50
}
```
Настройки задают начальную ширину и высоту окна, время перезапуска инструмента (менять нужно осознанно), включить звук при вызове инструмента, нужно ли вызывать инструмент после ответа.
**Автоматическое указание агенту**
Инструкция передаётся в MCP-поле `instructions` и дополнительно с первым
результатом инструмента; повторно — при изменении файла правил или после
длительного перерыва.
Параметр принимает только JSON `true`/`false`. Если параметр
отсутствует, указания включены.
**Ожидание и таймауты**
Когда время одного вызова истекает, инструмент возвращает `Call show-dialog-box again to continue waiting for the same window.` Агент должен вызвать его снова: продолжится ожидание **того же
окна**, без дубликата и без потери набранного текста. Если ответ уже отправлен, следующий вызов заберёт сохранённый результат. Одновременно ожидающий второй вызов получает сообщение, что ожидание уже идёт.
### Управление браузером (Browser Control)
Инструменты MCP Local Access `browser_*` управляют **вашим** браузером, используя расширение Browser Control.
`server.py` поднимает WebSocket на `ws://localhost:9009`, к которому подключается расширение.
Браузер могут использовать облачные агенты и работать с сайтами под вашей авторизацией.
**Как включить**
1. Установите в Chrome расширение Browser Control.
2. В браузере откройте нужную вкладку, нажмите на иконку Browser Control и кнопку **Connect this tab**.
Либо агент может сам открыть Chrome и подключить свою вкладку без дополнительных действий со стороны пользователя.
| Инструмент | Что делает |
| --- | --- |
| `browser_status()` | Показывает, слушает ли сервер порт, подключена ли вкладка и есть ли агентская сессия; выводит расширенную диагностику |
| `browser_launch(url)` | Запускает Chrome с вашим профилем и подключает вкладку агента без Connect |
| `browser_close(force)` | Закрывает вкладку агента; Chrome, запущенный агентом, завершает по PID |
| `browser_navigate(url)` | Открывает URL, возвращает снимок страницы |
| `browser_go_back()` / `browser_go_forward()` | Назад / вперёд по истории |
| `browser_snapshot()` | ARIA-снимок страницы с `ref` элементов |
| `browser_click(element, ref)` | Клик по элементу |
| `browser_hover(element, ref)` | Наведение на элемент |
| `browser_drag(start_element, start_ref, end_element, end_ref)` | Перетаскивание |
| `browser_type(element, ref, text, submit)` | Ввод текста, `submit=true` — Enter после |
| `browser_select_option(element, ref, values)` | Выбор в выпадающем списке |
| `browser_press_key(key)` | Нажатие клавиши, например `Enter`, `ArrowLeft` |
| `browser_wait(time)` | Пауза в секундах, отклоняет отрицательные, бесконечные и превышающие 60 секунд значения |
| `browser_screenshot()` | Скриншот вкладки (PNG) |
| `browser_get_console_logs()` | Возвращает логи и структурированную информацию о состоянии их сбора |
| `browser_cdp(method, params)` | Одна команда Chrome DevTools Protocol для подключённой вкладки (`DOM.getDocument`, `Performance.getMetrics`, ...). `Browser.*`, `Target.*` и отключение Runtime/Log/Page запрещены, как и куки/хранилище всего браузера и `Network.loadNetworkResource` |
| `browser_evaluate(expression, await_promise)` | JavaScript в странице, как в консоли DevTools; возвращает значение или исключение |
| `browser_cdp_events(action, domain, limit)` | Подписка на события домена CDP (`subscribe/unsubscribe/read/clear`) |
| `browser_diagnostics()` | Реальная проверка debugger, подписок, ошибок и потерь сбора |
| `browser_console(limit, level, clear)` | Console API, исключения и браузерные Log-сообщения; очистка только явно |
| `browser_network(action, request_id, limit, part, max_chars)` | `start/stop/clear/list/detail/body`; сеть выключена по умолчанию |
| `browser_capture(mode, scale)` | `viewport/full_page`, `original/reduced`; PNG веб-страницы, не всего окна Chrome |
Сценарий для модели: `browser_snapshot` → взять `ref` нужного элемента → `browser_click` / `browser_type`. Действия возвращают свежий снимок страницы.
В `readOnly` у `browser_cdp` разрешены только читающие методы (`get*`, `describe*`, `query*`, `search*`, `resolve*`); `browser_evaluate` и подписки запрещены. В журнал аудита записываются только имя метода и длина выражения, но не параметры и не код.
Сервер отказывает в методах, которые выходят за пределы вкладки (`CDP_DENIED_DOMAINS` / `CDP_DENIED_METHODS` в `server.py`): `Browser.*`, `Target.*`, `Network.getAllCookies`, `Storage.getCookies/setCookies/clearCookies`, `Network.clearBrowserCookies/clearBrowserCache`, `Storage.clearDataFor*`, `Network.loadNetworkResource`, `Network.getCookies` с `urls`, `Page.close/crash`, а также `Page.navigate` на `javascript:` и, по умолчанию, на `file://` (если `browser.allowFileUrls` не установлен в `true`). Browser Control дополнительно ограничивает `Network.setCookie(s)/deleteCookies` и методы с `origin`/`securityOrigin`/`storageKey` сайтом текущей вкладки.
**Настройка MCP Local Access** (необязательно; ниже приведены значения по умолчанию):
```json
"browser": {
"enabled": true,
"host": "localhost",
"port": 9009,
"timeoutSeconds": 30,
"allowFileUrls": false,
"chromePath": "",
"userDataDir": "",
"profileDirectory": "",
"launchArgs": [],
"launchTimeoutSeconds": 30,
"closeTimeoutSeconds": 10
}
```
| Ключ | Значение |
| --- | --- |
| `enabled` | `false` отключает мост: порт не слушается, инструменты отвечают отказом |
| `host` | Интерфейс WebSocket. `localhost` — только эта машина; наружу не открывайте |
| `port` | Порт, к которому подключается расширение. Browser MCP знает только `9009`; в Browser Control порт меняется в popup |
| `timeoutSeconds` | Сколько ждать ответа расширения на одну команду |
| `allowFileUrls` | По умолчанию `false`: сервер запрещает переходы на `file://` через `browser_navigate` / `browser_cdp Page.navigate`. Для разрешения доступа необходимо установить `browser.allowFileUrls` в `true` в `config.json`, **а также** установить флажок «Allow file:// pages» во всплывающем окне Browser Control и разрешить расширению «Доступ к URL файлов» в настройках Chrome. Эти разрешения дополняют, а не заменяют друг друга. Такой доступ позволяет агенту читать **любой доступный браузеру файл компьютера** в обход `rootDir` и правил Deny |
| `chromePath` | `browser_launch`: путь к `chrome.exe` / `Google Chrome`. Пусто: ищется сам (реестр App Paths, Program Files, LocalAppData; `/Applications`; `google-chrome`/`chromium` в PATH) |
| `userDataDir` | `browser_launch`: `--user-data-dir`. Пусто: ваш обычный профиль (сайты остаются залогинены) |
| `profileDirectory` | `browser_launch`: `--profile-directory`, например `Profile 1`. Пусто: профиль по умолчанию |
| `launchArgs` | `browser_launch`: дополнительные флаги Chrome (список строк) |
| `launchTimeoutSeconds` | Сколько секунд `browser_launch` ждёт подключения расширения (минимум 5) |
| `closeTimeoutSeconds` | Сколько `browser_close` ждёт, пока Chrome закроется сам после закрытия последней вкладки, прежде чем завершать его по PID |
**Важно**
- В режиме `readOnly` доступны чтение страницы, снимки и чтение диагностики.
Действия браузера, очистка журналов и включение/выключение сетевого сбора запрещены.
- В `logs/audit.log` записываются переходы, клики и события ввода; сам введённый текст не записывается (это может быть пароль) — только его длина.
- Браузер не входит в песочницу `rootDir`: агент видит всё, что открыто в подключённой вкладке, и действует от вашего имени.
- Для `browser_navigate` и `Page.navigate` сервер всегда запрещает переходы на `javascript:`, а на `file://` — только при `browser.allowFileUrls=false` (значение по умолчанию). Разрешение на сервере не отменяет отдельные ограничения Browser Control и Chrome.
### Агентский браузер: `browser_launch` / `browser_close`
Установить расширение Browser Control может только пользователь.
Агент может сам открыть Chrome и подключить свою вкладку.
Если `userDataDir` пуст, Chrome запускается с вашим профилем и может открывать сайты под вашей учётной записью.
Варианты запуска Chrome:
- Chrome не запущен: агент запускает его, а после работы закрывает свою вкладку. Если других открытых вкладок нет, агент завершает Chrome по PID.
- Chrome уже открыт: новая **вкладка** открывается в последнем активном окне, а по завершении работы агент закрывает только свою вкладку.
Для работы с браузером вкладка агента не обязана быть активной.
Агент может получать снимки страницы, нажимать на элементы, переходить по ссылкам и делать скриншоты даже в свёрнутом окне.
Страница при этом «спит», и её обновления происходят медленнее.
Сбор ограничен основной CDP-целью выбранной вкладки; дочерние CDP-сессии не агрегируются.
Журналы хранятся в памяти и теряются при перезапуске worker, отключении или смене вкладки.
Большие снимки ограничены 20 000 пикселей по стороне и бюджетом 32 млн пикселей
с учётом DPR/масштаба; возможны ограничения конкретного Chrome, sticky и lazy loading.
Перед выпуском требуется фактическая проверка в Chrome: mock-тесты её не заменяют.
## 10. Rules.md — краткое описание вашего проекта
`Rules.md` — (опционально) необязательный файл, кратко опишите свой проект,
или можете указать путь к файлам документации по вашему проекту.
Смысл простой — не объяснять модели детали о проекте в каждом новом диалоге.
- Название файла и путь к этому файлу, можно изменить в переменной `rulesFile`
в `config.json`. Если переменная не задана, то берется файл `Rules.md`,
расположенный в папке указанной в `rootDir`.
- Файл перечитывается при изменении, перезапуск не нужен: новый текст приходит со
следующим результатом инструмента и в следующей сессии клиента.
## 11. Права доступа
Правила живут в блоке `permissions` файла `config.json` и записываются в стиле
Claude Code: `Инструмент(glob-путь)`.
```json
{
"permissions": {
"defaultMode": "allow",
"allow": ["List(**)", "Read(**)", "Edit(**)", "Create(**)", "Delete(**)"],
"deny": [
"*(**/.env)",
"*(**/.env.*)",
"*(**/config.json)",
"*(**/secrets/**)",
"*(**/*.pem)",
"*(**/*.key)",
"Edit(**/.git/**)",
"Create(**/.git/**)",
"Delete(**/.git/**)"
]
}
}
```
- Глаголы: `List`, `Read`, `Edit`, `Create`, `Delete`, `Run`, внешний `Write` или `*` для всех
путевых глаголов. Соответствие инструментам: `List` и `Read` вместе определяют,
что показывает `list_files`; `Read` покрывает ещё `search_text` и `file_info`;
`Edit` покрывает `multi_edit`; `Create` — `create` и приёмник `move`; `Delete` —
`delete` и источник `move`.
- `Run` стоит особняком: его аргумент — шаблон команды, а не путь, поэтому
`*(**)` его никогда не выдаёт. См. «Правила для команд» ниже.
- `defaultMode: "allow"` — внутри `rootDir` разрешено всё, что не запрещено (чёрный список). Внешние папки всегда требуют явного абсолютного `Read`/`Write`.
- `defaultMode: "deny"` — работают только пути из `allow` (белый список).
- **`deny` всегда сильнее `allow`.**
- Обычные шаблоны задаются относительно `rootDir`, разделитель — `/`, поддерживаются
`*` (один сегмент), `**` (любая глубина) и `?` (один символ).
- Шаблоны привязаны к корню, поэтому `.env` совпадает **только** с файлом в
корне. Чтобы накрыть все подпапки, нужен префикс `**/` — `**/.env`.
- `secrets/**` скрывает и саму папку `secrets`.
- Запрещённые элементы не показываются в `list_files`, вместо них выводится
число скрытых записей.
- Если блока `permissions` нет, действуют встроенные значения: разрешено всё,
кроме `**/.env`, `**/.env.*`, `**/secrets/**`, `**/*.pem`, `**/*.key`, плюс
запрет записи и удаления внутри `**/.git/**`.
- Правила читаются при старте — после правки перезапустите сервер.
- `*(**/config.json)` доступ к файлу закрыт для модели, чтобы не переписала
собственные права.
### Внешние папки и сетевые пути
Чтобы подключить конкретную папку вне `rootDir`, владелец добавляет в
`permissions.allow` абсолютное правило `Read(папка)` или `Write(папка)`.
Корень проекта менять не нужно. Это разрешение папки и всего её содержимого,
а не всего диска.
```json
{
"permissions": {
"defaultMode": "allow",
"allow": [
"List(**)", "Read(**)", "Edit(**)", "Create(**)", "Delete(**)",
"Read(J:/src/**)",
"Write(//debian/share/work)"
],
"deny": [
"*(**/.env)", "*(**/.env.*)", "*(**/config.json)",
"*(**/secrets/**)", "*(**/*.pem)", "*(**/*.key)",
"Delete(//debian/share/work/important/**)"
]
}
}
```
| Внешнее правило | Листинг, поиск, сведения и чтение | Изменение и создание | Удаление и перемещение |
| --- | --- | --- | --- |
| `Read(J:/src)` | Да | Нет | Нет |
| `Write(J:/src)` | Да | Да | Да, с проверкой прав источника и назначения |
- `Read(J:/src)` и `Read(J:/src/**)` равнозначны: включают саму папку и любую
глубину вложенности. То же для `Write`. Другие шаблоны в разрешении внешней
папки запрещены: укажите конкретный абсолютный путь.
- Поддерживаются дисковые пути Windows (`J:/src`, `J:/Просто так`), UNC
(`//debian/share/src`) и абсолютные пути Linux/macOS (`/mnt/share/src`).
Используйте `/`; в JSON обратный слеш требует экранирования, например
`"Read(J:\\src)"` в виде JSON должен содержать два обратных слеша.
На Linux UNC не подключает Samba автоматически: сначала смонтируйте ресурс
средствами ОС и разрешите его локальный путь.
- `Read` с относительным шаблоном сохраняет старое значение внутри проекта.
`Write` в `allow` используется только с абсолютной внешней папкой.
- Широкие относительные `Edit(**)`, `*(**)` и `defaultMode: "allow"` не дают
внешних прав и не повышают внешний `Read` до `Write`.
- Можно обращаться через `src/main.py`, если `rootDir/src` — ссылка на
разрешённую папку, либо напрямую через `J:/src/main.py` или
`//debian/share/src/main.py`. Абсолютные пути внутри самого `rootDir`
по-прежнему не принимаются: используйте относительные.
- Проверяется конечная цель ссылок. Выход в неразрешённую папку, соседний
`src-other`, неполный UNC, `J:src`, device-пути и Windows alternate data
streams не разрешаются. Листинг и поиск пропускают запрещённые папки и
не зацикливаются на ссылках.
- `deny` проверяется по пути ссылки, конечной цели и пути относительно
подключённой папки. Запреты вроде `*(**/.env)` и `*(**/secrets/**)` действуют
также снаружи; ссылка с невинным именем не открывает запрещённую цель.
Запрет папки действует и на её содержимое. `Write(...)` в `deny` блокирует
все файловые операции; для запрета только удаления используйте `Delete(...)`.
- При вложенных разрешениях наиболее конкретная папка определяет уровень:
`Write(J:/src)` плюс `Read(J:/src/vendor)` делает `vendor` доступной только
для чтения. На одной и той же папке `Write` повышает `Read`.
- `readOnly: true` блокирует все инструменты изменения даже при `Write`.
Сам `rootDir` и корни подключённых папок нельзя удалить или переместить.
Изменение файла через символьную ссылку сохраняет ссылку; удаление обычной
символьной ссылки удаляет ссылку, а не её цель.
- Доступ зависит и от Windows/SMB: учётная запись сервера должна видеть ресурс.
UNC обычно надёжнее буквы диска, привязанной к пользовательскому сеансу.
При старте реальный путь разрешения фиксируется; подключите ресурс заранее.
Если диск переподключён на другой ресурс или изменена цель ссылки-разрешения,
перезапустите сервер и перепроверьте настройки.
- `Read`/`Write` не выдают `Run`. Внешний `cwd` команды требует `Write` и
отдельного разрешения `Run`. Однако команда, запущенная даже внутри проекта,
не ограничена файловой песочницей и может обращаться к другим путям.
- Эти проверки не заменяют изоляцию ОС: локальный процесс, одновременно
меняющий ссылки, и разрешённые на стороне Samba переходы за пределы share
требуют отдельных ограничений ОС/SMB.
После изменения кода или правил перезапустите сервер. `config.example.json`
по умолчанию не открывает никаких внешних папок; реальные пути добавляет владелец
в свой `config.json`.
Проверка реализации без чтения рабочего конфига и секретов:
```bash
python -m unittest discover -s tests -v
```
Тесты используют временные папки; UNC и сопоставление сетевого диска дополнительно
проверяются моделированием, без подключения к реальному Samba-ресурсу.
### Правила для команд
Команды — всегда белый список. `Run(...)` принимает шаблон команды, где `*`
означает любой текст; всё остальное сравнивается буквально, пробелы
сжимаются, регистр игнорируется. Если ни одного правила `Run` нет,
`run_command` отказывает во всём.
```json
{
"permissions": {
"allow": [
"Run(npm run dev)",
"Run(npm run build)",
"Run(npm install)",
"Run(git status)",
"Run(git diff*)",
"Run(python *)",
"Run(pytest*)"
],
"deny": ["Run(*rm -rf*)"]
}
}
```
- Цепочка разбивается по `&&`, `||`, `|`, `;` и переводам строк, и каждый сегмент
должен сам совпасть с правилом из `allow`. Для `npm run build && git status`
нужны оба правила; `npm run build && rm -rf /` упадёт на втором сегменте.
- Разбиение учитывает кавычки, поэтому `git commit -m "fix | bug"` — один сегмент.
- `deny` и здесь сильнее, а `defaultMode: "allow"` на команды **не**
распространяется.
**Насколько это обеспечивает безопасность.** Не сильно, и это осознанный компромисс.
Белый список спасает от случайностей и опечаток, а не от целенаправленной атаки: одного
разрешённого `python *` или `npm run *` достаточно, чтобы сделать всё, что может
обычная программа, включая выход за `rootDir` — песочница путей ограничивает
только файловые инструменты, но никогда не дочерний процесс. Держите список
коротким и конкретным и относитесь к запуску команд как к «я доверяю этому
клиенту свою учётную запись», а не как к песочнице.
Пример белого списка: читать только `src` и Markdown, писать только в
`src/generated`:
```json
{
"permissions": {
"defaultMode": "deny",
"allow": ["List(src/**)", "Read(src/**)", "Read(*.md)", "Create(src/generated/**)", "Edit(src/generated/**)"]
}
}
```
## 12. Лимиты
Зашиты в `server.py` (константы в начале файла), меняются правкой кода:
| Лимит | Значение |
| --- | --- |
| Размер одного ответа | 100 000 символов, дальше обрезка |
| Чтение/запись содержимого файла | 1 МБ |
| Записей в листинге | 1000 |
| Таймаут синхронной команды | 60 с по умолчанию, максимум — `maxCommandTimeout` (600 с) |
| Ожидание фоновой команды | `idle_timeout` 15 с по умолчанию |
| Одновременно фоновых процессов | 8 |
| Вывод команды в одном ответе | 20 000 символов |
| Завершённый процесс в реестре | 10 минут |
| `audit.log` | последние 300 строк |
| `Rules.md` | 20 000 символов |
## 13. Безопасность
- Любой, у кого есть публичный URL **и** токен, может читать, менять и удалять
внутри `rootDir` и явно подключённых внешних папок — в пределах разрешённых операций и правил `deny`.
- `run_command` — самая широкая дыра: дочерний процесс не связан песочницей
путей, поэтому разрешённые `python *`, `node *` или `npm run *` могут добраться
до всего, до чего дотягивается ваша учётная запись. Держите список `Run`
коротким.
- Останавливайте фоновые процессы, когда закончили (`stop_process(stop_all=true)`);
остановка сервера по `Ctrl+C` убивает их тоже. Жёсткое убийство — нет, но при
следующем старте живые процессы подхватываются обратно.
- Выключайте туннель, когда он не нужен.
- Токен меняется так: очистить `MCP_TOKEN` в `.env` и перезапустить сервер.
- `rootDir` держите настолько узким, насколько позволяет задача: родительская
папка втягивает в песочницу и сам этот проект вместе с его `.env`.
**Если нужно строже:** включите подтверждение вызовов на стороне клиента, сузьте
`rootDir`, поставьте `"readOnly": true` или уберите все правила `Run`.
## 14. Диагностика
| Симптом | Причина |
| --- | --- |
| `rootDir is not set` | Нет `config.json` или в нём не задан `rootDir` |
| `Config file not found` | Файла из `--config` нет в папке `server.py` |
| `browser : NOT started - port 9009 is busy` | Порт занят другим Browser MCP (`npx @browsermcp/mcp`); закройте его |
| `No connection to the browser extension` | Во вкладке не нажат **Connect** в расширении Browser MCP |
| `the active config file is protected` | Модель обратилась к активному файлу настроек — он закрыт всегда |
| 401 от клиента | Токен не совпадает или заголовок называется не `Authorization` |
| 421 `Invalid Host header` | Старый `server.py` без перезаписи `Host` |
| 406 Not Acceptable | Клиент не отправил заголовок `Accept`, показанный в разделе 8 |
| Cloudflare 1033 | Туннель потерял соединение; перезапустите с `--protocol http2` |
| Клиент не видит инструментов | В URL нет суффикса `/mcp` |
| Новый инструмент не появился | Клиент закэшировал старый список; переподключите коннектор |
| Ответы подвешиваются до таймаута | Прокси буферизует SSE: `proxy_buffering off` |
| `Permission denied` | Путь совпал с правилом `deny` в `config.json` |
| `Permission denied: Run(...)` | Ни одно правило `Run` не подошло команде или звену цепочки |
| `Already running as #N` | Та же команда ещё жива: посмотрите её, остановите или передайте `restart=true` |
| `Timed out after Ns` | Долгоживущая команда запущена без `background=true` |
| `command must be a single line` | Переводы строк разделяют команды; склейте шаги через `&&` |
| `Absolute paths inside rootDir are not allowed` | Для проекта используйте относительный путь |
| `Path escapes the allowed root directory or external grants` | Внешняя цель не разрешена абсолютным `Read`/`Write` или ссылка вышла из разрешённой папки |
| `external directory is read-only` | Для изменения внешней папки нужно явное `Write`, внутренних `Edit(**)` недостаточно |
## 15. FAQ
**А что если я хочу несколько rootDir?**
Один процесс — один основной корень. Для конкретных внешних папок добавьте
абсолютные `Read`/`Write` из раздела 11. Другие варианты:
- Указать общую родительскую папку и сузить доступ правами, например
`List(project-a/**)`, `Read(project-a/**)`, `Edit(project-a/**)` и то же для
`project-b`. Просто и работает, но всё лежит в одной песочнице.
- Запустить второй экземпляр сервера со своим конфигом и портом: конфиг
задаётся ключом `--config` (файл ищется рядом с `server.py`) или переменной
`CONFIG_FILE`, а порт — в самом конфиге.
```bash
python server.py --config config-b.json
CONFIG_FILE=/path/to/config-b.json python server.py # macOS / Linux
set CONFIG_FILE=C:\path\to\config-b.json && python server.py # Windows
```
Мост к браузеру может работать только в одном экземпляре (порт `9009` один):
в остальных поставьте `"browser": { "enabled": false }`.
Каждому экземпляру нужен свой публичный адрес (второй туннель или второй
`location` в Nginx) и своё подключение в клиенте. Токен берётся из `.env`
рядом с `server.py`, так что для разных токенов нужны разные копии проекта.
**Обязателен ли cloudflared?**
Нет. Нужен любой способ отдать порт наружу по HTTPS: свой Nginx/Caddy/Traefik,
именованный туннель Cloudflare, любой другой туннель. Сервер о нём ничего не
знает.
**Можно ли обойтись без публикации наружу?**
Notion и claude.ai работают через интернет, им нужен публичный HTTPS-адрес.
Локальные клиенты (Cursor и другие, запущенные на этой же машине) могут
подключаться прямо к `http://127.0.0.1:8000/mcp` — тогда поставьте
`"host": "127.0.0.1"`.
**Может ли модель выйти за пределы rootDir?**
По умолчанию файловыми инструментами — нет. Владелец может явно разрешить
конкретные внешние папки абсолютными `Read`/`Write`; другие внешние пути
и ссылки остаются закрыты. Через `run_command` — да: дочерний процесс
песочницей не ограничен. Если это неприемлемо, не давайте правил `Run` или
включите `readOnly`.
**Как вообще запретить любые изменения?**
`"readOnly": true` — остаются только чтение, поиск и листинг. Промежуточный
вариант: оставить `Edit`/`Create` на рабочую папку и запретить остальное через
`deny`.
**Нужен ли Rules.md?**
Нет, сервер работает и без него. Но с ним не приходится в каждом новом диалоге
объяснять, что это за проект и как в нём принято работать. См. раздел 10.
**Работает ли это на macOS и Linux?**
Код есть (`/bin/sh`, `start_new_session`, убийство группы процессов), но проверен
он только на Windows. Считайте первый запуск на macOS/Linux тестовым.
**Модель удалила нужный файл — как откатить?**
Никак средствами сервера: корзины нет, удаление безвозвратное. Держите проект под
git, а в `logs/audit.log` можно посмотреть, что именно произошло.
Рекомендуется настроить запуск бекапа проекта перед каждой новой задачей.
**Почему клиент спрашивает подтверждение на каждый вызов?**
Это поведение клиента, сервер на него не влияет. В Notion подтверждения
настраиваются в свойствах подключения; права сервера работают независимо от
того, что вы нажали.
**Токен утёк. Что делать?**
Очистить значение `MCP_TOKEN` в `.env`, перезапустить сервер (он сгенерирует
новый токен), вписать новый токен в клиента. Старый перестаёт работать сразу.
**Почему `logs/` внутри rootDir, а не рядом с сервером?**
Чтобы модель могла сама прочитать вывод команды через `read_file`. Побочный
эффект: добавьте `logs/` в `.gitignore`.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues