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: aGETanswers 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(thegoccia:canvasreference with a worked example) andshotcup://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 withloadImageand 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; nofunctionkeyword,var,whileor C-stylefor, and no semicolon insertion at line breaks (end declarations with;). There is no npm. A run that fails on one of these carries ahint(first in the MCP summary line) saying what to write instead. - Files get in three ways, on
run_disposable,run_code,create_sandboxandwrite_filesalike:{ 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 answers413 FUNCTION_PAYLOAD_TOO_LARGEin plain text before Shotcup sees the call, so bring large or many binaries in byurl(REST clients can also usePOST /api/v1/uploads). Load a photo withfiles: [{ path: "/in/photo.jpg", url }]andengine: { canvas: true }, thenloadImage("/in/photo.jpg"). Never transcribe an image as text chunks. Results never echo input bytes back;read_fileon 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: falseis passed:Date.now()andperformance.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 passartifacts: "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_disposableandrun_codetakefonts: Google Fonts families forgoccia: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_accountshows what this month used and what is left (usage.month). Past the allowance a run is refused with the quota kindallowance-runsorallowance-computeuntil the month ends, unless the account turned overage on with a spend cap (PATCH /api/v1/me; not on Free), and then withspend-caponce 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