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. [Инструменты](#9-инструменты)
9. [Rules.md — путеводитель по проекту](#10-rulesmd--путеводитель-по-проекту)
10. [Права доступа](#11-права-доступа)
11. [Лимиты](#12-лимиты)
12. [Безопасность](#13-безопасность)
13. [Диагностика](#14-диагностика)
14. [FAQ](#15-faq)
---
## 1. Как это работает
```
Notion AI / claude.ai / chatgpt
│ HTTPS + Bearer-токен
▼
cloudflared-туннель или свой web сервер с TLS
│ HTTP внутри локальной сети
▼
server.py (слушает host:port, отдаёт /mcp)
│ все пути проверяются: песочница + права
▼
rootDir — единственная папка, к которой есть доступ
```
Стек: Python + MCP SDK для Python (FastMCP) + uvicorn, транспорт Streamable HTTP
на пути `/mcp`, авторизация Bearer-токеном. Сервер ничего не знает о способе
публикации: он просто слушает `host:port`, а как этот порт оказался в интернете —
дело туннеля или прокси.
## 2. Структура проекта
| Путь | Что это |
| --- | --- |
| `server.py` | весь сервер: конфиг, песочница путей, права, инструменты, авторизация |
| `config.json` | файл настроек, создайте его из `config.example.json` |
| `config.example.json` | шаблон настроек |
| `.env` | файл хранит токен `MCP_TOKEN`, который генерируется при первом запуске `server.py` |
| `requirements.txt` | три зависимости: `mcp`, `uvicorn`, `python-dotenv` |
| `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` | Единственная папка, к которой сервер имеет доступ. Всё вне неё отклоняется. Обязательный параметр. |
| `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. |
В `.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)
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. Инструменты
Все пути в аргументах — относительные, от `rootDir`. Абсолютные пути
отклоняются, как и симлинки, ведущие за пределы корня.
### Путеводитель
| Инструмент | Что делает |
| --- | --- |
| `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` удаляются. Номера
продолжают расти, поэтому имена логов не конфликтуют. Подхваченный процесс
можно смотреть и останавливать как обычно — теряется только точный код
выхода, потому что сервер больше не владеет его хэндлом.
### Журнал действий
При `"audit": true` каждая команда, перемещение и `multi_edit` дописываются в
`logs/audit.log`:
```
2026-09-15 03:40:12 ok run_command: #1 npm run dev (cwd .)
2026-09-15 03:41:02 ok move: src/old.ts -> src/new.ts
```
Это ответ на вопрос «что модель на самом деле сделала» — полезно после долгой
сессии или когда что-то сломалось, а диффа недостаточно. Добавьте `logs/` в
`.gitignore`.
Журнал — скользящий хвост, а не архив: он обрезается до последних 300 строк
(`AUDIT_KEEP_LINES` в `server.py`) при старте и каждые 100 записей, поэтому расти
бесконечно не может. Логи команд чистятся отдельно: при старте удаляется каждый
`logs/cmd-NNN.log`, чей процесс не жив и не подхвачен.
## 9. 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` или `*` для всех
путевых глаголов. Соответствие инструментам: `List` и `Read` вместе определяют,
что показывает `list_files`; `Read` покрывает ещё `search_text` и `file_info`;
`Edit` покрывает `multi_edit`; `Create` — `create` и приёмник `move`; `Delete` —
`delete` и источник `move`.
- `Run` стоит особняком: его аргумент — шаблон команды, а не путь, поэтому
`*(**)` его никогда не выдаёт. См. «Правила для команд» ниже.
- `defaultMode: "allow"` — разрешено всё, что не запрещено (чёрный список).
- `defaultMode: "deny"` — работают только пути из `allow` (белый список).
- **`deny` всегда сильнее `allow`.**
- Шаблоны задаются относительно `rootDir`, разделитель — `/`, поддерживаются
`*` (один сегмент), `**` (любая глубина) и `?` (один символ).
- Шаблоны привязаны к корню, поэтому `.env` совпадает **только** с файлом в
корне. Чтобы накрыть все подпапки, нужен префикс `**/` — `**/.env`.
- `secrets/**` скрывает и саму папку `secrets`.
- Запрещённые элементы не показываются в `list_files`, вместо них выводится
число скрытых записей.
- Если блока `permissions` нет, действуют встроенные значения: разрешено всё,
кроме `**/.env`, `**/.env.*`, `**/secrets/**`, `**/*.pem`, `**/*.key`, плюс
запрет записи и удаления внутри `**/.git/**`.
- Правила читаются при старте — после правки перезапустите сервер.
- `*(**/config.json)` доступ к файлу закрыт для модели, чтобы не переписала
собственные права.
### Правила для команд
Команды — всегда белый список. `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/**)"]
}
}
```
## 11. Лимиты
Зашиты в `server.py` (константы в начале файла), меняются правкой кода:
| Лимит | Значение |
| --- | --- |
| Размер одного ответа | 100 000 символов, дальше обрезка |
| Чтение/запись содержимого файла | 1 МБ |
| Записей в листинге | 1000 |
| Таймаут синхронной команды | 60 с по умолчанию, максимум — `maxCommandTimeout` (600 с) |
| Ожидание фоновой команды | `idle_timeout` 15 с по умолчанию |
| Одновременно фоновых процессов | 8 |
| Вывод команды в одном ответе | 20 000 символов |
| Завершённый процесс в реестре | 10 минут |
| `audit.log` | последние 300 строк |
| `Rules.md` | 20 000 символов |
## 12. Безопасность
- Любой, у кого есть публичный 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`.
## 13. Диагностика
| Симптом | Причина |
| --- | --- |
| `rootDir is not set` | Нет `config.json` или в нём не задан `rootDir` |
| 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 are not allowed` | Так и задумано: пути относительны `rootDir` |
## 14. FAQ
**А что если я хочу несколько rootDir?**
Один процесс — один корень, это основа песочницы. Варианты:
- Указать общую родительскую папку и сузить доступ правами, например
`List(project-a/**)`, `Read(project-a/**)`, `Edit(project-a/**)` и то же для
`project-b`. Просто и работает, но всё лежит в одной песочнице.
- Запустить второй экземпляр сервера со своим конфигом и портом: путь к конфигу
берётся из переменной `CONFIG_FILE`, а порт — из самого конфига.
```bash
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
```
Каждому экземпляру нужен свой публичный адрес (второй туннель или второй
`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?**
Файловыми инструментами — нет: абсолютные пути отклоняются, `..` разворачивается,
симлинки наружу отбрасываются. Через `run_command` — да: дочерний процесс
песочницей не ограничен. Если это неприемлемо, не давайте правил `Run` или
включите `readOnly`.
**Как вообще запретить любые изменения?**
`"readOnly": true` — остаются только чтение, поиск и листинг. Промежуточный
вариант: оставить `Edit`/`Create` на рабочую папку и запретить остальное через
`deny`.
**Нужен ли Rules.md?**
Нет, сервер работает и без него. Но с ним не приходится в каждом новом диалоге
объяснять, что это за проект и как в нём принято работать. См. раздел 9.
**Работает ли это на 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