Human-in-the-Loop Task Manager for AI Agents
Docs / MCP Tools
Reference

Workspace MCP Reference

AgentRQ exposes 11 MCP tools to Claude Code within a specific workspace. All tools are available after connecting via .mcp.json.

createTask

Create a task for the human or agent to handle

Parameters
Name Type Required Description
title string required Short task title shown in the dashboard
body string required Full task description — include all context the human needs
assignee "human" | "agent" optional Who the task is assigned to. Default: "agent"
attachments Attachment[] optional Array of file attachments (see Attachment type below)
cronSchedule string optional Cron schedule (5 fields: minute hour dom month dow). Recurring schedules (wildcard dom/month) are hourly at most — the minute must be a single integer 0–59, e.g. 30 * * * *. One-time schedules with a fixed dom and month, e.g. 30 14 25 4 *, allow minute precision.
eventId string optional Event ID (base62). When this task completes the named event is published automatically.
Returns
Text: task created with id=<taskId> — the base62 ID of the new task, as a plain string rather than a JSON object
Example
Claude Code
const result = await createTask({
  title: "Review: DB migration for user_sessions table",
  body: `## What I'm about to do
Add a new \`user_sessions\` table to store auth tokens.

## Migration SQL
\`\`\`sql
CREATE TABLE user_sessions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id INTEGER REFERENCES users(id),
  token_hash VARCHAR(64) NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  expires_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX ON user_sessions(expires_at);
\`\`\`

## Approve to proceed?`,
  assignee: "human"
});

console.log(result); // "task created with id=0ZRgCquBZ7R"

updateTaskStatus

Transition a task to a new status

Parameters
Name Type Required Description
taskId string required ID of the task to update
status "ongoing" | "completed" | "rejected" | "notstarted" required The new status to transition to
Best Practice
Always call updateTaskStatus("ongoing") as the first thing when you start working on a task. This signals to the human that the agent has seen their request.
Example
Claude Code
// Received task — immediately mark as ongoing
await updateTaskStatus({
  taskId: "0ZRgCquBZ7R",
  status: "ongoing"
});

// ... do work ...

// Done — mark completed
await updateTaskStatus({
  taskId: "0ZRgCquBZ7R",
  status: "completed"
});

reply

Send a message in a task thread

Parameters
Name Type Required Description
chatId string required The chat ID from the channel message — the chat_id attribute on the <channel> tag
text string required The message text to send
attachments Attachment[] optional Files to attach to the reply
Example
Claude Code — sending progress update
await reply({
  chatId: "0ZRgCquBZ7R",
  text: "Migration complete. 14,322 rows deleted. Here's the query plan:",
  attachments: [{
    id: "att_plan_001",
    filename: "query-plan.txt",
    mimeType: "text/plain",
    data: btoa(queryPlanText)  // base64 encoded
  }]
});

getWorkspace

Fetch workspace metadata and context

Parameters
None — takes no parameters. Returns info about the workspace the MCP token belongs to.
Returns
Workspace name, ID, owner, and any custom mission/context set in settings.
When to Call
Call at the start of every session to load workspace context and confirm connectivity. Include it as an instruction in your CLAUDE.md.
Example
Claude Code
const workspace = await getWorkspace();
// Returns:
{
  id: "0ZPO4WBMZIP",
  name: "my-saas-backend",
  owner: "user@example.com",
  mission: "Build the v2 API. Ask before any DB changes or deploys."
}

getTask

Fetch the next "not started" task, or a specific task — optionally with its conversation history

With no taskId, getTask dequeues the oldest "not started" task assigned to the agent (and associates it with the current session). Pass a taskId to fetch that specific task instead. Set includeConversation to append the task's chat history.
Parameters
Name Type Required Description
taskId string optional A specific task to fetch. Omit to dequeue the next "not started" task.
includeConversation boolean optional When true, appends the task's chat history. Default: false
cursor string | null optional Pagination cursor for conversation messages. null = start from beginning
limit integer optional Max conversation messages to return. Default: 20, max: 100
Returns
A single Task object (ID, title, body, attachments) — or null if no task is available. When includeConversation is true, the response also carries the chat history as { messages, total, cursor }.
Example
Claude Code
// Dequeue the next "not started" task
const task = await getTask();
if (task) {
  console.log(`Found next task: ${task.title}`);
  await updateTaskStatus({ taskId: task.id, status: "ongoing" });
} else {
  console.log("No pending tasks.");
}

// Fetch a specific task with its conversation history
const result = await getTask({
  taskId: "0ZRgCquBZ7R",
  includeConversation: true,
  cursor: null,
  limit: 50
});
// result.messages → { messages: [...], total, cursor }

downloadAttachment

Fetch a file attached by the human in the dashboard

Parameters
Name Type Required Description
attachmentId string required The attachment ID from the channel message
taskId string required The ID of the task the attachment belongs to
Returns
The stored file content as a plain text string (base64 for binary attachments). The filename and MIME type come from the <channel> message that announced the attachment, not from this response.
Example
Claude Code — human sent a file
// Channel message contains attachment ID
// <channel ...> [attachment: att_abc123 — design.png] </channel>

const data = await downloadAttachment({
  attachmentId: "att_abc123",
  taskId:       "0ZRgCquBZ7R"
});

// the response is the content itself — decode to use
const content = Buffer.from(data, "base64");

publishEvent

Fire a named signal so subscriber workspaces spawn their trigger tasks

Parameters
Name Type Required Description
name string required The event to publish. Must already exist in the account that owns this workspace.
payload string optional Free text describing what happened. Lands wherever a trigger's body says {{EVENT_PAYLOAD}}.
taskId string optional The task you are completing (base62). Identifies which workflow run this publish continues — see the warning below.
faq { q, a }[] optional Question/answer pairs of extra context. Lands wherever a trigger's body says {{EVENT_FAQ}}.

Copy name and taskId exactly as the task gave them to you. When a task carries a publishEvent instruction, that taskId is what identifies the workflow run being continued — omitting it can leave the run stranded. Write the payload yourself.

Returns
Text: event "<name>" published. Subscriber workspaces create their trigger tasks automatically — this call does not wait for them.
Example
Claude Code — finishing a task that ends a stage
// The task said:
// [On completion: call publishEvent("tests_passed", "<payload>")]

await updateTaskStatus({ taskId: "0ZRgCquBZ7R", status: "completed" });

await publishEvent({
  name:    "tests_passed",       // copied from the instruction
  taskId:  "0ZRgCquBZ7R",        // copied from the instruction
  payload: "482 passed, 0 failed on build 2.3.1.",
  faq: [
    { q: "Any flakes?", a: "One retry in the billing suite, passed on rerun." }
  ]
});

loadMemory

Read what this workspace remembers, written by earlier tasks

The memory belongs to the workspace, not to the agent that wrote it. Every agent connected to the same workspace reads and writes the same notes, so what one learned is there for the next — even a different model on a different machine.
Parameters
Name Type Required Description
name string optional Which memory to read. Default: MEMORY.md, the index that says what else this workspace remembers
Returns
The memory's full content. A name that has never been written is not an error — it returns a note saying nothing is saved under it yet, which is what a fresh workspace looks like.
When to Call
At the start of a task, before asking the human something they may already have told you. Start with no arguments to read the index, then load the entries it links as memory://<name> that look relevant to what you are about to do.
Example
Claude Code
// Start with the index.
const index = await loadMemory();
// # What this workspace remembers
// - [How we deploy](memory://deploys.md) — the two gates that are not automated.
// - [The flaky tests](memory://flaky-tests.md) — which failures are real.

// Then load only what this task needs.
const deploys = await loadMemory({ name: "deploys.md" });

saveMemory

Write something worth remembering, so the next task starts with it

