Агенты самообслуживания в качестве средств MCP

Замечание

Поддержка самостоятельного размещения инструментов MCP в .NET скоро появится.

Go предоставляет агент как инструмент MCP, объединяя tool/agenttool, tool/mcptool и официальный пакет SDK MCP Go. Оберните агента с помощью 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 Model Context Protocol. Пакет не выбирает веб-фреймворк и не оборачивает жизненный цикл сервера MCP SDK. Приложение по-прежнему владеет сервером MCP, регистрацией обработчика, транспортом, политикой ключа сеанса, проверкой подлинности, авторизацией и развертыванием.

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

Преобразование по границе протокола

mcp_to_run(...) преобразует проверенные аргументы инструментов MCP в сообщения Agent Framework и выбранные параметры чата и 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 явным образом после преобразования завершенного ответа Agent Framework:

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, принадлежащие приложению, которые остаются доступными в необработанных аргументах 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 выполняет только последовательность get/run/set для сеанса AgentState; ваше приложение должно аутентифицировать или авторизовать идентификатор сеанса и сериализовать одновременные вызовы для одного и того же сеанса, как это показано в примере с отдельным 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:

Экземпляры рабочего процесса сохраняют состояние исполнения, поэтому приложения, которым требуются независимые вызовы, должны предоставлять фабрику WorkflowState с cache_target=False, как показано выше. Восстановление контрольных точек, ответы с участием человека и идентификаторы продолжения остаются в ведении приложения; если рабочий процесс запрашивает внешний ввод, адаптер генерирует исключение вместо возврата пустого успешного результата инструмента.

Полный набор исполняемых серверов, включая вариант MCPServer, см. в примерах размещения MCP.

Это важно

Обработайте идентификатор сеанса MCP и любой аргумент, определенный session_id приложением, как ненадежные входные данные. Проверяйте подлинность и авторизуйте инициатора запроса перед тем, как использовать любой из этих механизмов для загрузки или сохранения состояния сеанса, и определяйте устойчивое разбиение на основе аутентифицированного арендатора, пользователя или рабочей области, а не исходного значения.

Дальнейшие действия

Вернитесь глубже: