vetmanager-mcp
Enables managing veterinary clinic operations via natural language by integrating Vetmanager REST API with MCP clients.
README
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.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.
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) защищены от случайных массовых выборок.
Контракт 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.
License
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。