Skip to content
archmax.ai

Chat, AG-UI and MCP endpoints

Members of the organization talk to an environment’s published workflows on the Chat page, which needs no setup beyond a chat trigger. Everything outside the product, such as a website’s chat widget, another company’s agents or a sending system, reaches a workflow through an incoming connection: a connection that owns one endpoint and decides who may call it.

Chat in the icon rail opens the environment’s Chat page. A member holds production chats there with the workflows the current published version offers: those with a chat trigger that are in service. While none does, the page says a workflow with a chat trigger has to be published first.

  • New chat starts a chat with the one chat workflow, opens a menu of them by title when there are several, and is disabled when there is none. Opening the page or pressing New chat creates nothing; the chat is created with its first message or file.
  • A new chat opens on the start view: the agent’s icon, name and agent description, the workflow’s title when more than one workflow takes chats, and the workflow’s example prompts.
  • The history lists your own chats under Last 7 days and Older, eight at first and more with Show more. Search matches titles and the text of every message. A row’s context menu deletes the chat.
  • A chat runs the published version that was current when it started; publishing again does not change it.
  • Replies stream with their tool calls. A reply that parked reads Waiting: a chat waiting for input takes your next message, and one waiting for a decision takes none until the decision is made (see Sessions and decisions).
  • While a reply is being written, the composer’s Send becomes Stop, which cancels that turn; Enter sends nothing meanwhile. The reply keeps what it had produced and ends with a Cancelled note rather than a failure. The editor’s chat surface has the same Stop.

A chat belongs to the member who started it. Another member’s chat is not found, and the Sessions page marks it as personal with a lock. To try a draft, use the start action on a chat start state in the editor, which opens a testing chat over the workflow’s session view (see Workflows and the editor).

A chat trigger can list up to six example prompts, each at most 200 characters. Choosing one puts it in the composer without sending it. The agent never reads them, and editing them changes no deployment.

states:
answer:
triggers:
chat:
session: triggers.-1.threadId
examples:
- Where is my order 4711?
- I want to return a damaged item.

Register one on the Connections page from the catalog’s incoming entries: AG-UI endpoint, MCP endpoint or Webhook. The protocol is fixed for the connection’s life. Registering mints its endpoint’s address in this environment, shown on the row with Copy URL:

Protocol Address Serves
AG-UI endpoint (agui) /api/agui/<token> the chat trigger of one workflow
MCP endpoint (mcp) /api/mcp/<token> every mcp trigger that names it, one tool per workflow
Webhook (webhook) /api/webhooks/<token> the webhook trigger of one workflow

The address is stable: publishing, editing the connection, or removing and restoring a trigger leave it unchanged, and deleting the connection and registering the same identifier again restores it. An inherited connection gets an address of its own in each environment. The address is never written to a file or shown to an agent.

An endpoint serves nothing until the published version holds a trigger naming its connection; registering alone opens nothing. The trigger names it with connection:

states:
answer:
triggers:
chat:
session: triggers.-1.threadId
connection: support-widget
intake:
triggers:
intake:
type: webhook
connection: orders-hook

A chat, mcp or webhook trigger that names no connection has no endpoint (a chat one is still on the Chat page). Two triggers naming one AG-UI or webhook connection both fail to deploy, naming the connection and the workflows, until one remains. The row reads Serving with a count, Not used, or Error with the deployment’s message. A request to an address that serves nothing gets 404, whatever the reason.

Edits to a connection’s mode, origins and limits take effect when the environment is published. Regenerating an endpoint token takes effect at once.

Each connection declares one Authentication mode:

Mode UI label A caller is admitted when Protocols
public None: anyone with the URL always AG-UI, webhook
bearer Endpoint token it sends Authorization: Bearer <endpoint token> all three
oidc OIDC it sends a JWT the declared issuer signed all three

The endpoint token is issued by the platform, starts with amx_, and is shown once, when you register or switch to bearer. Only its SHA-256 digest is stored, so it cannot be shown again. Regenerate issues a new one and the old one stops working at once. Under oidc, the connection declares an Issuer and an Audience, and optionally a JWKS URL and the accepted algorithms (RS256 when unset). The audience is required: without it, every token the issuer signs for any application would be admitted. No platform user is created for the token’s subject.

