@platinum-dev/mcp
v0.3.0
Published
MCP server exposing Platinum sandbox microVMs as task-shaped tools (stdio transport).
Readme
@platinum-dev/mcp
The Platinum MCP server — 22 task-shaped tools that let any MCP-capable agent
(Claude Code, Cursor, Windsurf, Claude Desktop) drive Platinum sandbox microVMs.
Built on the official @modelcontextprotocol/sdk (pinned) over
@platinum-dev/sdk — every call goes through the typed SDK client.
Quick start (the first-party path: the pt CLI)
pt login # once — key goes into the OS keychain
pt mcp init claude # or: cursor | windsurf
# restart the agent, then ask it to list Platinum toolspt mcp init <agent>merges{"command":"pt","args":["mcp","start","--permissions","read,exec,write"]}into the agent's MCP config — no token is ever written to the file;pt mcp startreads the keychain at launch.pt mcp config [agent]prints the same snippet for manual setup.pt mcp startruns this server on stdio.
Scoping flags on pt mcp start
| Flag | Effect |
|---|---|
| --permissions read,exec,write,manage | Register selected groups (read, exec, write, manage). Default is read,exec,write; manage is opt-in (env: PT_MCP_PERMISSIONS) |
| --readonly | Register only read tools (list/read/search) — the model never sees exec/write/manage tools |
| --allow-public-expose | Permit sandbox_expose with public:true (default: private tokenized URLs only; env: PT_MCP_ALLOW_PUBLIC_EXPOSE) |
| --confine-sandbox <id> | HARD-lock every tool to one box: a foreign sandbox_id is rejected, sandbox_create/sandbox_delete are not registered (env: PT_MCP_CONFINE_SANDBOX) |
| --sandbox <id> | Pin all default-sandbox tools to one box; never auto-creates (env: PT_MCP_SANDBOX) |
Tools (23)
Grouped by permission; a fresh pt mcp init grants read,exec,write (14 tools) and manage is opt-in.
read — sandbox_list, template_list, fs_read (offset/limit paging, utf8/base64),
fs_list, fs_search (mode: name glob / mode: content regex), fs_stat,
secret_list, secret_get, sandbox_secrets_list (all three METADATA ONLY)
exec — run_command, run_code (python | node | bash | sh)
write — fs_write (5 MB cap), fs_mkdir (mkdir -p), fs_delete (recurse for non-empty)
manage (opt-in) — sandbox_create, sandbox_delete (batch, 404 = already gone),
sandbox_expose (returns the full preview URL), sandbox_unexpose, sandbox_revoke_exposure,
sandbox_set_egress (block_all/allow_only/block/allow_all),
secret_bind, secret_unbind, secret_delete
Org secrets: what is exposed, and what is not
A secret is an org credential the sandbox never holds — the host's egress proxy writes it
into the headers you named, on HTTPS only, for the exact hostnames on its allowlist. The
value is not returned by any endpoint, and these tools go through @platinum-dev/sdk, so
the SDK's "a raw response body is never handed back" projection is inherited here.
sandbox_create takes secrets: ['stripe-live'] (id or name, or {secret, alias?, header?},
max 16) — the same authority secret_bind needs, in the same manage group, but applied
before the box boots, so the sandbox is never up with the destination reachable and the
credential absent. sandbox_secrets_list answers the question an agent inside the loop
actually has: is this credential armed (spend it), arming (wait and retry) or
unavailable (something has to change first)?
These are deliberately absent, and should stay absent:
| Not a tool | Why |
|---|---|
| secret_create, secret_rotate | Both take the PLAINTEXT VALUE as an argument. A tool argument lands in the model's context, the transcript, and every MCP client log — a strictly worse channel than the shell history the CLI's stdin-only rule already refuses. Store a value as a human: printf %s "$KEY" \| pt secret create … |
| secret_update | Editing allow/headers IS disclosure: add a host you control, spend the credential against it once, and you have read it out. The control plane says so by requiring secrets:use on top of secrets:write for exactly those fields. |
| the declarative set-replace (PUT /:id/secrets) and detach-by-alias | Whole-set operations. A replace sent with a partial list silently revokes every credential the caller forgot, and an agent rebuilding a set from context is the caller most likely to forget one. secret_bind / secret_unbind express the same power one binding at a time. |
secret_unbind can return a warning: the revoke always lands, but if the host rejects the
updated egress policy the sandbox's requests to that hostname fail closed until it is
re-applied. The tool leads with that warning rather than reporting a clean success.
Under --confine-sandbox, secret_delete is not registered (it revokes bindings org-wide)
and secret_get shows only the confined sandbox's bindings.
Behavior agents get for free:
sandbox_idis optional on exec/fs tools. Exec tools reuse-or-create a small ephemeral default sandbox (taggedmetadata.mcp_default, rediscovered via the API on every call — survives server restarts and agent context compaction; fs tools reuse it but never create it). Every result echoes the sandbox it ran on.- Stopped default box → auto-resume + one retry; resume failure and 404s come back as prescriptive errors, never retry loops.
- Timeouts return partial output, clearly marked. All output is capped by one shared
~50 KB budget across stdout+stderr (agent-lowerable via
max_bytes) with signposted truncation — never a silent cut. - 429/402 errors explicitly say do not retry and point at
sandbox_list+sandbox_deleteto free capacity. - Background processes are reaped when a command returns — daemonize with
setsid. - The default
pt-basetemplate is busybox-based; pick a python/node template viatemplate_listforrun_code.
Env-configured use (npx / raw bun)
PT_API_URL=https://api.platinum.dev PT_TOKEN=pt_live_… bun packages/mcp/index.tsThe stdio entry (index.ts) fails loudly without PT_TOKEN. Prefer pt mcp start.
Development
cd packages/mcp && bun install
bun test test/output.test.ts # run per-file (repo CI convention)
for f in test/*.test.ts; do bun test "$f"; donesrc/server.ts exports buildServer(client, opts) — one McpServer per
transport (the SDK allows a single connect per instance). The stdio entry and the
pt CLI both build from this factory; a future Streamable-HTTP mount does too.
The anti-drift fragment test (test/fragment.openapi.test.ts) asserts the tool
schemas' API-mirroring fragments (enums, ranges) match apps/api/openapi.codegen.json.
