# Agent instructions: validate and publish a static proposal

Use these instructions with a native or server-side MCP client in OpenClaw,
Hermes, Codex, or Claude. The Agent connects to two Streamable HTTP MCP
endpoints: Validator and Deployer. A fresh installation exchanges one separately
supplied, one-time access code for two distinct 90-day credentials: a
Validator-only `ccpv_…` token and a Deployer-only `ccpd_…` token. Store each only
in its matching entry of the affected owner-private client configuration. The
one-time code is never an MCP bearer, and neither the code nor either claimed
token may be sent to the Guide, repeated, or placed in a URL, command argument,
output, or log.

The trial publishes to one administrator-owned Cloudflare Worker:

```text
https://<worker-script>.<account-subdomain>.workers.dev/<projectName>/
```

Every project path has its own user-selected Basic Auth username and password,
while all projects still share one browser origin. The user enters that credential
only on the short-lived trusted Deployer HTTPS page returned by
`create_credential_input`; the Agent never asks for or receives either value in
chat or MCP arguments. The Deployer stores it in its private project credential
store and exposes only an opaque `credentialRef`. It is not a Validator input.
Never put the credential in chat, logs, filenames, MCP arguments, status calls,
or error reports. The project password must not reuse the one-time access code,
either claimed MCP token, or another server control secret; the Deployer rejects
such reuse before capture.

HTTP is permitted for this page only when the configured Deployer endpoint is
itself an explicitly trusted loopback development endpoint; the page must still
use that exact origin.

## 0. URL-plus-one-time-code MCP client installation

The product-defined onboarding URL is:

```text
https://company-cloudflare-agent-mcp-guide.pages.dev/#install
```

This is not an MCP protocol standard. A URL without the exact `#install` fragment
is documentation only and never authorizes publishing. The fragment has no
parameters. The user supplies exactly one one-time access code separately. It
must match `^ccpc_[a-f0-9]{32}$`, expires 30 minutes after issue, and is consumed
at most once. Do not ask for a second secret.

For an explicit install request:

1. Trust only the canonical HTTPS guide origin and exact `#install` marker. Fetch
   and validate its same-origin `/.well-known/mcp-install.json` without forwarding
   the fragment; reject cross-origin redirects. Require schema `4.0.0`, contract
   `7.0.0`, and a byte-valid manifest before touching the supplied code.
2. Require exactly one role-preserving
   `trust.endpointPolicy.allowedEndpointPairs` entry. Normalize its distinct
   Validator and Deployer URLs. In production each must be an absolute HTTPS URL
   ending exactly in `/mcp`, without userinfo, query, or fragment. Missing,
   multiple, malformed, loopback, or role-swapped production pairs stop before
   code use, config changes, or MCP initialization. The canonical HTTPS Guide's
   pair id must be exactly `production`; a different id stops before code use so
   a pair-id mismatch cannot consume the code. An HTTP loopback Guide may use
   exactly one configured pair of loopback endpoints with another id for
   development.
3. Validate exactly one separately supplied code against
   `^ccpc_[a-f0-9]{32}$` without echoing it. The code is authorization for one
   credential claim only: never use it as an MCP bearer, never send it to the
   Guide, and never place it in a URL, shell argument, tool output, diagnostic,
   config file, backup, or log.
4. Detect one supported client, show the two non-secret entries, backup target,
   derived claim origin/path, and the client's persistence limitation. Preserve
   unrelated settings and create any backup with owner-private protection. Before
   consuming the code, prove that the target configuration and secret-bearing
   backup can be protected as `0600` on POSIX or user-only equivalent; otherwise
   stop without claiming credentials.
5. Derive the claim URL from the already trusted Deployer endpoint by retaining
   only its origin and appending the exact path `/agent/claim`. There is no third
   configurable origin. POST once with `Content-Type: application/json`, reject
   every redirect, and send exactly:

   ```json
   {
     "code": "<one-time code>",
     "client_id": "<codex|claude|hermes|openclaw>",
     "contract_version": "7.0.0",
     "endpoint_pair_id": "<trusted manifest pair id>"
   }
   ```

   Accept only HTTP 200 with `Cache-Control: no-store` and an exact JSON object
   containing no fields other than an opaque `install_id` matching
   `^[a-f0-9]{32}$`, a
   `validator_access_token` matching `^ccpv_[a-f0-9]{48}$`, and a
   `deployer_access_token` matching `^ccpd_[a-f0-9]{48}$`. Both tokens are
   role-scoped, revocable, and expire after 90 days. Reject uppercase, wrong
   length, extra characters, or role-swapped values before rendering any config.
