Post Plan
FreeNot checkedEnables publishing and unpublishing model-authored HTML plans through a dependency-free MCP server with publish_plan and unpublish_plan tools.
About
Enables publishing and unpublishing model-authored HTML plans through a dependency-free MCP server with publish_plan and unpublish_plan tools.
README
Post Plan is a Cloudflare Worker that publishes complete model-authored HTML plans at post-plan.boyan.live. It stores current plans and revision history in D1, isolates the model document in a hardened viewer, and adds a visible codex://threads/<thread-id> link back to the originating Codex task.
Publishing is designed for a small, invite-only group. Each publisher signs in through a dedicated GitHub OAuth App, receives an independent and revocable Post Plan session, and owns the plans they create. GitHub is used only to establish public account identity: the device flow requests no OAuth scopes, and Post Plan does not retain the GitHub access token.
The repository also contains a Codex plugin that supplies:
- an implicitly invoked
publish-planplanning skill; - a dependency-free MCP server with
publish_planandunpublish_plantools; - a trusted
UserPromptSubmithook that passes the current session ID into the workflow; - a deliberate replacement for built-in Plan mode in Default mode.
Codex does not expose a plugin API for registering or changing collaboration modes. The hook therefore blocks prompts sent in built-in Plan mode and asks the user to switch to Default, where the plugin can publish the online plan.
Architecture and authorization
One-time login
Local configure script ──> Post Plan Worker ──device flow──> GitHub
│ │ │
│ ├── public profile / ID <──┤
│ ├── invite check in D1
│<── Post Plan session ───┤ (GitHub token discarded)
Normal publishing
Codex skill/hook ──> local stdio MCP ──Post Plan session──> Worker
│
├── D1 users, session hashes,
│ owned plans + revisions
└── /p/<unlisted-id>
└── codex://threads/<uuid>
GitHub device authorization is a login bootstrap, not a dependency for normal publishing. The Worker uses GitHub's immutable numeric user ID for authorization and keeps the current login only as a display snapshot. It requests no email, repository, organization, or write scope. The GitHub access token returned during device login is used only to call GitHub's authenticated-user endpoint and is then discarded.
Post Plan stores only a SHA-256 digest of each opaque API session. Sessions can expire or be revoked without affecting other friends. A plan has one owner: only that owner can revise or revoke it, and a cross-user mutation returns 404 rather than revealing whether the plan exists.
Public plan links remain unlisted, not private. Anyone with a /p/<id> URL can read it, including historical revision URLs. GitHub sign-in protects publishing; it does not protect viewing. The Codex deep link is only a routing identifier and neither contains credentials nor grants access to a task.
The Worker supports three rollout modes:
AUTH_MODE |
Accepted write credential | Intended use |
|---|---|---|
legacy |
Shared PUBLISH_TOKEN only |
Compatibility and rollback only |
dual |
GitHub-backed Post Plan sessions or PUBLISH_TOKEN |
Migration and canary window |
session |
GitHub-backed Post Plan sessions only | Final friend-sharing configuration |
The checked-in implementation does not migrate or deploy the production service automatically. Follow the staged rollout below after registering the OAuth App and taking a D1 backup.
Local development
Requirements: Node.js 20 or newer, a Cloudflare account containing the active boyan.live zone, Wrangler authentication, and a dedicated GitHub OAuth App with Device Flow enabled.
Create the OAuth App in GitHub's developer settings. Use the local or production Post Plan URL as its homepage, enable device flow, and do not request scopes. Device flow uses the app's client ID and does not require its client secret.
npm install
npx wrangler whoami
Create the D1 database once, then put its returned UUID in wrangler.jsonc. Copy the local variable template and set these values in .dev.vars:
npx wrangler d1 create post-plan
cp .dev.vars.example .dev.vars
# Required only when overriding AUTH_MODE to legacy or dual.
PUBLISH_TOKEN=replace-with-at-least-32-random-characters
# Public identifier of a dedicated GitHub OAuth App with device flow enabled.
GITHUB_CLIENT_ID=Iv1.replace-with-your-client-id
wrangler.jsonc contains the checked-in production defaults AUTH_MODE=session and SESSION_TTL_DAYS=90. Override the Wrangler variables only when testing a different rollout mode or session lifetime; do not duplicate them in .dev.vars.
Apply migrations, grant your GitHub account access in the local D1 database, and start the Worker:
npm run db:migrate:local
npm run auth:user -- grant YOUR_GITHUB_LOGIN --role owner --apply
npm run dev
In another terminal, authenticate the plugin against the local Worker. The command prints GitHub's verification URL and one-time user code, polls at GitHub's required interval, and stores only the returned Post Plan session in the mode-0600 config file.
node plugins/post-plan/scripts/configure.mjs \
--url http://127.0.0.1:8787 \
--login
node plugins/post-plan/scripts/configure.mjs --check
The local API is available at http://127.0.0.1:8787. A valid Post Plan session can also be supplied to automation as POST_PLAN_ACCESS_TOKEN; the old POST_PLAN_TOKEN name is accepted only during the compatibility release.
curl -fsS http://127.0.0.1:8787/healthz
jq -n --rawfile html examples/example-plan.html \
'{title:"Example plan", html:$html, status:"active"}' | \
curl -fsS http://127.0.0.1:8787/api/plans \
-H "Authorization: Bearer $POST_PLAN_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @-
Production rollout
Treat this as a migration checklist. Do not switch directly from the shared token to session-only authentication.
- Register a dedicated production GitHub OAuth App, set its homepage to
https://post-plan.boyan.live, enable device flow, and record its client ID. No client secret or requested scopes are needed. - Export or otherwise back up the remote D1 database and record the current plan and revision counts.
- Configure
GITHUB_CLIENT_ID,AUTH_MODE=dual, andSESSION_TTL_DAYS=90as Worker variables. Keep the existingPUBLISH_TOKENsecret during the dual-auth canary. - Run the checks and additive migration, then grant the owner and one friend by GitHub login. The management script resolves each login to the immutable GitHub numeric ID before storing the grant.
npm run check
npm run db:migrate:remote
npm run auth:user -- grant OWNER_GITHUB_LOGIN --role owner --apply --remote
npm run auth:user -- grant FRIEND_GITHUB_LOGIN --role publisher --apply --remote
npm run auth:user -- list --remote
npm run deploy
curl -fsS https://post-plan.boyan.live/healthz
- Have both accounts complete
--login, publish, revise, and revoke separate canary plans. Confirm that each can mutate only their own plan and that existing public and historical URLs still render. - Immediately before session-only cutover, backfill every legacy plan to the seeded owner and verify unchanged IDs, row counts, and zero null owners:
npm run auth:user -- backfill-owner OWNER_GITHUB_LOGIN --apply --remote
npm run auth:user -- verify --remote
The checked-in 0003 migration is deliberately additive. Create and apply the NOT NULL/owner-scoped idempotency rebuild only in a later release after these production checks pass.
- Change
AUTH_MODEtosession, deploy, verify the canaries again, and only then remove the shared secret:
npm run deploy
npx wrangler secret delete PUBLISH_TOKEN
If the canary fails before that later ownership-enforcement release, return to AUTH_MODE=dual and redeploy while keeping the additive identity tables. Do not rewrite ownership data as part of an application rollback. Restore the D1 backup only for a verified migration-integrity failure.
Invite management is intentionally operator-only:
# Add or restore a friend.
npm run auth:user -- grant FRIEND_GITHUB_LOGIN --role publisher --apply --remote
# Disable the account and all of its sessions. A stored login or numeric ID works.
npm run auth:user -- revoke FRIEND_GITHUB_LOGIN_OR_ID --apply --remote
# Audit current grants without changing them.
npm run auth:user -- list --remote
Omit --remote to manage the local Wrangler D1 database. Mutating commands require --apply, which makes accidental read-only invocations safe.
Friend install and login
An operator must grant the friend's GitHub login before they authenticate. A grant is tied to the account's immutable numeric GitHub ID, so a later username change does not transfer access to another person.
From a public checkout, install the Codex plugin:
git clone https://github.com/Johnnybyzhang/post-plan.git
cd post-plan
/Applications/Codex.app/Contents/Resources/codex \
plugin marketplace add "$PWD"
/Applications/Codex.app/Contents/Resources/codex \
plugin add post-plan@post-plan
Then sign in. --login opens or prints GitHub's verification page and user code; GitHub shows that the app requests no scopes. Post Plan writes its own session to ~/.config/post-plan/config.json with mode 0600 and never writes it into the checkout.
node plugins/post-plan/scripts/configure.mjs --login
node plugins/post-plan/scripts/configure.mjs --check
--check verifies the session with the service and shows the GitHub login snapshot and session expiry without printing the credential. Review and trust the bundled hook when Codex asks, restart Codex, use Default mode, and ask naturally for a plan—or invoke $post-plan:publish-plan explicitly.
Log out when the machine should no longer publish:
node plugins/post-plan/scripts/configure.mjs --logout
node plugins/post-plan/scripts/configure.mjs --check
Logout revokes the current Post Plan session; it does not revoke unrelated friends, change GitHub permissions, delete published plans, or make their unlisted viewer URLs private.
The public GitHub marketplace form is also supported:
/Applications/Codex.app/Contents/Resources/codex \
plugin marketplace add Johnnybyzhang/post-plan
/Applications/Codex.app/Contents/Resources/codex \
plugin add post-plan@post-plan
For plugin development, use the checkout-path form above so Codex reads the local plugin files.
API
All responses are JSON except public HTML pages. Authenticated API routes use Authorization: Bearer <POST_PLAN_ACCESS_TOKEN>. A legacy PUBLISH_TOKEN is accepted only when AUTH_MODE is legacy or dual.
| Method | Route | Purpose |
|---|---|---|
POST |
/api/auth/device/start |
Start GitHub device authorization; returns the verification URL, user code, expiry, and polling interval |
POST |
/api/auth/device/poll |
Poll authorization and exchange an invited GitHub identity for a Post Plan session |
GET |
/api/me |
Read the current login snapshot, role, and session expiry |
DELETE |
/api/sessions/current |
Revoke the current Post Plan session |
POST |
/api/plans |
Publish an owned plan; supports idempotency_key |
GET |
/api/plans/:id |
Read the current plan as JSON |
PATCH |
/api/plans/:id |
Publish a new revision of the caller's plan; supports if_revision |
DELETE |
/api/plans/:id |
Revoke the caller's public plan |
GET |
/api/plans/:id/revisions |
List revision metadata |
GET |
/p/:id |
View the current public, unlisted plan |
GET |
/p/:id/r/:revision |
View a historical public revision |
GET |
/healthz |
Check Worker and D1 health |
Device start and poll routes are unauthenticated but rate-limited. The Worker never returns or logs GitHub's temporary access token. A successful poll returns the Post Plan session once; only its hash and lifecycle metadata are retained in D1.
Create fields are title, html, summary, status, tags, codex_thread_id, repository, branch, and idempotency_key. html must be a complete document beginning with <!doctype html> and containing html and body elements.
The stable viewer route embeds the model document from /p/:id/content in an iframe without allow-same-origin. A response CSP blocks network connections, external scripts/styles, forms, nested frames, workers, and objects. Inline CSS and small inline presentation scripts are allowed inside the isolated document.
Stored identity data
Post Plan stores the GitHub account's immutable numeric ID, its current login as a display snapshot, the assigned Post Plan role and invite/revocation timestamps, and session metadata such as a token hash, session label, and issue/expiry/last-used/revocation times. It does not store the GitHub access token, GitHub email, private repository data, or organization membership. See the live /privacy and /terms pages before inviting friends.
Codex links
The installed Codex desktop app registers the codex URL scheme. Existing local tasks use:
codex://threads/<uuid>
Codex injects the current exact thread as MCP request metadata at _meta.threadId. Shell-based fallbacks receive CODEX_THREAD_ID, while the plugin hook receives the root session_id. The public page uses a normal user-clicked anchor because browsers may ask for confirmation before opening Codex.
Cloud tasks use a separate web route, https://chatgpt.com/codex/tasks/<cloud-task-id>. A local thread UUID must not be inserted into that Cloud-task URL.
Installing Post Plan
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/Johnnybyzhang/post-planFAQ
Is Post Plan MCP free?
Yes, Post Plan MCP is free — one-click install via Unyly at no cost.
Does Post Plan need an API key?
No, Post Plan runs without API keys or environment variables.
Is Post Plan hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Post Plan in Claude Desktop, Claude Code or Cursor?
Open Post Plan on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectCompare Post Plan with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
