MCP
The MCP adapter wraps a client once at construction. Every callTool() then passes through Actera before the underlying transport is invoked.
Set up the client
Section titled “Set up the client”This function receives authentication headers from your server configuration and returns the named objects used by the invocation and resume examples below.
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'import { ActeraClient } from '@actera/sdk'import { acteraAuthorization, createActeraMcpContinuation, interceptMcpClient } from '@actera/sdk/mcp'import { createInvocationContext } from '@actera/sdk/mcp/node'
async function connectMcp(acteraHeaders: Record<string, string>) { const actera = new ActeraClient({ baseUrl: process.env.ACTERA_BASE_URL!, workspaceSlug: process.env.ACTERA_WORKSPACE_SLUG!, headers: acteraHeaders, }) const rawClient = new Client({ name: 'support-agent', version: '1.0.0' }) await rawClient.connect(new StreamableHTTPClientTransport(new URL(process.env.MCP_SERVER_URL!))) const invocationContext = createInvocationContext() const continuation = createActeraMcpContinuation({ client: actera, hostId: 'support-host', targetId: 'support-tools', dispatch: (request) => rawClient.callTool(request), }) const client = interceptMcpClient(rawClient, [acteraAuthorization({ client: actera, continuation, failOpen: false, resolveInvocation: () => invocationContext.getStore(), })]) return { client, continuation, invocationContext }}Invoke and pause
Section titled “Invoke and pause”import type { ActionIntentHandle } from '@actera/sdk'import { ActeraApprovalRequiredError } from '@actera/sdk/mcp'
async function invokeTool(input: { integration: Awaited<ReturnType<typeof connectMcp>> traceId: string toolCallId: string refund: Record<string, unknown> persistHandle: (handle: ActionIntentHandle) => Promise<void>}) { try { return await input.integration.invocationContext.run( { agentId: 'support-agent', traceId: input.traceId, toolCallId: input.toolCallId }, () => input.integration.client.callTool({ name: 'create_refund', arguments: input.refund }), ) } catch (error) { if (!(error instanceof ActeraApprovalRequiredError) || !error.handle) throw error await input.persistHandle(error.handle) return { status: 'pending_approval' } }}Persist the handle and end the initial request. Do not resume inside this catch block because no human approval has occurred yet.
Resume after approval
Section titled “Resume after approval”import { ActeraExecutionUncertainError } from '@actera/sdk/mcp'
async function resumeApprovedTool( integration: Awaited<ReturnType<typeof connectMcp>>, savedHandle: ActionIntentHandle,) { try { const result = await integration.continuation.resumeToolCall(savedHandle) if (result.status !== 'COMPLETED') return result return result.result } catch (error) { if (error instanceof ActeraExecutionUncertainError) { return { status: 'unknown', intentId: error.intent.id } } throw error }}The continuation claim prevents two hosts from being granted the same dispatch. It does not make an arbitrary external side effect exactly once. If the tool call fails after dispatch begins, Actera reports an unknown outcome and does not automatically dispatch it again.
