Files
puter/src/mcp-connector

Puter MCP Connector (Cloudflare Workers)

An MCP server that runs on Cloudflare Workers and lets anyone use their own Puter auth token to drive their Puter account's filesystem and static website hosting from an MCP client (Claude, Cursor, etc.).

The Worker stores no credentials of its own. Every request runs as the caller, authenticated either by a Authorization: Bearer <token> header or by an OAuth "Sign in with Puter" flow the Worker hosts itself (see Authentication).

Tools

Account

Tool Description
whoami Get the authenticated user's info (username, uuid, home directory).

Filesystem

Tool Description
fs_read_file Read a file (UTF-8 or base64; optional byte offset/length window).
fs_stat Stat a file or directory (size, type, timestamps, uid).
fs_write_file Create/overwrite a file from inline content (UTF-8 or base64).
fs_start_upload Get a presigned URL to upload a local file out of band.
fs_complete_upload Finalize an upload started with fs_start_upload.
fs_abort_upload Discard an upload without creating a file.
fs_mkdir Create a directory (optionally creating missing parents).
fs_delete Delete a file or directory (recursive by default).
fs_readdir List the entries of a directory.
fs_copy Copy a file or directory to another location.
fs_move Move a file or directory to another location (also renames).
fs_rename Rename a file or directory in place.

Uploading a local file

fs_write_file carries its content inline, which means a file on disk has to be base64-encoded through the agent's context before it reaches the server — slow for anything large, and impossible past the client's message size limit.

fs_start_upload avoids that entirely by handing back a presigned storage URL. The bytes go straight from the machine holding the file to storage; neither this server nor the conversation ever sees them.

fs_start_upload({ path: "~/uploads/build.zip", size: 48210433, local_path: "./build.zip" })
  -> { upload_id, url, upload_command, expires_at, ... }

# run the returned upload_command:
curl -sS --fail-with-body -X PUT -H 'Content-Type: application/zip' \
  --upload-file './build.zip' 'https://...'

fs_complete_upload({ upload_id })   -> the created file entry

The file does not exist in Puter until fs_complete_upload succeeds; use fs_abort_upload to release a session whose upload failed. Two constraints come from the signing itself: size must be the file's exact byte count (wc -c), and the PUT must send the same Content-Type that was signed — storage rejects a mismatch on either. The emitted upload_command already gets both right.

Uploads larger than the server's single-PUT ceiling would need a multipart part dance that these tools don't implement; fs_start_upload detects that case, releases the session, and says so.

Hosting (static websites)

Publishing a website in Puter means creating a hosting subdomain served at https://<subdomain>.puter.site, backed by a directory in your Puter filesystem.

Tool Description
hosting_list List the caller's published websites (hosting subdomains).
hosting_get Get a website by its subdomain.
hosting_create Publish a website on a subdomain (optionally pointing at a root_dir).
hosting_update Re-point a website at a different root_dir.
hosting_delete Unpublish a website (deletes the subdomain, not the files).

Workers (serverless functions)

Puter Workers are serverless JavaScript functions deployed from a file in your Puter filesystem. The worker file defines handlers on the global router object and has the full puter.js SDK available as puter — they are designed to be used with puter.js and Puter authentication. Update a worker by writing new code to its associated file (there is no separate update call).

Tool Description
workers_create Deploy a worker from a JS file; returns its public URL.
workers_list List the caller's deployed workers.
workers_get Get a worker (name, URL, source file) by name.
workers_exec Call a worker over HTTP as the authenticated user.
workers_delete Undeploy a worker (leaves its source file in place).

Key-value store

Puter's KV store is namespaced per app inside each user's account. This connector authenticates with a user token, so these tools read and write the user's own namespace by default; every one takes an optional app_uuid to target a single app's store instead (e.g. the sandbox-<worker> app a deployed worker runs as). Values are stored as JSON. Tools that take a "dot path" (profile.bio) address into a stored object; the empty path is the value itself.

