Flowline
Reference

MCP server — agentic project, flow & QA authoring

The Flowline app ships a local MCP server that lets an AI assistant (Claude Desktop, Cursor, Copilot CLI, or any stdio MCP client) drive almost the entire QA workflow in natural language: create projects, author and refine flows, define scenarios (value sets) with per-field values and expected outcomes, build regression suites, run them against a build, record results, file bugs, and read pass-rate analytics.

New here? Read the hands-on MCP tutorial for an end-to-end walkthrough (natural-language ask → tool call → result) plus a QA management guide. This page is the reference.

How it works

The MCP server is a thin stdio process that calls the tool's existing REST API on http://127.0.0.1:8787 — the same data the web app shows. An open editor merges what it writes (08).

  1. Start the app/API first (npm run dev or npm start).
  2. Point your assistant at the MCP server (below).
  3. Pick the workspace with list_workspaces and select_workspace (which workspace a session uses).

Every write tool reads the project, applies a pure transform, and writes only the changed files, retrying up to 3 times when another save changed them first (how saves resolve). Unknown ids fail fast with a clear error instead of silently succeeding.

Configure your assistant

The server is started with npm run mcp (which runs tsx server/mcp/index.ts). Set FLOW_API_URL if your API is not on the default port. All stdout is reserved for JSON-RPC; logs go to stderr.

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "flowline": {
      "command": "npx",
      "args": ["tsx", "server/mcp/index.ts"],
      "cwd": "/absolute/path/to/flowline",
      "env": { "FLOW_API_URL": "http://127.0.0.1:8787" }
    }
  }
}

Cursor (.cursor/mcp.json) / Copilot CLI / other MCP clients

Use the same command / args / cwd / env. Any stdio MCP client works.

Remote / hosted MCP (multi-user)

