spacesheep

Documentation

Deploy and share sites instantly from Claude Code.

Setup

Connect spacesheep to any MCP-compatible client. The server URL is always https://mcp.spacesheep.dev/mcp.

To skip the hand-editing, paste Set up spacesheep for me: https://spacesheep.dev/setup into your agent — it reads the setup guide and writes the config for whichever client it's running in.

Claude Code (plugin — one line)

The plugin bundles the /sheep skill and the MCP connection, and updates itself from here — you never reinstall it to get new guidance:

claude plugin marketplace add https://spacesheep.dev/plugin/marketplace.json
claude plugin install spacesheep@spacesheep

Sign in the first time /sheep runs: Claude Code walks the OAuth flow itself, so there's no key to paste. Later, claude plugin update spacesheep pulls the current skill. Grok Build reads the same .mcp.json, so an install here works there too.

Claude Code / Cursor / Windsurf (API key)

Claude Code writes the correct config for you — and via OAuth, so there's no key to manage:

claude mcp add --transport http --scope user spacesheep https://mcp.spacesheep.dev/mcp

By hand, the file is ~/.claude.json (user scope) or .mcp.json (project); Cursor uses ~/.cursor/mcp.json, and Windsurf uses ~/.codeium/windsurf/mcp_config.json with serverUrl instead of url:

{
  "mcpServers": {
    "spacesheep": {
      "type": "http",
      "url": "https://mcp.spacesheep.dev/mcp",
      "headers": {
        "Authorization": "Bearer ${SPACESHEEP_API_KEY}"
      }
    }
  }
}

"type": "http" is required for a remote server — "type": "url" is not a valid transport, and a url with no type errors. Drop the headers block entirely if you're using OAuth. Get an API key from settings (claim a username first), and keep it in an environment variable rather than inline, since .mcp.json is usually committed.

Claude.ai, ChatGPT, Grok — connectors

Anywhere that takes a remote MCP server, paste the URL and nothing else:

https://mcp.spacesheep.dev/mcp

Each one discovers the OAuth endpoints from the server, sends you here to sign in, and shows you what it's about to be allowed to do. No API key: the connection is its own credential, and revoking it in settings disconnects that client.

Authorization Code flow with PKCE (S256), Dynamic Client Registration and Client ID Metadata Documents, rotating refresh tokens. Metadata: /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server on mcp.spacesheep.dev.

Gemini CLI

Two files into an extension directory — the second one is the live skill, so it's the same guidance everything else runs:

mkdir -p ~/.gemini/extensions/spacesheep && cd ~/.gemini/extensions/spacesheep
curl -fsSLO https://spacesheep.dev/plugin/gemini-extension.json
curl -fsSL https://spacesheep.dev/skill/sheep.md -o SHEEP.md

Grok API, and other agents that pass a static key

xAI's API takes the server as a tool on the request. It sends a bearer header rather than running OAuth, so give it a key from settings:

{
  "type": "mcp",
  "server_url": "https://mcp.spacesheep.dev/mcp",
  "server_label": "spacesheep",
  "authorization": "ss_your_key_here"
}

Terminal and CI (no agent)

The open-source spacesheep CLI (github.com/micmmakarov/spacesheep-cli) publishes a folder or one HTML file without any assistant in the loop:

npx spacesheep login          # browser sign-in, key stored locally
npx spacesheep deploy ./dist  # prints the URL; the folder remembers its space

Deploy on every push with the GitHub Action — put an API key from settings in the SPACESHEEP_KEY secret:

- uses: micmmakarov/spacesheep-cli@v1
  with: { dir: dist, key: ${{ secrets.SPACESHEEP_KEY }} }

npx spacesheep-skill (the skill installer) uses the same browser login to mint a key for your MCP config.

MCP Tools

