如果您需要深入交流了解請加入我們一塊交流
簡單介紹
在進(jìn)入教程之前我們可以先對MCP進(jìn)行一個大概的了解,MCP是什么?為什么要用MCP?MCP和Function Calling有什么區(qū)別?
MCP是什么?
Model Context Protocol (MCP) 是一個開放協(xié)議,它使 LLM 應(yīng)用與外部數(shù)據(jù)源和工具之間的無縫集成成為可能。無論你是構(gòu)建 AI 驅(qū)動的 IDE、改善 chat 交互,還是構(gòu)建自定義的 AI 工作流,MCP 提供了一種標(biāo)準(zhǔn)化的方式,將 LLM 與它們所需的上下文連接起來。

- MCP Hosts: 想要通過MCP訪問數(shù)據(jù)的程序,如Claude Desktop、ide或AI工具
- MCP Clients: 與服務(wù)器保持1:1連接的協(xié)議客戶端
- MCP Servers: 輕量級程序,每個程序都通過標(biāo)準(zhǔn)化的模型上下文協(xié)議公開特定的功能
- Local Data Sources: MCP服務(wù)器可以安全訪問的計算機文件、數(shù)據(jù)庫和服務(wù)
- Remote Services: MCP服務(wù)器可以連接的internet上可用的外部系統(tǒng)(例如通過api)
為什么要用MCP?
- 擴展LLM的能力:讓模型能夠訪問最新數(shù)據(jù)、專有信息和本地資源
- 數(shù)據(jù)私密性和安全性:數(shù)據(jù)可以保留在本地或受控環(huán)境中,減少敏感信息共享的風(fēng)險
- 工具集成:使LLM能夠調(diào)用和控制外部工具,擴展其功能范圍
- 減少幻覺:通過提供準(zhǔn)確的上下文信息,降低模型生成虛假內(nèi)容的可能性
- 標(biāo)準(zhǔn)化:為開發(fā)者提供一致的接口,簡化集成過程
- 可擴展性:支持從簡單用例到復(fù)雜企業(yè)級應(yīng)用的多種場景
- 跨語言的能力:通過標(biāo)準(zhǔn)的MCP協(xié)議我們可以使用不同語言提供的MCPServer的能力
MCP和Function Calling有什么區(qū)別?
我聽過最多人問的就是MCP和Function Calling有什么區(qū)別?
| 特性 | MCP | Function Calling |
|---|---|---|
| 基礎(chǔ)關(guān)系 | 基于Function Calling構(gòu)建并擴展 | 作為MCP的基礎(chǔ)技術(shù) |
| 設(shè)計范圍 | 更廣泛的協(xié)議,處理上下文獲取和工具使用 | 主要聚焦于調(diào)用特定函數(shù) |
| 架構(gòu) | 客戶端-服務(wù)器架構(gòu),支持分布式系統(tǒng) | 通常是API參數(shù)的直接定義和調(diào)用 |
| 數(shù)據(jù)處理 | 可以處理大型數(shù)據(jù)集和復(fù)雜上下文 | 主要處理結(jié)構(gòu)化的函數(shù)參數(shù)和返回值 |
| 上下文管理 | 專門設(shè)計用于管理和提供上下文 | 上下文管理不是核心功能 |
| 標(biāo)準(zhǔn)化程度 | 開放協(xié)議,旨在跨不同系統(tǒng)和模型標(biāo)準(zhǔn)化 | 實現(xiàn)方式因平臺而異,標(biāo)準(zhǔn)化程度較低 |
| 應(yīng)用場景 | 適用于需要復(fù)雜上下文和工具集成的場景 | 適用于調(diào)用預(yù)定義函數(shù)執(zhí)行特定任務(wù) |
簡而言之,MCP(Model Context Protocol)是在Function Calling基礎(chǔ)上發(fā)展而來的更全面的協(xié)議,它不僅保留了Function Calling的核心功能,還擴展了上下文獲取和管理能力,為LLM提供更豐富、更標(biāo)準(zhǔn)化的環(huán)境交互能力。
開始MCPClient基礎(chǔ)教程
前提條件,您需要先將上一個教程的MCPServer跑起來一會才能進(jìn)行當(dāng)前步驟,因為MCPClient是需要MCPServer提供Tools,下面我默認(rèn)您已經(jīng)運行了MCPServer,
然后可以訪問 https://account.coreshub.cn/signup?invite=ZmpMQlZxYVU= 獲取免費的DeepSeek-V3模型和白嫖GPU服務(wù)器,通過上面連接注冊以后訪問
https://console.coreshub.cn/xb3/maas/global-keys/ 地址獲取到APIKey
然后記住我們的APIKey,開始下面的旅途
-
創(chuàng)建屬于您的MCPClient程序
創(chuàng)建一個McpClient的控制臺項目

