Cách để một AI coding agent điều tra sự cố ở môi trường phát triển và production bằng cách điều khiển các công cụ chẩn đoán .NET qua MCP server, tệp SKILL.md, hoặc thậm chí một ứng dụng giao diện đồ họa.
Ngày 8 tháng 6 năm 2026 · 16 phút đọc · Christophe Nasarre

Mục lục
Ba hướng tích hợp AI
Từ cuối năm ngoái, tôi sử dụng nhiều AI coding agent như Cursor, VS Code kết hợp Copilot hoặc Claude Code để lên ý tưởng, lập kế hoạch và tạo mã. Tuy nhiên, viết mã chỉ là một nửa công việc của nhà phát triển. Nửa còn lại là điều tra sự cố: một dịch vụ bị rò rỉ bộ nhớ trong production, một ứng dụng bị treo do deadlock, thread pool cạn kiệt dưới tải cao, hoặc một request mất quá nhiều thời gian mà không có nguyên nhân rõ ràng.
Các cuộc điều tra này thường dựa vào một bộ công cụ chẩn đoán dòng lệnh quen thuộc. Trong .NET, đó thường là dotnet-dump, dotnet-counters, dotnet-trace, dotnet-pstacks, dotnet-dstrings và các công cụ CLI khác. Chúng có một đặc điểm rất thuận tiện: đều là chương trình console theo mô hình vào bằng văn bản, ra bằng văn bản. Bạn cung cấp process ID hoặc tệp dump, công cụ in ra rất nhiều văn bản, bạn cố gắng hiểu chuyện gì đang xảy ra rồi suy luận bước tiếp theo.
Việc suy luận từ một khối văn bản để quyết định lệnh kế tiếp chính là điều AI agent làm khá tốt. Vì vậy, ngoài việc dùng agent để viết tính năng mới, tại sao không để nó tự chạy cuộc điều tra — chụp dump, xem heap, lần theo tham chiếu từ GC root và cho biết trường static nào đang giữ cache sống? Agent chạy lệnh, đọc kết quả và quyết định bước tiếp theo, tương tự một nhà phát triển giàu kinh nghiệm.
Trong bài viết này, tôi trình bày hai cách chính để kết nối các công cụ chẩn đoán .NET với AI agent, đồng thời giới thiệu một mô hình nâng cao ở cuối bài dành cho trường hợp công cụ cần điều khiển không phải CLI mà là ứng dụng GUI.
Hai hướng tích hợp thực tế
Hướng đầu tiên là không làm gì đặc biệt và tin vào năng lực của mô hình. Nhưng làm sao chắc rằng mô hình biết công cụ nào cần gọi cho từng tình huống? Và nếu bạn có công cụ riêng mà mô hình chưa từng học trong quá trình huấn luyện thì sao?
Khi bắt đầu xây dựng cách tự động hóa điều tra bằng AI, tôi nhận ra các mô hình thường làm tốt bước khởi đầu nhưng sau đó dễ rơi vào hai vấn đề: (1) bám quá lâu vào một giả thuyết, hoặc (2) ngày càng kém sáng tạo khi ngữ cảnh trở nên lớn hơn.
Ở mức khái quát, người dùng tương tác với agent harness như Cursor, Copilot, Claude hoặc công cụ tự xây dựng. Harness cung cấp cho mô hình quyền truy cập vào môi trường thông qua các tool.
- Đăng ký một MCP Server, nơi liệt kê các tool và prompt để mô hình sử dụng.
- Định nghĩa các tệp Markdown dạng skill để hướng dẫn mô hình dùng đúng công cụ theo đúng thứ tự.
Hai cách này giúp agent tập trung hơn và đưa ra chẩn đoán hợp lý dựa trên các công cụ sẵn có. Lựa chọn thường phụ thuộc vào một câu hỏi: bạn có sở hữu mã nguồn của công cụ hay không?
| Cách tiếp cận | Khi nên dùng | Có thay đổi mã không? | Cách agent sử dụng |
|---|---|---|---|
| MCP Server (stdio) | Bạn sở hữu công cụ và muốn có cơ chế khám phá tool có cấu trúc | Có — thêm tham chiếu ModelContextProtocol, Microsoft.Extensions.Hosting và thay đổi cách xử lý dòng lệnh |
Agent gọi các tool có kiểu dữ liệu, hoạt động tự động |
Skill (SKILL.md) |
Muốn dùng nguyên trạng các công cụ CLI hiện có | Không — chỉ cần một tệp Markdown | Agent chạy lệnh shell, hoạt động tự động |
MCP Server có cấu trúc tốt hơn: agent tự khám phá các tool có tham số được đặt tên và kiểu rõ ràng, cùng các prompt workflow dựng sẵn, không cần phân tích lệnh shell. Đổi lại, bạn phải sửa mã nguồn công cụ, nên cách này chỉ phù hợp với công cụ do bạn viết. Ngoài ra, mỗi agent harness sẽ tạo một instance của từng MCP server đã đăng ký.
SKILL.md là lựa chọn ngược lại: không ma sát và không cần mã. Một tệp Markdown duy nhất hướng dẫn agent điều khiển đúng CLI vào đúng thời điểm — kể cả dotnet-dump và dotnet-counters của Microsoft, vốn không thể biên dịch lại. Cái giá phải trả là agent làm việc với văn bản thô thay vì JSON có kiểu.
Hai cách này bổ trợ cho nhau: dùng MCP cho công cụ do bạn viết và dùng Skill cho mọi công cụ còn lại.
MCP Server: biến dotnet-dstrings thành công cụ CLI/MCP hai chế độ
MCP là gì?
Model Context Protocol (MCP) là một chuẩn mở cho phép agent harness khám phá và gọi các tool. MCP server cung cấp hai loại khả năng thông qua một transport — stdio cho công cụ cục bộ và HTTP cho công cụ từ xa:
- Tool — hàm mà agent có thể gọi để lấy dữ liệu, với tham số có kiểu và tài liệu mô tả.
- Prompt — mẫu workflow dựng sẵn, hướng dẫn agent phải làm gì với các tool.
Trong .NET, để biến console app thành MCP server dùng stdio, chỉ cần hai gói NuGet:
ModelContextProtocolphiên bản 1.1.0 cho hosting, dependency injection và cơ chế phát hiện tool, prompt dựa trên attribute.Microsoft.Extensions.Hostingcho generic host.
Nếu bắt đầu từ đầu, .NET 10 SDK còn có template dotnet new mcpserver. Nhưng ví dụ thực tế hơn là nâng cấp một CLI hiện có thành MCP server mà không làm mất tính chất dòng lệnh của nó.
Mô hình hai chế độ với --mcp
Ý tưởng chính là tạo một binary duy nhất: hoạt động như CLI thông thường hoặc như MCP server tùy vào cờ --mcp. Công cụ dotnet-dstrings của tôi, dùng để tìm chuỗi bị trùng trong process hoặc memory dump, đã được nâng cấp theo cách này: một gói NuGet, một lệnh dotnet tool install, hai nhóm người dùng.
using System.Text;
using dstrings;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
if (args.Contains("--mcp"))
{
var mcpArgs = args.Where(a => a != "--mcp").ToArray();
var host = Host.CreateDefaultBuilder(mcpArgs)
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning))
.ConfigureServices(services =>
{
services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly()
.WithPromptsFromAssembly();
})
.Build();
await host.RunAsync();
return;
}
// Chế độ CLI — tự xử lý tham số, giống dstrings ban đầu
...
Cả hai nhánh cuối cùng đều gọi cùng một phương thức StringAnalyzer.Analyze() để tính toán các chuỗi trùng lặp.
MCP tool: truy cập dữ liệu
Các MCP tool là lớp bọc mỏng quanh analyzer dùng chung, được gắn attribute để thư viện có thể phát hiện và công khai tham số cho agent. Một static class có attribute McpServerToolType định nghĩa các hàm được gắn McpServerTool. Thuộc tính Description cung cấp đủ thông tin để agent chọn tool phù hợp. Mỗi tham số cũng được mô tả bằng attribute Description.
[McpServerToolType]
public static class GenerationStatsTool
{
[McpServerTool, Description("Shows per-generation heap statistics including string size, " +
"duplication ratios, and object counts for Gen0, Gen1, Gen2, LOH, POH, and Frozen heaps. " +
"Provide either a process ID or a dump file path (not both).")]
public static string GetGenerationStats(
[Description("Process ID to attach to (mutually exclusive with dumpPath)")] int? pid = null,
[Description("Path to a memory dump file (mutually exclusive with pid)")] string? dumpPath = null)
{
if (pid.HasValue == !string.IsNullOrEmpty(dumpPath))
return "Error: provide either a process ID or a dump file path, but not both.";
var analyzer = StringAnalyzer.Analyze(pid, dumpPath);
var sb = new StringBuilder();
OutputFormatter.FormatGenerationStats(sb, analyzer.GenerationStats);
return sb.ToString();
}
}
Tool thứ hai, GetDuplicatedStrings, có cấu trúc tương tự nhưng bổ sung các tham số countThreshold, sizeThresholdKB và stringLengthLimit, giúp agent điều chỉnh mức độ tìm kiếm chuỗi trùng. Các attribute Description không phải phần trang trí; đó là thông tin agent đọc để biết khi nào và cách nào nên gọi tool.
MCP prompt: chuyên môn miền nghiệp vụ
MCP Server cho agent biết có thể gọi gì và với tham số nào. Prompt cho agent biết phải làm gì với những công cụ đó. Một MCP prompt là template dựng sẵn, mã hóa kiến thức workflow mà chuyên gia con người thường áp dụng: thứ tự thao tác, cột nào cần đọc và cách điều chỉnh ngưỡng dựa trên kết quả vừa quan sát.
dotnet-dstrings cung cấp ba prompt. Prompt đầy đủ nhất, analyze_string_memory, điều phối hai tool theo thứ tự:
using Microsoft.Extensions.AI;
[McpServerPromptType]
public static class DstringsPrompts
{
[McpServerPrompt(Name = "analyze_string_memory")]
[Description("Full analysis workflow: get generation stats overview then find the worst duplicated strings")]
public static IEnumerable<ChatMessage> AnalyzeStringMemory(
[Description("Process ID to analyze")] int? pid = null,
[Description("Path to a memory dump file")] string? dumpPath = null)
{
var target = FormatTarget(pid, dumpPath);
return new[]
{
new ChatMessage(ChatRole.User,
$"Analyze string memory usage in {target}.\n\n" +
"Follow these steps:\n" +
$"1. Call the GetGenerationStats tool ({target}) to get per-generation heap statistics.\n" +
"2. Review the DupSize% and HeapSize% columns to identify which generations " +
"have the most string duplication.\n" +
$"3. Call the GetDuplicatedStrings tool ({target}) to get the list of duplicated strings. " +
"If the generation stats show low duplication, use a lower countThreshold (e.g. 32) " +
"and sizeThresholdKB (e.g. 10) to surface smaller issues.\n" +
"4. Summarize the findings: overall duplication ratio, which generations are most affected, " +
"and the top duplicated strings by wasted memory.")
};
}
}
Với 2 tool và 3 prompt, agent có cả dữ liệu và kiến thức workflow. Người dùng có thể chọn prompt analyze_string_memory; agent sẽ biết phải lấy thống kê theo generation trước, rồi điều chỉnh ngưỡng dựa trên tỷ lệ trùng vừa quan sát được.
Tệp dự án
Một tệp .csproj tạo ra công cụ hai chế độ. Dự án nhắm tới net6.0 với RollForward=Major, để công cụ chạy trên mọi runtime .NET 6 trở lên, kể cả .NET 10:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RollForward>Major</RollForward>
<RootNamespace>dstrings</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackAsTool>true</PackAsTool>
<ToolCommandName>dotnet-dstrings</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="3.1.512801" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageReference Include="ModelContextProtocol" Version="1.1.0" />
</ItemGroup>
</Project>
Việc phân tích tham số CLI được viết thủ công để giữ danh sách dependency tối thiểu. Gói ModelContextProtocol cũng mang theo Microsoft.Extensions.AI.Abstractions cho kiểu ChatMessage dùng trong prompt.
Template MCP Server tạo sẵn sẽ khác ở điểm có thêm:
<PackageType>McpServer</PackageType>
Khi gói được tải lên nuget.org, thông tin này khiến nó được nhận diện là MCP Server thay vì CLI tool. Không thể đồng thời là cả hai. Vì lý do tương thích, tôi giữ danh tính CLI cho dotnet-dstrings và dotnet-pstacks, dù chúng cũng có thể hoạt động như MCP server.
Kết nối với AI client
Sau khi cài công cụ toàn cục bằng dotnet tool install -g dotnet-dstrings, mọi client tương thích MCP đều có thể khởi chạy nó ở chế độ MCP bằng tham số --mcp.
VS Code — C:\Users\<user>\AppData\Roaming\Code\User\mcp.json:
{
"servers": {
"dstrings": {
"type": "stdio",
"command": "dotnet-dstrings",
"args": ["--mcp"]
}
}
}
Cursor — C:\Users\<user>\.cursor\mcp.json:
{
"mcpServers": {
"dstrings": {
"command": "dotnet-dstrings",
"args": ["--mcp"]
}
}
}
Claude Code — C:\Users\<user>\.mcp.json:
{
"mcpServers": {
"dstrings": {
"command": "dotnet-dstrings",
"args": ["--mcp"]
}
}
}
Visual Studio và GitHub Copilot CLI cũng hoạt động. Điểm khác nhau chỉ là vị trí tệp cấu hình và tên khóa JSON:
| Client | Khóa JSON | Tệp cấu hình |
|---|---|---|
| Visual Studio | servers |
.mcp.json ở thư mục solution hoặc %USERPROFILE% |
| Copilot CLI | mcpServers |
C:\Users\<user>\.copilot\mcp-config.json |
Tất cả đều phát hiện cùng 2 tool và 3 prompt từ cùng một binary.
Điểm cần nhớ về MCP Server
Một binary, một gói NuGet, một lệnh dotnet tool install — nhưng phục vụ hai nhóm người dùng. Con người gõ dotnet-dstrings app.dmp; agent khởi chạy dotnet-dstrings --mcp rồi khám phá các tool có kiểu dữ liệu và workflow prompt. Phía agent không cần phân tích lệnh shell vì MCP xử lý serialization JSON.
Hạn chế là bạn phải sửa mã nguồn công cụ. Cách này phù hợp với công cụ của bạn, nhưng không áp dụng được cho dotnet-dump, dotnet-counters của Microsoft và các công cụ tương tự. Đây là nơi phương pháp thứ hai phát huy tác dụng.
SKILL.md: tích hợp AI không cần viết mã
Skill là gì?
Tệp SKILL.md là một tài liệu Markdown hướng dẫn AI agent thực hiện workflow dựa trên các CLI có sẵn và không thay đổi. Không cần gói NuGet, không cần biên dịch lại; nó hoạt động ngay với dotnet-dump, dotnet-counters và dotnet-trace của Microsoft. Agent đọc skill, chạy các lệnh shell được mô tả và diễn giải kết quả dạng văn bản.
Định dạng này bắt đầu trong hệ sinh thái Claude và Cursor nhưng nhanh chóng trở thành chuẩn dùng chung giữa nhiều agent. Cùng một tệp có thể được Cursor, VS Code + Copilot, Visual Studio, Claude Code và GitHub Copilot CLI sử dụng.
Lưu skill ở đâu?
| Công cụ | Phạm vi dự án, dùng chung | Phạm vi cá nhân, cục bộ |
|---|---|---|
| Cursor | .cursor/skills/<name>/SKILL.md |
~/.cursor/skills/<name>/SKILL.md |
| VS Code, Copilot | .github/skills/<name>/SKILL.md |
~/.copilot/skills/<name>/SKILL.md |
| Visual Studio, Copilot | .github/skills/<name>/SKILL.md |
~/.copilot/skills/<name>/SKILL.md |
| GitHub Copilot CLI | .github/skills/<name>/SKILL.md |
~/.copilot/skills/<name>/SKILL.md |
| Claude Code | .claude/skills/<name>/SKILL.md |
~/.claude/skills/<name>/SKILL.md |
Để tăng tính di động, bạn có thể đặt cùng một skill trong nhiều thư mục hoặc dùng đường dẫn dùng chung đang dần hình thành là .agents/skills/. Agent tự động phát hiện skill dựa trên trường description trong YAML frontmatter, không cần bật thủ công.
Với Claude Code, CLAUDE.md và SKILL.md bổ trợ cho nhau. CLAUDE.md là ngữ cảnh luôn được đưa vào mọi cuộc hội thoại, chẳng hạn cấu trúc dự án, quy ước và lệnh build. SKILL.md chỉ được nạp khi cần, khi prompt của người dùng khớp với phần mô tả. Hãy dùng CLAUDE.md cho những điều agent luôn phải biết và SKILL.md cho workflow chuyên biệt như phân tích memory dump.
Cấu trúc của một diagnostics skill
Tôi đã xây dựng hai skill:
dotnet-memory-analysis: hướng dẫn agent thực hiện đầy đủ workflow điều tra rò rỉ bộ nhớ bằngdotnet-dump,dotnet-gcdumpvàdotnet-dstrings.dotnet-thread-analysis: tìm các vấn đề về độ trễ liên quan đến thread bằngdotnet-pstacksvàdotnet-dump.
Mỗi skill bắt đầu bằng bảng chọn công cụ, yêu cầu agent ưu tiên MCP server khi có và chuyển sang CLI nếu không có:
| Mục tiêu | Ưu tiên MCP | CLI dự phòng |
|---|---|---|
| Chuỗi trùng lặp | get_duplicated_strings, server user-dotnet-dstrings |
dotnet-dstrings <dump> |
| Thống kê heap | — | dotnet-dump analyze -c "dumpheap -stat" |
| GC root | — | dotnet-dump analyze -c "gcroot <addr>" |
| Tổng quan heap | — | dotnet-dump analyze -c "eeheap -gc" |
Kỹ thuật quan trọng nhất khi debug bằng agent là dùng các lệnh SOS không tương tác. Theo mặc định, dotnet-dump analyze chạy ở chế độ tương tác và giữ agent trong một prompt mà nó không thể tự thoát. Cách khắc phục là truyền lệnh bằng -c và luôn kết thúc bằng exit:
dotnet-dump analyze <dump> -c "dumpheap -stat" -c "exit"
Phần thân của skill nên là một cây quyết định, không phải đoạn văn mô tả chung chung. Agent chạy lệnh phân loại, sau đó tự định tuyến dựa trên kết quả:
| Phát hiện | Bước tiếp theo |
|---|---|
| Một kiểu ứng dụng chiếm phần lớn heap | Dùng dumpheap -mt <MT>, sau đó chạy gcroot trên 2-3 instance |
System.String chiếm ưu thế |
Dùng get_duplicated_strings hoặc dotnet-dstrings |
| Tăng trưởng phân tán trên nhiều kiểu | Tìm collection đang giữ đối tượng, sau đó chạy gcroot trên collection đó |
| Finalizer queue lớn | Dùng finalizequeue, kiểm tra mục “Ready for finalization” |
| Heap nhỏ nhưng bộ nhớ process lớn | Nghi ngờ rò rỉ native, kiểm tra khoảng chênh trong eeheap |
Skill cũng cần nêu rõ điều kiện dừng — khi nào cuộc điều tra thực sự hoàn tất — cùng bảng lỗi thường gặp như không tương thích phiên bản DAC, lỗi từ chối truy cập và kết quả MCP rỗng, cũng như các rào chắn an toàn.
Nên tạo một tài liệu tóm tắt liệt kê các bước điều tra và MCP tool hoặc CLI command đã sử dụng. Sau phiên bản đầu tiên, tôi tiếp tục cải tiến các tệp này bằng Claude Code và Cursor, dùng nhiều model khác nhau để xin phản hồi. Một thay đổi là tách template tóm tắt ra riêng nhằm giảm tác động lên lượng ngữ cảnh tiêu thụ.
Các nguyên tắc thiết kế quan trọng
Ba nguyên tắc giúp skill đáng tin cậy thay vì chỉ là một tập lệnh rời rạc:
- Phân tích tăng dần — bắt đầu bằng các bản tóm tắt rẻ như
dumpheap -stattrước khi đi sâu vào từng instance. Không cần xuất toàn bộ heap nếu histogram đã đủ. Cách này cũng giúp giảm lượng ngữ cảnh sử dụng. - Dựa trên quyết định — mỗi bước phải nêu rõ cần tìm gì trước khi chọn lệnh kế tiếp, để agent suy luận thay vì đoán.
- Rào chắn an toàn — cảnh báo trước khi đóng băng process bằng
dotnet-dump collectvà không bao giờ dừng process nếu chưa có sự đồng ý rõ ràng của người dùng.
Sau khi cài skill, bạn có thể nói với agent: “PID 12345 có vẻ đang rò rỉ bộ nhớ, hãy điều tra giúp tôi”. Agent có thể tự chụp dump, tìm kiểu chiếm ưu thế, lần theo GC root đến trường static đang giữ đối tượng và đề xuất cách sửa — tất cả bằng công cụ chuẩn của Microsoft, không cần bạn viết thêm một dòng mã nào.
Kết luận
Có hai điều cần nhớ:
- MCP server cung cấp cơ chế khám phá tool có cấu trúc cho những công cụ do bạn sở hữu — tham số có kiểu, prompt workflow, không cần phân tích shell và hoạt động nhất quán trên Visual Studio, VS Code, Cursor, Claude và Copilot CLI.
- Tệp SKILL.md cung cấp khả năng tích hợp không cần mã với bất kỳ CLI hiện có nào — một cây quyết định dạng Markdown dạy agent workflow chẩn đoán của bạn, đặc biệt phù hợp với
dotnet-dumpvàdotnet-counterscủa Microsoft.
Tiến xa hơn: điều khiển ứng dụng GUI
Ý tưởng này không chỉ giới hạn ở công cụ CLI. Dự án windbg-bridge của người bạn Kevin Gosse cho thấy AI agent có thể điều khiển một ứng dụng GUI — WinDbg — thông qua named-pipe bridge.
Người dùng và agent cùng chia sẻ một instance WinDbg: người dùng nhập câu hỏi trong harness, agent chạy các lệnh phù hợp và câu trả lời được trả về ngay trong giao diện. Người dùng vẫn có thể tự nhập lệnh trong WinDbg. Đây là một ví dụ cho thấy workflow có AI hỗ trợ có thể tiến xa hơn nhiều so với công cụ dòng lệnh.
Tài liệu tham khảo
- Skills và phản hồi từ model trong repository của buổi trình bày tại Update Conference Krakow 2026.
- Công cụ:
dotnet-dump,dotnet-counters,dotnet-pstacks,dotnet-dstrings. - chrisnas/DebuggingExtensions — nơi phát triển
dotnet-dstringsvàdotnet-pstacks. - MCP C# SDK: modelcontextprotocol/csharp-sdk.
- Xây dựng MCP server bằng C#.
- Template MCP project:
dotnet new mcpserver, qua gói Microsoft.McpServer.ProjectTemplates. - Điều khiển ứng dụng GUI: kevingosse/windbg-bridge.



