Skip to content

MCP

Actera enforces agent authority 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 a stable operationKey when supplied, otherwise the trace ID plus tool-call ID. Preserve that identity across retries and restarts. Policy selection and assignment determine which tool actions are governed; usage limits can apply cumulative state across those calls.

Use the TypeScript installation instructions. The examples below use @actera-ai/sdk@0.0.3 and @modelcontextprotocol/client@2.2.0:

Terminal window
npm install --save-exact @actera-ai/sdk@0.0.3 @modelcontextprotocol/client@2.2.0

Create the Actera client with an agent connection key, bind it to the configured agent once, then wrap the MCP client at construction.

import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'
import { ActeraClient, fromApiKey } from '@actera-ai/sdk'
import type { OutcomeClassification } from '@actera-ai/sdk/execution'
import { createActeraAuthorization, createActeraMcpContinuation, interceptMcpClient } from '@actera-ai/sdk/mcp'
import { createInvocationContext } from '@actera-ai/sdk/mcp/node'
async function connectMcp(classifyOutcome: (result: unknown) => OutcomeClassification) {
const actera = new ActeraClient({
credentials: fromApiKey(process.env.ACTERA_API_KEY!),
})
// Use the agent ID shown in connection setup.
const agent = actera.agent({ agentId: process.env.ACTERA_AGENT_ID! })
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: agent,
hostId: 'support-host',
targetId: 'support-tools',
classifyOutcome,
dispatch: (request) => rawClient.callTool(request),
})
const client = interceptMcpClient(rawClient, [createActeraAuthorization({
client: agent,
continuation,
behavior: { default: { mode: 'enforce', onUnavailable: 'deny' } },
resolveInvocation: () => invocationContext.getStore(),
})])
return { client, continuation, invocationContext }
}
import type { ActionIntentHandle } from '@actera-ai/sdk'
import { ActeraApprovalRequiredError } from '@actera-ai/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(
{ 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.

async function resumeApprovedTool(
integration: Awaited<ReturnType<typeof connectMcp>>,
savedHandle: ActionIntentHandle,
) {
return integration.continuation.resumeToolCall(savedHandle)
}

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.

Supply a provider-specific classifyOutcome that distinguishes confirmed completion from pending or uncertain results. The dispatcher receives an execution context containing the attempt ID and downstream idempotency key; connect that key to a verified provider idempotency mechanism. Both immediate and resumed calls use this dispatcher. Place other interceptors before managed authorization.

Customer infrastructure owns persistence and recovery. If reporting fails, ExecutionReportError from @actera-ai/sdk/execution contains the exact report to persist and retry through reportActionExecution; do not rerun the tool. Resume returns UNKNOWN or EXECUTING for unresolved attempts. Persist a stable business operationKey in invocation context when trace IDs change across restarts.

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 explicitly enforces policy and denies calls when Actera is unavailable. Set onUnavailable: 'defer' to return a typed deferral, or onUnavailable: 'allow' for an action whose business impact permits bypassing enforcement during a classified Actera outage. Authentication failures, permission failures, malformed responses, and invalid invocation context never take the unavailable bypass. Managed continuations require enforce with deny or defer.

For rollout, mode: 'monitor' records a policy decision without enforcing its effect. After the tool runs, it reports the execution outcome on a best-effort basis. Both modes reserve in-flight capacity for non-BLOCK decisions. Monitoring creates no approval or execution claim. A successful tool response alone does not establish that the provider completed the business operation.

Publishing a policy update affects new requests from its assigned agents. Review Policies and assignment before relying on an interceptor in production, and use Inspect a decision to trace the result.