給項目添加依賴包
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.44.0" />
<PackageReference Include="ModelContextProtocol" Version="0.1.0-preview.4" />
</ItemGroup>
下面的教程我們將使用Microsoft.SemanticKernel開發(fā),然后依賴ModelContextProtocol官方的包,然后我們需要用到Microsoft.Extensions.Hosting
開始我們的代碼開發(fā)
-
擴展SemanticKernel的MCPClient
由于SemnaticKernel默認(rèn)是不能直接使用MCP的功能,那么我們需要先對它進(jìn)行擴展
創(chuàng)建幾個基礎(chǔ)類
JsonSchema.cs
/// <summary>
/// Represents a JSON schema for a tool's input arguments.
/// <see >See the schema for details</see>
/// </summary>
internal class JsonSchema
{
/// <summary>
/// The type of the schema, should be "object".
/// </summary>
[JsonPropertyName("type")]
public string Type { get; set; } = "object";
/// <summary>
/// Map of property names to property definitions.
/// </summary>
[JsonPropertyName("properties")]
public Dictionary<string, JsonSchemaProperty>? Properties { get; set; }
/// <summary>
/// List of required property names.
/// </summary>
[JsonPropertyName("required")]
public List<string>? Required { get; set; }
}
JsonSchemaProperty.cs
/// <summary>
/// Represents a property in a JSON schema.
/// <see >See the schema for details</see>
/// </summary>
internal class JsonSchemaProperty
{
/// <summary>
/// The type of the property. Should be a JSON Schema type and is required.
/// </summary>
[JsonPropertyName("type")]
public string Type { get; set; } = string.Empty;
/// <summary>
/// A human-readable description of the property.
/// </summary>
[JsonPropertyName("description")]
public string? Description { get; set; } = string.Empty;
}
接下來創(chuàng)建MCP的擴展類,用于將MCPFunction進(jìn)行轉(zhuǎn)換成SemanticKernel Function
ModelContextProtocolExtensions.cs
/// <summary>
/// Extension methods for ModelContextProtocol
/// </summary>
internal static class ModelContextProtocolExtensions
{
/// <summary>
/// Map the tools exposed on this <see cref="IMcpClient"/> to a collection of <see cref="KernelFunction"/> instances for use with the Semantic Kernel.
/// <param name="mcpClient">The <see cref="IMcpClient"/>.</param>
/// <param name="cancellationToken">The optional <see cref="CancellationToken"/>.</param>
/// </summary>
internal static async Task<IReadOnlyList<KernelFunction>> MapToFunctionsAsync(this IMcpClient mcpClient, CancellationToken cancellationToken = default)
{
var functions = new List<KernelFunction>();
foreach (var tool in await mcpClient.ListToolsAsync(cancellationToken).ConfigureAwait(false))
{
functions.Add(tool.ToKernelFunction(mcpClient, cancellationToken));
}
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)
{
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.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;
}
}
然后創(chuàng)建SemanticKernel的擴展,對SKFunction進(jìn)行擴展
KernelExtensions.cs
/// <summary>
/// Extension methods for KernelPlugin
/// </summary>
public static class KernelExtensions
{
private static readonly ConcurrentDictionary<string, IKernelBuilderPlugins> SseMap = new();
/// <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]", "_");
}
}
-
實現(xiàn)連接MCPServer的Tools
現(xiàn)在我們就可以實現(xiàn)核心的代碼了,打開我們的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<Program>();
var kernelBuilder = builder.Services.AddKernel()
.AddOpenAIChatCompletion("DeepSeek-V3", new Uri("https://openapi.coreshub.cn/v1"), "您使用的https://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<Kernel>();
var chatCompletion = app.Services.GetService<IChatCompletionService>();
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, "下面如果需要計算倆個數(shù)的和,請使用我提供的工具。"),
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,在此之前需要先修改代碼中的參數(shù)改成自己的,然后先啟動MCPServer以后,在啟動我們的MCPClient
執(zhí)行以后提問1+1=?然后會得到一下的結(jié)果

在執(zhí)行之前我們可以先在MCPServer中的算法函數(shù)Debug,這樣就可以清晰的看到進(jìn)入的流程了

通過上面的案例,恭喜您您已經(jīng)熟練的掌握了MCPServer+MCPClient+SemanticKernel的入門級教程
當(dāng)然如果您需要更加深入的了解AI相關(guān)的教程,您可以與我們連續(xù)加入我們的AI VIP得到更多的AI相關(guān)的教程和幫助。
浙公網(wǎng)安備 33010602011771號