This replaces the named memory completely — there is no append, so pass the full new content including anything worth keeping.
Parameters
Name Type Required Description
content string required The full new content. Replaces the memory entirely
name string optional Which memory to write. Default: MEMORY.md, which should stay an index
Limits
Names are lowercase words joined by single hyphens and ending in .md, up to 32 characters — release-notes.md is fine, anything else is refused. One memory holds at most 16 KiB. Both limits refuse rather than truncate, so a memory that is too large comes back as an error telling you to split it and index the parts — nothing is silently cut in half.
When to Call
When you learn something that would save the next agent the same detour: a gate that is not automated, a test that is flaky for a known reason, who to ask when something is stuck. Keep detail in named memories and link them from MEMORY.md so the index stays short enough to read first.
Example
Claude Code
// Write the detail into its own memory.
await saveMemory({
  name: "deploys.md",
  content: [
    "# How we deploy",
    "",
    "Production needs a human to approve the release.",
    "Drain settlement retries first: a release mid-drain",
    "leaves duplicate charges to reconcile by hand."
  ].join("\n")
});

// Then link it from the index, which agents read first.
await saveMemory({
  content: [
    "# What this workspace remembers",
    "",
    "- [How we deploy](memory://deploys.md) — the manual gate."
  ].join("\n")
});
// Returns: Saved "memory.md" (98 bytes).

deleteMemory

Delete one of the workspace's memories, by name

With no name it deletes MEMORY.md itself — think before doing that, since it is the index the other memories link from.
Parameters
Name Type Required Description
name string optional Which memory to delete. Default: MEMORY.md, the index that says what else this workspace remembers
Returns
A confirmation naming what was deleted. Deleting a name nobody wrote under — or one already deleted — is not an error; it reports that there was nothing to remove, so a harmless retry never fails.
When to Call
When a memory has gone stale or was written by mistake and nothing should link to it anymore. Deleting MEMORY.md itself removes the index other memories are linked from, not the memories it pointed to — update it instead of deleting it unless you mean to abandon those links too.
Example
Claude Code
// The flaky-tests memory no longer applies; remove it.
await deleteMemory({ name: "flaky-tests.md" });
// Returns: Deleted "flaky-tests.md".

// Retrying a delete that already happened is not an error.
await deleteMemory({ name: "flaky-tests.md" });
// Returns: No memory was stored under "flaky-tests.md"; nothing to delete.

elicit

Ask the human a question and block until they answer

Mirrors the MCP protocol's client-side elicitation/create capability. Use it when you need a decision before you can continue — it waits, rather than guessing and carrying on.

Parameters
Name Type Required Description
taskId string required The task the question relates to (base62)
message string required The question or prompt shown to the human
mode "form" | "url" required form collects structured input; url points the human at a link and waits for them to confirm they are done.
requestedSchema object form only A flat JSON Schema: type: "object" whose properties are each primitive (string, number, integer, boolean, optionally with enum), an array of one of those (renders as multi-select), or a oneOf/anyOf enum. No nested objects.
url string url only The link to show the human
timeoutSeconds integer optional How long to wait. Default and maximum are both 3600 (one hour).
Returns
{ action, content? }action is "accept" (with content holding the form values), "decline" or "cancel". A timeout returns { action: "cancel" } rather than an error — the human simply did not answer in time, so handle it as an answer.
Example
Claude Code — needs a decision before continuing
const answer = await elicit({
  taskId:  "0ZRgCquBZ7R",
  mode:    "form",
  message: "Which environment should I deploy to?",
  requestedSchema: {
    type: "object",
    properties: {
      environment: { type: "string", enum: ["staging", "production"] },
      runMigrations: { type: "boolean" }
    },
    required: ["environment"]
  }
});

// A timeout is a "cancel", not an error — treat it as an answer.
if (answer.action !== "accept") return;

deploy(answer.content.environment, answer.content.runMigrations);

Attachment Type

Used in createTask and reply when sending files from Claude to the human:

Field Type Description
id string Unique ID for this attachment (any string)
filename string Display filename (e.g., "schema.sql")
mimeType string MIME type (e.g., "text/plain", "image/png")
data string Base64-encoded file content
Ready to build?
Start with a free workspace — no credit card.