When the app is deployed with authentication enabled, anyone with an account can use the MCP over the public internet — no repo checkout, no local process.

  1. Generate a token. Sign in to the web app, open the MCP access tokens panel in your profile, create a named token, and copy it (it is shown once).

  2. Point your assistant at the hosted endpoint (https://<your-host>/mcp), sending the token as a bearer header:

    {
      "mcpServers": {
        "flowline": {
          "url": "https://<your-host>/mcp",
          "headers": { "Authorization": "Bearer fl_pat_…" }
        }
      }
    }
    
  3. Choose a workspace. Each MCP session is isolated to your account. Call list_workspaces, then select_workspace { workspaceId }. The selection lives in the MCP session and never changes your web-app active workspace. Call current_workspace to see the current target.

Notes

  • Tokens are shown once and stored only as a hash; revoke them anytime in the profile panel. Token management itself requires an interactive (cookie) login.
  • The endpoint must be served over HTTPS. To enable DNS-rebinding protection, set MCP_ALLOWED_HOSTS to your public host(s) (comma-separated).

Local folders (no server, no account)

You can also point an assistant straight at folders on your own machine — no sign-in, no token, no repo checkout:

{
  "mcpServers": {
    "flowline": {
      "command": "npx",
      "args": ["-y", "flowline-mcp", "mcp", "/absolute/path/to/your/flows"]
    }
  }
}

This boots the folders itself; nothing else needs to be running. Add FLOWLINE_SITE/FLOWLINE_TOKEN to env to reach your hosted account from the same entry:

{
  "mcpServers": {
    "flowline": {
      "command": "npx",
      "args": ["-y", "flowline-mcp", "mcp", "/absolute/path/to/your/flows"],
      "env": {
        "FLOWLINE_SITE": "https://<your-host>",
        "FLOWLINE_TOKEN": "fl_pat_…"
      }
    }
  }
}

list_workspaces then returns both, with ids qualified as hosted:<id> / local:<id> so select_workspace can target either. Full walkthrough: Edit a folder on your computer.

Tool reference

64 tools, grouped by area — the registerTool calls under server/mcp/ are the source of truth. projectId and flowId are required by every flow-scoped tool unless noted. Node ids, scenario ids, suite ids, etc. are the real ids returned by the corresponding list_* / get_* / create_* tools.

Workspaces (session-scoped)

Tool Args Description
list_workspaces — List your workspaces across every backend this session can reach (hosted account and/or local folders). Ids are qualified as hosted:<id> / local:<id>; a backend that can't be reached is reported under unavailable instead of failing the call.
select_workspace workspaceId Target a workspace for this session by its qualified id (does not change your web-app active workspace).
current_workspace — Show the session's target workspace (selected, or active as a fallback) and which backend it lives on.
create_local_workspace name, path Register path, an existing writable folder under the server's allowed root, as a Local workspace (where projects are stored). It does not select it: call select_workspace with its id from list_workspaces.

Projects

Tool Args Description
list_projects — List all projects in the session's target workspace.
create_project name Create a new empty project. Returns the new id.
get_project projectId Compact summary of a project and its flows.

Flows

Tool Args Description
create_flow projectId, spec Build a whole flow from a high-level spec (see below).
get_flow projectId, flowId Return a flow as a high-level spec (point/end key = real node id).
rename_flow projectId, flowId, name Rename an existing flow.

create_flow spec

Reference nodes by author keys (not UUIDs). The server assigns ids, wires edges (fromHandle = branch), lays out the graph (BFS auto-layout), and returns warnings (dangling_branch, unreachable_node, auto_added_branch).

{
  "projectId": "<id from create_project>",
  "spec": {
    "name": "PIN entry",
    "entry": "idle",
    "points": [
      { "key": "idle", "title": "Idle screen", "branches": ["start"] },
      { "key": "pin",  "title": "Enter PIN",   "branches": ["correct", "wrong"] }
    ],
    "ends": [
      { "key": "ok",     "outcome": "Cell opens" },
      { "key": "locked", "outcome": "Account locked" }
    ],
    "edges": [
      { "from": "idle", "branch": "start",   "to": "pin" },
      { "from": "pin",  "branch": "correct", "to": "ok" },
      { "from": "pin",  "branch": "wrong",   "to": "locked" }
    ]
  }
}

A branch is either a bare name or an object. The object form carries the rest of the branch model:

{ "name": "Sign in", "id": "signin-wrong", "result": "Wrong password",
  "kind": "validation", "staysOnCard": false,
  "expected": [{ "label": "Red text under the password field" }] }
  • name is the action; result is what the system answers. The card renders them as Sign in / Wrong password.
  • id is what edges[].branch refers to; it defaults to name. Two branches on one card that share a name must each set id, or the edges cannot tell them apart.
  • kind is ui, network, or validation.
  • staysOnCard: true marks an outcome that records in place — an inline error — and must own no edge.
  • expected holds results the tester verifies for that outcome specifically, separate from the card-wide expected.

get_flow returns branches in this same form.

spec.notes carries the canvas notes:

{ "text": "Ask design about the empty state", "position": { "x": 40, "y": 80 },
  "pinnedTo": "pin", "color": "#ffd166" }
  • text is the note body; position defaults to 0,0.
  • pinnedTo is a spec key — an author key here, a node id in get_flow output, where keys are ids. A key no node claims is an unknown_note_pin error.
  • get_flow emits notes in this same form, so a get_flow → create_flow round trip keeps every note and its pin.

Graph (incremental editing)

Operate on a live flow by id. Get node ids from get_flow. Mutations return warnings for non-fatal notes (auto_added_branch, pruned_edges, entry_cleared).

Tool Args Description
add_point projectId, flowId, point Add a card (test step) node.
add_end projectId, flowId, outcome Add an end node with an outcome.
add_edge projectId, flowId, from, to, branch? Connect two nodes; auto-adds the branch if missing.
update_point projectId, flowId, … Patch a card's title / description / inputs / expected / branches.
update_end_outcome projectId, flowId, nodeId, outcome Update an end node's outcome label.
delete_node projectId, flowId, nodeId Delete a node and its edges; clears entry if it was the entry.
remove_edge projectId, flowId, edgeId Remove an edge.
set_entry_node projectId, flowId, nodeId Set the flow entry to a card node.
move_node projectId, flowId, nodeId, position {x,y} Reposition a node on the canvas.
add_branch projectId, flowId, nodeId, branch, result?, kind?, staysOnCard? Add a branch to a card. branch is the action, result what the system answers; kind is ui, network or validation; staysOnCard: true marks an outcome that records in place and owns no edge.
remove_branch projectId, flowId, nodeId, branch Remove a branch and prune its edges.
rename_branch projectId, flowId, nodeId, oldName, newName Rename a branch.
update_branch projectId, flowId, nodeId, branch, patch Patch an existing branch: action, result, kind, staysOnCard, expected. Omitted keys are left untouched; result: null and kind: null clear them; expected: [] clears the branch's expected results. branch is an id, an action, or the full action / result label. Setting staysOnCard: true drops the branch's edge and returns a pruned_edges warning.
set_branch_position projectId, flowId, nodeId, branch, side, offset Place a branch outlet. side is top, right, bottom or left; offset is 0–1 along that side.
set_branch_color projectId, flowId, nodeId, branch, color Set a branch color. color is required; pass color: null to clear it.

Card images & backgrounds

Attach images to point cards and end nodes, and control how they render. Images may be PNG, JPEG, WebP, GIF, or SVG, up to 8 MB. Supply each image as either image.base64 (a data: URL prefix is accepted) or image.url — exactly one. URL sources are fetched server-side and must resolve to a public host (loopback/private addresses are refused).

Tool Args Description
add_point_image projectId, flowId, nodeId, image, setAsBackground? Attach an image to a card. The first image becomes the background automatically; pass setAsBackground: true to force a specific one.
set_point_background projectId, flowId, nodeId, assetId (nullable) Pick which attached image is the card background; pass assetId: null to clear (falls back to the first image).
remove_point_image projectId, flowId, nodeId, assetId Detach an image from a point card; clears the background if it pointed there.
set_end_background projectId, flowId, nodeId, image? Set an end node's background image; omit image to clear it.
set_card_image_mode projectId, mode, overlayOpacity? Project-wide toggle between background and thumbnails rendering, with an optional darkening overlay opacity (0–1).

Notes

A note is authoring commentary on the canvas. It sits beside the graph, not in it: notes never appear in flow order, scenarios, runs, the table, or the player. Pin a note to a card or end with pinnedTo and it travels with that node and is deleted with it; its position is then relative to that node instead of the canvas. get_flow returns the flow's notes, and create_flow accepts them.

Tool Args Description
add_note projectId, flowId, text, position? {x,y}, pinnedTo?, color? Add a note. position defaults to 0,0; pinnedTo is a card or end node id. Returns noteId.
update_note projectId, flowId, noteId, patch Patch text, position, pinnedTo, or color. Omitted keys are left untouched; pinnedTo: null unpins the note and leaves it where it is.
delete_note projectId, flowId, noteId Delete a note.

Scenarios

A scenario is a named value set: per-field input/expected values, an optional expected outcome, a background image per card, and a pinned path used for guided runs.

The tools below reach the values, the expected outcome, the per-screen expected notes, and the per-card background overrides. The pinned path is UI-only — set by pinning a branch in the Table or by Update path / Save as new in the Player.

Tool Args Description
list_scenarios projectId, flowId List scenario summaries, each with its owner.
create_scenario projectId, flowId, name Create a scenario. Returns scenarioId.
duplicate_scenario projectId, flowId, scenarioId Deep-clone a scenario ("(copy)").
rename_scenario projectId, flowId, scenarioId, name Rename a scenario.
delete_scenario projectId, flowId, scenarioId Delete a scenario (prunes it from suites + run results).
set_scenario_expected_outcome projectId, flowId, scenarioId, outcome (nullable) Set/clear the expected outcome (null clears).
set_scenario_value projectId, flowId, scenarioId, nodeId, fieldId, value Set a field override; empty value removes it.
set_scenario_expected_note projectId, flowId, scenarioId, nodeId, note Set a per-screen freeform expected note; blank removes it.
set_scenario_background projectId, flowId, scenarioId, nodeId, assetId (nullable) Set the card's background asset for this scenario; null clears the override.

Suites

Tool Args Description
list_suites projectId, flowId List suite summaries, each with its owner.
create_suite projectId, flowId, name Create a suite. Returns suiteId.
update_suite projectId, flowId, suiteId, name?, description? Update name/description.
delete_suite projectId, flowId, suiteId Delete a suite and its cycles (suite runs).
duplicate_suite projectId, flowId, suiteId Deep-clone a suite definition.
set_suite_scenarios projectId, flowId, suiteId, scenarioIds[] Replace the ordered scenario list (every id must exist).

Suite runs & analytics

Tool Args Description
start_suite_run projectId, flowId, suiteId, environment, buildVersion, testDate, release? Start a cycle (one untested result per suite scenario). Each scenario goes to its owner, else to the suite's owner; the rest stay unassigned. Returns suiteRunId.
set_suite_result projectId, flowId, suiteRunId, scenarioId, status, assignee? Record a scenario outcome. status is one of untested, pass, fail, blocked, skipped, retest. assignee is an email or a name; "" clears it. This is the agentic way to record outcomes.
finish_suite_run projectId, flowId, suiteRunId Mark a run finished (stamps finishedAt).
delete_suite_run projectId, flowId, suiteRunId Delete a run.
list_suite_runs projectId, flowId, suiteId? Run summaries (counts, pass rate, progress) with startedBy and finishedBy, optionally per suite.
get_suite_run projectId, flowId, suiteRunId One run with its full results + summary.
list_suite_run_results projectId, flowId, suiteRunId, scenarioId? Every result recorded in a run, newest first, with recordedBy, recordedAt and latest (each scenario's current result); optionally one scenario's.
get_suite_analytics projectId, flowId, suiteId byBuild / byRelease pass-rate series + latestFailing scenarios of the most recent run.

Bugs & runs

Tool Args Description
list_bugs projectId, flowId, suiteRunId?, testSessionId? Bug summaries with reportedBy, optionally filtered by suite run or test session.
file_bug projectId, flowId, title, description?, expected?, actual?, severity?, status?, suiteRunId?, buildVersion?, valueSetId? File a bug. severity is one of low, medium, high, critical; status is one of open, fixed, wontfix. Returns bugId.
delete_bug projectId, flowId, bugId Delete a bug.
export_bugs projectId, flowId?, suiteRunId?, testSessionId?, bugIds? Export bugs with their attachments as a zip — per-bug attachment folders, manifest.csv, and report.pdf. Filter by flow, suite run, test session, or explicit ids; omit every filter to export the whole project. Returns { filename, base64 }; decode the base64 and write it to a .zip.
list_runs projectId, flowId Player-run summaries (id, scenario, status, build, started, tester).
delete_run projectId, flowId, runId Delete a player run.

owner, tester, reportedBy, startedBy and finishedBy are people as the project stores them (see the layout). A summary leaves the field out when the project doesn't record one.

Lifecycle (destructive)

Tool Args Description
delete_flow projectId, flowId Permanently delete a flow with its graph, scenarios, suites, runs, bugs and image files. The flow's folder goes too, unless it still holds files Flowline didn't write.
delete_project projectId Permanently delete a project and all its flows.

What's intentionally not exposed

These stay UI-only (by design) — the MCP surface is for authoring and QA orchestration, not binary/identity/transport concerns:

  • Git / auth management — connecting a workspace to a git remote or Google Drive, and signing in, are UI + server concerns.
  • Raw asset bytes & rendering — fetching or streaming stored image bytes is a multipart/transport concern handled in the player/table UI; MCP treats asset ids as opaque. (Attaching images and choosing backgrounds is now supported — see Card images & backgrounds above.)
  • Full player-run recording — stepping a flow card-by-card and capturing a Run with steps is the player's job. The agent records manual suite outcomes via set_suite_result; it never fabricates a full player run. (This keeps recordSuiteScenarioRun the single writer of suite-tagged runs.)
  • Project export / import & session PDF reports — generated from the UI. (Bug export is available — see export_bugs above.)
  • Template instances & QA test sessions — separate features not part of the scenario/suite path.

Safety notes

Which workspace a session uses, permanent deletes, and what happens when you and an agent change the same item: Driving the tool with MCP → Safety notes.