Domain Model: westside-mcp

arch-domain-westside-mcp Architecture

architecture active

Domain Model: westside-mcp

Diagram




          
  

Components

Component Purpose Notes
CONNECTOR The registered remote MCP URL in claude.ai One per Claude account; iOS auto-syncs
OAUTH_CLIENT OAuth 2.0 client gating access Shared <code>mcp-remote-auth</code> infra
USER The Claude account that owns the connector v1: westsidebasktball@gmail.com only
SESSION A single Claude conversation with MCP state Implicit — tracked via OAuth token
TOOL A capability exposed over MCP v1: ping, get_catalog, query
TOOL_CALL One invocation of a tool by Claude Logged to Loki
QUERY A SELECT executed against the replica Safety: READ ONLY, row_limit, timeout
DB_ROLE Postgres role with explicit SELECT grants Created by pal-e-services terraform
CNPG_CLUSTER The basketball-api Postgres cluster Untouched; MCP connects to <code>-ro</code> endpoint
CATALOG Schema map Claude reads before writing SQL Layer 1 live + Layer 2 YAML (reactive)
AUDIT_LOG Logical record of what Marcus asked Physically lives in Loki, not Postgres

Key Decisions

  • One USER in v1. Single-tenant keeps auth simple. Multi-user would require per-user roles and row-level security — out of scope until a second human needs access.
  • CATALOG is derived, not stored. Treating it as an entity clarifies that Claude reads it, but physically it regenerates on pod start from information_schema. No schema drift possible between "what the catalog says" and "what the DB actually has."
  • AUDIT_LOG is logical, not physical. Persisting to Postgres would require write grants — defeating the point. Loki gives us the same query surface ("what did Marcus ask yesterday?") without any write path.
  • basketball-api is deliberately absent. The MCP does not talk to the API. Keeping it off the domain model makes this invariant visible.
  • DB_ROLE is the enforcement boundary. Not the app, not OAuth — Postgres role grants. This is the only layer that physically cannot be bypassed by code bugs.