Microsoft Teams (플러그인)
“여기에 들어오는 자, 모든 희망을 버려라.”
업데이트: 2026-01-21
상태: 텍스트 + DM 첨부 파일 지원됨. 채널/그룹 파일 전송에는 sharePointSiteId + Graph 권한이 필요합니다 (그룹 채팅에서 파일 전송 참조). 설문조사는 Adaptive Card로 전송됩니다.
플러그인 필요
Microsoft Teams는 플러그인으로 제공되며 핵심 설치에 포함되어 있지 않습니다.
호환성 변경 (2026.1.15): MS Teams가 핵심에서 분리되었습니다. 사용하려면 플러그인을 설치해야 합니다.
이유: 핵심 설치를 가볍게 유지하고 MS Teams 종속성을 독립적으로 업데이트할 수 있게 합니다.
CLI를 통한 설치 (npm 레지스트리):
openclaw plugins install @openclaw/msteams
로컬 체크아웃 (git 레포에서 실행하는 경우):
openclaw plugins install ./extensions/msteams
설정/온보딩 중에 Teams를 선택하고 git 체크아웃이 감지되면 OpenClaw가 자동으로 로컬 설치 경로를 제안합니다.
자세히: Plugins
빠른 설정 (초보자)
- Microsoft Teams 플러그인을 설치합니다.
- Azure Bot을 생성합니다 (App ID + client secret + tenant ID).
- 해당 자격 증명으로 OpenClaw를 설정합니다.
/api/messages(기본 포트 3978)를 공개 URL 또는 터널을 통해 노출합니다.- Teams 앱 패키지를 설치하고 게이트웨이를 시작합니다.
최소 설정:
{
channels: {
msteams: {
enabled: true,
appId: "<APP_ID>",
appPassword: "<APP_PASSWORD>",
tenantId: "<TENANT_ID>",
webhook: { port: 3978, path: "/api/messages" },
},
},
}
참고: 그룹 채팅은 기본적으로 차단됩니다 (channels.msteams.groupPolicy: "allowlist"). 그룹 응답을 허용하려면 channels.msteams.groupAllowFrom을 설정하거나 (groupPolicy: "open"으로 모든 멤버를 허용, 멘션 게이팅 적용).
목표
- Teams DM, 그룹 채팅, 채널을 통해 OpenClaw와 대화.
- 결정론적 라우팅 유지: 응답은 항상 도착한 채널로 돌아감.
- 기본적으로 안전한 채널 동작 (별도 설정이 없으면 멘션 필요).
설정 쓰기
기본적으로 Microsoft Teams는 /config set|unset에 의해 트리거되는 설정 업데이트를 쓸 수 있습니다 (commands.config: true 필요).
비활성화:
{
channels: { msteams: { configWrites: false } },
}
접근 제어 (DM + 그룹)
DM 접근
- 기본값:
channels.msteams.dmPolicy = "pairing". 알 수 없는 발신자는 승인 전까지 무시됩니다. channels.msteams.allowFrom은 안정적인 AAD 개체 ID를 사용해야 합니다.- UPN/표시 이름은 변경 가능하므로 직접 매칭은 기본적으로 비활성화되어 있으며,
channels.msteams.dangerouslyAllowNameMatching: true로만 활성화됩니다. - 자격 증명이 허용되면 마법사가 Microsoft Graph를 통해 이름을 ID로 해석할 수 있습니다.
그룹 접근
- 기본값:
channels.msteams.groupPolicy = "allowlist"(groupAllowFrom을 추가하지 않으면 차단됨). 미설정 시 기본값을 재정의하려면channels.defaults.groupPolicy를 사용합니다. channels.msteams.groupAllowFrom은 그룹 채팅/채널에서 트리거할 수 있는 발신자를 제어합니다 (channels.msteams.allowFrom으로 폴백).groupPolicy: "open"으로 모든 멤버를 허용합니다 (기본적으로 멘션 게이팅 적용).- 채널을 전혀 허용하지 않으려면
channels.msteams.groupPolicy: "disabled"를 설정합니다.
예시:
{
channels: {
msteams: {
groupPolicy: "allowlist",
groupAllowFrom: ["[email protected]"],
},
},
}
팀 + 채널 허용 목록
channels.msteams.teams에서 팀과 채널을 나열하여 그룹/채널 응답 범위를 지정합니다.- 키는 안정적인 팀 ID와 채널 대화 ID를 사용해야 합니다.
groupPolicy="allowlist"이고 팀 허용 목록이 있으면 나열된 팀/채널만 수락됩니다 (멘션 게이팅).- 설정 마법사는
Team/Channel항목을 받아 저장합니다. - 시작 시 OpenClaw는 팀/채널 및 사용자 허용 목록 이름을 ID로 해석하고 (Graph 권한이 허용되는 경우) 매핑을 로깅합니다. 해석되지 않은 팀/채널 이름은 입력대로 유지되지만
channels.msteams.dangerouslyAllowNameMatching: true가 활성화되지 않으면 라우팅에서 기본적으로 무시됩니다.
예시:
{
channels: {
msteams: {
groupPolicy: "allowlist",
teams: {
"My Team": {
channels: {
General: { requireMention: true },
},
},
},
},
},
}
작동 방식
- Microsoft Teams 플러그인을 설치합니다.
- Azure Bot을 생성합니다 (App ID + secret + tenant ID).
- 봇을 참조하고 아래 RSC 권한을 포함하는 Teams 앱 패키지를 빌드합니다.
- Teams 앱을 팀에 업로드/설치합니다 (또는 DM용 개인 범위).
~/.openclaw/openclaw.json(또는 환경 변수)에서msteams를 설정하고 게이트웨이를 시작합니다.- 게이트웨이는 기본적으로
/api/messages에서 Bot Framework 웹훅 트래픽을 수신합니다.
Azure Bot 설정 (사전 준비)
OpenClaw를 설정하기 전에 Azure Bot 리소스를 생성해야 합니다.
1단계: Azure Bot 생성
-
Create Azure Bot으로 이동합니다.
-
Basics 탭을 작성합니다:
필드 값 Bot handle 봇 이름, 예: openclaw-msteams(고유해야 함)Subscription Azure 구독 선택 Resource group 새로 만들기 또는 기존 사용 Pricing tier 개발/테스트용 Free Type of App Single Tenant (권장 - 아래 참고 참조) Creation type Create new Microsoft App ID
참고: 2025-07-31 이후 새 멀티 테넌트 봇 생성이 폐기되었습니다. 새 봇에는 Single Tenant를 사용하세요.
- Review + create -> Create 클릭 (약 1-2분 소요)
2단계: 자격 증명 가져오기
- Azure Bot 리소스 -> Configuration으로 이동합니다.
- Microsoft App ID 복사 -> 이것이
appId입니다. - Manage Password 클릭 -> App Registration으로 이동합니다.
- Certificates & secrets -> New client secret -> Value 복사 -> 이것이
appPassword입니다. - Overview -> Directory (tenant) ID 복사 -> 이것이
tenantId입니다.
3단계: 메시징 엔드포인트 설정
- Azure Bot -> Configuration에서
- Messaging endpoint를 웹훅 URL로 설정합니다:
- 프로덕션:
https://your-domain.com/api/messages - 로컬 개발: 터널 사용 (아래 로컬 개발 참조)
- 프로덕션:
4단계: Teams 채널 활성화
- Azure Bot -> Channels에서
- Microsoft Teams 클릭 -> Configure -> Save
- 서비스 약관 동의
로컬 개발 (터널링)
Teams는 localhost에 접근할 수 없습니다. 로컬 개발에는 터널을 사용합니다:
옵션 A: ngrok
ngrok http 3978
# https URL 복사, 예: https://abc123.ngrok.io
# 메시징 엔드포인트 설정: https://abc123.ngrok.io/api/messages
옵션 B: Tailscale Funnel
tailscale funnel 3978
# Tailscale funnel URL을 메시징 엔드포인트로 사용
Teams 개발자 포털 (대안)
매니페스트 ZIP을 수동으로 만드는 대신 Teams Developer Portal을 사용할 수 있습니다:
- + New app 클릭
- 기본 정보 작성 (이름, 설명, 개발자 정보)
- App features -> Bot으로 이동
- Enter a bot ID manually 선택 후 Azure Bot App ID 붙여넣기
- 범위 체크: Personal, Team, Group Chat
- Distribute -> Download app package 클릭
- Teams에서: Apps -> Manage your apps -> Upload a custom app -> ZIP 선택
이 방법이 JSON 매니페스트를 직접 편집하는 것보다 편리한 경우가 많습니다.
봇 테스트
옵션 A: Azure Web Chat (웹훅 먼저 확인)
- Azure Portal -> Azure Bot 리소스 -> Test in Web Chat
- 메시지 전송 - 응답이 와야 합니다
- Teams 설정 전에 웹훅 엔드포인트가 작동하는지 확인합니다
옵션 B: Teams (앱 설치 후)
- Teams 앱 설치 (사이드로드 또는 조직 카탈로그)
- Teams에서 봇을 찾아 DM 전송
- 게이트웨이 로그에서 수신 활동 확인
설정 (최소 텍스트 전용)
-
Microsoft Teams 플러그인 설치
- npm:
openclaw plugins install @openclaw/msteams - 로컬 체크아웃:
openclaw plugins install ./extensions/msteams
- npm:
-
봇 등록
- Azure Bot을 생성하고 (위 참조) 다음을 메모합니다:
- App ID
- Client secret (App password)
- Tenant ID (single-tenant)
- Azure Bot을 생성하고 (위 참조) 다음을 메모합니다:
-
Teams 앱 매니페스트
botId = <App ID>인bot항목을 포함합니다.- 범위:
personal,team,groupChat. supportsFiles: true(개인 범위 파일 처리에 필요).- RSC 권한 추가 (아래).
- 아이콘 생성:
outline.png(32x32) 및color.png(192x192). - 세 파일을 함께 압축:
manifest.json,outline.png,color.png.
-
OpenClaw 설정
{ "msteams": { "enabled": true, "appId": "<APP_ID>", "appPassword": "<APP_PASSWORD>", "tenantId": "<TENANT_ID>", "webhook": { "port": 3978, "path": "/api/messages" } } }설정 키 대신 환경 변수를 사용할 수도 있습니다:
MSTEAMS_APP_IDMSTEAMS_APP_PASSWORDMSTEAMS_TENANT_ID
-
봇 엔드포인트
- Azure Bot 메시징 엔드포인트를 다음으로 설정합니다:
https://<host>:3978/api/messages(또는 선택한 경로/포트).
- Azure Bot 메시징 엔드포인트를 다음으로 설정합니다:
-
게이트웨이 실행
- 플러그인이 설치되고 자격 증명이 포함된
msteams설정이 있으면 Teams 채널이 자동으로 시작됩니다.
- 플러그인이 설치되고 자격 증명이 포함된
히스토리 컨텍스트
channels.msteams.historyLimit은 프롬프트에 포함되는 최근 채널/그룹 메시지 수를 제어합니다.messages.groupChat.historyLimit으로 폴백합니다.0으로 비활성화 (기본 50).- DM 히스토리는
channels.msteams.dmHistoryLimit(사용자 턴)으로 제한할 수 있습니다. 사용자별 재정의:channels.msteams.dms["<user_id>"].historyLimit.
현재 Teams RSC 권한 (매니페스트)
Teams 앱 매니페스트의 기존 resourceSpecific 권한입니다. 앱이 설치된 팀/채팅 내에서만 적용됩니다.
채널 (팀 범위):
ChannelMessage.Read.Group(Application) - @멘션 없이 모든 채널 메시지 수신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) - @멘션 없이 모든 그룹 채팅 메시지 수신
Teams 매니페스트 예시 (수정됨)
필수 필드가 포함된 최소한의 유효한 예시입니다. ID와 URL을 교체하세요.
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.23/MicrosoftTeams.schema.json",
"manifestVersion": "1.23",
"version": "1.0.0",
"id": "00000000-0000-0000-0000-000000000000",
"name": { "short": "OpenClaw" },
"developer": {
"name": "Your Org",
"websiteUrl": "https://example.com",
"privacyUrl": "https://example.com/privacy",
"termsOfUseUrl": "https://example.com/terms"
},
"description": { "short": "OpenClaw in Teams", "full": "OpenClaw in Teams" },
"icons": { "outline": "outline.png", "color": "color.png" },
"accentColor": "#5B6DEF",
"bots": [
{
"botId": "11111111-1111-1111-1111-111111111111",
"scopes": ["personal", "team", "groupChat"],
"isNotificationOnly": false,
"supportsCalling": false,
"supportsVideo": false,
"supportsFiles": true
}
],
"webApplicationInfo": {
"id": "11111111-1111-1111-1111-111111111111"
},
"authorization": {
"permissions": {
"resourceSpecific": [
{ "name": "ChannelMessage.Read.Group", "type": "Application" },
{ "name": "ChannelMessage.Send.Group", "type": "Application" },
{ "name": "Member.Read.Group", "type": "Application" },
{ "name": "Owner.Read.Group", "type": "Application" },
{ "name": "ChannelSettings.Read.Group", "type": "Application" },
{ "name": "TeamMember.Read.Group", "type": "Application" },
{ "name": "TeamSettings.Read.Group", "type": "Application" },
{ "name": "ChatMessage.Read.Chat", "type": "Application" }
]
}
}
}
매니페스트 주의사항 (필수 필드)
bots[].botId는 Azure Bot App ID와 반드시 일치해야 합니다.webApplicationInfo.id는 Azure Bot App ID와 반드시 일치해야 합니다.bots[].scopes에는 사용할 표면이 포함되어야 합니다 (personal,team,groupChat).bots[].supportsFiles: true는 개인 범위 파일 처리에 필요합니다.authorization.permissions.resourceSpecific에는 채널 트래픽을 원하면 채널 읽기/전송이 포함되어야 합니다.
기존 앱 업데이트
이미 설치된 Teams 앱을 업데이트하려면 (예: RSC 권한 추가):
- 새 설정으로
manifest.json을 업데이트합니다 version필드를 증가시킵니다 (예:1.0.0->1.1.0)- 아이콘과 함께 매니페스트를 다시 압축합니다 (
manifest.json,outline.png,color.png) - 새 zip을 업로드합니다:
- 옵션 A (Teams 관리 센터): Teams Admin Center -> Teams apps -> Manage apps -> 앱 찾기 -> Upload new version
- 옵션 B (사이드로드): Teams -> Apps -> Manage your apps -> Upload a custom app
- 팀 채널의 경우: 새 권한이 적용되려면 각 팀에 앱을 다시 설치합니다
- Teams를 완전히 종료 후 다시 실행합니다 (창만 닫는 것이 아님) - 캐시된 앱 메타데이터를 초기화합니다
기능: RSC만 vs Graph
Teams RSC만 (앱 설치됨, Graph API 권한 없음)
가능:
- 채널 메시지 텍스트 콘텐츠 읽기.
- 채널 메시지 텍스트 콘텐츠 전송.
- 개인 (DM) 파일 첨부 수신.
불가능:
- 채널/그룹 이미지 또는 파일 콘텐츠 (페이로드에 HTML 스텁만 포함).
- SharePoint/OneDrive에 저장된 첨부 파일 다운로드.
- 메시지 히스토리 읽기 (실시간 웹훅 이벤트 이후).
Teams RSC + Microsoft Graph Application 권한
추가:
- 호스팅된 콘텐츠 다운로드 (메시지에 붙여넣은 이미지).
- SharePoint/OneDrive에 저장된 파일 첨부 다운로드.
- Graph를 통한 채널/채팅 메시지 히스토리 읽기.
RSC vs Graph API
| 기능 | RSC 권한 | Graph API |
|---|---|---|
| 실시간 메시지 | 예 (웹훅 통해) | 아니오 (폴링만) |
| 과거 메시지 | 아니오 | 예 (히스토리 조회 가능) |
| 설정 복잡도 | 앱 매니페스트만 | 관리자 동의 + 토큰 흐름 필요 |
| 오프라인 작동 | 아니오 (실행 필요) | 예 (언제든 조회 가능) |
요약: RSC는 실시간 수신용이고, Graph API는 과거 접근용입니다. 오프라인 동안 놓친 메시지를 따라잡으려면 ChannelMessage.Read.All (관리자 동의 필요)이 포함된 Graph API가 필요합니다.
Graph 지원 미디어 + 히스토리 (채널에 필요)
채널에서 이미지/파일이 필요하거나 메시지 히스토리를 가져오려면 Microsoft Graph 권한을 활성화하고 관리자 동의를 부여해야 합니다.
- Entra ID (Azure AD) App Registration에서 Microsoft Graph Application permissions를 추가합니다:
ChannelMessage.Read.All(채널 첨부 파일 + 히스토리)Chat.Read.All또는ChatMessage.Read.All(그룹 채팅)
- 테넌트에 관리자 동의를 부여합니다.
- Teams 앱 매니페스트 버전을 올리고, 다시 업로드하고, Teams에서 앱을 다시 설치합니다.
- Teams를 완전히 종료 후 다시 실행하여 캐시된 앱 메타데이터를 초기화합니다.
사용자 멘션을 위한 추가 권한: 대화 참여자에 대한 사용자 @멘션은 기본적으로 작동합니다. 그러나 현재 대화에 없는 사용자를 동적으로 검색하고 멘션하려면 User.Read.All (Application) 권한을 추가하고 관리자 동의를 부여합니다.
알려진 제한 사항
웹훅 타임아웃
Teams는 HTTP 웹훅을 통해 메시지를 전달합니다. 처리 시간이 너무 오래 걸리면 (예: 느린 LLM 응답):
- 게이트웨이 타임아웃
- Teams가 메시지를 재시도 (중복 발생)
- 응답 누락
OpenClaw는 빠르게 반환하고 사전에 응답을 전송하여 이를 처리하지만, 매우 느린 응답은 여전히 문제를 일으킬 수 있습니다.
서식
Teams 마크다운은 Slack이나 Discord보다 제한적입니다:
- 기본 서식 가능: 굵게, 기울임,
코드, 링크 - 복잡한 마크다운 (표, 중첩 목록)은 올바르게 렌더링되지 않을 수 있음
- Adaptive Card는 설문조사 및 임의 카드 전송에 지원됨 (아래 참조)
설정
주요 설정 (공유 채널 패턴은 /gateway/configuration 참조):
channels.msteams.enabled: 채널 활성화/비활성화.channels.msteams.appId,channels.msteams.appPassword,channels.msteams.tenantId: 봇 자격 증명.channels.msteams.webhook.port(기본3978)channels.msteams.webhook.path(기본/api/messages)channels.msteams.dmPolicy:pairing | allowlist | open | disabled(기본: pairing)channels.msteams.allowFrom: DM 허용 목록 (AAD 개체 ID 권장). 마법사는 Graph 접근이 가능한 경우 설정 시 이름을 ID로 해석합니다.channels.msteams.dangerouslyAllowNameMatching: 변경 가능한 UPN/표시 이름 매칭 및 직접 팀/채널 이름 라우팅을 다시 활성화하는 비상 토글.channels.msteams.textChunkLimit: 아웃바운드 텍스트 청크 크기.channels.msteams.chunkMode:length(기본) 또는newline(길이 청킹 전에 빈 줄(단락 경계)로 분할).channels.msteams.mediaAllowHosts: 인바운드 첨부 호스트 허용 목록 (기본 Microsoft/Teams 도메인).channels.msteams.mediaAuthAllowHosts: 미디어 재시도 시 Authorization 헤더를 첨부할 호스트 허용 목록 (기본 Graph + Bot Framework 호스트).channels.msteams.requireMention: 채널/그룹에서 @멘션 필요 (기본 true).channels.msteams.replyStyle:thread | top-level(응답 스타일 참조).channels.msteams.teams.<teamId>.replyStyle: 팀별 재정의.channels.msteams.teams.<teamId>.requireMention: 팀별 재정의.channels.msteams.teams.<teamId>.tools: 채널 재정의가 없을 때 사용되는 팀별 기본 도구 정책 재정의 (allow/deny/alsoAllow).channels.msteams.teams.<teamId>.toolsBySender: 팀별 발신자별 도구 정책 재정의 ("*"와일드카드 지원).channels.msteams.teams.<teamId>.channels.<conversationId>.replyStyle: 채널별 재정의.channels.msteams.teams.<teamId>.channels.<conversationId>.requireMention: 채널별 재정의.channels.msteams.teams.<teamId>.channels.<conversationId>.tools: 채널별 도구 정책 재정의 (allow/deny/alsoAllow).channels.msteams.teams.<teamId>.channels.<conversationId>.toolsBySender: 채널별 발신자별 도구 정책 재정의 ("*"와일드카드 지원).toolsBySender키는 명시적 접두사를 사용해야 합니다:id:,e164:,username:,name:(레거시 접두사 없는 키는 여전히id:로만 매핑).channels.msteams.sharePointSiteId: 그룹 채팅/채널에서 파일 업로드를 위한 SharePoint 사이트 ID (그룹 채팅에서 파일 전송 참조).
라우팅 및 세션
- 세션 키는 표준 에이전트 형식을 따릅니다 (/concepts/session 참조):
- 다이렉트 메시지는 메인 세션을 공유합니다 (
agent:<agentId>:<mainKey>). - 채널/그룹 메시지는 대화 ID를 사용합니다:
agent:<agentId>:msteams:channel:<conversationId>agent:<agentId>:msteams:group:<conversationId>
- 다이렉트 메시지는 메인 세션을 공유합니다 (
응답 스타일: 스레드 vs 게시물
Teams는 최근 동일한 기본 데이터 모델 위에 두 가지 채널 UI 스타일을 도입했습니다:
| 스타일 | 설명 | 권장 replyStyle |
|---|---|---|
| Posts (클래식) | 메시지가 카드로 표시되고 아래에 스레드 응답이 달림 | thread (기본값) |
| Threads (Slack 스타일) | 메시지가 Slack처럼 선형으로 흐름 | top-level |
문제점: Teams API는 채널이 어떤 UI 스타일을 사용하는지 노출하지 않습니다. 잘못된 replyStyle을 사용하면:
- Threads 스타일 채널에서
thread-> 응답이 어색하게 중첩됨 - Posts 스타일 채널에서
top-level-> 응답이 스레드 내 대신 별도 최상위 게시물로 표시됨
해결 방법: 채널 설정에 따라 replyStyle을 채널별로 설정합니다:
{
"msteams": {
"replyStyle": "thread",
"teams": {
"19:[email protected]": {
"channels": {
"19:[email protected]": {
"replyStyle": "top-level"
}
}
}
}
}
}
첨부 파일 및 이미지
현재 제한 사항:
- DM: Teams 봇 파일 API를 통해 이미지 및 파일 첨부 가능.
- 채널/그룹: 첨부 파일은 M365 스토리지 (SharePoint/OneDrive)에 저장됩니다. 웹훅 페이로드에는 실제 파일 바이트가 아닌 HTML 스텁만 포함됩니다. 채널 첨부 파일을 다운로드하려면 Graph API 권한이 필요합니다.
Graph 권한이 없으면 이미지가 포함된 채널 메시지는 텍스트 전용으로 수신됩니다 (이미지 콘텐츠에 봇이 접근할 수 없음).
기본적으로 OpenClaw는 Microsoft/Teams 호스트 이름에서만 미디어를 다운로드합니다. channels.msteams.mediaAllowHosts로 재정의합니다 (모든 호스트를 허용하려면 ["*"] 사용).
Authorization 헤더는 channels.msteams.mediaAuthAllowHosts의 호스트에만 첨부됩니다 (기본 Graph + Bot Framework 호스트). 이 목록은 엄격하게 유지합니다 (멀티 테넌트 접미사 사용 금지).
그룹 채팅에서 파일 전송
봇은 DM에서 FileConsentCard 흐름을 사용하여 파일을 전송할 수 있습니다 (내장). 그러나 그룹 채팅/채널에서 파일을 전송하려면 추가 설정이 필요합니다:
| 컨텍스트 | 파일 전송 방법 | 필요한 설정 |
|---|---|---|
| DM | FileConsentCard -> 사용자 수락 -> 봇 업로드 | 기본 지원 |
| 그룹 채팅/채널 | SharePoint에 업로드 -> 공유 링크 | sharePointSiteId + Graph 권한 필요 |
| 이미지 (모든 컨텍스트) | Base64 인코딩 인라인 | 기본 지원 |
그룹 채팅에 SharePoint가 필요한 이유
봇에는 개인 OneDrive 드라이브가 없습니다 (/me/drive Graph API 엔드포인트는 애플리케이션 ID에서 작동하지 않음). 그룹 채팅/채널에서 파일을 전송하려면 봇이 SharePoint 사이트에 업로드하고 공유 링크를 생성합니다.
설정
-
Entra ID (Azure AD) -> App Registration에서 Graph API 권한을 추가합니다:
Sites.ReadWrite.All(Application) - SharePoint에 파일 업로드Chat.Read.All(Application) - 선택 사항, 사용자별 공유 링크 활성화
-
테넌트에 관리자 동의를 부여합니다.
-
SharePoint 사이트 ID 가져오기:
# Graph Explorer 또는 유효한 토큰을 사용한 curl: curl -H "Authorization: Bearer $TOKEN" \ "https://graph.microsoft.com/v1.0/sites/{hostname}:/{site-path}" # 예: "contoso.sharepoint.com/sites/BotFiles" 사이트의 경우 curl -H "Authorization: Bearer $TOKEN" \ "https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/BotFiles" # 응답에 포함: "id": "contoso.sharepoint.com,guid1,guid2" -
OpenClaw 설정:
{ channels: { msteams: { // ... 기타 설정 ... sharePointSiteId: "contoso.sharepoint.com,guid1,guid2", }, }, }
공유 동작
| 권한 | 공유 동작 |
|---|---|
Sites.ReadWrite.All만 | 조직 전체 공유 링크 (조직 내 누구나 접근 가능) |
Sites.ReadWrite.All + Chat.Read.All | 사용자별 공유 링크 (채팅 멤버만 접근 가능) |
사용자별 공유가 더 안전하며 채팅 참여자만 파일에 접근할 수 있습니다. Chat.Read.All 권한이 없으면 봇은 조직 전체 공유로 폴백합니다.
폴백 동작
| 시나리오 | 결과 |
|---|---|
그룹 채팅 + 파일 + sharePointSiteId 설정됨 | SharePoint에 업로드, 공유 링크 전송 |
그룹 채팅 + 파일 + sharePointSiteId 없음 | OneDrive 업로드 시도 (실패 가능), 텍스트만 전송 |
| 개인 채팅 + 파일 | FileConsentCard 흐름 (SharePoint 없이 작동) |
| 모든 컨텍스트 + 이미지 | Base64 인코딩 인라인 (SharePoint 없이 작동) |
파일 저장 위치
업로드된 파일은 설정된 SharePoint 사이트의 기본 문서 라이브러리 /OpenClawShared/ 폴더에 저장됩니다.
설문조사 (Adaptive Cards)
OpenClaw는 Teams 설문조사를 Adaptive Card로 전송합니다 (네이티브 Teams 설문조사 API가 없음).
- CLI:
openclaw message poll --channel msteams --target conversation:<id> ... - 투표는 게이트웨이의
~/.openclaw/msteams-polls.json에 기록됩니다. - 투표를 기록하려면 게이트웨이가 온라인 상태여야 합니다.
- 설문조사는 아직 결과 요약을 자동 게시하지 않습니다 (필요하면 저장소 파일을 확인).
Adaptive Cards (임의)
메시지 도구 또는 CLI를 사용하여 Teams 사용자 또는 대화에 임의의 Adaptive Card JSON을 전송합니다.
card 매개변수는 Adaptive Card JSON 객체를 받습니다. card가 제공되면 메시지 텍스트는 선택 사항입니다.
에이전트 도구:
{
"action": "send",
"channel": "msteams",
"target": "user:<id>",
"card": {
"type": "AdaptiveCard",
"version": "1.5",
"body": [{ "type": "TextBlock", "text": "Hello!" }]
}
}
CLI:
openclaw message send --channel msteams \
--target "conversation:19:[email protected]" \
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello!"}]}'
Adaptive Card 스키마 및 예시는 Adaptive Cards documentation을 참조하세요. 대상 형식 세부사항은 아래 대상 형식을 참조하세요.
대상 형식
MSTeams 대상은 접두사를 사용하여 사용자와 대화를 구분합니다:
| 대상 유형 | 형식 | 예시 |
|---|---|---|
| 사용자 (ID별) | user:<aad-object-id> | user:40a1a0ed-4ff2-4164-a219-55518990c197 |
| 사용자 (이름별) | user:<display-name> | user:John Smith (Graph API 필요) |
| 그룹/채널 | conversation:<conversation-id> | conversation:19:[email protected] |
| 그룹/채널 (원시) | <conversation-id> | 19:[email protected] (@thread 포함 시) |
CLI 예시:
# ID로 사용자에게 전송
openclaw message send --channel msteams --target "user:40a1a0ed-..." --message "Hello"
# 표시 이름으로 사용자에게 전송 (Graph API 조회 트리거)
openclaw message send --channel msteams --target "user:John Smith" --message "Hello"
# 그룹 채팅 또는 채널에 전송
openclaw message send --channel msteams --target "conversation:19:[email protected]" --message "Hello"
# 대화에 Adaptive Card 전송
openclaw message send --channel msteams --target "conversation:19:[email protected]" \
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello"}]}'
에이전트 도구 예시:
{
"action": "send",
"channel": "msteams",
"target": "user:John Smith",
"message": "Hello!"
}
{
"action": "send",
"channel": "msteams",
"target": "conversation:19:[email protected]",
"card": {
"type": "AdaptiveCard",
"version": "1.5",
"body": [{ "type": "TextBlock", "text": "Hello" }]
}
}
참고: user: 접두사가 없으면 이름은 기본적으로 그룹/팀 해석으로 됩니다. 표시 이름으로 사람을 대상으로 할 때는 항상 user:를 사용하세요.
사전 메시지
- 사전 메시지는 사용자가 상호작용한 후에만 가능합니다. 그 시점에 대화 참조가 저장되기 때문입니다.
dmPolicy및 허용 목록 게이팅은/gateway/configuration을 참조하세요.
팀 및 채널 ID (흔한 실수)
Teams URL의 groupId 쿼리 매개변수는 설정에 사용되는 팀 ID가 아닙니다. URL 경로에서 ID를 추출하세요:
팀 URL:
https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=...
└────────────────────────────┘
팀 ID (URL 디코딩 필요)
채널 URL:
https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=...
└─────────────────────────┘
채널 ID (URL 디코딩 필요)
설정용:
- 팀 ID =
/team/뒤의 경로 세그먼트 (URL 디코딩됨, 예:19:[email protected]) - 채널 ID =
/channel/뒤의 경로 세그먼트 (URL 디코딩됨) groupId쿼리 매개변수는 무시
비공개 채널
비공개 채널에서는 봇 지원이 제한적입니다:
| 기능 | 표준 채널 | 비공개 채널 |
|---|---|---|
| 봇 설치 | 예 | 제한적 |
| 실시간 메시지 (웹훅) | 예 | 작동하지 않을 수 있음 |
| RSC 권한 | 예 | 다르게 동작할 수 있음 |
| @멘션 | 예 | 봇 접근 가능한 경우 |
| Graph API 히스토리 | 예 | 예 (권한 있으면) |
비공개 채널이 작동하지 않을 때 해결 방법:
- 봇 상호작용에는 표준 채널을 사용합니다
- DM을 사용합니다 - 사용자가 항상 봇에게 직접 메시지를 보낼 수 있습니다
- 과거 접근을 위해 Graph API를 사용합니다 (
ChannelMessage.Read.All필요)
문제 해결
일반적인 문제
- 채널에서 이미지가 표시되지 않음: Graph 권한 또는 관리자 동의 누락. Teams 앱을 다시 설치하고 Teams를 완전히 종료 후 다시 엽니다.
- 채널에서 응답 없음: 기본적으로 멘션이 필요합니다.
channels.msteams.requireMention=false를 설정하거나 팀/채널별로 설정합니다. - 버전 불일치 (Teams가 여전히 이전 매니페스트 표시): 앱을 제거 + 다시 추가하고 Teams를 완전히 종료하여 새로고침합니다.
- 웹훅에서 401 Unauthorized: Azure JWT 없이 수동으로 테스트할 때 예상됨 - 엔드포인트에 접근 가능하지만 인증이 실패했음을 의미합니다. 적절한 테스트에는 Azure Web Chat을 사용합니다.
매니페스트 업로드 오류
- “Icon file cannot be empty”: 매니페스트가 0바이트인 아이콘 파일을 참조합니다. 유효한 PNG 아이콘을 생성합니다 (
outline.png32x32,color.png192x192). - “webApplicationInfo.Id already in use”: 앱이 아직 다른 팀/채팅에 설치되어 있습니다. 먼저 찾아서 제거하거나 전파를 위해 5-10분 기다립니다.
- 업로드 시 “Something went wrong”: https://admin.teams.microsoft.com을 통해 업로드하고 브라우저 DevTools (F12) -> Network 탭을 열어 실제 오류를 확인합니다.
- 사이드로드 실패: “Upload a custom app” 대신 “Upload an app to your org’s app catalog”를 시도합니다 - 사이드로드 제한을 우회하는 경우가 많습니다.
RSC 권한이 작동하지 않음
webApplicationInfo.id가 봇의 App ID와 정확히 일치하는지 확인합니다- 앱을 다시 업로드하고 팀/채팅에 다시 설치합니다
- 조직 관리자가 RSC 권한을 차단했는지 확인합니다
- 올바른 범위를 사용하고 있는지 확인합니다: 팀에는
ChannelMessage.Read.Group, 그룹 채팅에는ChatMessage.Read.Chat
참고 자료
- Create Azure Bot - Azure Bot 설정 가이드
- Teams Developer Portal - Teams 앱 생성/관리
- Teams app manifest schema
- Receive channel messages with RSC
- RSC permissions reference
- Teams bot file handling (채널/그룹은 Graph 필요)
- Proactive messaging