|
| 1 | +--- |
| 2 | +title: "SurrealDB MCP + a verification skill — querying augment-it's canonical layer directly, starting with FreedomFest 2026" |
| 3 | +date_created: 2026-07-07 |
| 4 | +date_modified: 2026-07-07 |
| 5 | +status: Shipped |
| 6 | +tags: |
| 7 | + - Plan |
| 8 | + - MCP |
| 9 | + - Agent-Skills |
| 10 | + - SurrealDB |
| 11 | + - Augment-It |
| 12 | + - Canonical-Layer |
| 13 | + - FreedomFest |
| 14 | +--- |
| 15 | + |
| 16 | +## Why this exists |
| 17 | + |
| 18 | +Tonight's work on `apps/person-db-resolver` created real `persons` + |
| 19 | +`organizations` + `affiliations` + `observations` rows in reach-edu's |
| 20 | +SurrealDB Cloud instance — a handful of FreedomFest 2026 speakers and |
| 21 | +orgs (Ethan Akimoto / Carl Menger Institute, Lyn Ulbricht, Rudolfo |
| 22 | +Beltran, Kevin Brady, Lt Gov Stavros Anthony / State of Nevada, a |
| 23 | +President-of-Basin-Ventures record, and others), created by hand while |
| 24 | +testing the new UI. Every check on this data so far has been an ad-hoc |
| 25 | +Node script (`connect → signin → use → query`, written fresh each time, |
| 26 | +deleted after) — the same pattern used earlier tonight to clean up a |
| 27 | +mis-created `ethan-akimoto` organization row. That's fine for a one-off |
| 28 | +cleanup; it's the wrong tool for "verify tonight's batch is linked up |
| 29 | +correctly," which is a recurring need every time a new event or client |
| 30 | +gets processed through the resolver apps. |
| 31 | + |
| 32 | +The fix is the same shape already proven for the Chroma corpus in this |
| 33 | +tree ([[../skills/search-lossless-corpus/SKILL.md]]): an **MCP server** |
| 34 | +for the raw query access, plus a **Skill** that carries the schema |
| 35 | +knowledge and the verification discipline, so a future session doesn't |
| 36 | +have to re-derive "what does an affiliation edge look like" from reading |
| 37 | +`resolver.ts` again. |
| 38 | + |
| 39 | +## Component 1 — SurrealMCP, project-scoped — SHIPPED |
| 40 | + |
| 41 | +[SurrealDB ships an official MCP server](https://github.com/surrealdb/surrealmcp) |
| 42 | +(`surrealmcp`) that talks to both self-hosted SurrealDB and SurrealDB |
| 43 | +Cloud — exactly augment-it's setup — over stdio/HTTP with bearer-token |
| 44 | +auth. This is the right default over a community alternative |
| 45 | +(`lfnovo/surreal-mcp`, `nsxdavid/surrealdb-mcp-server` also exist) since |
| 46 | +it's maintained by SurrealDB itself and Cloud auth is a first-class case, |
| 47 | +not a workaround. |
| 48 | + |
| 49 | +**Resolved 2026-07-07:** `surrealmcp` is a Rust binary with exactly two |
| 50 | +distribution paths — build from source (`cargo install --path .`) or the |
| 51 | +`surrealdb/surrealmcp:latest` Docker image; no npm/PyPI package, so no |
| 52 | +`uvx`/`npx`-style one-liner like the `chroma` MCP server gets. Went with |
| 53 | +**Docker** — zero submodule, zero build step, and Docker's already part of |
| 54 | +this stack (`Dockerfile`, `docker-compose.yml`). A "submodule in an |
| 55 | +`mcps/` folder, symlinked" structure (mirroring the skills-symlink |
| 56 | +convention) was considered and rejected: MCP servers are discovered |
| 57 | +exclusively through `.mcp.json`'s `command`/`args`, never by scanning a |
| 58 | +folder, so a symlink step there would be pure overhead with no functional |
| 59 | +effect. |
| 60 | + |
| 61 | +Shipped: |
| 62 | + |
| 63 | +- `scripts/mcp-surrealdb.sh` — sources `.env` relative to its own |
| 64 | + location (not `$PWD`, not the launching shell's environment) and execs |
| 65 | + the Docker container. This matters because Claude Code's `${VAR}` |
| 66 | + expansion in `.mcp.json` only reads variables already exported in the |
| 67 | + shell that launched `claude` — it does not read `.env` files — and this |
| 68 | + repo's habit is sourcing `.env` per-command, not exporting at shell |
| 69 | + startup. |
| 70 | +- `.mcp.json` (project scope, per [[feedback_mcp_project_scope]]): |
| 71 | + ```json |
| 72 | + { |
| 73 | + "mcpServers": { |
| 74 | + "surrealdb": { |
| 75 | + "command": "${CLAUDE_PROJECT_DIR:-.}/scripts/mcp-surrealdb.sh" |
| 76 | + } |
| 77 | + } |
| 78 | + } |
| 79 | + ``` |
| 80 | +- Verified via `claude mcp list` → `surrealdb: ... ✔ Connected`. |
| 81 | + |
| 82 | +**Open question — read-only vs. read-write. Still not resolved.** The |
| 83 | +Docker container runs with the same full read-write credentials the app's |
| 84 | +services already use, and surrealmcp additionally exposes **Cloud |
| 85 | +instance management** (create/pause/resume a SurrealDB Cloud instance) — |
| 86 | +capability none of augment-it's own services need. This is more blast |
| 87 | +radius than a verification connector strictly requires. Flagged in the |
| 88 | +`surrealdb-canonical-layer` skill as a standing caution (never issue a |
| 89 | +write/Cloud-management tool call from a verification pass without an |
| 90 | +explicit ask) rather than solved — provisioning a scoped read-only |
| 91 | +SurrealDB Cloud role is worth doing before this becomes routine |
| 92 | +infrastructure rather than an occasional tool. |
| 93 | + |
| 94 | +## Component 2 — a verification skill — SHIPPED |
| 95 | + |
| 96 | +Authored at `context-v/skills/surrealdb-canonical-layer/SKILL.md` in the |
| 97 | +lossless-skills repo (per |
| 98 | +[[feedback_skill_authoring_in_lossless_skills]] — never inside augment-it |
| 99 | +directly), then symlinked via `sync-skills-symlinks.sh`. |
| 100 | + |
| 101 | +**Scope decision:** generalized past augment-it, not augment-it-only. |
| 102 | +Written with augment-it's live schema as the worked example (source of |
| 103 | +truth: the code, cited explicitly as a snapshot that can drift) plus an |
| 104 | +explicit "adapting this skill to a new project" section, since |
| 105 | +`dididecks-ai` and `memopop-ai` are named as likely future SurrealDB |
| 106 | +adopters of the same schemaless-canonical-layer + observations-as-log |
| 107 | +pattern. |
| 108 | + |
| 109 | +**What it carries** (matches what was scoped here originally): |
| 110 | + |
| 111 | +- The schema shape: `persons`, `organizations`, `affiliations` (a real |
| 112 | + `RELATE` edge, `in`/`out`/`kind`/`client_access`/`added_at`), |
| 113 | + `observations` (`subject`/`predicate`/`object`/`source`/`observed_at`/ |
| 114 | + `client` — schemaless, predicates grow freely: `has_name`, |
| 115 | + `has_email`, `has_linkedin_url`, `affiliated_with`, `located_in`, and |
| 116 | + the event-tie family `speaker_at`/`sponsor_of`/`exhibitor_at`/ |
| 117 | + `attended`), `events` (`slug`/`name`/`client`/`client_access`/`source`). |
| 118 | +- **The verification pattern**, generalized past tonight's specific |
| 119 | + case: given a batch, confirm (a) rows exist with the right fields, (b) |
| 120 | + client tagging is correct **checked explicitly, not inferred from a |
| 121 | + filtered query**, (c) relationships exist where the write path is |
| 122 | + supposed to create them. Flag, don't silently fix — a gap can be a |
| 123 | + correct outcome (skip is first-class; an org resolved independently of |
| 124 | + any person is first-class). |
| 125 | +- Query recipes for the common shapes, including the |
| 126 | + `observed_at`-must-be-in-projection SurrealDB 2.x gotcha hit while |
| 127 | + building tonight's diagnostic. |
| 128 | +- **Client tagging as its own explicit check** — the per-table shape |
| 129 | + documented precisely: `persons`/`organizations`/`events`/`affiliations` |
| 130 | + use `client_access: string[]`; `observations` uses `client: string` |
| 131 | + (singular — a real inconsistency, not a typo); `events` carries both. |
| 132 | + |
| 133 | +## Tonight's actual verification task — RUN, findings below |
| 134 | + |
| 135 | +Ran directly against SurrealDB Cloud (a disposable diagnostic script, |
| 136 | +before the MCP+skill were wired — the MCP server came online mid-session |
| 137 | +after this ran). Scope: everything touched by the People-CSV flow in the |
| 138 | +last 3 days, cross-checked against |
| 139 | +`clients/reach-edu/inputs/events/freedomfest/2026-07-08_freedomfest-2026-speakers.csv`. |
| 140 | + |
| 141 | +**1. Client tagging — clean.** All 65 `persons` rows and all 40 |
| 142 | +`organizations` rows created in the batch carry `client_access: |
| 143 | +["reach-edu"]` correctly. Zero tagging gaps found, including on the |
| 144 | +re-check that didn't filter by client first (the check the plan called |
| 145 | +out as the one a naive filtered query would miss). |
| 146 | + |
| 147 | +**2. Person↔org affiliation — mostly missing.** Only **1 of 65 persons** |
| 148 | +(Ethan Akimoto, the original hand-test) has an `affiliations` edge. The |
| 149 | +other 64 — all created via the batch run — have `has_name` + |
| 150 | +`speaker_at` observations but no affiliation edge, even though 39 |
| 151 | +additional organizations were independently created/matched during the |
| 152 | +same run, correctly tagged, just never `RELATE`d to their person. This is |
| 153 | +exactly the gap the prior changelog entry (person-db-resolver's ship |
| 154 | +entry) flagged as an open question: nothing retroactively relates a |
| 155 | +person to an org resolved on the same row unless both steps happen in one |
| 156 | +pass with the person resolved first. |
| 157 | + |
| 158 | +**3. Duplicate person rows — a new finding, not previously flagged.** |
| 159 | +"Ethan Akimoto" and "Rudolfo Beltran" each have two separate `persons` |
| 160 | +rows (one from early hand-testing, one from the batch run); "Lt Gov |
| 161 | +Stavros Anthony" and "Stavros Anthony" look like the same person split |
| 162 | +across two rows by title-stripping. Candidate matching should have caught |
| 163 | +these on the second pass — worth investigating whether the fuzzy-match |
| 164 | +threshold or the create-without-reviewing-candidates path is the cause. |
| 165 | + |
| 166 | +**Coverage:** 65 of 200 CSV speakers resolved so far (the batch run |
| 167 | +stopped partway through, around "Jonathan Riches"). |
| 168 | + |
| 169 | +## Open questions |
| 170 | + |
| 171 | +- ~~Exact skill name~~ — resolved: `surrealdb-canonical-layer`, written |
| 172 | + generally per the scope decision above. |
| 173 | +- Read-only MCP role (see Component 1) — still open, resolve before this |
| 174 | + becomes a routine tool, not just an occasional one. |
| 175 | +- Whether the skill should also carry write patterns — resolved: no, |
| 176 | + stays read/verify only, so the UI's match/create/skip discipline (and |
| 177 | + its idempotency guarantees) isn't bypassed by a raw `CREATE`/`RELATE` |
| 178 | + run through MCP. |
| 179 | +- New from tonight's findings: does the People-CSV flow's next iteration |
| 180 | + need a "resolve org, then retroactively RELATE any already-resolved |
| 181 | + person on the same row" step, or should the operator workflow just be |
| 182 | + documented as "resolve person before org, always"? Deferred — the user |
| 183 | + is not fixing the UI right now, may iterate after more diagnostics. |
| 184 | +- New from tonight's findings: the duplicate-person gap (Ethan Akimoto, |
| 185 | + Rudolfo Beltran, Stavros Anthony) — worth a look whenever UI iteration |
| 186 | + resumes. |
| 187 | + |
| 188 | +## See also |
| 189 | + |
| 190 | +- `augment-it/context-v/plans/Person-Aware-Canonical-Resolver-Extension.md` |
| 191 | + — the schema and the person/org/affiliation/observation write path this |
| 192 | + skill reads, doesn't reinvent. |
| 193 | +- `context-v/skills/search-lossless-corpus/SKILL.md` — the Chroma |
| 194 | + precedent this plan's shape (MCP for access, Skill for discipline) is |
| 195 | + copied from. |
| 196 | +- `context-v/skills/surrealdb-canonical-layer/SKILL.md` — the shipped skill. |
| 197 | +- [SurrealMCP GitHub](https://github.com/surrealdb/surrealmcp), |
| 198 | + [SurrealMCP announcement](https://surrealdb.com/blog/introducing-surrealmcp) |
0 commit comments