Skip to content

MCP Agent Guide

This guide explains how AI agents use MCP Moira tools to execute workflows.

MCP Tools Overview

MCP Moira exposes these tools:

ToolPurpose
listList available workflows
startPrepare or execute a start
stepAdvance workflow with input
manageCRUD operations on workflows
sessionUser info and execution state
settingsUser settings
communicationAuthenticated user delivery
tokenUpload/download tokens
helpDocumentation

Basic Workflow Execution

1. Prepare and Start Workflow

start({ action: "prepare", workflowId: "moira/robust-task", parentExecutionId: "none" })

Preparation validates the request and reserves a Start attempt for 15 minutes without creating an execution or running any workflow node. It returns startAttemptId independently of mutable notification and trusted-lock readiness. Execute that exact attempt to run those preflight checks:

start({ action: "execute", startAttemptId: "start-attempt-123" })

The successful execution response contains:

{
"processId": "abc-123-def",
"attemptId": "attempt-456",
"directive": "Break down the task into steps...",
"completionCondition": "Task breakdown complete with 3+ steps",
"inputSchema": {
"type": "object",
"properties": {
"steps": { "type": "array" }
},
"required": ["steps"]
}
}

During execute, a generic notification workflow with no configured user channel returns a stable START_PRECONDITION_CHANGED receipt containing Settings > Notifications guidance and creates no execution. Legacy Telegram-notification workflows return Telegram setup guidance. Set skipNotificationCheck: true during prepare only to bypass optional ordinary-notification preflight at execute; it never authorizes a send or bypasses mandatory Telegram configuration for a lock node.

Repeat execute with the same Start attempt ID when its response is lost: a completed attempt returns the exact stored response and cannot create a second execution. Preparing again is an intentional request for a separate execution.

2. Execute Step

After completing the work described in directive:

step({
processId: "abc-123-def",
attemptId: "attempt-456",
input: {
"steps": ["Step 1", "Step 2", "Step 3"]
}
})

Returns next directive or completion status.

3. Continue Until Complete

Repeat step() calls until workflow returns completion.

Use the Step attempt ID from the current presentation for every call, including a step with empty input. A replay of the same attempt with the same input returns its stored result without advancing again. Never use an attempt from an older presentation.

Response Format

Every workflow step returns:

FieldDescription
processIdUUID for this execution, use in all step() calls
attemptIdIdentity of this exact presented step
directiveWhat to do (the instruction)
completionConditionWhen you’re done (success criteria)
inputSchemaHow to structure your response (JSON Schema)

Understanding Directives vs Conditions

directive = WHAT to do completionCondition = WHEN you’re successfully done

Example:

  • directive: “Run all project tests”
  • completionCondition: “All tests pass (0 failures)”

The agent must:

  1. Execute the directive (run tests)
  2. Verify the completionCondition is met (check for 0 failures)
  3. Only then proceed with step()

Input Schema

When inputSchema is provided, your response must match the schema exactly.

Example schema:

{
"type": "object",
"properties": {
"result": {
"type": "string",
"enum": ["pass", "fail"]
},
"evidence": {
"type": "string"
}
},
"required": ["result", "evidence"]
}

Valid response:

{
"result": "pass",
"evidence": "All 302 tests passed"
}

Submit a single flat object containing exactly the properties in inputSchema — one object per step, with every required property present. Keys not described in the schema are rejected. Some of a step’s values are workflow-wide and some are local to the step, but the schema you receive already merges both into one ordinary object: you do not declare or distinguish them — just match the schema.

List Executions

session({ action: "executions" })

Returns the first page of active executions for the current user with status, workflow ID, and notes. Use limit and offset for additional pages.

Get Current Step

Resume workflow after interruption:

session({ action: "current_step", executionId: "abc-123" })

Returns the current agent-facing step presentation without advancing the workflow, including the Process ID, Step attempt ID, directive, success criteria, and input schema when present. Applicable child-workflow, system-reminder, and teleport context is included as well.

Get Full Context

session({ action: "execution_context", executionId: "abc-123" })

Returns execution state including context variables, history, the workflow-step revision, and independent task-identity, parent, context, and reminder revisions. Supply the matching target revision together with expectedRevision when changing that metadata; successful writes return the next target revision without changing the step revision.

Naming the task

At intake, read execution context and persist the actual name before submitting the step:

session({
action: "update-task-title",
executionId,
taskTitle: "Implement authentication",
expectedRevision: current.revision,
expectedTaskIdentityRevision: current.metadataRevisions.taskIdentity,
});

current is the execution-context result. Apply the task-name contract for limits, stale guards and no-op behavior. Rename at an authorized actual scope change; ordinary progress retains the name and the current step attempt. Notes below remain separate.

Execution Notes

Track execution progress with notes:

start({ action: "prepare", workflowId: "dev-flow", note: "Feature: auth system", parentExecutionId: "none" })
start({ action: "execute", startAttemptId: "start-attempt-123" })

Update note during execution via step() input:

step({
processId: "abc-123",
attemptId: "attempt-456",
input: {
"task_result": "done",
"execution_note": "Step 3: Integration tests"
}
})

Or via session tool:

