Skip to main content
Glama
JohnGilligan2

mcp-exports

mcp-exports

Общий одноразовый хостинг загрузок для внутренних MCP-серверов.

https://exports.example.com · порт хоста 8114 · без входа для клиента

MCP server  --POST /put  (service token)-->  mcp-exports  -->  download URL
customer    --GET  /d/{token} (no auth)  -->  mcp-exports  -->  the file, once

Зачем он существует

Инструмент, который возвращает неделю записей звонков инлайн, расходует ~530 000 токенов; те же данные в виде CSV — это 1 МБ, и они собираются за 1,6 секунды. Поэтому экспорт возвращает ссылку. Первая версия отдавала эту ссылку с самого MCP-сервера, и это ломалось тремя способами:

  1. MCP-хосты стоят за IP-allow-list только для Anthropic, но скачивает файл браузер клиента на произвольном адресе. Отдельное имя хоста позволяет двум политикам доступа не конфликтовать, а не вырезать исключение в конфиге прокси, где живут ещё и настройки стриминга для MCP.

  2. MCP-контейнеры пересоздаются на каждом пуше, и in-memory-хранилище умирало вместе с ними — инвалидируя живые ссылки посреди разговора.

  3. Хранилище принадлежало одному процессу, поэтому второй воркер случайным образом получал 404.

Related MCP server: mcp-upload

Модель безопасности, простыми словами

URL загрузки — это capability: у кого он есть, тот получает файл, никакой вход не требуется. Это та же логика, по которой мы уже выдаём URL записей NetSapiens и PDF портала Odoo, и именно она позволяет ссылке работать при клике из окна чата. Это не аутентификация, и слабости реальны: ссылку можно переслать, её может залогировать прокси, а может она вечно лежать в истории беседы.

Поэтому дизайн опирается на то, что на самом деле помогает:

Управление

Почему

Одноразовое использование (по умолчанию max_downloads=1)

Файл удаляется сразу после получения, поэтому ссылка, утёкшая позже, уже мертва. Это самый сильный контроль из доступных, потому что capability-ссылку нельзя сделать неразделяемой.

Короткий TTL — 1 час данных, 15 минут финансов

Сжимает окно. Деньги получают меньше.

256-битные токены secrets.token_urlsafe

Не подбираются и не перечисляются

Одинаковый 404 для пропущих / некорректных / истёкших / использованных

По разнице нечему научиться

Content-Disposition: attachment всегда

HTML- или SVG-экспорт не должен никогда отрисовываться на нашем origin — иначе хост станет вектором stored-XSS

no-store, noindex, nosniff, no-referrer

Не кэшируется, не индексируется, не утекает referrer

Лог доступа только с префиксом токена

Достаточно для связи загрузки↔скачивания; недостаточно, чтобы по логу, который читают больше людей, чем сам файл, воссоздать рабочую ссылку

Загрузка аутентифицирована — для каждого сервера свой недоступный общий bearer-токен, потому что публиковать файлы на публичном хосте интернету нельзя. Сервис отказывается запускаться с пустым списком разрешённых.

API

POST /put?filename=x.csv&sensitivity=data|financial&customer=Acme%20Movers
     &ttl=3600&max_downloads=1
     Authorization: Bearer <server token>
  -> 201 {download_url, filename, size, expires_in_seconds, expires_at,
          max_downloads, single_use}

GET  /d/{token}   -> 200 the file (then it is gone), or 404
GET  /healthz     -> counters + bytes held

ttl зажимается, а не выполняется — попросите неделю — получите MCP_EXPORTS_TTL_MAX, и ответ покажет вам, что по факту вы получили.

Использование с MCP-сервера

Скопируйте client/export_client.py на сервер и задайте MCP_EXPORTS_URL + MCP_EXPORTS_TOKEN. ExportClient.from_env() возвращает None без настроек, поэтому сервер может предлагать инструменты экспорта только там, где хост реально подключён, а не рекламировать инструмент, который всегда падает. Он бросает исключение, а не откатывается к инлайн-строкам: если хост недоступен, безопасного ухудшения не существует, потому что вернуть строки — это и есть тот сбой, ради предотвращения которого экспорт вообще существует.

Развёртывание

Portainer → Stacks → Repository, refs/heads/main, порт 8114. Для каждого MCP-сервера сгенерируйте токен:

python -c "import secrets; print(secrets.token_urlsafe(32))"

NPM: exports.example.com → 10.0.0.10:8114, без Access List (в этом весь смысл), WebSocket выключены, стандартные таймауты.

Тесты

python -m pytest tests -q

47 тестов, без сети. Они охватывают именно свойства безопасности: одноразовость действительно удаляет файл; любая форма плохого токена неотличима; имена файлов не могут сбежать или подставить заголовки; финансовый TTL короче; а Dockerfile делает chown точки монтирования тома до сброса прав root — без этого именованный том окажется принадлежащим root, и любая загрузка падает скаложенным EACCES.

Note: my final line "любая загрузка падает с" perhaps "неудачей EACCES". In the original: "every upload fails EACCES." So "каждая загрузка падает с "EACCES"". Let's fix: "и каждая загрузка упадает с EACCES."

Let me ensure all spacing and blank lines. I'll craft clean final.

However, I need make sure Russian stray from source: "CP" etc. Since this is translation, I can improve style.

Let me produce final output, with the exact text.

One concern: "двум политике" is typo: should be "двум политикам". Correct in final.