Tool Description
kv_get Read a key (missing keys read as null).
kv_set Create/overwrite a key, optionally with an expiry timestamp.
kv_del Delete a key.
kv_list List keys (or key/value pairs) by pattern, with pagination.
kv_incr / kv_decr Change a number, or numbers at given dot paths.
kv_add Add to the stored value (sums numbers, appends to arrays).
kv_update Set specific dot paths inside a stored object.
kv_remove Remove dot paths from a stored object.
kv_expire / kv_expire_at Expire a key after N seconds / at a timestamp.

flush is deliberately not exposed — wiping a whole store is not something an agent should be able to do in one call.

Apps

A Puter app is a registered application in your account: it shows up in your Puter app list, can be launched in the Puter desktop UI, and (once approved) be listed in the marketplace. The core of an app is its index_url — the URL Puter loads when the app runs, usually a site published with hosting_create or a worker URL. Typical flow: fs_write_file the files → hosting_create to publish and get a URL → apps_create pointing index_url at that URL.

Tool Description
apps_list List the apps the caller owns / can edit (name, URL, icon, aggregate usage stats).
apps_get Get an app by name; pass stats_period for detailed open/user counts over a window.
apps_create Register a new app (requires name and index_url).
apps_update Update an app by name (only the fields you pass change; new_name renames).
apps_delete Delete an app (leaves the site/worker its index_url points at in place).
apps_check_name Check whether an app name is available before creating it.

Documentation

Tool Description
puter_docs_index Load the puter.js docs index (every topic + path) from docs.puter.com/llms.txt.
puter_docs_get Fetch a specific docs page as Markdown by topic path (e.g. Workers/router).

Paths

Every path lives under your home directory (/<username>). Tools pass paths straight to puter.js, so the usual conventions apply: absolute (/your-username/Desktop/file.txt), home-relative (~/Desktop/file.txt), or relative (Desktop/file.txt, resolved against your home directory). Bare root paths like /portfolio/index.html are not valid — call whoami to get your username, then use ~/... or /<username>/....

How it works

This is a fork of src/worker — Puter's port of puter.js to a Cloudflare Worker runtime — with two changes:

  1. No me.puter. The original worker creates a worker-owned puter instance from globalThis.puter_auth. That's removed: this connector holds no credentials of its own.
  2. user.puter from the Authorization header. The per-request puter instance is built from Authorization: Bearer <token> instead of the original puter-auth header.

Because it's the real port, the tools call genuine puter.fs.* and puter.hosting.* methods on a real puter.js instance (created via init_puter_portable(token, origin, 'userPuter'), which runs puter.js inside an isolated with context so concurrent requests don't share auth/cache state).

The MCP transport is Streamable HTTP: a single endpoint accepting JSON-RPC 2.0 over POST (single message or batch array). The server is stateless. The MCP layer (src/mcp.js) is a small hand-rolled JSON-RPC dispatcher registered onto the forked router as routes.

Build pipeline (same as src/worker)

npm run build does two steps:

  1. webpack bundles src/index.js (the router + src/mcp.js + src/tools.js) into dist/webpackPreamplePart.js.
  2. scripts/buildPreamble.mjs inlines #includes in template/puter-portable.template — pulling in ../puter-js/dist/puter.js and the webpack bundle — to produce dist/workerPreamble.js, the deployable service-worker-format script.

Requires src/puter-js/dist/puter.js to exist. Build puter.js first if needed (cd ../puter-js && npm run build).

Files

Path Role
src/s2w-router.js Forked router: builds event.user.puter from the bearer token (no me.puter).
src/index.js Entry: initS2w() + registers MCP and OAuth routes.
src/mcp.js MCP JSON-RPC dispatch; 401 + WWW-Authenticate when unauthenticated.
src/oauth.js OAuth bridge: discovery, /register, /authorize→authme, /oauth/callback, /token.
src/tools.js The tool definitions, calling real puter.fs.* / puter.hosting.* / puter.workers.* / puter.apps.*.
template/puter-portable.template Preamble template (defines init_puter_portable, inlines puter.js).

