Skip to content

Tools as durable activities

Agents and MCPSDKMCPMcpClient.toolActivitiesproxyActivitiesmcp_{serverName}_{toolName}

Register an MCP server and its tools become checkpointed workflow activities: retried on failure, replayed from cache after a crash.

The situation

A workflow needs a classifier that lives behind an MCP server. Calling it directly from workflow code means handling retries, partial failures and a process that crashes mid-call. The tool protocol itself guarantees none of that.

The recipe

workflows/classify.ts
import { Durable } from '@hotmeshio/hotmesh';
import { McpClient, type LTEnvelope } from '@hotmeshio/long-tail';
const tools = await McpClient.toolActivities(serverId);
const { mcp_doc_analyzer_classify } = Durable.workflow.proxyActivities<typeof tools>({
activities: tools,
retry: { maximumAttempts: 3 },
});
export async function classifyDocument(envelope: LTEnvelope) {
const result = await mcp_doc_analyzer_classify({ content: envelope.data.content });
if (result.confidence >= 0.85) return { type: 'return', data: result };
return { type: 'escalation', data: result, message: 'Low confidence', role: 'reviewer' };
}

Tool names derive from the server and tool: mcp_{serverName}_{toolName}, with non-alphanumeric characters replaced by underscores. The call reads like a function call; it is also an MCP tool call and a durable activity.

The guarantee

The result is checkpointed to Postgres. If the process crashes between the tool call and its checkpoint, the workflow replays from cache and the external server is not called a second time: exactly-once semantics over a protocol that does not provide them. The activity carries the workflow’s identity, so the tool acts as the run’s principal. See MCP.