zudo-text

検索したい単語を入力

いつでも検索バーを開ける

Restricted Kanban external access

The OAuth, REST and MCP boundary for approved Kanban boards.

This is an unreleased source capability. Deploy the S1 command service, S3 authority and leases, captured-base UI writer and first-party connection/consent UI together. Published npm instructions do not imply this change has been released.

Resource and transports

The canonical resource audience is ${KANBAN_OAUTH_ISSUER}/api/v1/kanban/mcp. The issuer is an HTTPS origin without a trailing slash or path. Exact loopback HTTP is permitted only for development. Remote MCP uses the official MCP SDK's stateless Streamable HTTP transport with JSON responses. Its only tools are:

  • list_kanban_boards

  • get_kanban_board

  • get_kanban_card

  • create_kanban_card

  • update_kanban_card

  • move_kanban_card

  • archive_kanban_card

  • restore_kanban_card

POST /api/v1/kanban/tools/{toolName} exposes the same strict input/output schemas and calls the same command service. REST intentionally accepts the MCP audience only on these eight routes. The local package's ZUDOTEXT_MCP_PROFILE=kanban exposes the same eight tools using an independently restricted PAT, HTTPS Worker origin and no workspace password. The normal authoring registry retains its 21 authoring tools; the restricted profile advertises only the eight Kanban tools. A full authoring PAT does not substitute for a restricted Kanban connection.

Every mutation requires a caller-retained idempotencyKey and the exact observed expectedRevision. A retry with the same request and key returns the receipt; a changed request with that key fails. On VERSION_CONFLICT, reread and reconsider the intended edit. Never silently replace the expected revision. Version-bound legacy card references expire with that card version. Board selection grants access to the selected manifest and its direct-child cards only. No directory, workspace-wide document, attachment, raw key or generic file operation is exposed.

Board revision and discovery contract

The observed board revision covers the selected manifest and every direct-child Markdown head, including orphan files, paths, versions, hashes and lifecycle identities. Unrelated workspace saves do not change this public revision. The workspace epoch remains an internal atomic membership/absence fence: admission may refresh it only after proving the same original board read set. Four bounded attempts prevent unbounded retries; sustained churn can still return a safe VERSION_CONFLICT. All authority, expiry, generation and file preconditions remain in the final D1 transaction. A successful same-state command advances the manifest version so competing claims from one observation cannot both win.

Board listing shares one encrypted metadata enumeration and downloads only selected manifest bodies. Pagination and final selection/authority checks remain bounded. Listing does not certify the availability or format of every card; full board reads perform those checks. Authenticated ciphertext accepts both existing desktop HMAC and automation SHA-256 content-hash formats; new Kanban writes use the desktop HMAC format.

OAuth protocol boundary

@cloudflare/workers-oauth-provider is pinned exactly to 1.2.3. Its split OAuthAuthorizationServer and OAuthResourceServer classes perform protocol parsing, browser consent binding, PKCE, token issuance/verification, refresh and revocation. No handwritten OAuth server or token validator substitutes for it. A pinned pnpm patch replaces only its untrusted CIMD error warning with fixed generic text; protocol behavior is unchanged. Patch SHA-256: f98603533e33294309092d1419871ed82b0b917fb4246d4510a2b10a5f1fd9e6.

Discovery lives at /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource/api/v1/kanban/mcp. Authorization is /oauth/authorize; token issuance, refresh and RFC 7009 revocation share /oauth/token. Only S256 authorization-code flows, the canonical resource and kanban:read/kanban:write are accepted for Kanban. The same server also declares the TODO resource (/api/v1/kanban/todo/mcp, todo:read/todo:write); the token audience selects exactly one family and an omitted resource stays Kanban — see Format-aware TODO item tools. There is no public dynamic registration endpoint. HTTPS client metadata documents and administratively registered clients use the provider's validation. global_fetch_strictly_public is required for safe metadata fetching. Client names are escaped and are not identity proof.

Token properties and provider grant metadata contain only an opaque Kanban grant identifier. Subject, client, expiration and token scopes come from provider verification. Effective scopes intersect verified token scopes with current D1 grant scopes. Generic app routes, token minting and AI inference do not accept these credentials. External access never depends on built-in AI inference consent.

The local kanban_oauth_bindings D1 table stores opaque provider-grant, application-grant, user and client identifiers, never tokens or keys. Binding is created and checked before token exchange completes. Every read, replay and final write transaction rechecks current authority. The memory-only lease cannot be recreated or extended by refresh. Explicit first-party unlock lasts at most 24 hours; eviction or deployment can end it earlier.

Pinned revocation adapter

Provider 1.2.3 has no application grant-revocation callback. Only explicit RFC 7009 requests receive a request-scoped KV wrapper. The wrapper observes the provider's grant:{user}:{grant} and token:{user}:{grant}:{hash} deletion keys after its client/token ownership validation. A single D1 transaction captures the current grant revision and revokes the connection, then disposes that exact revision's lease before KV removal. A concurrent completed unlock therefore cannot leave a newer lease resident after revocation. Revoking an access token deliberately revokes the entire connection. Refresh and ordinary cleanup use the unwrapped KV and cannot revoke continuing grants. Duplicate form fields fail closed; inspection uses a clone and the original request goes to the provider. A provider upgrade must re-audit these keys/call ordering and rerun real Worker protocol/race tests.

First-party management contract