session({
action: "update-note",
executionId: "abc-123",
note: "Step 3: Integration tests"
})

Waiting for the User

When you cannot continue without the user — a requirement you do not understand, access you lack, a choice only they can make — raise your question on the run and ask it in the chat as well:

session({
action: "await-user",
executionId: "abc-123",
question: "Which environment should the release go to?",
options: ["staging", "production"]
})

The run is then shown as waiting for the user, with your question and choices, instead of looking like it waits for you, and the user is notified through their channels with the question and a link to the run (a new question within ten minutes of the previous notification waits for its turn). question is up to 500 characters and options up to four choices. The user answers you in the chat, not in Moira. A new call replaces the question; your next step, a variable you set with set-variable, or resolve: true clears it, and so does the run leaving the step. The call does not change the step revision, so your current Step attempt stays valid.

Stopping a Workflow

When the user cancels the run or asks to continue outside it, read its revision with session({ action: "execution_context", executionId }), then stop that owned run:

session({
action: "stop-execution",
executionId: "abc-123",
expectedRevision: 4,
reason: "The user cancelled this workflow and asked to continue directly."
})

Use the actual returned revision. The reason is required and contains 1–500 characters after trimming. Stopping permanently ends the run, records stopReason, clears its waits and advances the revision once; it does not certify successful task completion. Do not invent step results to finish a cancelled workflow. If an execution operation, including recovery or a run-page answer, is still in progress, stopping is refused: wait for it to finish and read the context again. A stale revision or an already finished run is also refused. After a lost response, repeat the original revision and reason to receive the same stopped result without another change. Stopped runs do not match the default active overview; use Stopped or All to find them and choose All time for older runs. A stopped ancestor can remain as labelled context for an eligible child. Their active child runs continue independently. The web UI’s owner stop controls use the same guarded operation. Read stopCapability as an advisory snapshot; the stored mutation repeats ownership, revision and executing-operation checks.

Finding Workflows

List the First Page

list()

Search by Name

list({ search: "test" })

Filter by Visibility

list({ visibility: "public", limit: 10 })

Common Patterns

Start and Execute First Step

// 1. Prepare without creating an execution
start({ action: "prepare", workflowId: "moira/verified-research", parentExecutionId: "none" })
// → { startAttemptId: "start-1", expiresAt: "..." }
// 2. Execute that exact prepared start
start({ action: "execute", startAttemptId: "start-1" })
// → { processId: "xyz", attemptId: "attempt-1", directive: "...", ... }
// 3. Do work, then advance
step({ processId: "xyz", attemptId: "attempt-1", input: { findings: "..." } })
// → { attemptId: "attempt-2", directive: "next step...", ... }

Resume After Interruption

// 1. Find your execution
session({ action: "executions" })
// → [{ executionId: "xyz", status: "waiting", ... }]
// 2. Get current step
session({ action: "current_step", executionId: "xyz" })
// → { attemptId: "attempt-current", directive: "...", completionCondition: "...", ... }
// 3. Continue
step({ processId: "xyz", attemptId: "attempt-current", input: { ... } })

Validation Errors

If step() returns validation error, check:

  1. Field names - Must match schema exactly (case sensitive)
  2. Required fields - All required properties must be present
  3. Data types - String vs number vs boolean must match
  4. Enum values - Must be one of allowed values

ATTEMPT_PROCESSING means another caller still owns this exact mutation; retry the same Process ID, Step attempt ID, and input, or the same Start attempt ID for start({ action: "execute" }). ATTEMPT_STALE was rejected before handler work. Automatically read session({ action: "current_step", executionId }) and retry the intended submission once with the returned Step attempt ID; user guidance is not required. ATTEMPT_CONFLICT means the attempt is already bound to different input. Automatically read session({ action: "current_step", executionId }), discard the conflicting attempt, and continue from the returned directive and input schema without replaying the rejected input; user guidance is not required unless the current directive requires a decision. For a step-level ATTEMPT_INVALID_OR_EXPIRED that explicitly directs you to current_step, use the same state-refresh recovery and do not reuse the unavailable attempt. An unavailable start attempt does not provide this recovery. ATTEMPT_OUTCOME_UNKNOWN means an external effect may have happened; inspect the returned Process ID through session and do not automatically retry. The execution owner can retire a blocked execution with its current revision through session({ action: "cancel-execution", executionId, expectedRevision }).

A write made on a revision that is no longer current — a reminder, parent, context or cancellation whose expectedRevision or target revision is older than the stored one, or a message that says “reload” or “is stale” — is rejected before anything is written. Re-read that state (reminders, variables, execution_context, or the workflow for a workflow revision) and retry with its current revisions; user guidance is not required.

When an error carries an AGENT INSTRUCTIONS section, follow it. It tells you to stop only where a person is needed: missing permission, an authentication or connection problem you cannot resolve, a workflow that does not exist, or an effect that may already have happened. Any other error — an invalid input, a missing execution, an error with no specific instructions — is yours to resolve: diagnose it with help, session (diagnose, current_step, execution_context) and list, fix what lies within your task, and ask the user only for a decision, permission, credential or fact you cannot obtain yourself.