Microsoft Teams (plugin)
“Abandon all hope, ye who enter here.”Updated: 2026-01-21 Status: text와 DM attachments를 지원합니다. channel/group file sending에는
sharePointSiteId와 Graph permissions가 필요합니다. 자세한 내용은 Sending files in group chats를 참고하세요. Poll은 Adaptive Cards로 전송됩니다.
Plugin required
Microsoft Teams는 plugin으로 제공되며 core install에는 포함되지 않습니다. Breaking change (2026.1.15): MS Teams가 core에서 분리되었습니다. 사용하려면 plugin을 별도로 설치해야 합니다. 이렇게 분리한 이유는 core install을 더 가볍게 유지하고, MS Teams 의존성을 독립적으로 업데이트할 수 있게 하기 위해서입니다. CLI 설치 (npm registry):Quick setup (beginner)
- Microsoft Teams plugin을 설치합니다.
- Azure Bot를 생성합니다. App ID, client secret, tenant ID가 필요합니다.
- 해당 credential로 OpenClaw를 구성합니다.
- 공개 URL 또는 tunnel을 통해
/api/messages(기본 port3978)를 노출합니다. - Teams app package를 설치하고 gateway를 시작합니다.
channels.msteams.groupPolicy: "allowlist"). group reply를 허용하려면 channels.msteams.groupAllowFrom을 설정하거나, groupPolicy: "open"을 사용하세요. 후자의 경우에도 기본 mention gating은 유지됩니다.
Goals
- Teams DM, group chat, channel에서 OpenClaw와 대화할 수 있게 합니다.
- routing은 항상 deterministic하게 유지되어, reply가 들어온 채널로 다시 돌아갑니다.
- 별도 설정이 없으면 안전한 channel behavior를 기본값으로 사용합니다. 즉, mentions가 필요합니다.
Config writes
기본적으로 Microsoft Teams는/config set|unset로 유발된 config update를 기록할 수 있습니다. (commands.config: true 필요)
비활성화:
Access control (DMs + groups)
DM access- 기본값:
channels.msteams.dmPolicy = "pairing". 승인 전까지 unknown sender는 무시됩니다. channels.msteams.allowFrom에는 안정적인 AAD object ID 사용을 권장합니다.- UPN이나 display name은 바뀔 수 있으므로 direct matching은 기본 비활성화이며,
channels.msteams.dangerouslyAllowNameMatching: true일 때만 켤 수 있습니다. - credential이 허용되면 wizard가 Microsoft Graph를 통해 이름을 ID로 resolve할 수 있습니다.
- 기본값:
channels.msteams.groupPolicy = "allowlist"입니다.groupAllowFrom을 추가하기 전에는 차단됩니다. unset일 때 기본값을 바꾸려면channels.defaults.groupPolicy를 사용하세요. channels.msteams.groupAllowFrom은 group chat/channel에서 어떤 sender가 bot을 트리거할 수 있는지 제어합니다. unset이면channels.msteams.allowFrom으로 fallback합니다.groupPolicy: "open"이면 모든 member를 허용합니다. 그래도 기본적으로 mention gating은 유지됩니다.- 아무 channel도 허용하지 않으려면
channels.msteams.groupPolicy: "disabled"를 사용합니다.
channels.msteams.teams아래에 team과 channel을 나열해 group/channel reply 범위를 제한할 수 있습니다.- key는 team ID 또는 이름, channel key는 conversation ID 또는 이름이 될 수 있습니다.
groupPolicy="allowlist"이고 teams allowlist가 있으면, 나열된 team/channel만 mention-gated로 허용됩니다.- configure wizard는
Team/Channel항목을 받아 자동 저장할 수 있습니다. - startup 시 OpenClaw는 team/channel 이름과 user allowlist 이름을 ID로 resolve하고 그 매핑을 로그에 남깁니다. resolve되지 않은 항목은 입력한 값을 유지합니다.
How it works
- Microsoft Teams plugin을 설치합니다.
- Azure Bot를 만들고 App ID, secret, tenant ID를 확인합니다.
- bot을 참조하고 아래 RSC permission을 포함하는 Teams app package를 만듭니다.
- Teams app을 팀에 업로드하거나 personal scope에 설치합니다.
~/.openclaw/openclaw.json또는 env vars에msteams를 구성하고 gateway를 시작합니다.- gateway는 기본적으로
/api/messages에서 Bot Framework webhook traffic을 수신합니다.
Azure Bot Setup (Prerequisites)
OpenClaw를 구성하기 전에 Azure Bot resource를 먼저 만들어야 합니다.Step 1: Create Azure Bot
- Create Azure Bot으로 이동합니다.
-
Basics 탭을 채웁니다.
Field Value Bot handle 봇 이름. 예: openclaw-msteams(고유해야 함)Subscription 사용할 Azure subscription 선택 Resource group 새로 만들거나 기존 항목 사용 Pricing tier 개발/테스트에는 Free Type of App Single Tenant 권장 Creation type Create new Microsoft App ID
Deprecation notice: 새 multi-tenant bot 생성은 2025-07-31 이후 deprecated되었습니다. 새 bot은 Single Tenant를 사용하세요.
- Review + create → Create를 클릭하고 1~2분 정도 기다립니다.
Step 2: Get Credentials
- Azure Bot resource에서 Configuration으로 이동합니다.
- Microsoft App ID를 복사합니다. 이것이
appId입니다. - Manage Password를 클릭해 App Registration으로 이동합니다.
- Certificates & secrets에서 New client secret을 만든 뒤 Value를 복사합니다. 이것이
appPassword입니다. - Overview에서 Directory (tenant) ID를 복사합니다. 이것이
tenantId입니다.
Step 3: Configure Messaging Endpoint
- Azure Bot → Configuration
- Messaging endpoint를 webhook URL로 설정합니다.
- Production:
https://your-domain.com/api/messages - Local dev: 아래 Local Development 참고
- Production:
Step 4: Enable Teams Channel
- Azure Bot → Channels
- Microsoft Teams → Configure → Save
- Terms of Service를 수락합니다.
Local Development (Tunneling)
Teams는localhost에 직접 접근할 수 없습니다. local development에는 tunnel이 필요합니다.
Option A: ngrok
Teams Developer Portal (Alternative)
manifest ZIP을 손으로 만드는 대신 Teams Developer Portal을 사용할 수 있습니다.- + New app 클릭
- 기본 정보 입력 (name, description, developer info)
- App features → Bot
- Enter a bot ID manually를 선택하고 Azure Bot App ID를 붙여넣기
- scope 체크: Personal, Team, Group Chat
- Distribute → Download app package
- Teams에서 Apps → Manage your apps → Upload a custom app → ZIP 선택
Testing the Bot
Option A: Azure Web Chat (verify webhook first)- Azure Portal의 Azure Bot resource에서 Test in Web Chat로 이동합니다.
- 메시지를 보내고 응답을 확인합니다.
- Teams 설정 전에 webhook endpoint가 정상인지 확인할 수 있습니다.
- Teams app을 설치합니다. (sideload 또는 org catalog)
- Teams에서 bot을 찾아 DM을 보냅니다.
- gateway log에서 inbound activity를 확인합니다.
Setup (minimal text-only)
-
Install the Microsoft Teams plugin
- npm에서 설치:
openclaw plugins install @openclaw/msteams - local checkout에서 설치:
openclaw plugins install ./extensions/msteams
- npm에서 설치:
-
Bot registration
- 위 설명대로 Azure Bot를 만들고 다음 정보를 확보합니다.
- App ID
- Client secret (App password)
- Tenant ID (single-tenant)
- 위 설명대로 Azure Bot를 만들고 다음 정보를 확보합니다.
-
Teams app manifest
botentry에botId = <App ID>를 넣습니다.- scope:
personal,team,groupChat supportsFiles: true(personal scope file handling에 필요)- 아래 RSC permission 추가
- icon 생성:
outline.png(32x32),color.png(192x192) manifest.json,outline.png,color.png세 파일을 함께 ZIP으로 묶습니다.
-
Configure OpenClaw
config key 대신 env vars도 사용할 수 있습니다.
MSTEAMS_APP_IDMSTEAMS_APP_PASSWORDMSTEAMS_TENANT_ID
-
Bot endpoint
- Azure Bot Messaging Endpoint를 다음처럼 설정합니다.
https://<host>:3978/api/messages- 또는 사용 중인 path/port
- Azure Bot Messaging Endpoint를 다음처럼 설정합니다.
-
Run the gateway
- plugin이 설치되어 있고 credential이 포함된
msteamsconfig가 있으면 Teams channel은 자동으로 시작됩니다.
- plugin이 설치되어 있고 credential이 포함된
History context
channels.msteams.historyLimit은 최근 channel/group message 몇 개를 prompt에 감쌀지 제어합니다.messages.groupChat.historyLimit로 fallback합니다.0이면 비활성화됩니다. 기본값은50입니다.- DM history는
channels.msteams.dmHistoryLimit로 제한할 수 있습니다. 사용자별 override는channels.msteams.dms["<user_id>"].historyLimit를 사용합니다.
Current Teams RSC Permissions (Manifest)
아래는 현재 Teams app manifest에 있는 resourceSpecific permissions입니다. app이 설치된 team/chat 안에서만 적용됩니다. For channels (team scope):ChannelMessage.Read.Group(Application) -@mention없이도 모든 channel message 수신ChannelMessage.Send.Group(Application)Member.Read.Group(Application)Owner.Read.Group(Application)ChannelSettings.Read.Group(Application)TeamMember.Read.Group(Application)TeamSettings.Read.Group(Application)
ChatMessage.Read.Chat(Application) -@mention없이도 모든 group chat message 수신
Example Teams Manifest (redacted)
필수 필드를 포함한 최소한의 유효 예시입니다. ID와 URL은 실제 값으로 교체하세요.Manifest caveats (must-have fields)
bots[].botId는 Azure Bot App ID와 반드시 일치해야 합니다.webApplicationInfo.id도 Azure Bot App ID와 반드시 일치해야 합니다.bots[].scopes에는 사용할 surface (personal,team,groupChat)가 포함되어야 합니다.bots[].supportsFiles: true는 personal scope file handling에 필요합니다.- channel traffic을 원하면
authorization.permissions.resourceSpecific에 channel read/send 권한이 있어야 합니다.
Updating an existing app
이미 설치된 Teams app를 업데이트하려면:manifest.json을 새 설정으로 갱신합니다.version필드를 증가시킵니다. 예:1.0.0→1.1.0manifest.json,outline.png,color.png를 다시 ZIP으로 묶습니다.- 새 ZIP을 업로드합니다.
- Option A (Teams Admin Center): Teams Admin Center → Teams apps → Manage apps → 앱 선택 → Upload new version
- Option B (Sideload): Teams → Apps → Manage your apps → Upload a custom app
- team channel의 경우 새 permission이 반영되도록 각 team에 app을 다시 설치합니다.
- Teams app metadata cache를 비우기 위해 Teams를 완전히 종료한 뒤 다시 실행합니다.
Capabilities: RSC only vs Graph
With Teams RSC only (app installed, no Graph API permissions)
동작하는 것:- channel message text 내용 읽기
- channel message text 전송
- personal (DM) file attachment 수신
- channel/group의 image 또는 file contents (payload에는 HTML stub만 포함)
- SharePoint/OneDrive에 저장된 attachment 다운로드
- live webhook event를 넘는 과거 message history 조회
With Teams RSC + Microsoft Graph Application permissions
추가되는 기능:- hosted contents 다운로드 (메시지에 붙여넣은 image 등)
- SharePoint/OneDrive의 file attachment 다운로드
- Graph를 통한 channel/chat message history 조회
RSC vs Graph API
| Capability | RSC Permissions | Graph API |
|---|---|---|
| Real-time messages | Yes (webhook) | No (polling only) |
| Historical messages | No | Yes (history query 가능) |
| Setup complexity | manifest만 필요 | admin consent + token flow 필요 |
| Works offline | No | Yes |
ChannelMessage.Read.All이 포함된 Graph API 권한이 필요합니다. 이 권한에는 admin consent가 필요합니다.
Graph-enabled media + history (required for channels)
channel에서 image/file이 필요하거나 message history를 가져오려면 Microsoft Graph permission을 켜고 admin consent를 부여해야 합니다.- Entra ID (Azure AD) App Registration에서 Microsoft Graph Application permissions를 추가합니다.
ChannelMessage.Read.All(channel attachment + history)Chat.Read.All또는ChatMessage.Read.All(group chats)
- tenant에 대해 Grant admin consent를 수행합니다.
- Teams app manifest version을 올리고, 다시 업로드하고, Teams에서 app을 재설치합니다.
- cached app metadata를 지우기 위해 Teams를 완전히 종료 후 재실행합니다.
User.Read.All (Application) 권한도 추가하고 admin consent를 부여하세요. 현재 conversation에 이미 있는 사용자의 @mention은 기본 동작으로 처리됩니다.
Known Limitations
Webhook timeouts
Teams는 HTTP webhook으로 메시지를 전달합니다. 처리가 너무 오래 걸리면(예: 느린 LLM 응답):- Gateway timeout
- Teams의 message retry로 인한 duplicate
- dropped reply
Formatting
Teams markdown은 Slack이나 Discord보다 제약이 큽니다.- 기본 formatting은 동작: bold, italic,
code, links - 복잡한 markdown(table, nested list 등)은 올바르게 렌더링되지 않을 수 있음
- poll과 arbitrary card send에는 Adaptive Cards를 사용합니다.
Configuration
주요 설정 항목 (/gateway/configuration에는 공통 채널 패턴이 정리되어 있습니다):
channels.msteams.enabled: 채널 활성화/비활성화channels.msteams.appId,channels.msteams.appPassword,channels.msteams.tenantId: bot credentialschannels.msteams.webhook.port(default3978)channels.msteams.webhook.path(default/api/messages)channels.msteams.dmPolicy:pairing | allowlist | open | disabled(default: pairing)channels.msteams.allowFrom: DM allowlist (AAD object ID 권장). Graph access가 가능하면 wizard가 setup 중 이름을 ID로 resolve합니다.channels.msteams.dangerouslyAllowNameMatching: mutable UPN/display-name matching을 다시 켜는 break-glass togglechannels.msteams.textChunkLimit: outbound text chunk 크기channels.msteams.chunkMode:length(default) 또는newline. 빈 줄 단위로 먼저 나눈 뒤 길이 기준 chunking으로 fallbackchannels.msteams.mediaAllowHosts: inbound attachment host allowlist (default는 Microsoft/Teams domain)channels.msteams.mediaAuthAllowHosts: media retry 시 Authorization header를 붙일 host allowlist (기본은 Graph + Bot Framework host)channels.msteams.requireMention: channel/group에서@mention필요 여부 (default true)channels.msteams.replyStyle:thread | top-level(아래 Reply Style 참고)channels.msteams.teams.<teamId>.replyStyle: team별 overridechannels.msteams.teams.<teamId>.requireMention: team별 overridechannels.msteams.teams.<teamId>.tools: channel override가 없을 때 적용되는 team-level tool policy override (allow/deny/alsoAllow)channels.msteams.teams.<teamId>.toolsBySender: team-level per-sender tool policy override ("*"wildcard 지원)channels.msteams.teams.<teamId>.channels.<conversationId>.replyStyle: channel별 overridechannels.msteams.teams.<teamId>.channels.<conversationId>.requireMention: channel별 overridechannels.msteams.teams.<teamId>.channels.<conversationId>.tools: channel별 tool policy override (allow/deny/alsoAllow)channels.msteams.teams.<teamId>.channels.<conversationId>.toolsBySender: channel별 per-sender tool policy override ("*"wildcard 지원)toolsBySenderkey는 명시적 prefix 사용 권장:id:,e164:,username:,name:(legacy unprefixed key는id:로만 매핑)channels.msteams.sharePointSiteId: group chat/channel에서 file upload에 사용할 SharePoint site ID (아래 Sending files in group chats 참고)
Routing & Sessions
- session key는 표준 agent 형식을 따릅니다. 자세한 내용은 /concepts/session을 참고하세요.
- direct message는 main session을 공유합니다 (
agent:<agentId>:<mainKey>) - channel/group message는 conversation id를 사용합니다.
agent:<agentId>:msteams:channel:<conversationId>agent:<agentId>:msteams:group:<conversationId>
- direct message는 main session을 공유합니다 (
Reply Style: Threads vs Posts
Teams는 최근 동일한 data model 위에 두 가지 channel UI 스타일을 제공합니다.| Style | Description | Recommended replyStyle |
|---|---|---|
| Posts (classic) | 메시지가 카드 형태로 보이고 아래에 thread reply가 붙음 | thread (default) |
| Threads (Slack-like) | 메시지가 Slack처럼 선형 흐름으로 보임 | top-level |
replyStyle을 잘못 선택하면:
- Threads-style channel에서
thread를 쓰면 reply가 어색하게 중첩되어 보임 - Posts-style channel에서
top-level을 쓰면 reply가 thread 안이 아니라 새로운 top-level post처럼 보임
replyStyle을 지정합니다.
Attachments & Images
Current limitations:- DMs: Teams bot file API를 통해 image와 file attachment가 동작합니다.
- Channels/groups: attachment는 M365 storage(SharePoint/OneDrive)에 저장됩니다. webhook payload에는 실제 file bytes가 아니라 HTML stub만 포함됩니다. channel attachment를 다운로드하려면 Graph API permissions가 필요합니다.
channels.msteams.mediaAllowHosts로 override할 수 있습니다. (["*"]도 가능)
Authorization header는 channels.msteams.mediaAuthAllowHosts에 포함된 host에만 붙습니다. 기본값은 Graph + Bot Framework host이며, 이 목록은 가능한 엄격하게 유지하는 것이 좋습니다.
Sending files in group chats
bot은 DM에서는 FileConsentCard flow를 사용해 file을 보낼 수 있습니다. 그러나 group chat/channel에서 file을 보내려면 추가 설정이 필요합니다.| Context | How files are sent | Setup needed |
|---|---|---|
| DMs | FileConsentCard → user accepts → bot uploads | 기본 동작으로 지원 |
| Group chats/channels | SharePoint에 업로드 후 sharing link 전송 | sharePointSiteId + Graph permissions 필요 |
| Images (any context) | Base64-encoded inline | 기본 동작으로 지원 |
Why group chats need SharePoint
bot에는 개인 OneDrive가 없습니다. (/me/drive Graph API endpoint는 application identity에 동작하지 않음) 따라서 group chat/channel에 file을 보내려면 SharePoint site에 업로드한 뒤 sharing link를 생성해야 합니다.
Setup
-
Entra ID (Azure AD) → App Registration에서 Graph API permissions를 추가합니다.
Sites.ReadWrite.All(Application) - SharePoint file uploadChat.Read.All(Application) - 선택 사항, user별 sharing link 가능
- tenant에 대해 Grant admin consent를 수행합니다.
-
SharePoint site ID를 가져옵니다.
-
OpenClaw에 설정합니다.
Sharing behavior
| Permission | Sharing behavior |
|---|---|
Sites.ReadWrite.All only | org 전체 공유 링크 (조직 내 누구나 접근 가능) |
Sites.ReadWrite.All + Chat.Read.All | per-user sharing link (해당 chat member만 접근 가능) |
Chat.Read.All이 없으면 bot은 org-wide sharing으로 fallback합니다.
Fallback behavior
| Scenario | Result |
|---|---|
Group chat + file + sharePointSiteId configured | SharePoint 업로드 후 sharing link 전송 |
Group chat + file + no sharePointSiteId | OneDrive upload 시도 후 실패 가능, text만 전송 |
| Personal chat + file | FileConsentCard flow 사용 |
| Any context + image | Base64 inline 전송 |
Files stored location
업로드된 파일은 설정된 SharePoint site의 기본 document library 안/OpenClawShared/ 폴더에 저장됩니다.
Polls (Adaptive Cards)
OpenClaw는 Teams poll을 Adaptive Cards로 전송합니다. Teams에는 native poll API가 없습니다.- CLI:
openclaw message poll --channel msteams --target conversation:<id> ... - 투표 결과는 gateway가
~/.openclaw/msteams-polls.json에 기록합니다. - 투표 기록을 남기려면 gateway가 계속 online 상태여야 합니다.
- 아직 결과 요약을 자동 게시하지는 않습니다. 필요하면 store file을 직접 확인하세요.
Adaptive Cards (arbitrary)
message tool 또는 CLI를 사용해 Teams user나 conversation에 임의의 Adaptive Card JSON을 보낼 수 있습니다.
card parameter는 Adaptive Card JSON object를 받습니다. card가 있으면 message text는 선택 사항입니다.
Agent tool:
Target formats
MSTeams target은 user와 conversation을 구분하기 위해 prefix를 사용합니다.| Target type | Format | Example |
|---|---|---|
| User (by ID) | user:<aad-object-id> | user:40a1a0ed-4ff2-4164-a219-55518990c197 |
| User (by name) | user:<display-name> | user:John Smith (Graph API 필요) |
| Group/channel | conversation:<conversation-id> | conversation:19:abc123...@thread.tacv2 |
| Group/channel (raw) | <conversation-id> | 19:abc123...@thread.tacv2 (if contains @thread) |
user: prefix 없이 이름을 쓰면 group/team 해석이 기본값이 됩니다. 사람 이름을 target으로 쓸 때는 항상 user:를 붙이세요.
Proactive messaging
- proactive message는 user가 먼저 한 번 상호작용한 뒤에만 가능합니다. 그때 conversation reference를 저장하기 때문입니다.
dmPolicy와 allowlist gating은/gateway/configuration을 참고하세요.
Team and Channel IDs (Common Gotcha)
Teams URL의groupId query parameter는 config에 사용하는 team ID가 아닙니다. ID는 URL path에서 추출해야 합니다.
Team URL:
- Team ID =
/team/뒤 path segment (URL-decoded, 예:19:Bk4j...@thread.tacv2) - Channel ID =
/channel/뒤 path segment (URL-decoded) groupIdquery parameter는 무시
Private Channels
private channel에서 bot 지원은 제한적입니다.| Feature | Standard Channels | Private Channels |
|---|---|---|
| Bot installation | Yes | Limited |
| Real-time messages (webhook) | Yes | May not work |
| RSC permissions | Yes | May behave differently |
| @mentions | Yes | If bot is accessible |
| Graph API history | Yes | Yes (with permissions) |
- bot interaction은 standard channel을 사용
- DM 사용 - 사용자는 언제든 bot에게 직접 메시지 가능
- Graph API로 historical access 사용 (
ChannelMessage.Read.All필요)
Troubleshooting
Common issues
- 채널에서 이미지가 보이지 않음: Graph permission 또는 admin consent가 빠진 경우가 많습니다. Teams app을 재설치하고 Teams를 완전히 종료 후 다시 실행하세요.
- 채널에서 응답이 없음: 기본값으로 mention이 필요합니다.
channels.msteams.requireMention=false또는 team/channel별 override를 설정하세요. - Version mismatch (Teams에 옛 manifest가 계속 보임): app을 제거 후 다시 추가하고 Teams를 완전히 종료해 cache를 비우세요.
- Webhook의 401 Unauthorized: Azure JWT 없이 수동 테스트하면 정상적으로 보이는 현상입니다. endpoint는 도달 가능하지만 auth가 실패한 상태입니다. 검증은 Azure Web Chat으로 하세요.
Manifest upload errors
- “Icon file cannot be empty”: manifest가 참조하는 icon file이 0 byte입니다. 유효한 PNG icon을 생성하세요. (
outline.png32x32,color.png192x192) - “webApplicationInfo.Id already in use”: app이 다른 team/chat에 아직 설치되어 있습니다. 먼저 제거하거나 5~10분 정도 propagation을 기다리세요.
- “Something went wrong” on upload: https://admin.teams.microsoft.com에서 업로드하고, 브라우저 DevTools(F12) → Network 탭에서 실제 오류 응답 본문을 확인하세요.
- Sideload failing: “Upload a custom app” 대신 “Upload an app to your org’s app catalog”를 시도하세요. sideload 제한을 우회하는 경우가 많습니다.
RSC permissions not working
webApplicationInfo.id가 bot의 App ID와 정확히 일치하는지 확인- app을 다시 업로드하고 team/chat에 재설치
- 조직 admin이 RSC permission을 차단하지 않았는지 확인
- scope가 맞는지 확인: team에는
ChannelMessage.Read.Group, group chat에는ChatMessage.Read.Chat
References
- Create Azure Bot - Azure Bot setup guide
- Teams Developer Portal - Teams app 생성/관리
- Teams app manifest schema
- Receive channel messages with RSC
- RSC permissions reference
- Teams bot file handling (channel/group에는 Graph 필요)
- Proactive messaging