Stacktree MCP Server

Publish HTML to a private, unguessable URL from any MCP client. Gate by password or email domain; replace in place.

Documentation

Stacktree — docs

Source: https://stacktr.ee/docs

[

  Stacktree docs
](https://stacktr.ee/)

  [Agents](https://stacktr.ee/agents)
  [Docs](https://stacktr.ee/docs)
  [Use cases](https://stacktr.ee/use-cases)
  [Pricing](https://stacktr.ee/pricing)
  [Blog](https://stacktr.ee/blog)

[Dashboard](https://app.stacktr.ee)

Search docs⌘K

  Getting started

    - [Connect an agent](#connect)

        [CLI installer](#installer)

        - [Claude.ai](#claude-ai)

        - [Claude Code · Codex](#claude-code)

        - [MCP config file](#mcp-config)

        - [Slack](#slack)

  Reference

    - [HTTP API](#api)

        [Auth](#auth)

        - [POST /sites](#post-sites)

        - [PUT /sites/:id](#put-sites)

        - [PATCH /sites/:id](#patch-sites)

        - [List · raw · delete](#more-sites)

        - [Share tokens](#share-tokens)

        - [API keys](#keys)

    - [MCP server](#mcp)

    - [Agent payments (x402)](#payments)

    - [Custom domains](#domains)

    - [OAuth](#oauth)

    - [Limits](#limits)

  Machine-readable

    - [llms.txt](https://stacktr.ee/llms.txt)

    - [pricing.md](https://stacktr.ee/pricing.md)

    - [auth.md](https://stacktr.ee/auth.md)

    - [x402.md](https://stacktr.ee/x402.md)

[Changelog](https://stacktr.ee/changelog) · [Blog](https://stacktr.ee/blog)

[Dashboard](https://app.stacktr.ee) · [Pricing](https://stacktr.ee/pricing)

Documentation

The publish primitive for agent-made HTML.

private by default MCP-native replace-in-place

For a guided walkthrough with your actual API key inlined into snippets, open app.stacktr.ee/connect.

Connect an agent

npx stacktree-install — recommended

One command wires every agent at once — Claude Code, Cursor, Codex, OpenCode, Amp — and drops the stacktree-publish skill in for Claude:

`npx stacktree-install`

It signs you in via a one-time code at app.stacktr.ee/connect/cli and mints an API key automatically; pass an existing key as an argument (npx stacktree-install stk_live_…) to skip sign-in.

Claude.ai — custom connector

No CLI, no API key copy-paste.

- Open [claude.ai/settings/connectors](https://claude.ai/settings/connectors) → Add custom connector.

- Paste https://api.stacktr.ee/mcp as the Remote MCP server URL.

- Leave OAuth Client ID/Secret blank — Stacktree auto-registers via Dynamic Client Registration (RFC 7591).

- Click Add → Claude.ai redirects you to Stacktree to sign in and approve. Tools are then available in any conversation.

Claude Code · Codex — CLI

One-line install. Identical syntax between the two:

`claude mcp add stacktree -- npx -y stacktree-mcp
codex mcp add stacktree -- npx -y stacktree-mcp`

Both expose the same 8 tools (see MCP server). Set STACKTREE_API_KEY in your shell — generate one at app.stacktr.ee/api-keys.

Prefer a skill? Installs SKILL.md + helper script into your agent's skills directory:

`npx skills@latest add stevysmith/stacktree-skill
export STACKTREE_API_KEY=stk_live_...`

Source: github.com/stevysmith/stacktree-skill · the whole collection at stacktr.ee/skills

MCP config file — Cursor / Claude Desktop / Windsurf / Zed

`{
  "mcpServers": {
    "stacktree": {
      "command": "npx",
      "args": ["-y", "stacktree-mcp"],
      "env": { "STACKTREE_API_KEY": "stk_live_..." }
    }
  }
}`

Drop into ~/.cursor/mcp.json, ~/Library/Application Support/Claude/claude_desktop_config.json, ~/.codeium/windsurf/mcp_config.json, or the context_servers key in Zed's settings.

Slack

Add the Slack app (one approval; installing mints the workspace its own free identity, no Stacktree account needed). Then ⋮ → Host on Stacktree on any message with an .html or .md file — channels or DMs — posts a private link back into the conversation. Re-uploading the same filename republishes to the same URL. /stacktree link migrates the workspace's sites into a dashboard account. Details: stacktr.ee/slack.

HTTP API

Auth

Three methods, all resolve to the same user context:

  Authorization: Bearer stk_live_…API key
  Create at [app.stacktr.ee/api-keys](https://app.stacktr.ee/api-keys) — or let an agent buy its own over [x402](#payments).

  Authorization: Bearer <clerk-session-jwt>session
  Clerk session token, for dashboard-originated calls.

  Authorization: Bearer <oauth-jwt>OAuth
  Access token from `/oauth/token`, used by custom connectors.

POST/sites Upload a single HTML/markdown file or a zip. multipart/form-data. Anonymous uploads work — no auth header — and live 24 hours.

  filefilerequired
  .html / .htm / .md / .zip

  public_slugstring
  Opt-in public subdomain; authed only.

  passwordstring
  Basic-auth gate on serve.

  expires_in_hoursnumber | "never"
  Default: 24h anonymous, never authed (the MCP layer overrides this to 7 days).

  burn_after_read"true"
  Delete after first view.

  agentation"true"
  Inject feedback toolbar on serve.

  csp_strict"false"
  Disable strict CSP (default on).

  e2e"true"
  Treat upload as ciphertext; the decryption key lives in the URL fragment and is never sent to Stacktree.

  pii_checkoff | warn | block
  Default warn (MCP layer overrides to block).
`curl -F [email protected] \
     -F password=hunter2 \
     -F expires_in_hours=72 \
     -H "Authorization: Bearer stk_live_..." \
     https://api.stacktr.ee/sites`
`{
  "id": "…",
  "url": "https://stacktr.ee/p/abc123…/",
  "visibility": "unlisted",
  "expires_at": 1781234567,
  "file_count": 1,
  "size_bytes": 1234,
  "has_password": true,
  "agentation": false
}`

PUT/sites/:idOrSlug Replace a site's files in place — the URL never changes. Same multipart fields as POST. Authed only; 401 if not the owner. E2E-encrypted sites must be replaced with e2e=true uploads (no silent downgrade to plaintext).

`curl -X PUT -F [email protected] \
     -H "Authorization: Bearer stk_live_..." \
     https://api.stacktr.ee/sites/my-deck`

PATCH/sites/:idOrSlug Update settings without re-uploading files. JSON body.

  passwordstring | null
  Set or remove the password gate.

  expires_in_hoursnumber | null
  `null` cancels expiry.

  allowed_email_domainstring | null
  Viewers verify an email on that domain before the page renders.

  public_slugstring | null
  Claim or release a public subdomain.

  agentation · burn_after_read · csp_strictboolean
  Toggle the serve-time behaviors documented under POST.
`curl -X PATCH \
     -H "Authorization: Bearer stk_live_..." \
     -H "Content-Type: application/json" \
     -d '{"agentation": true, "expires_in_hours": null}' \
     https://api.stacktr.ee/sites/my-deck`

List · fetch · raw · delete

  GET/sites
  List your sites.

  GET/sites/:idOrSlug
  One site, with file manifest + absolute `preview_url`.

  GET/raw/:token
  Page HTML stripped of head/scripts — clean text for re-feeding into an agent. Honors password gates.

  DELETE/sites/:idOrSlug
  Hard delete: removes R2 objects and metadata.

Share tokens

`POST   https://api.stacktr.ee/sites/:idOrSlug/share-tokens   { "label": "alice", "max_uses": 5, "expires_in_hours": 168 }
GET    https://api.stacktr.ee/sites/:idOrSlug/share-tokens
DELETE https://api.stacktr.ee/share-tokens/:tokenId`

Returns a URL with ?t=… appended. Bypasses the password gate when valid; revocable per-token; optional max-use counter and expiry.

API keys

`POST   https://api.stacktr.ee/api-keys      { "label": "claude desktop" }
GET    https://api.stacktr.ee/api-keys
DELETE https://api.stacktr.ee/api-keys/:id`

MCP server

Streamable HTTP MCP server at https://api.stacktr.ee/mcp (spec 2025-11-25). Connect from any MCP host that supports OAuth-based remote servers — see Connect an agent.

Tools

  publish_html→ { url, id, expires_at, … }
  One call, one private URL.

  update_site→ { url, file_count, size_bytes }
  Replace in place; the URL survives every revision.

  set_password→ { ok: true }
  Password-gate a site.

  set_expiry→ { ok: true }
  Schedule or cancel auto-delete.

  set_email_gate→ { ok: true }
  Restrict viewers to an email domain.

  set_agentation→ { ok: true }
  Toggle the on-page feedback toolbar.

  list_sites→ { sites: [...] }
  Everything the key owns.

  delete_site→ { ok: true }
  Hard delete.

Privacy-first MCP defaults

Agents act autonomously without a human reviewing every flag. The MCP layer applies tighter defaults than the raw API:

  - 7-day expiry (raw API: never for authed). Pass expires_in_hours: "never" for permanence.

  - PII scan in block mode (raw API: warn). Pass pii_check: "warn" to publish despite detected sensitive data.

  - Unlisted token URL, strict CSP, X-Robots-Tag: noai — same defaults as the raw API.

WebMCP (in-browser)

The dashboard registers the same verbs on document.modelContext where the browser supports it (Chrome origin trial), so an in-browser agent helping a signed-in human can call them without an API key. How and why.

Want the same pattern in your own app? The palette and WebMCP registration are built on agentk, our open-source cmdk extension: define tools once as JSON Schema, humans get generated forms, agents get the schemas.

Agent payments (x402)

A fully autonomous agent — no human, no account — can buy a persistent identity and unlock paid features over x402, the open HTTP 402 payment standard (v1 and v2 accepted, USDC on Base, gasless for the payer). Machine-readable version: x402.md.

  GET/provision
  Lists the accepted payment rails.

  POST/provision
  402 → pay $1.00 → persistent `stk_live_` key, no account.

  GET/unlock
  The à-la-carte catalog: make-permanent $1, custom domain $5/30d, higher limits $5/30d.

  POST/unlock
  402 → pay → feature entitlement on your key.

  POST/pay/sessions
  No wallet? Returns a pay link + terminal QR; a human pays by card in two taps. Poll `GET /pay/sessions/:code/poll`.

Paying above the price in a pay session (up to $20) leaves a prepaid balance on the key that later paid actions draw from silently. Balances never expire and are refundable on request.

Custom domains

Pro+ feature (or the custom_domain x402 unlock). Bring your own hostname (docs.acme.com et al), point a CNAME at our Cloudflare for SaaS fallback origin, prove ownership via a TXT record, and traffic to that hostname serves your site over HTTPS.

POST/custom-domains

`curl -X POST https://api.stacktr.ee/custom-domains \
     -H "Authorization: Bearer stk_live_..." \
     -H "Content-Type: application/json" \
     -d '{"hostname":"docs.acme.com","site_id":"abc123"}'`

Response includes a verify_token and the two DNS records you need to add:

`{
  "hostname": "docs.acme.com",
  "site_id": "abc123",
  "verified": false,
  "instructions": {
    "cname": { "name": "docs.acme.com", "value": "proxy.stacktr.ee", "type": "CNAME" },
    "txt":   { "name": "_stacktree-verify.docs.acme.com", "value": "verify_", "type": "TXT" }
  }
}`

POST/custom-domains/:hostname/verify After adding the DNS records, call verify. We DNS-lookup the TXT record; on match we register the hostname with CF for SaaS and SSL provisioning begins (~60 s).

`curl -X POST https://api.stacktr.ee/custom-domains/docs.acme.com/verify \
     -H "Authorization: Bearer stk_live_..."`
Gotcha — DNS-only CNAME. If your DNS is on Cloudflare, the CNAME must be set to DNS only (grey cloud), not Proxied (orange). A proxied CNAME makes Cloudflare claim the hostname for your own zone and Stacktree's SaaS routing never sees the SNI.

Re-bind or delete

`PATCH  https://api.stacktr.ee/custom-domains/:hostname    # { "site_id": "..." }  — re-bind
DELETE https://api.stacktr.ee/custom-domains/:hostname    # unregister + drop row`

List your domains with GET https://api.stacktr.ee/custom-domains. Unverified rows are auto-pruned after 7 days.

OAuth (custom connector authors)

For MCP host implementers — if you're using a maintained client (Claude.ai, Cursor, etc.) skip this section.

Discovery

`GET https://api.stacktr.ee/.well-known/oauth-authorization-server
GET https://api.stacktr.ee/.well-known/oauth-protected-resource`

Both return standard RFC 8414 / RFC 9728 metadata documents.

Flow

OAuth 2.1 with PKCE (S256 required) and Dynamic Client Registration (RFC 7591). Endpoints:

  POST/oauth/register
  DCR — rate-limited to 10/IP/hour.

  GET/oauth/authorize
  Bounces to Clerk-gated consent page on app.stacktr.ee.

  POST/oauth/token
  Code → access token (HS256 JWT, 30-day TTL).

  POST/oauth/revoke
  RFC 7009 revocation.

Callback for hosted Claude surfaces: https://claude.ai/api/mcp/auth_callback.

Limits

LimitAnonymousFreeProAgent
Uploads / 24h20 per IP501,000unlimited
Per-site size10 MB25 MB250 MB1 GB
Active sites1 / 24h window5unlimitedunlimited
Files / archive1,0001,0001,0001,000
Default expiry24hnever (API) · 7d (MCP)never (API) · 7d (MCP)never (API) · 7d (MCP)
Custom slug—✓✓✓
Unbranded social previews——✓✓
Custom domains——550

DCR rate limit: 10 client registrations / IP / hour. Sites auto-purge from R2 + D1 within 1 hour of expiry.

No plan? The higher_limits unlock ($5 / 30 days over x402) lifts a free identity to Agent-tier limits — 1 GB per site, unlimited daily publishes, no active-site cap.


Full markdown summary of the Stacktree marketing surface: https://stacktr.ee/llms-full.txt