Skip to content

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

Terminal
$ 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.

Cursor, Windsurf, Codex — mcpServers-style clients
{
  "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.

ToolArgumentsKeyReturns
get_twin_catalog
GET /api/v1/twin/catalog
nonenoneplane, twins[] {id, slug, name, description, surface, status, mounted, provisionable, runtime{source, operations, entities}}, counts
provision_twins
POST /api/v1/twin/environments
  • twins string[]Catalog twin ids (or slugs) to mount, e.g. ["salesforce"] or ["slack", "github"]. Get them from get_twin_catalog.
  • ttl_minutes integer · optionalServing lease in minutes (5–10080; default 60).
  • name string · optionalDisplay name for the environment (≤120 chars).
  • scenario_id string · optionalA saved scenario (twsc_<16 hex>, see list_scenarios) or a preset (twsp_…, see list_preset_scenarios) whose rows seed the twins. Mutually exclusive with scenario_prompt.
  • scenario_prompt string · optionalPlain-language starting state synthesized into the mounted twins (e.g. "three overdue invoices for a suspended recipient"). Mutually exclusive with scenario_id.
bf_ keyenv_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_environment
GET /api/v1/twin/environments/{envId} + GET …/gateway
  • env_id stringEnvironment id as returned by provision_twins or list_twin_environments (twe_ + 16 hex). Cluster-plane tenv_ ids are not served here.
bf_ keythe environment record + status, lease_expired, links, and gateway{services[]{mcp_url, rest_base_url, tools[]}, sessions_url, examples{curl, python, typescript}}
list_twin_environments
GET /api/v1/twin/environments
  • limit integer · optionalMaximum environments to return (1–200, default 50).
bf_ keyplane, environments[] {env_id, name, status, services, expires_at, lease_expired, mcp_path, …}, count
extend_twin_environment
POST /api/v1/twin/environments/{envId}/extend
  • env_id stringEnvironment id as returned by provision_twins or list_twin_environments (twe_ + 16 hex). Cluster-plane tenv_ ids are not served here.
  • ttl_minutes integerMinutes to add (5–10080).
bf_ keyok, env_id, expires_at
teardown_twin_environment
DELETE /api/v1/twin/environments/{envId}
  • env_id stringEnvironment id as returned by provision_twins or list_twin_environments (twe_ + 16 hex). Cluster-plane tenv_ ids are not served here.
bf_ keyok, env_id, detail
check_twin_health
GET /api/v1/twin/environments/{envId}/health
  • env_id stringEnvironment id as returned by provision_twins or list_twin_environments (twe_ + 16 hex). Cluster-plane tenv_ ids are not served here.
bf_ keyok, env_id, plane, status, checks[] {name, ok, detail}, latency_ms, checked_at, http_status (200 | 503)
list_twin_tasks
GET /api/v1/twin/environments/{envId}/tasks
  • env_id stringEnvironment id as returned by provision_twins or list_twin_environments (twe_ + 16 hex). Cluster-plane tenv_ ids are not served here.
bf_ keyenv_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_twin
POST /api/v1/twin/environments/{envId}/runs
  • env_id stringEnvironment id as returned by provision_twins or list_twin_environments (twe_ + 16 hex). Cluster-plane tenv_ ids are not served here.
  • model string · optionalModel id, e.g. "claude-sonnet-5", "gpt-5-mini", "grok-4", "deepseek-v4-flash", or your custom endpoint's model. Optional when provider is given and has a default.
  • prompt string · optionalThe task for the model, in plain language (1–4000 chars). Optional when task_id or task_ids is given — then it is extra guidance appended to the task's instruction.
  • task_id string · optionalA task id from list_twin_tasks: the run is graded by that task's verifier and the record carries score (0..1), passed, and the verifier detail.
  • task_ids string[] · optionalSeveral task ids from list_twin_tasks (or the single string "all"): a bounded sequential batch; the result is the batch with per-task records and an aggregate {tasks, completed, graded, passed, mean_score}.
  • max_tasks integer · optionalWith task_ids: run at most this many of them (1–10).
  • max_steps integer · optionalTool-call budget (1–20, default 8).
  • provider string · optionalanthropic | openai | xai | deepseek | custom. Inferred from the model id when omitted; "custom" serves the OpenAI-compatible endpoint saved under your account.
  • base_url string · optionalOnly with provider "custom": pins the run to this endpoint, which must be the custom endpoint saved under your account (POST /api/v1/accounts/providers) — this tool never carries keys.
bf_ keyrun_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_scenarios
GET /api/v1/twin/scenarios
  • twin string · optionalOptional catalog twin id or slug (e.g. "salesforce", "google-calendar"); only scenarios that seed that twin are listed.
bf_ keyplane, scenarios[] {scenario_id, name, twins, seed_config | prompt, tags, captured_from?, created_at}, count
list_preset_scenarios
GET /api/v1/twin/scenarios/presets
  • twin string · optionalOptional catalog twin id or slug (e.g. "salesforce", "google-calendar"); only scenarios that seed that twin are listed.
bf_ keypresets[] {scenario_id, name, category, description, services, seeded_entities, seed_rows, seed_config}, count, revision
create_scenario
POST /api/v1/twin/scenarios
  • name stringDisplay name (≤120 chars).
  • twins string[]Catalog twin ids (or slugs) the scenario mounts.
  • seed_config object · optional{ service: { entity: [rows…] } } — explicit rows per twin entity (mutually exclusive with prompt). get_twin_catalog names the twins; each twin's entities and fields are validated at save time.
  • prompt string · optionalPlain-language starting state to synthesize into the named twins (mutually exclusive with seed_config).
  • description string · optionalWhat the scenario sets up (≤500 chars).
  • tags string[] · optionalUp to 8 tags for filtering.
bf_ keyscenario_id, name, twins, seed_config, source_prompt?, synthesis?, plane, detail, links{self, export, apply}
save_scenario_from_twin
POST /api/v1/twin/environments/{envId}/save-scenario
  • env_id stringEnvironment id as returned by provision_twins or list_twin_environments (twe_ + 16 hex). Cluster-plane tenv_ ids are not served here.
  • name stringDisplay name for the captured scenario (≤120 chars).
  • description string · optionalWhat the captured state represents (≤500 chars).
  • tags string[] · optionalUp to 8 tags.
bf_ keyscenario_id, twins, seed_config, captured_from{env_id, world_id, session_id?}, capture{captured_rows, skipped_modified, skipped_tables}, detail, links

An agent session

What Claude Code calls, and what comes back
# 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
  1. Provision. provision_twins mounts 1–6 catalog twins (ids from get_twin_catalog) and answers with routing: per-service mcp_url / rest_base_url, the world-level MCP, sessions_url for isolated forks, and an mcp_config block the agent can paste into the app under test. Seed it with scenario_id (a saved twsc_ scenario or a twsp_ preset) or scenario_prompt.
  2. 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, and credential_source— the server’s key or the provider key saved under your account. Keys never travel through a tool argument.
  3. Verify, keep, tear down. check_twin_health is a real probe (expect 15–20 s on production; read ok and checks[]). save_scenario_from_twin captures the rows added beyond the baseline as a reusable scenario. teardown_twin_environment delists the environment — the backing world is persistent data and is never destroyed.

Raw JSON-RPC with curl

Terminal
# 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, and get_twin_catalog serve without a key. Every other tools/call needs a resolved identity; without one the server answers JSON-RPC error -32001 (HTTP 401) naming the x-api-key header and blobfish login.
  • What a REST route refuses as 4xx comes back as a tool result with isError: true whose text is the same {error, detail} JSON plus http_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 bad env_id never reaches the runtime.
  • Website plane only. The cluster plane (tenv_ projections) is not operational on this deployment and is neither advertised nor accepted; a tenv_ id is refused with invalid_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