内容
# TFS 2013 MCP 服务器(C# / .NET 10)
一个 [MCP](https://modelcontextprotocol.io) 服务器,暴露 **TFS 2013 工作项** 操作,作为 [PostQode](https://www.postqode.ai) 等 AI 客户端的工具。
包括一个 **完整的内存模拟**,以便在没有任何 TFS 实例的情况下测试所有工具,以及一个 **xUnit 测试套件**,涵盖模拟存储、配置解析和响应塑形。
---
## 解决方案结构
```
TfsMcpServer.sln
├── src/TfsMcpServer/ - MCP 服务器(入口点 + 所有源代码)
├── tests/TfsMcpServer.Tests/ - xUnit 测试套件
├── global.json - 固定的 .NET SDK 版本
└── .editorconfig - 格式化和命名约定
```
---
## 暴露的 MCP 工具
| 工具 | 描述 |
|---------------------|----------------------------------------------------------|
| `QueryWorkItems` | 运行任何 WIQL 查询并返回匹配的项目 |
| `GetWorkItem` | 获取工作项的完整详细信息,ID |
| `CreateWorkItem` | 在项目中创建新的工作项 |
| `UpdateWorkItem` | 更新字段和/或添加历史记录注释 |
| `ListWorkItemTypes` | 发现项目中可用的工作项类型 |
---
## 先决条件
| 需求 | 备注 |
|-------------------|-----------------------------------------------------------------|
| **.NET 10 SDK** | https://dotnet.microsoft.com/download/dotnet/10 |
| **PostQode** | VS Code 扩展 - https://postqode.ai |
| **TFS 程序集**| 仅在真实 TFS 模式下需要 - 通过 NuGet 安装(见下文) |
`global.json` 将 SDK 固定为 `10.0.100`。如果您安装的 SDK 是不同的补丁版本,请安装 `10.0.100`+ 或在 `global.json` 中放松 `rollForward`。
---
## 运行测试
```bash
cd TfsMcpServer
dotnet test
```
测试项目始终在 `MockOnly` 模式下构建主项目,因此测试可以在 **任何操作系统** 上运行 - 无需 TFS 程序集,无需 Windows,无需实时 TFS 实例。此功能通过 `<AdditionalProperties>MockOnly=true</AdditionalProperties>` 在 `TfsMcpServer.Tests.csproj` 中的 `<ProjectReference>` 自动转发。
**覆盖范围:** 跨越:
- `MockWorkItemStore` - 查询(WIQL 过滤器解析、排序、分页)、GetById、创建、更新、ListWorkItemTypes
- `TfsConfig.ParseAuthMode` - 不区分大小写的解析、默认值和 typo 的错误路径
- `WorkItemViewModels` - 每个工具输出的响应塑形
---
## 快速入门 - 模拟模式(无需 TFS)
### 1. 构建和发布
```bash
cd TfsMcpServer/src/TfsMcpServer
dotnet publish -c Release -p:MockOnly=true -o ../../publish
```
### 2. 在 PostQode 中注册
打开 PostQode -> MCP 服务器图标 -> 已安装选项卡 -> 配置 MCP 服务器。
在 `postqode_mcp_settings.json` 中添加以下条目:
```json
{
"mcpServers": {
"tfs2013": {
"command": "dotnet",
"args": ["C:\\path\\to\\TfsMcpServer\\publish\\TfsMcpServer.dll"],
"env": {
"TFS_AUTH_MODE": "mock"
}
}
}
}
```
> 在 macOS/Linux 上,将 `C:\\path\\to\\` 替换为 Unix 路径,例如 `/home/user/TfsMcpServer/`
### 3. 在 PostQode 聊天中测试
模拟种子数据包含两个项目中的 10 个工作项。
**项目:** `FabrikamFiber`,`AdventureWorks`
**模拟 ID:** 1-10
尝试这些提示:
```
"列出 FabrikamFiber 中的工作项类型"
"查找 FabrikamFiber 中所有活动 bug"
"显示 FabrikamFiber 中工作项 #1 的完整详细信息"
"在 FabrikamFiber 中创建一个标题为 'Test the MCP server' 的 bug"
"将 FabrikamFiber 中的工作项 #3 更新为已解决并分配给 Alice Johnson"
```
---
## 模拟种子数据
| ID | 项目 | 类型 | 标题 | 状态 | 指派给 |
|----|----------------|---------------------|------------------------------------------------|-------------|------------|
| 1 | FabrikamFiber | Bug | 登录页面在无效电子邮件格式时崩溃 | 活动 | Alice Johnson |
| 2 | FabrikamFiber | Bug | 仪表板小部件在更新后无法刷新 | 已解决 | Carol White |
| 3 | FabrikamFiber | Bug | 导出到 CSV 时字段截断 > 255 个字符 | 活动 | Bob Smith |
| 4 | FabrikamFiber | 任务 | 为 AuthController 添加单元测试 | 待办 | Alice Johnson |
| 5 | FabrikamFiber | 任务 | 将 NuGet 包升级到最新稳定版本 | 进行中 | Bob Smith |
| 6 | FabrikamFiber | 用户故事 | 作为用户,我可以通过电子邮件重置密码 | 新 | (未分配) |
| 7 | FabrikamFiber | 用户故事 | 作为管理员,我可以导出所有用户活动 | 活动 | Carol White |
| 8 | AdventureWorks | Bug | 产品图片在移动 Safari 上无法加载 | 活动 | Dave Lee |
| 9 | AdventureWorks | 产品待办事项 | 实现具有过滤器的产品搜索 | 新 | Eve Martinez |
| 10 | AdventureWorks | 任务 | 在 TFS Build 中设置 CI 管道 | 已完成 | Dave Lee |
模拟支持通过 `[System.TeamProject]`、 `[System.WorkItemType]`、 `[System.State]`(= 和 <>)和 `[System.AssignedTo]` 进行 WIQL 过滤,以及 `ORDER BY [System.Id] ASC/DESC`。
### 注意
在模拟模式下,WorkItemData 上的 Url 属性永远无效,无法点击,http://mock-tfs 也不解析为任何内容。它存在是因为 WorkItemData 有一个 Url 属性(在真实的 TFS 2013 中,它为您提供了一个直接的浏览器链接到工作项),而模拟只是填充它以满足形状。
---
## 生产模式 - 真实的 TFS 2013
TFS 客户端程序集通过 NuGet 自动安装 - 无需手动复制 DLL。
### 1. 构建并发布到生产环境
```bash
cd TfsMcpServer/src/TfsMcpServer
dotnet publish -c Release -o ../../publish
```
这将从 NuGet 还原 `Microsoft.TeamFoundationServer.ExtendedClient` 并生成仅限 Windows(`win-x64`)的可执行文件。
### 2. 更新 PostQode 配置
```json
{
"mcpServers": {
"tfs2013": {
"command": "dotnet",
"args": ["C:\\path\\to\\TfsMcpServer\\publish\\TfsMcpServer.dll"],
"env": {
"TFS_COLLECTION_URL": "http://tfs2013:8080/tfs/DefaultCollection",
"TFS_AUTH_MODE": "ntlm"
}
}
}
}
```
### 身份验证模式
| `TFS_AUTH_MODE` | 枚举值 | 何时使用 | 需要额外环境变量 |
|-----------------|-----------------|------------------------------------------|------------------------------------|
| `mock` | `AuthMode.Mock` | 无 TFS 测试 | 无 |
| `ntlm` | `AuthMode.Ntlm` | 域加入的 Windows 机器(默认) | 仅 `TFS_COLLECTION_URL` |
| `basic` | `AuthMode.Basic`| 非域机器,显式凭证 | + `TFS_USERNAME`,`TFS_PASSWORD` |
| `pat` | `AuthMode.Pat` | TFS 2013 更新 5+ / TFS 服务 | + `TFS_PASSWORD`(仅限令牌) |
值被不区分大小写解析。无法识别的值(例如拼写错误,如 `nmtl`)会在启动时立即失败,并显示清晰的错误,列出有效的选项,而不是在 TFS 连接逻辑内部失败。
---
## 日志记录
所有组件都通过 `ILogger<T>`(标准 `Microsoft.Extensions.Logging`)记录到
**stderr**,因此 MCP 的 JSON-RPC 协议流量在 stdout 上保持不变。默认级别是
`Information`。要在故障排除期间查看详细输出,请在
`Program.cs` 中将 `SetMinimumLevel(...)` 更改为 `LogLevel.Debug` 或 `LogLevel.Trace`。
---
## 故障排除
| 症状 | 修复 |
|---------|-----|
| TFS 包的 `NU1701` 兼容性警告 | `Microsoft.TeamFoundationServer.ExtendedClient` 及其依赖项针对 `.NETFramework`,而没有 `net10.0` 版本 - 针对冻结的 TFS 2013 客户端 OM 的永久性和预期结果。`win-x64` RuntimeIdentifier 通过 Windows 模拟处理实际运行时兼容性。接受原样;对于工作项 CRUD 用例,这不是真正的兼容性问题的迹象。 |
| `Newtonsoft.Json` 和 `System.IdentityModel.Tokens.Jwt` 的 `NU1902`/`NU1903` 漏洞警告 | 这些是从 `Microsoft.TeamFoundationServer.ExtendedClient 15.112.1` 内部锁定到的传递依赖项,该版本是冻结的软件包,微软在 TFS 成为 Azure DevOps 后停止更新。它们无法通过直接在 `.csproj` 中覆盖版本(首先尝试此方法 - 参见文件中的注释)或用直接的 TFS REST API 调用替换 TFS 客户端 OM 来解决。工作项 CRUD 不会执行这些代码路径,因此目前接受此风险。 |
---
## 架构 - 它是如何工作的
将服务器视为 **4 层**。来自 PostQode 的工具调用流经它们,响应流回。
```
PostQode
│ 调用工具
v
+----------------------------------------------------------+
│ 层 1 - WorkItemTools.cs (菜单) │
│ 每个 [McpServerTool] 方法:验证输入 → 调用存储 → 塑形为视图模型 → 序列化。除此之外什么都没有。│
+-----------------------+----------------------------------+
│
v
+-----------------------+----------------------------------+
│ 层 2 - IWorkItemStore (契约) │
│ 声明存在哪些操作:查询、GetById、创建、 │
│ 更新、ListWorkItemTypes。说明 *如何*。 │
+----------------------------------------------------------+
│ │
v v
+-----------------------+ +---------------------------+
│ 模拟工作项存储 │ │ 层 3b - 真实的 TFS,通过 │
│ 层 3a - 内存中,用于测试 │ │ TFS 客户端 SDK │
+-----------------------+ +---------------------------+
│ │
+------------------------------+
v
两者返回相同的
工作项数据形状
│
v
+-----------------------------------------------------------+
│ 工作项视图模型 (格式化器) │
│ 将工作项数据塑造成 JSON 响应 - 列表的简要摘要,单个查找的完整转储,创建/更新的成功消息。 │
+-----------------------------------------------------------+
│
v
JSON 字符串返回到 PostQode
```
### 4 层
**层 1 - `WorkItemTools.cs` (菜单)**
这是 PostQode 看到的内容。每个标记为 `[McpServerTool]` 的方法都是一个可调用的操作 - `QueryWorkItems`、`CreateWorkItem` 等。这些方法故意设计得很简单:输入 → 调用
存储 → 将结果交给视图模型 → 返回 JSON。这里没有业务逻辑。
**层 2 - `IWorkItemStore` (契约)**
此接口说明“任何工作项存储都必须支持查询、GetById、创建、更新、ListWorkItemTypes” - 而不说明 *如何* 实现。 这是让模拟和真实 TFS 可互换的接口。
**层 3 - `MockWorkItemStore` / `TfsWorkItemStore` (实际工作)**
两个不同的引擎都实现了 `IWorkItemStore` 接口。 一个在内存中伪造数据;另一个与真实的 TFS 交互。 `ServiceLocator` 在启动时根据 `TFS_AUTH_MODE` 选择其中一个。
**回到 `WorkItemViewModels` (格式化器)**
一旦您拥有 `WorkItemData` 结果,此层就会决定发送回 PostQode 的 JSON 形状 - 列表的简要摘要,单个查找的完整转储,创建/更新的成功消息。
### 为什么使用 ViewModel?
ViewModel 是 **“我们拥有的数据”与“其他人需要看到的格式”之间的转换器。** 在此项目中:`WorkItemData` 是工作项的 *完整*、内部表示(每个字段)。JSON 响应是 PostQode 实际接收的内容。 `WorkItemViewModels` 位于它们之间,并为每个工具决定哪些字段重要以及如何标记它们。
不同的工具需要相同数据的不同视图:
| 工具 | 需要显示的内容 |
|----------------------|---------------------------------------------------------------|
| `QueryWorkItems` | 仅需足够扫描列表 — id、标题、状态、指派人 |
| `GetWorkItem` | 所有内容 — 完整描述、历史记录、所有自定义字段 |
| `CreateWorkItem` | 不需要工作项本身 — 仅需成功消息 + 新 ID |
| `UpdateWorkItem` | 成功消息 + 哪些字段已更改 |
如果每个工具都直接序列化 `WorkItemData`,则每个响应都会转储所有 13+ 个字段,即使只需要 5 个字段 - 这会使响应膨胀并掩盖信号。 通过在 `WorkItemViewModels.cs` 中集中塑形,可以在一个地方更改“完整详细信息”的含义,每个使用 `Full()` 的工具都会自动获取更新。
### 添加新工具 - 具体演练
假设您要添加 **`DeleteWorkItem`**。以下是您需要修改的每个文件:
**1. 将方法添加到 `IWorkItemStore.cs`**
```csharp
void Delete(int id);
```
**2. 在两个存储中实现它**
`MockWorkItemStore.cs`:
```csharp
public void Delete(int id)
{
lock (_lock)
{
if (!_items.Remove(id))
throw new KeyNotFoundException($"工作项 #{id} 不存在于模拟存储中。");
}
}
```
`TfsWorkItemStore.cs`:
```csharp
public void Delete(int id)
{
var store = _factory.GetWorkItemStore();
var wi = store.GetWorkItem(id);
wi.State = "已删除"; // 通常,TFS 工作项不会被硬删除
wi.Save();
}
```
**3. 将响应形状添加到 `WorkItemViewModels.cs`**
```csharp
public static object Deleted(int id) => new
{
success = true,
id,
message = $"工作项 #{id} 成功删除。"
};
```
**4. 将工具方法添加到 `WorkItemTools.cs`**
```csharp
[McpServerTool, Description("删除或移除工作项,ID 为。")]
public static string DeleteWorkItem(
[Description("要删除的工作项的数字 ID。")]
int id)
{
Store.Delete(id);
return JsonSerializer.Serialize(WorkItemViewModels.Deleted(id), JsonOptions.Default);
}
```
**5. 在 `tests/` 中编写测试**
一个新的 `MockWorkItemStoreDeleteTests.cs`,遵循与现有测试相同的模式。
就是这样 - **5 次编辑,无需架构决策。** `Program.cs`、`ServiceLocator.cs` 和 `.csproj` 永远不需要为新 *工作项* 工具更改,因为线路已经很通用。
### 添加全新 TFS 区域(不仅仅是一个新操作)
如果您要添加工具来获取不同的 TFS 区域 - 构建、源代码管理、测试计划 - 请用新名称镜像相同的 4 层:
```
IBuildStore.cs (契约)
MockBuildStore.cs (模拟实现)
TfsBuildStore.cs (真实实现)
BuildViewModels.cs (响应塑形)
BuildTools.cs ( [McpServerTool] 方法)
```
然后在 `ServiceLocator.cs` 中添加一行,以连接使用哪个 `IBuildStore` - 与 `WorkItemStore` 相同的模式,只是它是一个兄弟属性。 `Program.cs` 不需要更改,因为
`WithToolsFromAssembly()` 会自动发现具有 `[McpServerToolType]` 的任何类。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器