Skip to main content

Reference Frontend and MCP Patterns

This document summarizes practical integration patterns for connecting a customer frontend, ACP Engine, and downstream MCP servers. It is based on a reference implementation, but all names and examples are intentionally generic.

The frontend should not call MCP servers directly. A backend-for-frontend layer or server-side frontend route should communicate with ACP Engine, and ACP Engine should communicate with MCP servers.

Frontend Runtime Pattern​

A production frontend typically contains a small server-side runtime adapter with these responsibilities:

  • Exchange integration credentials for an ACP Engine token.
  • Fetch public theme or SDK configuration.
  • Create or reuse a session ID.
  • Send chat messages to ACP Engine.
  • Execute MCP tools through ACP Engine when a direct tool action is required.
  • Normalize runtime responses for the UI.
  • Capture trace, session, and error metadata for support.

Suggested Frontend Environment Variables​

Use customer-specific values per environment:

ACP_ENGINE_URL=https://engine.customer.example.com
ACP_REALM_ID=realm-id
ACP_AGENT_ID=agent-access-key-or-id
ACP_AGENT_KEY=agent-secret-key
ACP_AGENT_UUID=agent-runtime-id
MCP_SERVER_IDS=mcp-server-id-1,mcp-server-id-2

MCP_MODE=multi
SEARCH_MODE=multi

APP_CHANNEL=web
APP_LOCALE=en-US

Do not expose secret values to browser code. Variables that contain credentials should be available only on the server side.

Server-Side Token Exchange Pattern​

The frontend backend should request a short-lived token from ACP Engine:

async function getEngineToken() {
const response = await fetch(`${process.env.ACP_ENGINE_URL}/api/v1/auth/token`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
access_key: process.env.ACP_AGENT_ID,
secret_key: process.env.ACP_AGENT_KEY
})
});

if (!response.ok) {
throw new Error("ACP Engine token exchange failed");
}

const data = await response.json();
return data.access_token;
}

Cache the token only according to the security policy of the deployment. Re-run token exchange when the token expires.

Fetching Theme and Runtime Capabilities​

Frontend applications can initialize customer-specific UI behavior and check supported runtime capabilities through ACP Engine:

async function getRuntimeConfiguration() {
const token = await getEngineToken();

const [themeResponse, capabilitiesResponse] = await Promise.all([
fetch(
`${process.env.ACP_ENGINE_URL}/api/v1/realms/${process.env.ACP_REALM_ID}/theme`,
{
headers: { Authorization: `Bearer ${token}` },
cache: "no-store"
}
),
fetch(
`${process.env.ACP_ENGINE_URL}/api/v1/runtime/capabilities`,
{
headers: { Authorization: `Bearer ${token}` },
cache: "no-store"
}
)
]);

if (!themeResponse.ok || !capabilitiesResponse.ok) {
throw new Error("Runtime configuration request failed");
}

return {
theme: await themeResponse.json(),
capabilities: await capabilitiesResponse.json()
};
}

The frontend can use a safe local visual default when theme loading fails. Do not enable protected runtime actions until the backend has successfully read the runtime capability surface.

Chat Adapter Pattern​

The frontend backend should send chat requests to ACP Engine:

async function sendChatMessage(messages, sessionId, resumeToken) {
const token = await getEngineToken();

const response = await fetch(`${process.env.ACP_ENGINE_URL}/api/v1/runtime/ai/chat`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
agent_id: process.env.ACP_AGENT_UUID,
session_id: sessionId,
resume_token: resumeToken,
messages
})
});

if (!response.ok) {
throw new Error("Runtime chat request failed");
}

return response.json();
}

The request omits flow_id, so ACP automatically selects or continues an eligible Flow. The BFF must keep resumeToken in protected state. Add a protected server-configured flow_id only when one exact conversational Flow is intentionally required. The frontend should render the returned action and payload instead of assuming all responses are plain text.

Runtime Action Contract​

The UI should be prepared for these common action patterns:

RESPOND : Render content as a conversational answer.

CLARIFY : Render a follow-up question and keep the same session.

RENDER_PRODUCTS : Render a product grid, carousel, or list using structured result data.

OFFER_PRODUCT_ALTERNATIVES : Explain that the exact request was unavailable and ask permission before executing a bounded broader search. Do not render product cards from the offer alone.

RENDER_PRODUCT_ALTERNATIVES : Render verified broader catalog results and preserve requested/applied query plus relaxed-constraint provenance.

RENDER_PRODUCT_DETAILS : Render one catalog-backed product's authorized detail component. A direct routed-tool widget lookup has no runtime action; the BFF maps its validated raw result to the same view.

RENDER_RESERVATIONS : Render travel, booking, or availability-style cards using structured result data.

