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
| 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. |
task created with id=<taskId> — the base62
ID of the new task, as a plain string rather than a JSON object
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
| 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 |
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.
// 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
| 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 |
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
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
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.
| 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 |
includeConversation is true, the response also
carries the chat history as { messages, total, cursor }.
// 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
| 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 |
<channel> message that announced the
attachment, not from this response.
// 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
| 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.
event "<name>" published. Subscriber
workspaces create their trigger tasks automatically — this call does not wait for
them.
// 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
| Name | Type | Required | Description |
|---|---|---|---|
name |
string | optional | Which memory to read. Default:
MEMORY.md, the index that says what else this workspace
remembers
|
memory://<name> that
look relevant to what you are about to do.
// 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
| 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
|
.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.
MEMORY.md so the index stays short enough to read
first.
// 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
name it deletes
MEMORY.md itself — think before doing that,
since it is the index the other memories link from.
| Name | Type | Required | Description |
|---|---|---|---|
name |
string | optional | Which memory to delete. Default:
MEMORY.md, the index that says what else this workspace
remembers
|
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.
// 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.
| 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). |
{ 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.
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 |