- Go 87.6%
- HTML 11.9%
- Dockerfile 0.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
REST: - POST /api/edit — последовательные текстовые замены - GET /api/search — regex-поиск имён файлов - GET /api/info — метаданные файла/каталога - POST /api/archive/create — создать архив (attachment) - POST /api/archive/extract — распаковать архив - POST /api/concat — конкатенация (attachment) - POST /api/download_url — скачать из URL (attachment) - POST /api/upload_url — отправить файл на внешний URL - POST /api/diff — сравнение (text/binary) Web UI: - Поиск по regex (input + результаты) - Архив: создание из текущей папки + распаковка - Download URL: скачивание по URL - Diff: выбор 2 файлов через чекбоксы, unified diff / SHA256 |
||
| cmd/mcp-fs | ||
| docs | ||
| internal | ||
| .gitignore | ||
| .woodpecker.yml | ||
| config.yml.example | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| README.md | ||
mcp-fs
MCP-сервер для работы с файловой системой (filesystem) на Go. Работает в двух режимах:
- stdio — классический локальный MCP-сервер на stdin/stdout;
- http — многопользовательский streamable HTTP (MCP) + REST API + встроенный Web UI для управления файлами.
Аутентификация в HTTP-режиме — по JWT (HS256), который выдаёт внешний провайдер (например, OpenWebUI). Идентификатор пользователя берётся из настраиваемого claim и маппится на директорию.
Возможности
- Режимы:
stdio/http(streamable HTTP). - Логирование в JSONL через
log/slog: файл или консоль; в stdio —stderr, в http —stdout; уровниerror|warn|info|debug|off. - JWT (HS256): настраиваемый
claim, секрет, режимreject/skip. - Привязка идентификатора пользователя к директории:
- нет маппинга → одна общая директория для всех;
- известный id → своя директория;
- аноним/неизвестный id → общая «анонимная» директория.
- 17 MCP-инструментов: чтение/запись/поиск/управление файлами, архивы (create/extract), конкатенация, загрузка/отправка URL, diff (текст/бинарник).
- REST API + Web UI (загрузка, скачивание, правка текста, переименование, удаление, каталоги) с теми же правами доступа, что и MCP.
- Защита от выхода за пределы директории пользователя (включая симлинки).
Сборка
Требуется Go 1.27+.
go build -o mcp-fs ./cmd/mcp-fs
# или с версионированием
go build -ldflags="-s -w \
-X git.ymnuktech.ru/ymnuk/mcp-fs/internal/version.Version=1.0.0 \
-X git.ymnuktech.ru/ymnuk/mcp-fs/internal/version.Commit=$(git rev-parse --short HEAD) \
-X git.ymnuktech.ru/ymnuk/mcp-fs/internal/version.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-o mcp-fs ./cmd/mcp-fs
Быстрый старт
Локально (stdio)
./mcp-fs --mode stdio --storage-root ./data
Подключение через любой MCP-клиент, поддерживающий stdio. В stdio-режиме JWT не используется — файлы размещаются в общей/анонимной директории.
Многопользовательский HTTP
cp config.yml.example config.yml
# задайте jwt.secret и storage.*
./mcp-fs --config config.yml
- MCP-эндпоинт:
POST /mcp(stdlib MCP-клиентам указать URLhttp://host:8091/mcp) - Web UI:
http://host:8091/ - REST API:
http://host:8091/api/...
HTTP-эндпоинты принимают заголовок Authorization: Bearer <jwt> (или
jwt.header), но токен не обязателен: без него запрос идёт как анонимный.
В режиме unauthorized: reject переданный невалидный токен отклоняется с
401; в skip подписанный/неподписанный токен доверяется без проверки подписи.
Конфигурация
Парсинг — библиотека go-simple-args. Приоритет: CLI > env > config.yml > defaults. Путь к файлу: --config config.yml или CONFIG_FILE=....
| Раздел | Ключ | CLI / env | Default | Описание |
|---|---|---|---|---|
mode |
--mode / MODE |
stdio |
stdio или http |
|
log |
file |
--log-file / LOG_FILE |
пусто | путь JSONL-лога; пусто = консоль |
log |
level |
--log-level / LOG_LEVEL |
info |
error|warn|info|debug|off |
http |
addr |
--http-addr / HTTP_ADDR |
:8091 |
адрес прослушивания |
http |
mcp_path |
--http-mcp-path / HTTP_MCP_PATH |
/mcp |
путь MCP-эндпоинта |
http |
max_upload_bytes |
--http-max-upload-bytes / HTTP_MAX_UPLOAD_BYTES |
104857600 |
лимит upload (100 MiB) |
http |
max_text_file_size |
--http-max-text-file-size / HTTP_MAX_TEXT_FILE_SIZE |
10485760 |
лимит текста (10 MiB) |
mcp |
default_lines |
--mcp-default-lines / MCP_DEFAULT_LINES |
0 |
строк по умолчанию для read_file (0 = все) |
jwt |
secret |
--jwt-secret / JWT_SECRET |
пусто | HS256 секрет |
jwt |
claim |
--jwt-claim / JWT_CLAIM |
sub |
поле JWT — id пользователя |
jwt |
header |
--jwt-header / JWT_HEADER |
Authorization |
заголовок с токеном |
jwt |
unauthorized |
--jwt-unauthorized / JWT_UNAUTHORIZED |
reject |
reject = проверять подпись переданного токена (401 если невалиден); skip = доверять claim без проверки. Без токена всегда аноним (см. storage.anonymous_enabled) |
storage |
root |
--storage-root / STORAGE_ROOT |
./data |
общая директория |
storage |
anonymous |
--storage-anonymous / STORAGE_ANONYMOUS |
пусто | директория анонимов |
storage |
anonymous_enabled |
--storage-anonymous-enabled / STORAGE_ANONYMOUS_ENABLED |
true |
разрешить анонимов (нет токена / ненамапленный id); false = блокировать |
storage |
users |
— (только YAML) | — | map id -> директория |
archive |
max_input_bytes |
--archive-max-input-bytes / ARCHIVE_MAX_INPUT_BYTES |
5368709120 |
лимит входа archive_create (5 GiB) |
archive |
max_uncompressed_bytes |
--archive-max-uncompressed-bytes / ARCHIVE_MAX_UNCOMPRESSED_BYTES |
10737418240 |
лимит распаковки (10 GiB) |
archive |
max_files |
--archive-max-files / ARCHIVE_MAX_FILES |
10000 |
макс. файлов в архиве |
archive |
timeout_seconds |
--archive-timeout-seconds / ARCHIVE_TIMEOUT_SECONDS |
300 |
таймаут операции архивации |
archive |
compression_level |
--archive-compression-level / ARCHIVE_COMPRESSION_LEVEL |
6 |
уровень сжатия (1-9) |
concat |
max_result_bytes |
--concat-max-result-bytes / CONCAT_MAX_RESULT_BYTES |
10737418240 |
лимит результата конкатенации (10 GiB) |
download |
max_bytes |
--download-max-bytes / DOWNLOAD_MAX_BYTES |
10737418240 |
лимит загрузки (10 GiB) |
download |
timeout_seconds |
--download-timeout-seconds / DOWNLOAD_TIMEOUT_SECONDS |
600 |
таймаут загрузки |
download |
block_private_ips |
--download-block-private-ips / DOWNLOAD_BLOCK_PRIVATE_IPS |
true |
блокировать private/loopback/link-local/multicast |
download |
allowed_domains |
— (только YAML) | пусто | allowlist доменов (обходит проверку private) |
download |
user_agent |
--download-user-agent / DOWNLOAD_USER_AGENT |
mcp-fs-downloader/1.0 |
User-Agent на загрузке |
upload |
max_bytes |
--upload-max-bytes / UPLOAD_MAX_BYTES |
10737418240 |
лимит отправки (10 GiB) |
upload |
timeout_seconds |
--upload-timeout-seconds / UPLOAD_TIMEOUT_SECONDS |
300 |
таймаут отправки |
upload |
allowed_target_hosts |
— (только YAML) | пусто | allowlist целевых хостов |
diff |
max_text_size |
--diff-max-text-size / DIFF_MAX_TEXT_SIZE |
104857600 |
лимит текста для diff (100 MiB) |
diff |
max_binary_size |
--diff-max-binary-size / DIFF_MAX_BINARY_SIZE |
1073741824 |
лимит бинарника (1 GiB) |
Полный пример — config.yml.example.
JWT из OpenWebUI
- Войдите в OpenWebUI как нужный пользователь.
- Извлеките JWT (например, из localStorage браузера: ключ
token/id_token, или из cookie). - Укажите в
config.ymlтот же секрет, которым OpenWebUI подписывает токены, и claim, который соответствует id пользователя (обычноsub). - В Web UI вставьте токен один раз — он сохранится в
localStorage(mcp_fs_jwt). При истечении (ответ401) токен удаляется автоматически, и UI запросит его заново.
REST API
Все маршруты — под JWT-аутентификацией, ошибки — {"error": "..."}.
| Метод | Путь | Описание |
|---|---|---|
| GET | /api/root |
текущий root и identity |
| GET | /api/list?path= |
список каталога |
| GET | /api/tree?path= |
рекурсивное дерево |
| GET | /api/file?path= |
текстовый контент файла |
| POST | /api/file |
запись текстового файла {path, content} |
| GET | /api/download?path= |
скачивание файла |
| POST | /api/upload?path= |
multipart-загрузка файлов |
| POST | /api/dir |
создать каталог {path} |
| POST | /api/move |
переименовать/переместить {from, to} |
| DELETE | /api/file?path= |
удалить файл |
| DELETE | /api/dir?path= |
удалить каталог (рекурсивно) |
| POST | /api/edit |
последовательные текстовые замены {path, edits[]} |
| GET | /api/search?path=&pattern=&max_depth= |
regex-поиск имён файлов |
| GET | /api/info?path= |
метаданные файла/каталога |
| POST | /api/archive/create |
создать архив, вернуть как файл {archive, sources[], format, …} |
| POST | /api/archive/extract |
распаковать архив из storage {archive, destination, …} |
| POST | /api/concat |
конкатенация файлов, вернуть результат как файл {sources[], destination, …} |
| POST | /api/download_url |
скачать из URL, вернуть как файл {url, destination, …} |
| POST | /api/upload_url |
отправить файл из storage на внешний URL {source, url, …} |
| POST | /api/diff |
сравнение двух файлов {file1, file2, mode} → JSON |
MCP-инструменты
list_directory, directory_tree, read_file, write_file, edit_file,
create_directory, move_file, delete_file, delete_directory,
search_files, get_file_info,
archive_create, archive_extract, concat_files, download_url,
upload_url, diff_files.
read_fileопределяет тип по содержимому/MIME: текст (txt, md, csv, json, yaml, …) возвращается строкой, изображения —ImageContent, аудио —AudioContent, остальные бинарные — embedded-ресурсом. Для текстовых файлов доступно чтение по частям:offset(1-based номер первой строки) иlimit(макс. число строк отoffset; если не задан — берётсяmcp.default_linesиз конфига,0= все строки).delete_directoryудаляет только пустой каталог; для удаления непустого каталога (рекурсивно) передавайтеrecursive: true.archive_create/archive_extract— архивыtar, tar.gz, tar.xz, tar.br, zip, gz(формат по расширению илиformat). Экстракция защищена от path-escape, симлинков/хардлинков и zip-bomb (лимиты файлов/размера).concat_files— конкатенация файлов (стриминг, опциональныйseparator).download_url/upload_url— загрузка из внешнего http(s) и отправка файла на внешний http(s) (POST/PUT). SSRF-защита (блокировка private/loopback/link-local/multicast) + лимиты размера/таймаута. Серверный JWT во внешние сервисы не передаётся.diff_files— сравнение двух файлов:mode=text(unified diff, по умолчанию) илиmode=binary(SHA256-хэши).
Пути в инструментах указываются внутри директории пользователя, например
/docs/note.txt (не абсолютные пути хоста).
Docker
docker build -t mcp-fs .
docker run -p 8091:8091 -v "$PWD/data:/app/data" mcp-fs --config /app/config.yml
CI
Woodpecker-пайплайн см. в .woodpecker.yml: lint/test, сборка бинарника, Docker-образ, release.
Документация
- ROADMAP.md — план с декомпозицией и отметками выполнения.
- security.md — модель защиты путей, остаточные риски.
- config.yml.example — пример конфигурации.
Безопасность
- Каждый запрос (в т.ч. каждый MCP-вызов в stateless-режиме) проверяется на валидность JWT.
- Все пути проверяются на выход за root двухуровнево: лексически
(
filepath.Rel, запрет../абсолютных путей) и черезEvalSymlinks(симлинки наружу блокируются даже при создании новых файлов). Подробно — docs/security.md. - Не выставляйте сервер без
jwt.secretв открытый интернет.