Cowork プラグイン開発(Dataverse MCP セット)
目的特化型の Copilot Cowork プラグイン(M365 アプリパッケージ .zip)を作る。
ビジネススキル(SKILL.md)と Dataverse MCP コネクタをセットにし、Entra ID の OAuth 2.0 認可コードフロー認証で
Cowork から Dataverse を直接操作できるようにする。
データ先は Dataverse だけではない: agentConnectors は remote MCP server を指すため、
mcp-server スキル で構築した 自前 MCP Server(Azure Functions) も
同じようにコネクタにできる。基幹 DB・ファイルサーバー・業務 API など Dataverse に無いデータを
Cowork から扱う場合は 自前 MCP Server をコネクタにする を併せて読む
(差分は Step 3 の権限・Step 4 の可否・Step 5 の Scope・Step 6 の URL の 4 点だけ)。
前提: 利用テナントが Frontier プレビュー に参加していること。
会社環境で Cowork の利用が許可されている場合のみこのスキルを使用してください。利用可否が不明な場合は管理者に確認してください。
異常系・トラブルシュートは references/troubleshooting.md を参照。
サブリファレンス
パッケージ構成(Skills + remote connector)
<plugin-root>/
├── manifest.json # M365 Unified App Manifest v1.28
├── color.png # 192×192 フルカラーアイコン
├── outline.png # 32×32 アウトラインアイコン
├── build-package.ps1 # .zip 生成(検証付き)
└── skills/
└── <skill-name>/ # kebab-case。フォルダ名 = SKILL.md の name と一致必須
└── SKILL.md # frontmatter(name/description) + ワークフロー本文
事前確認(会話の最初に 1 回だけ)
本スキルの利用が確定したら、standard の共通契約に加え、
1 回の AskUserQuestion で次をまとめて確認する。
| # | 質問 | 合格条件 |
|---|
| 1 | Cowork と Frontier を会社が許可し、対象者を登録済みか | 管理者とテストユーザーに Microsoft Copilot ライセンスがあり、Frontier 対象ユーザーである |
| 2 | Dataverse MCP を使う対象環境とテーブルはどれか | DATAVERSE_URL、論理名、データ分類、許可する read / write 操作が確定している |
| 3 | Entra OAuth アプリを作成・変更する担当者は誰か | アプリ登録を作成でき、必要な API permission を構成できる担当者を記録している |
| 4 | 管理者同意と MCP クライアント許可の担当者は誰か | tenant-wide consent の担当者と、環境の allowedmcpclients を変更できる担当者を記録している |
| 5 | Teams Developer Portal と M365 管理センターの担当者は誰か | OAuth registration ID を作成でき、AI Administrator がパッケージの追加・公開・配布を実行できる |
| 6 | 公開対象と検証範囲はどこまでか | 最初は単一テストユーザーまたはセキュリティ グループ、1 skill + 1 connector で合意している |
担当ごとの必要な権限は references/permissions.md を見せて確認する(同意できるロール: クラウド アプリケーション管理者・アプリケーション管理者・AI 管理者)。
Frontier は Cowork を利用するためのプレビュー条件であり、Microsoft Copilot ライセンスとは別に確認する。
Global Administrator を常用せず、Agent 管理は AI Administrator、全テナントへの高権限な
OAuth 同意が必要な場合は Privileged Role Administrator を担当工程だけに使用する。
前提チェック:
python .github/skills/standard/scripts/check_mcp_client.py cowork が ✅ であること。
- Python 3(auth_helper.py / アイコン生成)と PowerShell(パッケージビルド)が利用可能であること。
- いずれかの前提が未確認なら、アプリ登録・シークレット作成・公開へ進まないこと。
スキル同梱スクリプト(再利用)
scripts/ のスクリプトは汎用化されており、どのテナント・環境でも .env(TENANT_ID /
DATAVERSE_URL / PUBLISHER_PREFIX / COWORK_OAUTH_CLIENT_ID)を参照して動作する。
ワークフロー(正常系)
Step 0: ヒアリング(プラグイン構想の提案)
ユーザーが「プラグインを作りたい」「Cowork で〜したい」と相談してきたら、いきなり実装に入らず
まず構想を提案する。以下の流れで会話を始める。
-
目的の確認: 「Cowork 用のプラグインとして作りますか? 例えば『〇〇業務を Cowork から
Dataverse のデータで支援する』ような形を想定しています」と確認する。
-
対象データの把握: 連携する Dataverse 環境/テーブル(顧客・契約・実績など)をヒアリング。
不明なら describe / search 系で既存テーブルを軽く調べて候補を出す。
Dataverse に無いデータ(基幹 DB・ファイルサーバー・業務 API)が必要なら、この段階で確認する。
必要なら mcp-server スキル で自前 MCP Server を先に立て、
references/custom-mcp-connector.md でコネクタとして併載する。
-
スキル案を5つ提案: その業務ドメインで Cowork が役立つスキルを5つ程度列挙する。
各スキルは「名前(kebab-case)+一言の用途+トリガー語の例」をセットで示す。
例(保守契約ドメインの場合):
| スキル名 | 用途 | トリガー語の例 |
|---|
annual-customer-review | 年次レビュー資料作成 | 「年間レビュー資料を作って」 |
contract-renewal-proposal | 契約更新提案の下書き | 「更新提案をまとめて」 |
incident-trend-report | 障害傾向レポート | 「故障傾向を分析して」 |
equipment-lifecycle-plan | 機器更新計画 | 「更新計画を提案して」 |
cost-optimization-summary | コスト最適化提案 | 「コスト削減案を出して」 |
-
プラグイン名を提案: 5つのスキル群を束ねるプラグイン名(短縮名 / 正式名)を 2〜3 案提示する。
個社名は避け、業務ドメインが伝わる名前にする(例: 「MFP 年間レビュー」「保守契約アシスタント」)。
-
合意: ユーザーが採用するスキル(1つでも複数でも可)とプラグイン名を選んだら、Step 1 以降に進む。
テンプレートから始める: 営業支援(予定表・メール・チャット → CRM)なら、合意後に
sales-crm-plugin を scaffold_from_template.py で生成して Step 1 の describe 確認から続ける。
Code Apps の templates/sales-crm と同じ Dataverse を共有する。
最初は 1スキル + Dataverse MCP コネクタの最小構成で公開・疎通確認し、動いたら
残りのスキルを追加する流れが安全(コネクタ認証の検証を先に済ませられる)。
Step 1: 対象テーブルを describe でスキーマ確認(クエリを書く前に必須)
スキル本文にクエリを書く前に、必ず describe で対象テーブルの実スキーマを確認する。
テーブル名・列名・ルックアップ(外部キー)列の正確な名前を推測で書かない(推測は実行時の
Read query Failed の主因。特に FK 列は環境やクエリ方式で表記が異なる)。
- 対象テーブルごとに
describe を実行し、以下を確定する:
- テーブル名(論理名 / コレクション名のどちらをクエリで使うか)
- 列の論理名(表示名ではなく論理名)
- ルックアップ列の表記:
describe の結果に出る実際の列名を使う。
read_query のクエリ方式に合わせること(Web API/OData 形式の _xxxid_value と
SQL/論理名 xxxid は別物。describe が返す名前をそのまま使う)。
- 選択肢(Picklist)の値とラベルの対応
- 確認できたスキーマだけを使ってクエリを書く。describe に無い列・テーブルは使わない。
- スキル本文の冒頭にも「Step 1: 対象テーブルを
describe で確認してからクエリする」を入れ、
生成するスキル自身も実行時に describe で裏取りする手順にする(下のテンプレート参照)。
確認結果は「対象データ(テーブル)」表に論理名でまとめる。表示名は補足に留める。
Step 2: スキル(SKILL.md)を作る
skills/<skill-name>/SKILL.md を作成。フォルダ名と frontmatter name を完全一致させ、
name は kebab-case(小文字英数とハイフン、連続/先頭/末尾ハイフン禁止)。
---
name: annual-customer-review
description: |
<何をするスキルか>。Use when ユーザーが「<トリガー語1>」「<トリガー語2>」と依頼したとき。
Dataverse MCP コネクタ(read_query / search_data / search / describe)を使用する。
license: MIT
metadata:
author: <作成者> # 開発中のサインイン アカウントの表示名(manifest の developer.name と同じ値)
version: "1.0"
---
作成者は開発中のサインイン アカウントにする。python .github/skills/cowork/scripts/get_developer_account.py が
auth_helper のキャッシュで Graph の /me を読み、表示名(developerName。manifest の上限 32 文字に切り詰め)を返す。
この値を COWORK_DEVELOPER_NAME として manifest の developer.name と各スキルの metadata.author に入れる
(テンプレートでは質問の detect で既定値になる)。会社名・固定の文字列を入れない。
本文は ワークフローとして書く(番号付き手順/使用ツール名を明示/出力フォーマットを定義)。
本文は約 1,500〜2,000 語以内。詳細は references/ に逃がす(コンパニオンファイルは最大20・各5MB)。
生成するスキル本文の必須ルール: ワークフローの最初の Step を
「対象テーブルを describe でスキーマ確認(列名・FK 列名を確定)」とする。
クエリ例の列名は describe で確認済みのものだけを使い、未確認の列を推測で書かない。
結果の見せ方(提案するスキルは標準で Render UI のグラフ): 提案・助言・打ち手・対策案を出すスキルと、数字を扱うスキル(集計・点検・登録の確認)は、
答えを チャットのグラフ(Render UI)・表・保存用の HTML レポート で返す。スキルに書くのは 3 つだけ:
① 必須ルールの後に <!-- include: display-rules.md -->(ビルドで共通ルール display-rules.md に置き換わり、
scripts/ に report_builder.py とひな形が入る)、② 「このスキルのグラフ」の節(図の題名・種類 stacked_hbar / grouped_bar・元の表・単位)、
③ 図の位置 〔Render UI の図: …〕 と 📄 レポート: <ファイル名>.html を入れた回答のひな形と、「このスキルの確認(返す前・毎回)」。
グラフ・表・HTML は report_builder.py が 1 つの計算結果(results.json)から作り、取得不足・単位の混在・内部識別子・3 つの数字の不一致を止める。
Render UI の仕様と正式な色は公開されていないため、実行時に Render UI の説明を読んで使い、表示を確かめるまで成功と書かず、1 回直して駄目なら文字のグラフに切り替える。
書き方の例・results.json の形は references/visual-output.md。build_agent_package.ps1 が check_visual_output.py で毎回検査し、提案するスキルに手順が無ければ止める(#52・#53)。
Step 3: Entra アプリ(OAuth クライアント)を作成・構成
Dataverse MCP は Microsoft ファーストパーティ API のため、トークンの audience が Dataverse 自身
(https://<org>.crm.dynamics.com)である必要がある。SSO 方式は audience が「自前アプリ」になり失敗する
(→ troubleshooting #14)。そのため OAuth 2.0 認可コードフローを使い、Enterprise Token Store に
Dataverse 宛のトークンを直接取得させる。クライアントシークレットが必要。値は .env に保存する。
再利用スクリプト(推奨): scripts/setup_entra_oauth_graph.py が
Microsoft Graph API 経由でアプリ登録・mcp.tools 権限付与・シークレット作成・.env 書き込みを一括で行う。
standard の auth_helper.py が共有する認証キャッシュを利用するため、az CLI で別途サインインする必要がない。
# auth_helper.py のキャッシュ済みトークンで Graph API を呼ぶ
python .github/skills/cowork/scripts/setup_entra_oauth_graph.py
# 例: python .github/skills/cowork/scripts/setup_entra_oauth_graph.py --display-name "MyApp-Cowork-OAuth" --secret-years 1
自前 MCP Server を使う場合は付与する権限が違う(Dynamics CRM の mcp.tools ではなく
自前 API の公開スコープ)。--api-audience を指定する。
python .github/skills/cowork/scripts/setup_entra_oauth_graph.py --api-audience "api://<api-app-id>" --api-scope MCP.Access
併用するなら --include-dataverse を足す。詳細は custom-mcp-connector.md。
代替(az CLI 版): scripts/setup_entra_oauth.ps1。共通 auth_helper のキャッシュを使わず、az CLI のブラウザーサインインと独立した資格情報キャッシュを利用する:
./.github/skills/cowork/scripts/setup_entra_oauth.ps1 -DisplayName "Contoso-Cowork-OAuth"
az CLI で手動で行う場合の同等コマンド:
$env:PATH = "C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin;$env:PATH"
az login --tenant <TENANT_ID> --allow-no-subscriptions --only-show-errors
# 1. アプリ登録(リダイレクト URI は固定値2つ)
$appId = az ad app create --display-name "Cowork-DataverseMCP-OAuth" `
--web-redirect-uris `
"https://teams.microsoft.com/api/platform/v1.0/oAuthRedirect" `
"https://teams.microsoft.com/api/platform/v1.0/oAuthConsentRedirect" `
--sign-in-audience AzureADMyOrg --query appId -o tsv
# 2. Dynamics CRM の委任権限 mcp.tools を付与(Dataverse MCP 専用スコープ)
az ad app permission add --id $appId `
--api 00000007-0000-0000-c000-000000000000 `
--api-permissions a4c5bee6-25ff-4bb5-b926-b7eb8062ae7a=Scope --only-show-errors
# 3. クライアントシークレットを作成(OAuth 認可コードフローに必須)→ .env に保存
$secret = az ad app credential reset --id $appId --display-name "cowork-oauth" `
--years 2 --query password -o tsv
# .env の COWORK_OAUTH_CLIENT_ID / COWORK_OAUTH_CLIENT_SECRET に書き込む(Git にコミットしない)
(Get-Content .env) `
-replace '^COWORK_OAUTH_CLIENT_ID=.*', "COWORK_OAUTH_CLIENT_ID=$appId" `
-replace '^COWORK_OAUTH_CLIENT_SECRET=.*', "COWORK_OAUTH_CLIENT_SECRET=$secret" |
Set-Content .env
ポイント:
- SSO と違い
Expose an API(スコープ公開)も preAuthorizedApplications 事前承認も不要。
認可コードフローは Dynamics CRM の委任スコープ mcp.tools を直接同意するため。
- API 権限は
mcp.tools のみでよい(user_impersonation は不要)。OAuth registration の scope を
.default にすることで、このアプリに静的設定された mcp.tools が要求される。
- シークレットは機密。
.env は .gitignore で除外する。スキルや manifest には絶対に書かない。
テナント管理者の事前同意(admin consent)が必要な場合がある: テナントがユーザーの自己同意
(user consent)を制限していると、mcp.tools への同意は Cowork 初回利用時にサイレントに失敗する
(エラー表示なしで「コネクタが反応しない」ように見える → troubleshooting #22)。
setup_entra_oauth_graph.py はアプリ登録後にサービスプリンシパルの作成と admin consent の状態を
自動確認し、未完了なら https://login.microsoftonline.com/<TENANT_ID>/adminconsent?client_id=<appId>
の形式で URL を表示する。開発者自身がテナント管理者でなければ、この URL をテナント管理者に共有し、
同意を得てから Step 4 以降に進む。
Step 4: Entra Client ID を許可 MCP クライアントに登録(必須)
自前 MCP Server のみを使う場合はこの Step をスキップする(Dataverse を経由しないため)。
代わりに MCP Server 側の API アプリで Cowork の Client ID を事前承認する
→ custom-mcp-connector.md。
OAuth 認可コードフローでは Dataverse に提示されるトークンの appid がこのカスタムアプリになる。
そのため、Entra の Client ID を allowedmcpclients テーブルに登録・有効化しないと、認証は通っても
データ取得時に失敗する(ブログでも「抜けると plugin は正常に見えても Dataverse 利用時に失敗」と強調)。
再利用スクリプト(推奨): scripts/register_mcp_client.py(汎用・どのテナントでも可)。
# .env の COWORK_OAUTH_CLIENT_ID を登録(未登録なら作成、既存なら有効化)
python .github/skills/cowork/scripts/register_mcp_client.py
# 状態確認のみ(副作用なし)
python .github/skills/cowork/scripts/register_mcp_client.py --check
# 一覧
python .github/skills/cowork/scripts/register_mcp_client.py --list
# app id を明示
python .github/skills/cowork/scripts/register_mcp_client.py --app-id <CLIENT_ID> --name "Cowork Dataverse MCP"
uniquename 未指定時は <PUBLISHER_PREFIX>_<name のスラッグ> で生成される。
GUI なら Power Platform 管理センター → 環境 → 設定 → 機能 →
Dataverse MCP の詳細設定(etn=allowedmcpclient)で +New。
Step 5 に進む前に一括診断する(推奨): Teams 開発者ポータル登録や zip 再アップロードは
手戻りのコストが高いブラウザ操作なので、その前に3層(アプリ登録・admin consent・
allowedmcpclients)が揃っているかを1コマンドで確認しておく。
python .github/skills/cowork/scripts/diagnose_cowork_connector.py
いずれかのレイヤーが ❌/❓ の場合は、表示される対処コマンドを実行してから Step 5 に進む。
Step 5: OAuth client registration API → registrationId 取得
正常系は Developer Portal private API の CRUD を CLI から直接送る。auth_helper で Microsoft 365 Agents Toolkit の
公開クライアント(Developer Portal が事前承認済み)のトークンを取り、/api/v1.0/oauthConfigurations を呼ぶ。
初回だけ Device Code のサインインが要り、以後はキャッシュで無操作になる。同じ clientId・Base URL の登録が既にあれば止まり、
作成後は読み戻して plan と一致するかを確かめ、生の registration ID を .env に書く(画面には末尾だけ出す)。
手順と endpoint は portal-api-automation.mdを参照。
python .github/skills/cowork/scripts/manage_oauth_registration_api.py create `
--name "Dataverse MCP OAuth (<plugin>)" --base-url $env:DATAVERSE_URL `
--scopes "$env:DATAVERSE_URL/.default,offline_access"
# 承認後、同じ引数に --write-env .env --expected-hash <APPROVED_HASH> --apply
CLI のトークンが取れない(Device Code が条件付きアクセスで禁止など)場合は --transport browser で plan を出し、
ログイン済み VS Code 統合ブラウザから送る。API が 401 / 403 / 404、または観測済み schema と一致しない場合だけフォーム操作へ切り替える。
ブラウザ自動化方針に従い、使用する Edge プロファイルを確認する。
dev.teams.microsoft.com/tools → Tools →
OAuth client registration → New(SSO client registration ではない)。
| フィールド | 値 |
|---|
| Registration name | Dataverse MCP OAuth (<org>) |
| Base URL | https://<org>.crm.dynamics.com(/api/mcp は付けない。MCP URL は manifest 側に書く) |
| Client ID | .env の COWORK_OAUTH_CLIENT_ID |
| Client secret | .env の COWORK_OAUTH_CLIENT_SECRET(画面に直接貼る/質問ツール禁止) |
| Authorization endpoint | https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/authorize |
| Token endpoint | https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token |
| Refresh endpoint | https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token |
| Scope | https://<org>.crm.dynamics.com/.default,offline_access(カンマ区切り。UI のヘルプ文言が |
「separated by a comma」のため半角スペース区切りでは受け付けない環境がある。.default で静的権限= | |
mcp.tools、offline_access でリフレッシュトークン) | |
| Enable PKCE | 有効(推奨) |
| Restrict usage by org | My organization only(単一テナント)/Any Microsoft 365 Organization(複数テナント配布時) |
| Restrict usage by app | Any Teams app(ストア検証が通るまではこちら。公開・疎通確認後に Existing Teams app へ切替可) |
Save すると OAuth client registration ID が発行される。これを 生の値のまま(Base64 変換せず)
.env の COWORK_OAUTH_REGISTRATION_ID に保存する。
自前 MCP Server の場合は Base URL を https://<app>.azurewebsites.net、Scope を
api://<api-app-id>/.default,offline_access に差し替える
→ custom-mcp-connector.md。
referenceId の値: OAuth 方式でも SSO 方式と同じく
Base64("<tenantId>##<registrationId>") 形式が必要(実機検証で確認済み。→ troubleshooting.md #23)。
.env には生の registration ID を保存し、Base64 エンコードは Step 7 の
build_agent_package.ps1 が自動で行う(手動でエンコードして .env に入れない)。
Step 6: manifest.json を作成
{
"$schema": "https://developer.microsoft.com/json-schemas/teams/v1.29/MicrosoftTeams.schema.json",
"manifestVersion": "1.29",
"version": "1.0.0",
"id": "<決定的GUID: uuid5 から生成>",
"developer": { "name": "...", "websiteUrl": "...", "privacyUrl": "...", "termsOfUseUrl": "..." },
"name": { "short": "...", "full": "..." },
"description": { "short": "...", "full": "..." },
"icons": { "color": "color.png", "outline": "outline.png" },
"accentColor": "#D5001C",
"agentSkills": [ { "folder": "./skills/<skill-name>" } ],
"agentConnectors": [
{
"id": "<plugin-slug>-dataverse-mcp",
"displayName": "<プラグイン名> Dataverse MCP",
"description": "Dataverse のテーブル/レコードへ MCP 経由でアクセス。",
"toolSource": {
"remoteMcpServer": {
"mcpServerUrl": "https://<org>.crm.dynamics.com/api/mcp",
"authorization": {
"type": "OAuthPluginVault",
"referenceId": "__COWORK_OAUTH_REGISTRATION_ID__"
}
}
}
}
]
}
-
developer.name は開発中のサインイン アカウントの表示名(Step 2 の metadata.author と同じ値。32 文字まで)。
ビルド前に get_developer_account.py --check-manifest <plugin-root>/manifest.json で一致を確かめる。
-
コネクタの id はテナント内の全プラグインで一意にする(<プラグインの kebab 名>-<データ源>-mcp。例 sales-crm-dataverse-mcp)。
Cowork は同じコネクタ ID を持つプラグインを 1 つしか有効にできず、2 つ目を有効にすると「コネクタの競合
"dataverse-mcp" を指定できるプラグインは 1 つだけです」と表示され、どちらかを無効にするまで MCP が使えない
(→ troubleshooting #50)。テナントのプラグインのコネクタ ID は API で一覧できないので、汎用的な ID(dataverse-mcp など)を
使わないことで衝突を防ぎ、手元のプラグインとは次で突き合わせる(build_agent_package.ps1 も汎用 ID を止め、
COWORK_PLUGIN_SCAN_DIRS があれば同じ突き合わせを行う)。displayName もプラグイン名を入れて見分けられるようにする。
python .github/skills/cowork/scripts/check_connector_ids.py --manifest <plugin-root>/manifest.json --scan <ほかのプラグインの置き場所>
既に公開済みのプラグインの ID を変えた版を出すと、利用者は Cowork でコネクタを Connect し直す(初回同意)。
-
referenceId はプレースホルダー __COWORK_OAUTH_REGISTRATION_ID__ のまま source に残す(Step 5 の
実 registration ID を直接コミットしない)。実値は .env の COWORK_OAUTH_REGISTRATION_ID に置き、
Step 7 のビルドスクリプトが zip 生成時に注入する。
-
agentConnectors は複数書ける。Dataverse MCP と自前 MCP Server(https://<app>.azurewebsites.net/api/mcp)を
併載できる。その場合は各 description に どのコネクタをどの用途で使うかを明記する
(エージェントはこの説明でツールを選ぶため)→ custom-mcp-connector.md。
-
id は python -c "import uuid; print(uuid.uuid5(uuid.NAMESPACE_URL, '<安定URL>'))" で決定的に生成。
-
manifest は 1.29 にし、mcpToolDescription を書かない(動的ツール検出)。1.29 では mcpToolDescription が任意になり、
省略すると Cowork が実行時に MCP サーバーの tools/list でツールを取得する(wiqd plugin create も 1.29 を生成する)。
1.28 の固定ツール定義では、アップロード・Connect は成功するのに Cowork の実行時にツールが 0 件になる事例があった
(→ troubleshooting #46)。
- 動的検出では、サーバーの全ツール(
delete_record・create_table・delete_table など)が Cowork に見える。
各スキルの必須ルールに「使うツール」と「呼ばないツール」を明記し、Dataverse 側は利用者のセキュリティ ロールで
書き込み・削除を絞る(permissions.md)。
- スキルの最初の Step に「
describe が使えなければ止めて報告する(別の手段で探さない)」を入れる。
- スキルが挙げたツール名がサーバーにあるかは
rehearse_plugin.py tools(動的モード)で確かめる。
固定ツール定義(1.28 または 1.29 で絞りたい場合): 値は オブジェクト { "file": "<相対パス>" }(文字列不可。
スキーマ上 description で包む形ではない)。参照先は JSON 形式の tools 定義でなければ
is invalid or not found in manifest package になる(.md は invalid)。1.28 では省略不可。
ビルドスクリプトは manifest が参照するファイルだけを同梱する。
dataverse-mcp-tools.json(固定にする場合だけ。パッケージルートに配置):
{
"tools": [
{ "name": "read_query", "description": "FetchXML クエリでレコード取得。",
"annotations": { "readOnlyHint": true, "title": "Read Query" } },
{ "name": "search_data", "description": "テーブル内を条件検索。",
"annotations": { "readOnlyHint": true, "title": "Search Data" } },
{ "name": "search", "description": "Dataverse 全体を横断検索。",
"annotations": { "readOnlyHint": true, "title": "Search" } },
{ "name": "describe", "description": "テーブル/列のメタデータを取得。",
"annotations": { "readOnlyHint": true, "title": "Describe" } }
]
}
ツール名は Dataverse MCP 側の変更に追従させる(廃止された describe_table/list_tables/fetch や
旧 search(データ検索の意味)をそのまま書かない。現在の正しいツール名一覧は
standard/references/dataverse-mcp-setup.md を参照)。
古いツール名のまま公開すると、Cowork 側で該当ツールが認識されずスキルが動作しない(→ troubleshooting #15)。
上記の例は読み取り専用の4ツールのみ。スキルが create_record/update_record/delete_record/
upsert_skill 等の書き込み系ツールを呼ぶ場合は、そのツール名もここに追加すること。
追加を忘れると、読み取りは動くのに書き込み操作だけ「反応しない」原因不明の部分故障になる
(→ troubleshooting #15 の補足)。
書き込み系の引数は create_record(tablename, item) / update_record(tablename, recordId, item)。
Lookup は @odata.bind ではなく、Lookup 列の論理名に {"relatedTable":"<テーブル>","recordId":"<GUID>"} を
文字列化した JSON で渡す。スキル本文にもこの形式を明記する(→ troubleshooting #29)。
-
アイコンはドメイン文脈を読んでから設計する(→ standard/references/icon-creation.md
の「アイコン画像提案フロー」)。generate_icon_png.py の汎用スパークルをそのまま登録しない。
この manifest の name/description(プラグインの業務目的)と accentColor からモチーフ・配色を決め、
プラグイン専用の draw_<theme>_icon() を実装してから color.png(192×192)/ outline.png(32×32,
白い透明背景)を生成する。
Step 7: パッケージ(.zip)をビルド
manifest.json をルートに置いて圧縮する(フォルダごと圧縮しない)。mcpToolDescription を書いた場合だけ、そのツール説明 JSON も含める。
.env から COWORK_OAUTH_REGISTRATION_ID を読み __COWORK_OAUTH_REGISTRATION_ID__ に注入してから zip 化する
(手作業で referenceId を manifest に直接埋めない=取り違え・コミット事故を防ぐ)。
再利用スクリプト(推奨): scripts/build_agent_package.ps1。
.env の値が '...'/"..." で囲まれていても引用符を自動で取り除いてから注入する
(引用符付きのまま注入すると referenceId が壊れ、Cowork 初回同意時にコネクタ認証が失敗する)。
pwsh .github/skills/cowork/scripts/build_agent_package.ps1 -PluginRoot <plugin-root> -OutputName <name>
手動で行う場合の同等コマンド(quote-stripping を忘れないこと):
$regId = (Get-Content .env | Select-String '^COWORK_OAUTH_REGISTRATION_ID=').ToString().Split('=',2)[1].Trim("'", '"')
(Get-Content manifest.json -Raw) -replace '__COWORK_OAUTH_REGISTRATION_ID__', $regId | Set-Content manifest.built.json
Compress-Archive -Path manifest.built.json, color.png, outline.png, skills `
-DestinationPath dist/<name>.zip -Force # 固定ツール定義なら dataverse-mcp-tools.json も加える
ZIP 検証: ルートに manifest.json(build 後、プレースホルダーが実 ID に置換済み)、
skills/<skill-name>/SKILL.md が含まれること(固定ツール定義なら参照先 JSON も)。
公開前リハーサル(Step 7 の最後)
Step 7 でビルドしたら、公開する前にスキルを実データで通しで動かす。Cowork の画面と違い、何度でも同じ依頼で試せ、会話・ツール呼び出しが残る。
初回だけ専用の公開クライアントを作る(Cowork 本体のアプリとは別。mcp.tools の管理者同意と allowedmcpclients 登録まで行う)。
python .github/skills/cowork/scripts/rehearse_plugin.py setup-client --name "<Plugin>-Rehearsal" --apply # 初回だけ
python .github/skills/cowork/scripts/rehearse_plugin.py tools --plugin-root <plugin-root> # ツール名の差
# 依頼 → 提示 → 利用者の返事 → 登録。まず書き込みなしで、よければ --allow-write
python .github/skills/cowork/scripts/rehearse_plugin.py run --plugin-root <plugin-root> --skill <skill> `
--prompt "<依頼>" --reply "<確認の返事>" --transcript spec/eval/cowork-rehearsal/<skill>.md
見るところ: 検索が 0 件で止まっていないか(search_data に頼らず read_query の LIKE)、提示してから登録しているか、
承認済みの行を変えていないか、承認件数どおり登録したか、値(分類など)が既存と揃っているか、数値が根拠にあるか、
JSON などの文字列が壊れていないか(troubleshooting #38〜#45)。直したら版を上げてビルドし直す。
Step 8: 管理センター private API で新規登録・公開する
組織への公開の前に、作成者が自分だけにインストールして確かめる(管理者ロール不要・CLI だけで完結)。
atk install --scope Personal と同じ M365 Title サービスの API を、Step 5 と同じ Agents Toolkit クライアントで呼ぶ。
ZIP の事前検証(プレースホルダー残り・manifest の位置)→ plan → hash 承認 → upload → acquire → poll →
launchInfo でスキル数・コネクタ数・blockStatus を照合する。
python .github/skills/cowork/scripts/install_agent_package_personal.py install --package <zip>
# 承認後、同じ引数に --expected-hash <APPROVED_HASH> --apply。外すときは uninstall --title-id <titleId>
private API を正常系にする(UI ウィザードと同じ request を、ログイン済み VS Code 統合ブラウザの同一 session から送る)。
Graph の appCatalogs/teamsApps は agentSkills / agentConnectors を持つパッケージを成功応答のまま破棄することがある
(troubleshooting #26)ため、新規登録には使わない。契約の詳細は admin の M365 テナント管理 API。
| # | 処理 | request | plan operation |
|---|
| 1 | ZIP を検証・ステージ | POST /fd/addins/api/apps/uploadCustomApp?workloads=MetaOS(multipart, ActionType=DEPLOY) | runner の stageCustomApp() |
| 2 | パッケージ確定 | POST /fd/addins/api/v2/actionableApps(FINALIZEPACKAGE + MosOperationId) | agent-publish |
| 3 | インストールできる利用者 | POST /fd/addins/api/availableAgents(ALLOW, Workload=SharedAgent) | agent-allow |
| 4 | 事前インストール(任意) | POST /fd/addins/api/apps(DEPLOY + MosOperationId) | agent-lifecycle |
-
統合ブラウザで https://admin.cloud.microsoft/#/agents/tools/all を開き(サインインはユーザーが行う)、
stageCustomApp(page, { zipPath: "<zip の絶対パス>", actionType: "DEPLOY" }) を実行する。
戻り値(titleId / mosOperationId / currentVersion)を stage.json に保存する。
「already been deployed」は同じ manifest ID が登録済みの合図なので Step 10 の更新へ進む。
-
payload を生成し、各 plan を dry-run して PLAN_HASH をユーザーに提示する。
python .github/skills/cowork/scripts/build_cowork_publish_payloads.py --mode new `
--stage-file stage.json --publish-to <ユーザーのオブジェクトID> --install-to <ユーザーのオブジェクトID> `
--out-dir publish-plan
python .github/skills/admin/scripts/manage_m365_portal_api.py agent-publish --payload-file publish-plan/1-finalize.json
python .github/skills/admin/scripts/manage_m365_portal_api.py agent-allow --payload-file publish-plan/2-allow.json
python .github/skills/admin/scripts/manage_m365_portal_api.py agent-lifecycle --payload-file publish-plan/3-deploy.json
-
承認後、各 plan を --expected-hash <PLAN_HASH> --apply で READY_FOR_BROWSER_API にし、
m365_portal_browser_runner.mjs の runApprovedPlan() で 1 → 2 → 3 の順に 1 件ずつ送る。
各 request の appManagementRequestID を poll し、appsManagementStatus[].status が Success になってから次へ進む。
-
読み戻し: 管理センター Agents → Tools → Plugins に表示され、Cowork の Customize → Plugins に届くことを確認する
(Cowork への反映は数分かかる。Cowork プラグインは All agents(Registry)には出ない)。
公開対象は最初は検証ユーザー(またはセキュリティ グループ)だけにする。build_cowork_publish_payloads.py は
全員公開の payload を作らない。SendEmailToUsers は常に false。
Graph で管理しているプラグイン(代替)
既に Graph の組織カタログで登録・読み戻しできているプラグイン(manage_agent_package_graph.py list に出るもの)は、
同じスクリプトで更新できる。詳細は portal-api-automation.md。
# dry-run。package SHA-256 を含む PLAN_HASH を確認する
python .github/skills/cowork/scripts/manage_agent_package_graph.py deploy `
--package <name>.zip --requires-review
# 承認した同一 package だけを登録/更新する
python .github/skills/cowork/scripts/manage_agent_package_graph.py deploy `
--package <name>.zip --requires-review --expected-hash <APPROVED_HASH> --apply
403(AppCatalog.* スコープなし)の場合は --graph-powershell を付ける(deploy / list の前に置く)。
既定の認証クライアントには AppCatalog.ReadWrite.All の委任が無いため、Microsoft Graph PowerShell の
公開クライアントでこのスコープだけを要求する(auth_helper のキャッシュを client_id 別に再利用)。
API が401/403/404、schema不一致、read-back不一致の場合だけ、次の管理センターfallbackを選択する。
管理センター fallback
⚠️ Cowork プラグインは**「統合アプリ」ではなく、新しい「エージェント」画面**からアップロードする(UI 変更済み)。
ブラウザ自動化で実施する場合の既知の落とし穴(詳細は troubleshooting #18-20)。事前に把握してから進めると手戻りがない。
ブラウザ操作は VS Code 統合 Playwright ブラウザ(playwright-browser_navigate / playwright-browser_click / playwright-browser_handle_dialog 等)を使う
(Playwright MCP サーバー・Playwright 単体ブラウザは使わない → ブラウザ自動化方針)。
- Teams 開発者ポータル → 管理センターへの遷移で SSO 自動サインインが「Trying to sign you in」で止まることがある。数秒進まなければアカウントピッカーを探してクリックする。
- 「Choose file」は OS ネイティブのファイル選択ダイアログを開くため、通常の
playwright-browser_click では選択できない。
ウィザード内の input[type=file] に setInputFiles('<zip の絶対パス>') を直接設定するのが最も確実(ダイアログ不要)。
ボタンを押す場合はクリック後に playwright-browser_handle_dialog の paths にローカルの .zip の絶対パスを渡す。
- Publish to users / Install のラジオボタンは
<label> が pointer-events を奪っていることがあり、input への直接クリックはタイムアウトする。label[for=<id>] をクリックする。
- 統合ブラウザのタブが非表示だと
click が「visible, enabled and stable」待ちでタイムアウトする。
locator.evaluate(el => el.click()) で DOM クリックする(troubleshooting #27)。
- Microsoft 365 管理センター → 左ナビ エージェント(Agents) (
#/agents/all)
- Registry タブ → ツールバー右の More actions(…、Export の隣) → Add agent → Upload agent ウィザード起動
- Upload:
<name>.zip を選択→manifest 検証が走る(エラーが出たら troubleshooting 参照)
- Publish to users: 公開対象(All users / 特定ユーザー)と Install(None 推奨)を選択
- Apply template(Default で Next)→ Accept permissions(権限不要なら「No required permissions」)→ Review & finish → Publish
- 「You uploaded <name>」表示で完了 → Close。詳細パネルの Status が Available になる
(Cowork プラグインは Registry 一覧・
/fd/addins/api/agents には出ない。登録確認は Agents → Tools → Plugins で行う)
- Cowork(
https://m365.cloud.microsoft/cowork → Customize → Plugins)の Installed にプラグインが表示される。
トグルを ON にすると Connect ボタンに変わるので、ユーザー自身がクリックして Dataverse MCP の OAuth 接続を完了する
(自動クリックで開いた OAuth ポップアップはブラウザにブロックされる。troubleshooting #28)
アップロード検証でエラーバーが出たら、同じファイルを再選択しても再検証されない。
エラーバーを閉じてから zip を選び直すこと。
Step 9: Cowork で利用・初回同意
- Cowork の Customize → Plugins でプラグインを有効にし、スキルのトリガー語(例: 「年間レビュー資料を作って」)を入力
- 初回は Dataverse MCP コネクタの OAuth 同意が走る(Enterprise Token Store 経由)
- 同意後、
read_query 等が実行されデータ取得 → 資料生成
- 新しいタスクで
describe が実際に呼ばれることを確かめる。詳細画面の「接続済み」表示だけでは、実行時にツールが使える証明にならない。
ツールが 0 件なら check_oauth_signins.py で切り分け、トークン発行がすべて成功していれば Cowork 側の問題として扱い
(troubleshooting #46)、同じスキルを Copilot Studio のエージェントで動かす
(copilot-studio-v2 の agm-qa-author が例)。
Step 10: プラグインの更新(再公開)
スキル本文・manifest・アイコン等を変更したら、同じ id のまま再公開する。
更新経路は最初に登録した経路で決まる(troubleshooting #30)。
- manifest.json の
version をインクリメント(例: 1.0.2 → 1.0.3)。id は変更しない。
- zip を再ビルド(Step 7 と同じ。
dataverse-mcp-tools.json も忘れず含める)。
manage_agent_package_graph.py list に対象の manifest ID があれば Step 8 の deploy で更新する
(Graph の appDefinitions 更新。実績あり)。
- Graph に無い(管理センターのウィザードで登録した)場合、Add agent / Tools → Upload は
「The agent (tool) you are uploading has already been deployed.」で拒否される。private API で更新する
(Uninstall 不要。公開対象・利用者の Connect を維持する)。
- ログイン済み管理センターの同一 session で
stageCustomApp(page, { zipPath, actionType: "UPDATEAPP", productId: "<titleId>" }) を実行し、
latestVersion(新版)と mosOperationId を stage.json に保存する(titleId は Tools 詳細 URL の T_...)。
build_cowork_publish_payloads.py --mode update --stage-file stage.json --out-dir update-plan で payload を作り、
admin の manage_m365_portal_api.py agent-update-app --payload-file update-plan/1-update-app.json で dry-run → PLAN_HASH 承認 →
--expected-hash ... --apply → m365_portal_browser_runner.mjs で送信し、appsManagementStatus[].status=Success を確認する。
- Cowork の Customize → Plugins → 対象の Details で Version を読み戻す。
契約の詳細は admin の M365 テナント管理 API。
private API が使えない場合だけ、Agents → Tools → Plugins で Uninstall → Tools → Upload で登録し直す
(利用者は有効化と Connect をやり直す)。
注意:
- Cowork プラグインは Agents → All agents(Registry)の一覧には表示されない。状態・Version・Uninstall / Block は
Agents → Tools → Plugins の詳細パネルで確認・操作する。
id を変えると別プラグイン扱いになり、既存の公開設定・同意が引き継がれない。
- 継続的に更新するプラグインは、Graph で登録して読み戻せたものを正とする(更新も Graph で完結する)。
- 詳細パネルの Version / 説明文表示はテナント側キャッシュで反映が数分遅れることがある。
- 公開対象はウィザードで毎回選択する(前回設定は自動継承されない)。
検証チェックリスト
参考リンク