# Connecting an MCP client to Shotcup

Shotcup's MCP server runs JavaScript in sandboxes for a model: one-off runs
(`run_disposable`), saved sandboxes (`create_sandbox`, `run_code`,
`write_files`, `read_file`, `list_files`, `list_runs`, `destroy_sandbox`) and
the account (`get_account`). Images the code writes under `/out` come back as
image content and as links.

- **URL:** `https://shotcup.dev/api/mcp/mcp` (Streamable HTTP, stateless: a
  `GET` answers 405, which clients handle).
- **Header:** `Authorization: Bearer sk_…` with an API key from the
  dashboard. The key is the only credential: Shotcup has no OAuth yet
  (decision 41), so a client that only offers OAuth sign-in, or that sends no
  header, gets 401 and stops. Its OAuth discovery requests
  (`/.well-known/oauth-*`, `/register`) answer 404 by design.
- **Resources:** `shotcup://docs/canvas` (the `goccia:canvas` reference with a
  worked example) and `shotcup://docs/mcp` (this guide).

## Grok

Add a remote MCP server (connector) with the URL above, transport
"Streamable HTTP" (or "HTTP"), and a custom header named `Authorization`
whose value is `Bearer sk_…`. Do not choose an OAuth sign-in: there is none
to complete. Grok connects, lists the tools and calls `run_disposable`.

## Claude

Claude Code:

```sh
claude mcp add --transport http shotcup https://shotcup.dev/api/mcp/mcp \
  --header "Authorization: Bearer sk_…"
```

Claude Desktop and claude.ai custom connectors sign in with OAuth and do not
send custom headers, so they cannot connect until Shotcup offers OAuth. In
Claude Desktop the `mcp-remote` bridge adds the header
(`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "shotcup": {
      "command": "npx",
      "args": ["mcp-remote", "https://shotcup.dev/api/mcp/mcp", "--header", "Authorization: Bearer ${SHOTCUP_API_KEY}"],
      "env": { "SHOTCUP_API_KEY": "sk_…" }
    }
  }
}
```

## Cursor

`~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project):

```json
{
  "mcpServers": {
    "shotcup": {
      "url": "https://shotcup.dev/api/mcp/mcp",
      "headers": { "Authorization": "Bearer sk_…" }
    }
  }
}
```

Without `headers` Cursor connects anonymously, receives 401, tries OAuth
discovery and gives up.

## Example prompts

- "Use Shotcup to compute the first 30 Fibonacci numbers and show me the
  output."
- "Read shotcup://docs/canvas, then draw a bar chart of these monthly
  numbers with goccia:canvas and save it to /out/chart.png: Jan 12, Feb 18,
  Mar 9, Apr 22."
- "Create a saved Shotcup sandbox called notes with a /main.js that prints
  today's date (run it with deterministic: false), run it, and list its runs."
- "Render this todo list as a kanban board image (1600x900, columns To do,
  Doing, Done) and keep every label inside its card."
- "Load the photo at https://example.com/team.jpg into a Shotcup run
  (`files: [{ path: "/in/photo.jpg", url }]`, engine.canvas), put a caption
  on it with `loadImage` and save it as a JPEG under /out."

## What a model should know

- The language is modern JavaScript as ES modules in GocciaScript's default
  profile: arrow functions, `const`/`let`, `for...of`; no `function`
  keyword, `var`, `while` or C-style `for`, and no semicolon insertion at
  line breaks (end declarations with `;`). There is no npm. A run that fails
  on one of these carries a `hint` (first in the MCP summary line) saying
  what to write instead.
- Files get in three ways, on `run_disposable`, `run_code`, `create_sandbox`
  and `write_files` alike: `{ path, content }` for text (at most 1,048,576
  characters per file); `{ path, content, encoding: "base64" }` for small
  binary such as icons (at most 1 MiB decoded per file); `{ path, url }` for
  a file the service downloads before the run (https on port 443 only; per
  file Free 2 MiB, Pro 10 MiB, Scale 12 MiB). A run's files together stay
  within the plan's filesystem quota (Free 4 MiB, Pro 16 MiB, Scale 64 MiB).
  A whole tool call must stay under 4.5 MB of
  JSON, and base64 is a third larger than the bytes: above that the hosting
  platform answers `413 FUNCTION_PAYLOAD_TOO_LARGE` in plain text before
  Shotcup sees the call, so bring large or many binaries in by `url`
  (REST clients can also use `POST /api/v1/uploads`). Load a photo with
  `files: [{ path: "/in/photo.jpg", url }]` and `engine: { canvas: true }`,
  then `loadImage("/in/photo.jpg")`. Never transcribe an image as text
  chunks. Results never echo input bytes back; `read_file` on a binary file
  returns its size and encoding only.
- `import fs from "fs"` reads inputs and writes artifacts
  (`fs.writeFileSync("/out/report.json", …)`); `canvas.save("/out/x.png")`
  writes images.
- Runs are deterministic unless `deterministic: false` is passed:
  `Date.now()` and `performance.now()` return 0.
- Artifact links are capability URLs: anyone holding one can fetch the file
  without the API key until the plan's retention ends (Free 1 day, Pro 7
  days, Scale 30 days, Platform 90 days), so treat them as temporary and private. A link ends in the file's extension
  (`https://shotcup.page/a/<id>.png`) and is served from a domain that
  hosts user content only; the type served comes from the file, not the
  link. For personal output pass
  `artifacts: "private"`: the links then answer only with the API key
  (`Authorization: Bearer sk_…`) and everyone else gets 404; images still
  come back inline in the tool result (PNG, JPEG, GIF, WebP and APNG up to
  1 MiB each, at most four).
- Tool calls a run makes are not recorded unless the API key or the run sets
  `recordToolCalls: true`; the run's source and file manifest are kept for
  the plan's retention.
- `run_disposable` and `run_code` take `fonts`: Google Fonts families for
  `goccia:canvas`, loaded before the run (`"Playfair Display:400,700"`,
  `"Lora:400italic"`); the faces and bytes count against the plan.
- Every plan includes a monthly allowance of runs and compute-hours (Free
  30,000 runs and 3 compute-hours, Pro 100,000 and 10, Scale 300,000 and 30,
  Platform 5,000,000 and 300). A compute-hour is an hour a run holds a
  worker; a run with `memory: "large"` (Scale and Platform: a 1 GiB heap,
  at most 4 at once) counts four. `get_account` shows what this month used
  and what is left (`usage.month`). Past the allowance a run is refused with
  the quota kind `allowance-runs` or `allowance-compute` until the month
  ends, unless the account turned overage on with a spend cap
  (`PATCH /api/v1/me`; not on Free), and then with `spend-cap` once the cap
  is reached. Overage costs $0.05 per 1,000 runs and $0.08 per compute-hour
  on every paid plan; a spend cap is at most $10,000 a month. Do not retry
  these refusals in a loop; tell the user.
