ZenMoney MCP
README.md
# ZenMoney MCP
MCP-сервер для личных финансов ZenMoney: **28 инструментов** (15 для чтения, 13 для изменения данных), локальный stdio и удалённый Streamable HTTP с OAuth. Финансовая логика находится в scripts/zenmoney/, MCP-адаптер — в zenmoney_mcp/.
**Этот репозиторий содержит код, а не размещённый сервер.** Для ChatGPT Web и Claude Web разверните собственный HTTPS endpoint и OAuth-провайдер. Один экземпляр рассчитан на **одного владельца**: он использует один токен ZenMoney и допускает один заданный OAuth subject.
## Локальный запуск
Нужны Python 3.10+ и действующий API-токен ZenMoney. Передайте токен процессу через переменную окружения ZENMONEY_TOKEN из своего хранилища секретов. Не вводите его в аргументах командной строки и не сохраняйте в Git.
~~~bash
git clone https://github.com/theblackhaired/zenmoney-mcp.git
cd zenmoney-mcp
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python -m zenmoney_mcp
~~~
На Windows последняя команда — .\.venv\Scripts\python.exe -m zenmoney_mcp. Укажите её и защищённую переменную ZENMONEY_TOKEN в настройках stdio MCP-клиента. Есть также точка входа run_stdio.py.
Необязательная ZENMONEY_STATE_DIR задаёт приватный каталог настроек (по умолчанию каталог данных текущего пользователя). Для period=billing_period добавьте туда config.json с параметром billing_period_start_day — целым числом от 1 до 31. Файл может содержать другие личные настройки и не должен попадать в репозиторий. check_auth_status проверяет токен без финансовой записи.
## Инструменты
Полные схемы необязательных аргументов клиент получает через MCP tools/list. Таблицы показывают обязательные аргументы и назначение. **Каждый инструмент изменения данных дополнительно требует confirm_write=true; инструменты чтения этого аргумента не требуют.**
### Чтение — finance:read
| Инструмент | Обязательные аргументы | Результат |
| --- | --- | --- |
| get_accounts | — | Счета и текущие остатки |
| get_transactions | Период или диапазон дат | Операции с фильтрами; limit до 500 |
| get_categories | — | Дерево категорий |
| get_instruments | — | Валюты и текущие курсы |
| get_budgets | month (YYYY-MM) | Бюджеты месяца |
| get_reminders | — | Напоминания и маркеры; marker_from и marker_to задаются вместе |
| analyze_budget_detailed | period=billing_period | «Планы»: план/факт, календарь и прогноз остатка |
| get_analytics | report=income, outcome или net; период/даты | Доходы, расходы или чистый результат |
| get_category_report | Период или даты | Отчёт по категориям или получателям, сопоставление плана |
| get_money_flow | Период или даты | Денежные потоки, доли, остаток и перерасход по валютам |
| get_income_outcome_comparison | Период или даты | Сравнение с предыдущими периодами |
| get_balance_trend | Период или даты | Восстановленная динамика остатков |
| suggest | payee | Предложение категории и получателя |
| get_merchants | — | Получатели, поиск и пагинация |
| check_auth_status | — | Состояние доступа к ZenMoney |
### Изменения — finance:read и finance:write
| Инструмент | Обязательные аргументы помимо confirm_write | Действие |
| --- | --- | --- |
| setup_budget_mode | mode=balance_vs_expense или income_vs_expense | Локальное переопределение режима «Планов» |
| create_transaction | type, amount, account_id | Расход, доход или перевод |
| update_transaction | id | Меняет только переданные поля операции |
| delete_transaction | id | Помечает операцию удалённой |
| create_account | title, type, currency_id | Создаёт счёт |
| create_budget | month, category | Создаёт или обновляет бюджет |
| update_budget | month, category | Меняет существующий бюджет |
| delete_budget | month, category | Обнуляет значения и снимает блокировки; не удаляет запись физически |
| create_reminder | type, amount, account_id, interval | Повторяющееся напоминание с маркерами |
| update_reminder | id | Меняет переданные поля напоминания |
| delete_reminder | id | Удаляет напоминание и его маркеры |
| create_reminder_marker | type, amount, account_id, date | Маркер на дату; при необходимости разовое напоминание |
| delete_reminder_marker | id | Удаляет маркер |
Для type=transfer у create_transaction, create_reminder и create_reminder_marker нужен также to_account_id. amount должен быть положительным. В удалённом режиме scope finance:write проверяется сервером отдельно от confirm_write.
### Периоды и формат ответа
get_transactions, get_analytics, get_category_report, get_money_flow, get_income_outcome_comparison и get_balance_trend принимают **один** способ выбора дат:
- period=billing_period, week, month или year; period_offset сдвигает такой период;
- start_date и end_date вместе, обе даты включаются.
Для week обязателен first_weekday от 0 (понедельник) до 6 (воскресенье). Для billing_period нужен billing_period_start_day в приватном config.json. analyze_budget_detailed принимает только billing_period.
Пример аргументов get_analytics:
~~~json
{"report":"outcome","period":"month","period_offset":0,"response_mode":"full"}
~~~
У объёмных инструментов чтения по умолчанию response_mode=compact. Он сокращает ответ для экономии контекста модели. Если данные усечены, ответ содержит _response.truncated=true: это **не полный набор данных**. Повторите вызов с response_mode=full, чтобы получить все поля и элементы. Ошибки возвращаются полностью.
### Особенности расчётов
- Вызов, которому нужны данные ZenMoney, получает свежий полный снимок /v8/diff/. Постоянного кэша операций и счетов нет; снимок остаётся в памяти только на время вызова. После записи сервер повторно проверяет результат в ZenMoney. Открытые ключи JWKS могут кратковременно кэшироваться — это не финансовые данные.
- Переводы исключены из доходов и расходов get_analytics. Валюты по умолчанию разделены; скалярный итог смешанных валют выдаёт ошибку вместо неявного сложения.
- Продвинутые отчёты используют текущие синхронизированные курсы ZenMoney, а не исторический курс на дату операции.
- get_balance_trend восстанавливает прошлые остатки из текущих остатков и истории операций; готовых ежедневных снимков ZenMoney здесь нет.
- Режим «Планов» и настройки переводов берутся из синхронизированного пользователя, кроме явных локальных переопределений. В режиме balance_vs_expense расчёт разниц использует difference_calculation_mode=NONE.
- Фильтры аналитики по счёту, категории и получателю объединяются через AND. Неоднозначное короткое название категории бюджета вызывает ошибку; можно указать ID или полный путь.
- report=turnover в get_analytics возвращает UNSUPPORTED_CALCULATION. В сравнении доходов и расходов AVERAGE_VALUES для периода до 31 дня фактически использует WHOLE_PERIOD, для более длинного периода возвращает UNSUPPORTED_CALCULATION.
## Удалённый сервер и OAuth
Пример публичного endpoint: https://mcp.example.com/mcp. Приложение слушает 127.0.0.1:8769; HTTPS обеспечивает reverse proxy. В deploy/ есть обезличенные примеры nginx, systemd и необязательного Keycloak с PostgreSQL. Замените example.com, пользователя службы и пути. Compose **не создаёт** realm, пользователя, scopes, audience или политику регистрации OAuth-клиентов.
~~~bash
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python -m uvicorn zenmoney_mcp.remote:create_app --factory --host 127.0.0.1 --port 8769
~~~
| Переменная | Назначение |
| --- | --- |
| ZENMONEY_TOKEN | API-токен ZenMoney; остаётся только на сервере |
| ZENMONEY_STATE_DIR | Приватный каталог настроек |
| ZENMONEY_MCP_RESOURCE | Точный публичный URL, например https://mcp.example.com/mcp |
| ZENMONEY_OAUTH_ISSUER | HTTPS issuer провайдера |
| ZENMONEY_OAUTH_JWKS_URL | HTTPS URL открытых ключей |
| ZENMONEY_OAUTH_SUBJECT | Точное sub единственного разрешённого пользователя |
Без четырёх OAuth-переменных удалённый сервер не запустится. Токен ZenMoney и OAuth-токены клиентов — разные учётные данные; клиентам передаются только их OAuth-токены.
Порядок развёртывания на Linux:
1. Подготовьте DNS и сертификаты для MCP и OAuth-провайдера. Оставьте Uvicorn и Keycloak на loopback; используйте deploy/nginx.https.conf и deploy/nginx.auth.https.conf как шаблоны входящих HTTPS-маршрутов.
2. Установите зависимости в venv, создайте отдельного системного пользователя и приватный каталог состояния. Скопируйте deploy/server.env.template вне checkout (например, в /etc/zenmoney-mcp/server.env), задайте ZENMONEY_TOKEN и все OAuth-переменные, ограничьте чтение файла владельцем службы. Пример unit — deploy/zenmoney-mcp.service; после подстановки путей проверьте его через systemd-analyze verify.
3. Поднимите и настройте OAuth-провайдер. Для отдельного Keycloak можно использовать deploy/compose.yaml и предварительно создать приватный env-файл скриптом deploy/generate_env.py. Затем вручную настройте realm, пользователя, scopes, audience и регистрацию клиентов; compose выполняет только запуск служб.
4. После проверки issuer, JWKS и токена запустите MCP-службу, проверьте metadata и ответ 401 без токена. Только затем подключайте веб-клиенты.
5. Если нужен таймер Claude DCR, установите deploy/zenmoney-mcp-claude-scopes.service и .timer как user units. Предоставьте этой службе отдельный приватный Keycloak admin env-файл, доступный её пользователю, и проверьте результат первого запуска. Не храните admin-реквизиты в checkout.
Провайдер должен поддерживать Authorization Code + PKCE S256 и discovery. Сервер проверяет подпись RS256/ES256, iss, exp, sub, scope и **одиночное строковое aud, точно равное ZENMONEY_MCP_RESOURCE**, в каждом запросе. Разрешите клиентам оба scope — finance:read и finance:write: сервер объявляет их при первоначальном OAuth-запросе, поэтому запрет finance:write в DCR может сорвать подключение ещё до вызова записи. При наличии RFC 8707 настройте resource indicator; иначе настройте audience mapper и проверьте итоговое aud. См. [руководство Keycloak по MCP](https://www.keycloak.org/securing-apps/mcp-authz-server).
Для ChatGPT с Keycloak используйте DCR или заранее зарегистрированного клиента: текущая документация Keycloak указывает несовместимость его экспериментального CIMD с документом ChatGPT. Ограничьте redirect URI и Trusted Hosts фактическими клиентами. Для Claude deploy/reconcile_claude_dcr_scopes.py поддерживает необязательный finance:write у строго распознанных DCR-клиентов в уже настроенном realm master; пример таймера есть в deploy/. Скрипт не создаёт пользователя и не выдаёт согласие.
Безопасная проверка маршрута:
~~~bash
curl -i https://mcp.example.com/.well-known/oauth-protected-resource/mcp
curl -i https://mcp.example.com/mcp
curl -i https://auth.example.com/auth/realms/master/.well-known/openid-configuration
~~~
Ожидаются соответственно 200 с resource и authorization_servers, 401 с WWW-Authenticate и discovery провайдера. Ответ 401 без токена подтверждает защиту маршрута, но не права пользователя.
### ChatGPT Web
1. В ChatGPT откройте Settings → Security and login и включите Developer mode, если он доступен аккаунту.
2. В ChatGPT Plugins добавьте полный HTTPS URL с /mcp и проверьте обнаруженные инструменты.
3. Начните новый чат, выберите подключение и пройдите OAuth-вход.
4. Если Keycloak требует redirect URI, скопируйте **точное значение из страницы подключения ChatGPT**: оно зависит от поддержки issuer identification. Не используйте угаданный wildcard.
[Инструкция OpenAI по подключению](https://developers.openai.com/plugins/deploy/connect-chatgpt) · [Требования OpenAI к OAuth](https://developers.openai.com/plugins/build/auth)
### Claude Web
1. Откройте Customize → Connectors → Add custom connector и укажите тот же URL с /mcp.
2. Выберите OAuth. С Keycloak доступны Register automatically (DCR) или заранее созданный OAuth client; CIMD используйте только после проверки совместимости вашей версии Keycloak.
3. Для hosted Claude зарегистрируйте callback https://claude.ai/api/mcp/auth_callback. После входа проверьте чтение; если нужен scope записи у DCR-клиента, выполните reconciler и проверьте его результат.
[Инструкция Claude по коннекторам](https://claude.com/docs/connectors/custom/add-unlisted) · [OAuth-требования Claude](https://claude.com/docs/connectors/building/authentication)
## Проверка и структура
~~~bash
.venv/bin/python -m pip install -r requirements-dev.txt
.venv/bin/python -m pytest -q
~~~
Тесты используют синтетические данные; реальные токены и выгрузки не нужны.
| Путь | Назначение |
| --- | --- |
| zenmoney_mcp/ | MCP-инструменты, ответы, stdio/HTTP и OAuth |
| scripts/zenmoney/ | Финансовая логика, валидация и ZenMoney API |
| deploy/ | Обезличенные примеры сервера, nginx и Keycloak |
| tests/ | Регрессионные тесты |
Никогда не добавляйте в Git config.json, .cache.json, .env, токены, реальные суммы, имена счетов и финансовые выгрузки. Перед подключением модели проверьте, какие инструменты записи и scope она получит.
## Лицензия
MIT — см. LICENSE.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues