CumulusDocs
Open Studio
On this page

Customer HTTP adapters

An agent tool describes an action; your application implements it. Import the HTTP adapters from @cumulus/sdk/server to turn that implementation into an authenticated endpoint with input validation, a deadline and output validation.

For example, an agent can ask your service desk for repair guidance. Cumulus sends the tool arguments to your server, your handler returns a result, and the agent uses that result in its next response. Your database and business rules stay in your application.

Implement and serve a tool

Save this as tools-server.mjs in a project with SDK 0.3.4 installed. It defines a read-only contract and serves its implementation on Node's HTTP server.

javascript
import { createServer } from 'node:http';
import { defineTool } from '@cumulus/sdk/agent';
import { implementTool, createToolHandler, nodeHandler } from '@cumulus/sdk/server';

const guide = defineTool({
  key: 'service_guide',
  description: 'Read service guidance for a repair request.',
  input: {
    type: 'object',
    properties: { topic: { type: 'string', enum: ['repair'] } },
    required: ['topic'], additionalProperties: false,
  },
  output: {
    type: 'object',
    properties: { guidance: { type: 'string' } },
    required: ['guidance'], additionalProperties: false,
  },
  execution: {
    type: 'http', binding: 'customer_api',
    path: '/cumulus/tools/guide', method: 'POST', timeoutMs: 3000,
    idempotency: { mode: 'effect_key' }, onUncertain: 'failure_edge',
  },
});

const implementation = implementTool(guide, async ({ topic }, context) => {
  // Replace this read-only lookup with your application service.
  context.signal.throwIfAborted();
  return { guidance: `For ${topic}, ask which equipment needs attention.` };
});

const handleGuide = createToolHandler(implementation, {
  auth: {
    scheme: 'bearer',
    secret: process.env.CUSTOMER_TOOL_SECRET,
    integration: 'service_guide',
  },
});

createServer(nodeHandler(async request => {
  if (new URL(request.url).pathname !== '/cumulus/tools/guide') {
    return new Response('Not found', { status: 404 });
  }
  return handleGuide(request);
})).listen(8787, '127.0.0.1');

Set CUSTOMER_TOOL_SECRET to a development secret of at least 16 characters, then run node tools-server.mjs. Send a local request in another terminal with the same secret:

bash
curl --fail-with-body http://127.0.0.1:8787/cumulus/tools/guide \
  -H "Authorization: Bearer $CUSTOMER_TOOL_SECRET" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: example-effect-0001' \
  -d '{"args":{"topic":"repair"},"call":{"call_id":"example-conversation","run_id":"example-run","agent_id":"example-agent"}}'

The response is {"guidance":"For repair, ask which equipment needs attention."}. Remove the bearer header to get 401; send a different topic to get 400. These requests exercise your endpoint without placing a call.

In TypeScript, implementTool infers argument and result types from the tool's schemas. Reuse the same tool definition in your graph and server so they agree on the contract.

To receive Cumulus requests, deploy this route behind public HTTPS on port 443 and bind customer_api to it. Give the server the secret bytes through your secret manager; give Cumulus the matching pinned secret reference. See bindings. With Express, mount nodeHandler before express.json() so signature verification receives the original body.

Understand the request context

POST tools receive { args, call: { call_id, run_id?, agent_id, metadata? } }. GET tools receive query arguments; their context has no conversation, run or agent ID, and metadata is empty. The adapter passes your handler this context:

FieldUse
integrationThe integration name configured on your server.
conversationIdThe request's call_id; conversation correlation.
runIdThe optional run_id; use this for run API lookups.
agentId, metadataRequest context for your workflow.
effectIdThe Idempotency-Key header, required for effect_key tools.
signalCancellation and the tool deadline; pass it to work that supports cancellation.

Authentication establishes who can call the endpoint. Your business service must still authorize the account, booking or payment being changed. Metadata and a caller's phone number do not establish that authorization. If your workflow needs an authoritative run lookup, reject requests without runId.

Handle effects and failures

The read-only lookup above can safely return the same result again. A tool that books a visit needs durable effect handling: save its intent and outcome under (integration, effectId), reject reuse with different input, and return the saved result for a duplicate. The tutorial application includes a SQLite repository that retains duplicate results across restarts.

The adapter requires an effect key; it does not store or deduplicate business work for you. A timeout, cancellation or invalid output can occur after an external operation succeeded. Reconcile that operation before executing it again.

ResponseMeaning
405The method does not match the tool contract.
401Authentication failed.
400The envelope, identity or arguments are invalid.
500Execution failed or its outcome is uncertain.

The default body limit is 65,536 bytes; maxBodyBytes changes it. Avoid logging raw bodies or provider errors containing private data.

Receive initial context and lifecycle events

Use createInboundContextHandler(resolve, { auth, variables }) for initial string variables on an inbound call. The callback receives the full event envelope and a cancellation signal:

json
{
  "event": "call_inbound",
  "call_inbound": {
    "agent_id": "example-agent",
    "agent_version": 1,
    "from_number": "+12025550123",
    "to_number": "+12025550124"
  }
}

For example, this handler returns a declared context variable. Add its route to the Node server above:

javascript
import { createInboundContextHandler } from '@cumulus/sdk/server';

const handleContext = createInboundContextHandler(async (input, signal) => {
  signal.throwIfAborted();
  // Read caller and destination from input.call_inbound for your authorized lookup.
  return { dynamic_variables: { demo_channel: 'phone' } };
}, {
  auth: {
    scheme: 'bearer', secret: process.env.CUSTOMER_TOOL_SECRET,
    integration: 'service_guide',
  },
  variables: ['demo_channel'],
});

Route /cumulus/context to handleContext(request) before the server's not-found response. The response is { "call_inbound": { "dynamic_variables": { "demo_channel": "phone" } } }. Return only declared string variables; the resolver can also return { reject: true } to reject the inbound call. The default deadline is three seconds; timeoutMs changes it.

Use createEventHandler({ secret, consume }) for signed lifecycle notifications. It verifies x-talos-signature against the raw body and awaits consume before acknowledging. Store the event and deduplicate its ID before returning. If processing needs a background job, save that job intent in the same transaction. The signing secret needs at least 32 characters; the default freshness window is five minutes. Set maxBodyBytes: 262144 when creating this handler to accept the full lifecycle-event size supported by Cumulus; the adapter defaults to 65,536 bytes. See the complete HTTP callback contract, including raw-byte signature verification.

run.ended, recording.updated and analysis.updated arrive independently. Fetch each corresponding resource when your workflow needs it. The tutorial server combines tool, context and event routes. Private keypad input uses the dedicated tokenization adapter.

Adapter reference

ExportResponsibility
implementTool, createToolHandlerConnect and serve a tool contract.
createInboundContextHandlerServe declared initial context variables.
createEventHandler, verifyWebhookReceive or verify Cumulus lifecycle events.
verifyRetellSignatureVerify the separate Retell-compatible tool signature.
createTokenizationHandlerServe the private capture contract.
nodeHandler, readBoundedAdapt to Node HTTP; read a bounded body.
AuthenticationError, ToolOutcomeUncertain, TokenizationDeclinedDistinguish authentication failure, uncertain execution and definite vault refusal.
Content version 0c3e6260Customer SDKMarkdown source
Customer HTTP adapters · Cumulus developer docs