> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Server

> Expose the Coreflux broker as an MCP server so AI clients can run broker commands and LoT actions over Streamable HTTP

## Let AI Clients Operate the Broker

The Coreflux broker can listen as an **MCP (Model Context Protocol) server**. An AI client — Cursor, Claude Desktop, or any MCP-capable assistant — connects over HTTP, presents a broker-issued token, and calls tools that map to broker commands and your LoT (Language of Things) Actions. You stay in the editor; the assistant deploys Actions, lists connectors, and inspects topics **as a named broker user**, with that user's RBAC.

This is a **broker configuration feature** (`Mcp.Enabled`), not a LoT connector. There is no `DEFINE ROUTE` for it. `TYPE MCP_SERVER` was removed.

> [!TIP]
>
> **Like giving an assistant a badge for the control room.** The badge (a bearer token) is minted for a specific operator. Every tool call is checked against that operator's permissions — not against a shared admin back door.

### When to use this

* You want Cursor or Claude Desktop to **deploy LoT, inspect topics, and run broker commands** against a live broker
* You need a **least-privilege identity** for the assistant (not `root`) so RBAC still applies
* You are **migrating off** a saved `TYPE MCP_SERVER` connector or an old `/sse` client URL

### Three different MCP surfaces

Coreflux uses MCP in three places. They are not interchangeable:

| Surface | Direction | What it is | Docs |
| - | - | - | - |
| **MCP Server** | AI client → this broker | The broker's built-in MCP server. Config (`Mcp.Enabled`), Streamable HTTP at `/mcp`, bearer token. | This page |
| **Documentation MCP** | AI client → docs.coreflux.org | Hosted docs search for assistants. Does **not** control a broker. | [Documentation MCP](/ai/mcp) |
| **MCP connector** | Broker → an external MCP server | LoT `DEFINE ROUTE … WITH TYPE MCP` and `CALL MCP` from Actions. | [MCP Connectors](/latest/lot-language/routes/mcp-routes) |

The Coreflux HUB AI assistant does **not** talk to this MCP server. HUB is a control plane for enabling it and minting tokens — not an MCP client of the broker.

***

Enable the listener, mint a token for a real broker user, point the client at `/mcp`, then verify with `/health` and `$SYS/Coreflux/MCP/Info`.

## Quick Start

