MCP ツールとしてのセルフホスト エージェント

Note

.NETでのセルフホスティング MCP ツールのサポートは近日公開予定です。

Go では、 tool/agenttool、 tool/mcptool、公式の MCP Go SDK を組み合わせて、エージェントを MCP ツールとして公開します。 エージェントを agenttool.Newでラップし、 mcptool.AddToolに登録して、MCP トランスポート経由で提供します。

保守されている Foundry サンプルでは stdio を使用します。 Foundry プロジェクト エンドポイント、モデルデプロイ、およびAzure資格情報が必要です。

// Copyright (c) Microsoft. All rights reserved.

// This sample shows how to expose an Agent as an MCP tool.

package main

import (
    "context"

    "github.com/microsoft/agent-framework-go/agent"
    "github.com/microsoft/agent-framework-go/examples/internal/demo"
    "github.com/microsoft/agent-framework-go/provider/foundryprovider"
    "github.com/microsoft/agent-framework-go/tool/agenttool"
    "github.com/microsoft/agent-framework-go/tool/mcptool"
    "github.com/modelcontextprotocol/go-sdk/mcp"
)

// Run "npx @modelcontextprotocol/inspector go run ." to connect to this MCP server.

func main() {
    token := demo.FoundryTokenCredential()

    // Create Microsoft Foundry agent with the same configuration as the C# example.
    agent := foundryprovider.NewAgent(
        demo.FoundryProjectEndpoint,
        token,
        foundryprovider.ModelDeployment(demo.FoundryModel),
        foundryprovider.AgentConfig{
            Instructions: "You are good at telling jokes, and you always start each joke with 'Aye aye, captain!'.",
            Config: agent.Config{
                Name:        "Joker",
                Description: "An agent that tells jokes.",
            },
        },
    )

    // Create an MCP server with the agent exposed as a tool.
    srv := mcp.NewServer(&mcp.Implementation{
        Name:    "agent-mcp-server",
        Version: "1.0.0",
    }, nil)

    // Register the agent as an MCP tool.
    mcptool.AddTool(srv, agenttool.New(agent, agenttool.Config{}))

    // Run the MCP server with StdIO transport.
    if err := srv.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
        demo.Panic(err)
    }
}

クライアントとサーバーのパターンについては MCP ツールの統合 を、実行手順については MCP サーバーの完全なサンプル を参照してください。

agent-framework-hosting-mcpを使用して、Agent Framework エージェントまたはワークフローをネイティブ モデル コンテキスト プロトコル SDK のツールとして公開します。 パッケージは、Web フレームワークを選択したり、MCP SDK サーバーのライフサイクルをラップしたりすることはありません。 アプリケーションは引き続き MCP サーバー、ハンドラー登録、トランスポート、セッション キー ポリシー、認証、承認、デプロイを管理します。

pip install --pre agent-framework-hosting-mcp

プロトコル境界で変換する

mcp_to_run(...) は、検証済みの MCP ツール引数を Agent Framework メッセージと選択したチャット オプションに変換し、 mcp_from_run(...) 完了した応答をネイティブ MCP ContentBlock 値に変換します。 アプリケーションのツール コントラクトに完全にカスタムのネイティブ スキーマとハンドラーが必要な場合は、次の 2 つの関数を直接使用します。

                    "properties": {
                        TASK_ARGUMENT: {
                            "type": "string",
                            "description": "The request for the hosted agent.",
                        },
                        **CHAT_OPTION_ARGUMENTS,
                    },
                    "required": [TASK_ARGUMENT],
                    "additionalProperties": False,
                },
            )
        ]
    )


async def call_tool(
    _ctx: ServerRequestContext[dict[str, Any]], params: types.CallToolRequestParams
) -> types.CallToolResult:
    """Convert, run, and render without the agent-backed adapter."""
    name = params.name
    arguments = params.arguments or {}

    if name != "run_agent_manually":
        raise MCPError(types.INVALID_PARAMS, f"Unknown MCP tool: {name}")
    run = mcp_to_run(
        arguments,
        argument_name=TASK_ARGUMENT,
        chat_option_arguments=CHAT_OPTION_ARGUMENTS,
    )
    result = await agent.run(run["messages"], options=run["options"])
    return types.CallToolResult(content=mcp_from_run(result))


server = Server("agent-framework-hosting-mcp-manual-sample", on_list_tools=list_tools, on_call_tool=call_tool)
credential = DefaultAzureCredential()

chat_option_argumentsにリストされている引数名のみがrun["options"]にコピーされます。他の MCP 引数はメッセージの生表現で使用できますが、モデル クライアントには転送されません。

MCPServer でホストする

MCP SDK 2.x アプリケーションの場合は、 MCPServer を使用して、修飾された関数からネイティブ MCP ツール スキーマを派生させます。 完了した Agent Framework 応答を変換した後、 CallToolResult を明示的に返します。

from contextlib import asynccontextmanager
from typing import Literal

from agent_framework import Agent
from agent_framework.foundry import FoundryChatClient
from agent_framework_hosting_mcp import mcp_from_run, mcp_to_run
from azure.identity.aio import DefaultAzureCredential
from mcp import types
from mcp.server.mcpserver import MCPServer

credential = DefaultAzureCredential()
agent = Agent(
    client=FoundryChatClient(
        project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
        model=os.environ["FOUNDRY_MODEL"],
        credential=credential,
    ),
    name="MCPHostedAgent",
    description="Answer a request with the hosted Agent Framework agent.",
    instructions="Answer the user's request clearly and concisely.",
)


@asynccontextmanager
async def lifespan(_server: MCPServer[None]) -> AsyncGenerator[None]:
    """Close the model credential when the MCP server stops."""
    async with credential:
        yield


server = MCPServer(
    name="agent-framework-hosting-mcpserver-sample",
    instructions="Expose an Agent Framework agent as an MCP tool.",
    lifespan=lifespan,
)


@server.tool(
    name="run_agent",
    description="Run the hosted Agent Framework agent.",
    structured_output=False,
)
async def run_agent(
    task: str,
    reasoning_effort: Literal["low", "medium", "high"] | None = None,
) -> types.CallToolResult:
    """Run the agent with MCPServer-validated arguments."""

このサンプルでは、ステートレス ストリームが可能な HTTP を使用します。 信頼されたローカル環境を超えてサーバーを公開する前に、アプリケーション管理の認証、承認、およびセッション永続化を追加します。

生成された 1 つのツールとしてエージェントをホストする

AgentMCPTool は、エージェントからネイティブのツール名、説明、スキーマを取得し、一覧表示、解析、実行、結果変換の整合性を保つことで、両者にずれが生じないようにします。

) -> types.CallToolResult:
    """Run the app-owned tool with native MCP and Agent Framework values."""
    return await agent_tool.call_tool(params.name, params.arguments)


server = Server("agent-framework-hosting-mcp-sample", on_list_tools=list_tools, on_call_tool=call_tool)
credential = DefaultAzureCredential()
agent = Agent(
    client=FoundryChatClient(
        project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
        model=os.environ["FOUNDRY_MODEL"],
        credential=credential,
    ),
    name="MCPHostedAgent",
    description="Answer a request with the hosted Agent Framework agent.",
    instructions="Answer the user's request clearly and concisely.",
)
agent_tool = AgentMCPTool(
    agent,
    name="run_agent",
    argument_description="The request for the hosted agent.",
    chat_option_parameters={
        "reasoning_effort": {
            "type": "string",

AgentMCPTool は、オーバーライドされない限り、エージェントの名前と説明を使用します。 parameters では、未加工の MCP 引数で使用できるアプリ所有の JSON スキーマ プロパティが追加され、 chat_option_parameters は値が Agent Framework チャット オプションに明示的にコピーされるプロパティを追加します。

呼び出しごとにセッションを保持する

既存の AgentState と session_id_parameter を渡すと、同じ不透明な、アプリで定義された session_id を使った繰り返し呼び出しで、1 つの会話を継続できるようになります:

state = AgentState(agent)
agent_tool = AgentMCPTool(
    state,
    name="run_agent",
    argument_description="The request for the hosted agent.",
    parameters={
        "session_id": {
            "type": "string",
            "minLength": 1,
            "description": (
                "Opaque, app-defined conversation key. Reuse the same value to continue and update one conversation."
            ),
        }
    },
    required_parameters={"session_id"},
    chat_option_parameters={
        "reasoning_effort": {
            "type": "string",

AgentMCPTool は、 AgentState セッションの取得/実行/設定シーケンスのみを実行します。アプリケーションはセッション識別子を認証または承認し、同じセッションの同時呼び出しをシリアル化する必要があります。これは、サンプルがセッションごとの asyncio.Lockで行う場合と同じです。 これは previous_response_idスタイルの分岐ではありません。会話をフォークする必要があるアプリケーションでは、個別のソース ID と宛先 ID を受け入れ、ソース セッションをコピーし、結果を宛先キーの下に格納する必要があります。

ワークフローをツールとしてホストする

WorkflowMCPTool は、ワークフローの start-executor 入力型から 1 つのネイティブ MCP ツールを派生させ、完成したワークフロー出力を変換します。 データクラス、Pydantic、およびその他のオブジェクト型の入力は、最上位の MCP 引数になります。プリミティブ入力は、構成可能な引数名でラップされます。



async def list_tools(
    _ctx: ServerRequestContext[dict[str, Any]], params: types.PaginatedRequestParams | None
) -> types.ListToolsResult:

ワークフロー インスタンスは実行状態を維持するため、独立した呼び出しを必要とするアプリケーションは、上記のようにWorkflowStateを備えたcache_target=False ファクトリを提供する必要があります。 チェックポイントの復元、ヒューマン イン ザ ループによる対応、継続識別子は引き続きアプリケーションが管理します。ワークフローが外部入力を要求した場合、アダプターは空の成功ツール結果を返すのではなく、例外を発生させます。

MCPServerバリアントを含む実行可能サーバーの完全なセットについては、MCP ホスティングのサンプルを参照してください。

Important

MCP セッション識別子とアプリ定義の session_id 引数を信頼されていない入力として扱います。 セッション状態の読み込みまたは保存を行う前に呼び出し元を認証および承認し、未加工の値ではなく、認証済みのテナント、ユーザー、またはワークスペースから永続的なパーティション分割を派生させます。

次のステップ

より深く進む: