docs · docs/20-control-plane/MCP_SKILL_PACK.md · synced 2026-09-10

MCP server and skill pack

driving Vflin from an AI agent; the danger gate; with MCP_TOOL_CATALOG.md

For an AI agent driving Vflin over MCP. This is the method (deliverable 4 of AGENT_TOOLING_STRATEGY.md (internal: docs/20-control-plane/AGENT_TOOLING_STRATEGY.md)) — the workflow, the safety rules, and the idioms — so an agent uses the tools well instead of inventing workflows or tripping the danger gate. The tool list itself is not here (it would drift): call tools/list for the live, authoritative set (each tool’s description carries its danger level, required mode, and — for long ops — the status tool to poll). Setup + per-client config: crates/vflin-mcp/README.md (internal: crates/vflin-mcp/README.md).

What Vflin is

A browser-profile OS: antidetect profiles (signed, encrypted blobs), launched on a browser core, with automation (scripts/graphs, task groups, schedules, datasets), sync, and a hub. Every tool is a thin 1:1 wrapper over the Local API (ADR-0003) — if the vflin CLI can’t do it, neither can you.

The method: Audit → Plan → Act

  1. Audit — read before you write. system.health, profile.list, automation.script.list, automation.run.list are read-only (green) and safe to call freely. Discover ids before acting on them.
  2. Plan — state the few mutating calls you intend. Mutating tools are explicit, never a side effect of a read.
  3. Act — make the mutation, then verify (re-read / poll). Don’t assume success; check the envelope.

Safety: session mode + danger levels

Each tool has a danger level (in its tools/list description) and runs under a session mode (VFLIN_MCP_MODE, set by the operator): read-only < normal < trusted.

danger needs mode examples
green (read-only) read-only system.health, *.list, *.get, *.status
yellow (mutating) normal profile.create, profile.start, automation.run.start, cloud.push
red (dangerous) trusted (none yet — reserved for irreversible/exfil ops)
  • A tool above the session mode is refused before any daemon callisError:true, with a nextSteps telling the operator which VFLIN_MCP_MODE to set. Don’t retry the same call; ask the operator to raise the mode, or pick a read-only alternative. initialize tells you the active mode up front.
  • Treat a refusal as a policy signal, not a transient error.

Where the daemon is (the installed product, A88)

vflin-mcp reads the Local API discovery file $VFLIN_HOME/local-api.json, else ~/.vflin/local-api.json — the same root the daemon writes and the shell and the CLI read; nothing uses %LOCALAPPDATA%\VFlin. The installed product ships the server beside the app: %LOCALAPPDATA%\VFlin Antik flin-mcp.exe — that is the command an MCP client’s mcpServers entry points at (the directory is not on PATH). The daemon itself is started by the app on launch and stays up after the window closes; if no daemon answers, the app (or vflind.exe from the same directory) starts one — the MCP server never starts a daemon.

Getting CDP on a profile (A89)

Do not look for a port: by default there is none. Start the profile, then vflin_profile_cdp_attach (needs VFLIN_MCP_MODE=trusted — ask the operator; never retry the same call) and drive the returned wsEndpoint with your CDP client; it serves one client and expires — vflin_profile_cdp_detach when done. A profile whose detail says cdpTransport: port publishes its port to every local process; treat brokered:false as that fact, not as a convenience.

Poll, don’t block

Long-running tools (e.g. automation.run.start, automation.taskgroup.run) return an id immediately and queue the work. Their result’s summary/nextSteps name the status tool to poll (e.g. automation.run.status with {id}). Poll that to a terminal state (succeeded/failed) — never assume completion, never busy-block the operator. The tool’s tools/list description marks it long-running.

Reading results

Every result is JSON in the text block: { "summary": …, "envelope": { ok, data } } on success, or { "summary": …, "ok": false, "error": …, "nextSteps"? : … } on failure (isError:true). Read summary first, then data for detail.

Failure playbook (the nextSteps hints)

On a recoverable error the result carries a nextSteps keyed on the daemon’s code:

code meaning do
NOT_FOUND the id doesn’t exist call the matching *.list to get a valid id
LOCKED the crypto session is locked session.unlock first, then retry
LOCKED_REMOTE the profile is held by another device check its sync lock status, then retry
UNAUTHENTICATED no cloud/session auth cloud.login (or session.unlock) first
POLICY_DENIED the daemon’s policy refused it needs a higher policy mode
TOTP_REQUIRED/TOTP_INVALID 2FA needed re-call with a current totpCode
STALE/CONFLICT another device changed it pull/refresh the latest, then retry

Recipes (the live tool names are in tools/list)

  • Run a script on one profile: profile.listautomation.script.listautomation.run.start (id) → poll automation.run.status to terminal → read outputs (automation.run.artifact if any).
  • Fan a script across N profiles: automation.taskgroup.run with the profile ids (id) → poll automation.taskgroup.get until every run is terminal.
  • Create + launch a profile: profile.createprofile.start (returns the CDP endpoint).

Hard rules

  • Never invent a tool — only what tools/list returns exists.
  • Never retry a policy refusal; escalate the mode or change approach.
  • Never block on a long op — poll its status tool.
  • A RUNNING profile is never synced; the daemon enforces this — don’t fight it.