@minicor/mcp-server
v4.19.6
Published
Desktop and browser RPA automation, workflow management, and AI-powered debugging for the Minicor platform. Formerly Laminar.
Readme
Minicor MCP Server
Build, deploy, and debug VM-based browser and desktop automations from Cursor or Claude Code. The AI agent connects to your Windows VM, writes Python automation scripts, tests them through the Minicor executor, and deploys production-ready workflows - with skills accumulated from every build.
Quick Start
1. Install
Cursor - add to ~/.cursor/mcp.json:
{
"mcpServers": {
"minicor": {
"command": "npx",
"args": ["-y", "@minicor/mcp-server"]
}
}
}Claude Code - no global install needed (npx always runs the latest):
claude mcp add minicor -- npx -y @minicor/mcp-server2. Authenticate
npx -y -p @minicor/mcp-server minicor-mcp-setupOpens a browser to sign in. Tokens are stored at ~/.minicor/tokens.json with auto-refresh. For headless / SSH:
npx -y -p @minicor/mcp-server minicor-mcp-setup --cliThe package ships multiple commands. Always use
npx -p @minicor/mcp-server <command>to run a specific one (minicor-mcp-setup,minicor-bootstrap, …). Runningnpx @minicor/mcp-serveralone only starts the MCP server and ignores trailing arguments.
Hosted MCP (cloud, no install)
The same server also runs hosted at https://mcp.minicor.com/mcp (Streamable HTTP). Sign-in is your Minicor email/password through an OAuth flow - no API keys, no local tokens. The tool set matches the local server except for the tools that read and write your local disk (workspace clones, sessions, sync); those require the local install above.
The hosted server handles every request with a fresh instance, so it remembers no VM connection between calls: pass vmId + workspaceId (from vm_list) to the direct VM tools - vm_screenshot, vm_execute_script, debug_rpa_step and friends - and each call resolves the VM for itself. Locally, vm_connect still works the way it always has.
Claude (web / Desktop / mobile):
- Go to Settings > Connectors > Add custom connector.
- Paste
https://mcp.minicor.com/mcpand save. OAuth is auto-discovered - no client ID or secret. - Claude opens the Minicor sign-in page. Enter your email, password, and region (US/Canada). Done - the connector acts as your account.
Claude Code:
claude mcp add --transport http minicor-cloud https://mcp.minicor.com/mcpThen run /mcp inside Claude Code to trigger the OAuth sign-in.
Cursor - add a URL-based entry to ~/.cursor/mcp.json (Cursor prompts for the OAuth sign-in on first use):
{
"mcpServers": {
"minicor-cloud": {
"url": "https://mcp.minicor.com/mcp"
}
}
}Access tokens expire within an hour and refresh automatically for up to 30 days; after that you sign in again. To self-host the same endpoint (or restrict sign-in to your email domain), see docs/HOSTED.md.
Older installs may have a separate
minicor-builderentry in their MCP config. That server was folded intominicor; re-runningminicor-mcp-setupremoves the stale entry.
3. Bootstrap Workspace (optional)
After cloning a workspace, generate harness-specific config files:
# For Claude Code - creates CLAUDE.md + .claude/settings.local.json
npx -y -p @minicor/mcp-server minicor-bootstrap claude-code --dir ./my-workspace
# For Cursor - creates .cursor/rules/workspace.mdc
npx -y -p @minicor/mcp-server minicor-bootstrap cursor --dir ./my-workspace
# All harnesses at once
npx -y -p @minicor/mcp-server minicor-bootstrap all --dir ./my-workspace4. Update
npx users: restart your editor (auto-fetches latest). Global: npm update -g @minicor/mcp-server.
5. Uninstall
Remove the MCP server entry from your editor first:
Cursor - edit ~/.cursor/mcp.json and remove mcpServers.minicor, plus any legacy mcpServers.laminar or mcpServers.minicor-builder entry.
Claude Code - remove the registered server:
claude mcp remove minicorIf the command is unavailable, remove the minicor MCP server entry from your Claude Code MCP config.
Then remove local credentials and config:
rm -rf ~/.minicorIf this machine has an older Laminar install and you no longer need it:
rm -rf ~/.laminarIf you installed the package globally instead of using npx:
npm uninstall -g @minicor/mcp-serverWorkspace clones are not removed by these commands.
Code Mode (experimental, opt-in)
By default the server exposes every Minicor operation as its own MCP tool. As an experimental alternative, you can enable a code-mode surface: a single codemode tool that runs JavaScript and provides Minicor methods as async functions on the minicor global.
Enable it by setting the environment variable before launching the server:
EXPERIMENTAL_USE_CODE_MODE=1 minicor-mcpIn Cursor, add it to the server's env block in ~/.cursor/mcp.json:
{
"mcpServers": {
"minicor": {
"command": "npx",
"args": ["-y", "@minicor/mcp-server"],
"env": { "EXPERIMENTAL_USE_CODE_MODE": "1" }
}
}
}Once enabled:
const workspaces = await minicor.list_workspaces({});
const matches = await codemode.search("workflow");
const docs = await codemode.describe("minicor.create_rpa_flow");
return { workspaces, matches, docs };This follows Cloudflare's Code Mode pattern: the model writes code that chains tool calls, branches, loops, and composes results locally instead of making one model round trip per operation.
Inside codemode:
await minicor.tool_name({ ... })calls a Minicor MCP operation.await codemode.search("query")discovers relevant methods.await codemode.describe("minicor.tool_name")returns TypeScript-style parameter docs.await codemode.list()lists all available methods.
Unset EXPERIMENTAL_USE_CODE_MODE to return to the default one-tool-per-operation surface.
Session Lifecycle
The MCP server sends instructions to every client on connect, telling agents to start a session first when in a workspace clone. Call the session_start tool with the workspace directory:
session_start({ directory: "/path/to/workspace" })(With experimental code mode enabled, run the same call as await minicor.session_start({ ... }) through the codemode tool.)
This works across all harnesses, including Claude Code, Cursor, and Bedrock.
Session Tools
| Tool | What it does |
| --- | --- |
| session_start | Load workspace context (AGENTS.md, VMs, skills, issues, prior session). Call FIRST in a workspace clone. |
| session_checkpoint | Save progress mid-session. Writes .minicor/session.json + syncs [Workspace:Session] issue. |
| session_end | Persist final state, sync to platform, optionally update context.md. Call before ending work. |
| set_workspaces_root | Set default root directory for cloning workspaces (saved to ~/.minicor/config.json). |
Auto-Save on Disconnect
If the agent crashes or the user closes the IDE, the server auto-checkpoints with status: "interrupted" so no session state is lost. The next agent resumes from that checkpoint.
Multi-Agent Handoff
Sessions are synced to [Workspace:Session] issues on the platform, so agents on other machines (or scheduled Bedrock agents) can resume where the last agent left off.
The RPA Lifecycle
This is the complete flow from zero to production automation. The agent handles each phase using MCP tools and skills. Workflows are built and tested inside a Job (see Jobs below): the job's test cases are written before the workflow exists and are the definition of done for the whole build. Customers teach a job by sending sample API calls (JSON inputs); samples group into scenarios — named buckets for the distinct paths a job can take. Scenarios are first-class on the service (a Scenario entity with a slug unique per route, managed via list_scenarios / create_scenario / update_scenario / delete_scenario); the legacy scenario:<slug> tag on test cases is dual-written as a compatibility layer. One sample per case; cases in a scenario share identical assertions.
Phase 1: Workspace Setup
"Create a workspace for Acme Corp and set up a VM"create_workspace/list_workspaces/get_workspace- create or find a workspacevm_list- discover available VMsdeploy_vm- provision a new Windows VM if neededinstall_mds_on_vm/start_mds_on_vm- install the Minicor Desktop Servicevm_connect- connect to the VM via Cloudflare tunnel
Phase 2: Clone Workspace
"Clone workspace 257 to my local folder"clone_workspace- pulls everything to a local folder:minicor.json- workspace manifestworkflows/- all workflow steps as files (git-native).minicor/skills/general/- bundled RPA skills.minicor/skills/workspace/- workspace-specific learned skills.minicor/issues/- agent-persisted context from previous runs.minicor/skill-index.json- compact skill summary.minicor/workspace.json- workspace metadata
The folder is git-ready. Push to GitHub for version control, diffs, and CI/CD.
Phase 3: Scope & Define the Green-Gate
"I want to automate patient lookup in Centricity"The agent:
- Takes screenshots, inspects UI elements, identifies the app framework
- Loads relevant skills:
get_skill("job-build-loop"), thenget_skill("cdp-browser-automation")orget_skill("desktop-uiautomation") - Checks
list_skills()for customer-specific skills (e.g., "centricity-quirks") - Studies existing workflows via
get_workflow_overview - Determines strategy: CDP browser, desktop uiautomation, or hybrid
- Writes the test cases before building.
register_job(a thin job whose workflow step will call the new workflow) plusteach_joboradd_test_casewith a real input and assertions. Group each case into a scenario (scenarioId, named after the path/outcome it exercises —create_scenariofirst, or let ascenario:<slug>tag / execution-path auto-grouping create one). These cases start red; the rest of the lifecycle exists to turn them green.
Phase 4: Build (Iterative)
For each automation step:
- Observe -
vm_screenshot+vm_inspect_uito map the UI - Write - Python script using patterns from loaded skills
- Prototype -
vm_execute_scriptto test on the VM directly - Save -
create_rpa_flowto persist as a Minicor workflow step - Test through Minicor -
execute_workflow_asyncwithstart_from_step/end_at_stepto verify the step works through the real executor (config variables, data passing, JS wrapper)
The testing step is critical - vm_execute_script doesn't resolve {{config.*}} variables or data.input interpolation. The agent loads get_skill("rpa-testing-workflow") for exact tool call sequences.
Phase 5: Test End-to-End, Then Green-Gate the Job
After all steps pass individually:
execute_workflow_asyncwith real inputs +configurationId- full workflow, no step isolation- Poll
get_execution_status+vm_screenshotto monitor diagnose_executionon failure, fix withupdate_flow, re-run- The workflow is not done until it passes end-to-end through the Minicor executor
Then close the loop at the job level:
- Point the job's workflow step at the finished
workflowId(update_job variant=draft). If the workflow was edited in Dev Mode,dev_mode_publishfirst - jobs execute published workflows, not drafts. run_tests variant=draft- the test cases from Phase 3 are the pass/fail verdict for the build. Read results per-scenario (the report'sscenariosgrouping carries prebuilt summaries): a scenario passes only when all of its samples pass- On a red case,
resolve_job_state/inspect_job_executionlocate the failing step; replay just that slice withrun_job(fromStepId+seedExecutionId) or drop back into the workflow, fix, re-run - When the suite is green,
publish_jobputs it live at/m/:slug/:path
Phase 6: Production Hardening
For production workflows (not POCs):
- Load
get_skill("state-verification")- addexpectedPreState/expectedPostStateto each step - Run the workflow twice to verify idempotency
create_agentwithmode: "monitor"+watchWorkflowIdfor failure monitoringcreate_agentwithmode: "scheduled"+ cron for recurring runs- Persist context:
create_issuewith[Agent:<name>] Rulesfor cross-run learning
Phase 7: Extract & Share Knowledge
After a successful build:
generate_skill- gathers workflow code, execution history, and agent issues- The agent analyzes patterns and calls
save_skillto persist them - Skills are stored per-workspace and available to future builds
- Customer-specific quirks become reusable knowledge
Blueprints (the spec front door)
A Blueprint is the living spec + context library that owns a job. Instead of hand-writing a job definition, you feed the blueprint's library (SOPs, Loom videos, notes, transcripts, VM walkthrough sessions), let the service synthesize a spec proposal, review and apply it, then blueprint_build syncs the spec into a Job and hands the jobs toolset its coordinates (middlewareId/routeId/buildId). One ownership rule: when a route is blueprint-linked, never register_job / update_job / add_test_case on it directly - that trips reverse-drift detection (state job_ahead). Drive changes through the spec. Sanctioned escape hatch for intentional direct edits (e.g. bulk job edits): pass acknowledgeJobAhead: true to update_job / add_test_case; the tool proceeds and the response carries a job_ahead warning with the fold-back options (blueprint_import_cases, or blueprint_build which overwrites the direct edits).
The blueprint tools
| Tool | What it does |
| --- | --- |
| resolve_blueprint_state | The first call to make when working a blueprint (the sibling of resolve_job_state, one layer up). One call fetches the blueprint (state + drift + proposal + open questions rendered as [blocks_build\|advisory] <kind>: <question>, plus blockingQuestions - only those gate needs_answers), the library's enrichment status, and the linked build run, then returns an unfolded state (no_context / enriching / no_spec / proposal_pending / needs_answers / building / needs_input / build_failed / job_ahead / spec_stale / job_stale / in_sync - every state maps to one verb), blockers[] (enrichment failures, a stalled builder, questions needing a human), and ordered nextActions with the concrete tool calls prefilled. |
| blueprint_list | List a workspace's blueprints: id, name, computed state, drift flags, and the linked job coordinates. For any blueprint not in_sync, call resolve_blueprint_state on it and follow nextActions. |
| blueprint_create | Create a blueprint. Two entry paths: create empty and feed the library (new automation), or pass routeId + middlewareId to link an existing job (backfill) - blueprint_import_cases then adopts its test cases into the spec. |
| blueprint_get | Get a blueprint: the full spec document, computed state, both drift summaries, and the inbox (document.openQuestions + pendingProposal). headVersion here is the baseVersion for blueprint_update's CAS. |
| blueprint_update | Edit the spec directly: document replaces wholesale, patch is a section-level merge. Document edits require baseVersion (compare-and-swap: a 409 means the blueprint moved past your read - re-read and re-author, never retry blindly). Prefer the synthesize -> apply path for library-derived changes; direct edits are for surgical fixes. |
| blueprint_add_entry | Add (or, with entryId, update) a library entry. Context kinds (doc/video/url/note/transcript/data-source/walkthrough) feed synthesis; resource kinds (screen-map/anchor/screenshot) are runtime-referenceable as entry://<id>#<fragment>. Enrichable kinds start pending and are enriched to text by a worker. Every write bumps librarySeq, so the spec goes spec_stale until the next synthesize. |
| blueprint_list_entries | List a blueprint's library entries with enrichment status (pending/processing means the worker is still on it; failed carries the reason), or one entry via entryId (revisions: true adds its immutable history, downloadUrl: true adds a signed read URL for the uploaded blob). |
| blueprint_entry_upload_url | Get a short-lived signed upload URL for a file that will become a library entry: returns { uploadUrl, blobRef, expiresOn }; PUT the bytes (plain PUT with the file's Content-Type - works from inside a VM via vm_execute_script), then blueprint_add_entry with the returned gs:// blobRef. |
| blueprint_synthesize | Synthesize a spec proposal from the library (LLM). Produces ONE pending proposal for review - never applied silently. Answer all open questions first so one synthesis folds every answer in. Blocked responses are structured: proposal_pending, entries_enriching, llm_unconfigured, library_empty, plus retryable LLM-leg failures. |
| blueprint_get_proposal | Read the pending proposal for review: the section-level patch, regenerated SOP, summary, and per-claim citations (provenance extracted/inferred/ambiguous - scrutinize the latter two). Returns the exact apply coordinates (baseVersion + basedOnSeq). |
| blueprint_apply_proposal | Apply the reviewed proposal as one validated, version-snapshotted document update. Requires the reviewed proposal's baseVersion + proposalBasedOnSeq: the service atomically refuses (409 proposal_mismatch) if a concurrent resynthesize swapped the proposal after your review. After a successful apply the job is stale - blueprint_build is the next verb. |
| blueprint_reject_proposal | Discard the pending proposal (bad synthesis, or the spec moved underneath it). A 404 is success-shaped: someone else already applied or rejected it. Follow with blueprint_synthesize, optionally with steering instructions. |
| blueprint_answer_question | Answer ONE open synthesis question atomically: removed from openQuestions, stored as a note entry, version snapshotted. question is the exact question TEXT (the question field of a typed {question, kind, severity} entry, or the legacy string) - not the [severity] kind: label the tools render. Only blocks_build questions gate blueprint_build (legacy strings count as advisory). Answer ALL blocking questions, then synthesize ONCE - each answer bumps librarySeq, so never loop answer -> synthesize per question. Builder questions (needs_input) go to answer_build_question instead. |
| blueprint_build | Sync the spec into its Job (the primary build verb): validates the spec, ensures the router + route exist, upserts judge test cases from exampleCases (content-hash idempotent), attaches spec + library as the builder artifact, and queues a build run. Returns the handoff coordinates { middlewareId, routeId, buildId, testCaseIds } the jobs toolset takes over with. |
| blueprint_build_status | Blueprint-side view of the linked build run (status queued/running/green/failed/needs_input, summary, open questions). needs_input questions come back with answer_build_question coordinates prefilled - try to answer them from the library first. |
| blueprint_import_cases | Adopt the linked job's test cases into the spec's exampleCases (upsert by name) - the job_ahead fold-back arrow. The alternative exit is blueprint_build alone, which rebuilds from the spec and overwrites the direct edits. |
| blueprint_list_versions | List the spec's version snapshots (newest first) - every document change snapshots one, with its note. Pass version to fetch one full snapshot (diff a proposal, or recover a document after a CAS 409). |
The blueprint loop - context entries -> synthesize -> review -> apply -> build
The blueprint loop sits in front of the job build loop below. When in doubt about a blueprint, call resolve_blueprint_state - it returns the concrete next tool calls, exactly like resolve_job_state does for jobs.
1. FEED THE LIBRARY - blueprint_add_entry (SOPs, Looms, notes, transcripts, VM
walkthroughs). Files: blueprint_entry_upload_url -> PUT the
bytes -> blueprint_add_entry with the gs:// blobRef.
Enrichable entries start pending - poll blueprint_list_entries.
2. SYNTHESIZE - blueprint_synthesize drafts ONE pending spec proposal (LLM).
Never applied silently.
3. REVIEW + ANSWER - blueprint_get_proposal (patch + SOP + per-claim citations).
Answer open questions with blueprint_answer_question - ALL
blocks_build ones (advisory optional), then ONE resynthesize.
4. APPLY - blueprint_apply_proposal with the reviewed proposal's
baseVersion + proposalBasedOnSeq (409 proposal_mismatch if a
concurrent resynthesize swapped it).
5. BUILD - blueprint_build syncs the spec into the Job: router + route +
judge test cases + build run. Returns {middlewareId, routeId,
buildId} - the jobs loop (resolve_job_state) takes over.Two drift directions keep the spec and the job honest: spec_stale (the library moved past the spec - synthesize) and job_stale (the spec moved past the job - build). job_ahead means the job was edited directly behind the spec's back; exit with blueprint_import_cases (fold the edits back) or blueprint_build (overwrite them). To teach a blueprint from a live VM session, load get_skill("blueprint-vm-walkthrough").
Jobs (RPA orchestration)
A Job is a middleware route whose behavior is a structured definition (a step graph) instead of freeform handler code. Jobs sit above workflows and can contain native or legacy workflow calls, storage operations, code, and registered durable executor steps. A job is the macro zoom (what ran, status, replays); a workflow → step is the microscope. This is the layer that turns one or more workflows into a versioned, testable, observable API endpoint. When a route is owned by a Blueprint (see above), drive changes through the spec, not the direct job tools.
The job tools
| Tool | What it does |
| --- | --- |
| resolve_job_state | The first call to make when picking up a job. Returns the job's state (draft vs published, test cases, latest test run, recent executions, active build) plus nextActions - the concrete next tool calls. A red test run is drilled automatically: failing case → failing step → its workflowId (a dev_mode_load hint) → a seeded fromStepId replay command. Blueprint-owned routes come back with a blueprint block: edit those through the spec (resolve_blueprint_state), not update_job/add_test_case - or pass acknowledgeJobAhead: true to those tools for a sanctioned direct edit that returns a job_ahead warning with fold-back options. |
| list_configuration_references | List safe workspace configuration metadata: native configurationId, exact case-sensitive externalId, name, and property names. Values, credentials, citation IDs, and alias fallbacks are never returned. |
| validate_job_draft | Validate the persisted draft for phase: "framing", or phase: "populating" with one positive safe-integer workflowId (forbidden for framing), without execution. Population evidence also pins phase and workflow ID. |
| list_middlewares | List the routers (middlewares) in a workspace - jobs attach to one of these. |
| register_middleware | Create a router jobs attach to. autoConfigure (default true) populates the router's workspaceApiKey from the workspace's first key; or pass workspaceApiKey explicitly. |
| register_job | Create a job - a route backed by a structured definition (step graph). |
| update_job | Update a job's definition, path, method, or description. variant: "draft" writes the draft definition; expectedDraftRevision protects repair writes from overwriting a newer draft. variant: "published" (default) edits the live definition and snapshots a new route version. On a blueprint-linked route, pass acknowledgeJobAhead: true for an intentional direct edit. |
| get_job | Get a job (route) incl. its definition (and draftDefinition when one exists); omit routeId to list all routes on the router. |
| run_job | Run a job once with an input, poll to completion, return the full JobExecution (per-step Minicor deep-links). variant: "draft" runs the draft definition. For step-range debugging, fromStepId / toStepId (both inclusive) run a slice of the step graph, and seedExecutionId hydrates ctx from a prior execution so mid-graph starts still resolve earlier steps' values. waitForResult: false returns the jobExecutionId immediately for runs you want to stop or monitor. webhookId publishes workflow progress and job.completed to a registered destination for that run. |
| list_job_webhooks | List reusable webhook destinations on a router (url, masked auth, workflowIds, includeJob, plus subscribed eventTypes). |
| get_job_webhook_events | Catalog of the five JSON payloads (workflow.started, workflow.step.started, workflow.step.completed, workflow.completed, job.completed) with examples. |
| register_job_webhook | Register a destination. HTTPS URL only. Subscribe with includeJob (job.completed, default true) and/or workflowIds (null = all workflows, [] = none, [ids] = allow-list). Pass the returned id to run_job. Response includes example payloads for the events this destination will receive. |
| get_job_webhook | Get one destination. Secrets are never returned. |
| update_job_webhook | Update url, auth, workflowIds, and/or includeJob. |
| delete_job_webhook | Delete a destination. |
| get_job_default_webhook / set_job_default_webhook | Get or set the inherited destination; webhookId: null clears it. Explicit run webhooks win; draft/test/teach runs do not inherit it. |
| stop_job_execution | Stop a queued/running execution. This is a cooperative cancel (there is no pause): a queued run cancels immediately; a running one stops at the runner's next checkpoint (between steps, between forEach items, or while waiting on a workflow). A workflow already running on the VM is not killed - it finishes on its own; the job just stops waiting. Idempotent for already-cancelled runs. |
| list_job_executions | List a job's executions (paginated summaries, newest first). Filter by status (queued/running/succeeded/partial/failed/cancelled) and/or trigger (test = suite runs; api,mcp,cron = live traffic). Use to find a prior run to seed a range run from, a running execution to stop, or the latest failure to inspect. |
| get_job_execution | Fetch one past execution's full trace: status, input, output, ctx, per-step results. |
| inspect_job_execution | Deep-drill an execution end to end: job → steps → (workflow steps) the underlying workflow execution's internal flow-runs, with RPA failure analysis on failures. |
| teach_job | Teach a job by example: an input + a natural-language prompt (+ optional expect output or refusal, and artifacts like Loom links or SOP text). Upserts an idempotent judge-graded test case, folds it into the job's active build run, and, when a draft definition exists, runs the draft now and returns its output + judge verdict. |
| publish_job | Promote a job's draft definition to published: snapshots a route version, copies draft to published, stamps publishedAt. Live traffic serves the new definition immediately. Run run_tests with variant: "draft" first. |
| get_build_status | Get a job's build runs (the teach loop's cycles): status queued/running/green/failed/needs_input, folded-in test cases, builder summary, open questions. Pass buildId for one run; omit to list recent runs. |
| answer_build_question | Answer a needs_input question on a build run (credentials choice, ambiguity, 2FA policy). When no open questions remain the build re-queues automatically. |
| cancel_build | Stop a queued/running autonomous build run. The build flips to failed with a "stopped by user" summary; the job's draft definition is left as-is. Use it to take the loop over manually. |
| update_build_run | Builder-facing: update a build run's lifecycle (running when you start, green/failed with a summary when done, needs_input with questions when blocked). |
| add_test_case | Attach a test case to a job: named input + assertions (the green-gate). A test case is one sample API call; group it into a scenario with scenarioId (cases in a scenario share identical assertions; the legacy scenario:<slug> tag is dual-written). Assertions are discriminated on kind: expr (sandbox boolean, default), judge (an LLM grades the execution against a natural-language intent), status (equals a terminal status), exists / equals (RFC 6901 pointer into the output; equals deep-compares a typed value). On a blueprint-linked route, pass acknowledgeJobAhead: true for an intentional direct add; the response carries a job_ahead warning with fold-back options. |
| update_test_case | Update an existing test case: tighten assertions as behavior firms up, move a case into a different scenario (scenarioId; null ungroups), or set enabled: false to disable a case without deleting its history. |
| list_scenarios | List a job's scenarios, each with its member test cases embedded. Auto-created scenarios (from execution-path grouping) carry a pathFingerprint — rename them to describe the behavior. |
| create_scenario | Create a scenario (name + optional slug/description; slug unique per route). Name it after the path/outcome, not the input. |
| update_scenario | Rename a scenario (name/slug/description). A slug change rewrites the legacy scenario:<slug> tag on member cases. |
| delete_scenario | Delete a scenario. Member cases are kept but ungrouped (scenarioId null, legacy tag stripped). |
| run_tests | Run a job's test suite (or a subset), poll to completion, return the report (green, totals, per-case, plus a per-scenario scenarios grouping with prebuilt summaries). variant: "draft" runs against the draft definition. A scenario passes only when all of its sample cases pass; after every run the service auto-groups ungrouped cases by execution path. |
| get_test_report | Fetch a test run report by id (totals + per-case results, each linking to its job execution, plus the per-scenario scenarios grouping). |
| list_workspace_api_keys | List a workspace's API keys (id, apiKey, name) - to find a key for the two tools below. |
| set_middleware_api_key | Set a router's workspaceApiKey after the fact, then re-read to confirm it stuck. |
Triage (recurring production failures)
Live failures on published jobs cluster into one row per root cause per job. Read a job's open clusters BEFORE teaching, rebuilding, or debugging it; resolve them AFTER a fix ships (a resolved cluster reopens itself on recurrence).
| Tool | Description |
| ---- | ----------- |
| triage_clusters | List a workspace's triage clusters with occurrence counts and labeler verdicts. Filters: status (open means open work and includes reopened), jobId (routeId), category (vm-infra / payload-contract / workflow-logic / platform). Rows carry blueprintId when the job is blueprint-linked. |
| triage_cluster | One cluster in full: label (title, probable cause, suggested action, severity), masked error sample, failing step, and the newest sample execution ids to drill with inspect_job_execution. |
| triage_disposition | ack (triaged), mute (known noise), or resolve (cause fixed). Resolve after your fix ships; recurrence reopens the cluster automatically. |
The build loop - test-case-first, macroscope → microscope
You work backwards from the job's test cases. The job's test suite is the macro green-gate; individual workflows are the micro RPA underneath. This mirrors the platform's Jobs UI (Overview = macroscope, Build = microscope). The full playbook ships as the job-build-loop skill - load it with get_skill("job-build-loop").
1. GREEN-GATE FIRST - teach_job (input + intent) or add_test_case (input + assertions).
A job with no test cases has no definition of correct. Cases start RED.
Group cases into scenarios (first-class: list_scenarios /
add_test_case scenarioId; the legacy scenario:<slug> tag is
dual-written) — one sample input per case, identical assertions
across a scenario's cases.
2. BUILD THE DRAFT - register_job / update_job variant=draft. Job steps call PUBLISHED
Minicor workflows (by workflowId) or run code blocks.
3. RUN THE GATE - run_tests variant=draft. Green -> publish_job. Red -> step 4.
4. LOCATE (macro) - resolve_job_state tells you where you are and what to do next;
get_test_report + inspect_job_execution drill a failing case down to
the failing job step and its workflow's internal flow-runs.
5. FIX AT THE RIGHT LEVEL:
5a. Job-level - update_job variant=draft, then replay JUST the failing slice:
run_job variant=draft fromStepId=<failing step>
seedExecutionId=<failed execution> (seeds ctx so mid-graph starts
resolve earlier steps' values).
5b. Workflow-level (microscope) - the failing step's workflowId -> dev_mode_load,
fix/run the draft's flow steps (dev_mode_run needs vmId;
pass targetUser to pin the user, or null for standalone),
dev_mode_publish - jobs execute PUBLISHED workflows.
6. PUBLISH - when run_tests variant=draft is green, publish_job promotes the draft;
runtime traffic at /m/:slug serves it immediately.A job step supports saveAs, when (conditional skip), forEach (fan-out), onError (abort/skip/continue/{goto} - goto currently treated as abort), retry, and cache (a login/smart-launch gate). Use resource.lockKey to serialize execution per desktop/VM. To migrate an existing NestJS customer middleware onto this layer, use the nestjs-middleware-to-job prompt or skill.
Execution control - stop, ranges, and traffic lenses
- Stopping: there is no pause/resume.
stop_job_executionis a cooperative cancel: a queued run cancels immediately; a running one stops at the runner's next checkpoint (between steps, betweenforEachitems, or while waiting on a workflow). A workflow already running on a VM is not killed - it finishes on its own; the job just stops waiting and starts no new steps. - Long or risky runs:
run_jobwithwaitForResult: falsereturns thejobExecutionIdimmediately - poll it withget_job_execution, stop it withstop_job_execution. - Progress webhooks: destinations live on the router (
register_job_webhook/list_job_webhooks).set_job_default_webhookattaches one to every published api/mcp/cron run of a job; an explicitrun_job.webhookIdwins, and draft/test/teach runs do not inherit it.webhookId: nullclears the default.workflowIds: nullmeans every workflow the router runs;[]means none; a list is an allow-list.includeJob(default true on create) addsjob.completed. There is noworkflow.step.failed- a failed flow isworkflow.step.completedwithworkflowStep.status: "FAILED". Callget_job_webhook_events(orget_skill("job-webhooks")) for the shared envelope and a full example of each payload. - Step-range replays:
run_jobwithfromStepId/toStepIdruns only a slice of the step graph (both inclusive, by step id). For any mid-graph start, passseedExecutionId- it hydratesctxfrom that prior execution so steps you aren't re-running still have theirctx.*values. The job'soutputblock only runs when the range covers the last step. - Finding runs:
list_job_executionsreturns paginated summaries withstatusandtriggerfilters -trigger: "test"isolates suite runs,trigger: "api,mcp,cron"isolates live traffic (the same split as the platform's Development/Production lenses). - Autonomous builds: watch with
get_build_status, unblock withanswer_build_question, take over withcancel_build. Don't edit the draft while a build is running - coordinate or cancel first.
Teach-by-example: draft vs published
Every job has two definition variants:
- draft: the teach/build loop's working copy. Iterate here freely; live traffic never sees it.
- published: what runtime traffic at
/m/:slug/:pathserves.publish_jobpromotes draft to published (snapshots a route version, stampspublishedAt).
update_job, run_job, and run_tests all take variant: "draft" | "published" (default published). The teach flow, from the agent's perspective:
1. ACCRUE CASES: teach_job upserts judge-graded test cases from examples
(input + prompt + optional expected output / refusal + artifacts).
When a draft exists, it also runs the draft immediately and
returns the output + judge verdict — and the service auto-groups
the case into a scenario by the execution path the draft took.
Review with list_scenarios; rename auto-created scenarios
(update_scenario) or move a case (update_test_case scenarioId).
2. BUILD DRAFT: update_job variant=draft to iterate on the step graph.
3. VERIFY: run_tests variant=draft until green: true.
4. SHIP: publish_job promotes draft to published; live traffic serves it.Build runs track each teach cycle: get_build_status shows the run's status (queued/running/green/failed/needs_input), folded-in cases, and open questions. Answer blockers with answer_build_question (the build re-queues when no questions remain). A builder agent working the loop reports its own progress with update_build_run.
ctx / job-shape primer
The definition you pass to register_job (see register_job's definition param for the full schema):
{
region?: "us" | "ca", // optional region hint
resource?: { configurationId?, configStoreId?, lockKey? }, // native UUID or exact legacy alias; lockKey serializes runs
inputSchema?: JSONSchema,
outputSchema?: JSONSchema,
steps: [ { id, name, kind, saveAs?, when?, forEach?, onError?, retry?, cache?,
// kind:"native-workflow" → workflow: { workflowDraftId | workflowId | workflowVersionId }
// kind:"workflow" → legacy workflowId; storage uses operation; durable kinds retain their fields
} ],
output?: "(ctx) => response", // builds the job's response from the final ctx
}ctx is the data through-line between steps - each step's saveAs key is what the next step reads:
ctxstarts as{ input: <the job input> }.- Each step's result is stored at
ctx.<saveAs>(whensaveAsis set), so later steps /when/forEach/outputcan read it. - Inside a
forEachstep, the current item isctx.item(results are collected into an array undersaveAs). - The top-level
outputblock(ctx) => ...builds the job's response from the accumulatedctx.
Assertions (add_test_case) are discriminated on kind: expr (default; TS booleans over { input, output, ctx, steps }, e.g. output.status === "COMPLETED"), judge (an LLM grades the execution against a natural-language intent; expected refusals pass), status (equals one of succeeded/partial/failed/cancelled), exists (RFC 6901 pointer resolves in the output) and equals (output at pointer deep-equals a typed value — 291009, not "291009"). The full model + runner semantics live in the middleware-service repo's README.md ("## Jobs") and middlewares/JOBS-DESIGN.md.
Region & API key
Jobs are region-aware (us / ca) - pass region or let it be auto-detected from the workspace. Running a job requires the router's workspaceApiKey to be set so a job's workflow steps can call the Laminar API: it's populated by register_middleware's autoConfigure (default), or set later with set_middleware_api_key (find a key via list_workspace_api_keys). register_middleware / set_middleware_api_key report workspaceApiKeyConfigured so you can confirm it stuck.
Skills System
Skills are reusable RPA patterns that accumulate as you build automations. The MCP ships with bundled general skills, including:
| Skill | Purpose |
| ---------------------------- | --------------------------------------------------------------------------------- |
| job-build-loop | Mandatory for job work. The test-case-first build/debug loop: green-gate first, macroscope (job) → microscope (workflow steps), seeded range replays, stop semantics, publish gates |
| blueprint-vm-walkthrough | Guided VM walkthrough that teaches a blueprint: the user narrates a process on a live VM while you capture screens, ground UI anchors, and persist walkthrough + screenshot + screen-map entries |
| rpa-testing-workflow | Mandatory. Exact tool call sequences for testing through the Minicor executor |
| cdp-browser-automation | CDP starter template, React-safe setters, parallel execution |
| desktop-uiautomation | Framework selection, element selectors, wait/retry patterns |
| data-extraction-strategies | Priority-ordered methods for reading data from screen |
| data-passing-between-steps | JS-layer interpolation, config variables, step outputs |
| state-verification | LLM-based UI verification for production workflows |
| nestjs-middleware-to-job | Map a NestJS middleware controller onto the Jobs layer (step graph + test cases) |
Using Skills
list_skills() → see all available skills
get_skill("cdp-browser-automation") → load full patterns + code templates
save_skill(...) → persist a new skill from a successful build
generate_skill(workflowId) → extract patterns from an existing workflowSkills Service
Skills are automatically persisted through the Minicor platform API. When authenticated, save_skill and generate_skill store skills server-side so they persist across sessions and are available to other agents in the same workspace.
Skill tiers:
- Global - general RPA knowledge (read-only, bundled with the MCP)
- Per-workspace - learned from agent sessions, persisted across runs
- Customer-specific - app quirks, environment patterns
Without authentication, skills fall back to the bundled .md files in the package.
What You Can Automate
- Web portals - Chrome via CDP. React/Angular SPAs, anti-bot sites, payor portals, EHR web apps.
- Desktop applications - Windows apps via uiautomation, pywinauto, pyautogui. EMR clients, billing software, legacy systems.
- APIs - Direct HTTP when the agent discovers usable endpoints behind a portal.
- Hybrid workflows - Mix browser, desktop, and API steps in one workflow.
VM Setup
The easiest path is to let the agent handle everything:
"Deploy a VM and set it up for automation"The agent runs: deploy_vm → install_mds_on_vm → start_mds_on_vm → vm_connect
Once the VM is provisioned, a secure tunnel is automatically created. The agent connects via the tunnel URL and is ready to build automations. Chrome instances for browser automation are managed automatically by the MDS - no manual setup needed.
For manual setup, use get_lds_setup_guide for step-by-step instructions.
Tools
VM Tools
| Tool | What it does |
| ---------------------- | ------------------------------------------------------------------------------------------ |
| vm_list | Discover available VMs (returns ID, name, status, tunnel URL) |
| vm_connect | Connect to VM via LDS Cloudflare tunnel URL or VM ID; discovers the MDS mode (standalone or multi-session broker) |
| vm_disconnect | Disconnect from VM |
| vm_status | Health check for the connected VM, including its live MDS mode |
| vm_screenshot | Full-screen capture of the VM desktop (targetUser selects the session on broker VMs) |
| vm_screenshot_region | Zoom into a specific screen area |
| vm_execute_script | Run a Python script on the VM (targetUser required on broker VMs) |
| vm_execution_status | Check status of a running script (targetUser/executionId read one user's live progress on broker VMs) |
| vm_inspect_ui | Inspect Windows UI elements (window list, element tree, element at point, focused element) |
| vm_read_clipboard | Read VM clipboard contents |
| vm_list_sessions | Broker VMs: list registered users with session/agent state (pick a targetUser from here) |
| vm_start_session | Broker VMs: start (or reconnect) a registered user's session |
| vm_stop_session | Broker VMs: disconnect a user's session (stays logged in, goes dark) |
| vm_get_mds_config | Read a VM's MDS multi-session config + observed state (robot users, keeper, resolution) — workspace OWNER/ADMIN only |
| vm_update_mds_config | Replace the robot-user catalog / keeper / resolution; saving queues the broker restart that applies it — OWNER/ADMIN only |
| vm_service_control | start/stop/restart/status/redeploy MDS via the VM service; on broker VMs restart queues a broker-only restart (agents keep running) |
On multi-session (broker) VMs every execute/screenshot call must name a targetUser — MDS never picks a user itself. Missing targets come back as the typed target_user_required error listing the registered users. Standalone VMs reject targetUser-bearing requests (not_supported_on_standalone), so omit it there — tools only send the field when you pass it.
deploy_vm can seed the multi-session robot-user catalog at creation (mdsRegisteredUsers, mdsKeeperEnabled, rdpWidth/rdpHeight); raw passwords are exchanged for Secret Manager references by the VM service and never stored. vm_connect resolves credentials through the per-VM credentials endpoint automatically when the VM list response is redacted.
Automation Building
| Tool | What it does |
| ----------------------- | --------------------------------------------------------- |
| create_rpa_flow | Save a validated Python script as a Minicor workflow step (forBroker references the minicor_desktop_service_target_user config placeholder for multi-session VMs) |
| debug_rpa_step | Test a script with before/after screenshots |
| vm_reset_state | Manage windows: focus app, minimize all, close dialogs |
| batch_test_rpa | Run a workflow with multiple test inputs |
| replay_execution_step | Re-run a step from a failed execution on the VM |
| get_lds_setup_guide | LDS installation walkthrough |
Workflows and Executions
| Tool | What it does |
| ---------------------------------------------------------- | --------------------------------------------------- |
| create_workflow / update_workflow / delete_workflow | Workflow CRUD |
| create_flow / update_flow / delete_flow | Step CRUD |
| execute_workflow / execute_workflow_async | Run workflows synchronously or async |
| list_executions / get_execution / get_full_execution | View execution history and details |
| diagnose_execution | Analyze failures with RPA-specific pattern matching |
| get_workflow_overview | Full snapshot: steps, code, recent executions |
| test_workflow_step | Run a single step in isolation |
| preview_flow_changes / compare_flow_versions | Diff code before deploying |
Blueprints
| Tool | What it does |
| ----------------------------- | ---------------------------------------------------------------------------------------------- |
| resolve_blueprint_state | "Where am I in the blueprint loop?" - unfolded state + blockers + nextActions |
| blueprint_list | List a workspace's blueprints with computed state, drift flags, and linked job coordinates |
| blueprint_create | Create a blueprint (empty, or linked to an existing job via routeId + middlewareId) |
| blueprint_get | Get the spec document, computed state, drift, and inbox (openQuestions + pendingProposal) |
| blueprint_update | Direct spec edit (document or section-level patch); document edits require baseVersion CAS |
| blueprint_add_entry | Add or patch a library entry (context kinds feed synthesis; resource kinds are entry:// refs) |
| blueprint_list_entries | List entries with enrichment status; entryId + revisions/downloadUrl for one entry |
| blueprint_entry_upload_url | Signed upload handshake: { uploadUrl, blobRef } -> PUT the bytes -> blueprint_add_entry with the gs:// ref |
| blueprint_synthesize | LLM-draft a spec proposal from the library (one pending proposal; structured blocked reasons) |
| blueprint_get_proposal | Read the pending proposal + per-claim citations; returns the exact apply coordinates |
| blueprint_apply_proposal | Apply the reviewed proposal (baseVersion + proposalBasedOnSeq; 409 proposal_mismatch if swapped) |
| blueprint_reject_proposal | Discard the pending proposal; a 404 means someone already resolved it (success-shaped) |
| blueprint_answer_question | Atomically answer one open question (stored as a note entry; then synthesize ONCE) |
| blueprint_build | Sync spec -> Job; returns { middlewareId, routeId, buildId } for the jobs toolset |
| blueprint_build_status | Blueprint-side view of the linked build run (status, summary, open questions) |
| blueprint_import_cases | Fold the linked job's test cases back into the spec's exampleCases (the job_ahead exit) |
| blueprint_list_versions | List spec version snapshots; pass version for one full snapshot |
See the Blueprints (the spec front door) section above for the loop and the ownership rule.
App Map (per-blueprint screen memory)
One arctic-vault document per blueprint (kind appmap): the app's named screen states (detect predicates incl. ocr, reachedBy routes, elements incl. verified OCR regions, blockers, screens, screenshot refs), login/teardown workflow refs, and the gotchas ledger. Written by the Mapper/Fixer, read by the Author/Verifier; the second build of the same app maps nothing. version is the vault revision (CAS on write). Served by middleware-service's blueprint sub-resource (/api/workspaces/:ws/blueprints/:id/appmap…) with the user's bearer — no vault wiring on the MCP side; authorization is "may read this blueprint". The tool schemas mirror middleware's appmap.schema.ts field for field; backend failures (4xx/5xx other than the structured 409/404 blocks) come back with isError: true.
| Tool | What it does |
| ----------------------------- | ---------------------------------------------------------------------------------------------- |
| appmap_get | Read the map (optionally sliced to states, or a historical version); absent when unmapped |
| appmap_upsert_state | Write one state (merge or replace; creates the map with app on first write); CAS on baseVersion, rev_conflict block on a race, tombstoned block (revive with baseVersion=currentVersion; content is at appmap_history's lastLiveVersion) after a delete |
| appmap_find_element | Resolve an element ref in a state to its locator; misses list the states/refs that DO exist |
| appmap_history | The map's version ledger (version, reason, who, when, tombstoned), newest first, plus lastLiveVersion — a delete is itself a revision, so recover a deleted map from the last live one |
Jobs & Test Cases
| Tool | What it does |
| --------------------- | -------------------------------------------------------------------------------------------- |
| resolve_job_state | "Where am I in the build loop?" - job state + nextActions; flags blueprint-owned routes |
| list_configuration_references | List safe native/alias reference metadata and property names |
| validate_job_draft | Validate persisted framing or populating (requires workflowId) and return scoped evidence |
| list_middlewares | List the routers (middlewares) in a workspace - jobs attach to one of these |
| register_middleware | Create a router (middleware) jobs attach to; autoConfigure populates its workspace API key |
| register_job | Create a job - a middleware route backed by a structured definition (step graph) |
| update_job | Update a job; repair writes can require expectedDraftRevision |
| get_job | Get a job (route) incl. its definition; omit routeId to list all routes on the router |
| run_job | Run a job once, poll to completion; variant: "draft" runs the draft. executionTarget binds a full run or revision-pinned unseeded draft range to one desktop. Step-range replays via fromStepId/toStepId + seedExecutionId; webhookId publishes progress events; waitForResult: false returns the execution id immediately |
| list_job_webhooks | List reusable webhook destinations on a router |
| get_job_webhook_events | Catalog of the five JSON payloads with examples |
| register_job_webhook| Register a destination (includeJob + workflowIds); pass its id to run_job |
| get_job_webhook | Get one destination (secrets never returned) |
| update_job_webhook | Update url, auth, workflowIds, and/or includeJob |
| delete_job_webhook | Delete a destination |
| get_job_default_webhook / set_job_default_webhook | Get/set the inherited destination; explicit run webhooks win and draft/test/teach do not inherit it |
| stop_job_execution | Cooperative cancel of a queued/running execution (no pause; VM workflows finish on their own) |
| list_job_executions | Paginated execution summaries with status/trigger filters (test vs live traffic) |
| get_job_execution | Fetch one execution's full trace (status, input, output, ctx, per-step results) |
| inspect_job_execution | Deep-drill: job → steps → workflow execution's internal flow-runs, with RPA failure analysis |
| teach_job | Teach by example: upsert a judge-graded test case from input + prompt; runs the draft if one exists |
| publish_job | Promote a job's draft definition to published (snapshots a version); live traffic serves it |
| get_build_status | Get a job's build runs: status, folded-in test cases, builder summary, open questions |
| answer_build_question | Answer a needs_input question on a build run; the build re-queues when none remain |
| cancel_build | Stop a queued/running autonomous build run (flips to failed; the draft is left as-is) |
| update_build_run | Builder-facing: set a build run's status (running/green/failed/needs_input) + summary |
| add_test_case | Attach a test case to a job: named input + expr/judge/status/exists/equals assertions (the green-gate) |
| update_test_case | Update a test case (tighten assertions, or enabled: false to disable without deleting) |
| run_tests | Run a job's test suite, optionally bind every sample to one desktop with executionTarget, poll to completion, and return the report; variant: "draft" tests the draft |
| get_test_report | Fetch a test run report by id (totals + per-case results, each linking to its job execution) |
| list_workspace_api_keys | List a workspace's API keys (id, apiKey, name) - find one for the two tools below |
| set_middleware_api_key | Set a router's workspaceApiKey (then re-reads to confirm it stuck) |
See the Jobs (RPA orchestration) section above for the build loop and job/ctx shape. batch_test_rpa can also persist each passing input as a real job test case - set persistAsTestCases with routeId + middlewareId + workspaceId.
2FA / OTP
| Tool | What it does |
| ----------------------- | ------------------------------------------------------------------------- |
| tfa_provision_phone | Buy a Twilio phone number for the workspace (SMS webhook auto-configured) |
| tfa_provision_email | Generate a Mailgun email address for OTP capture |
| tfa_list_channels | List all provisioned phone numbers and email addresses |
| tfa_delete_channel | Remove a channel (releases Twilio number if phone) |
| tfa_register_secret | Store a TOTP secret (base32 or otpauth:// URI), encrypted at rest |
| tfa_list_secrets | List stored TOTP secrets for the workspace |
| tfa_delete_secret | Remove a stored TOTP secret |
| tfa_generate_totp | Get the current 6-digit TOTP code + seconds until expiry |
| tfa_verify_totp | Verify a TOTP code against a stored secret |
| tfa_parse_qr | Parse a QR code image to extract TOTP parameters |
| tfa_request_sms_otp | Wait for an SMS OTP (blocks until SMS arrives or timeout) |
| tfa_request_email_otp | Wait for an email OTP (blocks until email arrives or timeout) |
| tfa_get_challenge | Check the status of a pending challenge |
| tfa_resolve_challenge | Manually resolve a challenge (for captchas or Slack-provided codes) |
| tfa_cancel_challenge | Cancel a pending challenge |
Workflows that encounter 2FA prompts use these tools to auto-resolve OTP challenges. Provision channels once per workspace, store the IDs in config stores, and reference them with {{config.sms_channel_id}} or {{config.totp_secret_id}} at runtime.
Configurations
| Tool | What it does |
| --------------------------------------------------- | --------------------------------------------------- |
| create_config_store | Create a configuration with key-value properties. Use the returned externalId with configuration tools; native configurations also return a UUID id for execution. |
| list_config_stores / get_config_store | Browse stores |
| update_config_property / remove_config_property | Manage individual properties |
Workflow and execution IDs are opaque. Use the existing workflow tools for native and legacy workflows; do not infer the execution engine from an ID's format. Use create_job_draft and update_job_workflow_draft for job-owned, isolated edits, then run_job variant=draft to execute them. list_job_executions variant=draft|published selects the history; trigger selects how a run started. A run_job configurationId override applies only to that run.
Use executionTarget: { serviceId, targetUser } when a caller must preserve one canonical desktop lane across an execution handoff. serviceId is a UUID and targetUser is trimmed to 1 through 128 characters. Full run_job calls accept it directly. A range with executionTarget must use variant: "draft", include the nonnegative expectedDraftRevision, and omit seedExecutionId. Published legacy ranges omit executionTarget. run_tests applies one target to every selected sample. Ordinary callers may omit the field.
Standalone drafts use create_workflow_draft, get_workflow_draft, update_workflow_draft, and run_workflow_draft. Inspect the returned execution ID with get_native_execution. Discard unused drafts with discard_job_draft or discard_workflow_draft.
Scripts reference credentials as {{config.propertyKey}} - resolved at runtime by the workflow engine.
Issues
| Tool | What it does |
| -------------- | ---------------------------------------------- |
| list_issues | List all issues in a workspace |
| get_issue | Get a specific issue with full description |
| create_issue | Create an issue (supports Markdown) |
| update_issue | Update title, description, status, or assignee |
| delete_issue | Delete an issue |
Agents use Issues to persist context across runs with the [Agent:<name>] title convention:
[Agent:my-bot] Rules- learned behaviors (e.g., "login page takes 15s to load")[Agent:my-bot] Context- persistent state (URLs, credential rotation dates)[Agent:my-bot] Session 2026-04-09- run summaries
Workspace Context
| Tool | What it does |
| --------- | ---------------------------------------------------------------------------------------------------------------|
| context | Read or write the workspace's shared context document - three markdown sections: schemas, infrastructure, notes |
Read it before building or debugging workflows; write to it when you learn something durable about the workspace's systems. Writes are partial: only the sections you provide are replaced, the rest keep their current value.
Agents
| Tool | What it does |
| -------------- | ---------------------------------------------------------------- |
| list_agents | List agents in a workspace |
| get_agent | Get agent details (task, mode, VM, run stats) |
| create_agent | Create an agent on an explicit desktop lane (on-demand, scheduled, or workflow monitor mode) |
create_agent requires lane: { vmId, targetUser }. Select the numeric desktop ID and a registered Windows user; use targetUser: null only for a confirmed standalone desktop. The lane is the execution target; vmUrl does not select it. Existing agents without a lane must be edited before they run.
Agents are autonomous runners that execute tasks on VMs using MCP tools. After building a workflow, the build-rpa-workflow prompt offers to create a monitoring agent that watches for failures and auto-recovers.
Skills
| Tool |
