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-сервера, и это ломалось тремя способами:
MCP-хосты стоят за IP-allow-list только для Anthropic, но скачивает файл браузер клиента на произвольном адресе. Отдельное имя хоста позволяет двум политикам доступа не конфликтовать, а не вырезать исключение в конфиге прокси, где живут ещё и настройки стриминга для MCP.
MCP-контейнеры пересоздаются на каждом пуше, и in-memory-хранилище умирало вместе с ними — инвалидируя живые ссылки посреди разговора.
Хранилище принадлежало одному процессу, поэтому второй воркер случайным образом получал 404.
Related MCP server: mcp-upload
Модель безопасности, простыми словами
URL загрузки — это capability: у кого он есть, тот получает файл, никакой вход не требуется. Это та же логика, по которой мы уже выдаём URL записей NetSapiens и PDF портала Odoo, и именно она позволяет ссылке работать при клике из окна чата. Это не аутентификация, и слабости реальны: ссылку можно переслать, её может залогировать прокси, а может она вечно лежать в истории беседы.
Поэтому дизайн опирается на то, что на самом деле помогает:
Управление | Почему |
Одноразовое использование (по умолчанию | Файл удаляется сразу после получения, поэтому ссылка, утёкшая позже, уже мертва. Это самый сильный контроль из доступных, потому что capability-ссылку нельзя сделать неразделяемой. |
Короткий TTL — 1 час данных, 15 минут финансов | Сжимает окно. Деньги получают меньше. |
256-битные токены | Не подбираются и не перечисляются |
Одинаковый 404 для пропущих / некорректных / истёкших / использованных | По разнице нечему научиться |
| HTML- или SVG-экспорт не должен никогда отрисовываться на нашем origin — иначе хост станет вектором stored-XSS |
| Не кэшируется, не индексируется, не утекает 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 heldttl зажимается, а не выполняется — попросите неделю — получите 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 -q47 тестов, без сети. Они охватывают именно свойства безопасности: одноразовость действительно удаляет файл; любая форма плохого токена неотличима; имена файлов не могут сбежать или подставить заголовки; финансовый 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-сервера, и это ломалось тремя способами:
MCP-хосты стоят за позволёным только для Anthropic IP-allow-list, но скачивает файл браузер клиента с произвольного адреса. Отдельное имя хоста позволяет двум политикам доступа не конфликтовать, а не вырезать исключение в конфиге прокси, в котором также живут настройки стриминга, необходимые MCP.
MCP-контейнеры пересоздаются на каждом пуше, и in-memory-хранилище умирало вместе с ними — инвалидируя живые ссылки посреди разговора.
Хранилище принадлежало одному процессу, поэтому второй воркер случайно получал 404.
Модель безопасности, простыми словами
URL загрузки — это capability: у кого он есть, тот и получает файл, безо всякого входа. Та же сделка, что и с запросами NetSafe или Odoo PDF, которые мы и так раздаём; именно это позволяет ссылке работать при клике из окна чата. Это не аутентификация, и слабости реальны: ссылку могут переслать, прокси может её залогировать, а она может остаться в истории сообщений навсегда.
Поэтому конструкция опирается на то, что действительно помогает:
Управление | Почему |
Одноразовое использование (по умолчанию | Байты удаляются после получения, поэтому ссылка, утёкшая после, уже мертва. Самый сильный контроль из возможных: capability-ссылку нельзя отобрать. |
Короткий TTL — 1 час данных, 15 минут финансовых | Сжимает окно. Финансовое получает меньше. |
256-битные токены | Не угадываются и не перебираются |
Одинаковый 404 для отсутствующего / некорректного / истёкшего / использованного | По разнице нечему не научиться |
| HTML- или SVG-экспорт не должен никогда открываться на нашем origin — это сделало бы этот хост источником stored-XSS |
| Не кэшируется, не индексируется, не утекает реферер |
Лог доступа только с префиксом токена | Достаточно, чтобы связать загрузку и скачивание; недостаточно, чтобы по журналу, доступному большему, чем сам файл, собрать рабочий 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 heldttl ограничивается сверху, а не выполняется — запросите неделю, а получите 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 -q7 тестов, без сети. Они покрывают именно security-части:
одиночное использование реально удаляет байты;
каждый некорректный вид токена неразличим;
имена файлов не могут выйти за пределы или внедрить заголовки;
финансовый TTL короче;
а Dockerfile выполняет
chownдля точки монтирования тома до сбрасывания привилегий root — иначе именованный том приходит с владельцем root, и любая загрузка заканчивается сEACCES.
This server cannot be deployed
Maintenance
Related MCP Connectors
An authenticated remote MCP server for user-owned devices and one-shot capability invocation.
Publish AI-made web pages and share files as short links from any MCP client.
Ephemeral, human-friendly short URLs for Tollbooth MCP services
Managed MCP gateway with OAuth, per-agent permissions, shared accounts and activity logs.
Related MCP Servers
- AlicenseAqualityDmaintenanceProvides secure OAuth2-based credential management for MCP servers, allowing agents to obtain short-lived token references without exposing raw secrets.74 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables 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 PyPI2Apache 2.0
- AlicenseNot gradedqualityCmaintenanceEnables creation and management of short links with custom slugs, QR repointing, and cookieless analytics from any MCP client.8 npmMIT
- FlicenseNot gradedqualityBmaintenanceEnables authenticated members to request restricted sessions and use a curated set of upstream tools through streamable HTTP MCP, with admin-controlled allowlists and opaque expiring tokens.-