ToolDescription
deployDeploy files as a static site or Worker app. Pass files[], optional title, uuid (to update), and access: { visibility, emails[] } (visibility: public / members / private). Include worker.js for dynamic mode.
list_spacesList all spaces you own or have access to.
get_spaceGet metadata and access list. Accepts UUID or URL.
read_spaceRead deployed file contents. Accepts UUID or URL. Optional path for a specific file.
share_spaceGrant access by email. Owner only.
delete_spaceDelete a space and all files. Owner only.
list_commentsRead all comments on a space — threaded, with anchors, reactions, and resolve state.
add_commentComment or reply. Posts under the Claude bot identity. Optional anchor_id pins it to an element.
react_to_commentToggle an emoji reaction on a comment.
resolve_commentResolve or reopen a comment thread.
create_orgCreate a new organization (invite-only for now). Caller becomes owner. Org dashboard at <slug>.spacesheep.dev.
set_secretSet a personal or org secret. Optionally restrict allowed URLs. Available in workers via env.secrets.get(name).
list_secretsList secret names (never values). Personal or org scope.
delete_secretDelete a personal or org secret.
download_pdfGet a downloadable PDF of a space — the document as it prints, no chrome. Returns a link valid 2 hours; optional format (Letter / A4) and landscape.

Deploy a site

In Claude Code, just ask to deploy:

"Deploy this as a spacesheep site"
"Upload index.html and style.css to spacesheep"

Or use the tool directly:

deploy({
  files: [
    { path: "index.html", content: "<h1>Hello</h1>" },
    { path: "style.css", content: "body { color: red; }" }
  ],
  title: "My Site",
  access: { visibility: "members", emails: ["friend@example.com"] }
})

Access control

Every space has one of three visibility tiers. By default a space is private. Set access.visibility on deploy:

Use the share_space tool to add emails or change the tier after deploy. Visibility can be set on create or update (pass uuid to update). A worker space can't be open — it needs a session.

Worker mode (dynamic apps)

To deploy a dynamic app with server-side logic and database access, include a worker.js file. Spacesheep detects it automatically and deploys a live Cloudflare Worker.

// worker.js — ES module format
export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    if (url.pathname === "api/items") {
      await env.D1.exec("CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT)");
      if (request.method === "POST") {
        const { name } = await request.json();
        await env.D1.prepare("INSERT INTO items (name) VALUES (?)").bind(name).run();
        return Response.json({ ok: true });
      }
      const rows = await env.D1.prepare("SELECT * FROM items").all();
      return Response.json(rows.results);
    }

    return new Response("<h1>My App</h1>", {
      headers: { "Content-Type": "text/html" }
    });
  }
};

Database migrations: Include a schema.sql file — it runs against D1 on every deploy. Write idempotent SQL:

-- schema.sql
CREATE TABLE IF NOT EXISTS items (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

What your Worker gets:

Important: Use relative URLs in frontend code (fetch("api/data") not fetch("/api/data")). Your Worker's paths are relative to the space URL.

Secrets

Workers can use API keys and credentials without exposing them to the frontend. Secrets are set via MCP tools and accessed in worker code through a safe API.

Setting secrets

// Personal secret — available to all your worker spaces
set_secret({ name: "OPENAI_KEY", value: "sk-..." })

// Org secret — shared across all spaces in the org
set_secret({ name: "OPENAI_KEY", value: "sk-...", scope: "org", org: "my-team" })

// Restrict where the secret can be sent
set_secret({
  name: "OPENAI_KEY",
  value: "sk-...",
  allowed_urls: ["https://api.openai.com/"]
})

Using secrets in worker code

export default {
  async fetch(request, env) {
    const key = await env.secrets.get("OPENAI_KEY");
    const allNames = await env.secrets.list();

    // Use in API calls — safe, stays server-side
    const resp = await fetch("https://api.openai.com/v1/chat/completions", {
      headers: { "Authorization": "Bearer " + key },
      // ...
    });
  }
};

Safety layers

Sharing

Spaces are private by default. Share with specific people:

share_space({
  uuid: "abc-123...",
  emails: ["friend@example.com"]
})

Viewers sign in with Google or with email + password.

Reading content

Paste a spacesheep URL and ask Claude to read it:

read_space({ uuid: "https://spacesheep.dev/@misha/quarterly-report" })

URLs

Spaces live under their owner's username: spacesheep.dev/@username/<title-slug>. The slug is generated from the space title; renaming a space updates the slug and old links redirect permanently. Claim your username from the dashboard.

Legacy UUID links (/space/<uuid>) keep working — they redirect to the pretty URL once the owner has a username. All MCP tools accept either form.

Printing & PDF

Hit Ctrl/+P in a space and you print the document, not the page around it: no top bar, no comment markers, no sign-in pill — white paper, the author’s colors intact, and a small sheep in the corner of every page. Nothing to configure, and authors don’t write print CSS: it’s injected into every space that’s served. (One rule of thumb when designing: use position: sticky, never position: fixed — fixed elements repeat on every printed page.)

To get the file itself — to attach or send — ask Claude for a PDF:

download_pdf({ uuid, format?: "Letter" | "A4", landscape?: boolean })

It returns a link, valid two hours, that renders the current deployed version in a real browser on request. Redeploying never stales it, and private spaces work — the link carries a capability scoped to that one space and nothing else.

Comments & feedback

Every space has a real-time comment sidebar. Viewers can:

Commented elements show a 💬 count badge in the document and quoted passages get a subtle highlight, so open feedback is visible in place. Clicking a badge jumps to the thread. Resolving a thread clears its markers. Everything syncs live across viewers over WebSockets.

Close the loop with Claude: the MCP tools read and write the same comments. Claude replies post under a distinct bot identity — shown as Claude with an AI chip, attributed to the API key owner.

list_comments({ uuid })          // read feedback
add_comment({ uuid, body, parent_id?, anchor_id? })
react_to_comment({ uuid, comment_id, emoji })
resolve_comment({ uuid, comment_id })

Documents generated with the /sheep skill include data-ss-id anchors on every content block, so comments survive redeploys as long as those IDs stay stable.

The /sheep skill

Spacesheep includes a Claude Code skill called /sheep that generates publication-quality documents following perception science best practices — optimized typography, information hierarchy, data visualization, and perception-optimized light theme.

Usage in Claude Code:

/sheep quarterly metrics report from this data...
/sheep architecture overview of the payment system
/sheep comparison of React vs Svelte for our use case

The skill builds a self-contained HTML document and deploys it to spacesheep automatically. Documents follow principles from Tufte, perceptual psychology, and modern web typography:

Installing the skill

One-line install:

curl -fsSL https://spacesheep.app/install.sh | bash

Or via npm:

npx spacesheep-skill

Install globally (available in all projects):

curl -fsSL https://spacesheep.app/install.sh | bash -s -- -g
npx spacesheep-skill -g

The installer downloads the skill and optionally configures the MCP server.

Telegram bot

Spacesheep has one Telegram bot — @TheSpaceSheepBot — that acts as an agent across your spaces and orgs. DM it questions or actions (“list my spaces”, “deploy this”, “share that with…”, “remind me to…”); it runs the same MCP tools and uses your account’s LLM key. There’s nothing to install and no per-org bot to create — the same bot switches accounts depending on where you use it.

1. Link your account

Open a direct message with @TheSpaceSheepBot and send:

/start

Tap the link it replies with to connect your Spacesheep account — one tap, done. (Alternatively, grab a one-time code from Settings → Telegram and send /start <code>.) Once linked, anything you DM the bot acts on your personal account.

2. Use it in a group / for an org

Add @TheSpaceSheepBot to a Telegram group, then bind that group to whichever account it should act on:

After you bind to an org, everything the bot does in that group runs on the org — its spaces, its secrets and LLM key, its permissions — not your personal account. Mention @TheSpaceSheepBot or reply to one of its messages to ask it something.

Heads up: the <org-slug> is the org’s slug (the <slug> in its <slug>.spacesheep.dev URL), not its display name. You must be a member of the org to bind to it. For /summary and group awareness the bot needs to read every message — a group admin disables privacy mode once in @BotFather (/setprivacy → Disable).

Commands

CommandWhereWhat it does
/startDMLink your account (or /start <code> with a code from Settings)
/bind <org-slug>GroupBind the group to an org (omit the slug to bind to your personal account)
/unbindGroupClear the group’s binding
/summaryDM & groupRecap the recent conversation
/unlinkDMDisconnect and revoke the bot’s API key
/helpDM & groupShow the command list

Architecture

User content is served on a separate origin (spacesheep.app) so it cannot access spacesheep.dev cookies, storage, or DOM. The iframe sandbox attribute provides defense in depth.

Security