Ephemeral Pages lets users publish short-lived HTML pages that expire automatically.
The server is a public Streamable HTTP endpoint. There is no API key, bearer token, or OAuth flow to set up, and nothing to install or run locally. Point a client at it:
https://ephemeral.schalkneethling.com/mcp
This deployment uses the transport's buffered JSON response mode rather than SSE for normal requests.
With Codex, that is one command:
codex mcp add ephemeral-pages --url https://ephemeral.schalkneethling.com/mcpClaude Code has an equivalent. Using its CLI is the simplest path because it writes the configuration for you:
claude mcp add --transport http ephemeral-pages https://ephemeral.schalkneethling.com/mcpWithout --scope, that command creates a local-scoped entry for the current project inside
~/.claude.json. Add --scope project to write a shareable .mcp.json at the project root, or
--scope user to make the server available in every project.
If you prefer to write the configuration by hand, use .mcp.json at the project root or
~/.claude.json for a user-scoped server. Neither location is settings.json, and
claude_desktop_config.json belongs to the desktop application rather than Claude Code.
{
"mcpServers": {
"ephemeral-pages": {
"type": "http",
"url": "https://ephemeral.schalkneethling.com/mcp"
}
}
}The type field is the part to get right. Claude Code requires it for a remote HTTP entry; without
it, the configuration will not connect as an HTTP server. It also accepts streamable-http as an
alias for http if you prefer to match the specification's transport name.
Cursor reads a similar shape from .cursor/mcp.json in the project or ~/.cursor/mcp.json
globally, and it infers the transport from the presence of a url:
{
"mcpServers": {
"ephemeral-pages": {
"url": "https://ephemeral.schalkneethling.com/mcp"
}
}
}That difference matters when copying configuration between clients: the Cursor entry above needs
"type": "http" before it can be used by Claude Code.
The server intentionally sets legacy: "reject": it speaks MCP 2026-07-28 and nothing else. A
client pinned to a 2025-era protocol version will not connect rather than degrading to a partial
experience.
Once connected, an agent discovers the create_page and get_page tools and the
publish-html-page prompt. In practice, ask it to publish a complete HTML document, choose one of
the supported expiration periods if 12 hours is not what you want, and share the returned URL. That
URL is public for as long as the page lives, so nothing sensitive should ever go into a page: no
secrets, credentials, private source, or real customer data.
Configuration examples for OpenCode, Pi, VS Code, and MCP Inspector, along with the full tool arguments, limits, and security model, are in the MCP guide.
The documented POST /api/pages API supports plain JSON uploads, Brotli/Base64 CI
reports, optional GitHub Actions OIDC identity, actor-scoped idempotency, and stable error/rate-limit
responses. The MCP server uses the same create/get behavior through an agent-oriented interface.
Uploaded pages are intentionally constrained. The content endpoint returns uploaded HTML with a sandboxed Content Security Policy, and the normal viewer renders the content endpoint in a sandboxed same-origin iframe.
Allowed external loading is limited to declarative script, stylesheet, and font use:
<script src="...">may load JavaScript from the approved script CDNs.<link rel="stylesheet" href="...">and CSS@importmay load stylesheets from approved style origins, including Google Fonts CSS.- Font files may load from the approved font origin.
Programmatic network access is blocked. Uploaded pages do not define connect-src, so fetch,
XHR, WebSocket, and similar requests fall back to default-src 'none', even when the target
origin is allowed for script-src or style-src.
That means this is allowed when the origin is on the approved script list:
<script src="https://cdn.jsdelivr.net/npm/lodash/lodash.min.js"></script>But this is blocked:
fetch("https://cdn.jsdelivr.net/npm/lodash/lodash.min.js");Rate limits use short-lived, pseudonymous JSON records in Netlify Blobs. The record key is derived from an HMAC of the request actor signal and rate-limit subject, so raw IP addresses and verified GitHub repository identities are not stored in Blob keys.
Expired rate-limit records are hard-deleted by the scheduled cleanup function once their resetAt
window has passed. Malformed rate-limit records are also deleted during cleanup because they cannot
be used for enforcement.