ORDER_SUMMARY : Render authoritative existing order data in a summary or history view.

ACTION_REQUIRED : Render a modal, form, address selector, payment step, identity step, or another customer action.

The exact list of actions can be extended per deployment. Declare supported actions in client_capabilities.actions; unsupported alternative offers downgrade to RESPOND, while unsupported structured alternative/detail results downgrade to DATA_RESULT with metadata.original_type. Frontend teams should implement a safe default fallback for unknown actions.

MCP Tool Execution Through ACP Engine​

When the frontend backend needs to execute a known tool explicitly and wants ACP Engine to choose the right MCP server, it should call routed MCP execution. Select the tool from a fixed protected operation-to-tool allowlist, never from browser/mobile input:

const approvedTools = {
productDetails: "catalog_get_product_details"
} as const;

async function executeKnownTool(operation, args, sessionId) {
const token = await getEngineToken();

const response = await fetch(`${process.env.ACP_ENGINE_URL}/api/v1/runtime/mcp/tools/execute`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
tool_name: approvedTools[operation],
arguments: args,
session_id: sessionId,
strategy: "first_healthy"
})
});

if (!response.ok) {
throw new Error("MCP proxy request failed");
}

return response.json();
}

ACP Engine uses the discovered MCP tool registry and routing policy to select a matching server when the request does not include explicit server IDs. A known product-details widget can map to catalog_get_product_details with the exact retained catalogId. The response is a raw routed-tool envelope containing the selected server, tool name, and result; validate the expected tool and returned identifier before mapping result.data to the detail view. Use direct MCP proxy execution only for controlled diagnostics where a server ID is intentionally fixed. For natural-language behavior, prefer runtime chat; use /runtime/execute when an ACP action envelope is required.

MCP Server JSON-RPC Contract​

Customer MCP servers should support standard JSON-RPC requests over HTTPS.

Initialize​

{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {}
},
"clientInfo": {
"name": "thyris-agentic-commerce",
"version": "1.0.0"
}
}
}

List Tools​

{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {}
}

Expected response shape:

{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "search_products",
"description": "Search products in the customer product data.",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string"
}
},
"required": ["query"]
}
}
]
}
}

Call Tool​

{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "search_products",
"arguments": {
"query": "black shoes",
"limit": 25
}
}
}

Expected response shape:

{
"jsonrpc": "2.0",
"id": 3,
"result": {
"toolResult": {
"content": [
{
"type": "text",
"text": "{\"products\":[],\"count\":0}"
}
],
"isError": false
}
}
}

The text field may contain serialized JSON. ACP Engine can normalize this result for the frontend.

MCP Authentication Header​

Customer MCP servers can require an API key header:

X-MCP-API-Key: replace-with-mcp-api-key

ACP Engine stores and sends this key according to the configured MCP server registration. Do not expose MCP API keys to browser clients.

Tool Listing​

When a customer MCP server is registered or updated, ACP Engine can read tools/list and store the approved tool names, descriptions, and input schemas. Refresh the tool list when a downstream MCP server changes its public tool contract.

Session-Aware Tools​

Some tools are stateless and only need arguments:

  • Product search.
  • Product details.
  • Availability lookup.
  • Order status lookup.
  • Customer lookup.

Other tools require a session:

  • Add to cart.
  • Complete cart.
  • Checkout preparation.
  • Multi-step booking.
  • Payment handoff.
  • Stateful browser or workflow automation.

When a tool requires state, pass session_id consistently through ACP Engine and use a stable tool argument such as sessionId if the downstream MCP tool expects it.

Tool Naming Guidance​

Use names that are stable and business-readable:

  • search_products
  • get_product_details
  • add_to_cart
  • complete_cart
  • start_checkout
  • validate_address
  • complete_payment
  • search_bookings
  • get_order_status

Avoid environment-specific or brand-specific names in the public tool contract when a generic action name is enough.

Frontend Rendering Guidance​

A robust frontend should support:

  • Plain assistant text.
  • Product cards and product carousels.
  • Cart summary and order summary views.
  • Address selection or data collection modals.
  • Payment or confirmation steps.
  • Empty state responses.
  • Retryable error states.
  • Session reset and new purchase flows.

The frontend should not depend on downstream MCP implementation details. It should depend on normalized ACP Engine response fields and agreed action names.

Safe Logging Pattern​

Frontend and MCP logs should include:

  • session_id
  • trace_id
  • tool_name
  • mcp_server_id
  • agent_id
  • high-level action
  • status
  • duration

Logs should not include:

  • secret keys
  • full bearer tokens
  • payment credentials
  • private customer tokens
  • unnecessary personal data