VS Code の Teams Toolkit でボットを F5 ローカルデバッグすると、Deep Link の生成が「Invalid app manifest: deep link format incorrect」で落ちたり、添付ファイル取得が「Attachment download failed: 404 Resource not found」になる――この症状は“Toolkit のバグ”ではなく、ほぼ必ずマニフェストとローカル環境のずれが原因です。本稿では、再現条件から直し方、検証のコツ、運用時の落とし穴までをまとめて一気に解決します。
結論(先に要点)
次の 3 点を正しくそろえると、Deep Link 生成と添付ファイルの 404 は解消します。
- manifest.json の validDomains にローカル公開ドメイン(ngrok / dev tunnels)を追加
- webApplicationInfo(SSO 利用時)の
idとresourceをローカル URI と整合させる - bots[0].botId を Azure AD アプリ(Microsoft App ID)と一致させる(注:manifest の
idは Teams アプリ ID であり、AAD App ID と一致させる必要はありません)
修正後は アプリパッケージの再生成 → 再 sideload → Teams クライアント再読み込み を確実に行い、Deep Link と添付の双方をブラウザ&API の二重で検証します。
前提条件と症状
| 項目 | 内容 |
|---|---|
| 環境 | Windows 11 / VS Code / Teams Toolkit v5.2.0 / Node.js SDK(Bot Framework)v4.22 |
| 許可 | Azure AD で Chat.ReadWrite / Files.ReadWrite.All を付与(管理者同意済みが望ましい) |
| 公開 | ngrok で localhost を公開(または dev tunnels)。teamsApp.zip は sideload 済み |
| 症状① | Deep Link 生成時:Invalid app manifest: deep link format incorrect |
| 症状② | 添付ファイル取得時:Attachment download failed: 404 Resource not found |
なぜ起きるのか(技術的背景)
Teams クライアントは、アプリの manifest.json に記述された情報をもとに動作範囲を厳格に制限しています。特に次の 3 つがローカルデバッグでは落とし穴になります。
- validDomains:アプリが表示・呼び出し・遷移で利用するホストを列挙します。ローカル公開ドメイン(例:
*.ngrok-free.app/*.devtunnels.ms)を入れないと、Teams は “未知のドメインへ遷移しようとしている” と判断し、Deep Link の検証も含めて失敗しやすくなります。 - webApplicationInfo:SSO を使う Bot/Tab では Azure AD 側のアプリ登録と Application ID URI(Resource) が一致している必要があります。ローカル用 URI(
api://{ローカル公開ドメイン}/{AAD App ID})と manifest のresourceがズレると、トークン検証が通らず副作用で Deep Link の生成・遷移が弾かれることがあります。 - botId:
bots[0].botIdは Microsoft App ID(Azure AD アプリのクライアント ID) です。ここが違うと Bot Framework 側の認証・サービス URL 解決が破綻し、添付のダウンロード API が 404 を返しやすくなります。
注意: manifest の id は Teams アプリ ID であり、Azure AD の App ID と同一である必要はありません。混同して id を差し替えると、既に sideload 済みのアプリとは別物扱いになり、検証がさらに複雑化します。
まずはここを直す(最低限の修正例)
manifest.json のテンプレート
次の差分を参考に、ローカル公開ドメインと Azure AD の情報を正しく反映します。
{
"$schema": "https://developer.microsoft.com/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
"manifestVersion": "1.16",
"version": "1.0.0",
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", // <= Teams アプリ ID(変更しない)
"packageName": "com.example.bot",
"developer": { "name": "Your Org", "websiteUrl": "https://example.com" },
"name": { "short": "My Bot (Local)", "full": "My Bot (Local debug)" },
"description": { "short": "Local debug", "full": "Local debug app" },
"icons": { "color": "color.png", "outline": "outline.png" },
"validDomains": [
"localhost",
"abc-123-45-67-89.ngrok-free.app",
"*.ngrok-free.app",
"xyz-0000-00-00-00.devtunnels.ms",
"*.devtunnels.ms"
],
"bots": [
{
"botId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", // <= Azure AD の App (client) ID
"scopes": [ "personal", "team", "groupchat" ],
"supportsFiles": true
}
],
"permissions": [ "identity", "messageTeamMembers" ],
"webApplicationInfo": {
"id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", // <= Azure AD の App (client) ID
"resource": "api://abc-123-45-67-89.ngrok-free.app/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}
}
ポイントは以下の 3 つです。
- validDomains には具体のサブドメイン(例:
abc-123-...ngrok-free.app)とワイルドカードの両方を入れておくと、ドメインが再発行されても影響を最小化できます。 - botId / webApplicationInfo.id は Azure AD の Application (client) ID と一致させます。
- webApplicationInfo.resource は「
api://{公開ドメイン}/{AAD App ID}」形式に合わせ、Azure AD 側の「アプリ ID URI」も同一に設定します。
手戻りしないためのチェックリスト
| 要修正箇所 | 目的 | OK の例 | NG の例 |
|---|---|---|---|
| validDomains | ローカル公開ドメインを許可 | abc-...ngrok-free.app / *.ngrok-free.app / *.devtunnels.ms | 空・localhost のみ・古い ngrok ドメイン |
| bots[0].botId | Bot の認証 | Azure AD App(client)ID と一致 | 別テナントの ID / 古い ID |
| webApplicationInfo.id | SSO 対象アプリの指定 | Azure AD App(client)ID と一致 | アプリケーション(object)ID を誤設定 |
| webApplicationInfo.resource | アプリ ID URI の整合 | api://{公開ドメイン}/{clientID} | api://localhost/... のまま / 別ドメイン |
| API パーミッション | トークンの不足解消 | Chat.ReadWrite / Files.ReadWrite.All に管理者同意 | 許可追加後に同意未実施 |
| アプリパッケージ | 最新 manifest の反映 | 再生成 → 再 sideload | 古い teamsApp.zip を使い回し |
再パッケージと再 sideload の確実な手順
- ngrok(または dev tunnels)で公開し、発行された 完全なホスト名 を控える。
appPackage/manifest.json(テンプレートではmanifest.template.jsonの場合あり)を編集し、上記の通り validDomains / webApplicationInfo を更新。- Teams Toolkit のタスク(VS Code 左の Teams Toolkit ビュー → Lifecycle → Update App Package など)で
teamsApp.zipを再生成。 - Teams の「アプリをアップロード」から 既存アプリを更新 として新しい
teamsApp.zipを sideload。 - Teams クライアントを 完全終了→再起動。キャッシュが強い場合は後述の手順でクリア。
Deep Link の検証方法(失敗の切り分け)
1) URL の最低限の形を確認
チャットやメッセージへの Deep Link は、概ね次の形を取ります(例)。
https://teams.microsoft.com/l/message/<conversationId>/<messageId>?tenantId=<tenantId>[...]
- 自動生成するコードの前に、まず Teams クライアント上で「リンクをコピー」した URL をブラウザに貼り、Teams クライアントに正しくリダイレクトされるかを確認します(ここで駄目なら manifest/環境の問題が濃厚)。
- ローカルの ngrok ドメインに遷移する UI(タブ遷移や OAuth リダイレクトなど)が入るフローでは validDomains の不足が一次原因になります。
2) Teams 側の検証に通っているか
Deep Link 遷移時に Teams クライアント(デスクトップなら DevTools、ブラウザなら開発者ツール)でネットワークログを確認し、未知ドメイン拒否や manifest 検証エラーが出ていないかを確認します。
3) manifest の再適用を疑う
manifest を直しても古い teamsApp.zip が残っていると、改善しません。必ず 再 sideload と クライアント再起動 を行います。
添付ファイル 404 のよくある原因と対処
Teams のボット添付は Bot Framework Attachments API で配信されます。404 の多くは次の 3 つです。
- serviceUrl の不整合:着信アクティビティの
serviceUrlに基づいて API を呼ぶ必要があります。キャッシュした古い URL を使うと 404。 - 認可ヘッダーの欠落:
Authorization: Bearer <bot 用トークン>を付けないと 401/404 になりがち。 - botId の不一致:manifest の
bots[0].botIdと Azure AD のアプリ ID が違うと、トークンを取れてもダウンロードが失敗します。
Node.js(Bot Framework v4 系)の最小コード例
受信アクティビティの serviceUrl を使って Attachments API を叩きます。
import { ConnectorClient } from "botframework-connector";
// CloudAdapter 経由で context を取得している前提
async function downloadAttachment(context, attachmentId) {
// serviceUrl は毎回、受信アクティビティから取り出す
const serviceUrl = context.activity.serviceUrl;
// Adapter から ConnectorClient を取得(v4.22 以降)
const connector = context.adapter.createConnectorClient(serviceUrl);
// 1) メタデータ
const meta = await connector.attachments.getAttachment(attachmentId);
// 2) 本体(ストリーム)
const stream = await connector.attachments.getAttachmentStream(attachmentId);
return { meta, stream };
}
curl での動作確認(原因切り分け用)
- 受信アクティビティの
serviceUrl(例:https://smba.trafficmanager.net/...)を控える。 - Bot 用のクライアント資格情報でトークンを取得し、
/v3/attachments/{id}/views/originalへAuthorization: Bearerを付けて GET。200 が返れば認可・ルーティングは正しいです。
コード側のチェックポイント
- 添付生成時の
contentType/name/contentUrlが適切か(contentUrlに自己署名 https を使うとクライアント拒否されることがある)。 - 画像などを
AttachmentLayoutTypesでカルーセル表示する際、contentUrl先が validDomains に含まれているか。
まだ直らない時の追加対策
- Teams Toolkit のバージョン見直し:5.2.0 → 5.1.x など安定版で再検証。逆に 5.2.0 へ統一して設定差分を埋めるのも手です。
- dev tunnels への切替:ngrok の再発行が多い場合は dev tunnels を試し、validDomains を
*.devtunnels.msベースへ更新。 - Teams クライアントのキャッシュクリア:
- Classic(旧クライアント):
%AppData%\Microsoft\Teams配下の Cache / GPUCache を削除 → 再起動 - New Teams(新クライアント):
%LocalAppData%\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeamsを中心にキャッシュクリア → 再起動
- Classic(旧クライアント):
- Azure AD の管理者同意を再実行:ポリシー変更後は必ず再同意。テナント境界を跨いでいないかも確認。
- 権限の最小化検証:一度 Chat.ReadWrite のみに戻し、Deep Link 生成だけを検証 → 改善したら段階的に Files.* を追加。
実案件で役立つ“運用の型”
ローカル・開発・本番の 3 環境を明示的に分離
manifest を 3 つ用意し、ドメイン / webApplicationInfo.resource / botId を環境変数で埋め替えます。Teams Toolkit の env 機能を使い、パッケージ名と name.short に環境ラベル(例:(Local) / (Dev) / (Prod))を付与すると、sideload 先で混同しません。
ドメインの更新手順を標準化
- ngrok/dev tunnels を立てる → ドメイン取得
- manifest の置換(スクリプト化推奨)
- パッケージ再生成 → 再 sideload
- クライアント再起動 → 動作確認
ログの粒度を上げる
- Deep Link 生成箇所で 最終的に組み上がった URL を INFO ログ出力
- 添付 404 は serviceUrl / attachmentId / 会話 ID をセットで WARN ログ
トラブルを再現できる最小サンプル(考え方)
原因切り分けに、次の順で最小化してください。
- Bot:
messageReactionやmessageのみを処理する最小エコー Bot を作る - Deep Link:固定のメッセージ ID に対する URL をハードコードし、遷移だけを試す
- 添付:静的 PNG(小さな画像)を 1 枚だけ返すハンドラーを用意し、Attachments API で取得
この段階で動けば、残るのは manifest(特に validDomains)か SSO リダイレクトの整合性です。
よくある質問(FAQ)
Q. Deep Link のみ失敗し、Bot 会話自体は成立しています。
A. manifest の validDomains と webApplicationInfo.resource を重点的に確認。Deep Link 遷移先でタブや認証ページに飛ぶフローが挟まると、この 2 つのズレが顕在化します。
Q. 添付 404 はローカルだけ。本番(Azure App Service)だと再現しません。
A. ローカルでは serviceUrl が安定しないため、キャッシュしたクライアントを使い回すと 404 が出ます。毎リクエストで context.activity.serviceUrl を使って ConnectorClient を作る構成に変更してください。
Q. manifest の id と Azure AD の ID を一致させる必要は?
A. ありません。一致が必要なのは bots[0].botId と webApplicationInfo.id です。manifest の id は Teams アプリの識別子で、別物です。
Q. ngrok のドメインが毎回変わるのが面倒です。
A. 有償の固定サブドメイン、あるいは dev tunnels の固定 URL を利用し、manifest の置換をスクリプト化すると効果的です。
トラブル再発防止のベストプラクティス
- 環境変数の一元管理:
.env.local/.env.dev/.env.prodを分け、BOT_ID/RESOURCE_URI/PUBLIC_HOSTを注入。 - CI で manifest 検証:PR 時に manifest スキーマ検証・必須フィールドチェック・ドメイン検査 を自動化。
- 権限変更の可視化:Azure AD の API パーミッション変更と「管理者同意」を運用チェックリストに追加。
- クライアント差異の確認:Web 版 / デスクトップ版(Classic / New)で挙動が異なることがあるため、双方で検証。
トラブル対応フローチャート(文章版)
- Deep Link が失敗 → manifest の再確認(validDomains / webApplicationInfo) → 再パッケージ → 再 sideload → クライアント再起動
- 添付が 404 →
serviceUrlとAuthorizationをログに出す → API を単体で叩いて 200 ならコード側のルーティング、404 なら botId/認証を再点検 - 直らない → Toolkit 5.1 系で再現性確認 or dev tunnels へ切替 → Teams キャッシュのクリア
まとめ
「Deep Link 生成失敗」「添付 404」は、Teams Toolkit・Bot Framework・Azure AD・ローカル公開ドメインの 整合性が 1 箇所でも崩れると起こる現象です。特にローカルデバッグでは validDomains と webApplicationInfo.resource の更新忘れが頻発します。この記事のチェックリストと修正テンプレートを適用し、再パッケージ → 再 sideload → 再起動までを“ひとセット”にすれば、短時間で症状を解消できます。
付録:環境別の manifest スニペット例
| 環境 | validDomains | webApplicationInfo.resource | 備考 |
|---|---|---|---|
| Local(ngrok) | localhost / *.ngrok-free.app | api://<ngrok-sub>.ngrok-free.app/<clientId> | ngrok 再発行時は manifest 置換 → 再 sideload |
| Dev(dev tunnels) | *.devtunnels.ms | api://<devtunnel>.devtunnels.ms/<clientId> | 固定 URL で安定化 |
| Prod(独自ドメイン) | app.example.com | api://app.example.com/<clientId> | カスタムドメイン+証明書で運用 |
付録:VS Code/Teams Toolkit での操作メモ
- F5 の前に公開ドメインを確定 →
manifest.jsonを反映 - 「Update App Package」(または「Zip Teams App Package」等)で
teamsApp.zipを再生成 - Teams の「アプリをアップロード」→「組織用にアップロード」で既存アプリを更新
- 必要に応じて
supportsFiles: trueを設定(添付利用時)
付録:トラブル時に残すべきログ項目
- Deep Link:最終 URL/生成元の会話 ID・メッセージ ID・テナント ID
- 添付:
serviceUrl/attachmentId/呼び出し HTTP ステータスとレスポンス本文 - 認証:取得したトークンのスコープ(audience と expires だけでも良い)
最後に
Toolkit のアップグレードやローカル公開ドメインの切り替えは、manifest と Azure AD 側の設定に“微妙なズレ”を生みやすい工程です。この記事のテンプレートとチェックリストをプロジェクトに組み込み、「ドメインが変わったら manifest を再発行・再 sideload」という運用を徹底して、ローカルデバッグのストレスをゼロにしていきましょう。

コメント