> For the complete documentation index, see [llms.txt](https://coldbox.ortusbooks.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://coldbox.ortusbooks.com/the-basics/routing/routing-dsl/ai-routing.md).

# AI & MCP Routing

{% hint style="warning" %}
AI Routing requires **BoxLang** and the **bx-ai** module. It is not available on CFML engines.
{% endhint %}

ColdBox 8.1 introduces two powerful routing terminators for building AI-powered HTTP APIs:

* `toAi()` — Registers four standard AI inference endpoints (invoke, stream, batch, info) for any `IAiRunnable` object
* `toMCP()` — Registers a Model Context Protocol (MCP) server endpoint handled by `MCPRequestProcessor`

Both terminators behave like the `resources()` terminator: a single declaration expands to multiple concrete routes and all shared route **modifiers** (`.as()`, `.withModule()`, `.withDomain()`, etc.) are inherited by every generated sub-route.

***

## toAi()

### Overview

```javascript
route( "/api/assistant" ).toAi( "models.AssistantAgent" );
```

A single `toAi()` call registers **four** routes automatically:

| HTTP Verb | Pattern                 | Calls on the runnable                       | Purpose                                     |
| --------- | ----------------------- | ------------------------------------------- | ------------------------------------------- |
| `POST`    | `/api/assistant/invoke` | `run( input, params, options )`             | Synchronous single-turn inference           |
| `POST`    | `/api/assistant/stream` | `stream( onChunk, input, params, options )` | Server-Sent Events (SSE) streaming output   |
| `POST`    | `/api/assistant/batch`  | `run()` for each item in `inputs[]`         | Batch inference                             |
| `GET`     | `/api/assistant/info`   | `getName()` (no call for `description`)     | Metadata — name, description, endpoint list |

### Arguments

```javascript
route( pattern ).toAi( runnable )
```

| Argument   | Type             | Description                                                                                 |
| ---------- | ---------------- | ------------------------------------------------------------------------------------------- |
| `runnable` | string or object | A WireBox ID string (resolved lazily on every request) **or** a live `IAiRunnable` instance |

There's no separate `name` argument — sub-route names are derived from `.as()`/the route's own name if you've set one, or from the pattern otherwise, exactly like every other terminator.

### Basic Example

```javascript
// config/Router.bx
function configure(){

    // Register a single AI agent — four endpoints created automatically
    route( "/api/chat" ).toAi( "models.ChatAgent" );

}
```

This produces the following routes:

```
POST /api/chat/invoke  → chatAgent.run( input, params, options )
POST /api/chat/stream  → chatAgent.stream( onChunk, input, params, options )
POST /api/chat/batch   → chatAgent.run( item, params, options ) per item in inputs[]
GET  /api/chat/info    → chatAgent.getName() / getDescription()
```

### Modifier Inheritance

All standard route modifiers are inherited by every sub-route generated by `toAi()`:

```javascript
route( "/api/chat" )
    .withModule( "myModule" )
    .withDomain( "api.myapp.com" )
    .toAi( "models.ChatAgent" );
```

### Invoke Endpoint

The `invoke` action calls `run()` on the runnable for a standard synchronous request/response cycle.

**Request body (JSON):**

```json
{
  "input": "Summarize this document",
  "params": {},
  "options": {}
}
```

**Response (JSON):**

```json
{
  "output": "This document covers...",
  "success": true,
  "threadId": "5b1e2c7a-..."
}
```

`input`/`params`/`options` are all optional and forwarded as-is to `runnable.run( input, params, options )`. `threadId` is always present — see [Conversational Context](#conversational-context) below.

### Stream Endpoint

The `stream` action calls `stream()` on the runnable and pipes each chunk out as a **Server-Sent Events (SSE)** frame. The client should set `Accept: text/event-stream`. See [Server-Sent Events](/the-basics/event-handlers/server-sent-events.md) for the underlying `event.sse()` mechanics this endpoint builds on.

**Request body (JSON):** same shape as `invoke` - `{ "input": ..., "params": {}, "options": {} }`

**SSE Response:**

```
event: thread
data: {"threadId":"5b1e2c7a-..."}

event: chunk
data: {"token": "Code"}

event: chunk
data: {"token": " flows"}

event: done
data: [DONE]
```

The leading `thread` frame carries the resolved `threadId` - the response header (below) isn't readable from a browser `EventSource`, so this is how a browser client learns a server-generated thread id in time to persist it.

### Batch Endpoint

The `batch` action calls `run()` once per item in `inputs[]`, sharing the same `params`/`options` (and resolved conversational context) across every item, and returns the results in the same order.

**Request body (JSON):**

```json
{
  "params": {},
  "options": {},
  "inputs": [
    "Translate: Hello",
    "Translate: Goodbye"
  ]
}
```

**Response (JSON):**

```json
{
  "outputs": [
    { "output": "Hola", "success": true },
    { "output": "Adiós", "success": true }
  ],
  "threadId": "5b1e2c7a-..."
}
```

An item that throws is reported inline instead of failing the whole batch: `{ "error": "<message>", "success": false }`.

### Info Endpoint

The `info` action (`GET`) returns metadata about the AI runnable and the sub-routes registered for it.

**Response (JSON):**

```json
{
  "name": "ChatAgent",
  "description": "",
  "pattern": "/api/chat",
  "endpoints": [
    { "verb": "POST", "path": "/api/chat/invoke", "description": "Synchronous execution" },
    { "verb": "POST", "path": "/api/chat/stream", "description": "Streaming SSE execution" },
    { "verb": "POST", "path": "/api/chat/batch",  "description": "Batch execution" },
    { "verb": "GET",  "path": "/api/chat/info",   "description": "Endpoint metadata" }
  ]
}
```

### Conversational Context

Alongside `input`/`params`/`options`, the request body accepted by `invoke`, `stream`, and `batch` may carry `userId`, `conversationId`, and `threadId`. Whatever's resolved is merged into `options` before the runnable is called, so a runnable reads them at `options.userId`/`options.conversationId`/`options.threadId` with no interface changes:

| Field            | Behavior                                                                                                                                                                                     |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `userId`         | The request body's `userId` if supplied, else the framework's own request/session tracking identifier (`Controller.getUserSessionIdentifier()`) - so every call is attributable to *someone* |
| `conversationId` | Passed through only if supplied. **No default is generated** - an absent `conversationId` means the caller isn't tracking conversations                                                      |
| `threadId`       | Passed through if supplied, otherwise a new one is generated. **Always** present in the result, so a follow-up call can continue the same thread                                             |

`threadId` is echoed back three ways, so it's usable from any client:

* On the JSON response body (`invoke`/`batch`) as `threadId`
* As an `X-Thread-Id` response header (all three sub-routes)
* As a leading `event: thread` SSE frame on `/stream` (shown above)

```javascript
// POST /api/chat/invoke
// { "input": "hi", "threadId": "t-123" }

// → runnable.run( "hi", {}, { userId: "<session id>", threadId: "t-123" } )
// → { "output": ..., "success": true, "threadId": "t-123" }
// → response header: X-Thread-Id: t-123
```

A runnable that wants to persist conversation history reads the resolved context straight off `options`:

```javascript
// models/ChatAgent.bx
class implements="bxModules.bxai.models.runnables.IAiRunnable" {

    function run( input, params = {}, options = {} ){
        var thread = conversationStore.loadOrCreate(
            userId         = options.userId,
            conversationId = options.conversationId ?: "",
            threadId       = options.threadId
        );
        return chatModel.reply( thread, input );
    }

}
```

### IAiRunnable Interface

Your agent class must implement `IAiRunnable` from the **bx-ai** module (or satisfy its duck-typed interface). The methods `toAi()` actually calls are:

```javascript
// models/ChatAgent.bx
class implements="bxModules.bxai.models.runnables.IAiRunnable" {

    // Called by invoke and once per item by batch
    any function run( any input = {}, struct params = {}, struct options = {} ){}

    // Called by stream - invoke onChunk for each chunk produced
    void function stream( required function onChunk, any input = {}, struct params = {}, struct options = {} ){}

    // Used by the info endpoint
    string function getName(){}
    string function getDescription(){}

}
```

`params` configures the operation (model knobs, temperature, etc, overriding whatever defaults the runnable has); `options` carries runtime context - `userId`/`conversationId`/`threadId` land here, alongside anything else you pass through the request body's `options` key.

***

## toMCP()

### Overview

`toMCP()` registers a [Model Context Protocol](https://modelcontextprotocol.io/) server endpoint. MCP is an open standard that allows AI models to interact with external tools, data sources, and services via a uniform API.

```javascript
route( "/mcp/:mcpServer" ).toMCP();
```

### Arguments

```javascript
route( pattern ).toMCP( [name] )
```

| Argument | Type   | Description                                         |
| -------- | ------ | --------------------------------------------------- |
| `name`   | string | Optional route name. Defaults to the route pattern. |

The actual MCP server to execute is resolved from the URL pattern. You can define either a **static** name or use the **`:mcpServer` placeholder** to select the server dynamically from the URL.

### Static Server

```javascript
// Always routes to the "myServer" MCP server
route( "/mcp/assistant" ).toMCP( "myServer" );
```

### Dynamic Server (Recommended)

Use the `:mcpServer` placeholder in your pattern to allow the URL to identify which MCP server to invoke:

```javascript
// /mcp/codeReviewer  →  MCPRequestProcessor dispatching to "codeReviewer"
// /mcp/dataAnalyst   →  MCPRequestProcessor dispatching to "dataAnalyst"
route( "/mcp/:mcpServer" ).toMCP();
```

### Modifier Inheritance

Like `toAi()`, all modifiers are inherited:

```javascript
route( "/mcp/:mcpServer" )
    .withSSL()
    .withDomain( "mcp.myapp.com" )
    .toMCP();
```

### MCP Server Registration

MCP servers are registered via WireBox or the ColdBox configuration. Refer to the [BoxLang AI documentation](https://ai.ortusbooks.com/) and the [Agentic ColdBox](/digging-deeper/ai/agentic-coldbox.md) guide for details on building and registering MCP servers.

***

## Using Both Together

```javascript
// config/Router.bx
function configure(){

    // AI inference for a specific assistant
    route( "/api/v1/chat" )
        .withSSL()
        .toAi( "models.ChatAgent" );

    // MCP server hub — any registered MCP server accessible via URL
    route( "/mcp/:mcpServer" )
        .withSSL()
        .toMCP();

}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://coldbox.ortusbooks.com/the-basics/routing/routing-dsl/ai-routing.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
