A team-cognition MCP server for multiple AI agents, including Claude and Grok. Your agents write shared memory fragments, store files, and search them by full text or meaning — on their own. It runs in your Cloudflare account, gated to your GitHub account. One click deploys it; nothing is shared with anyone.
Any MCP client — Claude (web, desktop, Claude Code) or Grok — talks to it as a remote connector over the Model Context Protocol. You get a private file store and a shared memory layer that are yours end to end: your bucket, your identity, your OAuth gate.
| Tools | What they do |
|---|---|
list_files read_file write_file copy_file move_file delete_file get_file_info list_buckets |
Full file management on your R2 bucket |
query_files |
Find objects by attributes — type, size, date, prefix (D1-backed index) |
search_files |
Find objects by words — full-text search over names and content (BM25, snippets) |
semantic_search |
Find objects by meaning — vector search (optional tier, see below) |
index_coverage |
Which text files have no vectors (silent semantic_search misses). Semantic tier. |
index_bucket |
Rebuild the indexes from R2 (out-of-band uploads, backfill, targeted repair) |
write_memory read_memory list_memory forget_memory |
Shared-memory fragments (KV) — create, read, list, delete |
patch_memory append_memory archive_memory |
Edit in place, append, or move a range into an archive shard |
Writes through the connector keep the search indexes current automatically (write-through). If
indexing fails, the write still lands and the tool returns a non-fatal indexWarning — run
index_bucket to repair. copy_file and move_file refuse to overwrite an existing destination
unless you pass overwrite; a move that would clobber leaves both files intact.
Memory is a small KV-backed notebook AI agents can share across chats. Each fragment is a
≤200-line doc keyed programme/codename/slug — three labels you choose (e.g. acme/notes/inbox).
write_memory creates or overwrites the whole body; prefer patch_memory (exactly-once string
replace) or append_memory (add to the end) so a correction does not resend the doc.
archive_memory moves a unique range into a sibling shard and leaves a one-line pointer.
list_memory is the index (refs + titles, no bodies).
The connector is single-user by design: an OAuth 2.1 + PKCE gate delegates login to GitHub and admits exactly one allowlisted account — yours.
- Click Deploy to Cloudflare above. A free Cloudflare account works, but enabling R2 asks for billing verification (a payment card) even on the free tier — do that first if your account is brand new.
- On the setup page, give the two KV namespaces distinct names — e.g.
lemurkit-oauthandlemurkit-memory. Both default to the Worker's name, and KV names are unique per account, so the second one fails to provision if you leave the defaults. Everything else can stay as suggested. - Cloudflare clones this repo into your GitHub account, auto-provisions the R2 bucket, the
two KV namespaces, and the D1 database (its migrations apply automatically as part of every
deploy if the deploy command is this repo's
deployscript — see Updating an existing install), deploys the Worker, and wires up CI (every push to your new repo redeploys). - Note your Worker URL:
https://<worker-name>.<your-subdomain>.workers.dev(the Worker is named after the project name you chose on the setup page). If the URL isn't live, the initial deploy sometimes leaves the workers.dev route disabled: open the Worker in the dashboard → Settings → Domains & Routes → enable workers.dev. Any later push re-enables it too.
The deploy finishes in a "not configured yet" state — that's expected. Visiting the URL shows a checklist of the secrets you're about to set. The Worker deploys before the OAuth app can exist because GitHub needs your live URL as the callback. Finish the setup below (~5 minutes).
Go to https://github.com/settings/developers (that's your account settings → Developer settings — not a repository's settings) → OAuth Apps → New OAuth App:
- Application name: anything (e.g.
my lemurkit) - Homepage URL:
https://<worker-name>.<your-subdomain>.workers.dev - Authorization callback URL:
https://<worker-name>.<your-subdomain>.workers.dev/callback— exactly this path; GitHub OAuth apps accept a single callback URL.
Create it, then generate a client secret. Keep the client ID and secret handy.
Clone your new repo, then from its root (npx wrangler login first if needed):
npx wrangler secret put GITHUB_CLIENT_ID # from step 1
npx wrangler secret put GITHUB_CLIENT_SECRET # from step 1
npx wrangler secret put GITHUB_ALLOWED_USERNAME # your GitHub login, e.g. octocat
npx wrangler secret put GITHUB_ALLOWED_USER_ID # your numeric id — see below
npx wrangler secret put STATE_SIGNING_KEY # any long random string, e.g. `openssl rand -hex 32`Your numeric id is at https://api.github.com/users/<your-login> (the id field). The
allowlist pins both values: the id is immutable (logins can be renamed and re-registered by
someone else; ids can't), the login is defence-in-depth.
Secrets take effect immediately — no redeploy needed. (The database migrations already applied
during the deploy — the deploy script runs them every time, so there's nothing to migrate by
hand.)
In Claude: Settings → Connectors → Add custom connector, URL:
https://<worker-name>.<your-subdomain>.workers.dev/mcp
Authorize — you'll see the consent page, sign in with GitHub, and land back in Claude. Ask Claude to write a file and read it back. Done.
semantic_search finds files by meaning (vector similarity) rather than keywords. It's off by
default because Vectorize isn't auto-provisioned and embedding has a small per-write cost
(every text file you write is embedded via Workers AI once enabled — typically well within
the free tier for personal use, but it's your account: know it's there). Binary and oversized
objects are never embedded; find those with query_files or search_files.
To enable:
npx wrangler vectorize create lemurkit-objects --dimensions=1024 --metric=cosineThe CLI (or API) is the only way to create a Vectorize index — the Cloudflare dashboard has
no create UI. npx wrangler login opens a browser; on a headless machine use a scoped API token
instead: CLOUDFLARE_API_TOKEN=<token> npx wrangler vectorize create ….
Then uncomment the ai and vectorize bindings in wrangler.jsonc, push (CI redeploys), and
run index_bucket once from Claude to embed your existing files. New writes are embedded
write-through. Confirm a repair with index_coverage — a green run is empty missing and empty
unchecked. Dropout is silent: objects just stop being found.
On a large bucket, pass prefix (e.g. docs/) and re-run until index_coverage is green.
Repair is the default: keys that already hold vectors are skipped, so a re-run continues from
dropouts. Pass force to rebuild already-indexed keys. A whole-bucket run can hit the Worker's
per-invocation subrequest cap; scoped runs are the intended path.
The connector addresses stores through a small registry. To add one:
wrangler.jsonc: add a binding, e.g.{ "binding": "BUCKET_ARCHIVE" }underr2_buckets.src/env.ts: addBUCKET_ARCHIVE: R2Bucket;.src/buckets.ts: add an entry, e.g.archive: { get: (env) => env.BUCKET_ARCHIVE, description: "Cold archive." }.
Push; the new store auto-provisions on deploy and shows up in list_buckets. Every tool takes
it via the bucket argument.
The Deploy button snapshots this tree into a new repo in your account, with unrelated git history — there is no "Sync fork" button. A plain merge of upstream often refuses or makes a mess.
Deploy-to-Cloudflare clones (the README button):
git clone <your-copy>
cd <your-copy>
git remote add upstream https://github.com/lemur47/lemurkit.git
git fetch upstream
git cherry-pick <new-upstream-commit> # or merge; see below
git push # Workers Builds redeploysKeep your wrangler.jsonc ids and names (bucket, KV, D1) if the upstream commit touched that
file; take upstream's structure. Those values survive automatically when the commit does not
touch wrangler.jsonc.
Manual wrangler installs: git pull && pnpm install && pnpm run deploy. Migrations already
fold into deploy — there is no separate migrate step.
Confirm Workers Builds' deploy command is pnpm run deploy (or npm run deploy), not bare
wrangler deploy. Cloudflare's suggested default is the latter, which does not apply D1
migrations.
A one-line post-upgrade check: GET (not HEAD) https://<your-worker-host>/callback and
confirm the security-header set is present. A HEAD on that route 404s — that is a known trap,
not a broken deploy.
Needs Node 22+.
pnpm install
pnpm test # offline: in-memory fakes + node:sqlite against the real migrations
pnpm typecheck
pnpm dev # wrangler dev; put secrets for local runs in .dev.vars (gitignored)core/ is the framework-agnostic logic (storage, indexes, memory), consumed as TypeScript
source via a tsconfig path alias — there's no build step for it. src/ is the Worker: entry
point, OAuth, and the MCP tool surface.
- Single-account allowlist on an OAuth 2.1 + PKCE gate; login delegated to GitHub. The authorized identity lives in Worker secrets, never in the repo.
- Everything private by default: no public bucket URLs; all access goes through the OAuth-gated Worker. Data at rest uses R2's default encryption (AES-256).
- Supply-chain:
.npmrcpins the npm registry, disables install scripts entirely (ignore-scripts=true— the Worker is bundled, nothing needs native builds), and refuses packages published less than 3 days ago. Dependencies are exact-pinned. - Hardened auth pages: OAuth
statetokens expire after 10 minutes, and every HTML page the Worker serves carries a strict CSP, frame denial, andno-store. - Rate limiting is yours to add: the open OAuth endpoints ship unthrottled (the MCP spec requires open client registration). Recommended: a custom domain + one zone WAF rate-limiting rule on the pre-auth paths — recipe in SECURITY.md.
- Know your logs: Workers observability is on by default, so tool calls — including file content on writes — appear in your Cloudflare account's logs. Details and other accepted trade-offs (e.g. prompt injection via stored content) in SECURITY.md.
- Found a vulnerability? See SECURITY.md.
| Symptom | Cause / fix |
|---|---|
503 not_configured (or the checklist page) |
One or more secrets missing — the response names which. Set them (Setup step 2). |
| You get "This GitHub account is not authorized" (403) after signing in | The allowlist secrets don't match your account — re-put GITHUB_ALLOWED_USER_ID (numeric, from api.github.com/users/<login>) and GITHUB_ALLOWED_USERNAME (your exact login). |
| Someone else gets 403 | Working as intended — single-user by design. |
| GitHub login fails with a redirect error | The OAuth app's callback URL isn't exactly https://<your-worker-host>/callback. |
HEAD /callback 404s |
Probe with GET. A HEAD on that route 404s on purpose; it is not a broken deploy. |
query_files / search_files return a D1 error |
Migrations not applied — they run with every pnpm run deploy, so push any commit (or run that script), or apply directly: npx wrangler d1 migrations apply lemurkit-metadata --remote. Confirm the Workers Builds deploy command is not bare wrangler deploy. |
| Files uploaded via the Cloudflare dashboard don't show in search | Out-of-band writes aren't indexed automatically — run the index_bucket tool. |
semantic_search missing from the tool list |
The semantic tier is off — that's the default; see "Optional: semantic search". |
semantic_search misses files you know exist |
Dropout is silent. Run index_coverage; repair with prefix-scoped index_bucket. Binary and oversized objects are never embedded — use query_files / search_files. |
index_bucket on the whole bucket errors or stops early |
Pass prefix and re-run. Repair (the default) skips keys that already hold vectors. |