Repository navigation
fix(byod): generate the bootstrap from migrations and fix BYOD's limits #690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
8a46f0a
fix(byod): generate bootstrap.sql from the migrations
mthines 9e12c01
fix(byod): gate bootstrap drift through supabase:test, probe storage …
mthines 8875f97
docs(byod): correct how BYOD is wired, and propose a local SQLite mode
mthines 06f42ff
fix(byod): address review — anon-key reads, old bootstrap databases, …
mthines a64ffd3
fix(byod): require the service key, scope session callers, drop user …
mthines 59af877
docs(tasks): replace the SQLite draft with a Docker self-host proposal
mthines da441cc
fix(mcp): return a BYOD storage misconfiguration in-band, not as a ba…
mthines 7bdf0e0
docs(byod): call the storage service key required, not optional
mthines 1c85def
fix(mcp): stamp hosted session-JWT tool calls with the caller's id
mthines 4666d9f
fix(mcp): redact URLs from server-fault (-32603) tool error messages
mthines 1bab442
docs: a token-permission denial is -32003, not -32001
mthines ee7cf30
address review comment: correct stale comments: toolUserId is the JWT…
mthines 69b41d1
address review comment: docs: a token-permission denial is -32003 in …
mthines 6c63753
address review comment: docs: -32603 also reports a BYOD storage misc…
mthines ac17011
address review comment: cli: bootstrap help names a storage key, not …
mthines 086ee01
address review comment: install.md -32603 row named a scope error; sp…
mthines File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # Generated from supabase/migrations/ by `node supabase/byod/generate.mjs`. | ||
| # Collapsed in GitHub diffs; review the migrations, not this projection. | ||
| supabase/byod/bootstrap.sql linguist-generated=true | ||
|
|
||
| # The generator's drift check compares bytes. Keep both sides LF on every | ||
| # checkout, so core.autocrlf on Windows cannot turn a clean tree red. | ||
| supabase/migrations/*.sql text eol=lf | ||
| supabase/byod/bootstrap.sql text eol=lf |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,80 +1,173 @@ | ||
| # Bring Your Own Database (BYOD) | ||
|
|
||
| LoreKit's memory tools can point at a Supabase project you control instead of | ||
| the hosted LoreKit backend. This is useful when you have data residency | ||
| requirements, an existing Supabase subscription, or want full control over | ||
| your memory data. | ||
| BYOD stores memories in a Supabase project you control, separate from the | ||
| project that runs LoreKit's `mcp` Edge Function. Use it when memory rows must | ||
| live in a specific database — data residency, an existing Supabase | ||
| subscription, or full control over the data. | ||
|
|
||
| It covers the MCP server's tools. Read [How BYOD is wired](#how-byod-is-wired) | ||
| before setting it up: what reaches the storage project, and what does not, is | ||
| the part most likely to surprise you. | ||
|
|
||
| ## How BYOD is wired | ||
|
|
||
| - **It needs your own deployment of the `mcp` function.** The function reads | ||
| `LOREKIT_STORAGE_URL`, the required `LOREKIT_STORAGE_SERVICE_KEY` and the | ||
| optional `LOREKIT_STORAGE_ANON_KEY` from its **own Edge Function secrets**. The | ||
| hosted endpoint (`https://pqokxlhvnosogizsjztg.supabase.co/functions/v1/mcp`) | ||
| has no BYOD storage configured, and an `env` block in your `.mcp.json` does | ||
| not reach it — those variables only reach the local `mcp-remote` process on | ||
| your machine. Deploy the function yourself first: [install.md](./install.md). | ||
| - **Two projects, two jobs.** API tokens and users are resolved in the | ||
| function's own project; memory rows are read from and written to the | ||
| storage project. The storage project's `auth.users` never holds those users, | ||
| which is why the storage schema drops its foreign keys into `auth.users` | ||
| (step 2). | ||
| - **Only the `mcp` function has a BYOD mode.** The `memories` REST function — | ||
| which backs the web dashboard, the CLI's remote commands and the hook | ||
| engine's lesson injection — always uses the function's own project. See | ||
| [Limitations](#limitations). | ||
|
|
||
| ## Setup | ||
|
|
||
| ### 1. Create a Supabase project | ||
| ### 1. Create the storage project | ||
|
|
||
| Create a free project at [supabase.com](https://supabase.com). Note your | ||
| project URL and API keys from **Settings → API**. | ||
| Create a project at [supabase.com](https://supabase.com). Note its URL and API | ||
| keys (**Project Settings → API**) and its database connection string | ||
| (**Connect**). Enable the `pg_cron` extension (**Database → Extensions**) | ||
| before applying the schema: the purge jobs are scheduled only when it is | ||
| already enabled. | ||
|
|
||
| ### 2. Apply the LoreKit schema | ||
|
|
||
| Run the bootstrap command to create the required tables and functions: | ||
| The storage project needs the full LoreKit schema — the same migrations the | ||
| hosted project runs. Pick one route. | ||
|
|
||
| **Supabase CLI (recommended — the same commands upgrade it later):** | ||
|
|
||
| ```bash | ||
| LOREKIT_STORAGE_URL=https://your-project.supabase.co \ | ||
| LOREKIT_STORAGE_SERVICE_KEY=your-service-role-key \ | ||
| lorekit bootstrap | ||
| git clone https://github.com/mthines/lorekit.git | ||
| cd lorekit | ||
| supabase link --project-ref <storage-project-ref> | ||
| supabase db push | ||
| psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f supabase/byod/storage-overlay.sql | ||
| ``` | ||
|
|
||
| If the CLI is not installed, apply the schema directly with psql: | ||
| The last line applies the **storage overlay**: it drops every foreign key from | ||
| a `public` table into `auth.users`, because the users live in the function's | ||
| project, not this one. Without it, every write made with an `lk_*` token fails | ||
| with `memories_user_id_fkey`. It is idempotent; re-run it after every | ||
| `db push`. Never apply it to the project that runs the `mcp` function. | ||
|
|
||
| **One SQL file (empty database only):** | ||
|
|
||
| ```bash | ||
| psql "$DATABASE_URL" -f path/to/bootstrap.sql | ||
| psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f supabase/byod/bootstrap.sql | ||
| ``` | ||
|
|
||
| Or copy the raw SQL from the [LoreKit repository](https://github.com/mthines/lorekit/blob/main/supabase/byod/bootstrap.sql) and paste it into the Supabase dashboard → SQL Editor. | ||
| or paste [`supabase/byod/bootstrap.sql`](https://github.com/mthines/lorekit/blob/main/supabase/byod/bootstrap.sql) | ||
| into the Supabase dashboard → **SQL Editor** and run it (the file is about 1 MB; | ||
| if the editor refuses it, use `psql`). `$DATABASE_URL` is the connection string | ||
| from **Connect**; its password is your **database password**, not an API key. | ||
|
|
||
| ### 3. Configure your MCP server | ||
| `bootstrap.sql` is generated from `supabase/migrations/` (CI fails when the two | ||
| disagree) and ends with the storage overlay. It applies everything in one | ||
| transaction, so a failure leaves the database untouched; it refuses to run | ||
| against a database that already has the schema; and it records each migration | ||
| in Supabase's migration history, so the CLI route above upgrades the database | ||
| later. | ||
|
|
||
| In your `.mcp.json` or equivalent MCP config, add the env vars: | ||
| `lorekit bootstrap`, run with `LOREKIT_STORAGE_URL` and a key in your shell, | ||
| checks whether the project is reachable and already has the schema, and prints | ||
| these commands. It does not run any DDL itself — a Supabase API key cannot. | ||
|
|
||
| ```json | ||
| { | ||
| "env": { | ||
| "LOREKIT_STORAGE_URL": "https://your-project.supabase.co", | ||
| "LOREKIT_STORAGE_ANON_KEY": "your-anon-key" | ||
| } | ||
| } | ||
| ``` | ||
| ### 3. Point your `mcp` function at it | ||
|
|
||
| Or set them in your shell before starting the MCP server. | ||
| Set the storage secrets on the project that runs your `mcp` function: | ||
|
|
||
| ## Billing | ||
| ```bash | ||
| supabase secrets set --project-ref <function-project-ref> \ | ||
| LOREKIT_STORAGE_URL=https://<storage-project-ref>.supabase.co \ | ||
| LOREKIT_STORAGE_ANON_KEY=<storage-anon-key> \ | ||
| LOREKIT_STORAGE_SERVICE_KEY=<storage-service-role-key> | ||
| ``` | ||
|
|
||
| Memories stored in your own database are **not counted** against LoreKit's | ||
| hosted memory-count billing. LoreKit has no visibility into your private | ||
| database and cannot meter memories stored there. | ||
| The service key is **required**: without it, every call to the function fails | ||
| with an error naming `LOREKIT_STORAGE_SERVICE_KEY`. The storage project's read policies need `auth.uid()`, which an API | ||
| key never carries, so an anon-key client could write but would read back | ||
| nothing. `LOREKIT_STORAGE_ANON_KEY` is accepted but unused once the service key | ||
| is set. | ||
|
|
||
| BYOD users are on a flat-rate or open-source tier — no per-memory billing. | ||
| The service key bypasses row-level security, so the function scopes every call | ||
| itself: reads and writes made with an `lk_*` token, or from a Supabase session, | ||
| are filtered to and stamped with that caller's user id. Only the service-role | ||
| tier (CI) sees every row. | ||
|
|
||
| You are responsible for configuring memory limits and rate limiting in your | ||
| own Supabase project. The bootstrap schema ships with a default 5,000-memory | ||
| cap trigger, which you can adjust by editing the `lorekit_default_limit` | ||
| function in your database. | ||
| ### 4. Check it | ||
|
|
||
| ## Limitations | ||
| ```bash | ||
| LOREKIT_STORAGE_URL=https://<storage-project-ref>.supabase.co \ | ||
| LOREKIT_STORAGE_ANON_KEY=<storage-anon-key> \ | ||
| lorekit doctor | ||
| ``` | ||
|
|
||
| The following LoreKit features are **not available** with BYOD: | ||
| The `byod storage` line passes when the project is reachable and holds the | ||
| schema. It fails when the project is unreachable, rejects the key, or does not | ||
| have the schema yet, and says which. | ||
|
|
||
| - **Org/scope sharing** — requires the hosted backend's org management RPCs | ||
| - **Web dashboard** — tightly coupled to hosted Supabase Auth | ||
| - **Audit logs** — not included in the BYOD schema | ||
| - **Webhook secrets** — not included in the BYOD schema | ||
| - **Automatic schema upgrades** — when LoreKit ships schema changes, you | ||
| must apply them manually. Run `lorekit doctor` to check compatibility. | ||
| ## Upgrading | ||
|
|
||
| ## Checking connectivity | ||
| When a LoreKit release adds migrations, upgrade the storage project from a | ||
| checkout of that release: | ||
|
|
||
| ```bash | ||
| LOREKIT_STORAGE_URL=https://your-project.supabase.co \ | ||
| LOREKIT_STORAGE_ANON_KEY=your-anon-key \ | ||
| lorekit doctor | ||
| supabase link --project-ref <storage-project-ref> | ||
| supabase db push | ||
| psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f supabase/byod/storage-overlay.sql | ||
| ``` | ||
|
|
||
| The output will include a `[byod] storage ok` line if the database is reachable. | ||
| `db push` applies only the migrations the database has not recorded, which | ||
| works the same whether you set the database up with `db push` or with | ||
| `bootstrap.sql`. Re-run the overlay after it, in case a new migration added a | ||
| foreign key into `auth.users`. Upgrade the storage project before deploying the | ||
| matching `mcp` function: the function calls the RPCs those migrations define. | ||
|
|
||
| ### Databases set up from the old `bootstrap.sql` | ||
|
|
||
| `bootstrap.sql` used to be a hand-written subset of the schema. A database set | ||
| up from that version has no migration history — `select count(*) from | ||
| supabase_migrations.schema_migrations` errors or returns 0 — so `db push` would | ||
| try to replay every migration from the first and fail, and its schema is | ||
| missing most of the functions the current `mcp` function calls. Move to a | ||
| fresh storage project instead: apply the current schema to it as above, copy the `memories` | ||
| rows across (for example `pg_dump --data-only --table=public.memories` from the | ||
| old database, loaded with `psql` into the new one), then point | ||
| `LOREKIT_STORAGE_URL` at the new project. | ||
|
|
||
| ## Billing | ||
|
|
||
| Memories stored in your own database are **not counted** against LoreKit's | ||
| hosted memory-count billing. LoreKit has no visibility into your private | ||
| database and cannot meter memories stored there. | ||
|
|
||
| The memory cap (default 5,000 active memories per user) and the limits table | ||
| live in your database: adjust them through `lorekit_default_limit` and | ||
| `user_limits` there. In BYOD mode the function skips the hosted rate limiter | ||
| and does not record hosted usage events. | ||
|
|
||
| ## Limitations | ||
|
|
||
| - **Only the MCP tools use the storage project.** The web dashboard, the REST | ||
| API, the CLI's remote commands (`list`, `search`, `show`, `write` against the | ||
| remote store) and the hook engine's lesson injection go through the `memories` | ||
| function, which has no BYOD mode. They read and write the function's own | ||
| project and do not see memories stored through BYOD. To run the dashboard on | ||
| a database you control, self-host the whole stack instead | ||
| ([install.md](./install.md)). | ||
| - **No row-level security in the storage project.** The function talks to it | ||
| with the service key and scopes every call in its own code (step 3); RLS | ||
| policies there do not apply to it. | ||
| - **Tested surface.** The BYOD smoke test exercises write, read, list, search | ||
| and delete, and runs only where its secrets are configured. The schema now | ||
| also contains the org, audit, retention and webhook tables and RPCs, but BYOD | ||
| has not been exercised for those features — treat them as unsupported. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.