MCP Tasks未対応で長時間toolを実装する方法|Azure Durable Functionsのstart/poll pattern

数分かかるMCP toolをtimeoutさせずに提供するには、1回のtools/callで処理完了まで待たせてはいけません。開始用のstart_miningと結果取得用のget_mining_resultに分け、実処理をAzure Durable Functionsのorchestrationとして継続実行させます。

start_miningは短い待機予算内に完了すれば結果を返し、終わらなければworkflow_idpoll_after_secondsnextを返します。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/gettasks/cancelなどで長時間処理を標準的に管理できますが、clientとSDKの対応がそろうまでは、start/poll patternが現実的な互換策になります。(Model Context Protocol Blog)

目次

MCP Tasks未対応環境ではstart/poll patternを使う

start/poll patternのポイントは、MCP toolの呼び出し時間と、業務処理の実行時間を分離することです。

処理役割実行時間の目安clientへ返す内容
start_miningDurable orchestrationを開始する数秒~20秒程度完了結果、またはworkflow_id
Durable orchestration数分かかる実処理を継続する数分以上でも可Durable Functions側で状態を保持
get_mining_result現在の状態を取得する数秒以内runningcompletedfailednot_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 clienttool呼び出しの待機上限を超えた
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/updatetasks/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です。

主な処理は次のとおりです。

  1. 入力値を検証する
  2. workflow_idを生成する
  3. Durable orchestrationを開始する
  4. 一定時間だけ完了を待つ
  5. 完了していれば結果を返す
  6. 終わっていなければ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_foundworkflowが存在しないIDの推測をやめ、必要なら再開始

公式サンプルをローカルで動かす

Microsoftは.NET isolated worker版とPython版のサンプルを公開しています。両者の戻り値契約と動作はほぼ同じですが、待機方法が異なります。

項目.NETサンプルPythonサンプル
サンプルのruntime.NET 10Python 3.13
orchestration開始ScheduleNewOrchestrationInstanceAsyncclient.start_new
短時間の完了待ちcancellation token付きの完了待機get_statusの短いloop
状態取得DurableTaskClientDurableOrchestrationClient
MCP tool定義attributedecorator

これらは公式サンプルの前提であり、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-functionsazure-functions-durableを利用し、decoratorでMCP toolとDurable client bindingを定義しています。(GitHub)

inline完了とpoll経路の両方を試す

公式サンプルでは処理難易度を変更できるため、次の2経路を確認できます。

テスト設定例期待する結果
短時間完了難易度を18程度へ下げるstart_miningからcompletedが返る可能性が高い
長時間処理標準の難易度24runningworkflow_idが返り、pollが始まる
不正なID存在しないworkflow_idnot_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する可能性があります。

実務では、次の順序で決めます。

  1. 対応するMCP clientの最短timeoutを測定する
  2. cold startを含むserver応答時間を測定する
  3. 5~10秒程度の余裕を引く
  4. その残りを待機予算にする
  5. 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の自然言語を解析して動作を決めるべきではありません。statuspoll_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を追加する

公式の採掘サンプルは、基本的にrunningcompletedかを返す粗い進捗管理です。「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の契約は同じなので、既存システムと運用体制で選びます。

判断基準.NETPython
型安全なtool定義を重視適しているschema管理を丁寧に行う
既存のAzure Functions資産.NET中心なら有利Python中心なら有利
AI、データ処理library利用可能選択肢が多い
待機処理Durable client APIで簡潔status loopを明示しやすい
大規模な業務システム強い型とDIを活用しやすい開発速度を出しやすい
sampleからの変更量.NET sampleを直接拡張Python sampleを直接拡張

新規に検証するだけなら、担当者が日常的に扱っている言語を選ぶのが最も安全です。言語よりも、statusworkflow_idpoll_after_secondsの契約をclientとserverで一致させることが重要です。

MCP Tasksへ移行しやすい構成にする

MCP Tasksへ移行するときも、Durable orchestrationやactivityを作り直す必要はありません。変更の中心は、MCP clientへ返す長時間処理の表現です。

start/poll patternMCP Tasks移行後
start_miningからworkflow_idを返すtask handleを返す
get_mining_resultを呼ぶtasks/getを呼ぶ
独自のcancel tooltasks/cancel
独自のresume tooltasks/update
runningcompletedなどを独自定義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/gettasks/updatetasks/cancelでlifecycleを管理する設計です。(Model Context Protocol Blog)

導入前に確認するチェックリスト

実装を本番公開する前に、少なくとも次の項目を確認します。

  • 対応対象となるMCP clientのtimeoutを実測した
  • start_miningの待機予算をclient timeoutより十分短くした
  • inline完了とpoll完了の両方をテストした
  • workflow_idを入力schemaで必須にした
  • runningcompletedfailednot_foundを区別した
  • poll_after_secondsを構造化fieldとして返した
  • failednot_foundでpollが停止する
  • start再送時の二重起動対策を行った
  • workflow所有者の認可確認を実装した
  • 大きな結果をBlob Storageなどへ分離した
  • orchestration stateへ秘密情報を保存していない
  • activityの外部書き込みを冪等にした
  • workflow retentionと履歴削除方針を決めた
  • status照会回数と失敗率を監視できる
  • cancellationが必要か判断した
  • MCP Tasks対応後の移行境界を分離した

まとめ

MCP Tasks未対応環境で数分かかるMCP toolを提供するなら、同期toolのtimeoutを延長するのではなく、start_miningget_mining_resultへ分割します。

start_miningはAzure Durable Functionsのorchestrationを開始し、短い待機予算内に終われば結果を返します。終わらなければworkflow_idpoll_after_secondsnextを返し、clientは指定された間隔で結果をpollします。

最初に行うべき作業は、Microsoftの.NETまたはPython公式サンプルをローカルで起動し、次の4経路を確認することです。

  1. 待機予算内に完了するcompleted
  2. runningからpollして完了する経路
  3. 存在しないIDによるnot_found
  4. activity例外によるfailed

その後、採掘activityを実際の業務処理へ置き換え、Custom Status、認証、冪等性、大容量resultの外部保存を追加します。この順序なら、MCP clientの対応状況に左右されず長時間処理を提供でき、将来のMCP Tasks移行にも備えられます。

この記事を書いた人

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

コメント

コメントする

目次