feat: dedicated paliad MCP — replace supabase-MCP-with-SQL-wrapper for Paliadin DB access #37
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
t-paliad-156 shipped per-user RLS by wrapping every
paliad.*query inBEGIN; SET LOCAL ROLE authenticated; SET LOCAL request.jwt.claims = '...'; <q>; ROLLBACK;and reaching the DB through the generic supabase MCP atystudio.msbls.de. RLS now enforces visibility properly, but the trust boundary is at the prompt layer — SKILL.md says "never query paliad.* without the wrapper", and one forgotten wrapper means a leak. The MCP itself doesn't know about paliad's auth model; it just executes whatever SQL it gets, assupabase_admin(BYPASSRLS) before our wrapper kicks in.A dedicated paliad MCP moves the trust boundary from prompt-discipline to tool-availability: remove
mcp__supabase__execute_sqlfrom the paliadin pane entirely; onlymcp__paliad__*tools exist. Forgetting the wrapper becomes impossible because the unsafe surface is gone.What we get
request.jwt.claimsserver-side once, claude callspaliad_whats_on_my_plate()without any SET LOCAL boilerplate.references/sql-recipes.mdeither disappears or shrinks to an advanced "raw SQL escape hatch" section (clearly walled off).data.*or other schemas. The connection pool authenticates as a role with USAGE onpaliadonly.(user_id, tool_name, args, row_count). Way better than parsing wrapped SQL./api/paliadin/suggest/*HTTP endpoints from t-paliad-161 (deadline / appointment suggestions, approval-pipeline) becomepaliad_suggest_deadline(...)MCP tools. Same auth, same audit, no curl-in-tmux dance.Prior art in m's stack
We already run two MCPs in production, with very different shapes:
mBrian MCP —
~/dev/mBrian/src/mcp/server.tsget_node,list_nodes,create_node,get_edges,get_backlinks,find_similar,get_neighbors, etc. ~30 tools, each with a zod schema and a one-line description that doubles as the recipe.mAi MCP —
~/dev/mAi/internal/api/mcp_handler.go+cmd_mcp_memory.gomcp.godefines the wire format directly).~/.local/bin/maimcp, registered in~/.mcp.jsonascommand: maimcp.list_tasks,create_task,get_messages,send_message,list_projects,register_worker, etc. — domain verbs, not SQL..mcp.json.mai.workers(worker_id → project access). Paliad's per-call auth is JWT-based (auth.uid()→ RLS).Naive paliad MCP would land somewhere between
sqlx.DBpool, themintTurnJWThelper, the typedservices.*layer. Registering them as MCP tools is mostly wire-up.paliad_whats_on_my_plate,paliad_list_my_projects,paliad_get_project_detail,paliad_search_my_deadlines,paliad_list_my_appointments,paliad_lookup_court,paliad_lookup_deadline_rule. 7–10 tools to cover the current recipe set.jwt_path(or raw JWT) parameter; the MCP reads + verifies the signature againstSUPABASE_JWT_SECRET, extracts claims, runs the query inside a tx that setsrequest.jwt.claims. Bad/missing JWT → typed error, no fallback to service-role.Design questions to settle before writing code
paliad_execute_sql(jwt_path, sql)or no? Pros: unblocks novel recipes during dogfood. Cons: reopens the discipline problem (claude could passBEGIN; SET LOCAL ROLE supabase_admin; ...). Likely answer: no for v1; pre-build every recipe; add new tools as new patterns emerge. Revisit if dogfood reveals the tool list is too narrow./tmp/paliadin/<turn>.jwtand the envelope carries|jwt=<path>. Option A: keep file, MCP reads it (filesystem coupling between paliad-shim and paliad-mcp on the same host). Option B: skip the file, paliad-shim hands the JWT directly via tool args (simpler but JWT shows up in MCP stdio logs)..mcp.json. DB connection to youpc supabase over Tailscale — same pattern as paliad itself.postgres(BYPASSRLS). The MCP should connect as a dedicated role with USAGE onpaliadonly + GRANT to switch intoauthenticatedviaSET ROLE. Migration on top of 078.cmd/paliad-mcp/main.goproduces a binary, ships in the same Docker image, gets symlinked to~/.local/bin/paliad-mcpon mRiver via the existing install scripts.Surface sketch (v1)
Tools, all take
jwt_path(orjwtraw, TBD per Q2):paliad_whats_on_my_plate→ overdue / today / this_week / appointments_today countspaliad_list_my_projects→ active projects, paginatedpaliad_get_project_detail(id | slug)→ project + deadlines + appointments + partiespaliad_search_my_deadlines(status?, due_after?, due_before?)paliad_list_my_appointments(from, to)paliad_lookup_court(q)— firm-wide ref, no auth needed but kept in same MCP for surface coherencepaliad_lookup_deadline_rule(q)— samepaliad_suggest_deadline(...)— agent-write, folds in/api/paliadin/suggest/deadlinepaliad_suggest_appointment(...)— sameEach tool's description is the recipe; SKILL.md collapses to "call the relevant
paliad_*tool withjwt_pathfrom the envelope; on missing/invalid JWT the tool returns a structured error you should surface verbatim."Out of scope
data.*(UPC case law in youpc.org). Different schema, different ownership.sqlxdirectly.Done when
cmd/paliad-mcp/main.gobuilds, ships in the paliad image, deploys to mRiver via the existing scripts pattern (scripts/install-paliad-mcpnext toscripts/install-paliadin-skill).paliadintest config swapsmcp__supabase__execute_sqlformcp__paliad__*in.mcp.jsonfor the claude pane.references/sql-recipes.md(or wall it off as raw-SQL escape hatch).paliad_mcpPostgres role withUSAGEon schemapaliadand GRANT toSET ROLE authenticated.mcp__paliad__*, RLS enforces, audit row reflects per-call tool name.Phasing relative to t-paliad-156
Do NOT block on t-156's dogfood. The SQL-wrapper-via-MCP cut shipped today is a working enforcement layer; this issue is the next tightening pass. Right order:
.mcp.jsonon mRiver. Roll back trivially by re-enablingsupabasein the same file.