自架代理程式作為 MCP 工具

備註

.NET 即將提供對自行託管 MCP 工具的支援。

Go 透過結合 tool/agenttool、tool/mcptool 與官方 MCP Go SDK 來將代理程式提供為 MCP 工具。 使用 agenttool.New 包裝 Agent、使用 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 代理程式或工作流程在原生 Model Context Protocol SDK 中公開為工具。 這個套件不會選擇網頁框架,也不會包裝 MCP SDK 伺服器生命週期。 你的應用程式仍然擁有 MCP 伺服器、處理器註冊、傳輸、會話金鑰政策、認證、授權和部署。

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

在協定邊界進行轉換

mcp_to_run(...) 將已驗證的 MCP 工具參數轉換為代理框架訊息及選定的聊天選項,並將 mcp_from_run(...) 完成的回應轉換為原生 MCP ContentBlock 值。 當應用程式的工具合約需要完全自訂的原生結構描述與處理常式時,請直接使用這兩個函式:

                    "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 工具架構。 在轉換完成的代理框架回應後,明確回傳 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。 在將伺服器暴露於受信任的本地環境之外之前,先加入應用程式自有的認證、授權與會話持久性。

將代理作為一個生成工具來架設

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 新增應用程式擁有的 JSON Schema 屬性,這些屬性仍保留在原始 MCP 參數中,並 chat_option_parameters 新增可明確複製至 Agent Framework 聊天選項的屬性。

每次呼叫持續一個工作階段

傳遞現有的 AgentState 和 session_id_parameter,讓具有相同、應用程式定義的模糊 session_id 的重複呼叫能夠繼續同一個交談:

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 get /run/set 這個會話序列;你的應用程式必須認證或授權會話識別碼,並序列化同一會話的並行呼叫,就像範例中對每個會話 asyncio.Lock所做的 。 這不是 previous_response_id 風格的分支 — 需要分岔交談的應用程式應該接受獨立的來源與目的識別碼、複製來源工作階段,並將結果儲存在目的地金鑰下方。

將工作流程當作工具來架設

WorkflowMCPTool 從工作流程的啟動-執行器輸入型態衍生出一個原生 MCP 工具,並將完成的工作流程輸出轉換。 Dataclass、Pydantic 及其他物件形狀輸入成為頂層 MCP 參數;原始輸入會被包裹在可配置的參數名稱中:



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

工作流程執行個體會保留執行狀態,因此需要獨立呼叫的應用程式應提供具有 cache_target=False 的 WorkflowState 工廠,如上所示。 檢查點還原、人機互動回應和接續識別碼仍由應用程式擁有;若工作流程要求外部輸入,介面卡會引發例外狀況,而不是傳回空的成功工具結果。

如需查看完整的可執行伺服器組合(包括 MCPServer 變體),請參見 MCP 主機範例。

這很重要

將 MCP 會話識別碼及任何應用程式定義 session_id 的參數視為不可信輸入。 在使用前,先驗證並授權呼叫者,以載入或儲存會話狀態,並從已認證的租戶、使用者或工作區而非原始值中推導出持久的分割。

下一步

深入探討: