Skip to content
Shotcup

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:

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):

{
  "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):

{
  "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.

This page as Markdown: /docs/mcp.md