Bearer first-party app authentication is required for /api/v1/kanban management routes. Restricted tokens are rejected. Cookie-based Better Auth authentication is required for the Worker-origin /oauth browser routes; they map the existing better_auth|{id} subject without provisioning another application identity. Browser mutations require the configured issuer as Origin.

  • GET /api/v1/kanban/catalog or /oauth/catalog: owned ready workspaces with id, name, encryptionSalt.

  • GET .../catalog/{workspaceId}?offset=0: up to 500 encrypted file heads with manifestFileId, encryptedPath, lifecycleRevision, and nullable nextOffset. The first-party browser decrypts paths to offer candidate KANBAN.md boards.

  • POST /api/v1/kanban/selection or /oauth/selection: {workspaceId, manifestFileId, keys} verifies the actual manifest and returns its current lifecycle selection and display-only manifestPath. Keep the path for display; submit only {manifestFileId,lifecycleRevision} in selections. Keys are transient first-party inputs only.

  • GET /api/v1/kanban/connections: {connections}. Each item includes grant metadata, independent leaseStatus (locked, unlocked, expired, revoked), nullable leaseExpiresAt and nullable lastSuccessfulMutationAt from receipts. This timestamp covers successful mutations; reads do not write activity state.

  • POST /api/v1/kanban/connections: {workspaceId, manifestSelections:[{manifestFileId,lifecycleRevision}], scopes, expiresAt, keys} creates a locked local connection with a server-assigned client.

  • POST /api/v1/kanban/connections/{grantId}/unlock-reservation: {expectedRevision} advances to a locked revision and returns that revision with a single-use, memory-only attemptToken valid for at most 60 seconds. This request contains no keys.

  • POST /api/v1/kanban/connections/{grantId}/unlock: {expectedRevision,attemptToken,keys} enrolls the reserved revision, then conditionally activates it; returns {revision,expiresAt}. A bare legacy unlock body is rejected.

  • PATCH /api/v1/kanban/connections/{grantId}: {expectedRevision,status?,scopes?,removeManifestFileIds?} locks, revokes or reduces authority. Scopes and board choices cannot be enlarged this way.

  • POST /api/v1/kanban/connections/{grantId}/token: {name} mints a restricted PAT only for local connections. An OAuth connection cannot be converted to a PAT.

  • GET /oauth/session: existing signed-in application's {userId}.

  • POST /oauth/consent: connection-selection input plus the provider's opaque browser-bound handle. The provider supplies client identity and original requested scopes. Returns {grantId,revision,redirectTo} and cookie headers. Approval creates a locked connection; unlock is a separate human action.

  • POST /oauth/deny: form handle; returns the provider denial redirect.

Unlock enrollment happens while D1 remains locked. Failed or ambiguous enrollment conditionally advances a locked barrier for that attempt and disposes its lease; cleanup never overwrites a later lock, revoke, reduction or unlock. Revisions never roll back. The first-party client reconciles authoritative connection state on failure, so Retry and Lock do not need a page refresh. Lost final responses trigger conditional cancellation using the reserved revision. If cancellation and reconciliation are both unreachable, the client reports an unknown outcome rather than claiming confirmed cancellation. Memory-only custody, expiry and final transaction authorization still apply.

The Worker-origin consent shell exposes #kanban-consent with a data-consent-handle integration seam. The first-party consent UI owns login, workspace/board selection, clear review and explicit submission; no human should need to enter IDs or key material. It must preserve browser cookies and use same-origin requests. Full UI integration belongs to S5. The bundle must be served from this Worker origin; inline scripts require an explicit CSP hash or nonce. For email login use same-origin POST /api/auth/sign-in/email with {email,password,callbackURL} and credentials included. Return to the original authorization URL after login to obtain a fresh browser-bound handle. Verify /oauth/session before selection; never accept a user ID from the browser as proof of identity. Existing Better Auth social flows retain their configured providers and callback handling.

Connection lists expose a safe opaque clientId; they do not fetch third-party metadata during settings reads. Label a local connection as a local client, and show an escaped identifier fallback for OAuth connections until a trusted display label is available. Pure response interfaces are exported by @takazudo/kanban-api for the first-party settings client.

Configuration and verification

Existing deployments remain operational without OAuth configuration. OAuth returns an explicit unavailable response until both a real OAUTH_KV binding and KANBAN_OAUTH_ISSUER are configured. No fake namespace ID is committed. Namespace provisioning, migration rollout, deployment and persistent credentials require separate approval. Better Auth's existing required configuration remains in force.

Focused tests bundle the actual production app into Miniflare/workerd with real D1, KV, R2 and Durable Objects. They exercise the pinned provider and MCP SDK; Node-only Hono tests separately use a minimal WorkerEntrypoint lifecycle shim. Protocol tests are not evidence of acceptance by ChatGPT, Codex CLI, Claude Code or Codex Cloud. Their deployed login/consent/reconnect/token-storage and actual-client acceptance checks remain explicit release gates. No real external account action or package publication is part of these local tests.

The optional live verifier accepts kanban: {enabled:true, boardId, column, acknowledgeArchivedResidue:true} in its non-secret config, plus an independent ZUDOTEXT_LIVE_KANBAN_PAT environment credential. It requires an explicitly approved disposable board/column and leaves one archived fixture card. Missing inputs keep the restricted Kanban verdict unverified; generic authoring verification retains its separate enabled-probe verdict. This probe does not provision credentials, unlock a connection, discover a board as authorization, or stand in for real ChatGPT/Codex/Claude acceptance.