No description
  • Go 87.6%
  • HTML 11.9%
  • Dockerfile 0.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Ymnuk 787fb6464f
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
feat: REST API — 9 эндпоинтов Data Gateway + Web UI (поиск, архив, URL, diff)
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
2026-09-02 21:23:35 +03:00
cmd/mcp-fs feat: REST API — 9 эндпоинтов Data Gateway + Web UI (поиск, архив, URL, diff) 2026-09-02 21:23:35 +03:00
docs feat: REST API — 9 эндпоинтов Data Gateway + Web UI (поиск, архив, URL, diff) 2026-09-02 21:23:35 +03:00
internal feat: REST API — 9 эндпоинтов Data Gateway + Web UI (поиск, архив, URL, diff) 2026-09-02 21:23:35 +03:00
.gitignore update: Обновление версии Go 2026-09-01 21:55:17 +03:00
.woodpecker.yml update: Обновление версии Go 2026-09-01 21:55:17 +03:00
config.yml.example fix: семантика JWT-режима reject (пустой токен = аноним) 2026-09-02 11:14:30 +03:00
Dockerfile update: Обновление версии Go 2026-09-01 21:55:17 +03:00
go.mod feat: Реализация новых функций 2026-09-02 08:52:36 +03:00
go.sum feat: Реализация новых функций 2026-09-02 08:52:36 +03:00
LICENSE first commit 2026-08-12 11:34:19 +03:00
README.md feat: REST API — 9 эндпоинтов Data Gateway + Web UI (поиск, архив, URL, diff) 2026-09-02 21:23:35 +03:00

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-клиентам указать URL http://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

  1. Войдите в OpenWebUI как нужный пользователь.
  2. Извлеките JWT (например, из localStorage браузера: ключ token / id_token, или из cookie).
  3. Укажите в config.yml тот же секрет, которым OpenWebUI подписывает токены, и claim, который соответствует id пользователя (обычно sub).
  4. В 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 в открытый интернет.