6. Substitute the Validator token only into the Validator `Authorization` header
   and the Deployer token only into the Deployer header. Use a format-aware merge
   to change only `company-cloudflare-validator` and
   `company-cloudflare-deployer`. Enforce owner-private permissions before the
   first secret-bearing write and fail closed if they cannot be enforced. Backups
   containing an existing token are equally private. The current client
   APIs do not provide a common supported way to import custom claimed bearers
   into their native OAuth stores, so these interim templates use static headers:
   Codex `config.toml`, Claude user configuration, Hermes `config.yaml`, and
   OpenClaw `openclaw.json`. Never use project/shared scope or put a token in argv.
7. Run only the manifest's non-JSON `mcp list` verification command, reload the
   client, list tools inside the new Agent session, then call only
   `get_site_requirements` and `get_deployment_requirements`. Do not use a JSON
   config/list export, `hermes mcp test`, or any diagnostic that prints even a
   redacted portion of static headers. Installation must not create an upload,
   validate, deploy, verify Basic Auth, or roll back.
8. On an unambiguous rejection, stop and request a new code. On any timeout,
   disconnect, malformed response, or other ambiguous exchange, treat the code
   as consumed, do not retry it, restore the backup, and request a new code. If a
   supposedly fresh code is reported used, stop, revoke the associated install
   when identifiable, and escalate. Never fall back to a shared bearer or supply
   the Agent with per-service operator break-glass credentials. Existing static
   operator credentials are outside this onboarding contract; migration requires
   re-onboarding. Later OAuth replaces the claimed role credentials through each
   client's native login without changing the install URL or server roles.

## Required conversation order

```text
administrator setup
  -> test both connections
  -> ask for source + exact projectName
  -> one-use upload
  -> validation
  -> create trusted credential-input page
  -> user enters login only on that page
  -> wait for credential status=captured
  -> read-only plan
  -> explicit confirmation
  -> deploy/resume until active
  -> return the server-derived URL + username + captured status (never password)
```

Validation success, an installation request, the original publish request, and
urgency words are not confirmation of a plan the user has not seen.

## 1. Connection check

Before accepting a file:

1. Initialize both MCP connections and list their tools.
2. Call `get_site_requirements` and `get_deployment_requirements`.
3. Confirm Validator reports requirements version 6, `remote-upload`, and
   first-claim admission. Confirm Deployer reports receipt version 6 and
   authentication `{mode:"basic",scope:"project-path",credentialsAcceptedByMcp:false,credentialReferenceAcceptedByMcp:true,credentialInputMode:"url",credentialSource:"user-supplied-out-of-band",credentialsReturnedWhenActive:false,usernameReturnedWhenActive:true,immutablePerProject:true}`.
4. If either endpoint fails, stop with a redacted error. Never ask the user to
   repeat an already configured role token or reuse the one-time code.

Browser JavaScript must not call the MCP endpoints directly. Their static
role tokens must stay in the owner-private native/client configuration and must
never be exposed to page JavaScript.

## 2. Collect exact user decisions

Ask only for missing information:

- an HTML file, or a ZIP whose root directly contains `index.html`;
- an exact `projectName` using lowercase letters, digits, and single hyphens.

For every new upload, ask again for the exact project name. Do not guess a
project name, append a suffix, reuse a claimed name, or disclose another
claimant. `PROJECT_NAME_TAKEN` means the user must choose a different exact name.
Do not ask for, accept, generate, repeat, or infer the username or password in
chat. The user supplies both only on the trusted Deployer credential page.

React, Vue, or other source must be built locally by the user; upload only the
ready static output. Do not upload dependencies, server code, source maps with
secrets, credentials, or build-time environment files.

## 3. Allocate and upload once

Call Validator:

```json
{
  "tool": "create_input_upload",
  "arguments": {
    "projectName": "client-demo",
    "format": "html",
    "filename": "proposal.html"
  }
}
```

PUT the raw bytes once to the returned URL with only the returned upload headers.
The upload authorization is short-lived and one-use: do not show or persist it,
and do not blindly replay after an ambiguous response.

Call `check_site_readiness` with exactly `{projectName,inputRef}`. Follow its safe
next action; never invent an `inputRef` or send a client-local path to remote MCP.

## 4. Validate

Call `validate_site` with exactly `{projectName,inputRef}`. Show a concise result:

- valid or failed;
- site kind, file count, and total size;
- warnings and actionable fixes;
- proposed publishing hostname, project path, and trailing-slash URL.

The signed manifest is version 6 and binds the claim ID, artifact digest,
`authMode=basic`, exact hostname, path, and URL. On failure, do not call a
Deployer mutation.

### Expiry recovery boundary

Choose recovery from Deployer state, not from convenience:

- First call `get_deployment_status({projectName})`. It selects only the recovery
  branch and never returns a receipt. Do not infer that deployment has not
  started from a missing or ambiguous confirmation response.
- Only `STATUS_NOT_FOUND` permits pre-deploy renewal. If the credential page or
  receipt expires, retain the exact original `projectName` and `inputRef`. Call
  `validate_site({projectName,inputRef})` again. This renews only the receipt for
  the same finalized first-claim and same artifact; it is not a new validation
  claim. Then create one new credential input, repeat readiness and planning,
  display the changed receipt/reference, and obtain a new explicit confirmation.
- `SERVICE_UNAVAILABLE`, a status read failure, or any other error that is not an
  explicit `STATUS_NOT_FOUND` forbids both Validator renewal and creation of a
  credential input. Stop. Retry `get_deployment_status` later or escalate to the
  operator; never treat an error as absence.
- A nonterminal deployment may reuse the Agent-retained receipt only when status
  matches the exact project and artifact and its server-authoritative
  `recoveryMode` is `exact-receipt`. This means the confirm response may have
  been lost. Never call Validator renewal. Use the exact original accepted signed
  receipt retained from validation; do not try to obtain it from status. If
  the credential reference is no longer available, create one new credential
  input for the same project and immutable credential, then repeat readiness and
  planning, display the changed reference, and obtain a new confirmation.
- `recoveryMode="operator-required"`, a missing mode, or an invalid mode forbids
  receipt reuse and all automated recovery. Stop and escalate to the operator.
- A stable, mismatched, or malformed status is not a recovery signal. Stop and
  escalate to the operator without renewing a receipt or creating a credential
  input.

Neither recovery path may create a new upload, allocate a new project name,
reclaim the name, change the finalized artifact, or accept a changed credential.

## 5. Build a read-only deployment plan

For a valid receipt:

1. Call `create_credential_input` with exactly `{projectName}`. Capture its
   opaque `credentialRef` and returned URL without exposing the fragment in logs.
2. Accept the page URL only if its origin exactly equals the configured Deployer
   endpoint origin, its path is exactly `/credential-input/`, it has no query,
   and its fragment has exactly one each of `projectName`, `credentialRef`, and
   `submissionToken`. The first two must match the tool response. Never decode,
   copy, or log the submission token separately. If any check fails, stop.
3. Ask the user to open that exact URL and enter the username and password only
   on that page. Never ask the user to paste them into chat.
4. Call `get_credential_input_status` with exactly
   `{projectName,credentialRef}` until it reports `status="captured"`; capture
   that status response. Do not continue while it is pending or expired.
5. Call `check_deployment_readiness` with exactly
   `{projectName,receipt,credentialRef}`.
6. Call `plan_deployment` with exactly
   `{projectName,receipt,credentialRef}`.
7. Verify status, readiness, and plan agree on project, credentialRef, captured
   username, hostname, path, URL, artifact, and state.

Present one confirmation block containing:

- exact project name;
- exact `https://<worker>.<account>.workers.dev/<project>/` URL;
- artifact ID, kind, file count, and size;
- `authMode=basic`, current state, and `noOp`;
- authentication scope `project-path`, the captured username,
  plan `credentialState` (`new` or `unchanged`), the separately captured
  credential-input status, the opaque `credentialRef`, and the returned `confirmationId`;
- effect: activate a complete immutable snapshot containing every current
  active project plus this pending project.

The password never enters the Agent or MCP, and must not appear in the plan or confirmation block. Do not
expose receipt internals, filesystem paths, Worker IDs, or tokens. Ask an
explicit yes/no question tied to this exact plan and `confirmationId`. Any
source, project, receipt, artifact, target, credentialRef, captured status,
captured username, auth mode, or confirmation ID change invalidates confirmation.

## 6. Deploy only after confirmation

After explicit confirmation, call `deploy_proposal` with the exact planned
`{projectName,receipt,credentialRef,confirmationId}`. Before any
proposal bytes are uploaded, Deployer verifies:

- the exact Worker and configured workers.dev hostname;
- workers.dev is enabled and preview URLs are disabled;
- no Worker Custom Domain is attached beyond the declared publishing hostname;
- Worker routes are not enumerated because Deployer intentionally has no Zone
  permission. Route absence is an administrator-controlled assumption, not a
  runtime proof; never claim that Deployer verified it;
- an unauthenticated request receives `401`, `WWW-Authenticate: Basic`, and
  `Cache-Control: no-store` without proposal content;
- the credential is new for this project, or exactly matches the credential
  already fixed to it.

It then uploads one complete immutable Static Assets snapshot and activates the
new version at 100 percent. Only an exact project/artifact match with
`recoveryMode="exact-receipt"` may use the exact original accepted signed receipt
retained by the Agent workflow. Status does not provide it. The receipt is locked
and must never be renewed by Validator or replaced. Wait at least
`retryAfterSeconds` and retry `deploy_proposal` with the exact same receipt,
projectName, credentialRef, and `confirmationId` while that reference remains
available. If it expires first, call `create_credential_input` once for the same
project and have the user re-enter the exact immutable credential on the new
trusted page. Then repeat readiness and planning, display the changed
credentialRef, and obtain separate explicit confirmation before retrying. Never
create a replacement receipt, upload, or project name, or accept a changed
credential.
Use `get_deployment_status` to reconcile an ambiguous or non-terminal response
before retrying; status never returns credentials.

Only after state is `active`, require `access.url` and
`access.basicAuth.username` plus `access.basicAuth.status="captured"`. Then return
those three non-secret values to the user; never request or return the password:

```text
已發布：https://<worker-script>.<account-subdomain>.workers.dev/<project>/
Basic Auth username：<user-selected-username>
Credential status：captured
```

## 7. Happy-flow self-check

- Both MCP connections and requirement calls passed.
- Exact source and project came from the user.
- The user entered the Basic Auth username and password only on the exact trusted
  Deployer-origin page; the Agent and MCP arguments used only `credentialRef`.
- `get_credential_input_status` for the exact project and reference was captured.
- The one-use upload completed and validation produced a v5 receipt.
- Planning made no Cloudflare mutation.
- The user confirmed the exact displayed plan.
- Deployment used the same receipt and reached `active`.
- Missing, wrong, and another project's Basic credentials return 401; the
  correct credential serves only the exact project URL.
- The final response returned URL, username, and captured status from the active
  deploy response, and never returned the password.

## 8. Unhappy-flow behavior

- `PROJECT_NAME_TAKEN`: ask for another exact name; never suffix automatically.
- Invalid archive, root-relative asset, executable/server source, symlink, or
  secret: stop and report the safe validation fix.
- Expired receipt with `STATUS_NOT_FOUND`: renew only the same finalized
  claim/artifact receipt with the exact original projectName and inputRef; never
  allocate a new upload or name. Matching nonterminal with exact project/artifact
  and `recoveryMode="exact-receipt"`: use the Agent-retained original accepted
  signed receipt and forbid Validator renewal. `operator-required`, missing, or
  invalid recoveryMode, stable,
  mismatched, malformed, tampered, or wrong-project state: stop and escalate
  without mutation.
- `SERVICE_UNAVAILABLE` or any other status error: do not renew and do not create
  a credential input. Retry status later or escalate to the operator.
- Wrong-origin, wrong-path, malformed-fragment, or project-mismatched credential
  input: stop; never accept credentials in chat. Pending input stays pending.
  For an expired otherwise-valid input, follow the expiry recovery boundary;
  never silently create a replacement reference outside that flow.
- Missing/mismatched `confirmationId`: plan again and obtain a new explicit
  confirmation.
- User rejects or does not clearly confirm: do not deploy.
- `auth_pending` or another nonterminal deployment: bounded persisted-receipt
  recovery only when server metadata permits; never renew or replace the receipt.
- Wrong topology, missing Basic challenge, or proposal bytes visible without
  credentials: stop before upload and escalate as a security issue.
- Ambiguous deployment response: query status and reconcile; never blindly
  start a second deployment.

## 9. Administrative operations

`verify_basic_auth(projectName)` rechecks the stored project gate without
accepting or revealing its credential. Treat it as a separate administrative
operation requiring explicit confirmation. It does not rotate or return the
username or password.

Rollback is a separate administrative flow, never part of publishing or install:

1. On an explicit rollback request, call the read-only
   `plan_rollback({projectName})` first.
2. Display the exact current artifact and deployment state returned by the plan.
   Display every recorded candidate exactly as returned, including its opaque
   `rollbackTargetId`, `artifactId`, `deployedAt`, and `confirmationId`.
3. Ask the user to select one exact candidate. Never infer, default, omit, or
   auto-select `rollbackTargetId`, including when there is only one candidate.
4. After selection, display the exact current state and exact selected target
   together, then obtain a separate explicit rollback confirmation bound to that
   project, current state, candidate, and `confirmationId`.
5. Only while those values remain unchanged, call
   `rollback_proposal({projectName,rollbackTargetId,confirmationId})`. If the
   current state, candidates, selection, or confirmation changes, stop, call
   `plan_rollback` again, and obtain a new confirmation.

Rollback creates a new complete aggregate Worker version while preserving every
other project's current artifact; it never reactivates an old aggregate version.

## Security wording

Basic Auth credentials are isolated per project path, but this is still not
identity-aware authorization: there is no individual audit, OTP, or self-service
revocation. All projects also share one workers.dev browser origin. Do not claim
DOM isolation or isolation of cookies, storage, service workers, and same-origin
requests. This is a trial for content the operator is willing to trust on one
origin. The current Worker binding cap is 60 projects.
