Einrichtung
Diese Anleitung führt Schritt für Schritt durch alles, was nötig ist, um einen Microsoft-Teams-Bot
mit OctoMesh zu verbinden — zuerst im Azure-Portal, dann die entsprechenden Azure-CLI-(az)-Befehle
und schließlich die OctoMesh-Seite (Konfiguration, Pipeline, Teams-App-Paket, Test).
Voraussetzungen
- Ein Azure-Abonnement und die Berechtigung, eine App-Registrierung und eine Azure-Bot-Ressource anzulegen.
- Ein Microsoft-Teams-Konto mit der Berechtigung, eine benutzerdefinierte App querzuladen (ist die Option ausgegraut, muss ein Teams-Administrator Benutzerdefinierte Apps hochladen in der App-Einrichtungsrichtlinie aktivieren).
- Ein laufender OctoMesh-Mesh Adapter für den Ziel-Tenant (der Messaging-Endpunkt des Bots zeigt darauf).
- Für die lokale Entwicklung: ein Tunneling-Werkzeug wie Dev Tunnels oder ngrok, um den lokalen Adapter über HTTPS verfügbar zu machen.
Die Beispiele verwenden die Ressourcengruppe my-rg, das Bot-Handle octo-accounting-bot und die
Tenant-ID salzburgdev. Ersetzen Sie sie durch Ihre eigenen Werte.
Schritt 1 — App-Registrierung + Client-Secret
Die App-Registrierung ist die Identität des Bots. Ihre Client-ID/ihr Secret werden sowohl für die
eingehende Authentifizierung als auch für das ausgehende Token von TeamsBotReply@1 verwendet.
Portal
- Microsoft Entra ID → App-Registrierungen → + Neue Registrierung.
- Name:
octo-accounting-bot. - Unterstützte Kontotypen: Nur Konten in diesem Organisationsverzeichnis (Einzelmandant).
- Registrieren, dann Anwendungs-(Client-)ID und Verzeichnis-(Tenant-)ID notieren.
- Zertifikate & Geheimnisse → + Neuer geheimer Clientschlüssel → Ablaufdatum festlegen → Hinzufügen → den Wert sofort kopieren (er wird nur einmal angezeigt).
Azure CLI
# create a single-tenant app registration
az ad app create --display-name "octo-accounting-bot" --sign-in-audience AzureADMyOrg
# -> note the "appId" from the output
# add a client secret (prints the secret once)
az ad app credential reset --id <APP_ID> --append --display-name teams-bot --years 1 \
--query password -o tsv
# your tenant id
az account show --query tenantId -o tsv
Bewahren Sie drei Werte für später auf: App-(Client-)ID, Client-Secret, Tenant-ID.
Schritt 2 — Azure-Bot-Ressource
Portal
-
Ressource erstellen → nach „Azure Bot" suchen → Erstellen.
-
Bot-Handle:
octo-accounting-bot. -
Wählen Sie Ihr Abonnement und Ihre Ressourcengruppe.
-
Tarif: Tarif ändern → F0 (Kostenlos) genügt für die meisten Szenarien.
-
Microsoft App-ID:
- App-Typ: Einzelmandant.
- Erstellungstyp: Vorhandene App-Registrierung verwenden → die App-ID aus Schritt 1 einfügen.
Wählen Sie nicht „User-Assigned Managed Identity"Ein Bot mit Managed Identity hat kein Client-Secret und kann den selbst gehosteten Mesh Adapter nicht authentifizieren. Verwenden Sie Einzelmandant mit der App-Registrierung aus Schritt 1.
-
Überprüfen + erstellen → Erstellen.
-
Öffnen Sie auf der Bot-Ressource Kanäle → Microsoft Teams, akzeptieren Sie die Bedingungen und Übernehmen.
-
Legen Sie unter Konfiguration den Messaging-Endpunkt fest (die URL siehe Schritt 3) und Übernehmen.
Azure CLI
RG=my-rg
BOT=octo-accounting-bot
APP_ID=<APP_ID> # from Step 1
TENANT_ID=<TENANT_ID> # from Step 1
ENDPOINT="https://<public-host>/salzburgdev/teamsBot" # from Step 3
# create the bot (single tenant, free tier)
az bot create -g $RG -n $BOT --app-type SingleTenant --appid $APP_ID \
--tenant-id $TENANT_ID --endpoint "$ENDPOINT" --sku F0
# enable the Microsoft Teams channel
az bot msteams create -n $BOT -g $RG
# (later) update just the messaging endpoint, e.g. after the tunnel URL changes
az bot update -n $BOT -g $RG --endpoint "$ENDPOINT"
# verify
az bot show -n $BOT -g $RG \
--query "{appId:properties.msaAppId, appType:properties.msaAppType, endpoint:properties.endpoint}" -o json
az bot msteams show -n $BOT -g $RG --query "properties.properties.isEnabled" -o tsv
Ein fehlender Messaging-Endpunkt oder ein deaktivierter Teams-Kanal äußern sich in Teams beide als
„Invalid bot". Die beiden obigen show-Befehle bestätigen, dass der Endpunkt gesetzt und
isEnabled true ist.
Schritt 3 — Den Messaging-Endpunkt verfügbar machen
Der Messaging-Endpunkt ist https://<public-host>/{tenant}/teamsBot, wobei {tenant} die
Tenant-ID des Adapters ist (zum Beispiel salzburgdev) und <public-host> den Mesh Adapter erreicht.
Lokale Entwicklung
Machen Sie den HTTP-Listener des Adapters über einen Tunnel verfügbar (vermeidet den Sprung über selbstsigniertes TLS):
# dev tunnels
devtunnel host -p 5041 --allow-anonymous
# or ngrok
ngrok http 5041
Nehmen Sie die öffentliche HTTPS-URL des Tunnels und setzen Sie den Messaging-Endpunkt auf
https://<tunnel-host>/salzburgdev/teamsBot. Mit dem kostenlosen Tarif von ngrok ändert sich die URL bei jedem
Neustart — reservieren Sie eine statische Domain oder führen Sie nach jedem Start erneut az bot update --endpoint … aus.
Produktion
Richten Sie den Messaging-Endpunkt auf die öffentliche HTTPS-Adresse des Adapters (zum Beispiel hinter einem
Ingress / Reverse Proxy) und behalten Sie dabei den Pfad /{tenant}/teamsBot bei.
Schritt 4 — OctoMesh konfigurieren
1. Bot-Anmeldedaten (MicrosoftGraphConfiguration)
Stellen Sie die App-ID / das Secret / die Tenant-ID als System.Communication/MicrosoftGraphConfiguration-Entität
bereit. Bewahren Sie das Secret außerhalb der Versionsverwaltung auf (eine lokale Secrets-Datei, ein Key Vault, …).
- rtId: <config-rtId>
ckTypeId: System.Communication/MicrosoftGraphConfiguration
rtWellKnownName: MicrosoftGraphDocuments
attributes:
- id: System.Communication/AzureTenantId
value: "<tenant id>" # REQUIRED for a single-tenant bot
- id: System.Communication/ClientId
value: "<app (client) id>"
- id: System.Communication/ClientSecret
value: "<client secret>"
2. Pipeline mit FromTeamsBot@1 und TeamsBotReply@1
Die Pipeline muss eine System.Communication/Uses-Assoziation zur
MicrosoftGraphConfiguration tragen (die GlobalConfiguration wird ausschließlich aus Uses-Assoziationen
aufgebaut — der Name allein wird nicht aufgelöst).
# on the Pipeline entity
associations:
- roleId: System.Communication/Uses
targetRtId: <config-rtId>
targetCkTypeId: System.Communication/MicrosoftGraphConfiguration
Eine minimale Echo-Pipeline-Definition:
triggers:
- type: FromTeamsBot@1
serverConfiguration: MicrosoftGraphDocuments
route: /teamsBot
validateInboundToken: false # local dev; enable + harden before public exposure
transformations:
- type: TeamsBotReply@1
serverConfiguration: MicrosoftGraphDocuments
messageBody: "Hello from OctoMesh!"
# serviceUrlPath / conversationIdPath default to $.Conversation.ServiceUrl / .ConversationId
Eine echte Pipeline verzweigt anhand von $.Emails[0].Attachments: Anhänge → die Datei(en) einlesen, reiner
Text → antworten (z. B. mit AnthropicAiQuery@1), dann über TeamsBotReply@1 antworten, das
$.Conversation liest.
3. Bereitstellen
Importieren Sie die Konfiguration und die Pipeline und stellen Sie den Datenfluss mit der octo-cli
bereit (ImportRt, DeployDataFlow). Nach der Bereitstellung protokolliert der Adapter
FromTeamsBot: listening on /teamsBot.
Schritt 5 — Teams-App-Paket
Ein Bot wird in Teams über ein kleines App-Paket erreicht (ein Zip, das manifest.json und
zwei Icons enthält). Die bots[].botId im Manifest muss die App-ID des Bots sein, und supportsFiles: true
ist erforderlich, damit der Bot in 1:1-Chats Datei-Uploads empfangen kann.
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.17/MicrosoftTeams.schema.json",
"manifestVersion": "1.17",
"version": "1.0.0",
"id": "<app (client) id>",
"developer": {
"name": "Your Company",
"websiteUrl": "https://example.com",
"privacyUrl": "https://example.com/privacy",
"termsOfUseUrl": "https://example.com/terms"
},
"icons": { "color": "color.png", "outline": "outline.png" },
"name": { "short": "Accounting Assistant", "full": "OctoMesh Accounting Assistant" },
"description": { "short": "Upload invoices and ask questions.", "full": "…" },
"accentColor": "#0076D7",
"bots": [
{ "botId": "<app (client) id>", "scopes": ["personal"], "supportsFiles": true }
],
"permissions": ["identity", "messageTeamMembers"],
"validDomains": []
}
Stellen Sie color.png (192×192) und outline.png (32×32, transparent) bereit, komprimieren Sie die drei Dateien im
Wurzelverzeichnis des Archivs und dann in Teams: Apps → Ihre Apps verwalten → App hochladen →
Benutzerdefinierte App hochladen und wählen Sie das Zip aus.
Schritt 6 — Test
- Öffnen Sie den Bot aus Teams (der Hinzufügen-Dialog nach dem Querladen oder die Suche nach seinem App-Namen).
- Senden Sie eine Textnachricht → die Pipeline läuft und
TeamsBotReply@1antwortet im Chat. - Hängen Sie eine Datei an (z. B. ein PDF) → der Bot liest sie ein (
$.Emails[0].Attachments[0].Data) und antwortet.
Beobachten Sie das Adapter-Log, um den Roundtrip zu bestätigen:
FromTeamsBot: listening on /teamsBot
FromTeamsBot: processed activity from <user> with <n> attachment(s)
Node-Referenz
FromTeamsBot@1 (Trigger)
| Eigenschaft | Beschreibung |
|---|---|
serverConfiguration | WellKnownName der MicrosoftGraphConfiguration (Bot-App-ID/-Secret + Tenant). |
route | Relative Route des Messaging-Endpunkts; das Tenant-Präfix wird vom Adapter hinzugefügt. Standard /teamsBot. |
validateInboundToken | Das eingehende Bot-Framework-JWT validieren. Standard false (lokale Entwicklung). Siehe Sicherheit unten. |
botAppId | Erwartetes Token-Audience; standardmäßig die ClientId der Konfiguration. |
Ausgabe: $.Emails[] (Nachricht + Anhänge, gleiche Form wie bei den E-Mail-Triggern) und
$.Conversation (ServiceUrl, ConversationId, ActivityId, FromId, FromName,
FromAadObjectId).
TeamsBotReply@1 (Load)
| Eigenschaft | Beschreibung |
|---|---|
serverConfiguration | WellKnownName der MicrosoftGraphConfiguration. |
serviceUrlPath | JSONPath zur Bot-Framework-serviceUrl. Standard $.Conversation.ServiceUrl. |
conversationIdPath | JSONPath zur Konversations-ID. Standard $.Conversation.ConversationId. |
replyToActivityIdPath | Optionaler JSONPath für eine Antwort im Thread. Standard $.Conversation.ActivityId. |
messageBodyPath | JSONPath zum Antworttext (z. B. eine KI-Antwort). |
messageBody | Literaler Antworttext (wird verwendet, wenn messageBodyPath leer ist). |
timeoutSeconds | HTTP-Timeout. Standard 30. |
continueOnError | Die Pipeline fortsetzen, wenn das Senden fehlschlägt. Standard true. |
Sicherheit
validateInboundToken steuert die eingehende Authentifizierung:
false(Standard) — keine Authentifizierung. Nur akzeptabel für die lokale Entwicklung hinter einem privaten Dev Tunnel oder dem Bot Framework Emulator.true— das eingehende Bot-Framework-JWT wird geprüft (Audience + Ablauf).
Die aktuelle Eingangsprüfung validiert Audience und Ablauf des Tokens, aber noch nicht dessen kryptografische Signatur. Bevor Sie den Endpunkt öffentlich verfügbar machen (Test/Produktion), platzieren Sie ihn hinter einer vollständigen Bot-Framework-Token-Validierung und weisen Sie nicht authentifizierte Anfragen zurück. Bis dahin kann jeder Aufrufer, der die URL kennt, die Pipeline auslösen.
Fehlerbehebung
| Symptom | Ursache / Behebung |
|---|---|
| „Invalid bot" / „Ungültiger Bot" in Teams | Messaging-Endpunkt leer oder Teams-Kanal nicht aktiviert. Prüfen mit az bot show … --query properties.endpoint und az bot msteams show … --query properties.properties.isEnabled; beheben mit az bot update --endpoint … und az bot msteams create. |
| Bot-Framework-POSTs kommen an (200), aber keine Pipeline läuft | Die ersten Aktivitäten beim Öffnen eines Chats sind conversationUpdate/Typing-Events, die der Trigger absichtlich ignoriert. Nur message-Aktivitäten lassen die Pipeline laufen. |
invalid_client / es wird keine Antwort zugestellt | Falsche Token-Authority. Für einen Einzelmandanten-Bot muss AzureTenantId auf der MicrosoftGraphConfiguration gesetzt sein (der Antwort-Node verwendet dann die Tenant-Authority). |
| In einem 1:1-Chat werden keine Dateien empfangen | Im Teams-App-Manifest fehlt "supportsFiles": true im bots-Eintrag. |
| Antwort schlägt mit einem DNS-/Socket-Fehler an die Service-URL fehl | Erwartbar beim Testen mit einer fingierten serviceUrl; eine echte Teams-Konversation liefert eine erreichbare serviceUrl. |
Kein FromTeamsBot: listening on /teamsBot im Log | Der Datenfluss ist nicht bereitgestellt, oder der Adapter-Build ist älter als der Node — neu bereitstellen und den Adapter neu starten. |