A member calling an AG-UI or MCP endpoint with their own platform sign-in is admitted whatever the mode, when their role on the agent holds workflows:use (a User); without it, the call is treated as any other caller’s and the mode decides. A webhook reads no platform sign-in. A credential opens only its own connection’s endpoint and nothing else of the platform.

Browser origins list the sites whose pages may call an AG-UI or MCP endpoint, one per line; blank means server-to-server only, and * allows any site. A call from a page on any other site is refused before anything starts. A webhook takes none.

A member’s sign-in counts on these endpoints only when it is the member’s own doing: a bearer token, or a proxy sign-in’s session from the platform’s own pages. A page on another site, even a declared one, that a signed-in member happens to visit calls as one of the connection’s own callers, never as that member.

Calls per minute sets two ceilings: per caller (the token’s subject, otherwise the client address), and for the endpoint in total. Blank uses the protocol’s defaults: 30 and 300 for AG-UI and MCP, 600 and 600 for a webhook. A refused call gets 429 with a retry time, before any session is opened, and only calls that would start a turn count. Members are bound by the platform’s own per-user ceiling instead.

Every session an endpoint opens runs in the organization’s scope and appears on the Sessions page for every member. One an AG-UI or MCP caller opened records the connection it came through and, under oidc, the caller’s subject.

An AG-UI client posts a RunAgentInput to the address and reads the turn as an AG-UI event stream (server-sent events, or the protocol’s binary encoding when Accept asks for it):

Terminal window
curl -N "https://<host>/api/agui/<token>" \
-H "Authorization: Bearer amx_..." \
-H "Content-Type: application/json" \
-d '{"threadId":"","runId":"r-1","tools":[],"context":[],
"messages":[{"id":"m1","role":"user","content":"Where is my order 4711?"}]}'
  • The platform owns the thread id. The stream’s RUN_STARTED event carries the one to send next; a value it did not issue to this caller opens a fresh chat.
  • A message continues the chat where its previous turn left off; it does not replay history. While a turn is still running, or the chat waits for a decision, a new message is refused with 409.
  • A turn that parks ends its stream with a RUN_FINISHED event whose outcome describes the pending decision or wait. A resume entry naming it resolves it as the platform’s own surfaces would: it records the decision, or delivers the firing it names.
  • Client-supplied tools and context are not honoured, and the stream says so. An external caller cannot attach uploaded files.
  • A workflow that runs another one as a child session reports it as archmax.sub_workflow_start and archmax.sub_workflow_result custom events, and frames the child’s messages with archmax.sub_workflow_frame. Each names the child session by its dispatch id: 2 for the session’s second dispatch, 1~1 for the first one made inside dispatch 1. A dispatch made inside another one also names it as parent. Treat the id as an opaque string, unique within the turn.

Chats an endpoint opens appear in no member’s chat history.

An MCP client connects to the address over Streamable HTTP; the endpoint keeps no MCP session. It lists one tool per workflow it serves, named after the workflow’s slug and described by the trigger’s description. The tool’s input schema comes from the trigger’s requires, and its result carries the trigger’s returns:

states:
refund:
triggers:
mcp:
connection: partner-agents
description: Refund one order and report the amount.
requires:
- { name: order_id, type: string, description: The order to refund. }
returns:
- { name: total, type: number }

Every call opens a new session at the mcp entry with its arguments as locked variables, and answers when the turn settles: status (succeeded, awaiting_decision, awaiting_input, failed, cancelled), a message, and on success the returns. When the platform stops waiting first, the status is running and the session carries on. A non-conforming argument, a workflow out of service or a reached spend cap is a tool error, and no session opens. trace: true on the trigger adds the session’s steps and tool calls to every result. An MCP call takes no files: an mcp trigger declaring a type: file entry in requires or returns is a validation error, and its deployment is in error, so the endpoint does not list its tool. See the signature.

A refused request gets 401 with a Bearer challenge; under oidc it names the endpoint’s protected resource metadata, /api/mcp/<token>/.well-known/oauth-protected-resource.

A sending system POSTs to the address; 202 answers with { "delivery": "<id>" }, and the firing is processed off the request. The firing’s payload holds the method, the content-type, user-agent and x-request-id headers, the query and the body (parsed when it is JSON, at most 256 KiB). An X-Delivery-Id header names the delivery, so a sender’s retry fires once; without it, the body’s digest serves as its id. Payload fields, trigger guards and testing a webhook are on Triggers.