Skip to main content
◆ FORGE · MCP
The Forge MCP server exposes every Forge operation as a tool your agent can call directly — register a dataset, run augmentation, fine-tune a world model or policy on your robot, read its gate receipts, deploy it to a serving endpoint and drive it, check credits, pull the trained weights — without ever opening forge.alakazam.gg. It’s a thin, stateless proxy over the same /api/workbench, /api/aug, and /api/billing surfaces the web app uses; your bearer is forwarded verbatim and Forge re-verifies every call, so an agent can only touch your own campaigns.

One server, the whole Forge

71 forge_* tools across eight areas — account, datasets & embodiment, sources, augmentation, world-model fine-tuning, evaluation & serving, monitoring & diagnostics, and the playground — behind one Streamable-HTTP endpoint. The loop closes: a dataset goes in, a served model endpoint comes out.

Connect

The server speaks MCP over Streamable HTTP at /mcp. Point your agent at it and supply your Forge credential (see Authenticate).
If you don’t have a token yet, connect without the header and call forge_authenticate first (below) — every other tool accepts the returned token as an optional token argument.

The terminal client

The MCP also serves a small CLI, version-locked to the deployed tools — nothing separate to install or keep updated:
Your agent reaches for it on its own: forge_upload_dataset returns the exact command to run. Output is a rendered panel in a terminal and plain text when piped, so it is safe to capture in logs. Add --json to any command for raw data.

Authenticate

Not sure which method to use? Call forge_login — it returns a short menu to show the user (email link · API key · password) and routes to the right tool, the way a CLI would. Everything below is what it routes to. Forge is owner-scoped by a Supabase JWT or a forge_sk_ API key — both go in the same Authorization: Bearer header and every endpoint accepts either. A headless agent gets one three ways:
  1. forge_send_login_code(email)forge_verify_login_link(url) — passwordless. An email arrives with a “Log in” button. Copy the link address (right-click → Copy link address; do not click it — the link is single-use) and pass the whole URL to forge_verify_login_link. It returns { access_token, refresh_token, expires_in }. If your project’s email template carries a 6-digit code instead, use forge_verify_login_code(email, code) — same result.
    If you signed up with “Continue with Google” or “Sign in with Apple”, this is your only option. An OAuth account has no password, so forge_authenticate can never work for it.
  2. forge_authenticate(email, password) → same { access_token, … }, for accounts that actually have a password.
  3. Connection header — set Authorization: Bearer <jwt> on the MCP connection (above); every tool inherits it, no token argument needed.
Renew with forge_refresh_token(refresh_token) before expires_in elapses (~1h). forge_whoami confirms who a token is; forge_get_credits is your affordability preflight before any paid render.

No account yet?

The MCP deliberately cannot create one. A login tool that mints accounts turns a typo’d address into a real user, so forge_send_login_code sends to existing accounts only. An unknown address comes back with reason: "no_account" and no email is sent. Sign up once at forge.alakazam.gg — email + password, Google, or Apple all work — then come back and call forge_send_login_code with that same address. New accounts start with a small credit grant; forge_get_credits shows it.
If you signed up with Google or Apple, you have no password — use the passwordless code flow above. forge_authenticate cannot work for those accounts, by construction.

Keeping the token

The server is stateless — it stores nothing, including your session. Where the token lives is your client’s job:
  • Per-call — pass token: "<access_token>" to each tool. Simple, but the token lives only in the agent’s context: a fresh session (or a context compaction that drops it) means logging in again.
  • On the connection — put it in the Authorization: Bearer header (see Connect). Survives across sessions until it expires; every tool inherits it with no token argument.
Codes are single-use, ~1h, and rate-limited. Each new send invalidates the previous code, so if a send appears to do nothing, check the inbox before re-sending — a retry loop makes it strictly worse. A rate-limited send returns reason: "rate_limited" rather than an opaque error.

API keys — the credential for anything unattended

A login token expires in about an hour, and renewing it for an OAuth account means another email. That is fine for a person and wrong for an MCP client that reconnects, a CI job, or a partner integration. Log in once, mint a key, use the key forever:
Then put it where the token would have gone — it works identically:
The raw key is returned exactly once. Forge stores only a SHA-256 hash and genuinely cannot show it again — if it is lost, revoke it and mint another.
forge_list_api_keys shows every key masked, with last_used_at so you can spot ones nothing uses any more. forge_revoke_api_key kills one permanently; an already-running server may honour it for up to a minute while its auth cache expires. Twenty keys per account.

The tools

Import from anywhere

