Model Context Protocol に新しい仕様、MCP 2026-07-28 が登場しました。Streamable HTTP 以来、プロトコルにとって最大の変更です。セッションのハンドシェイクはなくなり、各リクエストはサーバーが応答に必要とする情報をすべて持ち運び、長時間または対話的な処理も第一級のサポートを受けられるようになりました。sgcWebSockets 2026.10.0 は、Delphi と C++Builder 向けの MCP サーバーと MCP クライアントの両方でこれを実装しています。
すでに Delphi の MCP サーバーを運用している方にとって重要な点は、何も壊れないということです。サーバーは2つの世代に対応しています。VS Code や Claude のように 2025-11-25 を話すクライアントは、これまでどおり initialize を呼び出してセッションを取得できる一方、2026-07-28 クライアントは同じエンドポイント上で新しいステートレスモデルを使用します。切り替えスイッチも2台目のサーバーも必要ありません。
MCP 2026-07-28 での変更点
サーバーやクライアントを構築する際に重要となる変更点を、平易に説明します。
- ステートレスなリクエスト。
initializeハンドシェイクもMcp-Session-Idもありません。各リクエストは、プロトコルバージョン、クライアント情報、クライアントの機能を_metaの中に持ち運ぶため、ロードバランサーの背後にあるどのサーバーインスタンスでも応答できます。 - server/discover。 サーバーが提供する機能を知りたいクライアントは
server/discoverを呼び出し、サポートされているバージョン、機能、指示を受け取ります。結果はキャッシュ可能で、キャッシュのヒント(ttlMs、cacheScope)も含まれます。 - subscriptions/listen。 従来の GET ストリームは、1本のストリーミングリクエストに置き換えられました。クライアントは必要な通知(tools、prompts、resources のリスト変更、resource の更新)を指定し、サーバーはそのストリーム上でそれらをプッシュします。
- 複数往復リクエスト(MRTR)。 tool、prompt、resource のハンドラーが、入力を求めてクライアントを呼び返すことはもうありません。代わりに質問とともに
input_requiredを返し、クライアントが回答した上で、その回答と署名済みのrequestStateを添えて呼び出しを再試行します。 - Tasks 拡張。 時間のかかる tool 呼び出しは、即座にタスクハンドルを返すことができます。クライアントは
tasks/getでポーリングし、tasks/updateで質問に回答し、tasks/cancelで停止させます。 - ルーティングヘッダー。 HTTP リクエストには
MCP-Protocol-Version、Mcp-Method、Mcp-Nameヘッダー(x-mcp-headerでマークされた tool 引数用のMcp-Param-*も含む)が付与されるため、ゲートウェイはボディを解析せずにルーティングとフィルタリングを行えます。サーバー側はこれらをボディと照合してチェックします。 - クライアント認可。 クライアント向けの OAuth フローが強化されました。保護されたリソースのメタデータ検出、Client ID Metadata Documents、リソースインジケーターを伴う PKCE、RFC 9207 の
iss検証です。
Delphi における2世代対応の MCP サーバー
TsgcWSAPIServer_MCP コンポーネントは、リクエストごとに世代を検出します。_meta が 2026-07-28 を示すリクエストはステートレスな経路を通り、initialize や旧バージョンはこれまでどおりのセッション経路を通ります。tool、prompt、resource の各ハンドラーは両方で共有されます。新しいオプションは、2026-07-28 クライアントが server/discover やキャッシュのヒントから読み取る内容を補うだけです。
procedure TMainForm.FormCreate(Sender: TObject);
begin
MCPServer.MCPOptions.ServerInfo.Name := 'tickets-mcp';
// returned by server/discover to 2026-07-28 clients
MCPServer.MCPOptions.Instructions := 'Use search_tickets before opening a ticket.';
// cache hints returned with 2026-07-28 results (ttlMs, cacheScope)
MCPServer.MCPOptions.Cache.TTLMs := 60000;
MCPServer.MCPOptions.Cache.Scope := aimcpcsPublic;
MCPServer.Active := True;
end;
ServerInfo.Name を設定すると、2026-07-28 の結果にもサーバー情報が _meta に含まれるようになります。プロトコルエラーには新しいコードが使われます。ヘッダーとボディが一致しない場合は -32020、クライアントの機能が不足している場合は -32021、サポートされていないバージョンの場合は -32022 で、エラーデータにはサポートされているバージョンの一覧が含まれます。
subscriptions/listen によるサブスクリプション
サブスクリプションはデフォルトで有効です。2026-07-28 クライアントは1本の subscriptions/listen ストリームを開き、すでに SendNotificationToolsListChanged や SendNotificationResourcesUpdated で送信している通知は、2025-11-25 セッションだけでなくこれらのリスナーにも届きます。サーバーはアイドル状態のストリームに keep-alive コメントを送信し、サーバーが非アクティブ化されると、開いているすべてのサブスクリプションが最終結果とともに正常にクローズされます。
procedure TMainForm.FormCreate(Sender: TObject);
begin
MCPServer.MCPOptions.Subscriptions.Enabled := True;
MCPServer.MCPOptions.Subscriptions.KeepAliveInterval := 15000; // 0 disables it
end;
procedure TMainForm.ToolsChanged;
begin
// delivered to legacy sessions and to every subscriptions/listen stream
MCPServer.SendNotificationToolsListChanged;
end;
複数往復リクエスト: ユーザーに名前を尋ねる
MRTR では、ハンドラーは aResponse.InputRequired を設定して戻ることで、追加の入力を求めます。クライアントは回答を収集し、再び tool を呼び出します。2回目のラウンドでは、回答は aRequest.InputResponse で読み取ります。以下の例は、サーバーデモの ask_name tool です。
procedure TMainForm.MCPServerMCPRequestTool(Sender: TObject;
const aSession: TsgcAI_MCP_Session;
const aRequest: TsgcAI_MCP_Request_ToolsCall;
const aResponse: TsgcAI_MCP_Response_ToolsCall);
begin
if aRequest.Params.Name = 'ask_name' then
begin
if not aRequest.HasInputResponse('name') then
begin
// first round: ask the client for the user's name
aResponse.InputRequired.AddElicitation('name', 'What is your name?',
'{"type":"object","properties":{"name":{"type":"string"}},' +
'"required":["name"]}');
Exit;
end;
// second round: the answer arrives as raw JSON
aResponse.Result.Content.AddText('Hello, ' + aRequest.InputResponse('name'));
end;
end;
requestState は MCPOptions.MRTR.Secret を使って HMAC-SHA256 で署名された状態でやり取りされ、MCPOptions.MRTR.StateTTL 秒後に失効するため、クライアントが改ざんすることはできません。URL elicitation(AddElicitationURL)、sampling(AddSampling)、roots(AddRoots)も同じパターンに従います。input_required に応答する前に、サーバーはクライアントが対応する機能を宣言していることを確認します。
Tasks 拡張: long_job tool
MCPOptions.Tasks で tasks を有効にし、tool ハンドラーから CreateTask を呼び出します。クライアントがそのリクエストで io.modelcontextprotocol/tasks 拡張を宣言していた場合、呼び出しはただちにタスク ID を返し、あとはコード側が独自のスレッドで処理を完了させます。tasks が無効な場合、リクエストが 2025-11-25 の場合、またはクライアントが拡張を宣言していない場合、CreateTask は nil を返すため、それらのクライアント向けに同期的な経路も残しておいてください。
procedure TMainForm.FormCreate(Sender: TObject);
begin
MCPServer.MCPOptions.Tasks.Enabled := True;
MCPServer.MCPOptions.Tasks.TTL := 3600000;
MCPServer.MCPOptions.Tasks.PollInterval := 1000;
end;
procedure TMainForm.MCPServerMCPRequestTool(Sender: TObject;
const aSession: TsgcAI_MCP_Session;
const aRequest: TsgcAI_MCP_Request_ToolsCall;
const aResponse: TsgcAI_MCP_Response_ToolsCall);
var
oTask: TsgcAI_MCP_Task;
begin
if aRequest.Params.Name = 'long_job' then
begin
oTask := MCPServer.CreateTask(aSession, aResponse);
if Assigned(oTask) then
TLongJobThread.Create(oTask) // runs the job, see below
else
aResponse.Result.Content.AddText(RunLongJob);
end;
end;
procedure TLongJobThread.Execute;
var
i: Integer;
oResponse: TsgcAI_MCP_Response_ToolsCall;
begin
for i := 1 to 3 do
begin
if FTask.IsCancelled then
Break;
DoStep(i);
FTask.SetStatusMessage(Format('long_job step %d of 3', [i]));
end;
if FTask.IsCancelled then
FTask.Fail(CS_AI_MCP_INTERNAL_ERROR, 'Cancelled by the client')
else
begin
oResponse := TsgcAI_MCP_Response_ToolsCall.Create;
try
oResponse.Result.Content.AddText('long_job finished after 3 steps');
FTask.Complete(oResponse);
finally
oResponse.Free;
end;
end;
end;
タスクオブジェクトはスレッドセーフです。クライアントが tasks/cancel を呼び出すと OnMCPTaskCancel が発火し、入力待ちのタスクに回答すると OnMCPTaskUpdate が発火します。タスクはそれを作成したプリンシパルに紐づけられ、TTL ミリ秒後に失効します。
MCP クライアント: ProtocolEra と Discover
TsgcWSAPIClient_MCP コンポーネントに、新しい MCPOptions.ProtocolEra プロパティが追加されました。aimcpeAuto のままにしておくと、クライアントは server/discover でサーバーを調べ、サーバーが対応していれば 2026-07-28 を使用し、対応していなければ initialize ハンドシェイクにフォールバックします。ネゴシエートされた世代はエンドポイントごとにキャッシュされます。aimcpeModern または aimcpeLegacy を設定すれば、どちらかを強制できます。
procedure TMainForm.Connect;
begin
MCPClient.MCPOptions.ProtocolEra := aimcpeAuto;
if MCPClient.Initialize then
begin
MemoLog.Lines.Add('protocol: ' + MCPClient.NegotiatedProtocolVersion);
if MCPClient.NegotiatedEra = aimcpeModern then
MCPClient.Discover; // result in OnMCPDiscover and ServerDiscover
MCPClient.ListTools;
end;
end;
2026-07-28 の経路では、クライアントはすべてのリクエストに _meta を書き込み、x-mcp-header を宣言している tool 用の Mcp-Param-* を含むルーティングヘッダーを送信し、SubscriptionsListen でサブスクリプションを開きます。リスト変更は OnMCPListChanged に、resource の更新は OnMCPResourcesUpdated に届きます。MCPOptions.Tasks.Enabled が有効な場合、クライアントは Tasks 拡張を宣言し、自らタスクをポーリングして OnMCPTaskCreated、OnMCPTaskStatus、OnMCPTaskCompleted を発火させます。
OnMCPInputRequired で MRTR の質問に回答する
サーバーが input_required で応答すると、クライアントは保留中の質問とともに OnMCPInputRequired を発火させます。各キーに回答し、Accept は True のままにしてください。その後クライアントは、回答と署名済みの requestState を添えて、最大 MCPOptions.MRTR.MaxRounds ラウンドまで呼び出しを再試行します。それらの機能を宣言するには MCPOptions.MRTR.Elicitation、Sampling、Roots を設定してください。
procedure TMainForm.MCPClientMCPInputRequired(Sender: TObject;
const aMethod: string;
const aInputRequests: TsgcAI_MCP_Client_InputRequests;
var Accept: Boolean);
var
i: Integer;
begin
for i := 0 to aInputRequests.Count - 1 do
if aInputRequests.Methods[i] = 'elicitation/create' then
aInputRequests.SetElicitationAccept(aInputRequests.Keys[i],
'{"name":"Sergio"}');
Accept := True;
end;
同じイベントは入力待ちのタスクもカバーしており、その場合 aMethod は tasks/update になります。roots、sampling、elicitation のリクエストを直接送信する従来型のサーバーについては、OnMCPListRoots、OnMCPSamplingCreateMessage、OnMCPElicitationCreate から応答が返されます。
MCPOptions.Authorization によるクライアント認可
MCPOptions.Authorization.Enabled を設定すると、サーバーからの 401 または 403 応答が MCP 認可フローを開始します。保護されたリソースのメタデータ検出、登録(事前登録済みの ClientId、ClientMetadataURL からの Client ID Metadata Document、または動的登録)、リソースインジケーターを伴う PKCE、ループバックの RedirectURL 上でのブラウザーサインイン、iss 検証、トークンリクエストが行われます。その後、元のリクエストが再試行され、トークンは失効前に更新されます。
procedure TMainForm.FormCreate(Sender: TObject);
begin
MCPClient.MCPOptions.Authorization.Enabled := True;
MCPClient.MCPOptions.Authorization.ClientMetadataURL :=
'https://app.example.com/oauth/client.json';
MCPClient.MCPOptions.Authorization.AllowDynamicRegistration := True;
MCPClient.MCPOptions.Authorization.RedirectURL := 'http://127.0.0.1:33418/callback';
MCPClient.OnMCPAuthorizationURL := MCPClientAuthorizationURL;
end;
procedure TMainForm.MCPClientAuthorizationURL(Sender: TObject;
const aURL: string; var Handled: Boolean);
begin
MemoLog.Lines.Add('Sign in at ' + aURL);
Handled := False; // False: the default browser is opened
end;
資格情報は issuer ごとに保持されます。OnMCPAuthorizationCredentials を処理して保存し、次回実行時に読み込むようにすれば、ユーザーのサインインは一度だけで済みます。
sgcWebSockets .NET でも利用可能
この記事で紹介した内容はすべて sgcWebSockets .NET でも、同じクラス名、プロパティ名、イベント名で利用できます。TsgcWSAPIServer_MCP と TsgcWSAPIClient_MCP は MCPOptions.Subscriptions、MCPOptions.MRTR、MCPOptions.Tasks、ProtocolEra、Discover、MCPOptions.Authorization を公開しており、C# アプリケーションはどちらの世代でも Delphi の MCP サーバーと通信でき、その逆も可能です。
デモとドキュメント
Demos\15.AI\03.MCP にあるデモでは、すべての機能を確認できます。サーバーデモ(01.MCP_Server)は各リクエストの世代をログに記録し、tasks と subscriptions の切り替えスイッチを備え、ask_name と long_job の tool を実装しています。クライアントデモ(02.MCP_Client)では、世代を選択し、Discover を呼び出し、通知を購読し、MRTR の質問に回答し、タスクを実行できます。
完全なリファレンスはMCP サーバーのドキュメントとMCP クライアントのドキュメントにあります。またDelphi 向け MCP コンポーネントのページでは、コンポーネントの機能全体を概観できます。sgcWebSockets 2026.10.0 はsgcWebSockets ダウンロードページからダウンロードできます。
ご質問、フィードバック、移行に関するお手伝いが必要ですか?お問い合わせください。コードを書いた本人から返信が届きます。
