数分かかるMCP toolをtimeoutさせずに提供するには、1回のtools/callで処理完了まで待たせてはいけません。開始用のstart_miningと結果取得用のget_mining_resultに分け、実処理をAzure Durable Functionsのorchestrationとして継続実行させます。
start_miningは短い待機予算内に完了すれば結果を返し、終わらなければworkflow_id、poll_after_seconds、nextを返します。MCP clientは指定された秒数を待ってからget_mining_resultを呼び、completedになるまでpollします。Microsoftは、MCP Tasksに未対応のclient向け回避策として、このstart/poll patternの.NET版とPython版サンプルを公開しています。(Microsoft for Developers)
2026年7月19日時点では、MCPプロトコル2026-07-28版のTasksはrelease candidateです。Tasksを利用するとtask handleやtasks/get、tasks/cancelなどで長時間処理を標準的に管理できますが、clientとSDKの対応がそろうまでは、start/poll patternが現実的な互換策になります。(Model Context Protocol Blog)
MCP Tasks未対応環境ではstart/poll patternを使う
start/poll patternのポイントは、MCP toolの呼び出し時間と、業務処理の実行時間を分離することです。
| 処理 | 役割 | 実行時間の目安 | clientへ返す内容 |
|---|---|---|---|
start_mining | Durable orchestrationを開始する | 数秒~20秒程度 | 完了結果、またはworkflow_id |
| Durable orchestration | 数分かかる実処理を継続する | 数分以上でも可 | Durable Functions側で状態を保持 |
get_mining_result | 現在の状態を取得する | 数秒以内 | running、completed、failed、not_found |
処理の流れは次のようになります。
MCP client
│
├─ start_mining
│ │
│ ├─ Durable orchestrationを開始
│ │
│ ├─ 待機予算内に完了
│ │ └─ completedと結果を返す
│ │
│ └─ 待機予算を超過
│ └─ workflow_idとpoll_after_secondsを返す
│
├─ 指定秒数だけ待機
│
├─ get_mining_result(workflow_id)
│ ├─ running → 再度待ってpoll
│ ├─ completed → 結果を利用
│ ├─ failed → pollを終了
│ └─ not_found → IDを確認し、必要なら再実行
│
└─ 終了
重要なのは、start_miningが待機を打ち切っても、Durable orchestration自体は終了しない点です。Durable Functionsはworkflowの状態、checkpoint、activityの結果などを永続化し、Functions hostの再起動やスケール変更があっても処理を継続できるように設計されています。(Microsoft Learn)
数分かかるMCP toolがtimeoutする理由
従来のMCP toolは、基本的にrequest/response型です。clientがtoolを呼び出した後、serverが結果を返すまで同じ接続と呼び出しを維持します。
しかし、実際のtimeoutはMCP serverだけで決まりません。
| timeoutの発生箇所 | 典型的な原因 |
|---|---|
| MCP client | tool呼び出しの待機上限を超えた |
| AIエージェント基盤 | 1回のtool実行時間に上限がある |
| リバースプロキシ | HTTP requestの待機時間を超えた |
| Functionsフロントエンド | 接続やrequestの制限に達した |
| 利用者側ネットワーク | 切断、再接続、ブラウザ終了が発生した |
Microsoftの解説では、MCP clientのtimeoutは標準化されておらず、30~60秒程度で打ち切られる環境もあるとされています。そのため、Functions側で数分動作できるように設定しても、MCP clientが先に切断すれば結果を返せません。(Microsoft for Developers)
start/poll patternでは、最初の呼び出しをclientのtimeoutより十分短く終わらせます。長時間処理はDurable orchestrationへ移し、結果確認だけを短いMCP toolとして繰り返します。
MCP Tasksは本命だが、未対応clientには返せない
MCP Tasksは長時間処理を扱うための標準的な仕組みです。serverはtool呼び出しに対してtask handleを返し、clientはtasks/getで状態を確認できます。必要に応じてtasks/updateやtasks/cancelも利用できます。
ただし、TasksはMCP extensionです。clientはTasksに対応していることをcapabilityとして明示する必要があり、未対応clientに対してserverがTaskを返すことは想定されていません。(Model Context Protocol Blog)
したがって、実装方式はclientの対応状況で判断します。
| 利用環境 | 推奨方式 |
|---|---|
| 数秒で必ず完了する | 通常の同期MCP tool |
| 数分かかり、clientがTasks未対応 | start/poll pattern |
| clientとserverの双方がTasks対応 | MCP Tasks |
| 実行途中で利用者の追加入力が必要 | MCP Tasks、または独自のresume tool |
| 大量データを逐次配信したい | start/pollに加えてartifact storageやstreamingを検討 |
当面はstart/pollとMCP Tasksの両方を実装し、client capabilityに応じて切り替えられる構成が安全です。
Azure Durable Functionsで構成する要素
実装は、次の4要素に分けると整理しやすくなります。
start_mining
最初に呼ばれるMCP toolです。
主な処理は次のとおりです。
- 入力値を検証する
workflow_idを生成する- Durable orchestrationを開始する
- 一定時間だけ完了を待つ
- 完了していれば結果を返す
- 終わっていなければ
runningを返す
Microsoftの.NETサンプルでは、標準の待機予算が20秒、poll間隔が5秒に設定されています。待機中に完了した場合は結果をその場で返し、時間を超えた場合はworkflow_idを返します。(GitHub)
Durable orchestrator
処理全体の順序を管理します。
たとえば、採掘処理を模した公式サンプルでは、orchestratorが複数のblock処理を順番にactivityへ委譲し、最後にレポートを生成します。CPU負荷の高い処理や外部通信はorchestratorに直接書かず、activity functionで実行します。(GitHub)
実際のシステムでは、次のような処理へ置き換えられます。
- 大量文書の解析
- レポート生成
- AIモデルによる複数段階の推論
- データのimport、変換、export
- 複数APIをまたぐ業務処理
- 動画や画像の変換
- バックアップや棚卸し処理
Activity Functions
外部API呼び出し、ファイル操作、データベース更新、CPUを使う計算などを担当します。
activityを細かく分割すると、どこで失敗したかを確認しやすくなり、retry対象も限定できます。ただし、retryされても二重登録や二重課金が起きないよう、外部への書き込みは冪等性を持たせる必要があります。
get_mining_result
workflow_idを受け取り、Durable orchestrationの現在状態を照会するMCP toolです。
公式サンプルでは、状態を次の4種類へ変換します。(Microsoft for Developers)
status | 意味 | clientの動作 |
|---|---|---|
completed | 正常終了 | resultを利用して終了 |
running | 処理中 | 指定秒数後に再度poll |
failed | 失敗または強制終了 | pollを終了し、エラーを表示 |
not_found | workflowが存在しない | IDの推測をやめ、必要なら再開始 |
公式サンプルをローカルで動かす
Microsoftは.NET isolated worker版とPython版のサンプルを公開しています。両者の戻り値契約と動作はほぼ同じですが、待機方法が異なります。
| 項目 | .NETサンプル | Pythonサンプル |
|---|---|---|
| サンプルのruntime | .NET 10 | Python 3.13 |
| orchestration開始 | ScheduleNewOrchestrationInstanceAsync | client.start_new |
| 短時間の完了待ち | cancellation token付きの完了待機 | get_statusの短いloop |
| 状態取得 | DurableTaskClient | DurableOrchestrationClient |
| MCP tool定義 | attribute | decorator |
これらは公式サンプルの前提であり、Azure Functions全体の対応runtimeを意味するものではありません。package versionは変更される可能性があるため、手動で組み合わせるより、サンプルのプロジェクトファイルを基準に開始する方が安全です。(GitHub)
.NETサンプルの起動
必要な主な環境は次のとおりです。
- .NET 10 SDK
- Azure Functions Core Tools v4
- Azurite
- Azure Developer CLI
- MCP対応client
Azuriteを起動した後、Functionsを実行します。
azurite --skipApiVersionCheck --silent --location ./.azurite
cd src
func start
ローカルのMCP endpointは次の形式です。
http://localhost:7071/runtime/webhooks/mcp
公式サンプルにはMCP client登録用の設定例も含まれているため、まずはその設定を利用して接続確認を行います。(GitHub)
Pythonサンプルの起動
Python版では仮想環境を作成し、依存packageをinstallします。
cd src
python -m venv .venv
# Windows
.venv\Scripts\activate
pip install -r requirements.txt
func start
別のterminalでAzuriteも起動しておきます。
azurite --skipApiVersionCheck --silent --location ./.azurite
Pythonサンプルはazure-functionsとazure-functions-durableを利用し、decoratorでMCP toolとDurable client bindingを定義しています。(GitHub)
inline完了とpoll経路の両方を試す
公式サンプルでは処理難易度を変更できるため、次の2経路を確認できます。
| テスト | 設定例 | 期待する結果 |
|---|---|---|
| 短時間完了 | 難易度を18程度へ下げる | start_miningからcompletedが返る可能性が高い |
| 長時間処理 | 標準の難易度24 | runningとworkflow_idが返り、pollが始まる |
| 不正なID | 存在しないworkflow_id | not_found |
| activity失敗 | 意図的に例外を発生 | failed |
| host再起動 | 処理中にFunctionsを再起動 | 永続化された状態から継続または復旧 |
実行時間はPC性能によって変わるため、特定の難易度で必ず同じ結果になるとは限りません。待機予算を短くする方法でも、poll経路を確実にテストできます。(GitHub)
start_miningの実装ポイント
待機予算を設ける
.NET版の中心部分は、orchestration開始後にcancellation token付きで完了を待つ処理です。概念的には次の形になります。
var workflowId = CreateWorkflowId();
await durableClient.ScheduleNewOrchestrationInstanceAsync(
orchestratorName,
input,
new StartOrchestrationOptions(workflowId));
using var budget = new CancellationTokenSource(
TimeSpan.FromSeconds(waitBudgetSeconds));
try
{
var state = await durableClient.WaitForInstanceCompletionAsync(
workflowId,
getInputsAndOutputs: true,
budget.Token);
return MapToToolResponse(state);
}
catch (OperationCanceledException)
{
return CreateRunningResponse(
workflowId,
pollAfterSeconds: 5);
}
待機予算を超えたことは、workflowの失敗ではありません。ここでorchestrationを停止せず、runningを返すことが重要です。.NET公式サンプルも、完了待ちだけを打ち切り、workflowはバックグラウンドで継続する構造です。(GitHub)
Python版では、一定時間だけstatusを確認するloopを実行します。
instance_id = await client.start_new(
"run_orchestrator",
client_input=payload,
)
deadline = time.monotonic() + WAIT_BUDGET_SECONDS
while time.monotonic() < deadline:
state = await client.get_status(instance_id)
if is_terminal(state):
return map_to_tool_response(state)
await asyncio.sleep(1)
return create_running_response(
instance_id,
poll_after_seconds=5,
)
Python公式サンプルも、待機予算を超えた時点でrunningを返し、後続のget_mining_resultへ処理を引き継ぎます。(GitHub)
待機予算はclient timeoutより短くする
公式サンプルの20秒は、そのまま全環境に適用すべき固定値ではありません。
たとえば、client timeoutが30秒なら、network遅延、認証、JSON変換、Functionsのcold startを考慮し、待機予算を15~20秒程度に抑えます。待機予算を29秒にすると、orchestrationの状態を返す直前にclient側がtimeoutする可能性があります。
実務では、次の順序で決めます。
- 対応するMCP clientの最短timeoutを測定する
- cold startを含むserver応答時間を測定する
- 5~10秒程度の余裕を引く
- その残りを待機予算にする
- timeout件数を監視しながら調整する
clientごとにtimeoutが異なる場合は、最も短い環境に合わせるか、client種別ごとに待機予算を変えます。
戻り値は文章ではなく構造化する
start/poll patternを安定させるには、AIへの説明文だけでなく、clientが機械的に判定できるfieldを返す必要があります。
処理中の戻り値
{
"status": "running",
"workflow_id": "a497839e-3358-4e79-a80a-b95a3a427f73",
"poll_after_seconds": 5,
"next": "5秒後に、このworkflow_idを指定してget_mining_resultを呼び出してください"
}
各fieldの役割は明確に分けます。
status:programやagentが次の動作を判断するworkflow_id:同一workflowを照会するpoll_after_seconds:次回照会までの推奨待機時間next:LLMに次のtool操作を説明する
programmatic clientは、nextの自然言語を解析して動作を決めるべきではありません。statusとpoll_after_secondsを利用して分岐し、nextはAIエージェント向けの補助情報として扱います。
完了時の戻り値
{
"status": "completed",
"workflow_id": "a497839e-3358-4e79-a80a-b95a3a427f73",
"result": {
"artifact_id": "report-20260719-001",
"summary": "処理が正常に完了しました"
}
}
失敗時の戻り値
{
"status": "failed",
"workflow_id": "a497839e-3358-4e79-a80a-b95a3a427f73",
"reason": "activity_failed",
"error": "データ変換処理に失敗しました"
}
failedを返した後にpollを続けさせてはいけません。再実行可能かどうかを示したい場合は、retryableなどのfieldを追加します。
ただし、内部のstack trace、接続文字列、storage account名、個人情報をerrorへそのまま含めないようにしてください。
workflowが見つからない場合
{
"status": "not_found",
"workflow_id": "a497839e-3358-4e79-a80a-b95a3a427f73",
"reason": "workflow_not_found",
"next": "workflow_idを推測せず、必要に応じてstart_miningから再実行してください"
}
LLMは過去の会話からIDを誤って再構成する場合があります。Microsoftのサンプルがnot_foundを独立した状態として返すのは、存在しないIDでpollを繰り返させないためです。(Microsoft for Developers)
get_mining_resultではworkflow_idを必須にする
get_mining_resultのinput schemaでは、workflow_idをrequiredにします。
概念的なschemaは次のとおりです。
{
"type": "object",
"properties": {
"workflow_id": {
"type": "string",
"description": "start_miningから返されたworkflow_id"
}
},
"required": [
"workflow_id"
],
"additionalProperties": false
}
.NETサンプルではMCP tool propertyのisRequiredを有効にし、Pythonサンプルでもdecoratorで必須propertyとして定義しています。(GitHub)
さらに、tool descriptionにも次のルールを明記します。
start_miningを呼ぶ前にget_mining_resultを呼ばないworkflow_idを生成、修正、短縮しないrunningの場合だけ再度pollするfailedまたはnot_foundの場合はpollを終了する- 必ず最新レスポンスの
poll_after_secondsを利用する
MCP client側のpoll処理
client側は、次のような単純な状態機械として実装できます。
response = call start_mining(arguments)
while response.status == "running":
wait(response.poll_after_seconds)
response = call get_mining_result({
"workflow_id": response.workflow_id
})
if response.status == "completed":
use response.result
else if response.status == "failed":
show response.error
else if response.status == "not_found":
stop polling
ask whether to start a new workflow
ここで、poll_after_secondsはserverからの推奨値として扱います。clientが毎秒pollする設計にすると、同時workflow数が増えたときに、実処理よりstatus照会の方が多くなります。
一方、必ず指定秒数ちょうどに呼ぶ必要はありません。ブラウザがbackgroundになった場合やagentが別のtoolを使用している場合など、遅れてpollしても同じ状態を取得できるようにします。
進捗を返すならCustom Statusを追加する
公式の採掘サンプルは、基本的にrunningかcompletedかを返す粗い進捗管理です。「4工程中2工程が完了」「変換処理が60%」といった情報を返したい場合は、Durable FunctionsのCustom Statusを利用します。
Custom Statusには任意のJSON metadataを設定でき、実行中のorchestrationを外部から照会できます。.NETではSetCustomStatus、Pythonではset_custom_statusを利用します。Custom Statusの上限はUTF-16 JSONで16KBであるため、進捗の要約だけを保存します。(Microsoft Learn)
.NETの例
context.SetCustomStatus(new
{
phase = "mining",
completed = currentBlock,
total = totalBlocks,
percent = currentBlock * 100 / totalBlocks
});
Pythonの例
context.set_custom_status({
"phase": "mining",
"completed": current_block,
"total": total_blocks,
"percent": current_block * 100 // total_blocks,
})
get_mining_resultではCustom Statusを読み取り、runningの戻り値へ追加します。
{
"status": "running",
"workflow_id": "a497839e-3358-4e79-a80a-b95a3a427f73",
"poll_after_seconds": 5,
"progress": {
"phase": "mining",
"completed": 2,
"total": 4,
"percent": 50
},
"next": "5秒後にget_mining_resultを再実行してください"
}
Custom Statusは詳細ログではなく、現在状態のスナップショットとして使います。過去の全工程を保存したい場合はApplication Insights、データベース、Blob Storageなどへ別途記録します。
最終結果が大きい場合はBlob Storageへ分離する
Durable Functionsはorchestratorの入力、出力、activityの結果、実行履歴をtask hubへ永続化します。数MB以上の文書、画像、動画、大量のJSONをそのままorchestration outputへ格納すると、履歴の肥大化やmemory使用量の増加につながります。Microsoftも、大きなpayloadは外部storageへ置き、orchestrationには参照情報だけを渡す方法を推奨しています。(Microsoft Learn)
実務では次の形にします。
{
"status": "completed",
"workflow_id": "a497839e-3358-4e79-a80a-b95a3a427f73",
"result": {
"artifact_id": "report-20260719-001",
"content_type": "application/pdf",
"size_bytes": 1842300,
"download_expires_at": "2026-07-19T12:00:00Z"
}
}
実ファイルはBlob Storageへ保存し、認証済みAPI、短時間のSAS、または別のMCP resourceを通して取得させます。恒久的な公開URLをtool responseへ含める方法は避けます。
機密情報についても同様です。orchestration inputやoutputは永続化されるため、access token、password、秘密鍵、個人情報を直接渡さないようにします。必要な秘密情報はactivity内でKey Vaultなどから取得します。(Microsoft Learn)
Azureへdeployする
公式サンプルはAzure Developer CLIに対応しており、repository rootから次のcommandでdeployできます。
azd up
deploy後のMCP endpointは次の形式です。
https://<function-app-name>.azurewebsites.net/runtime/webhooks/mcp
公式サンプルでは、ローカル環境はAzuriteを使ったAzure Storage backend、Azure環境はDurable Task Schedulerを使う構成です。Durable Task SchedulerはAzureでの運用に推奨されるmanaged backendで、managed identity、RBAC、監視用dashboardなどを利用できます。(GitHub)
system keyで接続する場合
既定のkey認証では、mcp_extensionというsystem keyを取得し、x-functions-key headerへ設定します。
az functionapp keys list \
--resource-group <resource-group-name> \
--name <function-app-name> \
--query "systemKeys.mcp_extension" \
--output tsv
MCP client側では、概念的に次のように登録します。
{
"servers": {
"long-running-tools": {
"type": "http",
"url": "https://<function-app-name>.azurewebsites.net/runtime/webhooks/mcp",
"headers": {
"x-functions-key": "${input:mcp-functions-key}"
}
}
}
}
system keyは簡単に接続確認できる一方、共有秘密として扱う必要があります。source codeや公開repositoryへ直接書き込まず、client側のsecret storageや環境変数を利用します。(GitHub)
本番環境ではEntra ID認証を検討する
Azure FunctionsのMCP serverでは、App Service Authenticationを利用したbuilt-in MCP authenticationも提供されています。Microsoft Entra IDをidentity providerとして設定でき、既定のkey認証より利用者単位の認証やaccess制御を実装しやすくなります。(Microsoft Learn)
ただし、認証できたことと、任意のworkflowを参照してよいことは別問題です。get_mining_resultでは、次の情報を照合します。
- workflowの所有user
- tenant ID
- application ID
- roleやscope
- 対象resourceへの権限
workflow_idは識別子であり、認証tokenではありません。他人のworkflow_idを入手しただけで結果を閲覧できる実装にしないでください。
実運用で失敗しやすいポイント
start呼び出しの再送でworkflowが二重起動する
clientがstart_miningを呼び出した直後にnetwork切断すると、server側では開始済みでも、clientは結果を受け取れません。その後clientが同じtoolを再送すると、同じ処理が二重に起動します。
外部課金、メール送信、注文処理、データ更新を含む場合は、入力にidempotency_keyを追加します。
{
"target": "2026-07-report",
"idempotency_key": "report-request-000123"
}
server側では、認証済みtenant IDとidempotency_keyを組み合わせて重複を確認します。既存workflowがあれば、新規作成せず同じworkflow_idを返します。
単純に利用者が指定した文字列をDurable instance IDへ流用すると、衝突や情報漏えいの原因になります。hash化や対応表の保存を検討してください。
固定5秒pollでstatus照会が集中する
公式サンプルのpoll_after_seconds: 5は理解しやすい初期値ですが、数千workflowが同時実行される環境ではpollが集中します。
実務では、処理状況に応じて間隔を変えます。
開始直後 3~5秒
通常処理中 10~15秒
待機工程中 30~60秒
完了見込み間近 3~5秒
多数のclientが同時に動く場合は、少量のjitterも追加します。
実際の待機時間 =
poll_after_seconds + 0~2秒のランダム値
ただし、LLM任せでは正確に実装されないことがあります。jitterやbackoffが必要な環境では、MCP clientまたはagent基盤側のprogram codeで制御します。
nextだけでpoll手順を伝える
nextはAIにとって有効ですが、自然言語なので解釈が変わる可能性があります。
次のような戻り値は避けます。
{
"message": "まだ処理中です。後でもう一度試してください"
}
これでは、どのtoolを、何秒後に、どのIDで呼ぶか分かりません。
最低でも次の3項目を構造化します。
{
"status": "running",
"workflow_id": "...",
"poll_after_seconds": 5
}
そのうえでnextを補助的に付けます。
not_foundでもpollを続ける
workflowのretention期間切れ、環境の取り違え、誤ったID、storage backendの変更などにより、workflowが見つからない場合があります。
not_foundは一時的なrunningとして扱わず、pollを停止します。利用者へ再実行するか確認し、新しいstart_miningを呼びます。
なお、Durable Functionsではstorage provider間で既存orchestrationの状態をそのまま移行できません。backendを変更する場合は、既存workflowの完了を待つか、新しいFunction Appへ切り替える計画が必要です。(Microsoft Learn)
orchestrator内で外部APIを直接呼ぶ
Durable orchestratorはreplayされることを前提とした決定的なcodeである必要があります。
次の処理はactivityへ移します。
- HTTP API呼び出し
- database更新
- file入出力
- random値の生成
- CPU負荷の高い計算
- 通常のsystem clockへの依存
- メールや通知の送信
orchestratorは「どのactivityをどの順番で実行するか」の管理に集中させます。公式サンプルも、採掘計算をactivityへ分離しています。(GitHub)
cancellationを考慮していない
start/poll patternだけでは、MCP Tasksのtasks/cancelに相当する標準的なcancel操作は得られません。
数十分以上かかる処理や課金が発生する処理では、必要に応じてcancel_miningを追加します。
cancel_mining(workflow_id)
cancel toolにも、結果取得と同じ所有者確認が必要です。また、外部システムですでに実行された副作用まで自動的に戻るわけではありません。必要なら補償処理を別activityとして設計します。
実行途中の追加入力が必要になる
start/poll patternは、開始後に自動で完了まで進められる処理に向いています。
途中で「この候補のどちらを採用するか」「追加ファイルをアップロードするか」と利用者へ確認するworkflowでは、次のいずれかが必要です。
- Durable Functionsのexternal eventと独自resume toolを組み合わせる
input_requiredを扱えるMCP Tasksへ移行する- workflowを複数の短い処理へ分割する
対話的なworkflowを無理にrunningだけで表すと、利用者入力待ちなのか、server処理中なのか区別できなくなります。
.NETとPythonはどちらを選ぶべきか
start/poll patternの契約は同じなので、既存システムと運用体制で選びます。
| 判断基準 | .NET | Python |
|---|---|---|
| 型安全なtool定義を重視 | 適している | schema管理を丁寧に行う |
| 既存のAzure Functions資産 | .NET中心なら有利 | Python中心なら有利 |
| AI、データ処理library | 利用可能 | 選択肢が多い |
| 待機処理 | Durable client APIで簡潔 | status loopを明示しやすい |
| 大規模な業務システム | 強い型とDIを活用しやすい | 開発速度を出しやすい |
| sampleからの変更量 | .NET sampleを直接拡張 | Python sampleを直接拡張 |
新規に検証するだけなら、担当者が日常的に扱っている言語を選ぶのが最も安全です。言語よりも、status、workflow_id、poll_after_secondsの契約をclientとserverで一致させることが重要です。
MCP Tasksへ移行しやすい構成にする
MCP Tasksへ移行するときも、Durable orchestrationやactivityを作り直す必要はありません。変更の中心は、MCP clientへ返す長時間処理の表現です。
| start/poll pattern | MCP Tasks移行後 |
|---|---|
start_miningからworkflow_idを返す | task handleを返す |
get_mining_resultを呼ぶ | tasks/getを呼ぶ |
| 独自のcancel tool | tasks/cancel |
| 独自のresume tool | tasks/update |
running、completedなどを独自定義 | Taskの標準statusへ変換 |
移行を見据えるなら、MCP layerとworkflow layerを分離します。
MCP adapter
├─ start/poll adapter
└─ MCP Tasks adapter
│
▼
Workflow service
│
▼
Azure Durable Functions
clientがTasks capabilityをadvertiseしている場合はTaskを返し、未対応clientには従来のstart/poll responseを返します。Tasksではserverがtask creationを判断し、clientがtasks/get、tasks/update、tasks/cancelでlifecycleを管理する設計です。(Model Context Protocol Blog)
導入前に確認するチェックリスト
実装を本番公開する前に、少なくとも次の項目を確認します。
- 対応対象となるMCP clientのtimeoutを実測した
start_miningの待機予算をclient timeoutより十分短くした- inline完了とpoll完了の両方をテストした
workflow_idを入力schemaで必須にしたrunning、completed、failed、not_foundを区別したpoll_after_secondsを構造化fieldとして返したfailedとnot_foundでpollが停止する- start再送時の二重起動対策を行った
- workflow所有者の認可確認を実装した
- 大きな結果をBlob Storageなどへ分離した
- orchestration stateへ秘密情報を保存していない
- activityの外部書き込みを冪等にした
- workflow retentionと履歴削除方針を決めた
- status照会回数と失敗率を監視できる
- cancellationが必要か判断した
- MCP Tasks対応後の移行境界を分離した
まとめ
MCP Tasks未対応環境で数分かかるMCP toolを提供するなら、同期toolのtimeoutを延長するのではなく、start_miningとget_mining_resultへ分割します。
start_miningはAzure Durable Functionsのorchestrationを開始し、短い待機予算内に終われば結果を返します。終わらなければworkflow_id、poll_after_seconds、nextを返し、clientは指定された間隔で結果をpollします。
最初に行うべき作業は、Microsoftの.NETまたはPython公式サンプルをローカルで起動し、次の4経路を確認することです。
- 待機予算内に完了する
completed runningからpollして完了する経路- 存在しないIDによる
not_found - activity例外による
failed
その後、採掘activityを実際の業務処理へ置き換え、Custom Status、認証、冪等性、大容量resultの外部保存を追加します。この順序なら、MCP clientの対応状況に左右されず長時間処理を提供でき、将来のMCP Tasks移行にも備えられます。

コメント