메인 콘텐츠로 건너뛰기

Documentation Index

Fetch the complete documentation index at: https://docs.openclaw.ai/llms.txt

Use this file to discover all available pages before exploring further.

상태: 텍스트 + DM 첨부 파일이 지원됩니다. 채널/그룹 파일 전송에는 sharePointSiteId + Graph 권한이 필요합니다(그룹 채팅에서 파일 보내기 참조). 투표는 Adaptive Cards를 통해 전송됩니다. 메시지 작업은 파일 우선 전송을 위해 명시적인 upload-file을 노출합니다.

번들 Plugin

Microsoft Teams는 현재 OpenClaw 릴리스에서 번들 Plugin으로 제공되므로, 일반 패키지 빌드에서는 별도 설치가 필요하지 않습니다. 이전 빌드를 사용 중이거나 번들 Teams를 제외한 사용자 지정 설치를 사용 중이라면 npm 패키지를 직접 설치하세요.
openclaw plugins install @openclaw/msteams
현재 공식 릴리스 태그를 따르려면 bare 패키지를 사용하세요. 재현 가능한 설치가 필요할 때만 정확한 버전을 고정하세요. 로컬 체크아웃(git repo에서 실행하는 경우):
openclaw plugins install ./path/to/local/msteams-plugin
자세한 내용: Plugins

빠른 설정

@microsoft/teams.cli는 단일 명령으로 bot 등록, manifest 생성, 자격 증명 생성을 처리합니다. 1. 설치 및 로그인
npm install -g @microsoft/teams.cli@preview
teams login
teams status   # verify you're logged in and see your tenant info
Teams CLI는 현재 preview 상태입니다. 명령과 플래그는 릴리스 간에 변경될 수 있습니다.
2. 터널 시작(Teams는 localhost에 접근할 수 없습니다) 아직 설치하지 않았다면 devtunnel CLI를 설치하고 인증하세요(시작 가이드).
# One-time setup (persistent URL across sessions):
devtunnel create my-openclaw-bot --allow-anonymous
devtunnel port create my-openclaw-bot -p 3978 --protocol auto

# Each dev session:
devtunnel host my-openclaw-bot
# Your endpoint: https://<tunnel-id>.devtunnels.ms/api/messages
Teams는 devtunnels로 인증할 수 없으므로 --allow-anonymous가 필요합니다. 들어오는 각 bot 요청은 여전히 Teams SDK가 자동으로 검증합니다.
대안: ngrok http 3978 또는 tailscale funnel 3978(하지만 세션마다 URL이 변경될 수 있음). 3. 앱 생성
teams app create \
  --name "OpenClaw" \
  --endpoint "https://<your-tunnel-url>/api/messages"
이 단일 명령은 다음을 수행합니다.
  • Entra ID(Azure AD) 애플리케이션 생성
  • 클라이언트 secret 생성
  • Teams 앱 manifest 빌드 및 업로드(아이콘 포함)
  • bot 등록(기본적으로 Teams에서 관리 - Azure 구독 불필요)
출력에 CLIENT_ID, CLIENT_SECRET, TENANT_ID, Teams App ID가 표시됩니다. 다음 단계를 위해 기록해 두세요. 또한 Teams에 앱을 직접 설치할 수도 있습니다. 4. OpenClaw 구성 출력의 자격 증명을 사용하세요.
{
  channels: {
    msteams: {
      enabled: true,
      appId: "<CLIENT_ID>",
      appPassword: "<CLIENT_SECRET>",
      tenantId: "<TENANT_ID>",
      webhook: { port: 3978, path: "/api/messages" },
    },
  },
}
또는 환경 변수를 직접 사용하세요: MSTEAMS_APP_ID, MSTEAMS_APP_PASSWORD, MSTEAMS_TENANT_ID. 5. Teams에 앱 설치 teams app create는 앱 설치를 요청합니다. “Install in Teams”를 선택하세요. 건너뛰었다면 나중에 링크를 가져올 수 있습니다.
teams app get <teamsAppId> --install-link
6. 모든 것이 작동하는지 확인
teams app doctor <teamsAppId>
이 명령은 bot 등록, AAD 앱 구성, manifest 유효성, SSO 설정 전반의 진단을 실행합니다. 프로덕션 배포에서는 클라이언트 secret 대신 federated authentication(인증서 또는 관리 ID) 사용을 고려하세요.
그룹 채팅은 기본적으로 차단됩니다(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 또는 accessGroup:core-team 같은 정적 보낸 사람 접근 그룹을 사용해야 합니다.
  • 허용 목록에 UPN/표시 이름 매칭을 신뢰하지 마세요. 변경될 수 있습니다. OpenClaw는 기본적으로 직접 이름 매칭을 비활성화합니다. channels.msteams.dangerouslyAllowNameMatching: true로 명시적으로 옵트인하세요.
  • wizard는 자격 증명이 허용하는 경우 Microsoft Graph를 통해 이름을 ID로 확인할 수 있습니다.
그룹 접근
  • 기본값: channels.msteams.groupPolicy = "allowlist"(groupAllowFrom을 추가하지 않으면 차단). 설정되지 않은 기본값을 재정의하려면 channels.defaults.groupPolicy를 사용하세요.
  • channels.msteams.groupAllowFrom은 그룹 채팅/채널에서 어떤 보낸 사람 또는 정적 보낸 사람 접근 그룹이 트리거할 수 있는지 제어합니다(channels.msteams.allowFrom으로 fallback).
  • 모든 구성원을 허용하려면 groupPolicy: "open"을 설정하세요(기본적으로 여전히 멘션 게이트 적용).
  • 채널을 전혀 허용하지 않으려면 channels.msteams.groupPolicy: "disabled"를 설정하세요.
예:
{
  channels: {
    msteams: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["00000000-0000-0000-0000-000000000000", "accessGroup:core-team"],
    },
  },
}
Teams + 채널 허용 목록
  • channels.msteams.teams 아래에 팀과 채널을 나열해 그룹/채널 답장의 범위를 지정하세요.
  • 키는 변경 가능한 표시 이름이 아니라 Teams 링크의 안정적인 Teams 대화 ID를 사용해야 합니다.
  • groupPolicy="allowlist"이고 teams 허용 목록이 있으면, 나열된 팀/채널만 허용됩니다(멘션 게이트 적용).
  • 구성 wizard는 Team/Channel 항목을 받아 저장합니다.
  • 시작 시 OpenClaw는 team/channel 및 사용자 허용 목록 이름을 ID로 확인하고(Graph 권한이 허용하는 경우) 매핑을 로그에 기록합니다. 확인되지 않은 team/channel 이름은 입력한 그대로 유지되지만, channels.msteams.dangerouslyAllowNameMatching: true가 활성화되어 있지 않으면 기본적으로 라우팅에서 무시됩니다.
예:
{
  channels: {
    msteams: {
      groupPolicy: "allowlist",
      teams: {
        "My Team": {
          channels: {
            General: { requireMention: true },
          },
        },
      },
    },
  },
}

Federated authentication(인증서와 관리 ID)

2026.4.11에 추가됨
프로덕션 배포를 위해 OpenClaw는 클라이언트 secret보다 더 안전한 대안으로 federated authentication을 지원합니다. 두 가지 방법을 사용할 수 있습니다.

옵션 A: 인증서 기반 인증

Entra ID 앱 등록에 등록된 PEM 인증서를 사용합니다. 설정:
  1. 인증서를 생성하거나 가져옵니다(비공개 키가 포함된 PEM 형식).
  2. Entra ID → App Registration → Certificates & secretsCertificates에서 공개 인증서를 업로드합니다.
구성:
{
  channels: {
    msteams: {
      enabled: true,
      appId: "<APP_ID>",
      tenantId: "<TENANT_ID>",
      authType: "federated",
      certificatePath: "/path/to/cert.pem",
      webhook: { port: 3978, path: "/api/messages" },
    },
  },
}
Env vars:
  • MSTEAMS_AUTH_TYPE=federated
  • MSTEAMS_CERTIFICATE_PATH=/path/to/cert.pem

옵션 B: Azure Managed Identity

비밀번호 없는 인증을 위해 Azure Managed Identity를 사용합니다. 관리 ID를 사용할 수 있는 Azure 인프라(AKS, App Service, Azure VMs) 배포에 적합합니다. 작동 방식:
  1. bot pod/VM에 관리 ID(system-assigned 또는 user-assigned)가 있습니다.
  2. federated identity credential이 관리 ID를 Entra ID 앱 등록에 연결합니다.
  3. 런타임에 OpenClaw는 @azure/identity를 사용해 Azure IMDS endpoint(169.254.169.254)에서 토큰을 획득합니다.
  4. 토큰은 bot 인증을 위해 Teams SDK에 전달됩니다.
필수 조건:
  • 관리 ID가 활성화된 Azure 인프라(AKS workload identity, App Service, VM)
  • Entra ID 앱 등록에 생성된 federated identity credential
  • pod/VM에서 IMDS(169.254.169.254:80)로의 네트워크 접근
구성(system-assigned managed identity):
{
  channels: {
    msteams: {
      enabled: true,
      appId: "<APP_ID>",
      tenantId: "<TENANT_ID>",
      authType: "federated",
      useManagedIdentity: true,
      webhook: { port: 3978, path: "/api/messages" },
    },
  },
}
구성(사용자 할당 관리 ID):
{
  channels: {
    msteams: {
      enabled: true,
      appId: "<APP_ID>",
      tenantId: "<TENANT_ID>",
      authType: "federated",
      useManagedIdentity: true,
      managedIdentityClientId: "<MI_CLIENT_ID>",
      webhook: { port: 3978, path: "/api/messages" },
    },
  },
}
환경 변수:
  • MSTEAMS_AUTH_TYPE=federated
  • MSTEAMS_USE_MANAGED_IDENTITY=true
  • MSTEAMS_MANAGED_IDENTITY_CLIENT_ID=<client-id> (사용자 할당의 경우에만)

AKS 워크로드 ID 설정

워크로드 ID를 사용하는 AKS 배포의 경우:
  1. AKS 클러스터에서 워크로드 ID를 활성화합니다.
  2. Entra ID 앱 등록에 페더레이션 ID 자격 증명을 만듭니다.
    az ad app federated-credential create --id <APP_OBJECT_ID> --parameters '{
      "name": "my-bot-workload-identity",
      "issuer": "<AKS_OIDC_ISSUER_URL>",
      "subject": "system:serviceaccount:<NAMESPACE>:<SERVICE_ACCOUNT>",
      "audiences": ["api://AzureADTokenExchange"]
    }'
    
  3. 앱 클라이언트 ID로 Kubernetes 서비스 계정에 주석을 추가합니다.
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: my-bot-sa
      annotations:
        azure.workload.identity/client-id: "<APP_CLIENT_ID>"
    
  4. 워크로드 ID 주입을 위해 pod에 레이블을 지정합니다.
    metadata:
      labels:
        azure.workload.identity/use: "true"
    
  5. IMDS(169.254.169.254)에 대한 네트워크 액세스를 보장합니다. NetworkPolicy를 사용하는 경우 포트 80에서 169.254.169.254/32로 향하는 트래픽을 허용하는 egress 규칙을 추가합니다.

인증 유형 비교

방식구성장점단점
클라이언트 시크릿appPassword간단한 설정시크릿 순환 필요, 보안성 낮음
인증서authType: "federated" + certificatePath네트워크를 통한 공유 시크릿 없음인증서 관리 부담
관리 IDauthType: "federated" + useManagedIdentity비밀번호 없음, 관리할 시크릿 없음Azure 인프라 필요
기본 동작: authType이 설정되지 않으면 OpenClaw는 기본적으로 클라이언트 시크릿 인증을 사용합니다. 기존 구성은 변경 없이 계속 작동합니다.

로컬 개발(터널링)

Teams는 localhost에 연결할 수 없습니다. 세션 간에도 URL이 동일하게 유지되도록 영구 개발 터널을 사용하세요.
# One-time setup:
devtunnel create my-openclaw-bot --allow-anonymous
devtunnel port create my-openclaw-bot -p 3978 --protocol auto

# Each dev session:
devtunnel host my-openclaw-bot
대안: ngrok http 3978 또는 tailscale funnel 3978(URL은 세션마다 변경될 수 있음). 터널 URL이 변경되면 엔드포인트를 업데이트하세요.
teams app update <teamsAppId> --endpoint "https://<new-url>/api/messages"

Bot 테스트

진단 실행:
teams app doctor <teamsAppId>
Bot 등록, AAD 앱, 매니페스트, SSO 구성을 한 번에 확인합니다. 테스트 메시지 보내기:
  1. Teams 앱을 설치합니다(teams app get <id> --install-link의 설치 링크 사용).
  2. Teams에서 Bot을 찾아 DM을 보냅니다.
  3. 들어오는 활동이 있는지 Gateway 로그를 확인합니다.

환경 변수

모든 구성 키는 대신 환경 변수로 설정할 수 있습니다.
  • MSTEAMS_APP_ID
  • MSTEAMS_APP_PASSWORD
  • MSTEAMS_TENANT_ID
  • MSTEAMS_AUTH_TYPE(선택 사항: "secret" 또는 "federated")
  • MSTEAMS_CERTIFICATE_PATH(페더레이션 + 인증서)
  • MSTEAMS_CERTIFICATE_THUMBPRINT(선택 사항, 인증에는 필요하지 않음)
  • MSTEAMS_USE_MANAGED_IDENTITY(페더레이션 + 관리 ID)
  • MSTEAMS_MANAGED_IDENTITY_CLIENT_ID(사용자 할당 MI 전용)

멤버 정보 작업

OpenClaw는 Microsoft Teams용 Graph 기반 member-info 작업을 제공하므로 에이전트와 자동화가 Microsoft Graph에서 직접 채널 멤버 세부 정보(표시 이름, 이메일, 역할)를 확인할 수 있습니다. 요구 사항:
  • Member.Read.Group RSC 권한(권장 매니페스트에 이미 포함됨)
  • 팀 간 조회의 경우: 관리자 동의가 있는 User.Read.All Graph Application 권한
이 작업은 channels.msteams.actions.memberInfo로 제어됩니다(기본값: Graph 자격 증명을 사용할 수 있으면 활성화).

기록 컨텍스트

  • channels.msteams.historyLimit는 최근 채널/그룹 메시지 중 몇 개를 프롬프트에 포함할지 제어합니다.
  • messages.groupChat.historyLimit로 폴백합니다. 비활성화하려면 0으로 설정합니다(기본값 50).
  • 가져온 스레드 기록은 발신자 허용 목록(allowFrom / groupAllowFrom)으로 필터링되므로, 스레드 컨텍스트 시딩에는 허용된 발신자의 메시지만 포함됩니다.
  • 인용된 첨부 파일 컨텍스트(Teams 답장 HTML에서 파생된 ReplyTo*)는 현재 수신된 그대로 전달됩니다.
  • 즉, 허용 목록은 에이전트를 트리거할 수 있는 사용자를 제어하며, 현재는 특정 보조 컨텍스트 경로만 필터링됩니다.
  • 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 CLI로 RSC 권한을 추가하려면:
teams app rsc add <teamsAppId> ChannelMessage.Read.Group --type 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 앱 ID와 일치해야 합니다.
  • webApplicationInfo.id는 Azure Bot 앱 ID와 일치해야 합니다.
  • bots[].scopes에는 사용하려는 표면(personal, team, groupChat)이 포함되어야 합니다.
  • 개인 범위에서 파일을 처리하려면 bots[].supportsFiles: true가 필요합니다.
  • 채널 트래픽을 원하면 authorization.permissions.resourceSpecific에 채널 읽기/보내기가 포함되어야 합니다.

기존 앱 업데이트

이미 설치된 Teams 앱을 업데이트하려면(예: RSC 권한 추가):
# Download, edit, and re-upload the manifest
teams app manifest download <teamsAppId> manifest.json
# Edit manifest.json locally...
teams app manifest upload manifest.json <teamsAppId>
# Version is auto-bumped if content changed
업데이트 후 새 권한이 적용되도록 각 팀에서 앱을 다시 설치하고, 캐시된 앱 메타데이터를 지우려면 Teams를 완전히 종료하고 다시 시작합니다(창만 닫는 것이 아님).

기능: RSC 전용 vs Graph

Teams RSC 전용 사용 시(앱 설치됨, Graph API 권한 없음)

작동함:
  • 채널 메시지 텍스트 콘텐츠 읽기.
  • 채널 메시지 텍스트 콘텐츠 보내기.
  • 개인(DM) 파일 첨부 파일 수신.
작동하지 않음:
  • 채널/그룹 이미지 또는 파일 콘텐츠(페이로드에는 HTML 스텁만 포함됨).
  • SharePoint/OneDrive에 저장된 첨부 파일 다운로드.
  • 메시지 기록 읽기(실시간 Webhook 이벤트 외).

Teams RSC + Microsoft Graph Application 권한 사용 시

추가됨:
  • 호스팅된 콘텐츠 다운로드(메시지에 붙여넣은 이미지).
  • SharePoint/OneDrive에 저장된 파일 첨부 파일 다운로드.
  • Graph를 통해 채널/채팅 메시지 기록 읽기.

RSC vs Graph API

기능RSC 권한Graph API
실시간 메시지예(Webhook 사용)아니요(폴링만 가능)
기록 메시지아니요예(기록 조회 가능)
설정 복잡도앱 매니페스트만관리자 동의 + 토큰 흐름 필요
오프라인 작동아니요(실행 중이어야 함)예(언제든 조회 가능)
핵심: RSC는 실시간 수신용이고, Graph API는 기록 접근용입니다. 오프라인 중 놓친 메시지를 따라잡으려면 ChannelMessage.Read.All이 포함된 Graph API가 필요합니다(관리자 동의 필요).

Graph 지원 미디어 + 기록(채널에 필요)

채널에서 이미지/파일이 필요하거나 메시지 기록을 가져오려면 Microsoft Graph 권한을 활성화하고 관리자 동의를 부여해야 합니다.
  1. Entra ID(Azure AD) 앱 등록에서 Microsoft Graph Application 권한을 추가합니다.
    • ChannelMessage.Read.All(채널 첨부 파일 + 기록)
    • Chat.Read.All 또는 ChatMessage.Read.All(그룹 채팅)
  2. 테넌트에 대해 관리자 동의 부여를 수행합니다.
  3. Teams 앱 매니페스트 버전을 올리고 다시 업로드한 다음, Teams에서 앱을 다시 설치합니다.
  4. 캐시된 앱 메타데이터를 지우려면 Teams를 완전히 종료하고 다시 시작합니다.
사용자 멘션을 위한 추가 권한: 사용자 @멘션은 대화에 있는 사용자에 대해 기본적으로 작동합니다. 그러나 현재 대화에 없는 사용자를 동적으로 검색하고 멘션하려면 User.Read.All(Application) 권한을 추가하고 관리자 동의를 부여하세요.

알려진 제한 사항

Webhook 시간 초과

Teams는 HTTP Webhook을 통해 메시지를 전달합니다. 처리가 너무 오래 걸리면(예: 느린 LLM 응답) 다음이 발생할 수 있습니다.
  • Gateway 시간 초과
  • Teams가 메시지를 재시도함(중복 발생)
  • 답장이 삭제됨
OpenClaw는 빠르게 반환하고 선제적으로 응답을 보내는 방식으로 이를 처리하지만, 매우 느린 응답은 여전히 문제를 일으킬 수 있습니다.

형식 지정

Teams markdown은 Slack이나 Discord보다 더 제한적입니다.
  • 기본 형식은 동작합니다: 굵게, 기울임, code, 링크
  • 복잡한 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 개체 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: 채널/그룹에서 @mention을 요구합니다(기본값 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 키는 명시적 접두사를 사용해야 합니다: channel:, id:, e164:, username:, name:(기존 접두사 없는 키는 여전히 id:에만 매핑됨).
  • channels.msteams.actions.memberInfo: Graph 기반 멤버 정보 작업을 활성화하거나 비활성화합니다(기본값: Graph 자격 증명을 사용할 수 있으면 활성화).
  • channels.msteams.authType: 인증 유형 - "secret"(기본값) 또는 "federated"입니다.
  • channels.msteams.certificatePath: PEM 인증서 파일 경로입니다(federated + 인증서 인증).
  • channels.msteams.certificateThumbprint: 인증서 지문입니다(선택 사항, 인증에 필요하지 않음).
  • channels.msteams.useManagedIdentity: 관리 ID 인증을 활성화합니다(federated 모드).
  • channels.msteams.managedIdentityClientId: 사용자 할당 관리 ID의 클라이언트 ID입니다.
  • channels.msteams.sharePointSiteId: 그룹 채팅/채널에서 파일 업로드에 사용할 SharePoint 사이트 ID입니다(그룹 채팅에서 파일 보내기 참조).

라우팅 및 세션

  • 세션 키는 표준 에이전트 형식을 따릅니다(/concepts/session 참조):
    • 다이렉트 메시지는 기본 세션을 공유합니다(agent:<agentId>:<mainKey>).
    • 채널/그룹 메시지는 대화 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",
            },
          },
        },
      },
    },
  },
}

해석 우선순위

봇이 채널로 응답을 보낼 때 replyStyle은 가장 구체적인 재정의부터 기본값까지 순서대로 해석됩니다. 첫 번째 non-undefined 값이 사용됩니다.
  1. 채널별channels.msteams.teams.<teamId>.channels.<conversationId>.replyStyle
  2. 팀별channels.msteams.teams.<teamId>.replyStyle
  3. 전역channels.msteams.replyStyle
  4. 암시적 기본값requireMention에서 파생:
    • requireMention: truethread
    • requireMention: falsetop-level
명시적 replyStyle 없이 전역에서 requireMention: false를 설정하면, 인바운드가 스레드 응답이었더라도 Posts 스타일 채널의 멘션이 최상위 게시물로 표시됩니다. 예상치 못한 동작을 피하려면 전역, 팀 또는 채널 수준에서 replyStyle: "thread"를 고정하세요.

스레드 컨텍스트 보존

replyStyle: "thread"가 적용 중이고 봇이 채널 스레드 안에서 @mentioned된 경우, OpenClaw는 원본 스레드 루트를 아웃바운드 대화 참조(19:…@thread.tacv2;messageid=<root>)에 다시 첨부하여 응답이 같은 스레드 안에 도착하도록 합니다. 이는 실시간(턴 내부) 전송과 Bot Framework 턴 컨텍스트가 만료된 후 이루어지는 선제적 전송(예: 장기 실행 에이전트, mcp__openclaw__message를 통한 큐잉된 도구 호출 응답) 모두에 적용됩니다. 스레드 루트는 대화 참조에 저장된 threadId에서 가져옵니다. threadId 이전의 오래된 저장 참조는 activityId(대화의 시드가 된 마지막 인바운드 활동)로 폴백하므로, 기존 배포는 다시 시드하지 않아도 계속 동작합니다. replyStyle: "top-level"이 적용 중이면 채널 스레드 인바운드는 의도적으로 새 최상위 게시물로 답변됩니다. 스레드 접미사는 첨부되지 않습니다. 이는 Threads 스타일 채널에 올바른 동작입니다. 스레드 응답을 기대했는데 최상위 게시물이 보인다면 해당 채널의 replyStyle 설정이 잘못된 것입니다.

첨부 파일 및 이미지

현재 제한 사항:
  • 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 호스트). 이 목록은 엄격하게 유지하세요(멀티 테넌트 접미사는 피하세요).

그룹 채팅에서 파일 보내기

봇은 FileConsentCard 흐름(기본 제공)을 사용해 DM에서 파일을 보낼 수 있습니다. 그러나 그룹 채팅/채널에서 파일 보내기에는 추가 설정이 필요합니다.
컨텍스트파일 전송 방식필요한 설정
DMFileConsentCard → 사용자가 수락 → 봇 업로드기본으로 동작
그룹 채팅/채널SharePoint에 업로드 → 링크 공유sharePointSiteId + Graph 권한 필요
이미지(모든 컨텍스트)Base64 인코딩 인라인기본으로 동작

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

봇에는 개인 OneDrive 드라이브가 없습니다(/me/drive Graph API 엔드포인트는 애플리케이션 ID에서 동작하지 않음). 그룹 채팅/채널에서 파일을 보내려면 봇이 SharePoint 사이트에 업로드하고 공유 링크를 생성합니다.

설정

  1. Entra ID(Azure AD) → App Registration에서 Graph API 권한을 추가합니다:
    • Sites.ReadWrite.All(Application) - SharePoint에 파일 업로드
    • Chat.Read.All(Application) - 선택 사항, 사용자별 공유 링크 활성화
  2. 테넌트에 대해 관리자 동의를 부여합니다.
  3. SharePoint 사이트 ID를 가져옵니다:
    # 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/ 폴더에 저장됩니다.

설문(Adaptive Cards)

OpenClaw는 Teams 설문을 Adaptive Cards로 보냅니다(네이티브 Teams 설문 API는 없음).
  • CLI: openclaw message poll --channel msteams --target conversation:<id> ...
  • 투표는 Gateway가 ~/.openclaw/msteams-polls.json에 기록합니다.
  • 투표를 기록하려면 Gateway가 계속 온라인 상태여야 합니다.
  • Poll은 아직 결과 요약을 자동으로 게시하지 않습니다(필요한 경우 저장소 파일을 확인하세요).

프레젠테이션 카드

message 도구 또는 CLI를 사용하여 Teams 사용자나 대화에 의미적 프레젠테이션 페이로드를 보냅니다. OpenClaw는 일반 프레젠테이션 계약에 따라 이를 Teams Adaptive Cards로 렌더링합니다. presentation 매개변수는 의미적 블록을 허용합니다. presentation이 제공되면 메시지 텍스트는 선택 사항입니다. Agent 도구:
{
  action: "send",
  channel: "msteams",
  target: "user:<id>",
  presentation: {
    title: "Hello",
    blocks: [{ type: "text", text: "Hello!" }],
  },
}
CLI:
openclaw message send --channel msteams \
  --target "conversation:19:abc...@thread.tacv2" \
  --presentation '{"title":"Hello","blocks":[{"type":"text","text":"Hello!"}]}'
대상 형식에 대한 자세한 내용은 아래 대상 형식을 참고하세요.

대상 형식

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:abc123...@thread.tacv2
그룹/채널(원시)<conversation-id>19:abc123...@thread.tacv2 (@thread가 포함된 경우)
CLI 예시:
# 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 a presentation card to a conversation
openclaw message send --channel msteams --target "conversation:19:abc...@thread.tacv2" \
  --presentation '{"title":"Hello","blocks":[{"type":"text","text":"Hello"}]}'
Agent 도구 예시:
{
  action: "send",
  channel: "msteams",
  target: "user:John Smith",
  message: "Hello!",
}
{
  action: "send",
  channel: "msteams",
  target: "conversation:19:abc...@thread.tacv2",
  presentation: {
    title: "Hello",
    blocks: [{ type: "text", 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=...
                                    └────────────────────────────┘
                                    Team conversation ID (URL-decode this)
채널 URL:
https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=...
                                      └─────────────────────────┘
                                      Channel ID (URL-decode this)
구성의 경우:
  • 팀 키 = /team/ 뒤의 경로 세그먼트(URL 디코딩됨, 예: 19:Bk4j...@thread.tacv2; 이전 테넌트는 @thread.skype를 표시할 수 있으며, 이 역시 유효함)
  • 채널 키 = /channel/ 뒤의 경로 세그먼트(URL 디코딩됨)
  • OpenClaw 라우팅에는 groupId 쿼리 매개변수를 무시하세요. 이는 Microsoft Entra 그룹 ID이며, 들어오는 Teams 활동에서 사용하는 Bot Framework 대화 ID가 아닙니다.

비공개 채널

Bot은 비공개 채널에서 제한적으로 지원됩니다.
기능표준 채널비공개 채널
Bot 설치제한적
실시간 메시지(Webhook)작동하지 않을 수 있음
RSC 권한다르게 동작할 수 있음
@멘션Bot에 접근할 수 있는 경우
Graph API 기록예(권한 필요)
비공개 채널이 작동하지 않는 경우의 우회 방법:
  1. Bot 상호작용에는 표준 채널을 사용하세요
  2. DM을 사용하세요. 사용자는 언제든지 Bot에 직접 메시지를 보낼 수 있습니다
  3. 기록 접근에는 Graph API를 사용하세요(ChannelMessage.Read.All 필요)

문제 해결

일반적인 문제

  • 채널에 이미지가 표시되지 않음: Graph 권한 또는 관리자 동의가 없습니다. Teams 앱을 다시 설치하고 Teams를 완전히 종료한 뒤 다시 여세요.
  • 채널에서 응답 없음: 기본적으로 멘션이 필요합니다. channels.msteams.requireMention=false를 설정하거나 팀/채널별로 구성하세요.
  • 버전 불일치(Teams가 여전히 이전 매니페스트를 표시함): 앱을 제거한 뒤 다시 추가하고 Teams를 완전히 종료하여 새로 고치세요.
  • Webhook에서 401 Unauthorized: Azure JWT 없이 수동 테스트할 때 예상되는 동작입니다. 엔드포인트에 도달할 수 있지만 인증에 실패했다는 뜻입니다. 올바르게 테스트하려면 Azure Web Chat을 사용하세요.

매니페스트 업로드 오류

  • “Icon file cannot be empty”: 매니페스트가 0바이트인 아이콘 파일을 참조합니다. 유효한 PNG 아이콘을 만드세요(outline.png는 32x32, color.png는 192x192).
  • “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 권한이 작동하지 않음

  1. webApplicationInfo.id가 Bot의 App ID와 정확히 일치하는지 확인하세요
  2. 앱을 다시 업로드하고 팀/채팅에 다시 설치하세요
  3. 조직 관리자가 RSC 권한을 차단했는지 확인하세요
  4. 올바른 범위를 사용 중인지 확인하세요. 팀에는 ChannelMessage.Read.Group, 그룹 채팅에는 ChatMessage.Read.Chat

참고 자료

관련 항목