sgnl-mcp — MCP-сервер для SIGNAL Integration API
MCP-сервер, через который LLM-агент (Claude и др.) работает с документацией в SIGNAL: смотрит проекты и папки, ищет и скачивает документы, загружает новые документы и версии, работает с замечаниями, передачами и рассмотрениями.
Агент остаётся под контролем: чтение без ограничений, запись — только после предпросмотра и согласия пользователя, отправка передач и запуск рассмотрений — только после подтверждения пользователем в диалоге. Удалять, перемещать и переименовывать документы и папки, а также менять права агент не может вообще.
Построен на sgnl-api — обёртке над API из этого же репозитория.
Как обеспечивается контроль
| Рубеж | Что делает |
|---|---|
| 1. Scopes токена | Сервер запрашивает у SIGNAL только права своего профиля. В профиле read в токене нет ни одного права на запись — SIGNAL сам откажет. Права *:delete не запрашиваются никогда. |
| 2. Профиль | Инструменты выше профиля не регистрируются: агент их не видит. |
| 3. Белый список запросов | Каждый HTTP-запрос проверяется до отправки: только разрешённые метод и путь, никаких DELETE, никакого поля deleted в запросах на запись, внешние хосты — только для загрузки файла. |
| 4. Подтверждение | Запись — в два вызова: предпросмотр с токеном, затем выполнение с тем же токеном. Отправка передачи и запуск рассмотрения — через диалог подтверждения у пользователя (MCP elicitation). |
| 5. Клиент и аудит | Инструменты помечены readOnlyHint: клиент спрашивает разрешение на запись. Все записи и отказы пишутся в JSONL-журнал без секретов. |
Нагрузка на SIGNAL ограничена: по умолчанию не больше 3 запросов в секунду и 4 параллельных на весь сервер, у каждого вызова инструмента свой бюджет запросов, справочники кэшируются на 5 минут.
Установка
uvx sgnl-mcp # запуск без установки
# или
pip install sgnl-mcp
Нужен Python 3.11+.
Настройка
Вся настройка — через переменные окружения (или файл .env в рабочем каталоге). Агент не может их изменить.
| Переменная | Обязательна | По умолчанию | Назначение |
|---|---|---|---|
SGNL_CLIENT_ID |
да | — | Client ID интеграции SIGNAL |
SGNL_CLIENT_SECRET |
да | — | Client secret интеграции SIGNAL |
SGNL_MCP_PROFILE |
нет | read |
read, write или workflow |
SGNL_MCP_ACTOR_USER_ID |
для write/workflow |
— | ID пользователя SIGNAL, от имени которого делаются изменения (лучше служебная учётная запись «AI-агент») |
SGNL_MCP_UPLOAD_ROOT |
для write/workflow |
— | Каталог, из которого разрешено загружать файлы. Файлы вне него, в том числе через символические ссылки, недоступны |
SGNL_MCP_ALLOWED_EXTENSIONS |
нет | .pdf,.dwg,.dxf,… |
Разрешённые типы загружаемых файлов, через запятую |
SGNL_MCP_AUTO_CONFIRM |
нет | — | Write-инструменты, которые выполняются без предпросмотра (только для доверенных сценариев). Workflow сюда не входит |
SGNL_MCP_RATE_PER_SEC |
нет | 3 |
Запросов в секунду (не больше 10) |
SGNL_MCP_BURST |
нет | 6 |
Запас запросов для коротких всплесков |
SGNL_MCP_MAX_CONCURRENCY |
нет | 4 |
Параллельных запросов к SIGNAL |
SGNL_MCP_TOOL_TIMEOUT |
нет | 30 |
Таймаут вызова инструмента, с |
SGNL_MCP_UPLOAD_TIMEOUT |
нет | 600 |
Таймаут загрузки файла, с |
SGNL_MCP_CONFIRMATION_TTL_SEC |
нет | 300 |
Срок действия токена подтверждения |
SGNL_MCP_AUDIT_PATH |
нет | ~/.sgnl-mcp/audit.jsonl |
Журнал аудита |
SGNL_MCP_STATE_PATH |
нет | ~/.sgnl-mcp/state.sqlite |
Журнал выполненных операций (защита от повторов) |
Пользователю нужен только ключ (SGNL_CLIENT_ID и SGNL_CLIENT_SECRET), scope он не задаёт: сервер сам запрашивает права своего профиля.
Если в интеграции включены не все scope, сервер всё равно запускается, но без недостающих:
- операции, которым они нужны, отвечают ошибкой
SCOPE_MISSINGс готовым текстом для администратора, например: «У вашего ключа SIGNAL ограниченные права… Передайте администратору SIGNAL: „В интеграции (client_id …) включите scope: docs:issue:list“»; - запрос в SIGNAL при этом не отправляется;
- инструмент
get_access_statusзаранее показывает, чего не хватает; - то же сообщение сервер пишет в свой журнал при запуске.
Сервер не запускается, только если ключ неверный или в интеграции нет ни одного scope профиля. Для write и workflow при запуске проверяется, что пользователь SGNL_MCP_ACTOR_USER_ID есть в компании. Если для этой проверки нет company:user:list, запись недоступна, а чтение работает.
Права интеграции по профилям
Это раздел для администратора SIGNAL. Включите в интеграции эти scope, чтобы были доступны все инструменты профиля:
| Профиль | Scopes интеграции в SIGNAL |
|---|---|
read |
project:list, project:read, docs:folder:read, docs:item:list, docs:item:read, docs:version:list, docs:docs_transfer:list, docs:docs_transfer:read, docs:issue:list, docs:review:list, docs:signing:list, docs:attribute:list |
write |
всё из read + company:user:list, docs:object:create, docs:version:create, docs:version:update, docs:item:create, docs:docs_transfer:create, docs:docs_transfer:update, docs:issue:update, docs:review:create, docs:review:update |
workflow |
то же, что write |
Список составляется автоматически из запросов, которые разрешены профилю. Тест сверяет его со спецификацией SIGNAL, поэтому лишнего сервер не запросит. Обратите внимание: нужен docs:issue:list, а не docs:issue:read.
Отправка передачи и запуск рассмотрения в SIGNAL используют те же права, что и их наполнение. Поэтому их отделяет сам MCP-сервер: в профиле write этих инструментов нет, а в workflow они всегда требуют подтверждения пользователя.
Подключение к Claude
Claude Desktop — claude_desktop_config.json:
{
"mcpServers": {
"sgnl": {
"command": "uvx",
"args": ["sgnl-mcp"],
"env": {
"SGNL_CLIENT_ID": "…",
"SGNL_CLIENT_SECRET": "…",
"SGNL_MCP_PROFILE": "read"
}
}
}
}
Claude Code:
claude mcp add sgnl -e SGNL_CLIENT_ID=… -e SGNL_CLIENT_SECRET=… -e SGNL_MCP_PROFILE=read -- uvx sgnl-mcp
Для профиля write добавьте SGNL_MCP_ACTOR_USER_ID и SGNL_MCP_UPLOAD_ROOT.
Запуск в Docker
Готовый образ для linux/amd64 и linux/arm64 (в том числе Mac на Apple Silicon) публикуется в GitHub Container Registry:
docker pull ghcr.io/gritsyuk/sgnl-mcp:latest # или конкретная версия, например :0.1.0
docker tag ghcr.io/gritsyuk/sgnl-mcp:latest sgnl-mcp
Для работы стоит закрепить версию (:0.1.0), а не latest: тогда образ не обновится без вашего ведома. Можно и собрать образ самостоятельно из корня репозитория (ему нужны оба пакета рабочего пространства):
docker build -f mcp/Dockerfile -t sgnl-mcp .
Сервер общается с клиентом через stdin/stdout, поэтому контейнер запускается с -i, а клиент сам стартует его на каждую сессию. Секреты удобно держать в env-файле, а не в конфигурации клиента. Например, ~/.sgnl-mcp.env:
SGNL_CLIENT_ID=…
SGNL_CLIENT_SECRET=…
SGNL_MCP_PROFILE=read
Claude Desktop — claude_desktop_config.json (пути — абсолютные):
{
"mcpServers": {
"sgnl": {
"command": "docker",
"args": [
"run", "-i", "--rm", "--init",
"--read-only", "--tmpfs", "/tmp",
"--cap-drop", "ALL", "--security-opt", "no-new-privileges",
"--env-file", "/Users/me/.sgnl-mcp.env",
"-v", "sgnl-mcp-data:/data",
"sgnl-mcp"
]
}
}
}
Claude Code:
claude mcp add sgnl -- docker run -i --rm --init --read-only --tmpfs /tmp --cap-drop ALL \
--security-opt no-new-privileges --env-file ~/.sgnl-mcp.env -v sgnl-mcp-data:/data sgnl-mcp
Windows. В конфигурации Claude Desktop (%APPDATA%\Claude\claude_desktop_config.json) обратные слеши в путях удваиваются: "--env-file", "C:\\Users\\Admin\\.sgnl-mcp.env", а папка для загрузки подключается как "-v", "C:\\SIGNAL\\outgoing:/uploads:ro". В командной строке cmd вместо /dev/null используется NUL. Проверить, что образ запускается:
docker run -i --rm --init ghcr.io/gritsyuk/sgnl-mcp:0.1.0 < NUL
Без учётных данных сервер сразу завершится с ошибкой проверки настроек (не заданы SGNL_CLIENT_ID / SGNL_CLIENT_SECRET) — значит, образ рабочий.
Что внутри образа:
- Процесс работает от непривилегированного пользователя (uid 10001), зависимости зафиксированы
uv.lock. /data— том для журнала аудита (audit.jsonl) и журнала выполненных операций (state.sqlite). Именованный томsgnl-mcp-dataсохраняет их между запусками; без него защита от повторов и аудит теряются при каждом перезапуске./uploads— каталог загрузки (SGNL_MCP_UPLOAD_ROOTуже указывает на него). Для профилейwrite/workflowсмонтируйте туда папку с файлами только на чтение:-v /path/to/outgoing:/uploads:ro. Тогда агент физически не может ни изменить файлы на компьютере, ни прочитать что-то за пределами этой папки.- Файл
.envв образ не попадает (.dockerignoreпропускает только исходники пакетов).
Флаги --read-only, --cap-drop ALL и no-new-privileges необязательны, но сервер с ними работает, и это ещё один рубеж: контейнер не может ничего записать, кроме /data и /tmp.
Разрешения в клиенте
- Инструменты чтения можно разрешить без вопросов.
- Инструменты записи и Workflow не добавляйте в «разрешить всегда»: клиент должен спрашивать перед каждым вызовом. Это один из рубежей контроля.
- Для профиля
workflowклиент должен поддерживать MCP elicitation (диалог подтверждения). Без неё отправка передач и запуск рассмотрений завершаются ошибкой и ничего не делают.
Инструменты
| Инструмент | Профиль | Что делает |
|---|---|---|
get_access_status |
read | Что доступно ключу: профиль, недостающие scope и готовый текст для администратора SIGNAL (без запросов к SIGNAL) |
list_projects |
read | Проекты, доступные интеграции |
get_project |
read | Данные проекта и его корневая папка |
get_project_folder_tree |
read | Дерево папок с правами пользователя |
search_documents |
read | Поиск документов по проекту или папке (имя, расширение, автор, даты) |
list_folder_documents |
read | Документы одной папки (без вложенных) |
list_document_versions |
read | История версий документа |
get_document_version |
read | Связь «документ — версия файла» по item_version_id |
get_document_download_url |
read | Временная ссылка на скачивание версии (сам файл не передаётся) |
get_document_context |
read | Всё о документе за один вызов: версии, атрибуты, замечания, рассмотрения, подписи; у каждого блока свой статус |
list_document_transfers |
read | Передачи проекта с фильтрами |
get_document_transfer |
read | Полные данные передачи |
list_transfer_documents |
read | Документы в передаче |
list_document_transfer_types |
read | Типы передач проекта |
list_issues |
read | Замечания с фильтрами |
list_reviews |
read | Рассмотрения проекта |
get_review_details |
read | Версии рассмотрения и документы в них |
get_document_review_status |
read | Статус рассмотрения версий документов |
list_review_types |
read | Типы рассмотрений проекта |
upload_document |
write | Новый документ из файла |
add_document_version |
write | Новая версия существующего документа |
update_document_version_comment |
write | Комментарий версии |
create_document_transfer |
write | Новая передача (черновик) |
add_documents_to_transfer |
write | Добавить версии документов в неотправленную передачу |
update_issues |
write | Изменить 1–25 замечаний |
create_review |
write | Новое рассмотрение (черновик) |
add_documents_to_review |
write | Добавить до 20 версий документов в рассмотрение |
prepare_send_document_transfer |
workflow | Проверить передачу перед отправкой и получить токен |
send_document_transfer |
workflow | Отправить передачу (подтверждение пользователем в диалоге) |
prepare_start_review |
workflow | Проверить рассмотрение перед запуском и получить токен |
start_review |
workflow | Запустить рассмотрение (подтверждение пользователем в диалоге) |
Как проходит запись
- Агент вызывает инструмент без
confirmation_token. Сервер проверяет объекты и возвращает предпросмотр («было → станет», путь папки, состав передачи и т.п.) и токен. В SIGNAL ничего не меняется. - Агент показывает предпросмотр пользователю.
- После согласия агент вызывает инструмент с теми же аргументами и токеном. Сервер перепроверяет объекты: если с момента предпросмотра что-то изменилось (другой файл, кто-то правил замечание), токен отклоняется.
Токен одноразовый, действует 5 минут и привязан к аргументам. Повторный вызов с уже использованным токеном возвращает сохранённый результат, а не выполняет операцию второй раз.
Если запрос на запись завершился без ясного ответа (таймаут, ошибка сервера), сервер не повторяет его сам, а возвращает OUTCOME_UNKNOWN с тем, что удалось выяснить. Для загрузки файла можно завершить операцию параметром resume_version_id, не загружая файл заново.
Ограничения
- В API SIGNAL нет поиска передач по документу, поэтому блок
transfersвget_document_contextвсегдаnot_supported. - Создание замечаний, удаление, перемещение, переименование, изменение прав и пользователей в MCP-сервер намеренно не включены.
Ответы и ошибки
Все инструменты возвращают {"success": true, "data": …, "page": …, "warnings": […], "request_id": …} или {"success": false, "error": {"code", "message", "details"}, "request_id": …}.
| Код | Значение |
|---|---|
INVALID_REQUEST, NOT_FOUND, FORBIDDEN, AUTH_FAILED, QUOTA_EXCEEDED, RATE_LIMITED |
Ответ SIGNAL (400, 404, 403, 401, 450, 429) |
SERVER_ERROR, NETWORK_ERROR, TIMEOUT |
SIGNAL не ответил нормально; чтение можно повторить |
OUTCOME_UNKNOWN |
Запись могла пройти или не пройти; сервер её не повторял |
SCOPE_MISSING |
В интеграции ключа не включён нужный scope или у компании нет лицензии. В message — текст для администратора SIGNAL, в details.missing_scopes — список scope |
POLICY_DENIED |
Запрос запрещён политикой MCP-сервера |
CONFIRMATION_INVALID, CONFIRMATION_DECLINED |
Токен недействителен или пользователь не подтвердил |
INVALID_STATE, NOTHING_TO_DO, UPLOAD_FAILED, BUDGET_EXCEEDED |
Объект в неподходящем состоянии, изменять нечего, файл не передан, исчерпан бюджет запросов |
Metadata
Release files for sgnl-mcp 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| sgnl_mcp-0.2.0.tar.gz | 36.7 kB | Without provenance | Details for sgnl_mcp-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| sgnl_mcp-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for sgnl_mcp-0.2.0-py3-none-any.whl |
Total release size: 80.7 kB
Release files / sgnl_mcp-0.2.0.tar.gz
| Download URL | sgnl_mcp-0.2.0.tar.gz |
|---|---|
| Size | 36.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
27f3aae15d1f475a7b82471a934058bf2eeaf5061d120a55e3d7f945bec73ffe
|
|
BLAKE2b-256 checksum How to use checksums |
d08de281b3102ba29d56df9d057dfa0fc09a3bf9195d0a73bb803a13dbad78ae
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / sgnl_mcp-0.2.0-py3-none-any.whl
| Download URL | sgnl_mcp-0.2.0-py3-none-any.whl |
|---|---|
| Size | 44.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
389e27a218ac6ffb8342dbde3f5bc4f7391cc416197a4c372a0240b637057340
|
|
BLAKE2b-256 checksum How to use checksums |
8f81529cd0fc273c3f5c03c4feb06567e2b9a32f528962e7a1c7ab9c61d1f3d8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for sgnl-mcp 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.