콘텐츠로 이동

Microsoft Teams 플러그인 설정하기

“여기에 들어오는 자, 모든 희망을 버려라.”

업데이트: 2026-01-21

상태: 텍스트와 DM 첨부파일이 지원돼요. 채널이나 그룹에서 파일을 보내려면 sharePointSiteId와 Graph 권한이 필요해요 (그룹 채팅에서 파일 보내기 참고). 설문조사는 Adaptive Cards를 통해 전송돼요. 메시지 액션은 파일 우선 전송을 위해 명시적인 upload-file을 노출해요.

Microsoft Teams는 플러그인 형태로 제공되며 코어 설치 패키지에 포함되어 있지 않아요.

변경 사항 (2026.1.15): Microsoft Teams가 코어에서 분리되었어요. Teams를 사용하려면 반드시 플러그인을 설치해야 해요.

이유: 코어 설치 용량을 가볍게 유지하고, Microsoft Teams 의존성만 독립적으로 업데이트하기 위해서예요.

CLI를 통한 설치 (npm registry):

Terminal window
openclaw plugins install @openclaw/msteams

로컬 체크아웃 (git 저장소에서 실행 중인 경우):

Terminal window
openclaw plugins install ./path/to/local/msteams-plugin

설정 과정에서 Teams를 선택하고 git 체크아웃이 감지되면, OpenClaw가 로컬 설치 경로를 자동으로 제안해 줄 거예요.

상세 내용: Plugins

  1. Microsoft Teams 플러그인을 설치하세요.
  2. Azure Bot을 생성하세요 (App ID + client secret + tenant ID).
  3. 해당 자격 증명을 사용해 OpenClaw를 설정하세요.
  4. 공용 URL이나 터널을 통해 /api/messages (기본 포트 3978)를 외부로 노출하세요.
  5. Teams 앱 패키지를 설치하고 Gateway를 시작하세요.

최소 설정 예시:

{
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 액세스

  • 기본값은 channels.msteams.dmPolicy = "pairing"이에요. 승인 전까지는 모르는 발신자가 보낸 메시지는 무시됩니다.
  • channels.msteams.allowFrom에는 고정된 AAD object IDs를 사용하는 것이 좋습니다.
  • UPN이나 표시 이름은 변경될 수 있기 때문에, 이름 매칭 기능은 기본적으로 비활성화되어 있어요. channels.msteams.dangerouslyAllowNameMatching: true를 설정해야만 사용할 수 있습니다.
  • 설정 위저드(wizard)를 사용하면 권한이 있는 경우 Microsoft Graph를 통해 이름을 ID로 변환할 수 있어요.

그룹 액세스

  • 기본값은 channels.msteams.groupPolicy = "allowlist"입니다. groupAllowFrom에 추가하지 않으면 차단돼요. 설정값이 없을 때 기본 동작을 바꾸려면 channels.defaults.groupPolicy를 사용하세요.
  • channels.msteams.groupAllowFrom은 그룹 채팅이나 채널에서 어떤 발신자가 트리거를 발생시킬 수 있는지 제어합니다 (설정이 없으면 channels.msteams.allowFrom 설정을 따릅니다).
  • 모든 멤버를 허용하려면 groupPolicy: "open"으로 설정하세요 (이 경우에도 기본적으로 멘션이 필요합니다).
  • 모든 채널을 허용하지 않으려면 channels.msteams.groupPolicy: "disabled"로 설정하면 돼요.

Example:

{
channels: {
msteams: {
groupPolicy: "allowlist",
groupAllowFrom: ["user@org.com"],
},
},
}

Teams + 채널 allowlist

  • channels.msteams.teams 아래에 팀과 채널을 나열해서 응답 범위를 제한할 수 있어요.
  • Key 값은 고정된 team IDs와 channel conversation IDs를 사용해야 합니다.
  • groupPolicy="allowlist"이고 팀 allowlist가 설정되어 있다면, 목록에 있는 팀과 채널만 허용됩니다 (멘션 필요).
  • 설정 위저드에서 Team/Channel 항목을 입력하면 자동으로 저장해 줍니다.
  • OpenClaw는 시작할 때 (Graph 권한이 있다면) 팀/채널 및 사용자 allowlist 이름을 ID로 변환하고 매핑 정보를 로그에 기록해요. 변환되지 않은 팀/채널 이름은 입력한 대로 유지되지만, channels.msteams.dangerouslyAllowNameMatching: true가 활성화되어 있지 않으면 기본적으로 라우팅에서 무시됩니다.

Example:

{
channels: {
msteams: {
groupPolicy: "allowlist",
teams: {
"My Team": {
channels: {
General: { requireMention: true },
},
},
},
},
},
}
  1. Microsoft Teams 플러그인을 설치하세요.
  2. Azure Bot을 생성합니다 (App ID + secret + tenant ID).
  3. 봇을 참조하고 아래의 RSC 권한을 포함하는 Teams app package를 만드세요.
  4. Teams 앱을 팀에 업로드하거나 설치하세요 (DM의 경우 개인 범위).
  5. ~/.openclaw/openclaw.json (또는 환경 변수)에서 msteams를 설정하고 Gateway를 시작합니다.
  6. Gateway는 기본적으로 /api/messages 경로에서 Bot Framework webhook 트래픽을 대기해요.

OpenClaw를 설정하기 전에 Azure Bot 리소스를 먼저 생성해야 합니다.

  1. Azure Bot 만들기 페이지로 이동하세요.

  2. 기본 사항(Basics) 탭을 작성합니다.

    FieldValue
    Bot handle봇 이름, 예: openclaw-msteams (고유해야 함)
    SubscriptionAzure 구독 선택
    Resource group새로 만들거나 기존 그룹 사용
    Pricing tier개발/테스트용은 Free 선택
    Type of AppSingle Tenant (권장 - 아래 참고 사항 확인)
    Creation typeCreate new Microsoft App ID

지원 중단 공지: 2025년 7월 31일 이후로 새로운 multi-tenant 봇 생성 기능이 지원 중단되었습니다. 새로운 봇에는 Single Tenant를 사용하세요.

  1. 검토 + 만들기 → 만들기를 클릭하세요 (1~2분 정도 소요됩니다).

2단계: 자격 증명(Credentials) 가져오기

섹션 제목: “2단계: 자격 증명(Credentials) 가져오기”
  1. Azure Bot 리소스의 구성(Configuration) 메뉴로 이동하세요.
  2. Microsoft App ID를 복사하세요. 이 값이 appId가 됩니다.
  3. **암호 관리(Manage Password)**를 클릭하여 앱 등록 페이지로 이동합니다.
  4. 인증서 및 암호(Certificates & secrets) → **새 클라이언트 암호(New client secret)**를 클릭하고 **값(Value)**을 복사하세요. 이 값이 appPassword입니다.
  5. **개요(Overview)**로 이동해서 **디렉터리(테넌트) ID(Directory (tenant) ID)**를 복사하세요. 이 값이 tenantId입니다.
  1. Azure Bot의 구성(Configuration) 메뉴로 이동하세요.
  2. **메시징 엔드포인트(Messaging endpoint)**를 여러분의 webhook URL로 설정합니다.
    • 프로덕션: https://your-domain.com/api/messages
    • 로컬 개발: 터널을 사용하세요 (아래 로컬 개발 섹션 참고).
  1. Azure Bot의 채널(Channels) 메뉴로 이동하세요.
  2. Microsoft Teams를 클릭하고 구성한 뒤 저장하세요.
  3. 서비스 약관에 동의합니다.

Teams는 localhost에 직접 연결할 수 없어요. 로컬 개발 시에는 터널을 사용해야 합니다.

방법 A: ngrok

Terminal window
ngrok http 3978
# Copy the https URL, e.g., https://abc123.ngrok.io
# Set messaging endpoint to: https://abc123.ngrok.io/api/messages

방법 B: Tailscale Funnel

Terminal window
tailscale funnel 3978
# Use your Tailscale funnel URL as the messaging endpoint

매니페스트 ZIP 파일을 직접 만드는 대신 Teams Developer Portal을 사용할 수 있어요.

  1. + New app을 클릭하세요.
  2. 기본 정보(이름, 설명, 개발자 정보)를 입력하세요.
  3. App features → Bot으로 이동하세요.
  4. Enter a bot ID manually를 선택하고 Azure Bot App ID를 붙여넣으세요.
  5. Scopes를 확인하세요: Personal, Team, Group Chat.
  6. Distribute → Download app package를 클릭하세요.
  7. Teams에서: Apps → Manage your apps → Upload a custom app → ZIP 파일을 선택하세요.

JSON 매니페스트를 직접 수정하는 것보다 이 방법이 훨씬 편할 거예요.

옵션 A: Azure Web Chat (웹훅 먼저 확인)

  1. Azure Portal → Azure Bot 리소스 → Test in Web Chat으로 이동하세요.
  2. 메시지를 보내보세요. 응답이 와야 합니다.
  3. Teams 설정을 하기 전에 웹훅 Endpoint가 잘 작동하는지 확인할 수 있어요.

옵션 B: Teams (앱 설치 후)

  1. Teams 앱을 설치하세요 (사이드로드 또는 조직 카탈로그 이용).
  2. Teams에서 봇을 찾아 DM을 보내보세요.
  3. Gateway 로그에 인입되는 활동이 있는지 확인하세요.
  1. Microsoft Teams 플러그인 설치

    • npm 이용: openclaw plugins install @openclaw/msteams
    • 로컬 체크아웃 이용: openclaw plugins install ./path/to/local/msteams-plugin
  2. 봇 등록

    • Azure Bot을 생성하고(위 내용 참고) 다음 정보를 챙겨두세요:
      • App ID
      • Client secret (App password)
      • Tenant ID (single-tenant)
  3. Teams 앱 매니페스트

    • botId = <App ID>가 포함된 bot 항목을 넣으세요.
    • Scopes: personal, team, groupChat.
    • supportsFiles: true (Personal scope에서 파일 처리를 위해 필요해요).
    • RSC 권한을 추가하세요 (아래 참고).
    • 아이콘을 만드세요: outline.png (32x32) 및 color.png (192x192).
    • manifest.json, outline.png, color.png 세 파일을 하나로 압축하세요.
  4. OpenClaw 설정

    {
    channels: {
    msteams: {
    enabled: true,
    appId: "<APP_ID>",
    appPassword: "<APP_PASSWORD>",
    tenantId: "<TENANT_ID>",
    webhook: { port: 3978, path: "/api/messages" },
    },
    },
    }

    설정 키 대신 환경 변수를 사용할 수도 있어요:

    • MSTEAMS_APP_ID
    • MSTEAMS_APP_PASSWORD
    • MSTEAMS_TENANT_ID
  5. 봇 Endpoint

    • Azure Bot Messaging Endpoint를 다음과 같이 설정하세요:
      • https://<host>:3978/api/messages (또는 설정한 경로/포트).
  6. Gateway 실행

    • 플러그인이 설치되어 있고 인증 정보가 포함된 msteams 설정이 있으면 Teams 채널이 자동으로 시작돼요.

OpenClaw는 Microsoft Teams를 위한 Graph 기반 member-info 액션을 제공해요. 이 기능을 통해 에이전트나 자동화 도구가 Microsoft Graph에서 직접 채널 멤버의 상세 정보(표시 이름, 이메일, 역할)를 가져올 수 있어요.

요구 사항:

  • Member.Read.Group RSC 권한 (추천 매니페스트에 이미 포함되어 있어요)
  • 교차 팀(cross-team) 조회 시: 관리자 동의가 완료된 User.Read.All Graph Application 권한

이 액션은 channels.msteams.actions.memberInfo 설정으로 제어할 수 있어요 (Graph 인증 정보가 있으면 기본적으로 활성화돼요).

개발을 하다 보면 이전 대화 맥락을 파악하는 게 정말 중요하죠. Teams에서 히스토리를 어떻게 관리하고 권한을 설정하는지 정리해 드릴게요.

히스토리 컨텍스트 (History context)

섹션 제목: “히스토리 컨텍스트 (History context)”
  • channels.msteams.historyLimit 설정은 프롬프트에 포함할 최근 채널 및 그룹 메시지 개수를 조절해요.
  • 설정값이 없으면 messages.groupChat.historyLimit를 대신 사용해요. 0으로 설정하면 비활성화되고, 기본값은 50이에요.
  • 가져온 스레드 히스토리는 발신자 허용 리스트(allowFrom / groupAllowFrom)에 따라 필터링돼요. 즉, 허용된 발신자가 보낸 메시지만 스레드 컨텍스트에 포함된다는 점을 기억하세요.
  • DM 히스토리는 channels.msteams.dmHistoryLimit로 제한할 수 있어요. 사용자별로 다르게 설정하고 싶다면 channels.msteams.dms["<user_id>"].historyLimit를 사용하면 돼요.

Teams 앱 manifest에 정의된 기존 resourceSpecific 권한들이에요. 이 권한들은 앱이 설치된 팀이나 채팅 내부에서만 적용돼요.

채널(팀 스코프)용:

  • ChannelMessage.Read.Group (Application) - @mention 없이도 모든 채널 메시지 수신
  • 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 없이도 모든 그룹 채팅 메시지 수신

필요한 필드가 포함된 유효한 최소 예시예요. 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 권한을 추가하는 등 업데이트가 필요할 때는 이렇게 하세요.

  1. manifest.json 파일을 새로운 설정으로 업데이트해요.
  2. version 필드 값을 올려주세요 (예: 1.0.0 → 1.1.0).
  3. manifest와 아이콘 파일들(manifest.json, outline.png, color.png)을 **다시 압축(zip)**해요.
  4. 새로운 zip 파일을 업로드해요.
    • 방법 A (Teams 관리 센터): Teams 관리 센터 → Teams 앱 → 앱 관리 → 앱 찾기 → 새 버전 업로드
    • 방법 B (사이드로드): Teams 내에서 → 앱 → 앱 관리 → 사용자 지정 앱 업로드
  5. 팀 채널의 경우: 새로운 권한이 적용되도록 각 팀에 앱을 다시 설치하세요.
  6. 캐시된 앱 메타데이터를 지우기 위해 Teams를 완전히 종료한 후 다시 실행하세요 (창만 닫으면 안 돼요).

Teams RSC만 사용하는 경우 (앱 설치됨, Graph API 권한 없음)

섹션 제목: “Teams RSC만 사용하는 경우 (앱 설치됨, Graph API 권한 없음)”

가능한 작업:

  • 채널 메시지의 텍스트 내용 읽기
  • 채널 메시지 텍스트 내용 전송
  • 개인 DM 파일 첨부 수신

불가능한 작업:

  • 채널/그룹의 이미지나 파일 내용 확인 (HTML 스텁만 포함된 페이로드가 전달돼요)
  • SharePoint/OneDrive에 저장된 첨부 파일 다운로드
  • 실시간 webhook 이벤트 이외의 메시지 히스토리 읽기

Teams RSC와 Microsoft Graph Application 권한을 함께 사용하는 경우

섹션 제목: “Teams RSC와 Microsoft Graph Application 권한을 함께 사용하는 경우”

추가되는 기능:

  • 호스팅된 콘텐츠 다운로드 (메시지에 붙여넣은 이미지 등)
  • SharePoint/OneDrive에 저장된 첨부 파일 다운로드
  • Graph를 통해 채널/채팅 메시지 히스토리 읽기
기능RSC 권한Graph API
실시간 메시지가능 (webhook 방식)불가능 (폴링만 가능)
과거 메시지 히스토리불가능가능 (히스토리 쿼리 가능)
설정 복잡도앱 manifest만 설정관리자 동의 및 토큰 흐름 필요
오프라인 작동불가능 (실행 중이어야 함)가능 (언제든 쿼리 가능)

요약하자면: RSC는 실시간 리스닝을 위한 것이고, Graph API는 과거 데이터에 접근하기 위한 것이에요. 오프라인 상태일 때 놓친 메시지까지 파악하려면 ChannelMessage.Read.All 권한이 있는 Graph API가 필요해요.

AI Setup Assistant

Graph 활성화 미디어 + 히스토리 (채널 필수 사항)

섹션 제목: “Graph 활성화 미디어 + 히스토리 (채널 필수 사항)”

채널에서 이미지나 파일을 사용하거나 메시지 히스토리를 가져오려면 Microsoft Graph 권한을 활성화하고 관리자 동의를 받아야 해요.

  1. Entra ID (Azure AD) App Registration에서 다음 Microsoft Graph Application permissions를 추가하세요.
    • ChannelMessage.Read.All (채널 첨부 파일 + 히스토리)
    • Chat.Read.All 또는 ChatMessage.Read.All (그룹 채팅)
  2. 테넌트에 대해 **관리자 동의(Grant admin consent)**를 완료하세요.
  3. Teams 앱 manifest version을 올리고, 다시 업로드한 뒤 Teams에 앱을 재설치하세요.
  4. 캐시된 앱 메타데이터를 삭제하기 위해 Teams를 완전히 종료하고 다시 실행하세요.

사용자 멘션 관련 추가 권한: 대화에 참여 중인 사용자에 대한 @멘션은 기본적으로 작동해요. 하지만 현재 대화에 참여하지 않은 사용자를 동적으로 검색하고 멘션하고 싶다면 User.Read.All (Application) 권한을 추가하고 관리자 동의를 받으세요.

Teams는 HTTP webhook을 통해 메시지를 전달해요. 처리 시간이 너무 길어지면(예: LLM 응답 지연) 다음과 같은 문제가 발생할 수 있어요.

  • Gateway 타임아웃
  • Teams의 메시지 재시도 (중복 발생 원인)
  • 응답 누락

OpenClaw는 빠르게 응답을 반환하고 나중에 응답을 보내는 방식으로 이를 처리하지만, 응답이 매우 느리면 여전히 문제가 생길 수 있어요.

Teams의 markdown은 Slack이나 Discord보다 제한적이에요.

  • 기본 포맷팅은 잘 작동해요: 굵게, 기울임, 코드, 링크
  • 복잡한 markdown(표, 중첩 리스트)은 제대로 렌더링되지 않을 수 있어요.
  • 투표나 임의의 카드 전송을 위한 Adaptive Cards는 지원돼요 (아래 내용 참고).

주요 설정값이에요 (공유 채널 패턴은 /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 object 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 (Reply Style 참고).
  • 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.actions.memberInfo: Graph 기반 멤버 정보 액션 활성화/비활성화 (기본값: Graph 자격 증명이 있을 때 활성화).
  • channels.msteams.sharePointSiteId: 그룹 채팅/채널의 파일 업로드를 위한 SharePoint 사이트 ID (Sending files in group chats 참고).
  • 세션 키는 표준 에이전트 형식을 따라요 (/concepts/session 참고):
    • 다이렉트 메시지(DM)는 메인 세션을 공유해요 (agent:<agentId>:<mainKey>).
    • 채널/그룹 메시지는 conversation id를 사용해요:
      • agent:<agentId>:msteams:channel:<conversationId>
      • agent:<agentId>:msteams:group:<conversationId>

Teams는 최근 동일한 데이터 모델을 기반으로 두 가지 채널 UI 스타일을 도입했어요.

스타일설명권장 replyStyle
Posts (기존 방식)메시지가 카드 형태로 표시되며 아래에 스레드 답변이 달림thread (기본값)
Threads (Slack 방식)메시지가 Slack처럼 선형적으로 흐름top-level

문제점: Teams API는 채널이 어떤 UI 스타일을 사용하는지 노출하지 않아요. 잘못된 replyStyle을 사용하면 다음과 같은 현상이 발생해요.

  • Threads 스타일 채널에서 thread 사용 → 답변이 어색하게 중첩되어 표시됨
  • Posts 스타일 채널에서 top-level 사용 → 답변이 스레드 내부가 아닌 별개의 상위 포스트로 표시됨

해결 방법: 채널이 설정된 방식에 따라 채널별로 replyStyle을 구성하세요.

{
channels: {
msteams: {
replyStyle: "thread",
teams: {
"19:abc...@thread.tacv2": {
channels: {
"19:xyz...@thread.tacv2": {
replyStyle: "top-level",
},
},
},
},
},
},
}

현재 몇 가지 제한 사항이 있어요.

  • DM: 이미지와 파일 첨부는 Teams 봇 파일 API를 통해 작동해요.
  • 채널/그룹: 첨부 파일은 M365 스토리지(SharePoint/OneDrive)에 저장돼요. Webhook 페이로드에는 실제 파일 바이트가 아니라 HTML 스텁만 포함돼요. 채널 첨부 파일을 다운로드하려면 Graph API 권한이 필요해요.
  • 파일 우선 전송: 파일을 명시적으로 먼저 보내려면 media / filePath / path와 함께 action=upload-file을 사용하세요. 선택 사항인 message는 함께 전송되는 텍스트/댓글이 되며, filename은 업로드된 이름을 덮어써요.
  • 권한 및 호스트 설정: Graph 권한이 없으면 이미지가 포함된 채널 메시지는 텍스트 전용으로 수신돼요(봇이 이미지 콘텐츠에 접근할 수 없어요). 기본적으로 OpenClaw는 Microsoft/Teams 호스트 이름에서만 미디어를 다운로드해요. 이를 변경하려면 channels.msteams.mediaAllowHosts를 사용하세요(모든 호스트를 허용하려면 ["*"] 사용). Authorization 헤더는 channels.msteams.mediaAuthAllowHosts에 등록된 호스트에만 추가돼요(기본값은 Graph + Bot Framework 호스트). 이 목록은 엄격하게 관리하세요.

봇은 DM에서 기본 제공되는 FileConsentCard 흐름을 사용해 파일을 보낼 수 있어요. 하지만 그룹 채팅이나 채널에서 파일을 보내려면 추가 설정이 필요해요.

컨텍스트파일 전송 방식필요한 설정
DMFileConsentCard → 사용자 수락 → 봇 업로드별도 설정 없이 작동
그룹 채팅/채널SharePoint에 업로드 → 공유 링크 전송sharePointSiteId + Graph 권한 필요
이미지 (모든 컨텍스트)Base64 인코딩 인라인별도 설정 없이 작동

그룹 채팅에 SharePoint가 필요한 이유

섹션 제목: “그룹 채팅에 SharePoint가 필요한 이유”

봇은 개인 OneDrive 드라이브를 가지고 있지 않아요(애플리케이션 ID의 경우 /me/drive Graph API 엔드포인트가 작동하지 않아요). 그룹 채팅이나 채널에서 파일을 보내기 위해 봇은 SharePoint 사이트에 업로드하고 공유 링크를 생성해요.

  1. Entra ID (Azure AD) → 앱 등록에서 Graph API 권한을 추가하세요.

    • Sites.ReadWrite.All (Application) - SharePoint에 파일 업로드
    • Chat.Read.All (Application) - 선택 사항, 사용자별 공유 링크 활성화
  2. 테넌트에 대해 관리자 동의를 부여하세요.

  3. SharePoint 사이트 ID를 가져오세요.

    Terminal window
    # Via Graph Explorer or curl with a valid token:
    curl -H "Authorization: Bearer $TOKEN" \
    "https://graph.microsoft.com/v1.0/sites/{hostname}:/{site-path}"
    # Example: for a site at "contoso.sharepoint.com/sites/BotFiles"
    curl -H "Authorization: Bearer $TOKEN" \
    "https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/BotFiles"
    # Response includes: "id": "contoso.sharepoint.com,guid1,guid2"
  4. OpenClaw를 설정하세요.

    {
    channels: {
    msteams: {
    // ... other config ...
    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/ 폴더에 저장돼요.

OpenClaw는 Teams 투표를 Adaptive Cards로 전송해요(Teams 자체 투표 API는 없어요).

  • CLI: openclaw message poll --channel msteams --target conversation:<id> ...
  • 투표 결과는 Gateway의 ~/.openclaw/msteams-polls.json에 기록돼요.
  • 투표를 기록하려면 Gateway가 온라인 상태를 유지해야 해요.
  • 투표 결과 요약은 아직 자동으로 게시되지 않아요(필요한 경우 저장된 파일을 확인하세요).

message 툴이나 CLI를 사용해서 Teams 사용자나 대화방에 원하는 Adaptive Card JSON을 보낼 수 있어요.

card 파라미터는 Adaptive Card JSON 객체를 받아요. card가 포함된 경우, 메시지 텍스트는 선택 사항이에요.

Agent tool:

{
action: "send",
channel: "msteams",
target: "user:<id>",
card: {
type: "AdaptiveCard",
version: "1.5",
body: [{ type: "TextBlock", text: "Hello!" }],
},
}

CLI:

Terminal window
openclaw message send --channel msteams \
--target "conversation:19:abc...@thread.tacv2" \
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello!"}]}'

카드 스키마와 예시는 Adaptive Cards documentation를 참고해 주세요. 대상 형식에 대한 자세한 내용은 아래 대상 형식 섹션에서 확인할 수 있어요.

MSTeams 대상은 사용자와 대화를 구분하기 위해 접두사(prefix)를 사용해요.

대상 유형형식예시
사용자 (ID 기준)user:<aad-object-id>user:40a1a0ed-4ff2-4164-a219-55518990c197
사용자 (이름 기준)user:<display-name>user:John Smith (Graph API 필요)
그룹/채널conversation:<conversation-id>conversation:19:abc123...@thread.tacv2
그룹/채널 (Raw)<conversation-id>19:abc123...@thread.tacv2 (@thread 포함 시)

CLI 예시:

Terminal window
# Send to a user by ID
openclaw message send --channel msteams --target "user:40a1a0ed-..." --message "Hello"
# Send to a user by display name (triggers Graph API lookup)
openclaw message send --channel msteams --target "user:John Smith" --message "Hello"
# Send to a group chat or channel
openclaw message send --channel msteams --target "conversation:19:abc...@thread.tacv2" --message "Hello"
# Send an Adaptive Card to a conversation
openclaw message send --channel msteams --target "conversation:19:abc...@thread.tacv2" \
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello"}]}'

Agent tool 예시:

{
action: "send",
channel: "msteams",
target: "user:John Smith",
message: "Hello!",
}
{
action: "send",
channel: "msteams",
target: "conversation:19:abc...@thread.tacv2",
card: {
type: "AdaptiveCard",
version: "1.5",
body: [{ type: "TextBlock", text: "Hello" }],
},
}

참고: user: 접두사가 없으면 이름은 기본적으로 그룹이나 팀으로 인식돼요. 표시 이름으로 사람을 지정할 때는 항상 user:를 사용해 주세요.

선제적 메시지 전송 (Proactive messaging)

섹션 제목: “선제적 메시지 전송 (Proactive messaging)”

선제적 메시지는 사용자가 먼저 상호작용을 한 이후에만 보낼 수 있어요. 그 시점에 대화 참조(conversation references)를 저장해 두기 때문이죠. dmPolicy나 allowlist 게이팅에 대한 자세한 내용은 /gateway/configuration 문서를 참고해 보세요.

Teams URL에 포함된 groupId 쿼리 파라미터는 설정을 위한 팀 ID가 아니에요. 이 부분에서 실수가 정말 많은데, ID는 URL 경로에서 직접 추출해야 합니다.

Team URL:

https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=...
└────────────────────────────┘
Team ID (URL-decode this)

Channel URL:

https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=...
└─────────────────────────┘
Channel ID (URL-decode this)

설정 시 주의사항:

  • Team ID = /team/ 뒤에 오는 경로 세그먼트 (URL-decoded, 예: 19:Bk4j...@thread.tacv2)
  • Channel ID = /channel/ 뒤에 오는 경로 세그먼트 (URL-decoded)
  • groupId 쿼리 파라미터는 무시하세요.

비공개 채널에서 봇은 제한적으로만 작동해요.

기능표준 채널비공개 채널
봇 설치예제한적
실시간 메시지 (webhook)예작동하지 않을 수 있음
RSC 권한예다르게 동작할 수 있음
@mentions예봇에 접근 가능한 경우
Graph API 기록예예 (권한 필요)

비공개 채널이 제대로 작동하지 않을 때의 해결 방법:

  1. 봇과 상호작용할 때는 표준 채널을 사용하세요.
  2. DM을 활용해 보세요. 사용자는 언제든 봇에게 직접 메시지를 보낼 수 있어요.
  3. 과거 기록에 접근하려면 Graph API를 사용하세요 (ChannelMessage.Read.All 권한이 필요해요).
  • 채널에서 이미지가 보이지 않음: Graph 권한이나 관리자 동의가 누락된 경우예요. Teams 앱을 다시 설치하고 Teams를 완전히 종료했다가 다시 열어보세요.
  • 채널에서 응답이 없음: 기본적으로 멘션이 필요해요. channels.msteams.requireMention=false로 설정하거나 팀/채널별로 구성하세요.
  • 버전 불일치 (Teams에 여전히 이전 manifest가 표시됨): 앱을 제거하고 다시 추가한 뒤, Teams를 완전히 종료하여 새로고침하세요.
  • webhook에서 401 Unauthorized 발생: Azure JWT 없이 수동으로 테스트할 때 발생하는 예상된 결과예요. 엔드포인트에는 도달했지만 인증에 실패했다는 뜻이죠. 제대로 테스트하려면 Azure Web Chat을 사용하세요.
  • “Icon file cannot be empty”: Manifest가 0바이트인 아이콘 파일을 참조하고 있네요. 유효한 PNG 아이콘을 만드세요 (outline.png는 32x32, color.png는 192x192).
  • “webApplicationInfo.Id already in use”: 앱이 이미 다른 팀이나 채팅에 설치되어 있어요. 해당 앱을 찾아 삭제하거나, 변경 사항이 반영될 때까지 5~10분 정도 기다려 보세요.
  • 업로드 중 “Something went wrong” 발생: 대신 https://admin.teams.microsoft.com을 통해 업로드를 시도해 보세요. 브라우저 개발자 도구(F12)의 Network 탭을 열고 응답 본문(response body)에서 실제 오류 내용을 확인하세요.
  • 사이드로딩 실패: “커스텀 앱 업로드” 대신 “조직의 앱 카탈로그에 앱 업로드”를 시도해 보세요. 이 방법으로 사이드로딩 제한을 우회할 수 있는 경우가 많아요.
  1. webApplicationInfo.id가 봇의 App ID와 정확히 일치하는지 확인하세요.
  2. 앱을 다시 업로드하고 팀/채팅에 다시 설치하세요.
  3. 조직 관리자가 RSC 권한을 차단했는지 확인하세요.
  4. 올바른 scope를 사용 중인지 확인하세요. 팀의 경우 ChannelMessage.Read.Group, 그룹 채팅의 경우 ChatMessage.Read.Chat이 필요해요.

AI Setup Assistant

  • Channels Overview — 지원하는 모든 채널을 확인할 수 있어요.
  • Pairing — DM 인증과 페어링 흐름에 대한 내용이에요.
  • Groups — 그룹 채팅 동작과 멘션 게이팅을 다뤄요.
  • Channel Routing — 메시지 세션 라우팅에 대해 알아보세요.
  • Security — 액세스 모델과 보안 강화 방법이에요.
OpenClaw

OpenClaw Expert

아직 막혀 있나요?

이 문서에서 답을 못 찾았다면 OpenClaw Expert에게 바로 물어보세요.