MCP研讨会指南-在中构建服务器和客户端。净值8
本综合指南融合了模型理论 上下文 协议(MCP),以及用C#实现MCP服务器和客户端的实用教程。 材料分为章节,因此您可以按顺序阅读或直接跳到所需的部分。
第1章:理解模型上下文协议
1.1什么是MCP?
这 Model 上下文 协议(MCP) 是将人工智能模型与外部信息、工具或资源连接起来的开放标准。在MCP系统中,有两个角色:
- MCP服务器 –通过以下方式公开功能 资源, 工具 和 提示 1工具是模型可以调用的函数,资源提供数据,提示提供额外的上下文或指令。
- MCP客户端 –通常是通过JSON-RPC调用服务器工具的语言模型代理。客户端向服务器发送工具请求,并将JSON响应传递回模型。模型决定在对话中何时调用工具。
由于模型是无状态的,客户端在每次请求时都会重新发送相关消息和工具结果。这种模式允许AI“记住”之前的交互,而无需在服务器上持久化状态。
1.2资源、工具和提示
MCP规范将服务器功能分为三类 1:
| 能力 | 目的 |
|---|---|
| 资源 | 暴露文件、数据库或API等数据源;模型可以读取或搜索它们。 |
| 工具 | 执行操作(计算、API调用、文件操作)的函数。本指南中的大多数示例都使用工具。 |
| 提示 | 为模型提供上下文或指令的预定义字符串。 |
在许多简单的集成中,只需要工具。当您想向模型提供大型数据集时,资源很有用,提示可以指导模型的行为。
1.3服务器职责
MCP服务器必须:
- 注册工具,以便客户可以发现它们。在。NET SDK,你可以用
[McpServerToolType]和方法[McpServerTool]表明它们是工具 2. - 将JSON-RPC调用分派到相应的工具方法并返回结果或错误。SDK为您处理消息传递和调度。
- 如果使用标准输入/输出(STDIO)传输,请将消息记录到stderr,以避免损坏JSON-RPC流 1.
1.4客户责任
客户端充当语言模型和服务器之间的桥梁:
- 连接到服务器。 您提供运输对象(例如。
StdioClientTransport或HTTP传输),其知道如何启动或到达服务器。客户使用McpClientFactory.CreateAsync以建立连接。 - 发现工具。 连接后,呼叫
ListToolsAsync()获取可用工具列表 3每个工具都表示为McpClientTool(来源于AIFunction)它可以传递给你的AI模型。 - 与AI模型集成。 客户端维护聊天历史记录。对于每条用户消息,您将历史记录和工具列表发送到模型。该模型可能会返回一个函数调用,然后客户端在服务器上调用该函数。使用
ChatClientBuilder和.UseFunctionInvocation()从AI扩展库中自动执行此模式 4. - 保持上下文。 因为语言模型是无状态的,所以在每个请求中始终包含对话历史和任何工具输出。
1.5通信和运输
MCP使用JSON-RPC 2.0进行通信。请求包括方法名称(要调用的工具)、参数和id。服务器会返回结果或错误。传输决定了消息的传输方式:
- 工作室: 服务器和客户端通过标准输入和输出流进行通信。这便于本地开发或与桌面应用程序集成。使用
StdioClientTransport在客户和WithStdioServerTransport在服务器上。 - HTTP: 服务器公开一个HTTP端点;客户端发送POST请求。这对于远程或云部署非常有用。
- 定制运输: 如果您需要WebSockets或其他协议,MCP允许自定义传输。
1.6注册表和策划的服务器列表(添加到第1章末尾)
使用这些目录发现MCP服务器、示例和您自己的集成想法:
- 公司目录(请求和注册)\
- GitHub MCP注册表\
- 社区维护的列表\
第2章-使用官方SDK构建MCP服务器和客户端
本章将引导您使用C#创建MCP服务器和客户端 ModelContextProtocol 图书馆。该示例使用STDIO传输,因此客户端可以在本地启动服务器进程。所有NuGet包都被固定到一组经过测试的一致版本上。
2.1先决条件(更新)
- .NET 8 SDK 或更新版本\
验证已安装的SDK: dotnet --list-sdks
- Visual Studio Code
- 下载Visual Studio代码-Mac、Linux、Windows
- VS代码扩展
- C (ms-dotnetools.charp) - C#开发工具包 (ms-dotnetools.cdevkit) - GitHub Copilot ()
- NuGet包版本 (本章稍后安装)
- ModelContextProtocol 0.1.0-复习。8 - Microsoft.Extensions.AI 9.7.0 - Microsoft.Extensions.AI.Ollama 9.7.0-回顾1.25356.2 (可选,适用于本地型号) - Microsoft.Extensions.Logging / Microsoft.Extensions.Logging.Console 9.0.8
2.2项目设置
使用两个控制台应用程序创建解决方案——一个用于服务器,一个用于客户端:
dotnet new sln --name McpWorkshop dotnet new console -n MCPServer dotnet new console -n MCPClient dotnet sln McpWorkshop.sln add MCPServer/MCPServer.csproj MCPClient/MCPClient.csproj安装服务器所需的软件包:
cd MCPServerdotnet add package ModelContextProtocol --version 0.1.0-preview.8dotnet add package Microsoft.Extensions.Hosting --version 9.0.8dotnet add package Microsoft.Extensions.Logging --version 9.0.8dotnet add package Microsoft.Extensions.Logging.Console --version 9.0.8为客户端安装软件包:
cd ../MCPClientdotnet add package ModelContextProtocol --version 0.1.0-preview.8dotnet add package Microsoft.Extensions.AI --version 9.7.0dotnet add package Microsoft.Extensions.Logging --version 9.0.8dotnet add package Microsoft.Extensions.Logging.Console --version 9.0.8如果您打算通过Ollama使用本地型号,请同时安装 Microsoft.Extensions.AI.Ollama 9.7.0-回顾1.25356.2.对于OpenAI集成,请安装 Microsoft.Extensions.AI.OpenAI 9.7.x如第3章所示。
dotnet add package Microsoft.Extensions.AI.Ollama --version 9.7.0-preview.1.25356.2故障排除-缺少NuGet包源(api.nuget.org)
一些公司环境禁用默认的公共NuGet提要。如果 dotnet add package ... 失败与 Unable to load the service index for source https://api.nuget.org/v3/index.json 或者你只看到一个公司Artifactory源代码,显式添加公共提要:
- 添加NuGet.org源代码
dotnet nuget add source https://api.nuget.org/v3/index.json --name "nuget.org"或 2\. 从NuGet.org显式安装软件包
添加 --source https://api.nuget.org/v3/index.json 到你的 dotnet add package 命令,如下所示:
dotnet add package ModelContextProtocol --version 0.1.0-preview.8 --source https://api.nuget.org/v3/index.json- 可选:定义一个最小全局
NuGet.config\
把这个放在 %AppData%\NuGet\NuGet.config (Windows)或 ~/.config/NuGet/NuGet.config (Linux/macOS),或者如果您更喜欢repo本地配置,则可以与您的解决方案一起使用:
2.3实现服务器
cd ..code .替换 MCPServer/Program.cs 使用以下代码。它使用通用主机注册MCP服务器,该服务器通过STDIO进行通信,并自动发现当前程序集中的工具 5.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
var builder = Host.CreateApplicationBuilder(args);
// Configure logging to write to stderr (important for STDIO transport)
builder.Logging.AddConsole(options =>
{
options.LogToStandardErrorThreshold = LogLevel.Information;
});
// Register the MCP server and use STDIO as the transport
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly();
await builder.Build().RunAsync();呼叫 .WithToolsFromAssembly() 扫描组件以查找装饰方法 [McpServerToolType] 和 [McpServerTool] 2. 在单独文件中定义的工具会自动注册。
2.3.1定义工具
创建文件夹 MCPTools 在服务器项目中,添加您的工具类。每个类都应该是静态的,并带有注释 [McpServerToolType],并且每种方法都应注释为 [McpServerTool] 并且可选地, DescriptionAttribute:
文件: MCPTools/EchoTools.cs
using System.ComponentModel;
using ModelContextProtocol.Server;
namespace MCPServer.MCPTools;
[McpServerToolType]
public static class EchoTools
{
[McpServerTool, Description("Echoes your message back.")]
public static string Echo(string message) => message;
[McpServerTool, Description("Reverses the string you provide.")]
public static string ReverseEcho(string message) => new string(message.Reverse().ToArray());
}文件: MCPTools/TimeTools.cs
using System.ComponentModel;
using ModelContextProtocol.Server;
namespace MCPServer.MCPTools;
[McpServerToolType]
public static class TimeTools
{
[McpServerTool, Description("Returns the current UTC time.")]
public static DateTime GetUtcNow() => DateTime.UtcNow;
}2.3.2 MCP检验员测试(可选)
这 MCP检查员 允许您从web界面探索和调用您的工具。通过npm安装,在一个终端上运行服务器,然后运行 npx @modelcontextprotocol/inspector dotnet run 在另一个。打开提供的URL以列出并调用工具。
npm install -g @modelcontextprotocol/inspectorcd MCPServernpx @modelcontextprotocol/inspector dotnet run- 浏览器以以下方式打开
http://localhost:6274/ - 点击
Connect - 点击
List Tools - 选择工具并对其进行测试
2.3.3服务器与VS Code Agent模式集成
一旦您验证了您的服务器可以使用MCP Inspector,您就可以将其添加到Visual Studio代码中,并直接在 代理模式 聊天体验。代理模式运行一个大型语言模型,可以访问MCP工具;下面的步骤显示了如何连接本地服务器。
步骤1:构建您的服务器。跑 dotnet build 在 MCPServer 项目,以确保服务器可以启动。当通过STDIO启动服务器时,VS Code将调用此命令。
步骤2-将服务器添加到VS代码中。VS Code通过 mcp.json 文件或通过 MCP:添加服务器 命令。要使配置成为工作区的一部分,请创建 .vscode 在解决方案中添加目录 mcp.json 这样地:
{
"servers": {
"demoServer": {
"type": "stdio",
"command": "dotnet",
"args": ["run", "--project", "${workspaceFolder}/MCPServer/MCPServer.csproj"]
}
}
}此配置告诉VSCode在需要时使用dotnet run命令运行服务器。这 ${workspaceFolder} 变量解析到工作区的根,使路径在机器之间可移植。当您打开包含此文件的项目时,VS Code会在启动之前提示您确认是否信任该服务器 6,然后发现服务器的工具并将其缓存以供后续会话使用 7。或者,按 Ctrl+Shift+P (或 ⌘+Shift+P 在macOS上)并运行 MCP:添加服务器。选择 stdio 作为传输,请提供一个名称(例如 demoServer),将命令设置为.net,并将参数设置为run, --project,以及服务器项目的路径。VS Code为您编写配置 8.
步骤3–在代理模式下使用工具。打开 聊天 查看并选择 代理模式 从聊天窗格顶部的下拉菜单中 9。单击 工具 按钮显示可用工具列表并选择要启用的工具 10一次聊天最多可启用128个工具 11.键入提示,例如 “Reverse the string ‘hello world’”当模型决定调用工具时,VS Code会要求您确认调用;您可以为会话或所有未来的调用批准一次 12。您也可以通过键入直接引用工具 # 在提示中紧随其后的是它的名称 13。如果工具有输入参数,VS Code会显示一个表单,以便您在运行前查看或编辑值 14工具执行后,其结果出现在聊天中并成为上下文的一部分。
步骤4–管理您的服务器。使用 MCP:显示已安装的服务器 命令或Extensions视图的MCP Servers部分,用于启动、停止或重新启动服务器、查看日志或清除其缓存工具 15。如果更改服务器代码或添加新工具,请运行 MCP:重置缓存工具 因此,VS Code会重新加载服务器的功能 7请记住,MCP服务器可以执行任意代码;仅从可信来源添加服务器,并在运行前检查其配置 6.
2.4使用STDIO传输实现客户端
我们使用 StdioClientTransport 启动服务器进程并通过其通信 stdin/stdout 溪流。
以下是核心 MCPClient/Program.cs:
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using ModelContextProtocol;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Transport;
Console.WriteLine("MCP Client started.");
// Client metadata
var clientOptions = new McpClientOptions
{
ClientInfo = new() { Name = "mcp-demo-client", Version = "1.0.0" }
};
// Create a transport that runs the server project via dotnet
var transport = new StdioClientTransport(new StdioClientTransportOptions
{
Name = "Demo Server",
// Launch the server using dotnet; this avoids the need to hard‑code a path to the executable
Command = "dotnet",
Arguments = ["run", "--project", "../MCPServer/MCPServer.csproj"]
});
try
{
using var loggerFactory = LoggerFactory.Create(builder =>
builder.AddConsole().SetMinimumLevel(LogLevel.Information));
// Create the MCP client (this starts the server process)
await using var mcpClient =
await McpClientFactory.CreateAsync(transport, clientOptions, loggerFactory: loggerFactory);
// Discover tools
var tools = await mcpClient.ListToolsAsync();
// TODO: integrate with an AI model – see Chapters 3 and 4
}
catch (Exception ex)
{
Console.Error.WriteLine($"An error occurred: {ex.Message}");
}此时,客户端已启动服务器并列出了其工具(请参阅控制台输出)。 接下来的步骤取决于您要使用的模型,我们将在以下章节中介绍。
第3章与OpenAI集成
本章展示了如何使用以下工具将MCP服务器连接到OpenAI的模型 Microsoft.Extensions.AI.OpenAI 包裹。它假设您已经完成了第2章,并且已经有了服务器和客户端项目。
3.1先决条件
- 一 OpenAI API密钥 存储在名为的环境变量中
OPENAI_API_KEY. - 在您的 客户端 项目:
dotnet add package Microsoft.Extensions.AI.OpenAI --version 9.7.1-preview.1.25365.43.2 OpenAI的客户端实现
更新 MCPClient/Program.cs 用OpenAI客户端替换占位符AI客户端,并连接函数调用:
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using ModelContextProtocol;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Transport;
Console.WriteLine("MCP Client started.");
// Client metadata
var clientOptions = new McpClientOptions
{
ClientInfo = new() { Name = "mcp-demo-client", Version = "1.0.0" }
};
// Create a transport that runs the server project via dotnet
var transport = new StdioClientTransport(new StdioClientTransportOptions
{
Name = "Demo Server",
// Launch the server using dotnet; this avoids the need to hard‑code a path to the executable
Command = "dotnet",
Arguments = ["run", "--project", "../MCPServer/MCPServer.csproj"]
});
try
{
using var loggerFactory = LoggerFactory.Create(builder =>
builder.AddConsole().SetMinimumLevel(LogLevel.Information));
// Create the MCP client (this starts the server process)
await using var mcpClient =
await McpClientFactory.CreateAsync(transport, clientOptions, loggerFactory: loggerFactory);
// Read API key
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("OPENAI_API_KEY not set");
// Create the OpenAI chat client. Use a model like gpt-4o or gpt-3.5-turbo.
IChatClient openAiChatClient = new OpenAI.Chat.ChatClient("gpt-4o", apiKey).AsIChatClient();
// Build a higher‑level client with function invocation
IChatClient chatClient = new ChatClientBuilder(openAiChatClient)
.UseFunctionInvocation() // enables tool calls
.UseLogging(loggerFactory)
.Build();
// Discover tools
var tools = await mcpClient.ListToolsAsync();
// Chat loop
var history = new List();
Console.WriteLine("Ask a question (type 'exit' to quit):");
while (true)
{
Console.Write("\nYou: ");
var input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input)) continue;
if (input.Trim().ToLower() == "exit") break;
history.Add(new ChatMessage(ChatRole.User, input));
var options = new ChatOptions { Tools = [.. tools] };
var response = await chatClient.GetResponseAsync(history, options);
var assistant = response.Messages.LastOrDefault(m => m.Role == ChatRole.Assistant);
if (assistant != null)
{
Console.WriteLine("\nAI: " + string.Join(" ", assistant.Contents.Select(c => c.ToString())));
history.Add(assistant);
}
else
{
Console.WriteLine("\nAI: (no response)");
}
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"An error occurred: {ex.Message}");
}现在,您可以问诸如“反转字符串”hello world“,”现在几点了?“之类的问题,或者回显一些消息。模型可能会调用 ReverseEcho, GetUtcNow ,或 Echo 您之前定义的工具。如果模型没有选择调用工具,请尝试重新表述您的提示(例如“在'hello world'上使用反向工具”)。
Reverse the string "hello world"What time is it?Echo following message: "You are awesome!"3.3 OpenAI集成注意事项
- 确保所有
Microsoft.Extensions.AI.*套餐(包括OpenAI)是同一版本(在本例中为9.7.0)。版本不匹配会导致运行时错误。工作包清单为:
- 管理API的使用,以避免意外成本;对OpenAI API的每次调用都会计入您的代币配额。
- 尝试不同的模型(
gpt-3.5-turbo,gpt-4o)和参数(temperature,top‑p)控制模型调用函数的频率。
第4章使用Olama的局部模型
如果您希望避免外部API调用,则可以使用在本地运行大型语言模型 奥拉玛。本章总结了安装和集成步骤,并阐明了Windows特定的行为。
4.1安装Olama
Linux
运行官方安装程序脚本:
curl -fsSL https://ollama.com/install.sh | sh这将下载并安装运行库 6。或者,下载 tarball 并将其提取到 /usr:
curl -LO https://ollama.com/download/ollama-linux-amd64.tgz
sudo tar -C /usr -xzf ollama-linux-amd64.tgz
ollama serve & # start the server
ollama -v # verify installation[7]macOS
视窗
重要提示: 安装程序将Ollama注册为Windows服务,该服务在后台自动启动。你做 不 需要奔跑 ollama serve 手动。杀死 ollama.exe 进程将导致服务控制器重新启动它。
4.2牵引和运行模型
安装后,拉一个模型。例如,Llama 3.2型号:
ollama pull llama3.2:3b要以交互方式运行模型,请执行以下操作:
ollama run llama3.2要通过HTTP API为模型提供服务(Linux/macOS或Windows服务已在运行;仅在服务未自动启动的Linux/mcOS上):
ollama serveAPI可在http://localhost:11434 12。在Windows上,此服务会自动启动;无需自行运行service,您可以使用确认端口正在使用中 netstat。如果您看到属于ollama.exe的PID,则服务正在运行。
netstat -ano | findstr 114344.3将Olama整合到您的MCP客户中
使用 Microsoft.Extensions.AI.Ollama 软件包(版本 9.7.0-preview.1.25356.2)要连接到本地服务器,请执行以下操作:
dotnet add package Microsoft.Extensions.AI.Ollama --version 9.7.0-preview.1.25356.2using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using ModelContextProtocol;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Transport;
try
{
Console.WriteLine("MCP Client started.");
// Client metadata
var clientOptions = new McpClientOptions
{
ClientInfo = new() { Name = "mcp-demo-client", Version = "1.0.0" }
};
// Create a transport that runs the server project via dotnet
var transport = new StdioClientTransport(new StdioClientTransportOptions
{
Name = "Demo Server",
// Launch the server using dotnet; this avoids the need to hard‑code a path to the executable
Command = "dotnet",
Arguments = ["run", "--project", "../MCPServer/MCPServer.csproj"]
});
using var loggerFactory = LoggerFactory.Create(builder =>
builder.AddConsole().SetMinimumLevel(LogLevel.Information));
// Create the MCP client (this starts the server process)
await using var mcpClient =
await McpClientFactory.CreateAsync(transport, clientOptions, loggerFactory: loggerFactory);
// Connect to the local Ollama server and specify a model
IChatClient ollamaClient = new OllamaChatClient(
new Uri("http://localhost:11434/"),
"llama3.2:3b"
);
// Build a chat client with function invocation
IChatClient chatClient = new ChatClientBuilder(ollamaClient)
.UseFunctionInvocation() // enables tool calls
.UseLogging(loggerFactory)
.Build();
// Discover tools
var tools = await mcpClient.ListToolsAsync();
// Chat loop
var history = new List();
Console.WriteLine("Ask a question (type 'exit' to quit):");
while (true)
{
Console.Write("\nYou: ");
var input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input)) continue;
if (input.Trim().ToLower() == "exit") break;
history.Add(new ChatMessage(ChatRole.User, input));
var options = new ChatOptions { Tools = [.. tools] };
var response = await chatClient.GetResponseAsync(history, options);
var assistant = response.Messages.LastOrDefault(m => m.Role == ChatRole.Assistant);
if (assistant != null)
{
Console.WriteLine("\nAI: " + string.Join(" ", assistant.Contents.Select(c => c.ToString())));
history.Add(assistant);
}
else
{
Console.WriteLine("\nAI: (no response)");
}
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"An error occurred: {ex.Message}");
}确保Ollama服务器正在运行(或在Windows上,该服务处于活动状态)。然后,您可以运行MCP客户端并提问;本地模型将决定何时调用服务器的工具。
4.4最佳实践
- 不运行
ollama serve在Windows上;服务自动运行。使用netstat或PowerShellGet-NetTCPConnection验证该端口11434必定ollama.exe. - 型号需要大量的磁盘空间(此型号约为1.88 GB)。
- 在Windows上,它们存储在
%HOMEPATH%\.ollama13;在Linux和macOS上,它们位于~/.ollama. - 保持GPU驱动程序最新,特别是在Ollama使用硬件加速的Windows上 11.
- 使用
.UseFunctionInvocation()在构建聊天客户端时,您的模型可以自动调用工具 4.
第5章-故障排除
5.1端口冲突
如果您看到以下错误:
Error: listen tcp 127.0.0.1:11434: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.这意味着另一个进程(通常是Ollama的另一个实例)已经在使用端口11434。在Windows上,这是意料之中的,因为Ollama服务会自动运行。使用 netstat -ano 确认PID和 tasklist /FI "PID eq " 看看它是 ollama.exe。您不需要杀死或重新启动它;只需连接到 http://localhost:11434.
5.2缺少方法和版本不匹配
例外情况如下 System.MissingMethodException: Method not found: 'System.String Microsoft.Extensions.AI.ChatResponse.get_ChatThreadId() 表明不同 Microsoft.Extensions.AI 软件包的版本不兼容。通过将所有AI包对齐到同一版本来解决这个问题(例如。 9.7.0)并重新构建您的项目。避免混合预览和非预览版本,除非它们共享相同的版本号。
6 – 添加Azure DevOps测试用例工具
前面的章节展示了如何构建MCP服务器,在MCP检查器中对其进行测试,并将其与VS集成 代码的代理模式,并为本地和云LLM构建客户端。我们现在将使用 新工具 查询Azure DevOps从管道的最新成功构建中检索测试用例结果。
6.1概述和先决条件
这个工具可以让你的人工智能助手回答这样的问题:“ LoginTests 项目A管道中的测试用例?“通过调用Azure DevOps。为此,我们必须:
- 安装额外的NuGet包 在服务器项目中。
- 为服务器提供环境变量 对于Azure DevOps集合URL和PAT。
- 定义数据类型 (
TestCaseResult)对于返回的结果。 - 实施工具方法 接受项目名称、存储库、管道(定义)名称、可选分支(默认
main),以及测试用例标题子字符串;获取该分支上最新成功的构建;扫描其测试运行;按标题过滤结果;并返回结果和持续时间。 - 提示用户缺少参数 当需要时。
6.1.1安装所需的NuGet包
在您的 MCP服务器 添加Azure的目录 DevOps客户端库:
cd ../MCPServerdotnet add package Microsoft.TeamFoundationServer.Client --version 19.225.1这些套餐提供 VssConnection, BuildHttpClient,以及 TestManagementHttpClient.
6.1.2设置环境变量
在运行服务器之前,请设置:
AZURE_DEVOPS_COLLECTION_URL--您组织的集合URL(例如。https://dev.azure.com/my‑org).AZURE_DEVOPS_PAT--个人访问令牌 构建(读取) 和 测试管理(阅读) 范围。
不要将这些秘密提交给源代码管理。例如,在PowerShell中:
$env:AZURE_DEVOPS_COLLECTION_URL = "https://dev.azure.com/my-org"
$env:AZURE_DEVOPS_PAT = "your-token-here"西门子医疗:
$env:AZURE_DEVOPS_COLLECTION_URL = "https://apollo.siemens-healthineers.com/tfs/IKM.TPC.Projects/"在Linux/macOS上:
export AZURE_DEVOPS_COLLECTION_URL="https://dev.azure.com/my-org"
export AZURE_DEVOPS_PAT="your-token-here"6.1.3定义新的工具类
添加新文件 AzureDevOpsTools.cs 里面 MCPTools 服务器的文件夹。在课堂上标记 [McpServerToolType] 以及方法 [McpServerTool] 因此MCP主机会自动注册它。该方法使用Azure DevOps客户端API,用于定位构建和测试结果,并返回 List.
文件: MCPTools/AzureDevOpsTools.cs
using System.ComponentModel;
using Microsoft.Extensions.Logging;
using Microsoft.TeamFoundation.Build.WebApi;
using Microsoft.TeamFoundation.TestManagement.WebApi;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;
using ModelContextProtocol.Server;
namespace MCPServer.MCPTools;
public record TestCaseResult(string Title, string Outcome, double DurationMs, string? ErrorMessage = null, string? StackTrace = null);
public record GetTestCaseResultsResponse(
bool Success,
string LogMessages,
List TestResults,
string? ErrorMessage = null);
[McpServerToolType]
public class AzureDevOpsTools
{
///
/// Get test case results from the latest successful/partially successful build of a pipeline/definition.
///
[McpServerTool, Description("Retrieve test case results from the latest successful build of a pipeline/definition in Azure DevOps.")]
public static async Task GetTestCaseResultsAsync(
string projectName,
string definitionName,
string testCaseTitle)
{
var logMessages = new List();
var testResults = new List();
try
{
logMessages.Add($"Starting GetTestCaseResults for project: {projectName}, definition: {definitionName}, testCase: {testCaseTitle}");
// Validate inputs and elicit missing parameters
if (string.IsNullOrWhiteSpace(projectName))
return new GetTestCaseResultsResponse(false, string.Join("\n", logMessages), testResults, "Project name is required");
if (string.IsNullOrWhiteSpace(definitionName))
return new GetTestCaseResultsResponse(false, string.Join("\n", logMessages), testResults, "Definition (pipeline) name is required");
if (string.IsNullOrWhiteSpace(testCaseTitle))
return new GetTestCaseResultsResponse(false, string.Join("\n", logMessages), testResults, "Test case title is required");
// Read environment variables
string? collectionUrl = Environment.GetEnvironmentVariable("AZURE_DEVOPS_COLLECTION_URL");
string? pat = Environment.GetEnvironmentVariable("AZURE_DEVOPS_PAT");
if (string.IsNullOrWhiteSpace(collectionUrl) || string.IsNullOrWhiteSpace(pat))
return new GetTestCaseResultsResponse(false, string.Join("\n", logMessages), testResults, "AZURE_DEVOPS_COLLECTION_URL and AZURE_DEVOPS_PAT must be set");
logMessages.Add($"Using Azure DevOps collection URL: {collectionUrl}");
// Connect using PAT
var creds = new VssBasicCredential(string.Empty, pat);
var connection = new VssConnection(new Uri(collectionUrl), creds);
logMessages.Add("Connecting to Azure DevOps...");
var buildClient = await connection.GetClientAsync();
var testClient = await connection.GetClientAsync();
logMessages.Add("Successfully connected to Azure DevOps");
// Find build definition by name
logMessages.Add($"Looking for build definition: {definitionName}");
var definitions = await buildClient.GetDefinitionsAsync(project: projectName, name: definitionName);
var definition = definitions.FirstOrDefault();
if (definition == null)
return new GetTestCaseResultsResponse(false, string.Join("\n", logMessages), testResults, $"Build definition '{definitionName}' not found in project '{projectName}'.");
logMessages.Add($"Found build definition with ID: {definition.Id}");
logMessages.Add($"Getting latest successful or partially successful build...");
var builds = await buildClient.GetBuildsAsync(
project: projectName,
definitions: [definition.Id],
resultFilter: BuildResult.Succeeded | BuildResult.PartiallySucceeded,
statusFilter: BuildStatus.Completed,
branchName: null,
top: 1);
var build = builds.FirstOrDefault();
if (build == null)
{
// If no build found with the specified branch, let's try without branch filter to see what branches exist
logMessages.Add($"No builds found.");
return new GetTestCaseResultsResponse(false, string.Join("\n", logMessages), testResults,
$"No completed successful or partially successful build found for definition '{definitionName}'.");
}
logMessages.Add($"Found build ID: {build.Id}, Build Number: {build.BuildNumber}");
logMessages.Add("Getting test runs for build...");
var testRuns = await testClient.GetTestRunsAsync(projectName, buildUri: build.Uri.ToString());
logMessages.Add($"Found {testRuns.Count} test runs");
foreach (var run in testRuns)
{
var runResults = await testClient.GetTestResultsAsync(projectName, run.Id);
foreach (var r in runResults)
{
if (!string.IsNullOrWhiteSpace(r.TestCaseTitle) &&
r.TestCaseTitle.Contains(testCaseTitle, StringComparison.OrdinalIgnoreCase))
{
logMessages.Add($"Found matching test case: {r.TestCaseTitle}, Outcome: {r.Outcome}");
testResults.Add(new TestCaseResult(
r.TestCaseTitle!,
r.Outcome,
r.DurationInMs,
r.ErrorMessage,
r.StackTrace));
}
}
}
if (testResults.Count == 0)
{
logMessages.Add($"No test case results matching '{testCaseTitle}' were found");
return new GetTestCaseResultsResponse(false, string.Join("\n", logMessages), testResults,
$"No test case results matching '{testCaseTitle}' were found.");
}
logMessages.Add($"Successfully found {testResults.Count} matching test case results");
return new GetTestCaseResultsResponse(true, string.Join("\n", logMessages), testResults);
}
catch (Exception ex)
{
logMessages.Add($"ERROR: {ex.GetType().Name}: {ex.Message}");
logMessages.Add($"Stack trace: {ex.StackTrace}");
return new GetTestCaseResultsResponse(false, string.Join("\n", logMessages), testResults,
$"Exception occurred: {ex.GetType().Name}: {ex.Message}");
}
}
}笔记:
- 该方法从环境变量中读取集合URL和PAT。如果它们缺失,它会抛出一个异常,这样助手就可以提示用户设置它们。
- 它从其名称中检索构建定义ID,然后调用
GetBuildsAsync查找给定分支上最新的成功构建。 - 使用
GetTestRunsAsync和GetTestResultsAsync获取测试运行和结果。每个结果都暴露TestCaseTitle,Outcome和DurationInMs领域。 - 缺少的参数将通过显式检查
ArgumentException因此MCP运行时可以向用户询问缺失的值。
6.1.4测试工具
- 运行服务器 设置环境变量。
- MCP检验员测试 (可选):连接到您的服务器并呼叫
GetTestCaseResultsAsync具有真实的项目、管道和测试用例名称;检查器显示JSON结果。 - Visual Studio代码中的测试(代理 Mode):
- 确保您的服务器已在中注册 .vscode/mcp.json 或通过添加 _MCP:添加服务器_ (见第 2.3.3). 使用内置 .exe 路径和 stdio 运输。 - 在Agent聊天中,提出以下问题: - _“查找测试用例的结果 登录测试 在 WebApp‑CI 项目管道 我的项目.”_ - 法学硕士应决定致电 GetTestCaseResultsAsync。系统会要求您批准通话,然后显示结果。 - 如果助手报告缺少环境变量,请设置 AZURE_DEVOPS_COLLECTION_URL 和 AZURE_DEVOPS_PAT 并重新启动服务器。
MCP服务器的高级扩展到此结束。它演示了如何安全地访问外部服务(如Azure) DevOps)并将结构化数据返回给您的LLM助理。您可以将相同的模式应用于构建其他DevOps操作的工具(例如工作项查询、构建创建)。
7 – 使用Docker部署MCP服务器
将服务器容器化使得在任何安装了Docker的地方运行变得轻而易举,而不用担心。NET版本或主机依赖关系。本章展示了如何删除示例工具,为项目添加容器支持,构建本地映像并运行它(包括在VS中 代码代理模式)。服务器是 未发布到任何公共注册表--它仍然是本地的。
7.1从服务器中删除示例工具
在 MCPServer 项目、删除或排除您不打算发布的任何类(例如。 EchoTools.cs, TimeTools.cs).仅保留 AzureDevOpsTools.cs 所以 WithToolsFromAssembly() 仅注册您的Azure DevOps工具。
7.2在项目文件中启用内置容器支持
.NET 8可以在发布时自动构建Docker镜像。添加一个 到 MCPServer.csproj 如下图所示:
true
azuredevops/mcpserver
mcr.microsoft.com/dotnet/runtime:8.0-alpine
linux-x64
因为您的服务器使用STDIO并且不暴露网络端口,所以没有 ContainerPort 需要。
7.3构建Docker镜像
从 MCPServer 项目根目录,运行:
dotnet publish /t:PublishContainer -c Release这 /t:PublishContainer target构建您的项目,发布它,并创建一个标记有中指定名称的Docker映像 ContainerRepository。完成后,请确认:
docker images
# REPOSITORY TAG IMAGE ID CREATED SIZE
# azuredevops/mcpserver latest 此映像仅存在于您的本地计算机上;您尚未将其推送到注册表。
7.4在本地运行容器
运行容器并将Azure DevOps设置作为环境变量传递。例如:
docker run -i --rm \
-e AZURE_DEVOPS_COLLECTION_URL=https://dev.azure.com/my-org \
-e AZURE_DEVOPS_PAT=YOUR_PAT_TOKEN \
azuredevops/mcpserver-i保持STDIN打开(MCP的stdio传输所需)。--rm停止后取出容器。- 使用
--env-file相反,如果您更喜欢从文件加载变量。
当容器运行时,它会启动MCP服务器并等待STDIO上的请求。
7.5(替代)多级Dockerfile
如果您更喜欢自己管理Dockerfile(例如,自定义构建步骤或支持较旧的SDK版本),请创建 Dockerfile 在您的解决方案根:
# Build stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MCPServer/MCPServer.csproj", "MCPServer/"]
RUN dotnet restore "MCPServer/MCPServer.csproj"
COPY . .
WORKDIR "/src/MCPServer"
RUN dotnet publish "MCPServer.csproj" -c Release -o /app/publish
# Runtime stage
FROM mcr.microsoft.com/dotnet/runtime:8.0
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MCPServer.dll"]然后构建并运行:
docker build -t azuredevops/mcpserver .
docker run -i --rm -e AZURE_DEVOPS_COLLECTION_URL=... -e AZURE_DEVOPS_PAT=... azuredevops/mcpserver7.6在VS代码代理模式下使用Docker化服务器
要从VS Code(或任何MCP客户端)调用容器化服务器,请将客户端的命令指向 docker run 而不是 dotnet.对于VS 代码,添加到 .vscode/mcp.json:
{
"servers": {
"azuredevops-local": {
"type": "stdio",
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e", "AZURE_DEVOPS_COLLECTION_URL=https://dev.azure.com/my-org",
"-e", "AZURE_DEVOPS_PAT=YOUR_PAT_TOKEN",
"azuredevops/mcpserver"
]
}
}
}当您打开聊天>代理对话时,VS 代码将根据需要自动启动容器,并通过STDIO连接。你永远不必担心当地人。NET安装或文件路径。
7.7(可选)通过Docker MCP工具包运行
Docker桌面 _MCP工具包_ 提供了一个托管网关,可以运行容器并将其连接到多个客户端。要使用它而不发布到注册表,请执行以下操作:
- 在Docker桌面中启用MCP工具包(在“设置”>“测试版功能”中)。
- 在终端中,将您的映像加载到Docker Desktop中(在前面的步骤中,它已经是本地的)。
- 在Docker桌面中,打开 _MCP工具包_ → _目录_ → 添加本地服务器,然后选择您的
azuredevops/mcpserver图像。配置AZURE_DEVOPS_*配置选项卡中的变量(如果提供)。 - 通过在VS代码中添加以下内容,将您的客户端(例如VS代码)连接到MCP网关
mcp.json:
{
"servers": {
"MCP_DOCKER_GATEWAY": {
"type": "stdio",
"command": "docker",
"args": ["mcp", "gateway", "run"]
}
}
}Then run `docker mcp client connect vscode` to write `.vscode/mcp.json` automatically.因为你的镜像没有推送到Docker Hub,所以它对你的机器来说仍然是私有的。如果以后想与队友共享,请将其推送到私人注册表并更新 ContainerRepository 相应地命名。
7.8总结
通过启用。NET的内置容器支持或使用多级Dockerfile,您可以打包您的MCP服务器——现在只简化到您的 AzureDevOpsTools--变成一个轻量级的图像。运行它 docker run (传递必要的Azure DevOps环境变量)允许任何MCP客户端,包括VS 代码代理模式,可靠地使用您的工具,无需本地。NET运行时。
8 – 使用NuGet打包和发布MCP服务器
NuGet现在支持宿主 MCP服务器包.将服务器发布为包允许其他人通过NuGet搜索发现并安装它。本章将官方的NuGet快速入门应用于我们的Azure DevOps工具。
8.1准备 .mcp/server.json
这 .mcp/server.json 该文件定义了服务器的元数据和输入。更新如下(用您的信息替换占位符):
{
"description": "An MCP server that queries Azure DevOps test results",
"name": "io.github.yourusername/AzureDevOpsMcpServer",
"packages": [
{
"registry_name": "nuget",
"name": "YourUsername.AzureDevOpsMcpServer",
"version": "1.0.0",
"package_arguments": [],
"environment_variables": [
{ "name": "AZURE_DEVOPS_COLLECTION_URL", "description": "Base URL of your Azure DevOps organisation", "is_required": true, "is_secret": false },
{ "name": "AZURE_DEVOPS_PAT", "description": "Personal Access Token for Azure DevOps", "is_required": true, "is_secret": true }
]
}
],
"repository": {
"url": "https://github.com/yourusername/AzureDevOpsMcpServer",
"source": "github"
},
"version_detail": { "version": "1.0.0" }
}这 environment_variables 数组声明了工具所需的变量;像VS Code这样的主机会提示用户输入这些值。
8.2在项目文件中设置包ID
为确保您的包裹具有唯一标识符,请添加 到 MCPServer.csproj:
YourUsername.AzureDevOpsMcpServer
这与 name 领域 server.json.
8.3打包项目
跑吧 dotnet pack 生成NuGet包的命令。使用Release配置,使包包含优化的二进制文件:
dotnet pack -c Release这创建了一个 .nupkg 文件在 bin/Release 文件夹。
8.4发布包
若要私下共享服务器,请将包推送到测试源或内部NuGet服务器。避免发布到公共NuGet.org提要,除非您打算使您的服务器可公开发现。
dotnet nuget push bin/Release/*.nupkg --api-key --source https://int.nugettest.org/v3/index.json官方快速入门建议使用 NuGet测试环境 int.nugettest.org 在发布到生产之前。替换 --source 如果你有自己的内部订阅源的话。使用 --api-key 认证选项;从目标提要生成一个API密钥。
8.5消耗包装
一旦你的包被推送到NuGet提要,开发人员(或你)就可以在VS Code中安装它,而无需手动运行服务器:
- 访问提要(例如NuGet.org)并搜索类型为的包
mcpserver. - 打开包裹的详细信息页面并复制 MCP服务器 NuGet为VS代码生成的配置代码段。
- 将该代码段添加到您的工作区
.vscode/mcp.json.VS Code将自动下载包,并在首次使用时提示输入所需内容。
由于我们的车间包不供公众使用,请共享包文件(.nupkg)或者将其托管在内部NuGet服务器上。然后,参与者可以将提要URL添加到他们的 nuget.config 或者手动安装服务器包。
结论
本统一指南涵盖了模型上下文协议背后的理论,以及与官方实现MCP服务器和客户端的实际步骤。NET SDK,以及通过Ollama与OpenAI的云模型和本地模型集成的说明。它还展示了如何在MCP Inspector中验证和练习您的工具,并直接在VS Code的代理模式中尝试它们。最后,最后两章将介绍如何添加生产样式 Azure DevOps测试结果工具 (具有所需的输入和结构化的输出)以及 用Docker容器化MCP服务器 用于私人、可复制的本地运行。通过遵循这里提供的步骤和代码示例,您可以构建一个强大的研讨会或项目,演示大型语言模型如何在保持上下文和安全性的同时利用外部工具。