The listener starts only when `Mcp.Enabled` is on **and the broker has restarted**. Default bind is loopback on the broker host: `127.0.0.1:6278` at path `/mcp`. In a container, that's the container's own loopback. See [Docker and Kubernetes](#docker-and-kubernetes).

**Broker config**

Add an `Mcp` block to the broker configuration, then restart:

```json wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "Mcp": {
    "Enabled": true,
    "Port": 6278,
    "BindIP": "127.0.0.1",
    "Endpoint": "/mcp"
  }
}
```

Mint a token for an existing broker user. The plaintext is returned **once** in `data.token`:

```bash wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
-createMcpToken operator cursor-local
```

Point the AI client at `http://127.0.0.1:6278/mcp` with `Authorization: Bearer <token>`.

**Environment variables**

Override the file without editing JSON (useful in containers). Restart the broker after setting them:

```bash wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
export COREFLUX_MCP_ENABLED=true
export COREFLUX_MCP_PORT=6278
export COREFLUX_MCP_BIND_IP=127.0.0.1
export COREFLUX_MCP_ENDPOINT=/mcp
```

Then mint a token the same way (`-createMcpToken <username> <token-name>`) and put it in the client's MCP config.

**Coreflux HUB**

In **System → System Configuration**, open the **MCP** tab. Turn **Enabled** on, save, and **restart the broker**. Status stays **Pending restart** until the new `Mcp` block is live.

Click **Create token**, pick a broker user, give the token a name with no spaces, and copy the value immediately. The dialog offers Cursor, Claude Code, and Claude Desktop snippets with `"type": "http"` and the bearer header already filled in.

Older brokers with no `Mcp` key in `$SYS/Coreflux/Config` do not have this tab's editor — update the broker first. See [System Configuration](/hub/system/configuration#mcp).

> [!TIP]
>
> Probe the unauthenticated health path on the MCP port (this is **not** the ops `/health` on port 9100):
>
> ```bash wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
> curl http://127.0.0.1:6278/health
> ```
>
> A healthy listener returns `status: "healthy"` and a `tools_count`. Then subscribe in MQTT Explorer to `$SYS/Coreflux/MCP/Info` — the retained payload includes `http_endpoint` and `status`.

***

## Endpoints

The MCP listener is a **separate HTTP port** from MQTT and from the [operational endpoints](/latest/mqtt-broker/operational-endpoints) (`/health`, `/ready`, `/info`, `/metrics` on port `9100`).

| Path | Auth | Purpose |
| - | - | - |
| `{Endpoint}` (default `/mcp`) | Bearer token | Streamable HTTP JSON-RPC. Every MCP tool call. |
| `/health` | None | Liveness probe. Returns `status`, `server`, `version`, `tools_count`. |
| `/info` | Bearer token | Compact server name, version, transport, path, and advertised tool names. |

Default URL on the broker host:

```text wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
http://127.0.0.1:6278/mcp
```

The server is **HTTP-only**:

* Transport is **stateless Streamable HTTP**. There is no stdio transport.
* `/sse` and `/message` are **rejected**. There is no SSE compatibility mode.
* Native TLS for this listener is **not** in this release. Bind loopback, or terminate TLS in front of a non-loopback bind.

Authenticated requests send:

```http wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
Authorization: Bearer <token>
```

Anonymous MQTT login is **not** inherited. A client with no token gets `401` on `/mcp` and `/info`. The listener still stays up if you enabled it with zero tokens — every request is refused until you mint one.

> [!WARNING]
>
> A bind outside loopback (`0.0.0.0` or a LAN address) sends bearer tokens in the clear. Put TLS in front (a reverse proxy) before exposing the MCP port beyond the broker host.

***

## Docker and Kubernetes

The MCP server has its own HTTP listener. It isn't a path on the MQTT port (`1883`) or the WebSocket port (`5000`). A client URL such as `https://broker.example.com/mcp` reaches it only when the proxy in front of that host sends `/mcp` to port `6278`. A proxy that sends every path to port `5000` delivers the request to the WebSocket listener instead.

Publishing port `6278` isn't enough on its own. Inside a container, `127.0.0.1` is the container's own loopback, so a Docker port mapping or a Kubernetes Service can't reach a listener bound there. The administrator who deploys the broker sets all of these:

| Step | Setting | Why |
| - | - | - |
| Enable | `Mcp.Enabled` or `COREFLUX_MCP_ENABLED=true` | The listener is off by default. Restart the broker after the change. |
| Bind | `Mcp.BindIP` or `COREFLUX_MCP_BIND_IP` set to `0.0.0.0` | The default `127.0.0.1` can't be reached from a published port or from the pod network. |
| Publish | A Docker port mapping, or a container port and Service port `6278` | The broker image and the sample Compose file don't publish `6278`. |
| Route | A proxy or Ingress path `/mcp` to port `6278` | Send only this path to the MCP port. Keep `/` on the WebSocket port. |
| Allow | A firewall or NetworkPolicy rule for `6278`, from the proxy only | A policy that allows only `1883` and `5000` drops MCP traffic. |
| TLS | Terminate TLS on the proxy | The listener speaks plain HTTP, and bearer tokens must not cross the network in the clear. |
| Token | `-createMcpToken`, then `Authorization: Bearer <token>` on the client | `/mcp` doesn't accept anonymous MQTT login. |

If you changed `Port` or `Endpoint`, use those values instead of `6278` and `/mcp`.

**Docker Compose**

Add the MCP port and the two variables to the broker service:

```yaml wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
services:
  broker:
    image: coreflux/coreflux-mqtt-broker:latest
    ports:
      - "1883:1883"
      - "5000:5000"
      - "9100:9100"
      - "127.0.0.1:6278:6278"   # MCP, reached only through the TLS proxy
    environment:
      - COREFLUX_MCP_ENABLED=true
      - COREFLUX_MCP_BIND_IP=0.0.0.0
```

The `127.0.0.1:` prefix keeps the port off the host's public interfaces, so only a proxy on the same host reaches it. A proxy in the same Compose network can use `broker:6278` and needs no published port.

**Kubernetes**

Name the MCP port on the broker container and the Service, then add an Ingress path for `/mcp` next to the WebSocket path:

```yaml wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
# Broker container
ports:
  - name: mcp
    containerPort: 6278
env:
  - name: COREFLUX_MCP_ENABLED
    value: "true"
  - name: COREFLUX_MCP_BIND_IP
    value: "0.0.0.0"

# Service
ports:
  - name: mcp
    port: 6278
    targetPort: mcp

# Ingress rule on the public host, next to the "/" path to port 5000
paths:
  - path: /mcp
    pathType: Prefix
    backend:
      service:
        name: coreflux-broker
        port:
          number: 6278
```

Let the Ingress terminate TLS. If a NetworkPolicy guards the broker, allow TCP `6278` from the ingress controller's namespace.

`/health` and `/info` are on the same listener. Route them to `6278` too if operators should reach them through the public host. `/info` needs a token. The AI client needs only `/mcp`.

### Check the route

From outside the cluster or host, call `/mcp` without a token:

```bash wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
curl -i https://broker.example.com/mcp
```

The MCP listener answers `401` with `WWW-Authenticate: Bearer` and asks for a token from `-createMcpToken`. Any other answer, such as a `404` with an empty body, means the request reached a different listener, usually the WebSocket port. Then point the client at the same URL with its bearer token.

***

## Configuration reference

Environment variables override the JSON block. Changes apply on **restart**.

```json wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "Mcp": {
    "Enabled": true,
    "Port": 6278,
    "BindIP": "127.0.0.1",
    "Endpoint": "/mcp",
    "EnableAllTools": true,
    "EnabledTools": [],
    "TokenLifetimeDays": 0
  }
}
```

| Setting | Required | Default | Description |
| - | - | - | - |
| `Enabled` | Yes, to start | `false` | Master switch. Applied at startup. |
| `Port` | No | `6278` | Listening port for Streamable HTTP. |
| `BindIP` | No | `127.0.0.1` | Bind address. Non-loopback needs TLS in front. |
| `Endpoint` | No | `/mcp` | Streamable HTTP path. Must be an absolute path. `/sse` and `/message` are rejected. |
| `ServerName` | No | `CorefluxBroker` | MCP protocol server name (HUB: **Advanced**). |
| `ServerVersion` | No | Broker version | Sent to the client during discovery. |
| `EnableAllTools` | No | `true` | When `true`, advertise the full MCP surface (registry tools, handwritten tools, capability tools, and `action_*`). When `false`, only names in `EnabledTools`. |
| `EnabledTools` | When `EnableAllTools` is `false` | `[]` | Allow-list of **advertised MCP names** (`add_route`, `get_route_template`). Command tokens such as `-addRoute` are also accepted. An empty list advertises nothing. |
| `TokenLifetimeDays` | No | `0` | Default lifetime for newly minted tokens, in days. `0` means no expiry. Per-token `ttlDays` on `-createMcpToken` overrides this. |

### Environment variables

| Variable | Config key |
| - | - |
| `COREFLUX_MCP_ENABLED` | `Enabled` (`1` or `true`) |
| `COREFLUX_MCP_PORT` | `Port` |
| `COREFLUX_MCP_BIND_IP` | `BindIP` |
| `COREFLUX_MCP_ENDPOINT` | `Endpoint` |
| `COREFLUX_MCP_ENABLE_ALL_TOOLS` | `EnableAllTools` |
| `COREFLUX_MCP_ENABLED_TOOLS` | `EnabledTools` (comma- or semicolon-separated) |
| `COREFLUX_MCP_TOKEN_LIFETIME_DAYS` | `TokenLifetimeDays` |

HUB **Advanced** also exposes `ServerName` and `ServerVersion`. Restrict the tool list from HUB with **Expose all tools** off and the allow-list picker — write advertised names (`get_route_template`), not internal command tokens (`route_code`). Add `action_*` names with **Custom tool**.

***

## Bearer tokens

Every MCP HTTP request except `GET /health` requires a broker-issued bearer token. Tool calls run as **the broker user the token was minted for** and are checked against that user's RBAC.

These three commands are operator-only. They are **not** advertised as MCP tools.

| Command | Permission | What it does |
| - | - | - |
| `-createMcpToken <username> <token-name> [ttlDays]` | `McpTokenManagement` | Mint a token. Plaintext returned **once** in `data.token`. |
| `-listMcpTokens [username]` | `McpTokenManagement` | Metadata only: `id`, `name`, `userName`, `created`, `expires`, `lastUsed`, `state`. Never the secret or its hash. |
| `-revokeMcpToken <token-id\|token-name>` | `McpTokenManagement` | Immediate refuse for clients using that token. |

The username must already exist. Removing a broker user also deletes that user's tokens. Token names must not contain whitespace (the command parser splits on spaces).

The locked built-in `AllowMcpTokenManagement` admits `root`, `AllowedUserManagement`, and `AllowedSystemConfiguration` — the same flags as other user-management gates. You cannot override that gate with a custom rule; set permission flags instead. See [Operation scopes](/latest/lot-language/rules/syntax#operation-scopes).

### Create a token

Publish to `$SYS/Coreflux/Command` (or run it from [Command Console](/hub/system/command-console) / the broker console):

```bash wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
-createMcpToken operator cursor-local
```

Optional third argument is lifetime in days (`0` = no expiry), overriding `Mcp.TokenLifetimeDays`.

Success envelope (secret redacted here):

```json wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "success": true,
  "command": "CreateMcpToken",
  "entityType": "McpToken",
  "entityName": "cursor-local",
  "data": {
    "token": "cfx_mcp_…",
    "id": "…",
    "name": "cursor-local",
    "userName": "operator",
    "expires": "never"
  }
}
```

Store `data.token` immediately. `-listMcpTokens` will never show it again.

> [!WARNING]
>
> MQTT command replies always go to `$SYS/Coreflux/Command/Output`. The default `AllowSubscribeCommandOutput` audience is **wider** than `McpTokenManagement`, so a user who cannot mint tokens may still read a mint reply. To keep the plaintext off that topic, mint from the broker console or from the HUB MCP tab (HUB keeps the value in the create dialog only). If a token may have been observed, `-revokeMcpToken` and mint again.

Mint for a **least-privilege** account. Do not mint for `root` unless the client should have full operator access.

***

## Point a client at the broker

Put the same JSON shape in the file your client actually reads. `"type": "http"` is optional in Cursor (it infers HTTP from `url`) but **required** in Claude Code and Claude Desktop — without it they skip the entry with no error.

**Cursor**

Project file: `.cursor/mcp.json` (filename is `mcp.json`, not `.mcp.json`). Global fallback: `~/.cursor/mcp.json`.

```json wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "mcpServers": {
    "coreflux-broker": {
      "type": "http",
      "url": "http://127.0.0.1:6278/mcp",
      "headers": {
        "Authorization": "Bearer <token>"
      }
    }
  }
}
```

**Claude Code**

Project file: `.mcp.json` at the **repository root** (not under `.claude/`).

```json wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "mcpServers": {
    "coreflux-broker": {
      "type": "http",
      "url": "http://127.0.0.1:6278/mcp",
      "headers": {
        "Authorization": "Bearer <token>"
      }
    }
  }
}
```

**Claude Desktop**

OS app config (`claude_desktop_config.json`), not a file in the repo. `"type": "http"` is required.

```json wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "mcpServers": {
    "coreflux-broker": {
      "type": "http",
      "url": "http://127.0.0.1:6278/mcp",
      "headers": {
        "Authorization": "Bearer <token>"
      }
    }
  }
}
```

Do not commit a minted token. After changing the file, start a new Claude Code session. In Cursor, if the server shows resources but **0 tools**, remove the entry, save, and add it back (or quit Cursor) — an empty `tools/list` result can stay cached in memory; reloading the server is not enough.

When the broker binds `0.0.0.0`, `$SYS/Coreflux/MCP/Info` still prints that bind in `http_endpoint`. HUB substitutes the host it is connected to when offering snippets; a loopback bind is copied as written.

***

## What the tools do

A connected client calls `tools/list` on `/mcp`. The exact set is whatever this broker advertises (`/health` `tools_count` and `$SYS/Coreflux/MCP/Tools`). It is not a second copy of `$SYS/Coreflux/Command`.

Typical surface when `EnableAllTools` is `true`:

| Kind | Examples | Notes |
| - | - | - |
| Broker command wrappers | `add_action`, `list_routes`, `load_project` | Most management verbs. Each call runs as the token's user. |
| Things | `add_thing`, `remove_thing`, `list_things` | Wrap `-addThing`, `-removeThing`, and `-listThings`. `add_thing` takes one or more `DEFINE THING` blocks; a leading `--dryRun` returns the plan and changes nothing. |
| Disable and enable | `disable_action`, `enable_action`, `disable_route`, `enable_route`, `disable_agent`, `enable_agent` | Switch an entity off or on without deleting it. See [Disable and enable](/latest/mqtt-broker/commands#disable-and-enable). |
| Discovery helpers | `get_broker_info`, `get_available_commands`, `list_action_tools` | Handwritten; not MQTT command names. |
| Keep-list names | `get_route_template`, `list_route_templates`, `get_panel_code` | Use these in `EnabledTools`, not `route_code` / `list_templates` / `panel_code`. |
| Capability tools | `explore_topics`, `diagnose`, `provision_data_source`, `deploy_lot` | An allow-list must name them **exactly**. `explore` does not enable `explore_topics`. |
| LoT Actions | `action_{name}` | Registered when the MCP server **starts**. Restart the broker after deploying new Actions if they should appear as tools. |

Token, license, and password commands are **never** advertised (`-createMcpToken`, `-listMcpTokens`, `-revokeMcpToken`, `-changeUserPassword`, `-activateLicense`, `-loadLicense`, `-refreshLicense`, `-enroll`).

User-management tools (`add_user`, `remove_user`, `change_user_settings`, `add_user_to_group`, `remove_user_from_group`) **are** on the default surface. `add_user` takes a password in its arguments — chat transcripts will see it. Mint the token for a user without user-management permission, or turn `EnableAllTools` off and omit those names.

List-shaped tools default to a concise page of 50 items (`items`, `total`, `next_cursor`). Pass `response_format=detailed` for full objects.

> **Capability tools**
>
> | Tool | Purpose |
> | - | - |
> | `explore_topics` | Paginated MQTT topic listing with JSON schema inference. Does not wait for live publishes. |
> | `diagnose` | Connection status, Action errors, and pending status. Takes **no** arguments. |
> | `provision_data_source` | Add an OT or database connector from LoT (`route_type`, `name`; optional `definition`). |
> | `deploy_lot` | Deploy `DEFINE ACTION` / `MODEL` / `CONNECTOR` (or `ROUTE`) / `AGENT` / `RULE` / `THING` blocks in one call. Agent blocks go through `-addAgent`, and consecutive `DEFINE THING` blocks are sent as one `-addThing`. On first failure it rolls back entities **this call** created or overwrote. |
>
> `lot_diagnostic` is a **process-wide** tracing toggle (`true` / `false`), not a per-entity report. Use `diagnose` for health and Action error topics for compile failures.

> **Resources and prompts**
>
> Clients can call `resources/list` and `resources/read`. There is no `resources/subscribe` (the server is stateless). Unknown URIs return JSON-RPC `-32602` (Invalid params).
>
> | URI | Contents |
> | - | - |
> | `coreflux://topics` | First concise page of the topic tree |
> | `coreflux://topics/{topic}` | One topic snapshot (`/` in the topic is URI-escaped as `%2F`) |
> | `coreflux://lot/{kind}` | LoT inventory (`actions`, `models`, `routes`, `agents`, `rules`, `things`) |
> | `coreflux://lot/{kind}/{name}` | One entity definition |
> | `coreflux://skills` | Agent skill catalogue |
> | `coreflux://skills/{name}` | Full skill body |
>
> To install the public skill set straight into a coding agent instead, see [Agent Skills](/ai/agent-skills).
>
> Reads require `CommandCall` on the token's user. Topic URIs are filtered again by that user's subscribe rules (`Subscribe` / `SubscribeSys`).
>
> `prompts/list` advertises `lot_authoring` (optional `entity_type`: `action`, `model`, `route`, `rule`).

***

## System topics

When the MCP host starts or stops, it republishes two **retained** topics. Neither payload includes token values, hashes, or the usernames tokens map to.

### `$SYS/Coreflux/MCP/Info`

The live payload lists every advertised tool name. The sample below is abbreviated.

```json wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "server_name": "CorefluxBroker",
  "server_version": "2.22.0",
  "transport_type": "http",
  "http_endpoint": "http://127.0.0.1:6278/mcp",
  "http_port": 6278,
  "bind_ip": "127.0.0.1",
  "total_tools": 2,
  "tools": ["add_action", "list_routes"],
  "tokens_active": 1,
  "auth": "bearer",
  "status": "running",
  "connection_info": "Connect via Streamable HTTP at http://127.0.0.1:6278/mcp with an Authorization: Bearer header.",
  "timestamp": "2026-09-21T10:00:00.000Z"
}
```

`status` is `running` or `stopped`. A clean shutdown republishes `stopped` so a stale retain does not claim the server is up.

### `$SYS/Coreflux/MCP/Tools`

An array of `{ "name": "…" }` objects — currently advertised MCP names only, not the full possible catalogue. When `EnableAllTools` is false and `EnabledTools` is empty, this topic is an empty list.

> [!NOTE]
>
> These topics are `$SYS`. Subscribe with an identity that is allowed to read system topics (MQTT Explorer as an admin user). HUB maps `status: "running"` to **Listening**. If Info is missing after a saved, applied `Mcp.Enabled: true`, HUB still treats the server as Listening rather than as another pending restart.

The ops HTTP `/info` on port **9100** (separate from MCP `/info` on **6278**) includes a compact `mcp` object when the feature is enabled (`enabled`, `endpoint`, `bind`, `port`, `toolsCount`, `tokensActive`, `auth`) — still no token secrets. See [Operational endpoints](/latest/mqtt-broker/operational-endpoints#cluster-and-mcp-summaries).

***

## Moving off `TYPE MCP_SERVER` and SSE

A saved project that still carries `DEFINE ROUTE … WITH TYPE MCP_SERVER` **fails to load** with a message that the type was removed. The MCP *client* connector (`TYPE MCP`, `ADD MCP_CONFIG`, `CALL MCP`) is unchanged.

**Action:**

1. Remove the `TYPE MCP_SERVER` definition from the project.
2. Enable `Mcp.Enabled` (or `COREFLUX_MCP_ENABLED=1`) and restart the broker.
3. Mint a token: `-createMcpToken <username> <token-name>`.
4. Point the client at `http://127.0.0.1:6278/mcp` (or your bind, port, and endpoint) with `Authorization: Bearer <token>`.

| Before (broken) | After |
| - | - |
| URL `http://127.0.0.1:6278/sse` | URL `http://127.0.0.1:6278/mcp` |
| Separate `/message` URL | Same `/mcp` path for every JSON-RPC POST |
| `WITH TRANSPORT_TYPE "sse"` on `TYPE MCP_SERVER` | `Mcp.Enabled`; transport is HTTP-only |
| `WITH SSE_ENDPOINT` / `WITH MESSAGE_ENDPOINT` | `Mcp.Endpoint` (default `/mcp`) |

There is no compatibility mode. Every existing `/sse` client configuration stops working.

***

## Best practices

> **Least-privilege tokens**
>
> Mint a dedicated broker user for the assistant. Leave user-management and `AllowedSystemConfiguration` off unless the client must administer the broker. Prefer `EnableAllTools: false` plus an allow-list of the tools that job needs.

> **Loopback first**
>
> Keep `BindIP` at `127.0.0.1` when the AI client runs on the same host as the broker. Only bind a LAN address when a remote client must connect, and terminate TLS in front. In a container, bind `0.0.0.0` and reach the port only through the TLS proxy. See [Docker and Kubernetes](#docker-and-kubernetes).

> **Treat mint replies as secrets**
>
> Prefer the HUB create-token dialog or the broker console over MQTT when other administrators share `$SYS/Coreflux/Command/Output`. Revoke and re-mint if a token may have leaked into a chat log.

> **Do not confuse health ports**
>
> MCP `/health` is on the MCP port (default `6278`). Orchestrator probes for the broker process still use ops `/health` on port `9100`. See [Operational endpoints](/latest/mqtt-broker/operational-endpoints).

> [!WARNING]
>
> The MCP server does not speak OAuth 2.0 / OIDC. Identity is the minted bearer token only. An [OIDC access token](/latest/mqtt-broker/oidc-authentication) used as an MQTT password is a different credential and is not accepted on `/mcp`. Don't publish the MCP port directly on the internet. Reach it only through a proxy that terminates TLS.

***

## Next Steps

> **[MCP Connectors](/latest/lot-language/routes/mcp-routes)**
>
> Connect the broker as a client to external MCP servers and call their tools from LoT Actions.

> **[System Configuration](/hub/system/configuration#mcp)**
>
> Enable the listener and mint tokens from the Coreflux HUB MCP tab.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.