Skip to content

MCP

The MCP adapter wraps a client once at construction. Every callTool() then passes through Actera before the underlying transport is invoked.

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 }
}
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.

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.