Integration Guide #
Kuzzle IA Agent is a conversational orchestrator that lets users interact with a Kuzzle IoT Platform deployment in natural language. It is composed of a Kuzzle plugin, a Python agent service and an MCP tool server, and is integrated into an existing stack in five steps.
Prerequisites #
- A running Kuzzle IoT Platform stack with the
device-manager,multi-tenancyandkeycloakplugins enabled. - Node.js and npm matching the versions declared in
apps/api/package.json. - Python 3.11 and
uvif the services run outside Docker. - Docker and Docker Compose.
- An
OPENAI_API_KEY. The agent currently runs on OpenAI (gpt-4.1-mini); no other provider is wired in yet. - Access to your private npm registry if Kuzzle Enterprise packages are used.
Step 1 — Enable the Kuzzle plugin #
The plugin kuzzle-plugin-ag-ui (packages/kuzzle-plugin-ag-ui/) is loaded by the Kuzzle application alongside the other IoT Platform plugins. On boot, it:
- registers the
agent:runHTTP route (POST /_/agent/run), - creates a Kuzzle role named
agent.runnerand attaches it to every profile suffixed with-tenant_adminor-tenant_reader.
The role provisioning is scheduled ten seconds after boot; if no tenant profile exists yet, it retries every fifteen seconds until at least one matches. If the environment uses a custom profile naming convention, the auto-attachment does not match and the agent.runner role must be granted to the relevant profiles manually.
If the stack is slow to boot, the Kuzzle plugin init timeout can be bumped in apps/api/environments/<env>/kuzzlerc:
{
"plugins": {
"common": {
"initTimeout": 30000
}
}
}Step 2 — Deploy the Python services #
Two containers make up the runtime: the agent (kuzzle-ai-agent) and the MCP tool server (kuzzle-iot-mcp-server). Both are built from their respective Dockerfile.runner.
services:
kuzzle-iot-mcp-server:
build:
context: ./services/kuzzle-iot-mcp-server
dockerfile: Dockerfile.runner
command: uv run python app/main.py
environment:
- MCP_PACKS=
- KUZZLE_URL=http://kuzzle-api:7512
kuzzle-ai-agent:
build:
context: ./services/kuzzle-ai-agent
dockerfile: Dockerfile.runner
command: uv run uvicorn app.main:app --host 0.0.0.0 --port 8000
ports:
- "8000:8000"
environment:
- KUZZLE_AGENT_PACKS=
- MCP_URL=http://kuzzle-iot-mcp-server:5000/mcp
- OPENAI_API_KEY=${OPENAI_API_KEY}
- USE_MEMORY_SAVER=trueThe plugin reads the agent URL from the AGENT_API_URL environment variable (default: http://kuzzle-ai-agent:8000). It must be set on the kuzzle-api container so it points to the agent service:
services:
kuzzle-api:
environment:
- AGENT_API_URL=http://kuzzle-ai-agent:8000Step 3 — Configure the front-end #
The chat box is shipped as a Vue component (ChatLauncher) by the package @kuzzleio/vue-plugin-kuzzle-iot-chatbox. Install it from your npm registry:
npm install @kuzzleio/vue-plugin-kuzzle-iot-chatboxIt is then mounted in the application layout, alongside the KLayout component provided by @kuzzleio/iot-platform-frontend:
<template>
<KLayout :navbar-items="navbarItems" :sidebar-items="sidebarItems" />
<ChatLauncher class="tw:fixed tw:top-[0.33em] tw:right-36 tw:z-[51]" />
</template>
<script setup lang="ts">
import { KLayout } from '@kuzzleio/iot-platform-frontend';
import { ChatLauncher } from '@kuzzleio/vue-plugin-kuzzle-iot-chatbox';
</script>The launcher streams the chat conversation to the agent:run endpoint over Server-Sent Events (via an HttpAgent from @ag-ui/client), authenticated with the logged-in user's Kuzzle token. No additional configuration is required when the user is logged in.
Step 4 — Register a pack #
The agent boots with no capability. Features are added as packs, made of two complementary halves: an MCP pack declaring the tools, and an agent pack declaring the intents that call them. A pack may ship only one half when the other is not needed.
MCP side — declare the tools #
An MCP pack is a Python module exposing a PACK: Pack attribute whose mcp_tools lists plain functions. Each tool is registered onto the MCP server, namespaced with the pack id (hello_say_hello).
# app/packs/builtin/hello.py
from app.packs.protocols import Pack
def say_hello(name: str | None = None, kuzzle_token: str | None = None) -> dict:
"""Renvoie un hello world."""
return {"message": f"Hello {name or 'world'}!"}
PACK = Pack(
id="hello",
description="Demo pack: validates MCP tool registration from a pack.",
mcp_tools=[say_hello],
)Declare it on the MCP server:
MCP_PACKS=app.packs.builtin.helloAt boot the server logs:
[packs] registered tool 'hello_say_hello' from app.packs.builtin.helloAgent side — declare the intents #
An agent pack exposes a PACK: Pack whose intents describe how the LLM recognises a request and which handler runs it. A handler calls an MCP tool with tool_invoke and parses its result with parse_mcp. help_entries feed the dynamic help message.
# graph/packs/builtin/hello/__init__.py
from graph.packs.protocols import Intent, Pack
from graph.tools import tool_invoke, parse_mcp
async def _say_hello(state: dict, data: dict) -> dict:
raw = await tool_invoke("hello_say_hello", {"name": data.get("name")})
payload = parse_mcp(raw)
return {
"status": "success",
"message": payload.get("message", "Hello world!"),
"origin_intent": "hello",
}
PACK = Pack(
id="hello",
description="Demo pack: validates the pack loading and dispatch wiring.",
help_entries=[
"HELLO Renvoie un hello world (via le tool MCP)",
],
intents=[
Intent(
name="hello",
description="salutation simple : répondre via le tool MCP.",
handler=_say_hello,
),
],
)Declare it on the agent service:
KUZZLE_AGENT_PACKS=graph.packs.builtin.helloAt boot the agent logs:
[packs] loaded 'hello' from graph.packs.builtin.helloIntent names must be unique across all agent packs, and tool names across all MCP packs; both registries reject collisions at boot. For multiple packs, separate the module paths with commas.
Step 5 — Verify the end-to-end flow #
With a valid Kuzzle token, the endpoint can be called directly:
curl -N -X POST http://localhost:7512/_/agent/run \
-H "Authorization: Bearer $KUZZLE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"messages": [{ "content": "hello" }],
"threadId": "test-1",
"context": [
{ "description": "engineGroup", "value": "asset_tracking" },
{ "description": "engineIndex", "value": "tenant-asset_tracking-admin" }
]
}'The agent reads the last entry of messages as the user input (messages is required — an empty list returns 400 No messages received), threadId keys the conversation state, and context carries the AG-UI fields (engineGroup, engineIndex); soft-tenants are injected by the plugin from the authenticated user, so they are not passed here.
The response streams Server-Sent Events in the AG-UI format and ends with a final text message. Sending aide (or help) returns the built-in capabilities followed by the help_entries of every loaded pack.
Troubleshooting #
[packs] no pack declared — the agent (KUZZLE_AGENT_PACKS) or the MCP server (MCP_PACKS) booted with no pack. Set the relevant variable and recreate the container with docker compose up -d <service>. A plain docker restart does not re-read the .env file.