{
  "markdown": "# Durable Thinking\n\n[![CI](https://github.com/linxule/durable-thinking/actions/workflows/ci.yml/badge.svg)](https://github.com/linxule/durable-thinking/actions/workflows/ci.yml)\n\nPersistent sequential thinking for MCP clients, on Cloudflare Workers.\n\nThe canonical Sequential Thinking server keeps thoughts in process memory and forgets them when the process exits. This one gives them somewhere durable to live — the same reasoning model (adjustable totals, continuation, revision, branching), just persisted instead of discarded. Every step is written to a SQLite-backed Cloudflare Durable Object, addressable later by an unguessable id, and — for clients that support MCP Apps — rendered as a card of its own.\n\nIt's a single-user deployment: one GitHub account allowed through the sign-in gate, one bearer token for header-capable clients, one private store, and a deploy-button template for standing up your own copy.\n\n## What a thought looks like\n\nA normal `sequentialthinking` call stays readable in any client:\n\n```text\nThought 3/5\n\nA Durable Object keeps the application history persistent while the MCP HTTP\ntransport remains stateless.\n\nSequence: seq_... · 3 thoughts stored\n```\n\nThe structured result stays deliberately small — the thought itself isn't repeated in it:\n\n```json\n{\n  \"sequenceId\": \"seq_...\",\n  \"thoughtNumber\": 3,\n  \"totalThoughts\": 5,\n  \"thoughtHistoryLength\": 3\n}\n```\n\nMCP Apps-capable hosts get the same thought delivered separately, to a UI resource: `ui://sequential-thinking/process.html`. Hosts render one card per tool call, so the App leans into that instead of fighting it — each card shows only its own thought, no polling, no state shared with other cards, and the chat transcript itself becomes the timeline. When the sequence finishes (`nextThoughtNeeded: false`), that last card loads the full stored history and renders the entire process at once: every thought in order, branches and revisions marked, earlier steps collapsed and expandable.\n\nThe App is one self-contained HTML document — no external scripts, fonts, or network calls. It reaches history only through the host's authenticated MCP connection. Clients without MCP Apps support just get the plain text result.\n\nThoughts aren't retransmitted on every write, either — only the current one. The model reloads earlier ones on purpose, with `get_thought_history`.\n\n## Capability-scoped history\n\nThere's no tool to list sequences, and none is coming. The `sequenceId` handed back from the first call is the only way in — long enough to be unguessable, and the sole credential its history checks. Hold the id, read the sequence; don't have it, and it doesn't exist for you.\n\nThat's what lets one deployment serve many clients and sessions at once without any of them seeing each other's reasoning: authentication gets you in the door, the sequence id gets you into a room.\n\n## Tools\n\n### `sequentialthinking`\n\nPersists one reasoning step. Omit `sequenceId` on the first call; carry the returned id through every continuation.\n\n```text\nthought\nnextThoughtNeeded\nthoughtNumber\ntotalThoughts\nsequenceId?\nisRevision?\nrevisesThought?\nbranchFromThought?\nbranchId?\nbranchFromBranchId?\nneedsMoreThoughts?\n```\n\nBranch by pairing `branchId` with `branchFromThought` (forking from the main path) or adding `branchFromBranchId` (forking from inside another branch), then keep passing that `branchId` on later steps. Revise with `isRevision: true` and `revisesThought`.\n\nThought numbers are scoped to the branch writing them, so two branches can each have their own thought 3 — a reference resolves against the branch being written, then its ancestors back to each fork point, nearest scope wins.\n\nTwo edge cases are accepted and flagged in the result rather than treated as errors: continuing a sequence after a thought said `nextThoughtNeeded: false` just reopens it, and reusing a thought number on the same branch resolves later references to its newest occurrence.\n\n### `get_thought_history`\n\nReturns full-text history, oldest-first and paginated — pass `nextCursor` back as `cursor` to continue (cursor values are opaque; don't compute them). Pass `branchId` to restrict the page to one branch; `sequence.branches` lists every branch with its parent and fork point.\n\n### `delete_thought_sequence`\n\nPermanently deletes a sequence and its stored text. Requires `confirm: true`.\n\n## Architecture\n\n```text\nMCP client\n    │\n    │ POST /mcp or /mcp-compat\n    │ OAuth access token or static bearer token\n    ▼\nCloudflare Worker\n    │\n    │ static token → straight to the MCP handler\n    │ anything else → workers-oauth-provider validation\n    │ fresh MCP server for each request\n    ▼\nThoughtStore Durable Object: \"personal\"\n    │\n    ▼\nSQLite tables for sequences, thoughts, and branches\n```\n\nThe MCP transport is stateless — no `Mcp-Session-Id` is used as a database key or continuity mechanism. All application state lives in the Durable Object instead, keyed by the `sequenceId` passed explicitly in tool arguments.\n\nOnly text submitted through the public `thought` argument is ever stored; the server has no access to a model's private or hidden reasoning.\n\n## Deploy your own\n\n### One click\n\n[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/linxule/durable-thinking)\n\nDeployment requires three secrets: `MCP_API_TOKEN` (the static bearer — generate at least 32 random bytes), plus `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` from the OAuth app described under GitHub sign-in below.\n\n```bash\nopenssl rand -hex 32\n```\n\nYou'll also need your own OAuth KV namespace — `npx wrangler kv namespace create OAUTH_KV`, then put its id in `wrangler.jsonc` (namespace ids aren't secrets).\n\nYou'll get three endpoints:\n\n```text\nhttps://<worker>.<account>.workers.dev/mcp\nhttps://<worker>.<account>.workers.dev/mcp-compat\nhttps://<worker>.<account>.workers.dev/healthz\n```\n\n### GitHub sign-in\n\nBrowser-based clients authenticate by signing in to GitHub; access is granted only to allowlisted accounts.\n\n1. Create an OAuth App under [GitHub Developer settings](https://github.com/settings/developers) — Homepage URL `https://<worker-host>`, callback URL `https://<worker-host>/callback`.\n2. Store its credentials as Worker secrets: `npx wrangler secret put GITHUB_CLIENT_ID`, then `GITHUB_CLIENT_SECRET`.\n3. Set the allowlist: `npx wrangler secret put ALLOWED_GITHUB_LOGIN` — one or more comma-separated GitHub logins, matched case-insensitively. Empty or missing fails closed: nobody can complete authorization. It's a secret rather than a `wrangler.jsonc` var so continuous deploys never overwrite it.\n\nThe sign-in flow reads only your GitHub identity, checks it against the allowlist, and discards the GitHub token. Authorization is keyed to the immutable account id, not the renameable login.\n\n### Continuous deployment\n\nGitHub Actions runs `npm run verify` on every pull request and every push to `main`; a verified push to `main` deploys the Worker with Wrangler. Deploys never overlap, and the deploy job skips gracefully when its credentials are absent — as in a fork.\n\nSet these under **Settings → Secrets and variables → Actions**:\n\n- `CLOUDFLARE_API_TOKEN` — Workers Scripts: Edit permission;\n- `CLOUDFLARE_ACCOUNT_ID` — the account that owns the Worker.\n\nThese authorize deployment and are separate from the runtime secrets above.\n\n### Registry publication\n\nGitHub Releases publish Durable Thinking metadata to the official MCP Registry through `.github/workflows/publish-mcp.yml`. The job uses GitHub OIDC, so it needs `id-token: write` but no long-lived Registry credential. `server.json` advertises a required `worker_host` variable and the complete `https://{worker_host}/mcp-compat` URL, reflecting the project's deploy-your-own model rather than directing strangers to one private deployment.\n\nThis repository is deliberately not published to npm. Its `package.json` describes a Cloudflare application and has no `bin` or local stdio transport; an npm artifact would not give clients an installable MCP server. If a supported local runtime is added later, npm Trusted Publishing can be introduced then, with a real executable, package ownership metadata, and provenance.\n\nSmithery URL publication is also separate from the release workflow. Smithery expects one concrete upstream URL, while each Durable Thinking owner deploys a private Worker with a GitHub allowlist. Add Smithery only if the service gains a multi-user hosted access model or Smithery supports the deploy-your-own URL template directly.\n\n### By hand\n\nRequirements: Node.js 22+, a Cloudflare account with Workers and Durable Objects enabled, Wrangler authenticated.\n\n```bash\nnpm install\nnpm run verify\nnpm run secrets            # generates a local token\n```\n\nCopy the result into an uncommitted `.dev.vars` (see `.dev.vars.example` for the GitHub fields):\n\n```dotenv\nMCP_API_TOKEN=<generated token>\n```\n\n```bash\nnpm run dev\n```\n\nWhen ready to ship:\n\n```bash\nnpx wrangler secret put MCP_API_TOKEN\nnpm run deploy\n```\n\n### Configuration\n\n`wrangler.jsonc` declares the Durable Object and KV bindings. Optional Worker variables, set via Cloudflare or a local `.dev.vars`:\n\n| Variable | Default | Purpose |\n|---|---:|---|\n| `THOUGHT_RETENTION_DAYS` | `0` | `0` retains sequences until explicit deletion; a positive value enables sliding expiration. |\n| `ALLOWED_HOSTNAMES` | automatic | Optional comma-separated host allowlist, for custom domains. |\n| `ALLOWED_ORIGIN_HOSTNAMES` | supported web clients and same-host | Optional comma-separated browser-Origin hostname allowlist; setting it replaces the default Claude and ChatGPT web origins. |\n\nThere's deliberately no public mode, tenant selector, configurable storage id, thought-logging switch, or automatic recent-history return — one user, one hard-coded Durable Object name: `personal`. Rotating `MCP_API_TOKEN` or the OAuth credentials doesn't orphan history; storage identity is independent of both.\n\n## Connect your clients\n\nTwo doors, one server.\n\n**Browser sign-in** — for hosted clients that can't send custom headers, including Claude and ChatGPT. Add a custom connector or MCP app pointing at the complete compatibility URL:\n\n```text\nhttps://<worker-host>/mcp-compat\n```\n\nKeep the `/mcp-compat` path: it is part of the protected resource identifier, not an interchangeable routing detail. The compatibility endpoint supports the 2025-era Streamable HTTP protocol used by current hosted clients. The host discovers the OAuth endpoints, dynamically registers its own callback, and opens the Durable Thinking consent page. Continue to GitHub; if your login is on the allowlist, the host receives its own Durable Thinking access and refresh tokens.\n\nThe GitHub OAuth App still uses `https://<worker-host>/callback`, as configured during deployment. That is the Worker's upstream GitHub callback; it is separate from the redirect URI that Claude or ChatGPT registers with the Worker.\n\n**Bearer header** — for CLIs and anything header-capable:\n\n```http\nAuthorization: Bearer <MCP_API_TOKEN>\n```\n\nAn exact token match routes straight to the MCP handler; the OAuth machinery never sees it.\n\nUse `/mcp` only for clients that explicitly support MCP 2026-07-28. Use `/mcp-compat` for current hosted web clients and other 2025-era Streamable HTTP clients.\n\n### OAuth troubleshooting\n\nThe browser consent step does not depend on third-party cookies. If connection fails, check the protocol surfaces in order:\n\n1. `POST /mcp-compat` without credentials must return `401` and a `WWW-Authenticate` header whose `resource_metadata` URL ends in `/oauth-protected-resource/mcp-compat`.\n2. That metadata document's `resource` value must exactly equal `https://<worker-host>/mcp-compat`.\n3. `/.well-known/oauth-authorization-server` must advertise `/authorize`, `/token`, `/register`, and S256 PKCE support.\n4. If `ALLOWED_ORIGIN_HOSTNAMES` is set, include the hosted client's hostname. Leaving it unset permits the server's own host plus the supported Claude and ChatGPT web origins; unrelated origins remain rejected.\n5. The consent page's CSP must allow `form-action 'self' https://github.com`. Earlier deployments allowed only `'self'`, so Chrome accepted the form POST but blocked its redirect to GitHub.\n\nWorker logs use fixed stage and reason fields without recording authorization codes, state values, access tokens, client secrets, or thought text.\n\n## Retention and privacy\n\nThought text can contain private prompt context, copied credentials, personal information, or uncertain conclusions. The server treats it accordingly:\n\n- every MCP request authenticates — an issued OAuth token or the bearer secret;\n- thought text is never logged;\n- one private Durable Object, owned by you alone;\n- the App's CSP blocks all outbound network access;\n- sequences can be deleted explicitly, and are retained indefinitely by default.\n\nCORS and MCP App visibility metadata are not authentication controls — keep the bearer token secret.\n\n## Development\n\n```bash\nnpm run check:app       # validates the self-contained MCP App and protocol surface\nnpm run check:contract  # guards the compact tool and visibility contract\nnpm run check:registry  # guards the official Registry manifest and version sync\nnpm run typecheck       # Worker and test TypeScript projects\nnpm run test            # Durable Object, auth, routes, and App invariants\nnpm run build           # Wrangler dry run\nnpm run verify          # all checks above\n```\n\n`src/ui/thought-process.html` is the App's source of truth; Wrangler imports it as a text module via the rule in `wrangler.jsonc`.\n\n```text\nsrc/index.ts                 Worker routes, authentication boundary, MCP handler\nsrc/server.ts                tools, compact return shapes, MCP App resource\nsrc/thought-store.ts         SQLite Durable Object implementation\nsrc/oauth.ts                 GitHub sign-in and consent flow around the OAuth provider\nsrc/ui/thought-process.html  per-thought MCP App card with final process view\nsrc/auth.ts                  fixed personal bearer authentication\nsrc/model.ts                 storage commands and records\ntest/                        Worker, storage, auth, and App tests\n```\n\n## License\n\nMIT. See `LICENSE` and `NOTICE`.\n",
  "bytes": 14282,
  "sha": "b5b5b119f78bef863a0a364a369888d1895de4d5b98676b81941ffb0d4d0ad3f",
  "repo_slug": "linxule/durable-thinking",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_linxule_durable_thinking_5e65696c/readme"
}