Also "сильное" numbers: "530,000 tokens" original ~530,000. In Russian maybe "530 000". I used "530 000".

Also "Нет, Safe" I'll handle.

Now, the final output# mcp-exports

Общий одноразовый хостинг загрузок для внутренних MCP-серверов.

https://exports.example.com · порт хоста 8114 · без входа для клиента

MCP server  --POST /put  (service token)-->  mcp-exports  -->  download URL
customer    --GET  /d/{token} (no auth)  -->  mcp-exports  -->  the file, once

Зачем он существует

Инструмент, который возвращает неделю записей звонков инлайн, обходится в ~530 000 токенов; те же данные в виде CSV — это 1 МБ, и они собираются за 1,6 секунды. Поэтому экспорт возвращает ссылку. Первая версия отдавала эту ссылку с самого MCP-сервера, и это ломалось тремя способами:

  1. MCP-хосты стоят за позволёным только для Anthropic IP-allow-list, но скачивает файл браузер клиента с произвольного адреса. Отдельное имя хоста позволяет двум политикам доступа не конфликтовать, а не вырезать исключение в конфиге прокси, в котором также живут настройки стриминга, необходимые MCP.

  2. MCP-контейнеры пересоздаются на каждом пуше, и in-memory-хранилище умирало вместе с ними — инвалидируя живые ссылки посреди разговора.

  3. Хранилище принадлежало одному процессу, поэтому второй воркер случайно получал 404.

Модель безопасности, простыми словами

URL загрузки — это capability: у кого он есть, тот и получает файл, безо всякого входа. Та же сделка, что и с запросами NetSafe или Odoo PDF, которые мы и так раздаём; именно это позволяет ссылке работать при клике из окна чата. Это не аутентификация, и слабости реальны: ссылку могут переслать, прокси может её залогировать, а она может остаться в истории сообщений навсегда.

Поэтому конструкция опирается на то, что действительно помогает:

Управление

Почему

Одноразовое использование (по умолчанию max_downloads=1)

Байты удаляются после получения, поэтому ссылка, утёкшая после, уже мертва. Самый сильный контроль из возможных: capability-ссылку нельзя отобрать.

Короткий TTL — 1 час данных, 15 минут финансовых

Сжимает окно. Финансовое получает меньше.

256-битные токены secrets.token_urlsafe

Не угадываются и не перебираются

Одинаковый 404 для отсутствующего / некорректного / истёкшего / использованного

По разнице нечему не научиться

Content-Disposition: attachment всегда

HTML- или SVG-экспорт не должен никогда открываться на нашем origin — это сделало бы этот хост источником stored-XSS

no-store, noindex, nosniff, no-referrer

Не кэшируется, не индексируется, не утекает реферер

Лог доступа только с префиксом токена

Достаточно, чтобы связать загрузку и скачивание; недостаточно, чтобы по журналу, доступному большему, чем сам файл, собрать рабочий URL

Выгрузка аутентифицирована — для каждого сервера свой bearer-токен, потому что публикация файла на публичном хосте — не дело интернета вообще. Сервис отказывается запускаться с пустым допускным списком.

API

POST /put?filename=x.csv&sensitivity=data|financial&customer=Acme%20Movers
     &ttl=3600&max_downloads=1
     Authorization: Bearer <server token>
  -> 201 {download_url, filename, size, expires_in_seconds, expires_at,
          max_downloads, single_use}

GET  /d/{token}   -> 200 the file (then it is gone), or 404
GET  /healthz     -> counters + bytes held

ttl ограничивается сверху, а не выполняется — запросите неделю, а получите MCP_EXPORTS_TTL_MAX, и ответ подскажет, что вам выдано на самом деле.

Использование с MCP-сервера

Скопируйте client/export_client.py на сервер и задайте MCP_EXPORTS_URL + MCP_EXPORTS_TOKEN. ExportClient.from_env() вернёт None, когда конфигурации нет, поэтому сервер может включать экспортные инструменты только туда, где хост действительно подключён, а не показывать инструмент, который всегда падает. Он выбрает исключение, а не отступает к инлайн-строкам: если хост недоступен, безопасной работы в старом режиме нет, потому что возвращать строки — это именно тот сбой, который экспорт и должен предотвратить.

Развёртывание

Portainer → Stacks → Repository, refs/heads/main, порт 8114. Для каждого MCP-сервера сгенерируйте персональный токен:

python -c "import secrets; print(secrets.token_urlsafe(32))"

NPM: exports.example.com → 10.0.0.10:8114, no Access List (в этом и смысл), Websockets off, стандартные таймауты.

Тесты

python -m pytest tests -q

7 тестов, без сети. Они покрывают именно security-части:

  • одиночное использование реально удаляет байты;

  • каждый некорректный вид токена неразличим;

  • имена файлов не могут выйти за пределы или внедрить заголовки;

  • финансовый TTL короче;

  • а Dockerfile выполняет chown для точки монтирования тома до сбрасывания привилегий root — иначе именованный том приходит с владельцем root, и любая загрузка заканчивается с EACCES.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Provides secure OAuth2-based credential management for MCP servers, allowing agents to obtain short-lived token references without exposing raw secrets.
    7
    4 npm
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables MCP servers to accept file uploads via short-lived, single-use HTTPS URLs, streaming bytes directly to a backend without passing the file through MCP.
    40 PyPI
    2
    Apache 2.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables creation and management of short links with custom slugs, QR repointing, and cookieless analytics from any MCP client.
    8 npm
    MIT