Running locally

cd src/mcp-connector
npm install
npm run dev        # builds, then wrangler dev — serves on http://localhost:8787

Deploying

npm run deploy     # builds, then wrangler deploy

To target a self-hosted Puter instance, set puter_endpoint / puter_gui_origin (uncomment the [vars] block in wrangler.toml). They default to https://api.puter.com and https://puter.com.

If you use the OAuth flow (below), also set the sealing secret in production:

wrangler secret put OAUTH_SECRET

Authentication

Two ways, both running as the caller — the Worker holds no credentials of its own:

  1. OAuth "Sign in with Puter" (no token to copy). For clients that support OAuth over HTTP (e.g. Claude Code), the Worker is the authorization server: on first use the client opens a browser, you sign into Puter and approve, and the Worker hands the client your Puter token. See src/oauth.js. Under the hood it redirects to Puter's ?action=authme page and catches the returned token on its /oauth/callback; the short-lived flow/code blobs are AES-GCM sealed with OAUTH_SECRET, so the Worker stays stateless.
  2. Bearer token (copy/paste). Get it from a logged-in Puter browser tab's devtools console: puter.authToken. Treat it like a password. Pass it as an Authorization: Bearer <token> header (or the .mcpb token field).

Connecting a client

Option A — Claude Code (OAuth, no token)

claude mcp add --transport http puter https://puter-mcp.<your-subdomain>.workers.dev/

On first use Claude Code opens a browser to sign in with Puter; approve, and it's connected. (If you'd rather skip OAuth, add -H "Authorization: Bearer YOUR_PUTER_TOKEN".)

Option B — one-click .mcpb bundle (Claude Desktop etc.)

puter-mcp-connector.mcpb is a prebuilt MCP Bundle. Import it into a host that supports MCPB (e.g. Claude Desktop: Settings → Extensions → install from file), then fill in the two config fields it prompts for:

  • Server URL — your deployed Worker, e.g. https://puter-mcp.<your-subdomain>.workers.dev/ (or http://127.0.0.1:8799/ for local wrangler dev).
  • Puter Auth Token — your personal token (stored as a secret).

Because the connector is a remote HTTP Worker but MCPB extensions run a local process, the bundle ships a tiny zero-dependency Node stdio↔HTTP proxy (mcpb/server/index.cjs) that forwards JSON-RPC to your Worker with the token attached as Authorization: Bearer.

Rebuild the bundle after changing the proxy or manifest:

npm run pack:mcpb     # -> puter-mcp-connector.mcpb

The .mcpb is unsigned; hosts may warn it's from an unknown developer. To self-sign: npx @anthropic-ai/mcpb sign --self-signed puter-mcp-connector.mcpb.

Option C — direct HTTP

Point any MCP client that supports HTTP transport at the Worker URL and add your token as a bearer header. Example (mcp.json-style):

{
  "mcpServers": {
    "puter": {
      "url": "https://puter-mcp.<your-subdomain>.workers.dev/",
      "headers": { "Authorization": "Bearer YOUR_PUTER_TOKEN" }
    }
  }
}

Quick smoke test with curl

URL=http://localhost:8787
TOKEN=your_puter_token

# initialize
curl -s $URL -H 'content-type: application/json' -d '{
  "jsonrpc":"2.0","id":1,"method":"initialize",
  "params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}
}'

# list tools
curl -s $URL -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# stat your home directory
curl -s $URL -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{
  "jsonrpc":"2.0","id":3,"method":"tools/call",
  "params":{"name":"fs_stat","arguments":{"path":"~"}}
}'

# write then read a file
curl -s $URL -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{
  "jsonrpc":"2.0","id":4,"method":"tools/call",
  "params":{"name":"fs_write_file","arguments":{"path":"~/Desktop/hello.txt","content":"hi from MCP"}}
}'

# list hosted websites
curl -s $URL -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"hosting_list","arguments":{}}}'