sgnl-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,164 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ .pypirc
6
+
7
+ # C extensions
8
+ *.so
9
+
10
+ # Distribution / packaging
11
+ .Python
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ wheels/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+ *.xlsx
30
+
31
+ # PyInstaller
32
+ # Usually these files are written by a python script from a template
33
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
34
+ *.manifest
35
+ *.spec
36
+
37
+ # Installer logs
38
+ pip-log.txt
39
+ pip-delete-this-directory.txt
40
+
41
+ # Unit test / coverage reports
42
+ htmlcov/
43
+ .tox/
44
+ .nox/
45
+ .coverage
46
+ .coverage.*
47
+ .cache
48
+ nosetests.xml
49
+ coverage.xml
50
+ *.cover
51
+ *.py,cover
52
+ .hypothesis/
53
+ .pytest_cache/
54
+ cover/
55
+ test.py
56
+
57
+ # Translations
58
+ *.mo
59
+ *.pot
60
+
61
+ # Django stuff:
62
+ *.log
63
+ local_settings.py
64
+ db.sqlite3
65
+ db.sqlite3-journal
66
+
67
+ # Flask stuff:
68
+ instance/
69
+ .webassets-cache
70
+
71
+ # Scrapy stuff:
72
+ .scrapy
73
+
74
+ # Sphinx documentation
75
+ docs/_build/
76
+
77
+ # PyBuilder
78
+ .pybuilder/
79
+ target/
80
+
81
+ # Jupyter Notebook
82
+ .ipynb_checkpoints
83
+
84
+ # IPython
85
+ profile_default/
86
+ ipython_config.py
87
+
88
+ # pyenv
89
+ # For a library or package, you might want to ignore these files since the code is
90
+ # intended to run in multiple environments; otherwise, check them in:
91
+ # .python-version
92
+
93
+ # pipenv
94
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
96
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
97
+ # install all needed dependencies.
98
+ #Pipfile.lock
99
+
100
+ # poetry
101
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
102
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
103
+ # commonly ignored for libraries.
104
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
105
+ #poetry.lock
106
+
107
+ # pdm
108
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
109
+ #pdm.lock
110
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
111
+ # in version control.
112
+ # https://pdm.fming.dev/#use-with-ide
113
+ .pdm.toml
114
+
115
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
116
+ __pypackages__/
117
+
118
+ # Celery stuff
119
+ celerybeat-schedule
120
+ celerybeat.pid
121
+
122
+ # SageMath parsed files
123
+ *.sage.py
124
+
125
+ # Environments
126
+ .env
127
+ .venv
128
+ env/
129
+ venv/
130
+ ENV/
131
+ env.bak/
132
+ venv.bak/
133
+
134
+ # Spyder project settings
135
+ .spyderproject
136
+ .spyproject
137
+
138
+ # Rope project settings
139
+ .ropeproject
140
+
141
+ # mkdocs documentation
142
+ /site
143
+
144
+ # mypy
145
+ .mypy_cache/
146
+ .dmypy.json
147
+ dmypy.json
148
+
149
+ # Pyre type checker
150
+ .pyre/
151
+
152
+ # pytype static type analyzer
153
+ .pytype/
154
+
155
+ # Cython debug symbols
156
+ cython_debug/
157
+
158
+ # PyCharm
159
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
160
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
161
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
162
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
163
+ .idea/
164
+ .vscode/
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.5
2
+ Name: sgnl-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server giving an LLM agent controlled access to the SIGNAL Integration API
5
+ Project-URL: GitHub, https://github.com/gritsyuk/sgnl-api
6
+ Author-email: Igor Gritsyuk <gritsyuk.igor@gmail.com>
7
+ License-Expression: MIT
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: mcp<3,>=2.3
10
+ Requires-Dist: pydantic-settings>=2.3
11
+ Requires-Dist: pydantic>=2.7
12
+ Requires-Dist: sgnl-api>=0.4.0
13
+ Requires-Dist: tenacity>=8.2
14
+ Description-Content-Type: text/markdown
15
+
16
+ # sgnl-mcp — MCP-сервер для SIGNAL Integration API
17
+
18
+ MCP-сервер, через который LLM-агент (Claude и др.) работает с документацией в SIGNAL: смотрит проекты и папки, ищет и скачивает документы, загружает новые документы и версии, работает с замечаниями, передачами и рассмотрениями.
19
+
20
+ Агент остаётся под контролем: чтение без ограничений, запись — только после предпросмотра и согласия пользователя, отправка передач и запуск рассмотрений — только после подтверждения пользователем в диалоге. Удалять, перемещать и переименовывать документы и папки, а также менять права агент не может вообще.
21
+
22
+ Построен на [`sgnl-api`](../README.md) — обёртке над API из этого же репозитория.
23
+
24
+ ## Как обеспечивается контроль
25
+
26
+ | Рубеж | Что делает |
27
+ |---|---|
28
+ | 1. Scopes токена | Сервер запрашивает у SIGNAL только права своего профиля. В профиле `read` в токене нет ни одного права на запись — SIGNAL сам откажет. Права `*:delete` не запрашиваются никогда. |
29
+ | 2. Профиль | Инструменты выше профиля не регистрируются: агент их не видит. |
30
+ | 3. Белый список запросов | Каждый HTTP-запрос проверяется до отправки: только разрешённые метод и путь, никаких `DELETE`, никакого поля `deleted` в запросах на запись, внешние хосты — только для загрузки файла. |
31
+ | 4. Подтверждение | Запись — в два вызова: предпросмотр с токеном, затем выполнение с тем же токеном. Отправка передачи и запуск рассмотрения — через диалог подтверждения у пользователя (MCP elicitation). |
32
+ | 5. Клиент и аудит | Инструменты помечены `readOnlyHint`: клиент спрашивает разрешение на запись. Все записи и отказы пишутся в JSONL-журнал без секретов. |
33
+
34
+ Нагрузка на SIGNAL ограничена: по умолчанию не больше 3 запросов в секунду и 4 параллельных на весь сервер, у каждого вызова инструмента свой бюджет запросов, справочники кэшируются на 5 минут.
35
+
36
+ ## Установка
37
+
38
+ ```bash
39
+ uvx sgnl-mcp # запуск без установки
40
+ # или
41
+ pip install sgnl-mcp
42
+ ```
43
+
44
+ Нужен Python 3.11+.
45
+
46
+ ## Настройка
47
+
48
+ Вся настройка — через переменные окружения (или файл `.env` в рабочем каталоге). Агент не может их изменить.
49
+
50
+ | Переменная | Обязательна | По умолчанию | Назначение |
51
+ |---|---|---|---|
52
+ | `SGNL_CLIENT_ID` | да | — | Client ID интеграции SIGNAL |
53
+ | `SGNL_CLIENT_SECRET` | да | — | Client secret интеграции SIGNAL |
54
+ | `SGNL_MCP_PROFILE` | нет | `read` | `read`, `write` или `workflow` |
55
+ | `SGNL_MCP_ACTOR_USER_ID` | для `write`/`workflow` | — | ID пользователя SIGNAL, от имени которого делаются изменения (лучше служебная учётная запись «AI-агент») |
56
+ | `SGNL_MCP_UPLOAD_ROOT` | для `write`/`workflow` | — | Каталог, из которого разрешено загружать файлы. Файлы вне него, в том числе через символические ссылки, недоступны |
57
+ | `SGNL_MCP_ALLOWED_EXTENSIONS` | нет | `.pdf,.dwg,.dxf,…` | Разрешённые типы загружаемых файлов, через запятую |
58
+ | `SGNL_MCP_AUTO_CONFIRM` | нет | — | Write-инструменты, которые выполняются без предпросмотра (только для доверенных сценариев). Workflow сюда не входит |
59
+ | `SGNL_MCP_RATE_PER_SEC` | нет | `3` | Запросов в секунду (не больше 10) |
60
+ | `SGNL_MCP_BURST` | нет | `6` | Запас запросов для коротких всплесков |
61
+ | `SGNL_MCP_MAX_CONCURRENCY` | нет | `4` | Параллельных запросов к SIGNAL |
62
+ | `SGNL_MCP_TOOL_TIMEOUT` | нет | `30` | Таймаут вызова инструмента, с |
63
+ | `SGNL_MCP_UPLOAD_TIMEOUT` | нет | `600` | Таймаут загрузки файла, с |
64
+ | `SGNL_MCP_CONFIRMATION_TTL_SEC` | нет | `300` | Срок действия токена подтверждения |
65
+ | `SGNL_MCP_AUDIT_PATH` | нет | `~/.sgnl-mcp/audit.jsonl` | Журнал аудита |
66
+ | `SGNL_MCP_STATE_PATH` | нет | `~/.sgnl-mcp/state.sqlite` | Журнал выполненных операций (защита от повторов) |
67
+
68
+ При запуске сервер получает токен и проверяет, что интеграции выданы все права профиля, а для `write`/`workflow` — что пользователь `SGNL_MCP_ACTOR_USER_ID` есть в компании. Если что-то не так, сервер не запускается и пишет, чего не хватает.
69
+
70
+ ### Права интеграции по профилям
71
+
72
+ | Профиль | Scopes, которые должны быть выданы интеграции в SIGNAL |
73
+ |---|---|
74
+ | `read` | `project:list`, `project:read`, `docs:folder:read`, `docs:folder:list`, `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` |
75
+ | `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` |
76
+ | `workflow` | то же, что `write` |
77
+
78
+ Отправка передачи и запуск рассмотрения в SIGNAL используют те же права, что и их наполнение. Поэтому их отделяет сам MCP-сервер: в профиле `write` этих инструментов нет, а в `workflow` они всегда требуют подтверждения пользователя.
79
+
80
+ ## Подключение к Claude
81
+
82
+ **Claude Desktop** — `claude_desktop_config.json`:
83
+
84
+ ```json
85
+ {
86
+ "mcpServers": {
87
+ "sgnl": {
88
+ "command": "uvx",
89
+ "args": ["sgnl-mcp"],
90
+ "env": {
91
+ "SGNL_CLIENT_ID": "…",
92
+ "SGNL_CLIENT_SECRET": "…",
93
+ "SGNL_MCP_PROFILE": "read"
94
+ }
95
+ }
96
+ }
97
+ }
98
+ ```
99
+
100
+ **Claude Code:**
101
+
102
+ ```bash
103
+ claude mcp add sgnl -e SGNL_CLIENT_ID=… -e SGNL_CLIENT_SECRET=… -e SGNL_MCP_PROFILE=read -- uvx sgnl-mcp
104
+ ```
105
+
106
+ Для профиля `write` добавьте `SGNL_MCP_ACTOR_USER_ID` и `SGNL_MCP_UPLOAD_ROOT`.
107
+
108
+ ### Разрешения в клиенте
109
+ - Инструменты чтения можно разрешить без вопросов.
110
+ - Инструменты записи и Workflow **не добавляйте в «разрешить всегда»**: клиент должен спрашивать перед каждым вызовом. Это один из рубежей контроля.
111
+ - Для профиля `workflow` клиент должен поддерживать MCP elicitation (диалог подтверждения). Без неё отправка передач и запуск рассмотрений завершаются ошибкой и ничего не делают.
112
+
113
+ ## Инструменты
114
+
115
+ | Инструмент | Профиль | Что делает |
116
+ |---|---|---|
117
+ | `list_projects` | read | Проекты, доступные интеграции |
118
+ | `get_project` | read | Данные проекта и его корневая папка |
119
+ | `get_project_folder_tree` | read | Дерево папок с правами пользователя |
120
+ | `search_documents` | read | Поиск документов по проекту или папке (имя, расширение, автор, даты) |
121
+ | `list_folder_documents` | read | Документы одной папки (без вложенных) |
122
+ | `list_document_versions` | read | История версий документа |
123
+ | `get_document_version` | read | Связь «документ — версия файла» по `item_version_id` |
124
+ | `get_document_download_url` | read | Временная ссылка на скачивание версии (сам файл не передаётся) |
125
+ | `get_document_context` | read | Всё о документе за один вызов: версии, атрибуты, замечания, рассмотрения, подписи; у каждого блока свой статус |
126
+ | `list_document_transfers` | read | Передачи проекта с фильтрами |
127
+ | `get_document_transfer` | read | Полные данные передачи |
128
+ | `list_transfer_documents` | read | Документы в передаче |
129
+ | `list_document_transfer_types` | read | Типы передач проекта |
130
+ | `list_issues` | read | Замечания с фильтрами |
131
+ | `list_reviews` | read | Рассмотрения проекта |
132
+ | `get_review_details` | read | Версии рассмотрения и документы в них |
133
+ | `get_document_review_status` | read | Статус рассмотрения версий документов |
134
+ | `list_review_types` | read | Типы рассмотрений проекта |
135
+ | `upload_document` | write | Новый документ из файла |
136
+ | `add_document_version` | write | Новая версия существующего документа |
137
+ | `update_document_version_comment` | write | Комментарий версии |
138
+ | `create_document_transfer` | write | Новая передача (черновик) |
139
+ | `add_documents_to_transfer` | write | Добавить версии документов в неотправленную передачу |
140
+ | `update_issues` | write | Изменить 1–25 замечаний |
141
+ | `create_review` | write | Новое рассмотрение (черновик) |
142
+ | `add_documents_to_review` | write | Добавить до 20 версий документов в рассмотрение |
143
+ | `prepare_send_document_transfer` | workflow | Проверить передачу перед отправкой и получить токен |
144
+ | `send_document_transfer` | workflow | Отправить передачу (подтверждение пользователем в диалоге) |
145
+ | `prepare_start_review` | workflow | Проверить рассмотрение перед запуском и получить токен |
146
+ | `start_review` | workflow | Запустить рассмотрение (подтверждение пользователем в диалоге) |
147
+
148
+ ### Как проходит запись
149
+
150
+ 1. Агент вызывает инструмент без `confirmation_token`. Сервер проверяет объекты и возвращает предпросмотр («было → станет», путь папки, состав передачи и т.п.) и токен. В SIGNAL ничего не меняется.
151
+ 2. Агент показывает предпросмотр пользователю.
152
+ 3. После согласия агент вызывает инструмент с теми же аргументами и токеном. Сервер перепроверяет объекты: если с момента предпросмотра что-то изменилось (другой файл, кто-то правил замечание), токен отклоняется.
153
+
154
+ Токен одноразовый, действует 5 минут и привязан к аргументам. Повторный вызов с уже использованным токеном возвращает сохранённый результат, а не выполняет операцию второй раз.
155
+
156
+ Если запрос на запись завершился без ясного ответа (таймаут, ошибка сервера), сервер **не повторяет его сам**, а возвращает `OUTCOME_UNKNOWN` с тем, что удалось выяснить. Для загрузки файла можно завершить операцию параметром `resume_version_id`, не загружая файл заново.
157
+
158
+ ### Ограничения
159
+ - В API SIGNAL нет поиска передач по документу, поэтому блок `transfers` в `get_document_context` всегда `not_supported`.
160
+ - Создание замечаний, удаление, перемещение, переименование, изменение прав и пользователей в MCP-сервер намеренно не включены.
161
+
162
+ ## Ответы и ошибки
163
+
164
+ Все инструменты возвращают `{"success": true, "data": …, "page": …, "warnings": […], "request_id": …}` или `{"success": false, "error": {"code", "message", "details"}, "request_id": …}`.
165
+
166
+ | Код | Значение |
167
+ |---|---|
168
+ | `INVALID_REQUEST`, `NOT_FOUND`, `FORBIDDEN`, `AUTH_FAILED`, `QUOTA_EXCEEDED`, `RATE_LIMITED` | Ответ SIGNAL (400, 404, 403, 401, 450, 429) |
169
+ | `SERVER_ERROR`, `NETWORK_ERROR`, `TIMEOUT` | SIGNAL не ответил нормально; чтение можно повторить |
170
+ | `OUTCOME_UNKNOWN` | Запись могла пройти или не пройти; сервер её не повторял |
171
+ | `POLICY_DENIED` | Запрос запрещён политикой MCP-сервера |
172
+ | `CONFIRMATION_INVALID`, `CONFIRMATION_DECLINED` | Токен недействителен или пользователь не подтвердил |
173
+ | `INVALID_STATE`, `NOTHING_TO_DO`, `UPLOAD_FAILED`, `BUDGET_EXCEEDED` | Объект в неподходящем состоянии, изменять нечего, файл не передан, исчерпан бюджет запросов |
@@ -0,0 +1,158 @@
1
+ # sgnl-mcp — MCP-сервер для SIGNAL Integration API
2
+
3
+ MCP-сервер, через который LLM-агент (Claude и др.) работает с документацией в SIGNAL: смотрит проекты и папки, ищет и скачивает документы, загружает новые документы и версии, работает с замечаниями, передачами и рассмотрениями.
4
+
5
+ Агент остаётся под контролем: чтение без ограничений, запись — только после предпросмотра и согласия пользователя, отправка передач и запуск рассмотрений — только после подтверждения пользователем в диалоге. Удалять, перемещать и переименовывать документы и папки, а также менять права агент не может вообще.
6
+
7
+ Построен на [`sgnl-api`](../README.md) — обёртке над API из этого же репозитория.
8
+
9
+ ## Как обеспечивается контроль
10
+
11
+ | Рубеж | Что делает |
12
+ |---|---|
13
+ | 1. Scopes токена | Сервер запрашивает у SIGNAL только права своего профиля. В профиле `read` в токене нет ни одного права на запись — SIGNAL сам откажет. Права `*:delete` не запрашиваются никогда. |
14
+ | 2. Профиль | Инструменты выше профиля не регистрируются: агент их не видит. |
15
+ | 3. Белый список запросов | Каждый HTTP-запрос проверяется до отправки: только разрешённые метод и путь, никаких `DELETE`, никакого поля `deleted` в запросах на запись, внешние хосты — только для загрузки файла. |
16
+ | 4. Подтверждение | Запись — в два вызова: предпросмотр с токеном, затем выполнение с тем же токеном. Отправка передачи и запуск рассмотрения — через диалог подтверждения у пользователя (MCP elicitation). |
17
+ | 5. Клиент и аудит | Инструменты помечены `readOnlyHint`: клиент спрашивает разрешение на запись. Все записи и отказы пишутся в JSONL-журнал без секретов. |
18
+
19
+ Нагрузка на SIGNAL ограничена: по умолчанию не больше 3 запросов в секунду и 4 параллельных на весь сервер, у каждого вызова инструмента свой бюджет запросов, справочники кэшируются на 5 минут.
20
+
21
+ ## Установка
22
+
23
+ ```bash
24
+ uvx sgnl-mcp # запуск без установки
25
+ # или
26
+ pip install sgnl-mcp
27
+ ```
28
+
29
+ Нужен Python 3.11+.
30
+
31
+ ## Настройка
32
+
33
+ Вся настройка — через переменные окружения (или файл `.env` в рабочем каталоге). Агент не может их изменить.
34
+
35
+ | Переменная | Обязательна | По умолчанию | Назначение |
36
+ |---|---|---|---|
37
+ | `SGNL_CLIENT_ID` | да | — | Client ID интеграции SIGNAL |
38
+ | `SGNL_CLIENT_SECRET` | да | — | Client secret интеграции SIGNAL |
39
+ | `SGNL_MCP_PROFILE` | нет | `read` | `read`, `write` или `workflow` |
40
+ | `SGNL_MCP_ACTOR_USER_ID` | для `write`/`workflow` | — | ID пользователя SIGNAL, от имени которого делаются изменения (лучше служебная учётная запись «AI-агент») |
41
+ | `SGNL_MCP_UPLOAD_ROOT` | для `write`/`workflow` | — | Каталог, из которого разрешено загружать файлы. Файлы вне него, в том числе через символические ссылки, недоступны |
42
+ | `SGNL_MCP_ALLOWED_EXTENSIONS` | нет | `.pdf,.dwg,.dxf,…` | Разрешённые типы загружаемых файлов, через запятую |
43
+ | `SGNL_MCP_AUTO_CONFIRM` | нет | — | Write-инструменты, которые выполняются без предпросмотра (только для доверенных сценариев). Workflow сюда не входит |
44
+ | `SGNL_MCP_RATE_PER_SEC` | нет | `3` | Запросов в секунду (не больше 10) |
45
+ | `SGNL_MCP_BURST` | нет | `6` | Запас запросов для коротких всплесков |
46
+ | `SGNL_MCP_MAX_CONCURRENCY` | нет | `4` | Параллельных запросов к SIGNAL |
47
+ | `SGNL_MCP_TOOL_TIMEOUT` | нет | `30` | Таймаут вызова инструмента, с |
48
+ | `SGNL_MCP_UPLOAD_TIMEOUT` | нет | `600` | Таймаут загрузки файла, с |
49
+ | `SGNL_MCP_CONFIRMATION_TTL_SEC` | нет | `300` | Срок действия токена подтверждения |
50
+ | `SGNL_MCP_AUDIT_PATH` | нет | `~/.sgnl-mcp/audit.jsonl` | Журнал аудита |
51
+ | `SGNL_MCP_STATE_PATH` | нет | `~/.sgnl-mcp/state.sqlite` | Журнал выполненных операций (защита от повторов) |
52
+
53
+ При запуске сервер получает токен и проверяет, что интеграции выданы все права профиля, а для `write`/`workflow` — что пользователь `SGNL_MCP_ACTOR_USER_ID` есть в компании. Если что-то не так, сервер не запускается и пишет, чего не хватает.
54
+
55
+ ### Права интеграции по профилям
56
+
57
+ | Профиль | Scopes, которые должны быть выданы интеграции в SIGNAL |
58
+ |---|---|
59
+ | `read` | `project:list`, `project:read`, `docs:folder:read`, `docs:folder:list`, `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` |
60
+ | `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` |
61
+ | `workflow` | то же, что `write` |
62
+
63
+ Отправка передачи и запуск рассмотрения в SIGNAL используют те же права, что и их наполнение. Поэтому их отделяет сам MCP-сервер: в профиле `write` этих инструментов нет, а в `workflow` они всегда требуют подтверждения пользователя.
64
+
65
+ ## Подключение к Claude
66
+
67
+ **Claude Desktop** — `claude_desktop_config.json`:
68
+
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "sgnl": {
73
+ "command": "uvx",
74
+ "args": ["sgnl-mcp"],
75
+ "env": {
76
+ "SGNL_CLIENT_ID": "…",
77
+ "SGNL_CLIENT_SECRET": "…",
78
+ "SGNL_MCP_PROFILE": "read"
79
+ }
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ **Claude Code:**
86
+
87
+ ```bash
88
+ claude mcp add sgnl -e SGNL_CLIENT_ID=… -e SGNL_CLIENT_SECRET=… -e SGNL_MCP_PROFILE=read -- uvx sgnl-mcp
89
+ ```
90
+
91
+ Для профиля `write` добавьте `SGNL_MCP_ACTOR_USER_ID` и `SGNL_MCP_UPLOAD_ROOT`.
92
+
93
+ ### Разрешения в клиенте
94
+ - Инструменты чтения можно разрешить без вопросов.
95
+ - Инструменты записи и Workflow **не добавляйте в «разрешить всегда»**: клиент должен спрашивать перед каждым вызовом. Это один из рубежей контроля.
96
+ - Для профиля `workflow` клиент должен поддерживать MCP elicitation (диалог подтверждения). Без неё отправка передач и запуск рассмотрений завершаются ошибкой и ничего не делают.
97
+
98
+ ## Инструменты
99
+
100
+ | Инструмент | Профиль | Что делает |
101
+ |---|---|---|
102
+ | `list_projects` | read | Проекты, доступные интеграции |
103
+ | `get_project` | read | Данные проекта и его корневая папка |
104
+ | `get_project_folder_tree` | read | Дерево папок с правами пользователя |
105
+ | `search_documents` | read | Поиск документов по проекту или папке (имя, расширение, автор, даты) |
106
+ | `list_folder_documents` | read | Документы одной папки (без вложенных) |
107
+ | `list_document_versions` | read | История версий документа |
108
+ | `get_document_version` | read | Связь «документ — версия файла» по `item_version_id` |
109
+ | `get_document_download_url` | read | Временная ссылка на скачивание версии (сам файл не передаётся) |
110
+ | `get_document_context` | read | Всё о документе за один вызов: версии, атрибуты, замечания, рассмотрения, подписи; у каждого блока свой статус |
111
+ | `list_document_transfers` | read | Передачи проекта с фильтрами |
112
+ | `get_document_transfer` | read | Полные данные передачи |
113
+ | `list_transfer_documents` | read | Документы в передаче |
114
+ | `list_document_transfer_types` | read | Типы передач проекта |
115
+ | `list_issues` | read | Замечания с фильтрами |
116
+ | `list_reviews` | read | Рассмотрения проекта |
117
+ | `get_review_details` | read | Версии рассмотрения и документы в них |
118
+ | `get_document_review_status` | read | Статус рассмотрения версий документов |
119
+ | `list_review_types` | read | Типы рассмотрений проекта |
120
+ | `upload_document` | write | Новый документ из файла |
121
+ | `add_document_version` | write | Новая версия существующего документа |
122
+ | `update_document_version_comment` | write | Комментарий версии |
123
+ | `create_document_transfer` | write | Новая передача (черновик) |
124
+ | `add_documents_to_transfer` | write | Добавить версии документов в неотправленную передачу |
125
+ | `update_issues` | write | Изменить 1–25 замечаний |
126
+ | `create_review` | write | Новое рассмотрение (черновик) |
127
+ | `add_documents_to_review` | write | Добавить до 20 версий документов в рассмотрение |
128
+ | `prepare_send_document_transfer` | workflow | Проверить передачу перед отправкой и получить токен |
129
+ | `send_document_transfer` | workflow | Отправить передачу (подтверждение пользователем в диалоге) |
130
+ | `prepare_start_review` | workflow | Проверить рассмотрение перед запуском и получить токен |
131
+ | `start_review` | workflow | Запустить рассмотрение (подтверждение пользователем в диалоге) |
132
+
133
+ ### Как проходит запись
134
+
135
+ 1. Агент вызывает инструмент без `confirmation_token`. Сервер проверяет объекты и возвращает предпросмотр («было → станет», путь папки, состав передачи и т.п.) и токен. В SIGNAL ничего не меняется.
136
+ 2. Агент показывает предпросмотр пользователю.
137
+ 3. После согласия агент вызывает инструмент с теми же аргументами и токеном. Сервер перепроверяет объекты: если с момента предпросмотра что-то изменилось (другой файл, кто-то правил замечание), токен отклоняется.
138
+
139
+ Токен одноразовый, действует 5 минут и привязан к аргументам. Повторный вызов с уже использованным токеном возвращает сохранённый результат, а не выполняет операцию второй раз.
140
+
141
+ Если запрос на запись завершился без ясного ответа (таймаут, ошибка сервера), сервер **не повторяет его сам**, а возвращает `OUTCOME_UNKNOWN` с тем, что удалось выяснить. Для загрузки файла можно завершить операцию параметром `resume_version_id`, не загружая файл заново.
142
+
143
+ ### Ограничения
144
+ - В API SIGNAL нет поиска передач по документу, поэтому блок `transfers` в `get_document_context` всегда `not_supported`.
145
+ - Создание замечаний, удаление, перемещение, переименование, изменение прав и пользователей в MCP-сервер намеренно не включены.
146
+
147
+ ## Ответы и ошибки
148
+
149
+ Все инструменты возвращают `{"success": true, "data": …, "page": …, "warnings": […], "request_id": …}` или `{"success": false, "error": {"code", "message", "details"}, "request_id": …}`.
150
+
151
+ | Код | Значение |
152
+ |---|---|
153
+ | `INVALID_REQUEST`, `NOT_FOUND`, `FORBIDDEN`, `AUTH_FAILED`, `QUOTA_EXCEEDED`, `RATE_LIMITED` | Ответ SIGNAL (400, 404, 403, 401, 450, 429) |
154
+ | `SERVER_ERROR`, `NETWORK_ERROR`, `TIMEOUT` | SIGNAL не ответил нормально; чтение можно повторить |
155
+ | `OUTCOME_UNKNOWN` | Запись могла пройти или не пройти; сервер её не повторял |
156
+ | `POLICY_DENIED` | Запрос запрещён политикой MCP-сервера |
157
+ | `CONFIRMATION_INVALID`, `CONFIRMATION_DECLINED` | Токен недействителен или пользователь не подтвердил |
158
+ | `INVALID_STATE`, `NOTHING_TO_DO`, `UPLOAD_FAILED`, `BUDGET_EXCEEDED` | Объект в неподходящем состоянии, изменять нечего, файл не передан, исчерпан бюджет запросов |
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "sgnl-mcp"
7
+ version = "0.1.0"
8
+ description = "MCP server giving an LLM agent controlled access to the SIGNAL Integration API"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.11"
12
+ authors = [
13
+ { name = "Igor Gritsyuk", email = "gritsyuk.igor@gmail.com" }
14
+ ]
15
+ dependencies = [
16
+ "sgnl-api>=0.4.0",
17
+ "mcp>=2.3,<3",
18
+ "pydantic>=2.7",
19
+ "pydantic-settings>=2.3",
20
+ "tenacity>=8.2",
21
+ ]
22
+
23
+ [project.scripts]
24
+ sgnl-mcp = "sgnl_mcp.server:main"
25
+
26
+ [project.urls]
27
+ GitHub = "https://github.com/gritsyuk/sgnl-api"
28
+
29
+ [tool.uv.sources]
30
+ sgnl-api = { workspace = true }
31
+
32
+ [dependency-groups]
33
+ dev = [
34
+ "pytest>=8.0",
35
+ "pytest-asyncio>=0.23",
36
+ "pyyaml>=6.0.3",
37
+ ]
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["sgnl_mcp"]
41
+
42
+ [tool.hatch.build.targets.sdist]
43
+ include = ["sgnl_mcp", "README.md", "pyproject.toml"]
44
+
45
+ [tool.pytest.ini_options]
46
+ asyncio_mode = "auto"
47
+ testpaths = ["tests"]
File without changes
@@ -0,0 +1,41 @@
1
+ """Append-only JSONL audit log of Write and Workflow operations, with secrets removed."""
2
+ import json
3
+ import re
4
+ import threading
5
+ from datetime import datetime, timezone
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ SECRET_KEYS = {
10
+ "client_secret", "clientsecret", "token", "authorization", "jwt",
11
+ "signed_url", "signedurl", "password", "confirmation_token",
12
+ }
13
+ # Pre-signed S3 query parameters: whoever has the URL can download or upload until it expires.
14
+ _SIGNED_QUERY = re.compile(r"(https?://[^\s\"?]+)\?[^\s\"]*(X-Amz-|Signature=|signature=)[^\s\"]*")
15
+
16
+
17
+ def redact(value: Any) -> Any:
18
+ if isinstance(value, dict):
19
+ return {k: ("[redacted]" if k.lower() in SECRET_KEYS else redact(v)) for k, v in value.items()}
20
+ if isinstance(value, list):
21
+ return [redact(v) for v in value]
22
+ if isinstance(value, str):
23
+ return _SIGNED_QUERY.sub(r"\1?[redacted]", value)
24
+ return value
25
+
26
+
27
+ class AuditLog:
28
+ def __init__(self, path: Path, rotate_bytes: int = 10 * 1024 * 1024):
29
+ self.path = Path(path)
30
+ self.rotate_bytes = rotate_bytes
31
+ self._lock = threading.Lock()
32
+ self.path.parent.mkdir(parents=True, exist_ok=True)
33
+
34
+ def write(self, event: dict) -> None:
35
+ record = {"ts": datetime.now(timezone.utc).isoformat(), **redact(event)}
36
+ line = json.dumps(record, ensure_ascii=False, default=str)
37
+ with self._lock:
38
+ if self.path.exists() and self.path.stat().st_size > self.rotate_bytes:
39
+ self.path.replace(self.path.with_suffix(self.path.suffix + ".1"))
40
+ with self.path.open("a", encoding="utf-8") as f:
41
+ f.write(line + "\n")
@@ -0,0 +1,68 @@
1
+ from pathlib import Path
2
+ from typing import Optional
3
+ from uuid import UUID
4
+
5
+ from pydantic import Field, SecretStr, field_validator, model_validator
6
+ from pydantic_settings import BaseSettings, SettingsConfigDict
7
+
8
+ from .profiles import Profile
9
+
10
+ DEFAULT_EXTENSIONS = (
11
+ ".pdf", ".dwg", ".dxf", ".dgn", ".rvt", ".ifc", ".nwd", ".nwc",
12
+ ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".txt", ".csv", ".xml",
13
+ ".png", ".jpg", ".jpeg", ".tif", ".tiff", ".zip",
14
+ )
15
+ STATE_DIR = Path.home() / ".sgnl-mcp"
16
+
17
+
18
+ class Settings(BaseSettings):
19
+ """All configuration comes from the environment (or a .env file); none of it from tool arguments."""
20
+
21
+ model_config = SettingsConfigDict(env_prefix="SGNL_MCP_", env_file=".env", extra="ignore", populate_by_name=True)
22
+
23
+ client_id: str = Field(validation_alias="SGNL_CLIENT_ID")
24
+ client_secret: SecretStr = Field(validation_alias="SGNL_CLIENT_SECRET")
25
+ base_url: str = "https://api.sgnl.pro/public/v1"
26
+
27
+ profile: Profile = Profile.READ
28
+ # User id SIGNAL records as the author of every write. Never taken from the agent.
29
+ actor_user_id: Optional[UUID] = None
30
+ # Only files under this directory can be uploaded.
31
+ upload_root: Optional[Path] = None
32
+ allowed_extensions: tuple[str, ...] = DEFAULT_EXTENSIONS
33
+ # Write tools that run without the two-phase preview. Workflow tools can never be listed.
34
+ auto_confirm: tuple[str, ...] = ()
35
+
36
+ rate_per_sec: float = Field(3.0, gt=0, le=10)
37
+ burst: int = Field(6, ge=1, le=50)
38
+ max_concurrency: int = Field(4, ge=1, le=10)
39
+ http_timeout: float = 30.0
40
+ tool_timeout: float = 30.0
41
+ upload_timeout: float = 600.0
42
+ max_response_chars: int = 20_000
43
+ confirmation_ttl_sec: int = 300
44
+ cache_ttl_sec: int = 300
45
+
46
+ audit_path: Path = STATE_DIR / "audit.jsonl"
47
+ state_path: Path = STATE_DIR / "state.sqlite"
48
+
49
+ @field_validator("profile", mode="before")
50
+ @classmethod
51
+ def _parse_profile(cls, value):
52
+ return Profile.parse(value) if isinstance(value, str) else value
53
+
54
+ @field_validator("allowed_extensions", "auto_confirm", mode="before")
55
+ @classmethod
56
+ def _split_csv(cls, value):
57
+ if isinstance(value, str):
58
+ return tuple(v.strip().lower() if v.strip().startswith(".") else v.strip() for v in value.split(",") if v.strip())
59
+ return value
60
+
61
+ @model_validator(mode="after")
62
+ def _check_write_requirements(self):
63
+ if self.profile >= Profile.WRITE:
64
+ if self.actor_user_id is None:
65
+ raise ValueError("SGNL_MCP_ACTOR_USER_ID is required for the write and workflow profiles")
66
+ if self.upload_root is None:
67
+ raise ValueError("SGNL_MCP_UPLOAD_ROOT is required for the write and workflow profiles")
68
+ return self