Csharp使用MCP

Csharp使用MCP

基于官方csharp-sdk 玩转 MCP

MCP C# SDK

MCP的官方C# SDK目前由微软维护,因此.NET 的应用能够很方便的集成MCP,成为AI Agent开发的重要的一块拼图。

GITHUB地址:https://github.com/modelcontextprotocol

目前为Preview版本,后续可能会有大的变更。

1
2
3
4
// 如果代码运行异常,请尝试使用这个版本`0.1.0-preview.2`
// #r "nuget:ModelContextProtocol,0.1.0-preview.2"

#r "nuget:ModelContextProtocol,*-*"

创建 MCP 客户端

实际上就是创建了一个McpClient 建立和特定McpServer的连接

本示例使用server-github,本质上就是本地启动了一个Node.js的进程,因为他就是基于Node.js来运行的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using ModelContextProtocol;
using ModelContextProtocol.Client;
using ModelContextProtocol.Configuration;
using ModelContextProtocol.Protocol.Transport;

private static async Task<IMcpClient> GetGitHubMcpClientAsync()
{
McpClientOptions clientOptions = new()
{
ClientInfo = new() { Name = "GitHub", Version = "1.0.0" }
};

var serverConfig = new McpServerConfig
{
Id = "github",
Name = "GitHub",
TransportType = "stdio",
TransportOptions = new Dictionary<string, string>
{
["command"] = "npx",
["arguments"] = "-y @modelcontextprotocol/server-github",
}
};

var client =await McpClientFactory.CreateAsync(serverConfig, clientOptions);

return client;
}
1
2
3
var githubMcpClient = await GetGitHubMcpClientAsync();
var tools = await githubMcpClient.ListToolsAsync();
tools.Display();

直接通过MCP Client调用

1
2
var tool = tools.FirstOrDefault(t => t.Name == "search_repositories");
tool.Display();
1
2
3
4
5
var result = await githubMcpClient.CallToolAsync(
toolName: "search_repositories",
arguments: new Dictionary<string, Object?>() { ["query"] = "microsoft/autogen" });

result.Display();

通过IChatClient调用

image-20250427161033736

1
#r "nuget:Microsoft.Extensions.AI.AzureAIInference ,*-*"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using Azure;
using Azure.AI.Inference;
using Microsoft.Extensions.AI;

var uri = new Uri("...");
var apiKey = "163...";

IChatClient azureClient = new ChatCompletionsClient(uri, new AzureKeyCredential(apiKey)).AsChatClient("gpt-4o");


IChatClient client = new ChatClientBuilder(azureClient)
.UseFunctionInvocation() //自动调用
.Build();

//设置Tools
var tools = await githubMcpClient.ListToolsAsync();
ChatOptions chatOptions = new()
{
Tools = [.. tools]
};
var respsone = await client.GetResponseAsync("Summarize the last four commits to the microsoft/semantic-kernel repository?", chatOptions);

respsone.Display();

将 MCP 工具转换为SK Function后调用

1
2
3
4
5
6
7
//注册并激活AI 服务:

using PolyglotKernel= Microsoft.DotNet.Interactive.Kernel;// 引入交互式的内核命名空间,以便用户输入
var aiProviderCode = await PolyglotKernel.GetInputAsync("请输入AI服务提供商编码:");

var kernel = GetKernel(aiProviderCode);
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
using Microsoft.Extensions.AI;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Types;
using Microsoft.SemanticKernel;
using System.Text.Json;

//将MCP转成sk可识别的类型
internal static async Task<IEnumerable<KernelFunction>> MapToFunctionsAsync(this IMcpClient mcpClient)
{
var tools = await mcpClient.ListToolsAsync().ConfigureAwait(false);
return tools.Select(t => t.ToKernelFunction(mcpClient)).ToList();
}

