| name | setup-bot-connection |
| description | End-to-end guide for setting up a new bot connection in swiss_ai_hub.bot. Covers Azure App Registration, Bot Channels Registration, Teams/Slack channel config, PathEntity creation in bot_paths collection, and DevTunnel for local dev. Use when user says "set up a bot", "connect bot to Teams", "connect bot to Slack", "configure bot channel", "create bot connection", "DevTunnel setup", "bot local development", or "Azure bot registration". Do NOT use for bot handler code scaffolding (use scaffold-bot-handler), bot architecture questions (use bot-framework), or agent debugging (use debug-agent). |
| disable-model-invocation | true |
| allowed-tools | Read, Grep, Glob, Bash |
Bot Connection Setup Guide
Set up a new bot connection. Target channel or question via $ARGUMENTS.
Before You Start
Read packages/bot/CLAUDE.md for full architecture, routes, and essential files.
Key concept: Each bot endpoint has a PathEntity in MongoDB (bot_paths collection) containing Azure AD
credentials (APP_ID, APP_PASSWORD, APP_TENANTID), system message template, and Slack OAuth token.
Prerequisites
- Azure CLI installed and authenticated:
az login
- Azure subscription with permission to create App Registrations and Bot resources
- MongoDB/FerretDB running (for storing PathEntity credentials)
- NATS server running (for agent communication)
- Public endpoint or DevTunnel for the bot server
Option A: Automated Setup (Recommended)
Script: packages/bot/swiss_ai_hub/bot/setup_azure_bot.py
For Teams (Single-Tenant)
cd packages/bot
uv run python swiss_ai_hub/bot/setup_azure_bot.py \
--resource-group "my-resource-group" \
--bot-name "ai-hub-bot" \
--token-url "https://my-domain.com" \
--token-path "/api/v1/active/agent/chat/completions/MyAgent/my_agent_id/json" \
--mongo-connection-string "mongodb://localhost:27017" \
--tenant-id "your-azure-tenant-id" \
--system-message "You are {assistant_name}. The user's name is {username}." \
--location "westeurope" \
--sku "F0"
For Slack (Multi-Tenant)
cd packages/bot
uv run python swiss_ai_hub/bot/setup_azure_bot.py \
--resource-group "my-resource-group" \
--bot-name "ai-hub-slack-bot" \
--token-url "https://my-domain.com" \
--token-path "/api/v1/active/agent/chat/completions/MyAgent/my_agent_id/json" \
--mongo-connection-string "mongodb://localhost:27017" \
--slack-token "xoxb-your-slack-bot-token" \
--system-message "You are {assistant_name}. The user's name is {username}."
What the script does:
- Creates Azure AD App Registration (
az ad app create)
- Creates Service Principal (
az ad sp create)
- Resets credentials โ generates APP_PASSWORD
- Saves PathEntity to MongoDB
bot_paths collection
- Creates Azure Bot Resource (
az bot create)
Option B: Manual Setup (Step-by-Step)
Step 1: Azure AD App Registration
az ad app create --display-name "ai-hub-bot" --sign-in-audience "AzureADMyOrg"
az ad sp create --id <appId>
az ad app credential reset --id <appId>
Save these values:
APP_ID = appId
APP_PASSWORD = password
APP_TENANTID = tenant (for single-tenant/Teams)
Step 2: Create Azure Bot Resource
az bot create \
--app-type "SingleTenant" \
--appid "<APP_ID>" \
--name "ai-hub-bot" \
--resource-group "my-resource-group" \
--display-name "Swiss AI Hub Bot" \
--endpoint "https://your-domain.com/api/v1/active/agent/chat/completions/MyAgent/my_id/json" \
--location "westeurope" \
--sku "F0" \
--tenant-id "<APP_TENANTID>"
Step 3: Create PathEntity in MongoDB
Use the helper script or insert directly:
export BOT_APP_ID="<APP_ID>"
export BOT_APP_PASSWORD="<APP_PASSWORD>"
export BOT_TENANT_ID="<APP_TENANTID>"
export MONGO_CONNECTION_STRING="mongodb://localhost:27017"
cd packages/bot && uv run python swiss_ai_hub/bot/add_path_entity.py
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017")
client["aihub"]["bot_paths"].update_one(
{"path": "/api/v1/active/agent/chat/completions/MyAgent/my_id/json"},
{"$set": {
"path": "/api/v1/active/agent/chat/completions/MyAgent/my_id/json",
"credentials": {
"APP_TYPE": "SingleTenant",
"APP_ID": "<APP_ID>",
"APP_PASSWORD": "<APP_PASSWORD>",
"APP_TENANTID": "<TENANT_ID>",
},
"system_message": "You are {assistant_name}. The user's name is {username}.",
"slack_token": None,
}},
upsert=True,
)
Step 4: Configure Channel in Azure Portal
For Teams:
- Azure Portal โ Bot Services โ your bot โ Channels
- Click "Microsoft Teams" โ Configure
- Enable messaging
- Save
For Slack:
- Create Slack App at https://api.slack.com/apps
- Enable "Bot User OAuth Token" โ copy token (
xoxb-...)
- In Azure Portal โ Bot Services โ your bot โ Channels
- Click "Slack" โ Configure with OAuth redirect
- Save the
slack_token in PathEntity
For Web Chat:
- Azure Portal โ Bot Services โ your bot โ Channels
- Web Chat is enabled by default
- Copy the secret key for embedding
Local Development Setup
You can drive the bot locally from the Microsoft Bot Framework Emulator with no Azure registration, using the
testing playground. (For an authentic Teams/Slack test with real identities, see Real channel via DevTunnel + Azure at
the end.)
Step 0 โ The testing playground runner (main.py)
The runner is packages/bot/playground/testing/main.py โ read that file for the full implementation. It is
local-only โ never an entry point for a deployed bot. It does three things so a live emulator can drive the bot
without Azure:
- Forces the SDK's unauthenticated mode โ monkeypatches
RestChannelServiceClientFactory.create_connector_client
and create_user_token_client to pass use_anonymous=True. Without it the CloudAdapter requires MSAL: you hit
TENANT_ID is not set on inbound and a 401 (rejected token) on the outbound reply.
- Binds
0.0.0.0:8001 (not the runner's hard-coded localhost) so a Windows-hosted emulator can reach it across
the WSL2 boundary, and calls runner.start_simulation() before serving โ that starts the simulated agent's NATS
subscribers; skip it and every chat times out waiting for a reply.
- Optionally stubs the user email via
BOT_DEV_FAKE_EMAIL (see Step 4) โ a no-op when unset, so it never affects
real connector-based resolution.
Step 1 โ Seed a PathEntity for the endpoint
The runner serves the agent at /api/v1/agent/chat/completions/my_agent_class/my_agent_id/json. Seed a matching
PathEntity (empty credentials are fine โ the runner is unauthenticated):
cd packages/bot
uv run python - <<'PY'
from dotenv import load_dotenv; load_dotenv("../../.env")
from mongoengine import connect
from swiss_ai_hub.core.infrastructure import AIHubSettings, MongoSettings
from swiss_ai_hub.bot.persistence.entities.path_entity import Credentials, PathEntity
connect(db=AIHubSettings().MONGO_MAIN_DB_NAME, host=MongoSettings().CONNECTION_STRING.get_secret_value(), uuidRepresentation="standard")
path = "/api/v1/agent/chat/completions/my_agent_class/my_agent_id/json"
PathEntity.objects(path=path).delete()
PathEntity(path=path, credentials=Credentials(APP_TYPE="MultiTenant"),
system_message="You are a helpful local dev assistant.").save()
print("seeded", path)
PY
โ ๏ธ Keep the system_message free of placeholders, or use only one of {username} / {assistant_name}.
CompletionHandler.get_system_message calls .format() twice, so a message containing both placeholders raises
KeyError.
Step 2 โ Start (or restart) the runner
pkill -f main.py
cd packages/bot/playground/testing
uv run python main.py > /tmp/bot_local.log 2>&1 &
Watch logs with tail -f /tmp/bot_local.log. Requires the dev stack's MongoDB/FerretDB, NATS, and Keycloak to be
running. Re-run these three commands after changing BOT_DEV_FAKE_EMAIL (Step 4) โ env vars are read once at startup.
Always confirm exactly one clean instance after (re)starting โ uv run spawns two processes, and if you start a new
one before the old releases port 8001, the new one dies with address already in use while the old instance keeps
serving (so your .env change silently has no effect):
tail -5 /tmp/bot_local.log
ss -ltnp | grep :8001
If you see address already in use, run pkill -9 -f main.py, wait until ss -ltnp | grep :8001 is empty, then start
again.
Step 3 โ Connect the Bot Framework Emulator
Download from https://github.com/microsoft/BotFramework-Emulator/releases. Open Bot โ URL:
http://localhost:8001/api/v1/agent/chat/completions/my_agent_class/my_agent_id/json
Leave App ID / Password empty โ Connect โ send a message.
WSL2 note (Windows + WSL2 only)
On native Linux/macOS, the emulator and bot share localhost โ skip this section; the URL above and replies just
work. On WSL2 (bot in Linux, emulator on Windows), localhost bridges neither direction:
-
Emulator โ bot: connect via the WSL2 IP, not localhost. Get it with hostname -I (e.g. 172.23.171.112) and
use http://<WSL_IP>:8001/.... (The runner already binds 0.0.0.0.)
-
Bot โ emulator (replies): the emulator's reply URL is its own Windows localhost, unreachable from WSL2
(Cannot connect to localhost:<port>). Bridge it with a devtunnel:
a. Find the emulator's listening port. In the emulator's Live Chat tab, open the Log panel (right side)
and read the line Emulator listening on http://[::]:<port> โ e.g. โฆ:57705. This port is assigned per emulator
session, so re-check it whenever you restart the emulator. (The emulator's Settings โ Configure Tunnel section
also prints the exact command pre-filled with the current port, e.g. devtunnel host -a -p 57705.)
b. Host the tunnel on that port (run on Windows):
# One-time only โ skip if devtunnel is already installed (check with: devtunnel --version)
winget install Microsoft.devtunnel
# One-time only โ skip if already logged in (check with: devtunnel user show)
devtunnel user login
# Every session โ host the tunnel on the emulator's current port from step (a)
devtunnel host -a -p <emulator-port> # e.g. 57705
c. Paste the public URL into the emulator. devtunnel host prints two URLs โ copy the "Connect via browser"
one (NOT the -inspect one):
Hosting port: 57705
Connect via browser: https://g25mmhp5-57705.asse.devtunnels.ms โ copy THIS
Inspect network activity: https://g25mmhp5-57705-inspect.asse.devtunnels.ms โ NOT this (causes 401)
Ready to accept connections for tunnel: jolly-cat-1xpgknv.asse
Paste it into Settings โ Configure Tunnel โ Tunnel Url โ Save. Keep the devtunnel host window running; replies
now route back through the tunnel.
Step 4 โ Drive the user identity (BOT_DEV_FAKE_EMAIL)
The bot resolves the user's email via the Teams connector (get_conversation_member), which the emulator does not
implement (returns 404). To exercise identity-dependent logic (auth, Keycloak provisioning) from the emulator, set
BOT_DEV_FAKE_EMAIL in .env โ the runner resolves that email directly:
BOT_DEV_FAKE_EMAIL='admin@your-company.com'
Restart the runner after changing it; leave it unset for real connector-based resolution. To fake other identity fields
for future features, add another env-gated monkeypatch in the runner (same pattern) โ never in swiss_ai_hub/
production code.
Emulator troubleshooting
| Symptom | Cause | Fix |
|---|
No credentials found for path | PathEntity missing | Seed it (Step 1) |
Emulator POST 400, nothing in the bot log | Emulator can't reach the bot (WSL2) | Connect via the WSL2 IP, not localhost |
Reply fails: Cannot connect to localhost:<port> | Bot (WSL2) can't reach the emulator reply URL | Set up the devtunnel and paste the Tunnel Url |
Reply 401 to *.devtunnels.ms | Pasted the -inspect tunnel URL | Use the "Connect via browser" URL, not the -inspect one |
KeyError: 'assistant_name' | system_message uses both placeholders | Use โค1 placeholder in the seeded system_message |
Bot 404s on .../members/... โ generic error | Emulator can't do the Teams member lookup | Set BOT_DEV_FAKE_EMAIL to drive identity (Step 4) |
| 60s typing then "taking too long" | The simulated agent didn't reply (harness) | Identity resolved fine; use a real agent or test a pre-agent branch |
Real channel via DevTunnel + Azure (authentic Teams/Slack)
For a true end-to-end test (real display names, real connector emails, genuinely unprovisioned users), expose the local
bot and register it as an Azure Bot:
devtunnel host -a -p 8001
Use that URL as the Azure Bot resource's messaging endpoint (see Option A: Automated Setup / setup_azure_bot.py
above) and enable the Teams/Slack channel in the Azure portal.
System Message Templates
System messages support placeholders:
{username} โ replaced with the user's display name
{assistant_name} โ replaced with the bot's display name
Example:
You are {assistant_name}, an AI assistant for the Swiss AI Hub platform.
The user's name is {username}. Be helpful, concise, and professional.
Always respond in the user's language.
Verification Checklist
After setup, verify:
Troubleshooting Quick Reference
| Symptom | Likely Cause | Fix |
|---|
| "No credentials found for path" | PathEntity missing | Insert PathEntity in MongoDB |
| 401 Unauthorized from Azure | APP_PASSWORD expired | az ad app credential reset --id <appId> |
| Bot doesn't respond in Teams | Wrong endpoint URL | Update Azure Bot Resource endpoint |
| Bot doesn't respond in Slack | Missing slack_token | Add slack_token to PathEntity |
| "Connection refused" locally | Bot server not running | Start with python main.py or make run-prod |
| DevTunnel not forwarding | Port mismatch | Verify tunnel port matches bot server port (8001) |