Teams ToolkitローカルデバッグでDeep Link生成失敗・添付ファイル404を解決する完全ガイド

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 つがローカルデバッグでは落とし穴になります。

  1. validDomains:アプリが表示・呼び出し・遷移で利用するホストを列挙します。ローカル公開ドメイン(例:*.ngrok-free.app / *.devtunnels.ms)を入れないと、Teams は “未知のドメインへ遷移しようとしている” と判断し、Deep Link の検証も含めて失敗しやすくなります。
  2. webApplicationInfo:SSO を使う Bot/Tab では Azure AD 側のアプリ登録と Application ID URI(Resource) が一致している必要があります。ローカル用 URI(api://{ローカル公開ドメイン}/{AAD App ID})と manifest の resource がズレると、トークン検証が通らず副作用で Deep Link の生成・遷移が弾かれることがあります。
  3. 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].botIdBot の認証Azure AD App(client)ID と一致別テナントの ID / 古い ID
webApplicationInfo.idSSO 対象アプリの指定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 の確実な手順

  1. ngrok(または dev tunnels)で公開し、発行された 完全なホスト名 を控える。
  2. appPackage/manifest.json(テンプレートでは manifest.template.json の場合あり)を編集し、上記の通り validDomains / webApplicationInfo を更新。
  3. Teams Toolkit のタスク(VS Code 左の Teams Toolkit ビュー → Lifecycle → Update App Package など)で teamsApp.zip を再生成。
  4. Teams の「アプリをアップロード」から 既存アプリを更新 として新しい teamsApp.zip を sideload。
  5. 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 つです。

  1. serviceUrl の不整合:着信アクティビティの serviceUrl に基づいて API を呼ぶ必要があります。キャッシュした古い URL を使うと 404。
  2. 認可ヘッダーの欠落:Authorization: Bearer <bot 用トークン> を付けないと 401/404 になりがち。
  3. 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 での動作確認(原因切り分け用)

  1. 受信アクティビティの serviceUrl(例:https://smba.trafficmanager.net/...)を控える。
  2. 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 を中心にキャッシュクリア → 再起動
  • Azure AD の管理者同意を再実行:ポリシー変更後は必ず再同意。テナント境界を跨いでいないかも確認。
  • 権限の最小化検証:一度 Chat.ReadWrite のみに戻し、Deep Link 生成だけを検証 → 改善したら段階的に Files.* を追加。

実案件で役立つ“運用の型”

ローカル・開発・本番の 3 環境を明示的に分離

manifest を 3 つ用意し、ドメイン / webApplicationInfo.resource / botId を環境変数で埋め替えます。Teams Toolkit の env 機能を使い、パッケージ名と name.short に環境ラベル(例:(Local) / (Dev) / (Prod))を付与すると、sideload 先で混同しません。

ドメインの更新手順を標準化

  1. ngrok/dev tunnels を立てる → ドメイン取得
  2. manifest の置換(スクリプト化推奨)
  3. パッケージ再生成 → 再 sideload
  4. クライアント再起動 → 動作確認

ログの粒度を上げる

  • Deep Link 生成箇所で 最終的に組み上がった URL を INFO ログ出力
  • 添付 404 は serviceUrl / attachmentId / 会話 ID をセットで WARN ログ

トラブルを再現できる最小サンプル(考え方)

原因切り分けに、次の順で最小化してください。

  1. Bot:messageReaction や message のみを処理する最小エコー Bot を作る
  2. Deep Link:固定のメッセージ ID に対する URL をハードコードし、遷移だけを試す
  3. 添付:静的 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)で挙動が異なることがあるため、双方で検証。

トラブル対応フローチャート(文章版)

  1. Deep Link が失敗 → manifest の再確認(validDomains / webApplicationInfo) → 再パッケージ → 再 sideload → クライアント再起動
  2. 添付が 404 → serviceUrl と Authorization をログに出す → API を単体で叩いて 200 ならコード側のルーティング、404 なら botId/認証を再点検
  3. 直らない → Toolkit 5.1 系で再現性確認 or dev tunnels へ切替 → Teams キャッシュのクリア

まとめ

「Deep Link 生成失敗」「添付 404」は、Teams Toolkit・Bot Framework・Azure AD・ローカル公開ドメインの 整合性が 1 箇所でも崩れると起こる現象です。特にローカルデバッグでは validDomains と webApplicationInfo.resource の更新忘れが頻発します。この記事のチェックリストと修正テンプレートを適用し、再パッケージ → 再 sideload → 再起動までを“ひとセット”にすれば、短時間で症状を解消できます。

付録:環境別の manifest スニペット例

環境validDomainswebApplicationInfo.resource備考
Local(ngrok)localhost / *.ngrok-free.appapi://<ngrok-sub>.ngrok-free.app/<clientId>ngrok 再発行時は manifest 置換 → 再 sideload
Dev(dev tunnels)*.devtunnels.msapi://<devtunnel>.devtunnels.ms/<clientId>固定 URL で安定化
Prod(独自ドメイン)app.example.comapi://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」という運用を徹底して、ローカルデバッグのストレスをゼロにしていきましょう。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次