private static KernelFunction ToKernelFunction(this AIFunction tool, IMcpClient mcpClient)
{
async Task<string> InvokeToolAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken cancellationToken)
{
try
{
// Convert arguments to dictionary format expected by ModelContextProtocol
Dictionary<string, object> mcpArguments = [];
foreach (var arg in arguments)
{
if (arg.Value is not null)
{
mcpArguments[arg.Key] = function.ToArgumentValue(arg.Key, arg.Value);
}
}
// Call the tool through ModelContextProtocol
var result = await mcpClient.CallToolAsync(
tool.Name,
mcpArguments,
cancellationToken: cancellationToken
).ConfigureAwait(false);
// Extract the text content from the result
return string.Join("\n", result.Content
.Where(c => c.Type == "text")
.Select(c => c.Text));
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error invoking tool '{tool.Name}': {ex.Message}");
// Rethrowing to allow the kernel to handle the exception
throw;
}
}
return KernelFunctionFactory.CreateFromMethod(
method: InvokeToolAsync,
functionName: tool.Name,
description: tool.Description,
parameters: tool.ToParameters(),
returnParameter: ToReturnParameter()
);
}
private static object ToArgumentValue(this KernelFunction function, string name, object value)
{
var parameter = function.Metadata.Parameters.FirstOrDefault(p => p.Name == name);
return parameter?.ParameterType switch
{
Type t when Nullable.GetUnderlyingType(t) == typeof(int) => Convert.ToInt32(value),
Type t when Nullable.GetUnderlyingType(t) == typeof(double) => Convert.ToDouble(value),
Type t when Nullable.GetUnderlyingType(t) == typeof(bool) => Convert.ToBoolean(value),
Type t when t == typeof(List<string>) => (value as IEnumerable<object>)?.ToList(),
Type t when t == typeof(Dictionary<string, object>) => (value as Dictionary<string, object>)?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
_ => value,
} ?? value;
}
private static List<KernelParameterMetadata>? ToParameters(this AIFunction tool)
{
var inputSchema = JsonSerializer.Deserialize<JsonSchema>(tool.JsonSchema);
var properties = inputSchema?.Properties;
if (properties == null)
{
return null;
}
HashSet<string> requiredProperties = new(inputSchema!.Required ?? []);
return properties.Select(kvp =>
new KernelParameterMetadata(kvp.Key)
{
Description = kvp.Value.Description,
ParameterType = ConvertParameterDataType(kvp.Value, requiredProperties.Contains(kvp.Key)),
IsRequired = requiredProperties.Contains(kvp.Key)
}).ToList();
}
private static KernelReturnParameterMetadata? ToReturnParameter()
{
return new KernelReturnParameterMetadata()
{
ParameterType = typeof(string),
};
}
private static Type ConvertParameterDataType(JsonSchemaProperty property, bool required)
{
var type = property.Type switch
{
"string" => typeof(string),
"integer" => typeof(int),
"number" => typeof(double),
"boolean" => typeof(bool),
"array" => typeof(List<string>),
"object" => typeof(Dictionary<string, object>),
_ => typeof(object)
};
return !required && type.IsValueType ? typeof(Nullable<>).MakeGenericType(type) : type;
}


public class JsonSchema
{
/// <summary>
/// The type of the schema, should be "object".
/// </summary>
[System.Text.Json.Serialization.JsonPropertyName("type")]
public string Type { get; set; } = "object";

/// <summary>
/// Map of property names to property definitions.
/// </summary>
[System.Text.Json.Serialization.JsonPropertyName("properties")]
public Dictionary<string, JsonSchemaProperty>? Properties { get; set; }

/// <summary>
/// List of required property names.
/// </summary>
[System.Text.Json.Serialization.JsonPropertyName("required")]
public List<string>? Required { get; set; }
}

public class JsonSchemaProperty
{
/// <summary>
/// The type of the property. Should be a JSON Schema type and is required.
/// </summary>
[System.Text.Json.Serialization.JsonPropertyName("type")]
public string Type { get; set; } = string.Empty;

/// <summary>
/// A human-readable description of the property.
/// </summary>
[System.Text.Json.Serialization.JsonPropertyName("description")]
public string? Description { get; set; } = string.Empty;
}

McpClient中提供的Tool转换为Kernel Function:

1
2
3
4
using Microsoft.SemanticKernel.Connectors.OpenAI;
// Add the MCP tools as Kernel functions
var functions = await githubMcpClient.MapToFunctionsAsync();
functions.Display();
1
2
3
string[] needFunctions = ["search_repositories","list_commits","list_issues"];

var filteredFunctions = functions.Where(f => needFunctions.Contains(f.Name));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
kernel.Plugins.Clear();

kernel.Plugins.AddFromFunctions("GitHub", filteredFunctions);

// Enable automatic function calling
var executionSettings = new OpenAIPromptExecutionSettings
{
Temperature = 0,
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};

// Test using GitHub tools
var prompt = "Summarize the last four commits to the microsoft/semantic-kernel repository?";
var result = await kernel.InvokePromptAsync(prompt, new(executionSettings)).ConfigureAwait(false);
Console.WriteLine($"\n\n{prompt}\n{result}");

