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.phpcapp_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.
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=...}— fixed allowlist business events fromservice_metrics._ALLOWED_BUSINESS_EVENTS, including account/token/OAuth lifecycle events and token-expiry warning events;vetmanager_token_preset_issued_total{preset}— issuance counter by access preset;vetmanager_activation_funnel_accounts{stage}— aggregate activation funnel gauges (registered,connected,with_active_tokens,ready_for_mcp,with_recent_usage_7d) without account/email/domain labels;ready_for_mcpmeans an active Vetmanager connection and a live token, whilewith_active_tokenscan expose stale-token/no-connection gaps;with_recent_usage_7dis a freshness signal and is stricter than the account UI checklist, which marks a usable token as connected after any successful historical request;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.
Все клиенты 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
Рабочий 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.
Пользовательский 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 —
report_ai/Analytics, чтобы новые токены могли работать с Report AI;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.
cp .env.example .env # задайте UID/GID и LOG_LEVEL при необходимости
docker compose up -d mcphttp://localhost:8000/register— регистрация accounthttp://localhost:8000/login— вход в accounthttp://localhost:8000/account— кабинет после входа, включая Vetmanager integration, выпуск Bearer-токенов и их список
{
"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 |
|---|---|
| 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) |
- 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; не генерируйте новое значение для каждого запуска.
Если сервер не может делать 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-mcpFEEDBACK_FINGERPRINT_PEPPER нужно сгенерировать один раз, сохранить постоянно
на уровне STORAGE_ENCRYPTION_KEY и переиспользовать во всех последующих
deploy. Не регенерируйте его перед каждым запуском: смена значения ломает
сопоставление уже сохранённых feedback fingerprints без отдельной миграции.
Скрипт:
- синхронизирует проект через
rsync(без.git,.env, служебных директорий); - запускает
deploy_server.shсSKIP_GIT_PULL=1; - выполняет те же smoke-check и TLS-check, что обычный deploy.
Локально или на сервере можно прогнать:
./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.
Добавлен 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.
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.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-слоя сервиса в зашифрованном виде.
Инструменты работают по 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.
MCP-клиенты могут использовать tools/list как источник истины по возможностям сервера.
Для каждого зарегистрированного инструмента сервер публикует:
namedescriptioninputSchema
description формируется из актуальных docstrings инструментов и не должен
содержать runtime credentials. Начиная с этапа 18 descriptions также включают
доменные синонимы из справочника сущностей Vetmanager, чтобы LLM лучше
сопоставлял пользовательские формулировки вроде хозяин, запись на приём,
приходная накладная, остаток на складе с правильными MCP-инструментами.
inputSchema отражает реальные типы аргументов и ограничения вроде limit: 1..100.
Инструмент 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.
For aggregate-only effectiveness diagnostics without raw report text:
python scripts/triage_agent_feedback.py match-effectiveness --days 30The output shows report counts with/without linked known issues, match events
by source (report, auto, injection) and readiness counts for known issues
whose agent injection would be skipped because a valid playbook is missing.
It intentionally omits report summaries, details and known-issue titles.
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
сохраняются до ручного разбора.
Во всех 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": ">="}]
}Добавлены операционные инструменты для внутренних уведомлений 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-запросы к 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инвалидируется. - Ограничение подхода: кеш живёт только в памяти процесса и полностью сбрасывается при рестарте сервера.
120 инструментов по 15 группам сущностей:
| Группа | Инструменты | Кол-во |
|---|---|---|
| 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_date, get_medical_cards_by_client_id, get_medical_card_by_id, create_medical_card, update_medical_card, get_vaccinations |
7 |
| Invoice | get_invoices, get_average_invoice, get_invoice_by_id, update_invoice, delete_invoice |
5 |
| Finance | get_payments, get_payment_by_id, get_client_payment_applications, get_revenue_summary, 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 |
13 |
| 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 |
| Feedback | report_problem |
1 |
Payment REST API доступен только на чтение: Vetmanager Payment entity разрешает restList/restView, поэтому MCP не публикует create_payment. В реальном Payment REST нет прямых client_id/pet_id; для клиентского или pet-scoped контекста оплат используйте get_client_payment_applications, который читает применения оплат через closingOfInvoices.
Некоторые операции запрещены контроллерами 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.
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 |
Когда пользователю нужен отчёт, выборка, группировка, тренд или бизнес-условие, которое проще получить через конструктор отчётов 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, показатьjob.candidates, выбрать толькоreport_idиз этого списка и вызвать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и, когда upstream отдаёт,csv_export_url.
intent_text для create_report_ai_job ограничен Vetmanager лимитом 20000 символов. Для сложных отчётов лучше не просто писать длинный текст, а явно указать период, фильтры, метрики, группировки и сортировку. recognized.preview_example_row в статусе job — это LLM-generated пример ожидаемой строки, а не подтверждённая строка из данных клиники.
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 до 10000 JSON rows: если limited=true, rows обрезаны; если total близок к лимиту, не стоит вставлять всю таблицу в чат. Сначала стоит сузить отчёт периодом, фильтрами или агрегацией. Полный CSV/XLSX export — поддержанный bulk-путь, когда пользователю всё ещё нужны все строки и уже известен report_id:
start_report_export(report_id, filter_json=None)— запуск/rest/api/report/StartReport; использовать если пользователь далreport_id, явно просит CSV/XLSX илиlimited=trueтребует полного export иreport_idдоступен.get_report_export_file(report_file_id)— follow-up послеstart_report_export; получаетhtml_file,csv_file,csv_semicolon_file,xlsx_file; если генерация ещё идёт, повторить вызов после задержки с bounded retry.get_report_ai_job_export(job_id, filter_json=None)— convenience export только дляsaved/existing_report_matchedjobs сjob.report_id; не сохраняетready_to_savejobs автоматически.
Ограничения export flow: список отчётов по REST не опубликован, поэтому list_reports tool нет; export работает только для отчётов с включённым REST access (allow_rest_api=1). Новый upstream включает REST access для AI reports, но старые/частично обновлённые контуры могут возвращать отказ. StartReport также может возвращать временные состояния Report creating in progress или can not run a report more than 10 minutes; в этих случаях нужен bounded retry позже, а не создание дубликатов. Значения csv_export_url, html_file/csv_file/csv_semicolon_file/xlsx_file считать чувствительными ссылками/путями к bulk clinic data и не писать в логи.
Для быстрого ответа на «сколько живых аккаунтов, кто мёртв, сколько токенов выдано, какие 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.
- Activation funnel: connected / with tokens / with active tokens / with recent usage (7d) / ready for MCP / needs connection / needs token / needs client use.
- 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-дашбордов без изменения кода.
Production compose поднимает Prometheus и Grafana в профиле production:
- Prometheus:
127.0.0.1:${PROMETHEUS_PORT:-9090}. - Grafana:
127.0.0.1:${GRAFANA_PORT:-3000}. - Prometheus scrapes
mcp:8000/metricsвнутри Docker network. Production deploy требуетMETRICS_AUTH_TOKEN; Prometheus получает его через bearer-token file, токен не инлайнится в config. - Grafana provisioning добавляет datasource
Prometheusи dashboardVetmanager MCP Overview.
Доступ с рабочей машины — SSH-туннель (-N — только форвардинг, без shell):
ssh -N -L 3000:127.0.0.1:3000 -L 9090:127.0.0.1:9090 root@212.193.59.219После этого Grafana доступна на http://127.0.0.1:3000, Prometheus — на
http://127.0.0.1:9090. Если порт занят (Address already in use) — туннель
уже поднят, проверить: pgrep -af "ssh.*212.193.59.219".
Логин в Grafana — учётка админа из server .env:
ssh root@212.193.59.219 'cd /opt/vetmanager-mcp && grep -E "^(GRAFANA_ADMIN_USER|GRAFANA_ADMIN_PASSWORD)=" .env'GRAFANA_ADMIN_USER в .env может отсутствовать — тогда действует дефолт
admin (см. docker-compose.yml).
Публично Grafana не публикуется по умолчанию. Если нужен доступ через nginx, сначала включить basic auth на nginx location и только потом проксировать Grafana. Без basic auth наружу не открывать.
Перед production deploy задать METRICS_AUTH_TOKEN и GRAFANA_ADMIN_PASSWORD
в server .env; пустые значения и defaults вроде admin/password deploy
script отвергает. Anonymous access и sign-up в Grafana выключены.
Dashboard queries используют только низкокардинальные service labels
(tool, endpoint, outcome, target, status, event) и не должны
добавлять email, clinic/customer identifiers, token prefixes или
payload-derived labels.
| Воркфлоу | Триггер | Что делает |
|---|---|---|
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 |
Регламент релиза (если присутствует) |
Этот проект — 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. Подробнее — в секции Деплой на сервер.
Проект открыт для контрибуций. Если вы нашли баг или хотите предложить улучшение:
- Открыть issue
- Security-уязвимости — сообщайте приватно
Перед отправкой PR убедитесь, что тесты проходят: docker compose --profile test run --rm test.