Skip to content

MCP Tools

Tools are the primary way agents interact with your plugin. Every tool is an MCP tool — it appears in the agent’s tool list and can be called from any session.

Each tool is a TypeScript file that exports two things:

tools/my-tool.ts
const schema = { /* JSON Schema definition */ };
async function handler(args, ctx) { /* implementation */ }
export { schema, handler };

Recursive discovers all .ts and .js files in the directory pointed to by "tools" in your manifest.

The schema follows the MCP tool schema format:

const schema = {
name: 'my_plugin_do_thing',
description: 'One-line description of what this tool does.',
inputSchema: {
type: 'object',
properties: {
item_id: {
type: 'string',
description: 'The item to operate on. Required.',
},
options: {
type: 'object',
properties: {
verbose: { type: 'boolean', description: 'Include extra detail. Default: false.' },
},
},
},
required: ['item_id'],
additionalProperties: false,
},
audience: 'shared',
};
FieldTypeDescription
namestringTool name. Must be unique across all plugins. Convention: {pluginId}_{action}.
descriptionstringShown to agents. Be specific — agents use this to decide when to call the tool.
inputSchemaobjectJSON Schema for the tool’s parameters.
audiencestringWho can call this tool: "orchestrator", "agent", "shared", "dashboard", or "any".
ValueDescription
orchestratorOnly the orchestrator/coordinator agent.
agentOnly child/delegated agents.
sharedBoth orchestrator and agents. Most common.
dashboardOnly the dashboard (HTTP API).
anyEveryone, including dashboard.

The handler receives two arguments:

async function handler(args: Record<string, unknown>, ctx: ToolContext) {
// args = validated input matching your schema
// ctx = platform utilities
}
PropertyTypeDescription
ctx.dbStateDbFull access to the Recursive state database.
ctx.VValidatorSchema-based input validation utilities.
ctx.broadcastfunctionSend SSE events to connected dashboard clients.
ctx.getSettingsfunctionRead plugin settings.
ctx.pluginIdstringThe current plugin’s ID.

Return a plain object. Recursive automatically wraps it in the MCP tool result format.

async function handler(args, ctx) {
const widget = ctx.db.getWidget(args.id);
return {
id: widget.id,
name: widget.name,
summary: `Found widget "${widget.name}".`,
};
}
CodeWhen to use
MISSING_REQUIREDA required parameter was not provided.
NOT_FOUNDThe referenced entity doesn’t exist.
INVALID_INPUTInput failed validation.
CONFLICTOperation conflicts with current state (e.g., duplicate ID).
INTERNALUnexpected server error.
BOUNDARY_VIOLATIONPath traversal or scope violation.

Use ctx.V for structured validation:

async function handler(args, ctx) {
const { valid, errors } = ctx.V.validate(args, {
title: { required: true, type: 'string', minLength: 1 },
priority: { type: 'integer', min: 1, max: 4 },
});
if (!valid) {
return {
error: true,
code: 'INVALID_INPUT',
message: errors.map(e => e.message).join('; '),
retryable: true,
};
}
// proceed with validated args
}

When your tool modifies state, broadcast an SSE event so the dashboard updates in real time:

async function handler(args, ctx) {
const widget = createWidget(args);
ctx.broadcast('widgets', {
action: 'created',
id: widget.id,
});
return widget;
}

The dashboard store listens for the event channel you declared in ui.sseEvents and reacts accordingly.

  1. Name tools descriptively — agents read the name and description to decide what to call. widget_create is better than create.
  2. Always validate input — never trust that args match your schema. Use ctx.V.
  3. Return structured data — agents need to parse results. Return objects, not prose.
  4. Include a summary field — human-readable summary alongside machine data.
  5. Use additionalProperties: false — prevents agents from hallucinating extra fields.
  6. Document required fields in descriptions — add “Required.” to required field descriptions. Agents read these.