Token导航 LogoToken导航TokenDH.com
MCP Workshop Net logo
AI代理stdio官方级别未说明来源级核验

MCP Workshop Net

MCP Server

@modelcontextprotocol/inspector

一个用于在.NET 8中构建MCP服务器和客户端的工具,支持与AI模型集成和外部工具调用。

工具数

0

提示词数

0

GitHub Stars

3

资源数

0
开发工具C#VS CodeJavaScriptVS Code

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

作者 / 组织

PeterMilovcik

提供方

PeterMilovcik

最后核验

2026/5/17 20:22

运行时

Node.js

快速接入

先看主来源和安装命令,再打开仓库或文档;下面只保留这个条目的关键接入事实。

命令预览

npx @modelcontextprotocol/inspector dotnet run

详细介绍

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客户责任

客户端充当语言模型和服务器之间的桥梁:

  1. 连接到服务器。 您提供运输对象(例如。 StdioClientTransport 或HTTP传输),其知道如何启动或到达服务器。客户使用 McpClientFactory.CreateAsync 以建立连接。
  2. 发现工具。 连接后,呼叫 ListToolsAsync() 获取可用工具列表 3每个工具都表示为 McpClientTool (来源于 AIFunction)它可以传递给你的AI模型。
  3. 与AI模型集成。 客户端维护聊天历史记录。对于每条用户消息,您将历史记录和工具列表发送到模型。该模型可能会返回一个函数调用,然后客户端在服务器上调用该函数。使用 ChatClientBuilder.UseFunctionInvocation() 从AI扩展库中自动执行此模式 4.
  4. 保持上下文。 因为语言模型是无状态的,所以在每个请求中始终包含对话历史和任何工具输出。

1.5通信和运输

MCP使用JSON-RPC 2.0进行通信。请求包括方法名称(要调用的工具)、参数和id。服务器会返回结果或错误。传输决定了消息的传输方式:

  • 工作室: 服务器和客户端通过标准输入和输出流进行通信。这便于本地开发或与桌面应用程序集成。使用 StdioClientTransport 在客户和 WithStdioServerTransport 在服务器上。
  • HTTP: 服务器公开一个HTTP端点;客户端发送POST请求。这对于远程或云部署非常有用。
  • 定制运输: 如果您需要WebSockets或其他协议,MCP允许自定义传输。

1.6注册表和策划的服务器列表(添加到第1章末尾)

使用这些目录发现MCP服务器、示例和您自己的集成想法:

  • 公司目录(请求和注册)\

Healthineers MCP注册中心

  • GitHub MCP注册表\

MCP注册表

  • 社区维护的列表\

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 MCPServer
dotnet add package ModelContextProtocol --version 0.1.0-preview.8
dotnet add package Microsoft.Extensions.Hosting --version 9.0.8
dotnet add package Microsoft.Extensions.Logging --version 9.0.8
dotnet add package Microsoft.Extensions.Logging.Console --version 9.0.8

为客户端安装软件包:

cd ../MCPClient
dotnet add package ModelContextProtocol --version 0.1.0-preview.8
dotnet add package Microsoft.Extensions.AI --version 9.7.0
dotnet add package Microsoft.Extensions.Logging --version 9.0.8
dotnet 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源代码,显式添加公共提要:

  1. 添加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
  1. 可选:定义一个最小全局 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/inspector
cd MCPServer
npx @modelcontextprotocol/inspector dotnet run
  • 浏览器以以下方式打开 http://localhost:6274/
  • 点击 Connect
  • 点击 List Tools
  • 选择工具并对其进行测试

2.3.3服务器与VS Code Agent模式集成

一旦您验证了您的服务器可以使用MCP Inspector,您就可以将其添加到Visual Studio代码中,并直接在 代理模式 聊天体验。代理模式运行一个大型语言模型,可以访问MCP工具;下面的步骤显示了如何连接本地服务器。

步骤1:构建您的服务器。dotnet buildMCPServer 项目,以确保服务器可以启动。当通过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.4

3.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 serve

API可在http://localhost:11434 12。在Windows上,此服务会自动启动;无需自行运行service,您可以使用确认端口正在使用中 netstat。如果您看到属于ollama.exe的PID,则服务正在运行。

netstat -ano | findstr 11434

4.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.2
using 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 或PowerShell Get-NetTCPConnection 验证该端口 11434 必定 ollama.exe.
  • 型号需要大量的磁盘空间(此型号约为1.88 GB)。
  • 在Windows上,它们存储在 %HOMEPATH%\.ollama 13;在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。为此,我们必须:

  1. 安装额外的NuGet包 在服务器项目中。
  2. 为服务器提供环境变量 对于Azure DevOps集合URL和PAT。
  3. 定义数据类型 (TestCaseResult)对于返回的结果。
  4. 实施工具方法 接受项目名称、存储库、管道(定义)名称、可选分支(默认 main),以及测试用例标题子字符串;获取该分支上最新成功的构建;扫描其测试运行;按标题过滤结果;并返回结果和持续时间。
  5. 提示用户缺少参数 当需要时。

6.1.1安装所需的NuGet包

在您的 MCP服务器 添加Azure的目录 DevOps客户端库:

cd ../MCPServer
dotnet 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 查找给定分支上最新的成功构建。
  • 使用 GetTestRunsAsyncGetTestResultsAsync 获取测试运行和结果。每个结果都暴露 TestCaseTitle, OutcomeDurationInMs 领域。
  • 缺少的参数将通过显式检查 ArgumentException 因此MCP运行时可以向用户询问缺失的值。

6.1.4测试工具

  1. 运行服务器 设置环境变量。
  2. MCP检验员测试 (可选):连接到您的服务器并呼叫 GetTestCaseResultsAsync 具有真实的项目、管道和测试用例名称;检查器显示JSON结果。
  3. Visual Studio代码中的测试(代理 Mode):

- 确保您的服务器已在中注册 .vscode/mcp.json 或通过添加 _MCP:添加服务器_ (见第 2.3.3). 使用内置 .exe 路径和 stdio 运输。 - 在Agent聊天中,提出以下问题: - _“查找测试用例的结果 登录测试WebApp‑CI 项目管道 我的项目.”_ - 法学硕士应决定致电 GetTestCaseResultsAsync。系统会要求您批准通话,然后显示结果。 - 如果助手报告缺少环境变量,请设置 AZURE_DEVOPS_COLLECTION_URLAZURE_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/mcpserver

7.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工具包_ 提供了一个托管网关,可以运行容器并将其连接到多个客户端。要使用它而不发布到注册表,请执行以下操作:

  1. 在Docker桌面中启用MCP工具包(在“设置”>“测试版功能”中)。
  2. 在终端中,将您的映像加载到Docker Desktop中(在前面的步骤中,它已经是本地的)。
  3. 在Docker桌面中,打开 _MCP工具包_ → _目录_ → 添加本地服务器,然后选择您的 azuredevops/mcpserver 图像。配置 AZURE_DEVOPS_* 配置选项卡中的变量(如果提供)。
  4. 通过在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中安装它,而无需手动运行服务器:

  1. 访问提要(例如NuGet.org)并搜索类型为的包 mcpserver.
  2. 打开包裹的详细信息页面并复制 MCP服务器 NuGet为VS代码生成的配置代码段。
  3. 将该代码段添加到您的工作区 .vscode/mcp.json.VS Code将自动下载包,并在首次使用时提示输入所需内容。

由于我们的车间包不供公众使用,请共享包文件(.nupkg)或者将其托管在内部NuGet服务器上。然后,参与者可以将提要URL添加到他们的 nuget.config 或者手动安装服务器包。

结论

本统一指南涵盖了模型上下文协议背后的理论,以及与官方实现MCP服务器和客户端的实际步骤。NET SDK,以及通过Ollama与OpenAI的云模型和本地模型集成的说明。它还展示了如何在MCP Inspector中验证和练习您的工具,并直接在VS Code的代理模式中尝试它们。最后,最后两章将介绍如何添加生产样式 Azure DevOps测试结果工具 (具有所需的输入和结构化的输出)以及 用Docker容器化MCP服务器 用于私人、可复制的本地运行。通过遵循这里提供的步骤和代码示例,您可以构建一个强大的研讨会或项目,演示大型语言模型如何在保持上下文和安全性的同时利用外部工具。

目录标签

目录标签

开发工具C#VS CodeJavaScriptAI集成本地部署外部工具调用JSON-RPC模型上下文协议

支持客户端

VS Code

接入字段

传输方式(transport,传输协议)

stdio

鉴权方式(authType,认证方式)

api-key

运行时(runtime,运行环境)

Node.js

来源包(packageName,安装包名)

@modelcontextprotocol/inspector

工具数量(toolCount,工具数)

0

资源数量(resourceCount,资源数)

0

提示词数量(promptCount,提示词数)

0

权限和风险

stdioapi-key部署方式未说明

接入前请确认传输方式、认证方式和部署位置,并根据实际工具能力限制访问范围。

安装前确认

不要直接授予不必要的文件、网络或账号权限;先核对安装命令和配置内容。

来源信息

继续浏览同类 MCP