Skip to content

MCP

Actera provides runtime authorization for MCP tool calls by intercepting callTool() before the underlying MCP transport sends the request. The interceptor submits the proposed action to Actera and enforces the resulting ALLOW, REQUIRE_APPROVAL, or BLOCK decision.

The adapter wraps a client once at construction. Its authorization request preserves the MCP tool name and arguments, adds invocation context, and uses the trace ID plus tool-call ID as the stable idempotency key. Policy selection and assignment determine which tool actions are governed; usage limits can apply cumulative state across those calls.

Create the Actera client with the access token and workspace UUID from your server environment, then wrap the MCP client once at construction.

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() {
const accessToken = process.env.ACTERA_ACCESS_TOKEN!
const actera = new ActeraClient({
baseUrl: process.env.ACTERA_BASE_URL!,
workspaceId: process.env.ACTERA_WORKSPACE_ID!,
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
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.

REQUIRE_APPROVAL throws before the underlying tool call. The saved handle identifies the captured action intent; approval changes its authority state but does not prove the tool executed. See Decisions and approvals for the lifecycle distinction.

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.

Actera governs MCP calls made through the intercepted client. Calls made directly through rawClient, another client instance, or any path without the interceptor are outside that enforcement boundary. The language model that proposed the tool call does not change coverage.

The example sets failOpen: false. With this setting, invalid invocation context, authorization transport failures, and unsupported decision effects fail closed and the tool is not called. Setting failOpen: true deliberately calls the underlying tool when authorization cannot be obtained, but it is incompatible with managed MCP continuation. Use fail-open only when that weaker boundary is an explicit application decision.

Policy publication and agent assignment are separate. Review Policies and assignment before relying on an interceptor in production, and use Inspect a decision to trace the result.