Skip to content

Configuration Reference

All options live under plugins.entries."msteams-bridge".config. The schema is additionalProperties: false, so an unknown key is rejected. Defaults below come from the config resolver; secret-valued keys accept either a literal string or an OpenClaw secret reference.

KeyTypeDefaultMeaning
enabledbooltrueMaster on/off.
portint (1-65535)9442WebSocket server port.
bindAddressstring127.0.0.1Bind address for BOTH lanes. Loopback by default: the documented posture is a tunnel that terminates TLS publicly and proxies to loopback, so no port is exposed on your LAN. Use 0.0.0.0 only if the hosted bridge reaches the plugin directly. messagesBindAddress overrides the messages lane alone.
pathstring/msteams/callingWebSocket route; StandIn connects to {path}/{callId}.
secretstring | secret-ref-HMAC secret; must match StandIn. Fails closed - a non-string coerces to empty and rejects all handshakes.
requireRecordingStatusbooltrueHold media processing until Teams reports recording is active.
inboundPolicyenumunset (deny all)disabled | allowlist | pairing | open. Unset or disabled rejects every inbound call - you must set a policy to receive calls. pairing currently behaves like allowlist.
allowFromstring[][]Allowlisted callers, matched by AAD object id (case-insensitive) or phone number (digits only). Empty + allowlist = deny all.
inboundGreetingstring-Opening line the agent speaks on answer.
modeenumautorealtime | streaming. Auto-selects realtime if a realtime provider resolves.
sessionScopeenum-Conversation continuity: per-phone | per-call | per-thread.
maxConcurrentCallsint4Concurrent active-call cap.
maxDurationSecondsint0 (unlimited)Hard cap on a single answered call’s duration.
staleCallReaperSecondsint120Tear down calls that stop being serviced after this long.
maxVisionPerMinuteint30Per-call cap on PAID vision calls over a sliding 60 seconds, shared by look_at_screen, the ambient push and the streaming per-turn attach. 0 switches vision spend OFF - it is the kill switch, not “unlimited”. There is no unlimited value; set a large number for a cap that never bites in practice.
ambientVisionboolfalseKeep pushing the newest changed camera / screen-share frame at the agent between turns, so it stays visually aware without being asked. Off by default: it spends a vision call per scene change for the whole call. look_at_screen works either way. Setting this with maxVisionPerMinute: 0 delivers nothing (the bridge warns at startup).
meetingRecapbool-Post an end-of-call recap / minutes. On a StandIn managed connection the minutes go through the gateway as TEXT: the reply protocol carries text and cards, not files, so the Word document is not attached (the message says so). Bring-your-own-bot deployments still get the .docx.
bilingualbool-Enable English/Arabic handling.

Set on the StandIn Managed Bot path. secret alone is enough; the rest are overrides.

KeyMeaning
secretThe connection secret. One value covering BOTH lanes - calling and messages. This is what the StandIn portal gives you
messagesPort / messagesPathWhere the messages lane listens (default 9444, /msteams/messages)
callingPort / pathWhere the calling lane listens (default 9442, /msteams/calling). port is the older name for callingPort
gatewayReplyUrlWhere replies are posted (default https://teams.standin.komaa.com/api/chat/reply); override only for a self-hosted StandIn. Flat, like the other messages-lane keys - managedBot.gatewayReplyUrl is still read as the compatibility shape, and the flat key wins

The messages lane is enabled by the PRESENCE of a secret - there is no enable flag to remember. The managedBot block is still read as a compatibility shape for configs written before these flat keys.

KeyTypeDefaultMeaning
groupCall.requireAddressbool-In meetings (2+ humans), speak only when addressed.
groupCall.wakePhrasesstring[]-Wake words that address the agent.
groupCall.followUpWindowMsint-After being addressed, keep listening for follow-ups for this long.

1:1 calls always answer regardless of these settings.

KeyTypeDefaultMeaning
realtime.providerenumopenaiThe realtime provider.
realtime.providers.<id>.apiKeysecret-Provider API key.
realtime.providers.<id>.modelstring-Model, e.g. gpt-realtime.
realtime.providers.<id>.azureEndpointstring-Azure OpenAI endpoint (selects Azure).
realtime.providers.<id>.azureDeploymentstring-Azure deployment name.
realtime.instructionsstring-System instructions for the voice agent.
realtime.toolPolicyenumnoneWhich agent tools the voice model may call: safe-read-only | owner | none.
realtime.suppressInputDuringPlaybackbool-Echo guard: ignore input while the agent is speaking.
realtime.echoSuppressionWindowMsint-Echo-guard window.
realtime.echoBargeInRmsint-RMS threshold above which caller speech counts as barge-in.
KeyTypeDefaultMeaning
stt.providerstring-Transcription provider id (streaming mode).
stt.providers.<id>.apiKeysecret-STT provider key.

In streaming mode, TTS and the agent come from your OpenClaw configuration. If stt.provider is unset, the plugin uses your configured transcription provider, then a VAD-segmented file fallback.

KeyTypeDefaultMeaning
outbound.enabledbool-Enable outbound call-backs.
outbound.workerBaseUrlstring-StandIn outbound API base URL.
outbound.tenantIdstring-Your AAD tenant id for outbound.
outbound.answerTimeoutMsint120000How long to wait for an answer before finalizing the attempt as no-answer and cancelling the ringing call.
outbound.defaultModeenum-notify (speak and hang up) | conversation.

See Outbound Calls.

Keep these out of the config file with the ${VAR} form, which OpenClaw resolves from the environment at startup: secret, managedBot.chatSecret, realtime.providers.*.apiKey, stt.providers.*.apiKey. Prefer that over a literal in production.

secret and realtime.providers.*.apiKey are typed string, so the object secret-reference form is rejected by schema validation before it can be resolved. ${VAR} works at all four.

{
"plugins": {
"entries": {
"msteams-bridge": {
"config": {
"enabled": true,
"mode": "realtime",
// Loopback + a tunnel is the documented posture; 0.0.0.0 only if StandIn reaches you directly.
"bindAddress": "127.0.0.1",
"callingPort": 9442,
"messagesPort": 9444,
"path": "/msteams/calling",
// ONE connection secret from the StandIn portal, covering calling AND messages.
"secret": "<the connection secret from StandIn>",
"requireRecordingStatus": true,
"inboundPolicy": "allowlist",
"allowFrom": ["<caller AAD object id>"],
"inboundGreeting": "Hi, you've reached the assistant. How can I help?",
"sessionScope": "per-thread",
"maxConcurrentCalls": 4,
"maxVisionPerMinute": 30,
"meetingRecap": true,
"groupCall": {
"requireAddress": true,
"wakePhrases": ["assistant", "hey team"],
"followUpWindowMs": 8000
},
"realtime": {
"provider": "openai",
"providers": {
"openai": { "apiKey": "<key>", "model": "gpt-realtime" }
},
"toolPolicy": "safe-read-only",
"suppressInputDuringPlayback": true
}
}
}
}
}
}
"realtime": {
"provider": "openai",
"providers": {
"openai": {
"apiKey": "<azure-key>",
"azureEndpoint": "https://<resource>.cognitiveservices.azure.com",
"azureDeployment": "gpt-realtime"
}
}
}
"mode": "streaming",
"stt": {
"provider": "<your-stt-provider>",
"providers": { "<your-stt-provider>": { "apiKey": "<key>" } }
}