agentleFS
bbvch-aibbvch-ai/aihub-core/.claude/skills/setup-bot-connection/SKILL.md

setup-bot-connection

Skill16 starsChanged 3 days agoApache-2.0View on GitHub

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).

  • Reads credentials

We split this skill into the rules it gives an agent

6 rules. About 0 are common, 3 are distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Read packages/bot/CLAUDE.md for full architecture, routes, and essential files.

    line 17
  2. Distinctive:

    Use the helper script or insert directly:

    line 118
  3. Distinctive:

    Create Slack App at https://api.slack.com/apps

    line 161
  4. Very specific:

    If you see address already in use, run pkill -9 -f main.py, wait until ss -ltnp | grep :8001 is empty, then start again.

    line 247
  5. Distinctive:

    Leave App ID / Password empty → Connect → send a message.

    line 258
  6. Very specific:

    Use that URL as the Azure Bot resource's messaging endpoint (see *Option A: Automated Setup* / setup_azure_bot.py above) and enable the …

    line 340

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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

  1. Azure CLI installed and authenticated: az login
  2. Azure subscription with permission to create App Registrations and Bot resources
  3. MongoDB/FerretDB running (for storing PathEntity credentials)
  4. NATS server running (for agent communication)
  5. 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:

  1. Creates Azure AD App Registration (az ad app create)
  2. Creates Service Principal (az ad sp create)
  3. Resets credentials → generates APP_PASSWORD
  4. Saves PathEntity to MongoDB bot_paths collection
  5. Creates Azure Bot Resource (az bot create)

Option B: Manual Setup (Step-by-Step)

Step 1: Azure AD App Registration

# Create app registration
az ad app create --display-name "ai-hub-bot" --sign-in-audience "AzureADMyOrg"
# Output: { "appId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }

# Create service principal
az ad sp create --id <appId>

# Generate client secret
az ad app credential reset --id <appId>
# Output: { "appId": "...", "password": "...", "tenant": "..." }

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:

# Option 1: Use add_path_entity.py
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
# Option 2: Direct MongoDB insert
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",  # or "MultiTenant" for Slack
            "APP_ID": "<APP_ID>",
            "APP_PASSWORD": "<APP_PASSWORD>",
            "APP_TENANTID": "<TENANT_ID>",  # None for MultiTenant
        },
        "system_message": "You are {assistant_name}. The user's name is {username}.",
        "slack_token": None,  # Set for Slack bots: "xoxb-..."
    }},
    upsert=True,
)

Step 4: Configure Channel in Azure Portal

For Teams:

  1. Azure Portal → Bot Services → your bot → Channels
  2. Click "Microsoft Teams" → Configure
  3. Enable messaging
  4. Save

For Slack:

  1. Create Slack App at https://api.slack.com/apps
  2. Enable "Bot User OAuth Token" → copy token (xoxb-...)
  3. In Azure Portal → Bot Services → your bot → Channels
  4. Click "Slack" → Configure with OAuth redirect
  5. Save the slack_token in PathEntity

For Web Chat:

  1. Azure Portal → Bot Services → your bot → Channels
  2. Web Chat is enabled by default
  3. 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:

  1. 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.
  2. 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.
  3. 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

# 1. stop any running playground instance (no-op if none; matches any python "main.py")
pkill -f main.py

# 2. go to the testing playground
cd packages/bot/playground/testing

# 3. start it, backgrounded, logging to /tmp/bot_local.log  (serves on 0.0.0.0:8001)
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     # GOOD: "Uvicorn running on http://0.0.0.0:8001"
                               # BAD:  "address already in use" -> an old instance is still up
ss -ltnp | grep :8001          # must show exactly ONE listener

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:

  1. 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.)
  2. 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:

# .env (gitignored, DEV ONLY)
BOT_DEV_FAKE_EMAIL='admin@your-company.com'   # a provisioned Keycloak user  -> happy path
# BOT_DEV_FAKE_EMAIL='ghost@example.com'      # an unknown email             -> UserNotProvisionedError

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

SymptomCauseFix
No credentials found for pathPathEntity missingSeed it (Step 1)
Emulator POST 400, nothing in the bot logEmulator 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 URLSet up the devtunnel and paste the Tunnel Url
Reply 401 to *.devtunnels.msPasted the -inspect tunnel URLUse the "Connect via browser" URL, not the -inspect one
KeyError: 'assistant_name'system_message uses both placeholdersUse ≤1 placeholder in the seeded system_message
Bot 404s on .../members/... → generic errorEmulator can't do the Teams member lookupSet 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     # public URL for the bot itself

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:

  • PathEntity exists in MongoDB bot_paths collection with correct path
  • Credentials (APP_ID, APP_PASSWORD) are valid and not expired
  • Azure Bot Resource endpoint URL matches your server's public endpoint
  • Channel is configured in Azure Portal (Teams/Slack/Web Chat)
  • Bot server is running and reachable at the endpoint URL
  • NATS server is running (for agent-based bots)
  • MongoDB/FerretDB is running (for conversation persistence)
  • Test with Bot Framework Emulator or direct message in Teams/Slack

Troubleshooting Quick Reference

SymptomLikely CauseFix
"No credentials found for path"PathEntity missingInsert PathEntity in MongoDB
401 Unauthorized from AzureAPP_PASSWORD expiredaz ad app credential reset --id <appId>
Bot doesn't respond in TeamsWrong endpoint URLUpdate Azure Bot Resource endpoint
Bot doesn't respond in SlackMissing slack_tokenAdd slack_token to PathEntity
"Connection refused" locallyBot server not runningStart with python main.py or make run-prod
DevTunnel not forwardingPort mismatchVerify tunnel port matches bot server port (8001)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.