简单介绍
简单介绍
在进入教程之前我们可以先对MCP进行一个大概的了解,MCP是什么?为什么要用MCP?MCP和Function Calling有什么区别?
MCP是什么?
Model Context Protocol (MCP) 是一个开放协议,它使 LLM 应用与外部数据源和工具之间的无缝集成成为可能。无论你是构建 AI 驱动的 IDE、改善 chat 交互,还是构建自定义的 AI 工作流,MCP 提供了一种标准化的方式,将 LLM 与它们所需的上下文连接起来。
- MCP Hosts: 想要通过MCP访问数据的程序,如Claude Desktop、ide或AI工具
- MCP Clients: 与服务器保持1:1连接的协议客户端
- MCP Servers: 轻量级程序,每个程序都通过标准化的模型上下文协议公开特定的功能
- Local Data Sources: MCP服务器可以安全访问的计算机文件、数据库和服务
- Remote Services: MCP服务器可以连接的internet上可用的外部系统(例如通过api)
为什么要用MCP?
- 扩展LLM的能力:让模型能够访问最新数据、专有信息和本地资源
- 数据私密性和安全性:数据可以保留在本地或受控环境中,减少敏感信息共享的风险
- 工具集成:使LLM能够调用和控制外部工具,扩展其功能范围
- 减少幻觉:通过提供准确的上下文信息,降低模型生成虚假内容的可能性
- 标准化:为开发者提供一致的接口,简化集成过程
- 可扩展性:支持从简单用例到复杂企业级应用的多种场景
- 跨语言的能力:通过标准的MCP协议我们可以使用不同语言提供的MCPServer的能力
MCP和Function Calling有什么区别?
我听过最多人问的就是MCP和Function Calling有什么区别?
| 特性 | MCP | Function Calling |
|---|---|---|
| 基础关系 | 基于Function Calling构建并扩展 | 作为MCP的基础技术 |
| 设计范围 | 更广泛的协议,处理上下文获取和工具使用 | 主要聚焦于调用特定函数 |
| 架构 | 客户端-服务器架构,支持分布式系统 | 通常是API参数的直接定义和调用 |
| 数据处理 | 可以处理大型数据集和复杂上下文 | 主要处理结构化的函数参数和返回值 |
| 上下文管理 | 专门设计用于管理和提供上下文 | 上下文管理不是核心功能 |
| 标准化程度 | 开放协议,旨在跨不同系统和模型标准化 | 实现方式因平台而异,标准化程度较低 |
| 应用场景 | 适用于需要复杂上下文和工具集成的场景 | 适用于调用预定义函数执行特定任务 |
简而言之,MCP(Model Context Protocol)是在Function Calling基础上发展而来的更全面的协议,它不仅保留了Function Calling的核心功能,还扩展了上下文获取和管理能力,为LLM提供更丰富、更标准化的环境交互能力。
开始MCPClient基础教程
前提条件,您需要先将上一个教程的MCPServer跑起来一会才能进行当前步骤,因为MCPClient是需要MCPServer提供Tools,下面我默认您已经运行了MCPServer,
然后可以访问 account.coreshub.cn/signup?invi… 获取免费的DeepSeek-V3模型和白嫖GPU服务器,通过上面连接注册以后访问
console.coreshub.cn/xb3/maas/gl… 地址获取到APIKey
然后记住我们的APIKey,开始下面的旅途
- 创建属于您的MCPClient程序
创建一个McpClient的控制台项目
给项目添加依赖包
下面的教程我们将使用Microsoft.SemanticKernel开发,然后依赖ModelContextProtocol官方的包,然后我们需要用到Microsoft.Extensions.Hosting
开始我们的代码开发
- 扩展SemanticKernel的MCPClient
由于SemnaticKernel默认是不能直接使用MCP的功能,那么我们需要先对它进行扩展
创建几个基础类
JsonSchema.cs
///
/// <summary>
/// Map of property names to property definitions.
/// </summary>
public Dictionary<string, JsonSchemaProperty>? Properties { get; set; }
/// <summary>
/// List of required property names.
/// </summary>
public List<string>? Required { get; set; }
JsonSchemaProperty.cs ///
/// <summary>
/// A human-readable description of the property.
/// </summary>
public string? Description { get; set; } = string.Empty;
接下来创建MCP的扩展类,用于将MCPFunction进行转换成SemanticKernel Function
ModelContextProtocolExtensions.cs
///
return functions;
private static KernelFunction ToKernelFunction(this McpClientTool tool, IMcpClient mcpClient, CancellationToken cancellationToken)
async Task<string> InvokeToolAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken ct)
// 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.AsReadOnly(),
cancellationToken: ct
).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)
await Console.Error.WriteLineAsync($"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 parameterType = function.Metadata.Parameters.FirstOrDefault(p => p.Name == name)?.ParameterType;
if (parameterType == null)
return value;
if (Nullable.GetUnderlyingType(parameterType) == typeof(int))
return Convert.ToInt32(value);
if (Nullable.GetUnderlyingType(parameterType) == typeof(double))
return Convert.ToDouble(value);
if (Nullable.GetUnderlyingType(parameterType) == typeof(bool))
return Convert.ToBoolean(value);
if (parameterType == typeof(List<string>))
return (value as IEnumerable<object>)?.ToList() ?? value;
if (parameterType == typeof(Dictionary<string, object>))
return (value as Dictionary<string, object>)?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) ?? value;
return value;
private static List<KernelParameterMetadata>? ToParameters(this McpClientTool tool)
var inputSchema = JsonSerializer.Deserialize<JsonSchema>(tool.JsonSchema.GetRawText());
var properties = inputSchema?.Properties;
if (properties == null)
return null;
HashSet<string> requiredProperties = [.. 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;
然后创建SemanticKernel的扩展,对SKFunction进行扩展
KernelExtensions.cs
///
/// <summary>
/// Creates a Model Content Protocol plugin from an SSE server that contains the specified MCP functions and adds it into the plugin collection.
/// </summary>
/// <param name="endpoint"></param>
/// <param name="serverName"></param>
/// <param name="cancellationToken">The optional <see cref="CancellationToken"/>.</param>
/// <param name="plugins"></param>
/// <returns>A <see cref="KernelPlugin"/> containing the functions.</returns>
public static async Task<IKernelBuilderPlugins> AddMcpFunctionsFromSseServerAsync(this IKernelBuilderPlugins plugins,
string endpoint, string serverName, CancellationToken cancellationToken = default)
var key = ToSafePluginName(serverName);
if (SseMap.TryGetValue(key, out var sseKernelPlugin))
return sseKernelPlugin;
var mcpClient = await GetClientAsync(serverName, endpoint, null, null, cancellationToken).ConfigureAwait(false);
var functions = await mcpClient.MapToFunctionsAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
cancellationToken.Register(() => mcpClient.DisposeAsync().ConfigureAwait(false).GetAwaiter().GetResult());
sseKernelPlugin = plugins.AddFromFunctions(key, functions);
return SseMap[key] = sseKernelPlugin;
private static async Task<IMcpClient> GetClientAsync(string serverName, string? endpoint,
Dictionary<string, string>? transportOptions, ILoggerFactory? loggerFactory,
CancellationToken cancellationToken)
var transportType = !string.IsNullOrEmpty(endpoint) ? TransportTypes.Sse : TransportTypes.StdIo;
McpClientOptions options = new()
ClientInfo = new()
Name = $"{serverName} {transportType}Client",
Version = "1.0.0"
var config = new McpServerConfig
Id = serverName.ToLowerInvariant(),
Name = serverName,
Location = endpoint,
TransportType = transportType,
TransportOptions = transportOptions
return await McpClientFactory.CreateAsync(config, options,
loggerFactory: loggerFactory ?? NullLoggerFactory.Instance, cancellationToken: cancellationToken);
// A plugin name can contain only ASCII letters, digits, and underscores.
private static string ToSafePluginName(string serverName)
return Regex.Replace(serverName, @"[^\w]", "_");
- 实现连接MCPServer的Tools
现在我们就可以实现核心的代码了,打开我们的Program.cs
using McpClient; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; using ChatMessageContent = Microsoft.SemanticKernel.ChatMessageContent;
#pragma warning disable SKEXP0010
var builder = Host.CreateEmptyApplicationBuilder(settings: null);
builder.Configuration .AddEnvironmentVariables() .AddUserSecrets();
var kernelBuilder = builder.Services.AddKernel() .AddOpenAIChatCompletion("DeepSeek-V3", new Uri("openapi.coreshub.cn/v1"), "您使用的openapi.coreshub.cn/v1平台的APIKey");
await kernelBuilder.Plugins.AddMcpFunctionsFromSseServerAsync("http://您的MCPServerip:端口/sse", "token");
Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("MCP Client Started!"); Console.ResetColor();
var app = builder.Build();
var kernel = app.Services.GetService(); var chatCompletion = app.Services.GetService();
PromptForInput(); while (Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase)) if (string.IsNullOrWhiteSpace(query)) PromptForInput(); continue;
var history = new ChatHistory
new ChatMessageContent(AuthorRole.System, "下面如果需要计算俩个数的和,请使用我提供的工具。"),
new ChatMessageContent(AuthorRole.User, query)
await foreach (var message in chatCompletion?.GetStreamingChatMessageContentsAsync(history,
new OpenAIPromptExecutionSettings()
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
}, kernel))
Console.Write(message.Content);
Console.WriteLine();
PromptForInput();
static void PromptForInput() Console.WriteLine("Enter a command (or 'exit' to quit):"); Console.ForegroundColor = ConsoleColor.Cyan; Console.Write("> "); Console.ResetColor();
然后开始启动我们的MCPClient,在此之前需要先修改代码中的参数改成自己的,然后先启动MCPServer以后,在启动我们的MCPClient
执行以后提问1+1=?然后会得到一下的结果
在执行之前我们可以先在MCPServer中的算法函数Debug,这样就可以清晰的看到进入的流程了
通过上面的案例,恭喜您您已经熟练的掌握了MCPServer+MCPClient+SemanticKernel的入门级教程