LaunchFlag

Docs / Agents

Agents that ship dark.

Your agent writes code you did not read line by line. The skill teaches it to wrap every risky change in a flag. The MCP server lets it create and flip those flags without leaving the editor. You keep the switch.

Why agents need this

#
  • You did not read every line. An agent can write a checkout rewrite in four minutes. A flag means the whole thing can be off again in four seconds, from a phone, with no rollback and no deploy.
  • The skill changes what the agent writes. It wraps the new path in an if, keeps the old path intact and reachable, and reports the flag key back to you in its final message.
  • The MCP server changes what it can do. It can create a flag, turn it on in dev to test its own work, and promote it to staging. Turning a flag on in production stays yours.

One command setup

#

From the root of your project. It writes your key into .env.local, downloads the skill to .claude/skills/launchflag/SKILL.md, and configures the MCP server for Claude Code and Cursor.

Terminal

$ npx launchflag init

It asks for your dev key and your API token, or takes them as --key and --token. Both are on the dashboard Install page. It also adds .env.local, .mcp.json and .cursor/mcp.json to your .gitignore, because two of those now hold a token.

Manual setup

#

The hosted MCP server is one URL and a token. Nothing to install, nothing to keep running. Replace lfk_… with your API token.

Claude Code, hosted

$ claude mcp add --transport http launchflag https://launchflag.dev/mcp --header "Authorization: Bearer lfk_…"

Or commit nothing and check in a project file instead. Claude Code reads .mcp.json at the project root. Because it carries the token, keep it out of git.

.mcp.json

{
  "mcpServers": {
    "launchflag": {
      "type": "http",
      "url": "https://launchflag.dev/mcp",
      "headers": { "Authorization": "Bearer lfk_…" }
    }
  }
}

Cursor reads the same shape from .cursor/mcp.json for a single project, or ~/.cursor/mcp.json for every project.

.cursor/mcp.json

{
  "mcpServers": {
    "launchflag": {
      "type": "http",
      "url": "https://launchflag.dev/mcp",
      "headers": { "Authorization": "Bearer lfk_…" }
    }
  }
}

Codex

$ codex mcp add launchflag --url https://launchflag.dev/mcp --header "Authorization: Bearer lfk_…"

Anything else that reads an mcpServers block takes the same JSON on one line.

Any other client

{"mcpServers":{"launchflag":{"type":"http","url":"https://launchflag.dev/mcp","headers":{"Authorization":"Bearer lfk_…"}}}}

Prefer the process local? @launchflag/mcp is the same seven tools over stdio, reading the token from the environment instead of a header.

Local stdio

$ claude mcp add launchflag -e LAUNCHFLAG_TOKEN=lfk_… -e LAUNCHFLAG_URL=https://launchflag.dev -- npx -y @launchflag/mcp

The seven tools

#
ToolWhat it does
list_flagsEvery flag with its dev, staging and prod state, fail mode and variants.
create_flagCreate a flag, off in every environment, and get the snippet to wrap the new path in.
set_flagTurn a flag on or off in one environment, or change who it targets.
promote_flagCopy one environment's config forward: dev to staging, staging to prod.
delete_flagDelete a flag, once the code paths behind it are gone.
emergency_offKill switch: disable every non-essential flag in the project at once, or lift it.
list_projectsYour projects and their environment keys.

Every tool takes an optional project id and uses your first project when you leave it out. Errors come back as text the agent can read, not exceptions.

The skill workflow

#

The skill is served at https://launchflag.dev/skill.md and lives in your repo at .claude/skills/launchflag/SKILL.md. It fires on any user-facing feature, rewrite, payment, auth, email or deletion path, new agent tool, or prompt change. It tells the agent to:

  • Write the if. Wrap the new path, leave the old one intact and reachable, never delete it in the same change.
  • Ship it dark. No dashboard step. The flag registers itself the first time the code runs, off everywhere, fail closed.
  • Never turn it on in prod. Dev is fair game for testing its own work, staging by promotion. The staging to prod step is yours.
  • Tell you the key. Every final message names the flag, its fail mode, and the one line that turns it on.
  • Offer cleanup later. Once a feature has been fully on for weeks, it offers a change that deletes the flag and the old path.

What the agent writes

import { flag } from "@launchflag/sdk";

if (await flag("checkout_v2", user.id)) {
  return newCheckout(cart);   // new path
}
return oldCheckout(cart);     // old path, untouched

Not using Claude Code? The file is plain markdown. Point any agent at it, or paste it into your AGENTS.md. npx launchflag init appends a short LaunchFlag section to an existing AGENTS.md for you.

Token safety

#
  • The token is per user, not per project. It can read and change flags in every project you own, so treat it like a password.
  • It is shown once, when you create it. Only a hash is stored. Lost it, or leaked it: revoke and create a new one from Settings, then re-run npx launchflag init.
  • .mcp.json and .cursor/mcp.json hold the token in plain text. Keep both out of git. The init command adds them to .gitignore for you.
  • The token is not the same thing as an environment key. lfk_… manages flags, lf_dev_… and its siblings only evaluate them. Never give an agent a production environment key it does not need.

Troubleshooting

#
SymptomWhat it means
401 Missing API tokenNo Authorization header reached us, or it does not start with lfk_. Check the header is on the MCP entry and not the outer object.
401 Invalid API tokenThe token was revoked or regenerated. Create a new one in Settings and update every client that holds it.
Trial endedReads still work, writes do not. list_flags prints a TRIAL ENDED banner and your flags keep serving their last state. Upgrade to change them again.
429 Rate limited120 requests a minute per token. The response carries retry-after in seconds. An agent looping over list_flags is the usual cause.
Unknown projectThe project id does not belong to you, or you have no project yet. Run list_projects.
Tools do not appearThe endpoint is POST only and stateless. A client that opens a GET stream first will see a 405; use a Streamable HTTP client, or the stdio server instead.