以下是请求日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
{
"temperature": 0,
"tools": [
{
"function": {
"description": "Search for GitHub repositories",
"name": "GitHub-search_repositories",
"strict": false,
"parameters": {
"type": "object",
"required": [
"query"
],
"properties": {
"query": {
"description": "Search query (see GitHub search syntax)",
"type": "string"
},
"page": {
"description": "Page number for pagination (default: 1)",
"type": "number"
},
"perPage": {
"description": "Number of results per page (default: 30, max: 100)",
"type": "number"
}
}
}
},
"type": "function"
},
{
"function": {
"description": "Get list of commits of a branch in a GitHub repository",
"name": "GitHub-list_commits",
"strict": false,
"parameters": {
"type": "object",
"required": [
"owner",
"repo"
],
"properties": {
"owner": {
"type": "string"
},
"repo": {
"type": "string"
},
"sha": {
"type": "string"
},
"page": {
"type": "number"
},
"perPage": {
"type": "number"
}
}
}
},
"type": "function"
},
{
"function": {
"description": "List issues in a GitHub repository with filtering options",
"name": "GitHub-list_issues",
"strict": false,
"parameters": {
"type": "object",
"required": [
"owner",
"repo"
],
"properties": {
"owner": {
"type": "string"
},
"repo": {
"type": "string"
},
"direction": {
"type": "string"
},
"labels": {
"type": "array",
"items": {
"type": "string"
}
},
"page": {
"type": "number"
},
"per_page": {
"type": "number"
},
"since": {
"type": "string"
},
"sort": {
"type": "string"
},
"state": {
"type": "string"
}
}
}
},
"type": "function"
}
],
"messages": [
{
"role": "user",
"content": "Summarize the last four commits to the microsoft/semantic-kernel repository?"
}
],
"model": "gpt-4o",
"tool_choice": "auto"
}

响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"choices": [
{
"content_filter_results": {},
"finish_reason": "tool_calls",
"index": 0,
"logprobs": null,
"message": {
"content": null,
"refusal": null,
"role": "assistant",
"tool_calls": [
{
"function": {
"arguments": "{\"owner\":\"microsoft\",\"repo\":\"semantic-kernel\",\"perPage\":4}",
"name": "GitHub-list_commits"
},
"id": "call_b2BnIqDpUYSif6pIY4utniHQ",
"type": "function"
}
]
}
}
],
"created": 1742439180,
"id": "chatcmpl-BD0Jg2uuT5WbiuW1Wkjy5howQAOvZ",
"model": "gpt-4o-2024-08-06"
}

开发 MCP Server

  1. 创建控制台项目

  2. 添加Nuget 包

1
2
dotnet add package ModelContextProtocol --prerelease
dotnet add package Microsoft.Extensions.Hosting
  1. 修改Program.cs如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using ModelContextProtocol;
using ModelContextProtocol.Server;
using Microsoft.Extensions.Hosting;
using System.ComponentModel;
var builder = Host.CreateEmptyApplicationBuilder(settings: null);
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly();
await builder.Build().RunAsync();

[McpServerToolType]
public static class EchoTool
{
[McpServerTool, Description("Echoes the message back to the client.")]
public static string Echo(string message) => $"Hello {message}";
}
  1. 构建项目

连接至MCP Server

1
2
3
4
// 如果代码运行异常,请尝试使用这个版本`0.1.0-preview.2`
// #r "nuget:ModelContextProtocol,0.1.0-preview.2"

#r "nuget:ModelContextProtocol,*-*"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using ModelContextProtocol;
using ModelContextProtocol.Client;
using ModelContextProtocol.Configuration;
using ModelContextProtocol.Protocol.Transport;
using Microsoft.Extensions.Logging.Abstractions;

private static async Task<IMcpClient> GetDemoMcpClientAsync()
{
McpClientOptions clientOptions = new()
{
ClientInfo = new() { Name = "Demo", Version = "1.0.0" }
};

var serverConfig = new McpServerConfig
{
Id = "McpServer",
Name= "McpServer",
TransportType = "stdio",
TransportOptions = new Dictionary<string, string>
{
["command"] = "dotnet",
["arguments"] = "./McpServerDemo",
}
//如果是已经打包好了的exe,也可以直接使用["command"] = "./McpServerDemo.exe", ["arguments"]可以不写
};

var client = await McpClientFactory.CreateAsync(serverConfig, clientOptions);

return client;
}
1
2
3
4
var mcpClient = await GetDemoMcpClientAsync();
mcpClient.Display();
var tools = await mcpClient.ListToolsAsync();
tools.Display();
1
2
3
4
5
6
7
8
var result = await mcpClient.CallToolAsync(
toolName: "Echo",
arguments: new Dictionary<string, Object?>()
{
["message"] = "Mcp Server!"
});

result.Display();
作者

步步为营

发布于

2025-04-27

更新于

2025-04-27

许可协议