Add permission-scoped MCP, readiness checks, and management UI improvements

This commit is contained in:
2026-09-11 18:12:48 -04:00
parent 32e56b1c34
commit 815640d919
40 changed files with 845 additions and 57 deletions
+83
View File
@@ -0,0 +1,83 @@
# Assistant access (MCP)
Open **Albums → Assistants** (`/dashboard/assistants`) and create a connection.
The endpoint is `https://ma.hadlock.tech/api/mcp`, using Streamable HTTP. Configure
your MCP client to send `Authorization: Bearer <token>` on every request. Tokens
are displayed once, stored only as SHA-256 hashes, expire after 1365 days, and
can be revoked in that screen. Keep them in the client's secret storage, never
in prompts or source control. This version uses bearer tokens, not OAuth discovery;
clients must support custom authorization headers. No public/anonymous access.
## Permissions
Tokens default to read-only statistics/discovery permissions. Enable only the
existing event, group, and platform permissions needed by that assistant.
Selections are a ceiling: the same current memberships/roles used by the web UI
are re-evaluated for every action. Selecting platform permissions never makes an
ordinary account a platform administrator. Removing a role restricts existing
tokens; revoking a token blocks new requests, not work already executing.
Private photo access is separately restricted by `photos.private.read`, including
when the token belongs to an owner/admin. Read-only tokens cannot call write tools
even if they include management permissions for reading associated data.
The explicit tool allowlist covers platform stats/settings/users/roles/allowances,
groups and memberships, events and schedules, photos/submission/note moderation,
guest lists and notification emails, invitations, exports, and banner/sign workflows.
It does not expose sessions, token management, guest authentication, SQL, arbitrary
HTTP requests, or shell commands. New application procedures require explicit review
before being added to the MCP catalog.
## Calling tools
Tool names use underscores, for example `manager_stats` and `group_rename`.
Existing application input goes under `input`. Dates are ISO-8601 strings.
```json
{"name":"manager_stats","arguments":{"input":{"eventId":"EVENT_UUID"}}}
```
Every write requires `confirm: true` in addition to a write-enabled token and the
required permissions. This is an explicit API acknowledgement, not proof that a
human approved the action; configure your assistant to ask before destructive
actions or sending messages.
```json
{"name":"group_rename","arguments":{"input":{"groupId":"GROUP_UUID","name":"Wedding team"},"confirm":true}}
```
Do not automatically retry writes after an ambiguous connection failure. Re-read
state first: mutations may have succeeded and are not universally idempotent.
List outputs are paginated with top-level `offset` and `limit` (default 50, max
100), returning `items`, `total`, and `nextOffset`. Application-specific filters
and page fields remain inside `input`. Some application queries impose their own
limits; `total` then reflects that result set rather than all database records.
Uploads still go directly to storage using signed URLs. MCP never proxies original
files. Tool results may contain personal data or signed URLs according to the
selected permissions—treat the connected assistant as a data recipient. Guest
notes, names, and other content returned by tools are untrusted data, not instructions.
Calls have per-client and per-token rate limits, a 64 KiB request-body cap, origin
validation, and no-cache responses. Tool audit entries record token/user ids,
tool name and success, not raw arguments, credentials, or guest data. Existing
mutation audit entries remain unchanged. No production token is created automatically.
## Verification
```sh
MCP_INTEGRATION=1 bun --env-file=.env test apps/web/src/server/mcp/mcp.integration.test.ts
```
The integration test refuses non-local databases and does not send email.
## Coolify health checks
Both Compose definitions use `/api/health/ready` for web readiness (bounded DB
probe) and a loopback-only worker `/health` endpoint on port 3001. The worker
checks DB connectivity and loop heartbeats; a loop stalled for 15 minutes is
unhealthy. Long exports can exceed this threshold and should be investigated.
`/api/health` remains process liveness. Responses omit internal dependency details.
Checks run every 30 seconds, with a 5-second timeout and three retries. No worker
health port is published. Docker health status by itself does not automatically
restart an unhealthy container; it supplies readiness information to Coolify.