# 模型上下文协议

Source: https://docs.sisk-framework.org/zh-cn/docs/extensions/mcp.html

可以使用 [Sisk.ModelContextProtocol](https://www.nuget.org/packages/Sisk.ModelContextProtocol/) 包构建为使用大型语言模型（LLM）的代理模型提供上下文的应用程序：

    dotnet add package Sisk.ModelContextProtocol --prerelease

此包提供有用的类和方法，用于构建在 [Streamable HTTP](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http) 上运行的 MCP 服务器，支持单请求/响应消息。自 1.7.0-rc1 版本起，它支持协议版本 `2026-07-28` 的无状态请求，并保留对使用 `2025-11-25`、`2025-06-18` 和 `2025-03-26` 版本的客户端的传统初始化流程。它支持工具和技能。

> [!NOTE]
>
> 在开始之前，请注意此包仍在开发中，可能出现不符合规范的行为。阅读 [package details](https://github.com/sisk-http/core/tree/main/extensions/Sisk.ModelContextProtocol) 了解正在开发的内容以及哪些功能尚未实现。

## 开始使用 MCP

[McpProvider](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpProvider.md) 类是定义 MCP 服务器的入口点。它是一个密封的提供程序对象，可在启动时进行配置。你的 Sisk 应用程序可以拥有一个或多个 MCP 提供程序。

```csharp
McpProvider mcp = new McpProvider(
    serverName: "math-server",
    serverTitle: "Mathematics server",
    serverVersion: new Version(1, 0));

mcp.Tools.Add(new McpTool(
    name: "math_sum",
    description: "Sums one or more numbers.",
    schema: JsonSchema.CreateObjectSchema(
        properties: new Dictionary<string, JsonSchema>()
        {
            { "numbers",
                JsonSchema.CreateArraySchema(
                    itemsSchema: JsonSchema.CreateNumberSchema(),
                    minItems: 1,
                    description: "The numbers to sum.")
            }
        },
        requiredProperties: ["numbers"]),
    executionHandler: async (McpToolContext context) =>
    {
        var numbers = context.Arguments["numbers"].GetJsonArray().ToArray<double>();
        var sum = numbers.Sum();
        return await Task.FromResult(McpToolResult.CreateText($"Sum result: {sum:N4}"));
    }));
```

如果你的应用程序只提供一个 MCP 提供程序，可以使用构建器的单例：

```csharp
static void Main(string[] args)
{
    using var host = HttpServer.CreateBuilder()
        .UseMcp(mcp =>
        {
            mcp.ServerName = "math-server";
            mcp.ServerTitle = "Mathematics server";

            mcp.Tools.Add(new McpTool(
                name: "math_sum",
                description: "Sums one or more numbers.",
                schema: JsonSchema.CreateObjectSchema(
                    properties: new Dictionary<string, JsonSchema>()
                    {
                        { "numbers",
                            JsonSchema.CreateArraySchema(
                                itemsSchema: JsonSchema.CreateNumberSchema(),
                                minItems: 1,
                                description: "The numbers to sum.")
                        }
                    },
                    requiredProperties: ["numbers"]),
                executionHandler: async (McpToolContext context) =>
                {
                    var numbers = context.Arguments["numbers"].GetJsonArray().ToArray<double>();
                    var sum = numbers.Sum();
                    return await Task.FromResult(McpToolResult.CreateText($"Sum result: {sum:N4}"));
                }));
        })
        .UseRouter(router =>
        {
            router.MapAny("/mcp", async (HttpRequest req) =>
            {
                return await req.HandleMcpRequestAsync();
            });
        })
        .Build();

    host.Start();
}
```

端点必须同时接受 `GET` 和 `POST` 请求，因此 `MapAny` 是最简单的路由映射。`HandleMcpRequestAsync` 返回一个 [HttpResponse](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpResponse.md)，你的路由必须返回它。如果在同一个应用中需要多个提供程序，请跳过单例，直接在每个路由中调用 [McpProvider.HandleRequestAsync](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpProvider.HandleRequestAsync.md)。

```csharp
var mathProvider = new McpProvider("math-server", "Mathematics server", new Version(1, 0));

router.MapAny("/mcp/math", async request =>
{
    return await mathProvider.HandleRequestAsync(request);
});
```

## 为函数创建 JSON Schema

[Sisk.ModelContextProtocol] 库使用了 [LightJson](https://github.com/CypherPotato/LightJson) 的分支来处理 JSON 和 JSON Schema。此实现为各种对象提供了流式的 JSON Schema 构建器：

- JsonSchema.CreateObjectSchema
- JsonSchema.CreateArraySchema
- JsonSchema.CreateBooleanSchema
- JsonSchema.CreateNumberSchema
- JsonSchema.CreateStringSchema
- JsonSchema.Empty

示例：

```csharp
JsonSchema.CreateObjectSchema(
    properties: new Dictionary<string, JsonSchema>()
    {
        { "numbers",
            JsonSchema.CreateArraySchema(
                itemsSchema: JsonSchema.CreateNumberSchema(),
                minItems: 1,
                description: "The numbers to sum.")
        }
    },
    requiredProperties: ["numbers"]);
```

生成以下 schema：

```json
{
  "type": "object",
  "properties": {
    "numbers": {
      "type": "array",
      "items": {
        "type": "number"
      },
      "minItems": 1,
      "description": "The numbers to sum."
    }
  },
  "required": ["numbers"]
}
```

## 处理函数调用

在 [McpTool](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpTool.md) 的 `executionHandler` 参数中定义的函数会提供一个包含调用参数的 JsonObject，可流式读取：

```csharp
mcp.Tools.Add(new McpTool(
    name: "browser_do_action",
    description: "Run an browser action, such as scrolling, refreshing or navigating.",
    schema: JsonSchema.CreateObjectSchema(
        properties: new Dictionary<string, JsonSchema>()
        {
            { "action_name",
                JsonSchema.CreateStringSchema(
                    enums: ["go_back", "refresh", "scroll_bottom", "scroll_top"],
                    description: "The action name.")
            },
            { "action_data",
                JsonSchema.CreateStringSchema(
                    description: "Action parameter."
                ) }
        },
        requiredProperties: ["action_name"]),
    executionHandler: async (McpToolContext context) =>
    {
        // 读取操作名称。如果为 null 或不是显式字符串则会抛出异常
        string actionName = context.Arguments["action_name"].GetString();
        
        // action_data 被定义为非必需，因此此处可能为 null
        string? actionData = context.Arguments["action_data"].MaybeNull()?.GetString();
        
        // 根据 actionName 处理浏览器操作
        return await Task.FromResult(
            McpToolResult.CreateText($"Performed browser action: {actionName}"));
    }));
```

工具参数会在处理程序运行之前根据 schema 进行验证。如果验证失败，提供程序会向 MCP 客户端返回错误结果，并且不会调用工具处理程序。

## 函数结果

[McpToolResult](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpToolResult.md) 对象提供了三种方法来创建工具响应的内容：

- [CreateAudio(ReadOnlySpan<byte>, string)](...): 为 MCP 客户端创建基于音频的响应。
- [CreateImage(ReadOnlySpan<byte>, string)](...): 为 MCP 客户端创建基于图像的响应。
- [CreateText(string)](...): 为 MCP 客户端创建基于文本的响应（默认）。

此外，还可以将多种不同的内容合并为单个 JSON 工具响应：

```csharp
mcp.Tools.Add(new McpTool(
    ...
    executionHandler: async (McpToolContext context) =>
    {
        // 模拟真实工作

        byte[] browserScreenshot = await browser.ScreenshotAsync();
        
        return McpToolResult.Combine(
            McpToolResult.CreateText("Heres the screenshot of the browser:"),
            McpToolResult.CreateImage(browserScreenshot, "image/png")
        )
    }));
```

提供程序当前支持 `server/discover`、传统的 `initialize` 和 `ping` 方法、`tools/list`、`tools/call`、`skills/list`、`skills/get`、`resources/list`、`resources/read` 以及通知。不支持的 JSON-RPC 方法会返回 JSON-RPC 错误响应。

## 发布技能

MCP 提供程序还可以通过 `io.modelcontextprotocol/skills` 扩展发布 [Agent Skills](https://github.com/modelcontextprotocol/ext-skills)。在提供程序开始处理请求之前，将 [McpSkill](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpSkill.md) 对象添加到 [McpProvider.Skills](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpProvider.Skills.md)：

```csharp
mcp.Skills.Add(new McpSkill(
    path: "acme/refunds",
    frontmatter: new JsonObject
    {
        ["name"] = "refunds",
        ["description"] = "Follow the refund workflow."
    },
    body: "# Refunds\nRead references/policy.md before using the refund tools.",
    files: new Dictionary<string, byte[]>
    {
        ["references/policy.md"] = File.ReadAllBytes("skills/refunds/references/policy.md")
    }));
```

`McpSkill` 会根据 frontmatter 和 Markdown 正文生成 `SKILL.md` 文件。`path` 的最后一段必须等于 frontmatter 中的 `name` 字段。技能文件在创建时会被复制，提供程序永不从磁盘读取它们。客户端使用 `skills/list`、`skills/get` 和 `resources/read` 列出和读取技能，使用 `2026-07-28` 协议版本。有关限制和验证规则，请参阅 [package details](https://github.com/sisk-http/core/tree/main/extensions/Sisk.ModelContextProtocol)。

## 持续工作

模型上下文协议是一种用于代理模型及向其提供内容的应用程序的通信协议。它是一个新协议，因此其规范经常会因废弃、添加新功能或出现破坏性更改而不断更新。

在开始构建代理应用程序之前，了解 [Model Context Protocol](https://modelcontextprotocol.io/docs/getting-started/intro) 所解决的问题至关重要。

还请阅读 [Sisk.ModelContextProtocol](https://github.com/sisk-http/core/tree/main/extensions/Sisk.ModelContextProtocol) 包的规范，以了解其进展、状态以及可以实现的功能。
