---
read_when:
    - Microsoft Teams 채널 기능 작업 중
summary: Microsoft Teams 봇 지원 상태, 기능 및 구성
title: Microsoft Teams
x-i18n:
    generated_at: "2026-05-11T20:21:35Z"
    model: gpt-5.5
    provider: openai
    source_hash: d7bf8cd0ae6c6053f51794e6bc03bb6d927d640256272f3afb04f3b0ec99eb43
    source_path: channels/msteams.md
    workflow: 16
---

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

## 번들 Plugin

Microsoft Teams는 현재 OpenClaw 릴리스에서 번들 Plugin으로 제공되므로, 일반 패키지 빌드에서는 별도 설치가 필요하지 않습니다.

이전 빌드를 사용 중이거나 번들 Teams를 제외한 사용자 지정 설치를 사용 중이라면 npm 패키지를 직접 설치하세요.

```bash
openclaw plugins install @openclaw/msteams
```

현재 공식 릴리스 태그를 따르려면 bare 패키지를 사용하세요. 재현 가능한 설치가 필요할 때만 정확한 버전을 고정하세요.

로컬 체크아웃(git repo에서 실행하는 경우):

```bash
openclaw plugins install ./path/to/local/msteams-plugin
```

자세한 내용: [Plugins](/ko/tools/plugin)

## 빠른 설정

[`@microsoft/teams.cli`](https://www.npmjs.com/package/@microsoft/teams.cli)는 단일 명령으로 bot 등록, manifest 생성, 자격 증명 생성을 처리합니다.

**1. 설치 및 로그인**

```bash
npm install -g @microsoft/teams.cli@preview
teams login
teams status   # verify you're logged in and see your tenant info
```

<Note>
Teams CLI는 현재 preview 상태입니다. 명령과 플래그는 릴리스 간에 변경될 수 있습니다.
</Note>

**2. 터널 시작**(Teams는 localhost에 접근할 수 없습니다)

아직 설치하지 않았다면 devtunnel CLI를 설치하고 인증하세요([시작 가이드](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started)).

```bash
# 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
```

<Note>
Teams는 devtunnels로 인증할 수 없으므로 `--allow-anonymous`가 필요합니다. 들어오는 각 bot 요청은 여전히 Teams SDK가 자동으로 검증합니다.
</Note>

대안: `ngrok http 3978` 또는 `tailscale funnel 3978`(하지만 세션마다 URL이 변경될 수 있음).

**3. 앱 생성**

```bash
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 구성** 출력의 자격 증명을 사용하세요.

```json5
{
  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"를 선택하세요. 건너뛰었다면 나중에 링크를 가져올 수 있습니다.

```bash
teams app get <teamsAppId> --install-link
```

**6. 모든 것이 작동하는지 확인**

```bash
teams app doctor <teamsAppId>
```

이 명령은 bot 등록, AAD 앱 구성, manifest 유효성, SSO 설정 전반의 진단을 실행합니다.

프로덕션 배포에서는 클라이언트 secret 대신 [federated authentication](/ko/channels/msteams#federated-authentication-certificate-plus-managed-identity)(인증서 또는 관리 ID) 사용을 고려하세요.

<Note>
그룹 채팅은 기본적으로 차단됩니다(`channels.msteams.groupPolicy: "allowlist"`). 그룹 답장을 허용하려면 `channels.msteams.groupAllowFrom`을 설정하거나, `groupPolicy: "open"`을 사용해 모든 구성원(멘션 게이트 적용)을 허용하세요.
</Note>

## 목표

- Teams DM, 그룹 채팅 또는 채널을 통해 OpenClaw와 대화합니다.
- 라우팅을 결정적으로 유지합니다. 답장은 항상 도착한 채널로 돌아갑니다.
- 안전한 채널 동작을 기본값으로 사용합니다(별도로 구성하지 않는 한 멘션 필요).

## 구성 쓰기

기본적으로 Microsoft Teams는 `/config set|unset`으로 트리거되는 구성 업데이트를 쓸 수 있습니다(`commands.config: true` 필요).

비활성화하려면:

```json5
{
  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"`를 설정하세요.

예:

```json5
{
  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`가 활성화되어 있지 않으면 기본적으로 라우팅에서 무시됩니다.

예:

```json5
{
  channels: {
    msteams: {
      groupPolicy: "allowlist",
      teams: {
        "My Team": {
          channels: {
            General: { requireMention: true },
          },
        },
      },
    },
  },
}
```

<details>
<summary><strong>수동 설정(Teams CLI 없이)</strong></summary>

Teams CLI를 사용할 수 없다면 Azure Portal을 통해 bot을 수동으로 설정할 수 있습니다.

### 작동 방식

1. Microsoft Teams Plugin이 사용 가능한지 확인합니다(현재 릴리스에 번들로 포함).
2. **Azure Bot**(App ID + secret + tenant ID)을 생성합니다.
3. bot을 참조하고 아래 RSC 권한을 포함하는 **Teams 앱 패키지**를 빌드합니다.
4. Teams 앱을 팀(또는 DM용 개인 범위)에 업로드/설치합니다.
5. `~/.openclaw/openclaw.json`(또는 env vars)에서 `msteams`를 구성하고 Gateway를 시작합니다.
6. Gateway는 기본적으로 `/api/messages`에서 Bot Framework Webhook 트래픽을 수신합니다.

### 1단계: Azure Bot 생성

1. [Azure Bot 생성](https://portal.azure.com/#create/Microsoft.AzureBot)으로 이동합니다.
2. **Basics** 탭을 채웁니다.

   | 필드               | 값                                                       |
   | ------------------ | -------------------------------------------------------- |
   | **Bot handle**     | bot 이름, 예: `openclaw-msteams`(고유해야 함)            |
   | **Subscription**   | Azure 구독 선택                                          |
   | **Resource group** | 새로 생성하거나 기존 항목 사용                           |
   | **Pricing tier**   | dev/testing용 **Free**                                   |
   | **Type of App**    | **Single Tenant**(권장 - 아래 참고 참조)                 |
   | **Creation type**  | **Create new Microsoft App ID**                          |

<Warning>
새 multi-tenant bot 생성은 2025-07-31 이후 deprecated되었습니다. 새 bot에는 **Single Tenant**를 사용하세요.
</Warning>

3. **Review + create** → **Create**를 클릭합니다(~1-2분 대기).

### 2단계: 자격 증명 가져오기

1. Azure Bot 리소스 → **Configuration**으로 이동합니다.
2. **Microsoft App ID**를 복사합니다. 이것이 `appId`입니다.
3. **Manage Password**를 클릭합니다 → App Registration으로 이동합니다.
4. **Certificates & secrets** → **New client secret** 아래에서 **Value**를 복사합니다. 이것이 `appPassword`입니다.
5. **Overview**로 이동해 **Directory (tenant) ID**를 복사합니다. 이것이 `tenantId`입니다.

### 3단계: Messaging Endpoint 구성

1. Azure Bot → **Configuration**에서
2. **Messaging endpoint**를 Webhook URL로 설정합니다.
   - 프로덕션: `https://your-domain.com/api/messages`
   - 로컬 dev: 터널 사용(아래 [로컬 개발](#local-development-tunneling) 참조)

### 4단계: Teams 채널 활성화

1. Azure Bot → **Channels**에서
2. **Microsoft Teams** → Configure → Save를 클릭합니다.
3. 서비스 약관에 동의합니다.

### 5단계: Teams 앱 Manifest 빌드

- `botId = <App ID>`인 `bot` 항목을 포함합니다.
- 범위: `personal`, `team`, `groupChat`.
- `supportsFiles: true`(personal 범위 파일 처리에 필요).
- RSC 권한을 추가합니다([RSC 권한](#current-teams-rsc-permissions-manifest) 참조).
- 아이콘 생성: `outline.png`(32x32) 및 `color.png`(192x192).
- 세 파일 `manifest.json`, `outline.png`, `color.png`를 함께 zip으로 압축합니다.

### 6단계: OpenClaw 구성

```json5
{
  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`.

### 7단계: Gateway 실행

Teams 채널은 Plugin이 사용 가능하고 자격 증명이 포함된 `msteams` 구성이 있으면 자동으로 시작됩니다.

</details>

## Federated authentication(인증서와 관리 ID)

> 2026.4.11에 추가됨

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

### 옵션 A: 인증서 기반 인증

Entra ID 앱 등록에 등록된 PEM 인증서를 사용합니다.

**설정:**

1. 인증서를 생성하거나 가져옵니다(비공개 키가 포함된 PEM 형식).
2. Entra ID → App Registration → **Certificates & secrets** → **Certificates**에서 공개 인증서를 업로드합니다.

**구성:**

```json5
{
  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):**

```json5
{
  channels: {
    msteams: {
      enabled: true,
      appId: "<APP_ID>",
      tenantId: "<TENANT_ID>",
      authType: "federated",
      useManagedIdentity: true,
      webhook: { port: 3978, path: "/api/messages" },
    },
  },
}
```

**구성(사용자 할당 관리 ID):**

```json5
{
  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 자격 증명**을 만듭니다.

   ```bash
   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 서비스 계정에 주석을 추가**합니다.

   ```yaml
   apiVersion: v1
   kind: ServiceAccount
   metadata:
     name: my-bot-sa
     annotations:
       azure.workload.identity/client-id: "<APP_CLIENT_ID>"
   ```

4. 워크로드 ID 주입을 위해 **pod에 레이블을 지정**합니다.

   ```yaml
   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`    | 네트워크를 통한 공유 시크릿 없음    | 인증서 관리 부담                          |
| **관리 ID**          | `authType: "federated"` + `useManagedIdentity` | 비밀번호 없음, 관리할 시크릿 없음   | Azure 인프라 필요                         |

**기본 동작:** `authType`이 설정되지 않으면 OpenClaw는 기본적으로 클라이언트 시크릿 인증을 사용합니다. 기존 구성은 변경 없이 계속 작동합니다.

## 로컬 개발(터널링)

Teams는 `localhost`에 연결할 수 없습니다. 세션 간에도 URL이 동일하게 유지되도록 영구 개발 터널을 사용하세요.

```bash
# 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이 변경되면 엔드포인트를 업데이트하세요.

```bash
teams app update <teamsAppId> --endpoint "https://<new-url>/api/messages"
```

## Bot 테스트

**진단 실행:**

```bash
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 권한을 추가하려면:

```bash
teams app rsc add <teamsAppId> ChannelMessage.Read.Group --type Application
```

## Teams 매니페스트 예시(수정됨)

필수 필드가 포함된 최소한의 유효한 예시입니다. ID와 URL을 교체하세요.

```json5
{
  $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 권한 추가):

```bash
# 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를 **완전히 종료하고 다시 시작**합니다(창만 닫는 것이 아님).

<details>
<summary>수동 매니페스트 업데이트(CLI 없이)</summary>

1. 새 설정으로 `manifest.json`을 업데이트합니다.
2. **`version` 필드를 증가**시킵니다(예: `1.0.0` → `1.1.0`).
3. 아이콘(`manifest.json`, `outline.png`, `color.png`)과 함께 매니페스트를 **다시 압축**합니다.
4. 새 zip을 업로드합니다.
   - **Teams Admin Center:** Teams 앱 → 앱 관리 → 앱 찾기 → 새 버전 업로드
   - **사이드로드:** Teams에서 → 앱 → 앱 관리 → 사용자 지정 앱 업로드

</details>

## 기능: 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`([응답 스타일](#reply-style-threads-vs-posts) 참조).
- `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입니다([그룹 채팅에서 파일 보내기](#sending-files-in-group-chats) 참조).

## 라우팅 및 세션

- 세션 키는 표준 에이전트 형식을 따릅니다([/concepts/session](/ko/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`을 구성합니다.

```json5
{
  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: true` → `thread`
   - `requireMention: false` → `top-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에서 파일을 보낼 수 있습니다. 그러나 **그룹 채팅/채널에서 파일 보내기**에는 추가 설정이 필요합니다.

| 컨텍스트                 | 파일 전송 방식                                | 필요한 설정                                     |
| ------------------------ | -------------------------------------------- | ----------------------------------------------- |
| **DM**                   | FileConsentCard → 사용자가 수락 → 봇 업로드   | 기본으로 동작                                   |
| **그룹 채팅/채널**       | 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를 가져옵니다:**

   ```bash
   # 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를 구성합니다:**

   ```json5
   {
     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 도구:**

```json5
{
  action: "send",
  channel: "msteams",
  target: "user:<id>",
  presentation: {
    title: "Hello",
    blocks: [{ type: "text", text: "Hello!" }],
  },
}
```

**CLI:**

```bash
openclaw message send --channel msteams \
  --target "conversation:19:abc...@thread.tacv2" \
  --presentation '{"title":"Hello","blocks":[{"type":"text","text":"Hello!"}]}'
```

대상 형식에 대한 자세한 내용은 아래 [대상 형식](#target-formats)을 참고하세요.

## 대상 형식

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 예시:**

```bash
# 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 도구 예시:**

```json5
{
  action: "send",
  channel: "msteams",
  target: "user:John Smith",
  message: "Hello!",
}
```

```json5
{
  action: "send",
  channel: "msteams",
  target: "conversation:19:abc...@thread.tacv2",
  presentation: {
    title: "Hello",
    blocks: [{ type: "text", text: "Hello" }],
  },
}
```

<Note>
`user:` 접두사가 없으면 이름은 기본적으로 그룹 또는 팀 해석에 사용됩니다. 표시 이름으로 사람을 대상으로 지정할 때는 항상 `user:`를 사용하세요.
</Note>

## 사전 메시징

- 사전 메시지는 사용자가 상호작용한 **후에만** 가능합니다. 그 시점에 대화 참조를 저장하기 때문입니다.
- `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](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`

## 참고 자료

- [Azure Bot 만들기](https://learn.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) - Azure Bot 설정 가이드
- [Teams Developer Portal](https://dev.teams.microsoft.com/apps) - Teams 앱 생성/관리
- [Teams 앱 매니페스트 스키마](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema)
- [RSC로 채널 메시지 수신](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/channel-messages-with-rsc)
- [RSC 권한 참조](https://learn.microsoft.com/en-us/microsoftteams/platform/graph-api/rsc/resource-specific-consent)
- [Teams Bot 파일 처리](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/bots-filesv4) (채널/그룹에는 Graph 필요)
- [사전 메시징](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/send-proactive-messages)
- [@microsoft/teams.cli](https://www.npmjs.com/package/@microsoft/teams.cli) - Bot 관리를 위한 Teams CLI

## 관련 항목

- [채널 개요](/ko/channels) - 지원되는 모든 채널
- [페어링](/ko/channels/pairing) - DM 인증 및 페어링 흐름
- [그룹](/ko/channels/groups) - 그룹 채팅 동작 및 멘션 게이팅
- [채널 라우팅](/ko/channels/channel-routing) - 메시지의 세션 라우팅
- [보안](/ko/gateway/security) - 접근 모델 및 강화
