On this page
What this isWhat's actually in this PRWhat this PR deliberately does not doWhy it's built this wayClaude MCP server — Phase 1
Status: unreleased, draft. This PR (feat/claude-mcp, #425) is explicitly marked "not for review or merge yet" by its author. It exists to exercise a real deployment — HTTPS, cookies, real subdomains — before the feature is finished. Nothing here is available to clinics today, and the MCP server itself is not deployed anywhere yet, including the sandbox this walkthrough was captured from.
What this is
A new package, packages/claude-mcp, that lets a doctor connect Claude directly to their own clinic's data instead of exporting spreadsheets and pasting them into a chat. Once connected, a doctor can ask Claude things like "what's low on stock?" or "how much did we spend with suppliers this year?" and Claude answers by calling tools that query Dentolize's database — scoped to that one clinic, and nothing else.
Phase 1 is read-only, and three things enforce that independently:
- The tools never write — they only run
select. - The database login the tools connect as (
mcp_readonly) has noINSERT,
UPDATE, or DELETE grant on anything. Postgres refuses a write before the application code is even involved.
- Row-level security on every inventory table means a query that forgot its
tenant filter returns zero rows, not another clinic's rows.
What's actually in this PR
| Area | What it is |
|---|---|
packages/claude-mcp | The MCP server itself: an OAuth resource server (validates tokens), a connection pool scoped per-request to one clinic, and three inventory-reporting tools. |
packages/server/src/apis/oauth/** | The OAuth 2.1 authorization server — the part that issues tokens. Lives inside the existing Dentolize API, reusing its login, password hashing, and 2FA. |
packages/prisma | One migration adding three tables: OAuthClient, OAuthAuthorizationCode, OAuthToken. |
packages/clinic-web | Small changes to both existing login screens so they can return a doctor to an in-progress OAuth connection instead of dropping them on the dashboard. |
package.json, rollout.sh | Four new yarn scripts and one new build step in the deploy script — additive only. |
What this PR deliberately does not do
- The MCP server is not wired into the deployed stack. It's absent from
docker-compose.yml, ecosystem.config.js, and this sandbox's environment. Only a GitHub Actions workflow (.github/workflows/claude-mcp.yml) runs it, in CI, against a throwaway database. See Walkthrough for what that means for what you can and can't see live.
- Only three tools exist, all inventory: current stock levels, consumption
over a time window, and supplier spend. Five OAuth scopes are already defined (inventory:read, finance:read, appointments:read, patients:read, clinical:read) so future phases can add tools without a new authorization model, but only inventory:read is used by anything today.
- No management screen. A doctor or owner cannot yet see or revoke their
own Claude connections from clinic-web. The design document (packages/claude-mcp/OAUTH-DESIGN.md) calls this out explicitly as future work.
- No write capability of any kind. Every tool is a report. Nothing a
doctor asks Claude can change a record.
Why it's built this way
The two things a system like this must never do are: mix up two clinics' data, or let a compromised token write anything. The design leans on infrastructure, not code discipline, to guarantee both — a database role that cannot write, and row-level security that makes cross-tenant leakage fail closed (zero rows) instead of failing open (someone else's rows). See Tenant isolation and the read-only role for the mechanics, and Security hardening and bugs fixed for the list of real bugs this design caught along the way — several of which were only found by a person clicking through the product, not by an automated test.