vetmanager-mcp
 
English speakers: This project is documented in Russian. The API and MCP protocol are language-agnostic and work with any MCP-compatible client. Google Translate handles technical documentation well. See Quick Start to get running in 3 commands.
MCP-сервер для интеграции Vetmanager REST API с любым клиентом, поддерживающим Model Context Protocol. Позволяет управлять операциями ветеринарной клиники через диалоговых AI-агентов на естественном языке.
Сервер берёт на себя bearer-аутентификацию сервиса, хранение Vetmanager credentials на уровне service account, динамическое определение URL API (через billing-api.vetmanager.cloud) и форматирование данных. Runtime-контур теперь bearer-only: MCP-клиент передаёт только Authorization: Bearer <service_token>, а активное Vetmanager-подключение определяется через account context.
Privacy / auth boundary:
- сервис не сохраняет бизнес-данные Vetmanager для постоянного хранения;
- сервис хранит только технические данные integration и metadata сервисных bearer-токенов;
- для режима
login/password -> user tokenлогин и пароль используются только для token exchange и не сохраняются в storage; - при смене пароля в Vetmanager сохранённый user token может стать невалидным, и тогда в кабинете потребуется повторная авторизация.
Требования
- Docker (с плагином Compose)
ssh-copy-idнастроен для деплоя на удалённый сервер- Python на хосте не требуется — всё работает через Docker
Быстрый старт (локально)
cp .env.example .env # задайте UID/GID и LOG_LEVEL
docker compose build
docker compose up -d # запустить MCP-сервер
После запуска:
- публичный лендинг доступен на
http://localhost:8000/ - MCP endpoint остаётся на
http://localhost:8000/mcp
Полезные runtime-переменные:
DATABASE_URL— строка подключения к БД. По умолчанию используется локальныйsqlite+aiosqlite:///./data/vetmanager.db.STORAGE_ENCRYPTION_KEY— ключ шифрования для сохранённых Vetmanager secrets. В production должен быть задан явно.WEB_SESSION_SECRET— секрет подписи web session cookie для/register,/login,/account. В production должен быть задан явно.WEB_SESSION_MAX_AGE_SECONDS— срок жизни web session cookie. По умолчанию 86 400 (24 часа).WEB_TRUSTED_PROXY_IPS— список доверенных reverse proxy IP/host через запятую; только для них сервис учитываетX-Forwarded-For.SITE_BASE_URL— базовый URL self-hosted инсталляции (используется в canonical/og:url лендинга и в mcp.json snippet на странице аккаунта). По умолчаниюhttps://vetmanager-mcp.vromanichev.ru(prod). Для self-hosted — задайте свой домен (без trailing slash).ERROR_TRACKING_DSN/SENTRY_DSN— opt-in DSN для отправки unhandled errors в Sentry.ERROR_TRACKING_ENVIRONMENT,ERROR_TRACKING_RELEASE,ERROR_TRACKING_TRACES_SAMPLE_RATE— knobs для error tracking bootstrap.PORT,MCP_PATH,LOG_LEVEL— стандартные настройки MCP HTTP runtime.
Operational helpers:
scripts/post_deploy_smoke_checks.sh— post-deploy checks для/healthz,
/readyz, /metrics и /mcp. Поддерживает retry/grace knobs через env: SMOKE_MAX_ATTEMPTS, SMOKE_SLEEP_SECONDS, SMOKE_CONNECT_TIMEOUT_SECONDS, SMOKE_CURL_MAX_TIME_SECONDS. При падении deploy script дополнительно печатает docker compose ps и tail container logs для fast triage. Deploy path также принудительно выравнивает UID/GID для docker compose и заранее создаёт локальный data/, чтобы SQLite storage мог стартовать на bind-mounted репозитории без permission drift.
Запуск тестов:
# default contour: unit + mock + live browser happy-path tests
# Chromium уже предустановлен в test image, доп. setup не нужен
docker compose --profile test run --rm test
# fast contour: без browser и real contour tests
docker compose --profile test run --rm test sh -c "python scripts/run_fast_test_suite.py"
# security contour: только security regressions этапа 44
docker compose --profile test run --rm test sh -c "python -m pytest -m security -q"
# opt-in real contour: real API и real browser tests
docker compose run --rm \
-e TEST_DOMAIN=<домен> \
-e TEST_API_KEY=<ключ> \
test sh -c "python scripts/run_opt_in_real_test_suite.py"
# opt-in real contour с direct real smoke для уже выданного user-token
docker compose run --rm \
-e TEST_DOMAIN=<домен> \
-e TEST_USER_TOKEN=<user_token> \
test sh -c "python scripts/run_opt_in_real_test_suite.py"
# opt-in real contour с login/password -> user token exchange
docker compose run --rm \
-e TEST_DOMAIN=<домен> \
-e TEST_USER_TOKEN_BASE_URL=<https://clinic.vetmanager2.ru> \
-e TEST_USER_LOGIN=<login> \
-e TEST_USER_PASSWORD=<password> \
test sh -c "python scripts/run_opt_in_real_test_suite.py"
Тестовые контуры:
fast:
быстрый inner-loop без Playwright/browser и без real API/browser tests.
default:
unit + mock/e2e + live localhost browser tests, без реального Vetmanager API.
opt_in_real:
real API e2e + real browser tests; real browser flow дополнительно требует RUN_REAL_BROWSER_TESTS=1.
Safe workflow для real/browser verification:
- использовать только env-driven
TEST_*иRUN_REAL_BROWSER_TESTS=1, не
записывать clinic credentials в репозиторий;
- для
login/password -> user tokenreal flow сервис повторяет production
контракт Vetmanager: POST /token_auth.php c app_name=vetmanager-mcp, затем X-USER-TOKEN + X-APP-NAME на последующих API-запросах;
- для production browser checks использовать временный account и удалять его
после ручной верификации, чтобы не копить тестовые сущности в production.
Что входит в default docker compose --profile test run --rm test:
- unit tests;
- mock/e2e tests;
- live localhost browser tests через Playwright;
- browser happy-path tests для обоих web auth flows;
- cleanup regression для browser-created account data;
- zero-warning quality gate через warning-as-error launcher.
Observability
HTTP probes и scrape endpoints:
GET /healthz— process liveness, не ходит во внешние зависимости.GET /readyz— readiness probe; сейчас проверяет storage черезSELECT 1.GET /metrics— Prometheus-compatible text exposition для process-local service metrics.
Что собирается из коробки:
- structured logs c
request_id,correlation_id,event_category,event_name; runtime/audit/securitylogger taxonomy;- service metrics:
vetmanager_http_requests_total+vetmanager_http_request_latency_seconds_{count,sum,max}— inbound HTTP web routes;vetmanager_auth_failures_total{source,reason}— bearer/web auth failures grouped by reason;vetmanager_upstream_failures_total{target,reason}— failures to VM/billing upstream (timeout / network_error / http_5xx / circuit_open);vetmanager_upstream_requests_total{target,status}+vetmanager_upstream_request_latency_seconds_{count,sum,max}— all VM API requests with their outcome (stage 88);vetmanager_tool_calls_total{endpoint,method,outcome}+vetmanager_tool_call_latency_seconds_{count,sum,max}— per-tool (endpoint+method) latency and success/error rate via crud_helpers instrumentation (stage 88);vetmanager_cache_{hits,misses,invalidations,evictions}_total+vetmanager_cache_entries;vetmanager_business_events_total{event=...}— lifecycle business events (account_registered,web_login_succeeded,bearer_token_issued,bearer_token_revoked) (stage 110);vetmanager_token_preset_issued_total{preset}— issuance counter by access preset;vetmanager_account_last_request_age_hours{account_id}— hours since the
last successful bearer runtime request for active accounts with active connection and live token; never-used tokens use the earliest live token creation time as the age anchor;
vetmanager_rate_limit_backend_degraded_total{reason}— Redis rate-limit backend fallback/strict failure counter;vetmanager_sanitizer_failures_total— depersonalized response sanitizer failures;/metricsendpoint gated by optionalMETRICS_AUTH_TOKENenv (stage 111.1): when set, requiresAuthorization: Bearer <token>or returns 403;- activation telemetry scan on
/metricsruns only whenMETRICS_AUTH_TOKENis configured and the request passed bearer auth; - invalid
/metricsbearer attempts incrementvetmanager_auth_failures_total{source="metrics",reason="invalid_token"}and emit asecuritylog eventmetrics_auth_failed; - opt-in Sentry bootstrap для unhandled exceptions.
Exceptions raised by VM tools
Все клиенты MCP tools ловят исключения наследники VetmanagerError:
AuthError— 401/403 от Vetmanager (неверный токен/доступ запрещён);NotFoundError— 404 (ресурс не существует);VetmanagerTimeoutError— истёк timeout upstream запроса;VetmanagerUpstreamUnavailable(stage 91) — circuit breaker OPEN для domain, fast-fail без вызова upstream. НаследуетVetmanagerError, поэтому existingexcept VetmanagerError:ловят его без изменений;RateLimitError— срабатывание локального rate limiter'а;HostResolutionError— сбой резолва VM-хоста через billing API;VetmanagerError— база для upstream 5xx и протокольных ошибок.
Error tracking:
- без
ERROR_TRACKING_DSN/SENTRY_DSNintegration не активируется; - pattern-based sanitizer (stage 89) редактирует заголовки/cookies/query/body по substring'ам
token|key|secret|auth|api|cookie|bearer|password|credential|session|csrf|signature|jwt|hmac|otp|passphrase, с whitelist для observability headers (x-request-id,x-correlation-id,x-api-versionи т.д.).
Отдельный runbook по эксплуатации observability-контура:
artifacts/observability-runbook-vetmanager-mcp-ru.md
Bearer-only runtime
Рабочий MCP runtime больше не принимает X-VM-Domain и X-VM-Api-Key в HTTP-заголовках. Все tools и prompts работают только через Authorization: Bearer <service_token>.
Bearer-токен привязан к account сервиса:
service_bearer_tokenидентифицирует account;- account хранит ровно одно активное
vetmanager_connection; - активное connection поддерживает auth mode
domain + rest_api_keyиlogin/password -> user token; - домен и Vetmanager API key хранятся в storage-слое и не передаются в MCP tool arguments.
Текущий статус provisioning
Пользовательский web-контур уже начал работать:
- лендинг перепозиционирован под ветврачей, администраторов и руководителей клиник;
- регистрация вынесена в главный CTA главной страницы;
- доступны лендинг, регистрация account и login/logout;
- доступна страница
/account; - доступна настройка активной Vetmanager integration через wizard:
сначала выбор способа авторизации, затем только релевантные поля;
- доступна настройка
domain + rest_api_key; - доступна настройка user-token integration через
domain + login/password -> user token; - логин и пароль Vetmanager не сохраняются и не отображаются повторно после submit;
- для token exchange используется
POST /token_auth.phpсmultipart/form-data
и фиксированным app_name=vetmanager-mcp, без X-REST-API-KEY;
- для нового account кабинет показывает onboarding state с явным следующим шагом;
- state-changing web forms защищены signed CSRF token layer;
/register,/loginи bearer runtime защищены shared rate limiting:
in-memory по умолчанию, Redis-backed при REDIS_URL;
- HTML responses отдают baseline security headers:
CSP,X-Frame-Options,Referrer-Policy,X-Content-Type-Options; - кабинет показывает health активной integration и статус
reauth_required, если сохранённый user token больше не проходит валидацию; - доступен выпуск Bearer-токенов с именем, сроком действия, preset'ом доступа
(full_access, read_only, frontdesk, doctor, finance, inventory, report_ai) и опциональным режимом деперсонализации ответов; report_ai отображается в UI как Analytics и даёт full read-only доступ плюс права сохранения Report AI отчётов;
- web-выпуск безопасен по умолчанию: blank expiry становится 30 days,
default preset — read_only, а full_access и ... IP mask требуют явного подтверждения в форме;
- после выпуска raw bearer token показывается в отдельной success-card в верхней части страницы и может быть скопирован кнопкой;
- доступен список токенов со статусом, сроком действия,
last_used_at,request_countи revoke action.
На текущем этапе в репозитории уже есть:
- storage foundation и миграции для
accounts,vetmanager_connections,service_bearer_tokens; - шифрование Vetmanager credentials;
- hash-only хранение bearer-токенов;
- сервис сохранения Vetmanager connection
domain + rest_api_key; - web exchange
login/password -> user tokenc сохранением только полученного user token; - web auth для account через email/password и signed cookie session;
- account onboarding wizard с выбором
API keyилиlogin/password; - signed CSRF layer для
/register,/login,/logoutи/account/*; - rate limiting для
/register,/loginи bearer runtime через общий backend; - baseline security headers для HTML-ответов web UI;
- web-экран сохранения active Vetmanager integration;
- web-выпуск Bearer-токенов с preset-based scopes и one-time показом raw значения;
- централизованная деперсонализация ответов для токенов с включённым флагом:
structured PII поля маскируются, free-text scrub ограничен whitelist clinical fields, а при ошибке sanitizer'а raw payload не возвращается;
- success-card для нового raw bearer token с copy action;
- список Bearer-токенов с usage metadata;
- runtime usage accounting (
last_used_at,request_count); - безопасный audit log для create/revoke Bearer-токенов.
То есть runtime-контракт уже bearer-only, а account provisioning, Vetmanager integration, token management, security baseline web-контура и продуктовый landing больше не internal-only.
Подключение Cursor
Шаг 1 — запустить сервер
cp .env.example .env # задайте UID/GID и LOG_LEVEL при необходимости
docker compose up -d mcp
Шаг 1.1 — создать web account
http://localhost:8000/register— регистрация accounthttp://localhost:8000/login— вход в accounthttp://localhost:8000/account— кабинет после входа, включая Vetmanager integration, выпуск Bearer-токенов и их список
Шаг 2 — настроить ~/.cursor/mcp.json
{
"mcpServers": {
"vetmanager": {
"url": "http://localhost:8000/mcp",
"headers": {
"Authorization": "Bearer vm_st_your_service_token"
}
}
}
}
Authorizationдолжен содержать service bearer token, выданный для account сервиса.domainиapi_keyбольше не указываются вmcp.jsonи не передаются в tool arguments.
Политика credentials
| Контекст | Где берутся credentials | |----------|------------------------| | Cursor / Claude | ~/.cursor/mcp.json → Authorization: Bearer <service_token> | | Vetmanager domain / api_key | активное vetmanager_connection выбранного account | | Явный аргумент инструмента | не поддерживается | | e2e real tests (api_key) | TEST_DOMAIN / TEST_API_KEY в .env или CI secrets | | e2e real tests (user_token) | TEST_USER_TOKEN или TEST_USER_TOKEN_BASE_URL + TEST_USER_LOGIN + TEST_USER_PASSWORD | | Проектный .env (runtime) | используется только для infra-конфига (DATABASE_URL, STORAGE_ENCRYPTION_KEY, transport settings) |
Production notes
- Rate limiting по умолчанию in-memory и process-local. Для multi-worker /
multi-instance production задайте REDIS_URL, чтобы web и bearer limit state использовали общий backend.
- Redis backend задаёт bounded connect/socket/operation timeouts. При временной
недоступности Redis default policy fail-open деградирует в process-local fallback и увеличивает vetmanager_rate_limit_backend_degraded_total{reason}; RATE_LIMIT_REQUIRE_REDIS=1 делает init/runtime failures fail-closed.
- Текущий CSRF/session hardening рассчитан на single-instance deployment с
общим WEB_SESSION_SECRET; при горизонтальном масштабировании нужен единый secret и согласованный deployment policy.
- Для production рекомендуется:
- явный
WEB_SESSION_SECRET; - явный
STORAGE_ENCRYPTION_KEY; - раздельное хранение этих двух секретов;
WEB_TRUSTED_PROXY_IPS=<ip1,ip2>только если сервис реально стоит за
доверенным reverse proxy;
WEB_ENABLE_HSTS=1за HTTPS reverse proxy;- внешний rate limit на
/registerи/login. - Для error tracking в production:
- задавать только production DSN;
- держать
ERROR_TRACKING_TRACES_SAMPLE_RATE=0или низкое значение, если
нужен только exception tracking;
- проверять, что reverse proxy не добавляет в headers чувствительные данные,
которые не нужны приложению.
- Billing-resolved Vetmanager host теперь принимается только как bare HTTPS
origin: без userinfo, custom port и path/query/fragment.
- Отдельные deployment notes по security baseline этапа 44:
artifacts/security-deployment-notes-vetmanager-mcp-ru.md.
- Observability runbook этапа 45:
artifacts/observability-runbook-vetmanager-mcp-ru.md.
- Operations readiness baseline этапа 47:
artifacts/operations-readiness-vetmanager-mcp-ru.md.
- Release checklist этапа 47:
artifacts/release-checklist-vetmanager-mcp-ru.md.
Тест подключения
После запуска сервера и настройки mcp.json в чате Cursor попросите:
«Покажи список клиентов клиники» — инструмент
get_clientsвызовется с bearer-derived account context.
Деплой на сервер
Предусловие: ssh-copy-id user@host выполнен.
Прод-хост проекта: <your-domain> (например, mcp.example.com).
# Первичная настройка (один раз)
./scripts/init_server.sh user@host
# Обновление кода и перезапуск
export FEEDBACK_FINGERPRINT_PEPPER="<stored-production-pepper>"
./scripts/deploy_server.sh user@host
По умолчанию код размещается в /opt/vetmanager-mcp. Альтернативный путь:
./scripts/init_server.sh user@host /srv/vetmanager-mcp
export FEEDBACK_FINGERPRINT_PEPPER="<stored-production-pepper>"
./scripts/deploy_server.sh user@host /srv/vetmanager-mcp
Используйте тот же сохранённый FEEDBACK_FINGERPRINT_PEPPER, что и для автоматического deploy; не генерируйте новое значение для каждого запуска.
Режим для приватного репозитория: rsync + deploy
Если сервер не может делать git clone/pull (приватный repo), используйте синхронизацию кода по SSH:
export FEEDBACK_FINGERPRINT_PEPPER="$(openssl rand -hex 32)" # один раз для production, хранить как secret
./scripts/sync_and_deploy_server.sh root@<your-server-ip> /opt/vetmanager-mcp
FEEDBACK_FINGERPRINT_PEPPER нужно сгенерировать один раз, сохранить постоянно на уровне STORAGE_ENCRYPTION_KEY и переиспользовать во всех последующих deploy. Не регенерируйте его перед каждым запуском: смена значения ломает сопоставление уже сохранённых feedback fingerprints без отдельной миграции.
Скрипт:
- синхронизирует проект через
rsync(без.git,.env, служебных директорий); - запускает
deploy_server.shсSKIP_GIT_PULL=1; - выполняет те же smoke-check и TLS-check, что обычный deploy.
Post-deploy smoke
Локально или на сервере можно прогнать:
./scripts/post_deploy_smoke_checks.sh
./scripts/post_deploy_smoke_checks.sh http://127.0.0.1:8000 <your-domain>
Если /metrics защищён METRICS_AUTH_TOKEN, smoke script автоматически передаёт Authorization: Bearer $METRICS_AUTH_TOKEN.
Полностью автоматический деплой после push в main
Добавлен workflow: .github/workflows/deploy-prod.yml.
Он срабатывает после успешного workflow Tests для ветки main и делает:
rsyncкода на прод-сервер;- запуск
deploy_server.shв режимеSKIP_GIT_PULL=1.
Нужные GitHub Secrets:
PROD_SSH_TARGET(пример:root@<your-server-ip>)PROD_SSH_PRIVATE_KEY(приватный ключ для SSH)FEEDBACK_FINGERPRINT_PEPPER(обязателен для production/PostgreSQL feedback fingerprints)PROD_REMOTE_DIR(опционально, по умолчанию/opt/vetmanager-mcp)PROD_SSL_DOMAIN(опционально, ваш домен)PROD_CERTBOT_EMAIL(опционально, email для certbot)
FEEDBACK_FINGERPRINT_PEPPER храните как долгоживущий production secret: он должен оставаться тем же между deploy и не ротироваться без плана миграции исторических feedback fingerprints.
TLS (Let's Encrypt) и автообновление
init_server.sh настраивает nginx reverse proxy, а deploy_server.sh автоматически вызывает проверку сертификата:
- если сертификата нет — будет первичный выпуск;
- если до истечения осталось меньше 30 дней — будет выполнено продление;
- после обновления сертификата
nginxперезагружается автоматически.
При необходимости можно переопределить домен и email для certbot:
SSL_DOMAIN=mcp.example.com CERTBOT_EMAIL=ops@example.com \
./scripts/init_server.sh root@<your-server-ip>
SSL_DOMAIN=mcp.example.com CERTBOT_EMAIL=ops@example.com \
./scripts/deploy_server.sh root@<your-server-ip>
Обязательные внешние условия:
- DNS A-record вашего домена должен указывать на IP сервера;
- на сервере/в облачном firewall должны быть открыты порты
80/tcpи443/tcp.
Прод-конфиг Cursor MCP (локально, не в репозиторий)
Добавьте отдельный сервер в локальный ~/.cursor/mcp.json.
Только прод:
{
"mcpServers": {
"vetmanager-prod": {
"url": "https://<your-domain>/mcp",
"headers": {
"Authorization": "Bearer vm_st_prod_service_token"
}
}
}
}
Оба сервера (локальный + прод) одновременно:
{
"mcpServers": {
"vetmanager-local": {
"url": "http://localhost:8000/mcp",
"headers": {
"Authorization": "Bearer vm_st_local_service_token"
}
},
"vetmanager-prod": {
"url": "https://<your-domain>/mcp",
"headers": {
"Authorization": "Bearer vm_st_prod_service_token"
}
}
}
}
Можно подключить разные account/token пары к разным серверам или использовать разные bearer-токены для разных записей в mcpServers.
Bearer-токен должен храниться только в локальном mcp.json пользователя или в секретах CI, но не в репозитории. Vetmanager domain и api_key должны храниться только внутри storage-слоя сервиса в зашифрованном виде.
MCP-инструменты
Инструменты работают по bearer-only контракту: runtime credentials берутся только из Authorization: Bearer <service_token>, а Vetmanager credentials резолвятся через account context. Параметры limit (1–100) и offset (0–10 000) защищены от случайных массовых выборок.
get_daily_schedule поддерживает постраничное чтение насыщенного дневного расписания: если ответ содержит has_more=true, следующий вызов нужно делать с offset=next_offset. Поля data.admission и data.totalCount сохранены для совместимости. Если truncated=true, но has_more=false, значит данных больше, но безопасного следующего offset нет: либо достигнута граница offset (pagination_limit_reached=true), либо upstream вернул пустую страницу без продвижения (pagination_stalled=true). В таком случае нужно сузить дату/врача/ клинику, а не повторять тот же offset.
Контракт tools/list
MCP-клиенты могут использовать tools/list как источник истины по возможностям сервера. Для каждого зарегистрированного инструмента сервер публикует:
namedescriptioninputSchema
description формируется из актуальных docstrings инструментов и не должен содержать runtime credentials. Начиная с этапа 18 descriptions также включают доменные синонимы из справочника сущностей Vetmanager, чтобы LLM лучше сопоставлял пользовательские формулировки вроде хозяин, запись на приём, приходная накладная, остаток на складе с правильными MCP-инструментами. inputSchema отражает реальные типы аргументов и ограничения вроде limit: 1..100.
Agent feedback
Инструмент report_problem позволяет LLM-агенту сообщить о подозрительной ошибке, нехватке инструмента/параметра, плохом description или несовпадении документации с реальным поведением. Он доступен для активных bearer-токенов без отдельного business-scope, но не принимает runtime credentials и не должен получать raw Vetmanager payload, bearer token, API key, пароли или персональные данные.
Агент должен вызывать report_problem не только при явной ошибке, но и even when the tool call succeeded, если результат не позволяет качественно ответить пользователю: empty result but relevant records were expected, response is missing fields needed to answer, tool description/docs promised or implied a capability that the result does not provide, missing tool, parameter, filter, sort, pagination, or date semantics blocks a reasonable request, workaround was necessary because no direct tool or parameter exists, или successful response is suspicious, inconsistent, or not enough to answer. Do not call report_problem for legitimately empty results. Do not paste raw tool response bodies, raw record IDs, user's verbatim message, or full error payloads.
Feedback хранится в БД в agent_feedback_reports после redaction/truncation. Агентам нужно описывать форму проблемы, а не данные: вместо ФИО клиента, клички пациента, телефона и адреса использовать <client>, <patient>, <phone>, <address>. После этапа 150 отчёты получают possible_pii flag: старые ручные/model reports помечаются консервативно, новые reports получают флаг при privacy-like redaction или placeholders; auto-events остаются possible_pii=false, потому что не сохраняют raw error text. Проверенные workaround-и хранятся отдельно в known_issues; только они могут вернуться агенту как deterministic playbook. Runtime не вызывает LLM и не делает автоисправления кода. Offline triage выполняется через scripts/triage_agent_feedback.py.
Known-issue bootstrap and write-path diagnostic:
# Run after DB migrations are applied; the script expects known_issues and
# known_issue_match_events tables to exist.
python scripts/seed_known_issues.py --dry-run
python scripts/seed_known_issues.py --apply
# Production-safe diagnostic: pass real non-secret DB ids from an active
# account/token. Do not pass bearer token strings or Vetmanager credentials.
python scripts/seed_known_issues.py diagnostic-auto-event --apply \
--account-id <account_id> --bearer-token-id <bearer_token_id>
Diagnostic --apply requires FEEDBACK_FINGERPRINT_PEPPER; without it the script fails closed. diagnostic-auto-event requires an explicit --apply or --dry-run; a dry-run without identity reports status=skipped, not a successful write-path validation. --apply prevalidates that supplied DB ids exist and are active before it writes synthetic diagnostic rows. Before/after production runs, check runtime logs for feedback_auto_event_failed, known_issue_lookup_failed and known_issue_match_event_write_failed.
Each diagnostic --apply intentionally writes one synthetic auto-event/report with related_tool='__stage157_diagnostic__' to prove the write path. Clean it up after production verification if those rows should not remain in analytics:
BEGIN;
DELETE FROM known_issue_match_events
WHERE related_tool = '__stage157_diagnostic__';
DELETE FROM agent_feedback_reports
WHERE source = 'auto' AND related_tool = '__stage157_diagnostic__';
DELETE FROM known_issues
WHERE related_tool = '__stage157_diagnostic__'
AND title LIKE '[seed:stage157-diagnostic] %';
COMMIT;
Для production/PostgreSQL обязателен FEEDBACK_FINGERPRINT_PEPPER: он нужен, чтобы fingerprints ошибок хранились как non-reversible HMAC. Операторы должны запускать python scripts/triage_agent_feedback.py retention-cleanup --days 180 не реже одного раза в месяц после triage; активные new/grouped reports сохраняются до ручного разбора.
Универсальные sort/filter для list GET
Во всех list get_* инструментах поддерживаются дополнительные параметры:
sort: массив объектов{"property":"<field>","direction":"ASC|DESC"}filter: массив объектов{"property":"<field>","value":<value>,"operator":"<op>"}
Поддерживаемые операторы фильтра:
=,!=,<><,<=,>,>=in,not in(valueдолжен быть массивом)like
Пример:
{
"limit": 20,
"offset": 0,
"sort": [{"property": "id", "direction": "DESC"}],
"filter": [{"property": "id", "value": 10, "operator": ">="}]
}
Глобальные уведомления messages/*
Добавлены операционные инструменты для внутренних уведомлений Vetmanager:
send_message_to_all(message, campaign)send_message_to_users(message, campaign, user_ids)send_message_to_roles(message, campaign, roles)get_message_reports(limit, offset, campaign="", sort=None, filter=None)
get_message_reports поддерживает обычный list-контракт limit/offset/sort/filter и дополнительный query-параметр campaign, потому что этот сценарий нужен для получения статуса конкретной рассылки.
Кеширование GET-запросов
- Все успешные GET-запросы к Vetmanager API кешируются in-memory на 15 минут.
- Ключ кеша:
METHOD + canonical_full_url_with_sorted_query + api_key_hash + account_id. api_key_hash— отпечаток (sha256) API-ключа, изолирует кеш между разными ключами.account_id— добавлен с этапа 54.2.3 для строгой изоляции между аккаунтами
даже при случайном совпадении api_key. Fallback на none для legacy caller'ов без account-контекста.
- Кеш-tag:
domain:entity, гдеentityберётся из пути/rest/api/<entity>/.... - После успешного
POST/PUT/DELETEкеш для соответствующего тегаdomain:entityинвалидируется. - Ограничение подхода: кеш живёт только в памяти процесса и полностью сбрасывается при рестарте сервера.
114 инструментов по 14 группам сущностей:
| Группа | Инструменты | Кол-во | |--------|-------------|--------| | Client | get_clients, get_debtors, get_client_by_id, get_personal_account_link_by_phone, create_client, update_client, delete_client, get_client_profile, get_inactive_clients | 9 | | Pet | get_pets, get_pet_by_id, create_pet, update_pet, delete_pet, get_pet_profile, get_inactive_pets | 7 | | Admission | get_admissions, get_admission_by_id, create_admission, update_admission, get_client_upcoming_visits, get_daily_schedule | 6 | | MedicalCard | get_medical_cards, get_medical_cards_by_client_id, get_medical_card_by_id, create_medical_card, update_medical_card, get_vaccinations | 6 | | Invoice | get_invoices, get_average_invoice, get_invoice_by_id, update_invoice, delete_invoice | 5 | | Finance | get_payments, get_payment_by_id, get_invoice_documents, get_invoice_document_by_id, delete_invoice_document, get_closing_of_invoices, get_closing_of_invoice_by_id, get_cassas, get_cassa_by_id, get_cassa_closes, get_cassa_close_by_id | 11 | | Good | get_goods, get_good_by_id, search_invoice_goods, get_good_combination, calculate_good_combination_price, create_good, update_good | 7 | | User | get_users, get_user_by_id, update_user | 3 | | Warehouse | get_good_groups, get_good_group_by_id, get_good_sale_params, get_good_sale_param_by_id, get_party_accounts, get_party_account_by_id, get_party_account_docs, get_party_account_doc_by_id, get_store_documents, get_store_document_by_id, get_suppliers, get_supplier_by_id, create_supplier, update_supplier, get_good_stock_balance | 15 | | Clinical | get_hospitalizations, get_hospitalization_by_id, create_hospitalization, update_hospitalization, get_hospital_blocks, get_hospital_block_by_id, get_diagnoses | 7 | | Reference | get_breeds, get_breed_by_id, get_pet_types, get_pet_type_by_id, get_cities, get_city_by_id, get_city_types, get_streets, get_street_by_id, get_units, get_unit_by_id, get_roles, get_role_by_id, get_user_positions, get_user_position_by_id, get_combo_manual_names, get_combo_manual_name_by_id, get_combo_manual_items, get_combo_manual_item_by_id | 19 | | Operations | get_clinics, get_clinic_by_id, get_timesheets, get_timesheet_by_id, create_timesheet, get_properties, get_anonymous_clients, send_message_to_all, send_message_to_users, send_message_to_roles, get_message_reports | 11 | | Schedule | get_doctor_free_slots | 1 | | Report AI | get_report_ai_prompt_helper, create_report_ai_job, get_report_ai_job, confirm_report_ai_job_candidate, get_report_ai_job_data, start_report_export, get_report_export_file, get_report_ai_job_export, save_report_ai_job_as_report | 9 |
Payment REST API доступен только на чтение: Vetmanager Payment entity разрешает restList/restView, поэтому MCP не публикует create_payment.
Ограничения CRUD по API
Некоторые операции запрещены контроллерами Vetmanager REST API:
| Сущность | Запрещено | Причина | |----------|-----------|---------| | Admission | DELETE | явно запрещён в filterRestAccessRules | | MedicalCards | DELETE | явно запрещён в filterRestAccessRules | | Payment | CREATE, UPDATE, DELETE | только restList + restView | | Hospital | CREATE, DELETE | явно запрещены | | User | CREATE, DELETE | явно запрещены | | Suppliers | DELETE | не в whitelist | | Cassa, PartyAccount, StoreDocument, Properties, HospitalBlock | CUD | только чтение |
Полная матрица: artifacts/api_crud_permissions-ru.md.
MCP Prompts
20 готовых шаблонов для типовых сценариев — LLM использует их для составления цепочек вызовов инструментов:
Prompts работают по тому же bearer-only контракту, что и tools: они принимают только бизнес-параметры сценария. Runtime credentials не передаются в prompt arguments и не должны прокидываться в tool calls.
| Категория | Промпты | |-----------|---------| | Администратор | daily_schedule, find_client, client_balance, book_appointment, doctor_workload, unconfirmed_appointments | | Врач | pet_history, last_vaccinations, add_medical_note, current_inpatients, pet_invoices, pet_full_profile | | Финансы | daily_revenue, unpaid_invoices, popular_services | | Склад и клиентская база | search_good, low_stock, new_clients, client_no_visit | | Аналитика | report_ai_prompt_helper |
Report AI для ad-hoc отчётов
Когда пользователю нужен отчёт, выборка, группировка, тренд или бизнес-условие, которое проще получить через конструктор отчётов Vetmanager, агент должен сначала вызвать tool get_report_ai_prompt_helper или prompt report_ai_prompt_helper, затем сформировать русский intent_text и создать async job через create_report_ai_job. Если пользователь уже дал готовый русский intent_text, helper не является обязательным preflight.
Базовый flow:
get_report_ai_prompt_helperилиreport_ai_prompt_helper— получить правила формулировки intent и ограничения.create_report_ai_job(intent_text)— создать Report AI job.get_report_ai_job(job_id)— poll с bounded retry; статусыqueued/processingозначают ожидание,failedвозвращается пользователю как ошибка источника.- Если статус
needs_confirmation, выбрать толькоreport_idизjob.candidatesи вызватьconfirm_report_ai_job_candidate. - Если статус
existing_report_matched, сразу вызватьget_report_ai_job_data. - Если статус
ready_to_saveи нужны строки, явно вызватьsave_report_ai_job_as_reportс вменяемым названием отчёта; это write-tool, отчёт станет видимым в Vetmanager. get_report_ai_job_data(job_id)— получитьcolumns,rows,total,limited.
save_report_ai_job_as_report нельзя прятать внутри read-only сценария: пользователь или вызывающий агент должен понимать, что создаётся persistent report. Для этого есть preset Analytics (report_ai): full read-only scopes + report_ai.write, без общего Full access. Для названий использовать короткие осмысленные заголовки с вопросом, периодом и MCP-origin, например MCP debtors by negative balance 2026-06-15.
Для больших таблиц get_report_ai_job_data ограничен Vetmanager upstream: если limited=true, JSON rows обрезаны. Сначала стоит сузить отчёт периодом, фильтрами или агрегацией. Полный CSV/XLSX export — fallback-only путь, когда пользователю всё ещё нужны все строки и уже известен report_id:
start_report_export(report_id, filter_json=None)— fallback-only запуск/rest/api/report/StartReport; использовать только если пользователь далreport_id, явно просит CSV/XLSX илиlimited=trueтребует полного export иreport_idдоступен.get_report_export_file(report_file_id)— fallback-only follow-up послеstart_report_export; получаетhtml_file,csv_file,csv_semicolon_file,xlsx_file; если генерация ещё идёт, повторить вызов после задержки.get_report_ai_job_export(job_id, filter_json=None)— convenience fallback только дляsaved/existing_report_matchedjobs сjob.report_id; не сохраняетready_to_savejobs автоматически.
Ограничения export flow: список отчётов по REST не опубликован, поэтому list_reports tool нет; export работает только для отчётов с включённым REST access (allow_rest_api=1). AI reports, сохранённые текущим upstream path, могут возвращать not REST-exportable. Значения html_file/csv_file/csv_semicolon_file/xlsx_file считать чувствительными ссылками/путями к bulk clinic data.
Product metrics (ad-hoc report)
Для быстрого ответа на «сколько живых аккаунтов, кто мёртв, сколько токенов выдано, какие failures» — /product-metrics skill + scripts/product_metrics_report.py. Запускается on-demand через SSH → docker compose exec mcp.
Примеры: `` /product-metrics # default 30-day window, top-10 /product-metrics --top-n=5 # top-5 tables /product-metrics --format=json # машинно-читаемый ``
Метрики (не покидают prod-сервер при обычном просмотре; email маскируются al@ex.com — маскирование снижает случайный disclosure, но это не анонимизация: в небольшой customer base и при characteristic доменах результат остаётся частично re-identifiable. Отчёт предназначен для owner-local просмотра):
- Accounts: total / new 24h-7d-30d / live (req within 7d) / dead (reg>30d, 0 req in 30d) / no tokens / no active connection + dead-accounts table.
- Tokens: active / expiring in 7d / issued 24h / revoked 24h-7d.
- Requests: total 24h-7d-30d + top-N accounts by 30d request count.
- Failures (24h / 7d / 30d breakdown): rate_limited, revoked, expired, ip_denied, no_scopes, no_connection.
- Feedback: reports 24h-7d-30d, new/possible-PII 30d, breakdowns by source/status/severity/category, top related tools, known-issue match events 7d-30d and top known issues by 30d match count. Feedback section outputs aggregates only; raw report text, account ids and bearer token ids are not included.
Параллельно в Prometheus-expose'е /metrics копится vetmanager_business_events_total{event=...} counter для будущих Grafana-дашбордов без изменения кода.
CI/CD
| Воркфлоу | Триггер | Что делает | |----------|---------|------------| | test.yml | push / pull request → main | два обязательных contour job: fast и default | | test-real.yml | вручную (workflow_dispatch) | opt_in_real contour: real API e2e и opt-in real browser tests | | deploy-prod.yml | автоматически после успешного test.yml на main | rsync кода на сервер, docker build, restart, smoke checks |
Добавить secrets для real tests:
VETMANAGER_TEST_API_KEY- опционально
VETMANAGER_TEST_USER_TOKEN - либо
VETMANAGER_TEST_USER_LOGINиVETMANAGER_TEST_USER_PASSWORD - для real browser tests дополнительно включать
RUN_REAL_BROWSER_TESTS=1
Артефакты
| Путь | Назначение | |------|------------| | artifacts/prd-vetmanager-mcp-ru.md | Требования к продукту: видение, цели, персоны, функциональные и нефункциональные требования | | artifacts/technical-requirements-vetmanager-mcp-ru.md | Технические требования: архитектура, стек, структура проекта | | artifacts/api_entity_reference-ru.md | Справочник по сущностям Vetmanager API (38 сущностей) | | artifacts/api_crud_permissions-ru.md | Матрица разрешённых CRUD-операций по Vetmanager REST API | | artifacts/api-research-notes-ru.md | Накопленные неочевидные знания об API (чеклист полей, filter operators, edge cases) — читать перед work with admission/pet/medical_card/timesheet | | artifacts/vetmanager_openapi_v6.json | Спецификация OpenAPI v6 для Vetmanager REST API | | artifacts/vetmanager_postman_collection.json | Коллекция Postman для ручного тестирования | | artifacts/review/*.md | Периодические deep-review (super-review) отчёты с findings + Codex arbitration | | artifacts/security-deployment-notes-vetmanager-mcp-ru.md | Чек-лист для production deploy'я (если присутствует) | | artifacts/observability-runbook-vetmanager-mcp-ru.md | Runbook для операций с метриками и логами (если присутствует) | | artifacts/operations-readiness-vetmanager-mcp-ru.md | Go/No-Go чеклист готовности к прод (если присутствует) | | artifacts/release-checklist-vetmanager-mcp-ru.md | Регламент релиза (если присутствует) |
Self-hosted / Развернуть у себя
Этот проект — open-source. Вы можете развернуть собственный экземпляр MCP-сервера для вашей клиники или организации.
Что нужно:
- Сервер с Docker (любой VPS/dedicated)
- Домен с DNS A-record на IP сервера
- SSH-доступ к серверу
git clone https://github.com/otis22/vetmanager-mcp.git
cd vetmanager-mcp
./scripts/init_server.sh root@<your-server-ip>
./scripts/deploy_server.sh root@<your-server-ip>
Скрипт init_server.sh автоматически настроит Docker, PostgreSQL, Nginx reverse proxy и TLS-сертификат через Let's Encrypt. Подробнее — в секции Деплой на сервер.
Contributing
Проект открыт для контрибуций. Если вы нашли баг или хотите предложить улучшение:
- Открыть issue
- Security-уязвимости — сообщайте приватно
Перед отправкой PR убедитесь, что тесты проходят: docker compose --profile test run --rm test.






