Guides
Platform MCP server
One MCP endpoint — https://blobfish.ai/api/v1/twin/mcp, server name blobfish-twin-platform — lets a coding agent do what the twin REST API and blobfish twin …do, as tools: read the catalog, provision twins, read an environment’s routing card, run a model against it, save and list scenarios, extend or tear it down. Every tool calls the same functions the REST routes call, so the two surfaces cannot drift. Environments live on the website plane (twe_ ids): they assemble synchronously and are ready in seconds.
Connect from Claude Code
$ claude mcp add --transport http blobfish-twins https://blobfish.ai/api/v1/twin/mcp \
--header "x-api-key: $BLOBFISH_API_KEY"
# no key yet?
$ blobfish login # mints a bf_ key into ~/.blobfish/config
$ export BLOBFISH_API_KEY=bf_…Streamable HTTP, one JSON-RPC message per request, JSON responses. The key rides in the x-api-key header (Authorization: Bearer bf_… also works) and never in a tool argument. Against a local checkout use http://localhost:3000/api/v1/twin/mcp.
{
"mcpServers": {
"blobfish-twins": {
"url": "https://blobfish.ai/api/v1/twin/mcp",
"headers": { "x-api-key": "${BLOBFISH_API_KEY}" }
}
}
}Adapt the field names your client uses for a remote HTTP server and its headers; the URL and the x-api-key header are the whole configuration.
Tools
13 tools; 12 need your key. The table is rendered from the registry the server serves — tools/list returns exactly these names and schemas.
| Tool | Arguments | Key | Returns |
|---|---|---|---|
get_twin_catalogGET /api/v1/twin/catalog | none | none | plane, twins[] {id, slug, name, description, surface, status, mounted, provisionable, runtime{source, operations, entities}}, counts |
provision_twinsPOST /api/v1/twin/environments |
| bf_ key | env_id, status, plane, name, world_id, services, mounted_services[], scenario_rows?, routing{console_url, gateway_url, health_url, world_mcp_url, sessions_url, services[]{mcp_url, rest_base_url, …}, mcp_config}, links, next |
get_twin_environmentGET /api/v1/twin/environments/{envId} + GET …/gateway |
| bf_ key | the environment record + status, lease_expired, links, and gateway{services[]{mcp_url, rest_base_url, tools[]}, sessions_url, examples{curl, python, typescript}} |
list_twin_environmentsGET /api/v1/twin/environments |
| bf_ key | plane, environments[] {env_id, name, status, services, expires_at, lease_expired, mcp_path, …}, count |
extend_twin_environmentPOST /api/v1/twin/environments/{envId}/extend |
| bf_ key | ok, env_id, expires_at |
teardown_twin_environmentDELETE /api/v1/twin/environments/{envId} |
| bf_ key | ok, env_id, detail |
check_twin_healthGET /api/v1/twin/environments/{envId}/health |
| bf_ key | ok, env_id, plane, status, checks[] {name, ok, detail}, latency_ms, checked_at, http_status (200 | 503) |
list_twin_tasksGET /api/v1/twin/environments/{envId}/tasks |
| bf_ key | env_id, plane, world_id, source (world_tasks | minted_programmatic), seed, count, tasks[] {task_id, instruction, difficulty, kind, grades[], assertions}, rejected_count, detail, links |
run_model_on_twinPOST /api/v1/twin/environments/{envId}/runs |
| bf_ key | run_id, env_id, plane, model{provider, model, label, credential_source}, status, finish, steps[], final_answer, tool_calls, failed_tool_calls, usage, session_id, error?, links; graded runs add task_id, task_instruction, score, passed, verifier{kind, detail, assertions_passed, assertions_total, failed_conditions}; task_ids answers the batch {batch_id, runs[], skipped[], aggregate} |
list_scenariosGET /api/v1/twin/scenarios |
| bf_ key | plane, scenarios[] {scenario_id, name, twins, seed_config | prompt, tags, captured_from?, created_at}, count |
list_preset_scenariosGET /api/v1/twin/scenarios/presets |
| bf_ key | presets[] {scenario_id, name, category, description, services, seeded_entities, seed_rows, seed_config}, count, revision |
create_scenarioPOST /api/v1/twin/scenarios |
| bf_ key | scenario_id, name, twins, seed_config, source_prompt?, synthesis?, plane, detail, links{self, export, apply} |
save_scenario_from_twinPOST /api/v1/twin/environments/{envId}/save-scenario |
| bf_ key | scenario_id, twins, seed_config, captured_from{env_id, world_id, session_id?}, capture{captured_rows, skipped_modified, skipped_tables}, detail, links |
An agent session
# 1 · the agent provisions a Salesforce twin
tools/call provision_twins
{"twins": ["salesforce"], "ttl_minutes": 60, "name": "CRM agent test"}
→ env_id: twe_9246107c29038f60 · status: ready · mounted_services: [salesforce]
routing.services[0].mcp_url: https://blobfish.ai/api/v1/sandbox/worlds/sbx_…/services/salesforce/mcp
routing.services[0].rest_base_url: https://blobfish.ai/api/v1/sandbox/worlds/sbx_…/services/salesforce/rest
routing.console_url: https://blobfish.ai/twin/twe_9246107c29038f60
# 2 · it runs a model against the seeded twin (keys come from the server or your saved provider)
tools/call run_model_on_twin
{"env_id": "twe_9246107c29038f60", "model": "claude-sonnet-5",
"prompt": "List the open opportunities and close the smallest one as won.", "max_steps": 8}
→ run_id: twrun_… · status: completed · finish: final · model.credential_source: server
steps[0].tool: salesforce_opportunities_list · steps[1].tool: salesforce_opportunities_update
final_answer: "Closed Veyra Logistics – renewal ($12,400) as Closed Won."
# 3 · it tears the environment down when done (delists; the world is never destroyed)
tools/call teardown_twin_environment
{"env_id": "twe_9246107c29038f60"}
→ ok: true- Provision.
provision_twinsmounts 1–6 catalog twins (ids fromget_twin_catalog) and answers withrouting: per-servicemcp_url/rest_base_url, the world-level MCP,sessions_urlfor isolated forks, and anmcp_configblock the agent can paste into the app under test. Seed it withscenario_id(a savedtwsc_scenario or atwsp_preset) orscenario_prompt. - Drive. Either point the application at the routing URLs, or call
run_model_on_twin: the model runs in a fresh isolated session and the record names every tool call, the final answer, andcredential_source— the server’s key or the provider key saved under your account. Keys never travel through a tool argument. - Verify, keep, tear down.
check_twin_healthis a real probe (expect 15–20 s on production; readokandchecks[]).save_scenario_from_twincaptures the rows added beyond the baseline as a reusable scenario.teardown_twin_environmentdelists the environment — the backing world is persistent data and is never destroyed.
Raw JSON-RPC with curl
# initialize — no key
$ curl -sS -X POST https://blobfish.ai/api/v1/twin/mcp \
-H "Content-Type: application/json" -H "Accept: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
# tools/list — no key
$ curl -sS -X POST https://blobfish.ai/api/v1/twin/mcp \
-H "Content-Type: application/json" -H "Accept: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
# the catalog — no key
$ curl -sS -X POST https://blobfish.ai/api/v1/twin/mcp \
-H "Content-Type: application/json" -H "Accept: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_twin_catalog","arguments":{}}}'
# provision — bf_ key required (without it: JSON-RPC error -32001, HTTP 401)
$ curl -sS -X POST https://blobfish.ai/api/v1/twin/mcp \
-H "X-API-Key: $BLOBFISH_API_KEY" -H "Content-Type: application/json" -H "Accept: application/json" \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"provision_twins","arguments":{"twins":["trolley"],"ttl_minutes":10}}}'A tool result is {content: [{type: "text", text: <JSON>}], structuredContent, isError}. Notifications (no id) are acknowledged with 202; GET on the URL returns the discovery document.
Auth, planes, and errors
initialize,ping,tools/list, andget_twin_catalogserve without a key. Every othertools/callneeds a resolved identity; without one the server answers JSON-RPC error-32001(HTTP 401) naming thex-api-keyheader andblobfish login.- What a REST route refuses as 4xx comes back as a tool result with
isError: truewhose text is the same{error, detail}JSON plushttp_status— e.g.twin_service_unavailable,invalid_ttl,invalid_twin_environment,environment_not_found,invalid_run_request,model_unavailable. Arguments are validated before any call: an unknown twin, a bad TTL, or a badenv_idnever reaches the runtime. - Website plane only. The cluster plane (
tenv_projections) is not operational on this deployment and is neither advertised nor accepted; atenv_id is refused withinvalid_twin_environment. Expiry returns 410 from world surfaces until the lease is extended; teardown permanently revokes serving. Neither lifecycle action deletes retained world data. - The same identity, the same records: environments and scenarios created here appear in the console, in
blobfish twin-runs list, and on the REST API.
Next
Gateway & health · Models · Custom scenarios · Twin reference