Skip to content

Repository files navigation

lemurkit

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.

Deploy to Cloudflare

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.

What you get

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.

Deploy (one click)

  1. 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.
  2. On the setup page, give the two KV namespaces distinct names — e.g. lemurkit-oauth and lemurkit-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.
  3. 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 deploy script — see Updating an existing install), deploys the Worker, and wires up CI (every push to your new repo redeploys).
  4. 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).

Setup

1. Register a GitHub OAuth app

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.

2. Set the five secrets

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.)

3. Connect Claude

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.

Optional: semantic search

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=cosine

The 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.

Adding more buckets

The connector addresses stores through a small registry. To add one:

  1. wrangler.jsonc: add a binding, e.g. { "binding": "BUCKET_ARCHIVE" } under r2_buckets.
  2. src/env.ts: add BUCKET_ARCHIVE: R2Bucket;.
  3. 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.

Updating an existing install

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 redeploys

Keep 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.

Local development

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.

Security posture

  • 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: .npmrc pins 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 state tokens expire after 10 minutes, and every HTML page the Worker serves carries a strict CSP, frame denial, and no-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.

Troubleshooting

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.

License

Apache-2.0

About

AI Sovereignty - The team cognition for AI agents. The remote MCP connector on Cloudflare. KV is for Transactive Memory System, R2 is for Shared Knowledge and Mental Models. D1/Vectorize are for the semantic layer.

Topics

Resources

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages