OpenTree DashboardSign in

$ codex mcp

Private hosting for what Codex builds

Codex writes a page; the same session publishes it to a private URL. Works from the CLI, from the IDE extension, and from a ChatGPT-side workspace that can reach an HTTP MCP server.

Add the server

Codex reads an MCP configuration like every other client. Over HTTP:

{
  "mcpServers": {
    "opentree": {
      "type": "http",
      "url": "https://read.botook.ai/mcp",
      "headers": { "Authorization": "Bearer otr_live_YOUR_KEY" }
    }
  }
}

Or as a local stdio process, which is the shape the OpenAI CLI prefers by default:

[mcp_servers.opentree]
command = "npx"
args = ["-y", "opentree-mcp"]
env = { OPENTREE_API_KEY = "otr_live_YOUR_KEY" }

Both transports expose the identical twelve tools against the identical account — stdio is a thin bridge onto the same HTTP surface. Install it once:

npx -y opentree-mcp

Publish from the session

publish_html({
  content: "<!doctype html><title>Q3 review</title>…",
  expires_in_hours: 168,
  password: "optional",
  agentation: true
})

The reply contains the unlisted URL. Nobody needs an account to open it, and nothing about it is public: no listing, no index, no guessable id.

Where Codex Sites stops

Codex can already produce and preview an artifact. What it does not give you is a link with policy attached — a password, an expiry, a work-email gate, a hostname that is yours. That is the gap this fills, and it is the same gap described on the comparison page.

Access rules as arguments

FieldEffect
passwordA passcode in front of the page, checked before any content is served.
allowed_email_domainOnly addresses at that work domain may open it, confirmed by a one-time email.
expires_in_hoursDeletion time. never keeps the link open.
burn_after_readThe first successful open is the last one.
pii_checkblock by default: sensitive content refuses to publish.

Update, do not republish

Codex loops. Replace the content behind the existing token rather than minting a new link every iteration:

update_site({ site_id: "…", content: "<!doctype html>…" })

The same thing without the MCP layer

If the sandbox can run curl, it can publish — no server registration involved:

curl -X POST https://read.botook.ai/sites \
  -H "Authorization: Bearer otr_live_YOUR_KEY" \
  -F file=@index.html

And with no account at all:

curl -X POST https://read.botook.ai/v1/publish -F file=@index.html

Sharing across a team

One page can carry several links. Mint a share token per audience and revoke them independently, so cutting off a contractor does not disturb the client:

curl -X POST https://read.botook.ai/share-tokens \
  -H "Authorization: Bearer otr_live_YOUR_KEY" \
  -H "content-type: application/json" \
  -d '{"site_id":"SITE_ID","label":"design-review","max_views":25}'

Reading the page back

A second agent often needs the content, not the markup. /raw/:token answers plain text:

curl https://read.botook.ai/raw/UNLISTED_TOKEN

When the sandbox has no network

Codex often runs with egress switched off, and then no amount of configuration will let the tool call leave the box. The honest fix is to move the publish one step outward: have the session write the file to the workspace and let the host publish it, either by hand or from the same task runner that already has credentials.

# inside the sandbox: just write the file
#   dist/report.html
# on the host, afterwards:
curl -X POST https://read.botook.ai/sites \
  -H "Authorization: Bearer otr_live_YOUR_KEY" \
  -F file=@dist/report.html

What Codex can hand over

Not everything a coding session produces is HTML, and none of it needs converting first:

ShapeHow to send itWhat is stored
HTML-F file=@page.htmlThe bytes, as written
Markdown-H 'content-type: text/markdown'Rendered to a self-contained document at publish time
SVG-F file=@diagram.svgThe bytes, served as an image
A build tree-F file=@site.zipUnpacked, entry at index.html

What the reviewer sees

A rendered page in a browser, with no account. Turn agentation on and they can leave a note pinned to the paragraph they disagree with; turn reactions on and they get a one-click emoji row instead. Both are off by default, because an internal build artifact usually should not invite comments.

Reading the result back

The session that published a page can ask what happened to it. Two calls cover it:

curl -s https://read.botook.ai/sites/SITE_ID/engagement -H "Authorization: Bearer otr_live_YOUR_KEY"
curl -s https://read.botook.ai/sites/SITE_ID/feedback   -H "Authorization: Bearer otr_live_YOUR_KEY"

Engagement is aggregate: opens, unique viewers, dwell, scroll depth, read-to-end. Feedback is the actual text a human left, with the anchor it was attached to.

Tidying up a loop's output

A loop that publishes on every iteration leaves a trail. List and delete, or let the default seven-day expiry do it for you:

curl -s https://read.botook.ai/sites -H "Authorization: Bearer otr_live_YOUR_KEY" | jq '.sites[].id'
curl -X DELETE https://read.botook.ai/sites/SITE_ID -H "Authorization: Bearer otr_live_YOUR_KEY"

Questions

Does this work from ChatGPT rather than the CLI?

If the workspace can reach an HTTP MCP server, yes — point it at https://read.botook.ai/mcp. Otherwise use the connector-style OAuth flow or plain HTTP.

Is the link public?

No. The token is 22 random characters and the response carries noindex. Public sharing is something you opt into by handing the link over.

Can I keep the artifact forever?

Pass expires_in_hours: "never". There is no ceiling on this deployment — see pricing.

Keep reading