Your dataset does not have to live on Hugging Face, and it does not have to be uploaded chunk by chunk. forge_import_dataset takes a source_uri in any of these schemes, materializes the bytes server-side, and gives you a normal {campaign_id} that every other tool already understands. The import is asynchronous: the call returns {campaign_id} immediately, with health_status "importing" while the server fetches the bytes. Poll forge_get_campaign until it leaves "importing"ready means the dataset is mineable, failed means the fetch did not complete and health_error says why. Pass a stable idempotency_key so a retried create returns the same campaign instead of importing the dataset a second time.
Presign first. An S3 presigned GET, a GCS signed URL, or an Azure SAS link imports with no stored secret at all — time-boxed, scoped to one object, nothing to rotate or revoke. Pass it as source_uri, omit credential_id, done. Store a credential only when you cannot presign (for example importing a whole prefix rather than a single archive).
A private gs:// path needs a stored gcs credential or a signed URL — a credential-less gs:// import falls back to the server’s own identity and is therefore allowed only for buckets an operator has explicitly allowlisted. The same schemes work for a fine-tune: pass the URI as dataset_ref to forge_create_world_model, with credential_id alongside it when the source is private and you could not presign.

Credential flow

When presigning isn’t an option, keep a credential on the account and pass it by reference:
  1. forge_store_source_credential(provider, name, payload){id, provider, name, created_at}. payload holds the provider fields — s3: access_key_id, secret_access_key, optional region / endpoint_url / session_token; gcs: service_account_json; azure: account_name + sas_token or account_key; hf: hf_token.
  2. forge_import_dataset(source_uri, credential_id=<id>) — the server decrypts the credential for that one fetch.
  3. forge_delete_source_credential(credential_id) when the import is done.
Credentials are encrypted at rest, owner-scoped, and never echoed: forge_list_source_credentials returns only {id, provider, name, created_at}, so a lost secret is re-stored, not recovered. Give each one the narrowest read-only scope that works. Imports are guarded: https fetches are SSRF-checked (private, loopback, link-local, and metadata address ranges are refused, redirects capped and re-checked), archives unpack traversal-safe, and both byte and file counts are capped server-side (defaults 20 GB / 20 000 files). Failures answer 400 unsupported source scheme: <scheme> or 400 source fetch failed: <why>; an unknown credential_id answers 404 credential not found, and a server with no credential-store key answers 503 credential store not configured.

Long-running work & spend

Runs and fine-tunes are asynchronous. The create tool returns immediately; poll forge_get_campaign (and forge_get_job for augmentation renders) until a stage is terminal, then fetch the artifact. For a cross-job view, forge_list_activity returns everything you own that is queued or running, with measured ETAs (never guessed — a job with no observed rate reports eta: null and says why). Stuck vs. slow. Read all_healthy and needs_attention before saying anything is wrong: a job is a problem only when its row carries a diagnosis. Every row also carries stale_s (seconds since it last reported), but staleness alone is not a verdict — the server compares it against that job’s known heartbeat cadence (~60s for a training box) and writes the conclusion into diagnosis. For fine-tunes, forge_diagnose_finetune returns the full forensics: a verdict (healthy, late_heartbeat — usually a long checkpoint save, zombie_suspected — runner dead with the box still up and billing), the job’s state history, and the training log’s tail with error lines pre-extracted. Zombie boxes are also auto-remediated server-side: a train job silent for 45+ minutes is failed and its box deleted; forge_cancel_world_model frees the training slot immediately if you’d rather not wait. Guardrails, so a looping agent can’t run away with your wallet or GPUs:
  • Idempotency — pass an idempotency_key on forge_start_run, forge_start_playground_op, and forge_create_world_model; a retry with the same key returns the original result instead of billing again.
  • Caps — a per-owner ceiling bounds concurrent world-model jobs (count and summed $ budget); over it, creation is refused. Budget/steps are clamped server-side.
  • Cancelforge_cancel_world_model stops a fine-tune and its billable box; forge_cancel_job stops an augmentation run.
  • Queueing — training capacity is a shared fleet. A waiting job’s campaign note reads waiting for a training slot — position N of M (K training); that is normal motion, not a hang, and the queue advances whenever a box finishes. The first minutes after create may read created via forge workbench — the scheduler simply has not ticked yet.
  • Out of credits — a billable call with an empty wallet fails with HTTP 402 and a body insufficient_credits:need=N:checkout=/api/billing/credits/checkout:packs=/api/billing/credits/packs. To resolve it headlessly: forge_list_credit_packs → pick a pack → forge_credits_checkout → open the returned Stripe url → re-check forge_get_credits → retry. No web UI required.

Serve the trained model

The pipeline does not end at a weights archive. Once a fine-tune passes export:
  1. forge_deploy_model(id) — free: writes a deployment record and returns a wam_sk_ deployment token exactly once (the server keeps only a hash). No GPU runs yet.
  2. forge_run_rollout(deployment_id, deployment_token, frames|video_b64) — the first call cold-boots the model (minutes: weights + load); calls within the warm window return in seconds. For a wam policy you get the denoised action chunk — chunk × action_dim absolute targets, ready to drive the robot.
  3. forge_undeploy_model(deployment_id) — revokes the token; idle containers drain on their own either way.
Serving scales to zero (an idle deployment costs nothing) and each deployment carries a per-UTC-day GPU-second budget; past it, rollouts refuse until midnight. For sustained real-time control, talk to us about a dedicated lane — the rollout tool is for testing and evaluation traffic. Media tools (*_preview, *_video, *_clip, forge_download_export, forge_download_weights) never stream multi-MB payloads through the model — they return a URL (and, for weights, a short-lived signed URL) you fetch directly.