釁懶酒郝根據 MCP 協議的規定,在 MCP 協議中有以下對象:
MCP Hosts: 如 Claude Desktop、IDE 或 AI 工具,希望通過 MCP 訪問數據的程序;
MCP Clients: 維護與服務器一對一連接的協議客户端;
MCP Servers: 輕量級程序,通過標準的 Model Context Protocol 提供特定能力;
本地數據源: MCP 服務器可安全訪問的計算機文件、數據庫和服務;
遠程服務: MCP 服務器可連接的互聯網上的外部系統(如通過 APIs);
MCP Host 就是一個 AI 應用,跟用户交互的應用程序,一般是桌面程序,而 MCP Host 跟 MCP Client 可能是放在一起做的,自身即與用户交互,也具有直接調用 MCP Server 的能力。
MCP Server 就是提供 Tool 、資源內容、提示詞、對話補全等功能的服務端,MCP Server 的功能或職責是多種多樣的,比如高德地圖 MCP Server 只提供了 Tool,即接口調用。
本地數據源、遠程服務者兩個跟 MCP 本身沒有關聯,而是 MCP Server 自身實現功能的一部分,或者説是支撐 MCP Server 的基礎設施和外部依賴。
由於 MCP 概念和功能比較多,因此筆者將一步步使用案例和項目的方式講解其中的細節,建議讀者將示例項目倉庫拉下來,根據本文教程嘗試自行編寫代碼以及跑通案例。
核心概念
MCP 協議定義了以下功能模塊:
Resources
Prompts
Tools
Sampling
Roots
Transports
由於 Roots 沒有多少案例,並且 C# 的 SDK 還沒有完善,因此本文只介紹其它功能模塊。
本文知識並不是線性講解以上 MCP 功能。
Transport
Transport 指傳輸處理消息發送和接收的底層機制,MCP 主要包含兩個標準傳輸實現:
標準輸入輸出 (stdio):主要對象是本地集成和命令行工具,使用 stdio 傳輸通過標準輸入和輸出流進行通信;
服務器發送事件 (SSE):SSE 傳輸通過 HTTP POST 請求(長連接)實現服務器到客户端的流式通信;
當然,還有一個 Streamable ,但是由於社區支持還不算完善,並且本文也不講解。
以下是 MCP(Model Context Protocol)協議中 stdio、sse、streamable 三者的優缺點和差異的簡要説明:
stdio
優點:
平台兼容性高:stdio(標準輸入輸出)是操作系統底層的功能,幾乎所有操作系統和編程語言都支持。
簡單直接:用於進程間通信,通常是腳本和命令行工具的通信方式,易於實現。
缺點:
缺乏高級功能:stdio只能處理簡單的文本和二進制數據流,沒有內建的消息結構或格式。
不適合在網絡環境中的實時交互:stdio對於網絡通信來説不夠靈活和可靠,通常用於本地通信。
sse
優點:
實時更新**:允許服務器通過HTTP連接主動向客户端發送更新消息,適合實時推送的應用場景。
簡單實現:基於HTTP協議,不需要複雜的傳輸層協議,客户端通過 EventSource API 可以很容易地接收。
輕量級:相比WebSocket,SSE更輕量級,適合簡單的消息推送場景。
缺點:
單向通信:只能服務器向客户端發送消息,客户端如果需要發送消息,必須通過標準的HTTP請求回服務器。
連接限制:瀏覽器對同時建立的SSE連接數限制較嚴格,不適合大量連接的應用場景。
streamable
優點:
效率高:可以處理大數據或連續的數據流,不需要等待整個數據集傳輸完畢。
實時性好:可以在數據生成時逐步傳輸,在數據消費時逐步處理,提高實時響應能力。
靈活性高**:支持長時間的連接和傳輸,適合視頻、音頻、實時數據庫同步等應用。
缺點:
複雜性高:實現和管理流式傳輸協議、處理數據流的邏輯複雜度較高,需要確保數據的順序和完整性。
資源消耗:長時間的連接和持續的數據傳輸可能會消耗較多的服務器和網絡資源,需要優化處理。
ModelContextProtocol CSharp 中提供了三種 Transport ,其核心代碼在三個類中:
StdioClientTransport
SseClientTransport
StreamClientTransport
image-20250419133222108
下面筆者將會詳細講解 stdio、sse 兩種 Transport。
stdio
通過本地進程間通信實現,客户端以子進程形式啓動 MCP Server 程序,雙方通過 stdin/stdout 交換 JSON-RPC 消息,傳輸每條消息時以換行符分隔。
本節示例項目參考 TransportStdioServer、TransportStdioClient。
image-20250419143732894
當使用 stdio 時,McpServer 只需要實現靜態方法並配置特性註解即可,然後需要將該程序編譯為 .exe。
image-20250419140628291
TransportStdioServer 添加 Tool :
後面講解 Tool ,這裏先跳過。
[McpServerToolType]
public class EchoTool
{
[McpServerTool, Description("Echoes the message back to the client.")]
public static string Echo(string message) => $"hello {message}";
}
然後創建 MCP Server 服務,並使用 WithStdioServerTransport() 暴露接口能力。
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using TransportStdioServer;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMcpServer()
.WithStdioServerTransport()
.WithTools();
builder.Logging.AddConsole(options =>
{
options.LogToStandardErrorThreshold = LogLevel.Trace;
});
await builder.Build().RunAsync();
編譯 TransportStdioServer 項目,在 Windows 下會生成 .exe 文件,複製 .exe 文件的絕對路徑,在編寫 Client 時要用。
1745042567544
C# 編寫 Client 時,需要通過命令行參數導入 .exe 文件,示例如下:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Transport;
var builder = Host.CreateApplicationBuilder(args);
builder.Configuration
.AddEnvironmentVariables()
.AddUserSecrets();
var clientTransport = new StdioClientTransport(new()
{
Name = "Demo Server",
// 要使用絕對路徑,這裏筆者省略了
Command = "E:/../../TransportStdioServer.exe"
});
await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport);
var tools = await mcpClient.ListToolsAsync();
foreach (var tool in tools)
{
Console.WriteLine($"Connected to server with tools: {tool.Name}");
}
啓動 TransportStdioClient,控制枱會打印 TransportStdioServer 中的所有 Mcp tool。
image-20250419140917673
StdioClientTransport 原理是基於命令行參數啓動 TransportStdioServer,StdioClient 會將命令行參數拼接起來,然後以子進程方式啓動 MCP Server,命令行示例:
cmd.exe/c E:/../TransportStdioServer.exe
StdioClientTransport 核心代碼啓動子進程:
image-20250419135453777
SSE
本節參考示例項目:TransportSseServer、TransportSseClient。
SSE 是通過 HTTP 長連接實現遠程通信的,在使用各種 AI 對話應用時,AI 會像打字機一樣逐個輸出字符,這種通過 HTTP 長連接、由 HTTP Server 服務器持續推送內容的方式就叫 sse。
SSE Server 需提供兩個端點:
/sse(GET請求):建立長連接,接收服務器推送的事件流。
/messages(POST請求):客户端發送請求至該端點。
image-20250419142510347
在 TransportSseServer 實現簡單的 EchoTool。
[McpServerToolType]
public sealed class EchoTool
{
[McpServerTool, Description("Echoes the input back to the client.")]
public static string Echo(string message)
{
return "hello " + message;
}
}
配置 MCP Server 支持 SSE:
using TransportSseServer.Tools;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMcpServer()
.WithHttpTransport()
.WithTools()
.WithTools();
var app = builder.Build();
app.MapMcp();
app.Run("http://0.0.0.0:5000");
TransportSseClient 實現客户端連接 Mcp Server,其代碼非常簡單,連接到 MCP Server 後將對方支持的 Tool 列出來。
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Transport;
var defaultOptions = new McpClientOptions
{
ClientInfo = new() { Name = "IntegrationTestClient", Version = "1.0.0" }
};
var defaultConfig = new SseClientTransportOptions
{
Endpoint = new Uri($"http://localhost:5000/sse"),
Name = "Everything",
};
// Create client and run tests
await using var client = await McpClientFactory.CreateAsync(
new SseClientTransport(defaultConfig),
defaultOptions,
loggerFactory: NullLoggerFactory.Instance);
var tools = await client.ListToolsAsync();
foreach (var tool in tools)
{
Console.WriteLine($"Connected to server with tools: {tool.Name}");
}
Streamable
Streamable HTTP 是 SSE 的升級方案,完全基於標準 HTTP 協議,移除了專用 SSE 端點,所有消息通過 /message 端點傳輸。
本節不講解 Streamable 。
MCP Tool 説明
目前社區有兩大主流 LLM 開發框架,分別是 Microsoft.SemanticKernel、LangChain,它們都支持 Plugin ,能夠將本地函數、Swagger 等轉換為函數,將 Function 提交給 LLM,AI 返回要調用的 Function 後,由框架引擎實現動態調用,這樣功能叫 Function call。
注意,MCP 有很多功能,其中一個叫 MCP Tool,可以視為跟 Plugin 實現類似功能的東西。
MCP Tool 對標 Plugin ,MCP 不止包含 Tool 這一功能。
但是每個 LLM 框架的 Plugin 實現方式不一樣,其使用和實現機制跟語言特性深度綁定,不能實現跨服務跨平台使用,所以出現了 MCP Tool, MCP Tool 是對標 Plugin 的一類功能,主要目的跟 Plugin 一樣提供 Function,但是 MCP 有統一協議標準,跟語言無關、跟平台無關,但是 MCP 也不是完全替換 Plugin ,Plugin 依然具有很大的用武之地。
MCP Tool、Plugin 最後都是轉換為 Function call 的,有很多人會把 MCP 、MCP Tool 和 Function call 搞混,認為 MCP 是替換 Function call 的,所以要注意,對標 Plugin 的是 MCP Tool,而兩者都是轉換為 Function 給 AI 使用的。
MCP Tool
以 TransportSseClient 為例,如果要在 Client 調用 TransportSseServer 的 Tool,需要指定 Tool 名字和參數。
後續將會講解如何通過 SK 將 mcp tool 提供給 AI 模型。
var echoTool = tools.First(x => x.Name == "Echo");
var result = await client.CallToolAsync("Echo", new Dictionary
{
{ "message","痴者工良"}
});
foreach (var item in result.Content)
{
Console.WriteLine($"type: {item.Type},text: {item.Text}");
}
讓我們再回顧 MCP Server 是怎麼提供 Tool 的。
首先服務端需要定義 Tool 類和函數。
[McpServerToolType]
public sealed class EchoTool
{
[McpServerTool, Description("Echoes the input back to the client.")]
public static string Echo(string message)
{
return "hello " + message;
}
}
Mcp server 可以通過以下兩種方式暴露 tool。
// 直接指定 Tool 類
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithTools()
.WithTools();
// 掃描程序集
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithStdioServerTransport()
.WithToolsFromAssembly();
Client 識別服務端的 Tool 列表時,可以使用 McpClientTool.ProtocolTool.InputSchema 獲取 tool 的輸入參數格式:
image-20250419152644657
其內容格式示例如下:
Annotations: null
Description: "Echoes the input back to the client."
Name: "Echo"
InputSchema: "{"title":"Echo","description":"Echoes the input back to the client.","type":"object","properties":{"message":{"type":"string"}},"required":["message"]}"
[McpServerToolType] 用於將包含應該作為ModelContextProtocol.Server.McpServerTools公開的方法的類型屬性化。
[McpServerTool]用於指示應該將方法視為 ModelContextProtocol.Server.McpServerTool。
[Description] 則用於添加註釋。
依賴注入
在實現 Tool 函數時,服務端是可以通過函數實現依賴注入的。
參考示例項目 InjectServer、InjectClient。
image-20250419160655239
添加一個服務類並註冊到容器中。
public class MyService
{
public string Echo(string message)
{
return "hello " + message;
}
}
builder.Services.AddScoped();
在 Tool 函數中注入該服務:
[McpServerToolType]
public sealed class MyTool
{
[McpServerTool, Description("Echoes the input back to the client.")]
public static string Echo(MyService myService, string message)
{
return myService.Echo(message);
}
}
將 MCP Tool 提交到 AI 對話中
前面提到,MCP Tool 和 Plugin 都是實現 Function call 的一種方式,當在 AI 對話中使用 Tool 時,其主要過程如下:
當你提出問題時:
client 將你的問題發送給 LLM ;
LLM 分析可用的 tools 並決定使用哪些 tool;
client 通過 MCP server 執行選擇的 tool
結果被髮回給 LLM;
LLM 制定自然語言響應;
響應顯示給你;
這個過程並不是只有一兩次,可能發生多次,具體細節將會在 高德地圖 MCP 實戰 中講解,這裏只是簡單提及。
將 Tool 提交到對話上下文的偽代碼:
// Get available functions.
IList tools = await client.ListToolsAsync();
// Call the chat client using the tools.
IChatClient chatClient = ...;
var response = await chatClient.GetResponseAsync(
"your prompt here",
new() { Tools = [.. tools] },
高德地圖 MCP 實戰
聊了這麼久,終於到了實戰對接環節,本節將會通過高德地圖案例講解 MCP Tool 的邏輯細節和對接使用方式。
代碼參考示例項目 amap。
高德地圖 MCP Server 目前主要提供的功能:
地理編碼
逆地理編碼
IP 定位
天氣查詢
騎行路徑規劃
步行路徑規劃
駕車路徑規劃
公交路徑規劃
距離測量
關鍵詞搜索
周邊搜索
詳情搜索
其 Tool 名稱如下:
maps_direction_bicycling
maps_direction_driving
maps_direction_transit_integrated
maps_direction_walking
maps_distance
maps_geo
maps_regeocode
maps_ip_location
maps_around_search
maps_search_detail
maps_text_search
maps_weather
高德地圖每天都給開發者提供了免費額度,所以做該實驗時,不需要擔心需要付費。
打開 https://console.amap.com/dev/key/app 創建一個新的應用,然後複製應用 key。
在 amap 項目的 appsettings.json 添加以下 json,替換裏面的部分參數。
筆者注,除了 gpt-4o 模型,其它註冊 Function call 的模型也可以使用。
"McpServers": {
"amap-amap-sse": {
"url": "https://mcp.amap.com/sse?key={在高德官網上申請的key}"
}
},
"AIModel": {
"ModelId": "gpt-4o",
"DeploymentName": "gpt-4o",
"Endpoint": "https://openai.com/",
"Key": "aaaaaaaa"
}
image-20250419170432902
導入配置並創建日誌:
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.Development.json")
.Build();
using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
第一步:創建 mcp 客户端
連接高德 MCP Server,並獲取 Tool 列表。
var defaultOptions = new McpClientOptions
{
ClientInfo = new() { Name = "地圖規劃", Version = "1.0.0" }
};
var defaultConfig = new SseClientTransportOptions
{
Endpoint = new Uri(configuration["McpServers:amap-amap-sse:url"]!),
Name = "amap-amap-sse",
};
await using var client = await McpClientFactory.CreateAsync(
new SseClientTransport(defaultConfig),
defaultOptions,
loggerFactory: factory);
var tools = await client.ListToolsAsync();
foreach (var tool in tools)
{
Console.WriteLine($"Connected to server with tools: {tool.Name}");
}
image-20250419170702770
第二步:連接 AI 模型和配置 MCP
使用 SemanticKernel 框架對接 LLM,將 MCP Tool 轉換為 Function 添加到對話上下文中。
var aiModel = configuration.GetSection("AIModel");
var builder = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: aiModel["ModelId"],
endpoint: aiModel["Endpoint"],
apiKey: aiModel["Key"]);
builder.Services.AddLogging(s =>
{
s.AddConsole();
});
Kernel kernel = builder.Build();
// 這裏將 mcp 轉換為 functaion call
kernel.Plugins.AddFromFunctions("amap", tools.Select(aiFunction => aiFunction.AsKernelFunction()));
var chatCompletionService = kernel.GetRequiredService();
OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
Temperature = 0,
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: new() { RetainArgumentTypes = true })
};
image-20250419170714853
第三步:對話交互
編寫控制枱與用户對話交互。
var history = new ChatHistory();
string? userInput;
do
{
Console.Write("用户提問 > ");
userInput = Console.ReadLine();
history.AddUserMessage(userInput!);
var result = await chatCompletionService.GetChatMessageContentAsync(
history,
executionSettings: openAIPromptExecutionSettings,
kernel: kernel);
Console.WriteLine("AI 回答 > " + result);
history.AddMessage(result.Role, result.Content ?? string.Empty);
} while (userInput is not null);
image-20250419170840789
演示地圖規劃
注意,由於高德地圖免費額度限流,而 AI 對話時可能有多次對 MCP Server 請求,因此有時候效果並不是那麼好。
1. 智能旅遊路線規劃
最多支持16個途經點的旅遊路線規劃,自動計算最優順序,並提供可視化地圖鏈接。
使用示例:
請幫我規劃一條上海三日遊路線,包括外灘、東方明珠、迪士尼、豫園、南京路,並提供可視化地圖
image-20250419172348376
2. 景點搜索與詳情查詢
查詢景點的詳細信息,包括評分、開放時間、門票價格等。
使用示例:
請查詢黃山風景區的開放時間、門票價格和旅遊季節推薦
image-20250419172058217
AI 是怎麼識別調用 MCP
在編寫高德地圖規劃時,有一段代碼是將 MCP 服務器的接口轉換為 Function 的,代碼如下:
kernel.Plugins
.AddFromFunctions("amap", tools.Select(aiFunction => aiFunction.AsKernelFunction()))
其實在這裏就可以下結論,並不是 AI 模型直接調用 MCP Server 的,依然 Client 進行是 Function call 。
通過攔截 http 請求可以發現,當用户輸入 請幫我規劃一條上海三日遊路線,包括外灘、東方明珠、迪士尼、豫園、南京路,並提供可視化地圖 時,客户端首先將用户提問和 mcp 服務所提供的 function call 一起發送到 AI 模型服務器。
對話時,Client 提供給 LLM 的 Function (MCP Tool)列表。
image-20250419173936573
image-20250419173957048
然後 AI 回答要調用的 Function call 步驟和參數,接着由客户端實現將 Function 定位 MCP Server,並順序調用每個 Tool。
LLM 返回要順序調用的 Function 列表以及參數:
image-20250419174037025
客户端將每個 Function 的執行結果和用户的提問等信息,一起再次提交給 AI 模型服務器。
image-20250419174630396
由於高德接口併發限制,有部分接口調用失敗,那麼客户端可能會來回請求多次,最後輸出 AI 的回答。
image-20250419174824315
到這裏,讀者應該明白 MCP Tool、Plugin、Function Call 的關係了吧!
實現 Mcp Server
前面筆者介紹了 MCP Tool,但是 MCP Server 還可以提供很多很有用的功能,MCP 協議定義了以下核心模塊:
Core architecture
Resources
Prompts
Tools
Sampling
Roots
Transports
作為當前社區中最關注的 Tools,本文已經單獨介紹,接下來將會以繼續講解其它功能模塊。
實現 Resources
示例項目參考:ResourceServer、ResourceClient。
Resources 定義:Resources 是 Model Context Protocol (MCP) 中的一個核心原語,它允許服務器暴露可以被 clients 讀取並用作 LLM 交互上下文的數據和內容。
Resources 代表 MCP server 想要提供給 clients 的任何類型的數據,在使用上,MCP Server 可以給每種資源定義一個 Uri,這個 Uri 的協議格式可以是虛擬的,這不重要,只要是能夠定位資源的一段 Uri 字符串即可。
只看定義,讀者可能不理解什麼意思,沒關係,等後面動手做的時候就知道了。
Resources 可以包括:
文件內容
數據庫記錄
API 響應
實時系統數據
屏幕截圖和圖像
日誌文件
等等
每個 resource 都由一個唯一的 URI 標識,並且可以包含文本或二進制數據。
Resources 使用以下格式的 URIs 進行標識:
[protocol]://[host]/[path]
例如:
file:///home/user/documents/report.pdf
postgres://database/customers/schema
screen://localhost/display1
Resources 的文件類型,主要是文本資源和二進制資源。
文本資源
文本資源包含 UTF-8 編碼的文本數據。這些適用於:
配置文件
日誌文件
JSON/XML 數據
純文本
二進制資源
二進制資源包含以 base64 編碼的原始二進制數據。這些適用於:
圖像
PDFs
音頻文件
視頻文件
其他非文本格式
Resources Server、Client 實現
客户端使用 Resources 服務時,有以下 Api,那麼在本節的學習中,將會圍繞這這些接口講解如何在服務段實現對應的功能。
image-20250419194249066
實現 Resources 時,主要有兩種提供 Resources 的方式,一種是通過模板動態提供 Resource Uri 的格式,一種是直接提供具體的 Resource Uri。
Resource Uri 格式示例:
"test://static/resource/{README.txt}"
MCP Server 提供的 Resource Uri 格式是可以隨意自定義的,這些 Uri 並不是直接給 Client 讀取的,Client 在需要讀取 Resource 是,把 Uri 發送給 MCP Server,MCP Server 自行解析 Uri 並定位對應的資源,然後把資源內容返回給 Client。
也就是説,該 Uri 的協議其實就是字符串,只要在當前 MCP Server 和 Client 之間能用即可。
MCP Server 可以通過模板提供某類資源,這類資源的的地址是動態的,要根據 id 實時獲取。
builder.Services.AddMcpServer()
.WithListResourceTemplatesHandler(async (ctx, ct) =>
{
return new ListResourceTemplatesResult
{
ResourceTemplates =
[
new ResourceTemplate { Name = "Static Resource", Description = "A static resource with a numeric ID", UriTemplate = "test://static/resource/{id}" }
]
};
});
對於地址固定的 Resource,可以通過這種方式暴露出去,比如有個使用必讀的文件,只需要固定暴露地址。
builder.Services.AddMcpServer()
.WithListResourcesHandler(async (ctx, ct) =>
{
await Task.CompletedTask;
var readmeResource = new Resource
{
Uri = "test://static/resource/README.txt",
Name = "Resource README.txt",
MimeType = "application/octet-stream",
Description = Convert.ToBase64String(Encoding.UTF8.GetBytes("這是一個必讀文件"))
};
return new ListResourcesResult
{
Resources = new List
{
readmeResource
}
};
})
Client 讀取資源模板和靜態資源列表:
var defaultOptions = new McpClientOptions
{
ClientInfo = new() { Name = "ResourceClient", Version = "1.0.0" }
};
var defaultConfig = new SseClientTransportOptions
{
Endpoint = new Uri($"http://localhost:5000/sse"),
Name = "Everything",
};
// Create client and run tests
await using var client = await McpClientFactory.CreateAsync(
new SseClientTransport(defaultConfig),
defaultOptions,
loggerFactory: NullLoggerFactory.Instance);
var resourceTemplates = await client.ListResourceTemplatesAsync();
var resources = await client.ListResourcesAsync();
foreach (var template in resourceTemplates)
{
Console.WriteLine($"Connected to server with resource templates: {template.Name}");
}
foreach (var resource in resources)
{
Console.WriteLine($"Connected to server with resources: {resource.Name}");
}
那麼,客户端如果從 MCP 服務器讀取資源只需要將 Resource Uri 傳遞即可。
var readmeResource = await client.ReadResourceAsync(resources.First().Uri);
這裏只介紹了 MCP Server 提供 Resource Uri,那麼當 Client 要獲取某個 Resource Uri 的內容時,MCP Server 要怎麼處理呢?
ModelContextProtocol CSharp 目前提供了兩種實現:
TextResourceContents
BlobResourceContents
比如説,當 Client 訪問 test://static/resource/README.txt 時,可以將 README.txt 文件直接以文本的形式返回:
.WithReadResourceHandler(async (ctx, ct) =>
{
var uri = ctx.Params?.Uri;
if (uri is null || !uri.StartsWith("test://static/resource/"))
{
throw new NotSupportedException($"Unknown resource: {uri}");
}
if(uri == "test://static/resource/README.txt")
{
var readmeResource = new Resource
{
Uri = "test://static/resource/README.txt",
Name = "Resource README.txt",
MimeType = "application/octet-stream",
Description = "這是一個必讀文件"
};
return new ReadResourceResult
{
Contents = [new TextResourceContents
{
Text = File.ReadAllText("README.txt"),
MimeType = readmeResource.MimeType,
Uri = readmeResource.Uri,
}]
};
}
})
image-20250419201835608
如果 Client 訪問了其它 Resource,則以二進制的形式返回:
.WithReadResourceHandler(async (ctx, ct) =>
{
var uri = ctx.Params?.Uri;
if (uri is null || !uri.StartsWith("test://static/resource/"))
{
throw new NotSupportedException($"Unknown resource: {uri}");
}
int index = int.Parse(uri["test://static/resource/".Length..]) - 1;
if (index < 0 || index >= ResourceGenerator.Resources.Count)
{
throw new NotSupportedException($"Unknown resource: {uri}");
}
var resource = ResourceGenerator.Resources[index];
return new ReadResourceResult
{
Contents = [new TextResourceContents
{
Text = resource.Description!,
MimeType = resource.MimeType,
Uri = resource.Uri,
}]
};
})
客户端讀取 "test://static/resource/README.txt" 示例:
var readmeResource = await client.ReadResourceAsync(resources.First().Uri);
var textContent = readmeResource.Contents.First() as TextResourceContents;
Console.WriteLine(textContent.Text));
image-20250420151630678
Resource 訂閲
Clients 可以訂閲特定 resources 的更新:
Client 使用 resource URI 發送 resources/subscribe
當 resource 更改時,服務器發送 notifications/resources/updated
Client 可以使用 resources/read 獲取最新內容
Client 可以使用 resources/unsubscribe 取消訂閲
一般來説,MCP Server 要實現工廠模式,以便動態記錄有哪些 Resource Uri 是被訂閲的,那麼當這些 Uri 的資源發生變化時,才需要推送,否則即使發送變化,也沒有推送更新的必要。
但是目前來説,只有 WithStdioServerTransport() 才能起效,筆者在 WithHttpTransport() 實驗失敗。