First 5 Queries
You’ve run schemabrain init and restarted your MCP host. This is what to actually do with it for the next 10 minutes. Five queries that exercise each of the four load-bearing mechanisms — read-only tools, PII-aware refusal, audit chain, structured recovery — plus a closing CLI step that proves what happened.
Run these in order against the bundled SaaS fixture (the default if you pressed Enter at the wizard’s URL prompt). They work identically against your own Postgres — only the entity names change.
Prerequisites
schemabrain initcompleted successfully (Stage 7 reported a wired host).- MCP host restarted cold (Cmd+Q on macOS, full quit on Windows, new terminal session on
claude-code). - A new conversation window open.
schemabrain doctor --verify first — it smoke-tests the wiring without needing an Anthropic key. Details in /setup/claude-desktop and siblings.
Query 1 — verify the wire
Ask Claude: list the entities SchemaBrain knows aboutWhat happens. Claude calls
list_entities. With the bundled fixture, the response is the pre-curated entity pack the wizard auto-applies on the demo path — no ANTHROPIC_API_KEY required:
Against your own Postgres (not the demo), Stage 3 of
init runs the LLM-driven entity suggester instead of the bundled pack — names + descriptions will vary, but the shape stays the same: one EntitySummary per inferred entity, each bound to a physical table.
What it proves. The MCP stdio transport is up, the local SQLite store has entities applied, and Claude is calling SchemaBrain tools instead of guessing at your schema. If Claude responds without calling a tool — that’s an unwired host, not a SchemaBrain problem. Run schemabrain doctor --verify.
Query 2 — see the semantic layer with PII tags
Ask Claude: describe the user entityWhat happens. Claude calls
describe_entity(name="user"). The response lists every column on the bound physical table with its PII classification:
EntityDetail shape — including LLM-enriched descriptions, sample values, and description_source — is in the MCP tool reference.)
What it proves. Columns are tagged at index time against the 12-category PII taxonomy. On a zero-config install, --pii-block defaults to credential,government_id,payment_card — the three catastrophic-leak categories. password_hash is tagged credential and redacted: true — the firewall will refuse any query that returns this column. contact is tagged but not blocked by default, so redacted: false on email is correct: the agent sees the tag as advisory metadata and can self-regulate even when policy doesn’t refuse.
Query 3 — run a metric (happy path)
Ask Claude: what’s our total revenue per month?What happens. Claude calls
list_metrics to discover the vocabulary, finds total_revenue (entity invoice, sum of invoice_total_cents, time-dimension invoice.issued_at), then calls get_metric:
default_transaction_read_only=on, and returns one row per month (totals in integer cents — a total_revenue of 613634 is $6,136.34). WHERE-clause values (filter_predicates) bind through SQLAlchemy parameters — the only operator-controlled string baked into the SQL is the validated metric / column / entity identifier set.
What it proves. The semantic-layer substrate works end-to-end. The agent didn’t author SQL — it composed a structured tool call against an operator-defined metric. The compiled SQL is read-only at the session level, runs through a NullPool connection (no session-state leakage between calls), and any operator filter values flow through bound parameters. See /mechanism/read-only.
Query 4 — see the firewall refuse (zero config)
Ask Claude: break our revenue down by the card number on file for each workspaceWhat happens. Claude finds
total_revenue (anchored on invoice) and tries to group it by payment_method.card_number_last4, reached via the bundled canonical join chain invoice → workspace → payment_method (invoice_workspace + workspace_payment_methods). That group-by column is tagged payment_card at index time. The metric compiler propagates PII tags through the joins, sees payment_card in the default blocked set, and refuses before the database is queried. The response is a structured envelope, not prose:
suggested_args.name points Claude back at the metric’s anchor to re-inspect the join path. Claude calls describe_entity(name="payment_method"), sees that card_number_last4 is redacted: true, and either (a) drops the card grouping and retries against card_brand + card_exp_* only, or (b) reports back that the agent shouldn’t expose card data and asks the operator to confirm intent. Either pivot is programmatic — no human round-trip to debug.
What it proves. This is the canonical-PII-propagation demo: PII tags cross JOIN boundaries. Even though the metric anchors on a non-PII table (invoices.invoice_total_cents), the compiler refused the joined result because the grouping reached payment_methods.card_number_last4 two joins away. The database never saw the query. The refusal is a typed contract (one of 26 ErrorKind values) with a recovery.suggested_tool the agent can branch on programmatically. See /mechanism/pii-taxonomy and /mechanism/structured-recovery.
Try the credential variant too.Ask Claude: count users grouped by their password hash. The bundledusers.password_hashcolumn is taggedcredential, soget_metricrefuses for the same reason — different category, same shape.
Query 5 — verify the audit chain
This one is CLI-side, not agent-side. Open a terminal:mcp_audit row written by Queries 1–4. Each row carries chain_hash[N] = sha256(chain_hash[N-1] || canonical(row[N])). The verifier exits:
0— chain intact, no row rewritten since it was committed.1— mismatch found; the row index that broke the chain is reported.2— operational error (DB missing, schema mismatch, IO failure).
schemabrain audit list --limit 10 shows the last ten tool calls with their tool_name, status, pii_categories, and chain_hash columns.
What it proves. Every agent tool call is captured in an append-only table guarded by SQL triggers (mcp_audit_no_update, mcp_audit_no_delete) at the SQLite layer. Tampering with any past row breaks the chain at that row and every later one. The chain head can be persisted externally (post-verify cron) to detect even a full-table rewrite. See /mechanism/audit-chain.
What you just exercised
You’ve seen all four load-bearing mechanisms in ~10 minutes against a real database.
Where to next
- Add your own metric. Drop a YAML under
./schemabrain/metrics/and runschemabrain metrics apply ./schemabrain/metrics/ --url-env DATABASE_URL. Reference:docs/semantic-layer.md. - Stream the audit feed.
schemabrain tailshows every tool call live. Useful while you’re tuning policy. Reference:docs/operations.md. - Wire a second host. Same
init, different--hostflag. Reference:docs/setup/cursor,docs/setup/windsurf,docs/setup/claude-code. - Read the threat model. If you’re packaging SchemaBrain into a procurement review,
docs/security.mdis the summary;docs/threat-model.mdis the full walk-through.