@practicemakes/mcp
v26.824.1
Published
MCP server exposing PracticeMakes prod data (read-only) to Claude, authenticated as the calling user's own Firebase identity.
Readme
@practicemakes/mcp
A small MCP server that exposes PracticeMakes production data (read-only) to Claude for admin and development work — and surfaces your Planner backlog so your own task list can drive development.
It authenticates as your own platform identity (your Firebase user), so every result is scoped to your real role and organization by the backend. A SYSADMIN sees the whole platform; a regular user sees only their org. That's what lets the same server be handed to other users on the platform.
How it works
Claude ──stdio──▶ @practicemakes/mcp ──HTTPS Bearer──▶ https://api.practicemakes.io/api/...
(refreshes your Firebase ID token) (existing authz + business logic)No backend changes and no secret credentials — the Firebase Web API key it uses is a public client value (baked in as a default), so each user supplies only a credential for their own account. Read-only.
Use it (published package)
Once published to npm, any platform user connects with one line — nothing to clone or build:
claude mcp add practicemakes \
--env FIREBASE_REFRESH_TOKEN=<your-refresh-token> \
-- npx -y @practicemakes/mcpPoint at a local backend during development with --env PM_API_BASE_URL=http://localhost:9090.
Getting a refresh token
- Password account: create
.env(see.env.example) withPM_EMAIL/PM_PASSWORD, runyarn workspace @practicemakes/mcp login— it prints aFIREBASE_REFRESH_TOKEN=...line to use. - Google/Apple sign-in: log into the web app, open DevTools → Application → IndexedDB →
firebaseLocalStorage→ the auth user record, and copystsTokenManager.refreshToken.
Develop (from the monorepo)
yarn install # at repo root, links the workspace
yarn workspace @practicemakes/mcp build
cp packages/mcp/.env.example packages/mcp/.env # then fill in a credential
yarn workspace @practicemakes/mcp smoke # end-to-end check against prodsmoke prints who you're authenticated as, how many orgs are visible, and your Planner backlog. To run the local build directly in Claude instead of the published package:
claude mcp add practicemakes \
--env FIREBASE_REFRESH_TOKEN=... \
-- node "$(pwd)/packages/mcp/dist/index.js"Publish
Versioned on the monorepo's calendar scheme (26.MMDD.patch). Scoped public package:
yarn workspace @practicemakes/mcp publish # runs prepublishOnly (clean + build) firstTools (read)
| Tool | What it returns |
|---|---|
| list_organizations | Orgs visible to you (SYSADMIN → all) |
| get_organization | One org by UUID |
| get_my_profile | Your profile + entitlements/roles/org context |
| list_profiles | Profiles (SYSADMIN → all, ADMIN → your org) |
| get_profile | One profile by UUID |
| get_my_planner_tasks | Your Planner backlog (drives development) |
| search_tasks | Search tasks; reveal completed/deleted/snoozed via showFields |
| get_task | One task by UUID |
| list_workflows | Your Planner workflows |
| get_workflow | One workflow, hydrated with run state |
| get_billing_config | Billing/entitlement config for your org (takes a required platform) |
| list_servers | Monitored servers + both identifiers the tools below need |
| get_server | One server's config, including its maintenance windows |
| list_server_statuses | Live status of all your servers (is anything down now) |
| get_server_uptime | Uptime % over the last 1–168 hours, from raw heartbeats |
| get_server_uptime_daily | Per-day uptime history beyond heartbeat retention |
| list_server_incidents | Outage history for one server, planned vs unplanned |
Server identifiers
The monitoring tools take two different, non-interchangeable identifiers, and passing the wrong
one returns a 404 that reads like a missing server. list_servers returns both:
| Identifier | Used by | Route family |
|---|---|---|
| serverShortLink | get_server_uptime, get_server_uptime_daily | /api/public/... (unauthenticated) |
| serverId | get_server, list_server_incidents | /api/server/... (authenticated, org-scoped) |
Neither is a UUID — serverId is the operator-assigned id (e.g. web-01).
Tools (write)
Every write tool asks you to confirm the exact request before it sends anything.
| Tool | What it does |
|---|---|
| create_task | Creates a Planner task in your current org context (needs ADMIN there) |
| complete_task | Sets completedAt on a task — the tool that closes the Planner-driven-dev loop |
| switch_org_context | Switches your active org context (SYSADMIN → any org, otherwise invite-only) |
Read this before enabling write tools
These tools run as you, with all of your permissions. Authentication is a per-user Firebase ID token, not a service account — there is no read-only role, no separate service identity, and no server-side allow-list behind them. If your account is SYSADMIN, a write tool can write across every tenant on the platform. The tool list and the confirmation prompt are the entire safety boundary.
Confirmation needs client support. The gate uses MCP elicitation. If your client did not advertise elicitation support during the handshake, there is no channel to ask you on, and every write tool refuses and sends nothing — it will not fall back to writing unconfirmed. The error names the mutation it declined to perform. This is deliberate: silently proceeding would disable the guardrail for exactly the clients that never agreed to be asked.
switch_org_context is session-global. It mutates your profile, so it changes what every
later read tool returns for the rest of the session — not just the next call.
complete_task is a read-modify-write. There is no dedicated completion endpoint; the tool
reads the task, sets completedAt, and re-posts it, carrying the read's ETag back as
If-Match so a concurrent edit is not silently clobbered.
yarn smoke is unaffected — it issues three fixed read-only requests and never touches the tool
registry, so no write can reach production through it.
Roadmap
- Admin actions (e.g. the ARCH-153 org/user purge) — blocked: no executable endpoint exists
yet, only a
GET .../data-inventorythat reports what a purge would do and deletes nothing. - OpenAPI-driven tool generation from the backend's grouped specs (the monorepo already uses
openapi-typescriptinpackages/core). - Hosted remote server (streamable-HTTP + per-user OAuth) so users connect to a URL with nothing to install.
