当 AI 助手不再只是生成 SQL 语句,而是能够直接连接你的数据库、查询数据、分析结构——这就是 MCP 为 .NET 开发者带来的全新可能。

一、MCP 是什么?为什么 .NET 开发者应该关注?

1.1 MCP 协议简介

MCP(Model Context Protocol,模型上下文协议) 是由 Anthropic 推出的开源标准协议,旨在为大语言模型(LLM)与外部工具和数据源之间建立统一的连接方式。你可以把它理解为 “AI 世界的 USB-C 接口”——一个标准化的协议,让不同的 AI 助手(Cursor、VS Code Copilot、Claude Desktop 等)能够以统一的方式调用各种外部工具。

┌─────────────────┐     MCP 协议      ┌─────────────────┐
│   AI 客户端      │ ◄──────────────► │   MCP 服务器     │
│  (Cursor/Copilot)│    JSON-RPC      │  (.NET 应用)     │
└─────────────────┘                   └─────────────────┘
                                            │
                                      ┌─────┴─────┐
                                      │  数据库    │
                                      │  API      │
                                      │  文件系统  │
                                      └───────────┘

1.2 MCP 的核心概念

MCP 协议定义了三种核心能力:

能力 说明 示例
Tools(工具) 可被 AI 调用的函数 查询数据库、调用 API、执行计算
Resources(资源) AI 可读取的数据源 数据库 Schema、配置文件、文档
Prompts(提示模板) 预定义的提示词模板 SQL 生成模板、报表分析模板

1.3 为什么选择 .NET 开发 MCP 服务器?

作为 .NET 开发者,你有以下优势:

  • 官方 C# SDK:微软与 Anthropic 联合维护的 官方 C# SDK,品质有保障
  • 强大的依赖注入:完美整合 Microsoft.Extensions.DependencyInjection,天然支持 IoC
  • Entity Framework Core:与 EF Core 无缝结合,轻松实现数据库操作
  • 丰富的生态:可直接复用现有的 .NET 服务、中间件、认证体系
  • 高性能:.NET 的异步编程模型和 AOT 编译支持,确保 MCP 服务器的高响应性
  • 多种部署方式:支持本地 stdio、SSE(HTTP)、Docker 容器等多种传输方式

GitHub 仓库:https://github.com/modelcontextprotocol/csharp-sdk
NuGet 包:https://www.nuget.org/packages/ModelContextProtocol


二、环境准备

2.1 开发工具

工具 要求 说明
.NET SDK 8.0+ (推荐 10.0) MCP SDK 支持 .NET 8.0 及以上版本
IDE Visual Studio 2022 / VS Code / Cursor 任选其一
AI 客户端 Cursor / VS Code + GitHub Copilot 用于测试 MCP 服务器
数据库 SQL Server / SQLite 本文使用 SQLite 作为演示

2.2 安装 NuGet 包

MCP C# SDK 由三个包组成:

# 主包(推荐):包含宿主和依赖注入扩展
dotnet add package ModelContextProtocol --prerelease

# ASP.NET Core 扩展(用于 HTTP/SSE 传输)
dotnet add package ModelContextProtocol.AspNetCore --prerelease

# 核心包(最小依赖,适用于底层 API)
dotnet add package ModelContextProtocol.Core --prerelease

注意:截至 2026 年初,MCP C# SDK 仍处于预览版(当前版本 0.8.0-preview.1),API 可能会变化。

2.3 使用项目模板(.NET 10+)

如果你使用 .NET 10 SDK,可以直接使用官方项目模板快速创建:

# 安装 MCP Server 项目模板
dotnet new install Microsoft.McpServer.ProjectTemplates

# 创建新的 MCP 服务器项目
dotnet new mcpserver -n MyDbMcpServer

三、实战:构建数据库查询 MCP 服务器

3.1 项目概述

我们将构建一个名为 DbQueryMCP 的 MCP 服务器,它可以:

  • 查询数据库中所有的表结构信息
  • 根据表名获取字段详情
  • 执行安全的 SELECT 查询并返回结果
  • 获取表的数据统计摘要
  • 根据自然语言描述生成 SQL 查询建议

这在实际开发中非常有用——AI 助手可以理解你的数据库结构,帮你编写查询、分析数据、生成报表。

3.2 创建项目

# 创建控制台项目
dotnet new console -n DbQueryMCP

# 进入项目目录
cd DbQueryMCP

# 添加必要的 NuGet 包
dotnet add package ModelContextProtocol --prerelease
dotnet add package Microsoft.Extensions.Hosting
dotnet add package Microsoft.Data.Sqlite
dotnet add package Dapper

我们使用 Dapper 而非 EF Core,因为对于通用的数据库查询工具,Dapper 的灵活性更适合执行动态 SQL。

3.3 项目结构

DbQueryMCP/
├── Program.cs                 # 应用入口与 MCP 服务器配置
├── Services/
│   └── DatabaseService.cs     # 数据库操作服务
├── Tools/
│   ├── SchemaTools.cs         # 数据库结构查询工具
│   └── QueryTools.cs          # 数据查询工具
├── Models/
│   ├── TableInfo.cs           # 表信息模型
│   └── ColumnInfo.cs          # 列信息模型
├── DbQueryMCP.csproj          # 项目文件
└── appsettings.json           # 配置文件

3.4 定义数据模型

首先,定义我们需要用到的数据模型:

Models/TableInfo.cs

namespace DbQueryMCP.Models;

/// <summary>
/// 数据库表信息
/// </summary>
public record TableInfo(
    string TableName,
    string TableType,
    int RowCount
);

Models/ColumnInfo.cs

namespace DbQueryMCP.Models;

/// <summary>
/// 数据库列信息
/// </summary>
public record ColumnInfo(
    string ColumnName,
    string DataType,
    bool IsNullable,
    bool IsPrimaryKey,
    string? DefaultValue
);

3.5 编写数据库服务

Services/DatabaseService.cs

using System.Data;
using System.Text;
using System.Text.RegularExpressions;
using Dapper;
using DbQueryMCP.Models;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace DbQueryMCP.Services;

/// <summary>
/// 数据库操作服务,封装所有数据库相关的操作
/// </summary>
public class DatabaseService : IDisposable
{
    private readonly string _connectionString;
    private readonly ILogger<DatabaseService> _logger;
    private SqliteConnection? _connection;

    public DatabaseService(IConfiguration configuration, ILogger<DatabaseService> logger)
    {
        _connectionString = configuration.GetConnectionString("DefaultConnection")
            ?? "Data Source=sample.db";
        _logger = logger;
    }

    /// <summary>
    /// 获取数据库连接(懒加载)
    /// </summary>
    private async Task<SqliteConnection> GetConnectionAsync()
    {
        if (_connection is null || _connection.State != ConnectionState.Open)
        {
            _connection = new SqliteConnection(_connectionString);
            await _connection.OpenAsync();
        }
        return _connection;
    }

    /// <summary>
    /// 获取所有表信息
    /// </summary>
    public async Task<List<TableInfo>> GetTablesAsync()
    {
        var conn = await GetConnectionAsync();

        var tables = await conn.QueryAsync<string>(
            "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'");

        var result = new List<TableInfo>();
        foreach (var tableName in tables)
        {
            var count = await conn.ExecuteScalarAsync<int>(
                $"SELECT COUNT(*) FROM \"{tableName}\"");
            result.Add(new TableInfo(tableName, "TABLE", count));
        }

        return result;
    }

    /// <summary>
    /// 获取指定表的列信息
    /// </summary>
    public async Task<List<ColumnInfo>> GetColumnsAsync(string tableName)
    {
        var conn = await GetConnectionAsync();

        // 使用 PRAGMA 获取列信息
        var columns = await conn.QueryAsync(
            $"PRAGMA table_info(\"{tableName}\")");

        return columns.Select(c => new ColumnInfo(
            ColumnName: (string)c.name,
            DataType: (string)c.type,
            IsNullable: (long)c.notnull == 0,
            IsPrimaryKey: (long)c.pk > 0,
            DefaultValue: c.dflt_value?.ToString()
        )).ToList();
    }

    /// <summary>
    /// 执行安全的 SELECT 查询
    /// </summary>
    public async Task<string> ExecuteQueryAsync(string sql, int maxRows = 100)
    {
        // 安全检查:只允许 SELECT 语句
        var trimmedSql = sql.Trim().ToUpperInvariant();
        if (!trimmedSql.StartsWith("SELECT"))
        {
            throw new InvalidOperationException(
                "安全限制:仅允许执行 SELECT 查询语句。");
        }

        // 禁止 SQL 注释(防止注入)
        if (trimmedSql.Contains("--") || trimmedSql.Contains("/*"))
        {
            throw new InvalidOperationException(
                "安全限制:查询中不允许包含 SQL 注释。");
        }

        // 使用正则匹配完整的危险关键字(避免 DELETED 误匹配 DELETE)
        string[] forbiddenKeywords = ["DROP", "DELETE", "UPDATE", "INSERT",
            "ALTER", "CREATE", "TRUNCATE", "EXEC", "EXECUTE"];
        foreach (var keyword in forbiddenKeywords)
        {
            // \b 匹配单词边界,确保是独立的关键字而非字段名的一部分
            if (Regex.IsMatch(trimmedSql, $@"\b{keyword}\b"))
            {
                throw new InvalidOperationException(
                    $"安全限制:查询中包含不允许的关键字 '{keyword}'。");
            }
        }

        var conn = await GetConnectionAsync();

        // 添加行数限制
        if (!trimmedSql.Contains("LIMIT"))
        {
            sql = $"{sql.TrimEnd(';')} LIMIT {maxRows}";
        }

        _logger.LogInformation("执行查询: {Sql}", sql);

        var results = await conn.QueryAsync(sql);
        var resultList = results.ToList();

        if (resultList.Count == 0)
        {
            return "查询结果为空,没有找到匹配的数据。";
        }

        // 将结果格式化为 Markdown 表格
        return FormatAsMarkdownTable(resultList);
    }

    /// <summary>
    /// 获取表的统计摘要信息
    /// </summary>
    public async Task<string> GetTableSummaryAsync(string tableName)
    {
        var conn = await GetConnectionAsync();
        var sb = new StringBuilder();

        // 基本统计
        var rowCount = await conn.ExecuteScalarAsync<int>(
            $"SELECT COUNT(*) FROM \"{tableName}\"");
        sb.AppendLine($"## 表 `{tableName}` 统计摘要");
        sb.AppendLine();
        sb.AppendLine($"- **总行数**: {rowCount:N0}");

        // 获取列信息并统计
        var columns = await GetColumnsAsync(tableName);
        sb.AppendLine($"- **总列数**: {columns.Count}");
        sb.AppendLine($"- **主键列**: {string.Join(", ",
            columns.Where(c => c.IsPrimaryKey).Select(c => c.ColumnName))}");
        sb.AppendLine();

        // 对每个数值类型列获取统计信息
        sb.AppendLine("### 列详情");
        sb.AppendLine();
        sb.AppendLine("| 列名 | 类型 | 可空 | 主键 | 非空值数 |");
        sb.AppendLine("|------|------|------|------|---------|");

        foreach (var col in columns)
        {
            var nonNullCount = await conn.ExecuteScalarAsync<int>(
                $"SELECT COUNT(\"{col.ColumnName}\") FROM \"{tableName}\"");
            sb.AppendLine(
                $"| {col.ColumnName} | {col.DataType} | " +
                $"{(col.IsNullable ? "是" : "否")} | " +
                $"{(col.IsPrimaryKey ? "是" : "否")} | {nonNullCount:N0} |");
        }

        // 显示前 5 条样本数据
        sb.AppendLine();
        sb.AppendLine("### 样本数据(前 5 条)");
        sb.AppendLine();
        var sampleData = await conn.QueryAsync(
            $"SELECT * FROM \"{tableName}\" LIMIT 5");
        sb.AppendLine(FormatAsMarkdownTable(sampleData.ToList()));

        return sb.ToString();
    }

    /// <summary>
    /// 将查询结果格式化为 Markdown 表格
    /// </summary>
    private static string FormatAsMarkdownTable(List<dynamic> data)
    {
        if (data.Count == 0) return "无数据";

        var sb = new StringBuilder();
        var firstRow = (IDictionary<string, object>)data[0];
        var headers = firstRow.Keys.ToList();

        // 表头
        sb.AppendLine("| " + string.Join(" | ", headers) + " |");
        sb.AppendLine("| " + string.Join(" | ", headers.Select(_ => "---")) + " |");

        // 数据行
        foreach (var row in data)
        {
            var dict = (IDictionary<string, object>)row;
            var values = headers.Select(h =>
                dict.TryGetValue(h, out var v) ? v?.ToString() ?? "NULL" : "NULL");
            sb.AppendLine("| " + string.Join(" | ", values) + " |");
        }

        sb.AppendLine();
        sb.AppendLine($"*共 {data.Count} 条记录*");

        return sb.ToString();
    }

    public void Dispose()
    {
        _connection?.Dispose();
        GC.SuppressFinalize(this);
    }
}

3.6 定义 MCP 工具

这是 MCP 服务器的核心部分——通过 [McpServerToolType][McpServerTool] 特性将方法暴露给 AI 客户端。

Tools/SchemaTools.cs — 数据库结构查询工具

using System.ComponentModel;
using System.Text;
using DbQueryMCP.Services;
using ModelContextProtocol.Server;

namespace DbQueryMCP.Tools;

/// <summary>
/// 数据库结构查询工具集
/// 提供数据库表结构、列信息等元数据查询能力
/// </summary>
[McpServerToolType]
public static class SchemaTools
{
    /// <summary>
    /// 获取数据库中所有表的列表
    /// </summary>
    [McpServerTool, Description(
        "获取数据库中所有表的列表,包括表名、类型和行数。" +
        "当需要了解数据库有哪些表、数据规模时使用此工具。")]
    public static async Task<string> GetAllTables(DatabaseService dbService)
    {
        var tables = await dbService.GetTablesAsync();

        if (tables.Count == 0)
        {
            return "数据库中没有找到任何表。";
        }

        var sb = new StringBuilder();
        sb.AppendLine("## 数据库表列表");
        sb.AppendLine();
        sb.AppendLine("| 表名 | 类型 | 行数 |");
        sb.AppendLine("|------|------|------|");

        foreach (var table in tables)
        {
            sb.AppendLine($"| {table.TableName} | {table.TableType} | {table.RowCount:N0} |");
        }

        sb.AppendLine();
        sb.AppendLine($"*共 {tables.Count} 张表*");

        return sb.ToString();
    }

    /// <summary>
    /// 获取指定表的列信息
    /// </summary>
    [McpServerTool, Description(
        "获取指定表的所有列的详细信息,包括列名、数据类型、是否可空、是否为主键等。" +
        "当需要了解某张表的结构详情时使用此工具。")]
    public static async Task<string> GetTableColumns(
        DatabaseService dbService,
        [Description("要查询的表名")] string tableName)
    {
        var columns = await dbService.GetColumnsAsync(tableName);

        if (columns.Count == 0)
        {
            return $"未找到表 '{tableName}' 或该表没有列。";
        }

        var sb = new StringBuilder();
        sb.AppendLine($"## 表 `{tableName}` 的列信息");
        sb.AppendLine();
        sb.AppendLine("| 列名 | 数据类型 | 可空 | 主键 | 默认值 |");
        sb.AppendLine("|------|---------|------|------|--------|");

        foreach (var col in columns)
        {
            sb.AppendLine(
                $"| {col.ColumnName} | {col.DataType} | " +
                $"{(col.IsNullable ? "是" : "否")} | " +
                $"{(col.IsPrimaryKey ? "✓" : "")} | " +
                $"{col.DefaultValue ?? "-"} |");
        }

        return sb.ToString();
    }

    /// <summary>
    /// 获取表的统计摘要
    /// </summary>
    [McpServerTool, Description(
        "获取指定表的完整统计摘要,包括行数、列数、主键、每列的非空值统计以及样本数据。" +
        "当需要快速了解一张表的全貌时使用此工具。")]
    public static async Task<string> GetTableSummary(
        DatabaseService dbService,
        [Description("要分析的表名")] string tableName)
    {
        return await dbService.GetTableSummaryAsync(tableName);
    }
}

Tools/QueryTools.cs — 数据查询工具

using System.ComponentModel;
using DbQueryMCP.Services;
using ModelContextProtocol.Server;

namespace DbQueryMCP.Tools;

/// <summary>
/// 数据查询工具集
/// 提供安全的 SQL 查询执行能力
/// </summary>
[McpServerToolType]
public static class QueryTools
{
    /// <summary>
    /// 执行 SQL SELECT 查询
    /// </summary>
    [McpServerTool, Description(
        "在数据库上执行 SQL SELECT 查询并返回结果。" +
        "仅支持 SELECT 语句,禁止 INSERT/UPDATE/DELETE 等修改操作。" +
        "结果默认限制最多返回 100 行,可通过 maxRows 参数调整。")]
    public static async Task<string> ExecuteSelectQuery(
        DatabaseService dbService,
        [Description("要执行的 SQL SELECT 查询语句")] string sql,
        [Description("最大返回行数,默认 100")] int maxRows = 100)
    {
        try
        {
            return await dbService.ExecuteQueryAsync(sql, maxRows);
        }
        catch (InvalidOperationException ex)
        {
            return $"⚠️ 查询被拒绝:{ex.Message}";
        }
        catch (Exception ex)
        {
            return $"❌ 查询执行失败:{ex.Message}";
        }
    }

    /// <summary>
    /// 根据条件搜索数据
    /// </summary>
    [McpServerTool, Description(
        "在指定表中根据条件搜索数据。支持模糊搜索(LIKE)和精确匹配。" +
        "适合快速查找特定数据,无需编写完整的 SQL 语句。")]
    public static async Task<string> SearchInTable(
        DatabaseService dbService,
        [Description("要搜索的表名")] string tableName,
        [Description("要搜索的列名")] string columnName,
        [Description("搜索关键字")] string keyword,
        [Description("是否使用模糊匹配(默认 true)")] bool fuzzy = true)
    {
        try
        {
            var condition = fuzzy
                ? $"\"{columnName}\" LIKE '%{keyword.Replace("'", "''")}%'"
                : $"\"{columnName}\" = '{keyword.Replace("'", "''")}'";

            var sql = $"SELECT * FROM \"{tableName}\" WHERE {condition}";

            return await dbService.ExecuteQueryAsync(sql);
        }
        catch (Exception ex)
        {
            return $"❌ 搜索失败:{ex.Message}";
        }
    }

    /// <summary>
    /// 获取表中的聚合统计数据
    /// </summary>
    [McpServerTool, Description(
        "对指定表的指定列执行聚合统计(COUNT、SUM、AVG、MIN、MAX)。" +
        "适合快速获取数据的统计信息。")]
    public static async Task<string> GetAggregateStats(
        DatabaseService dbService,
        [Description("要统计的表名")] string tableName,
        [Description("要统计的列名")] string columnName,
        [Description("聚合函数:COUNT、SUM、AVG、MIN、MAX")] string aggregateFunction = "COUNT",
        [Description("可选的 WHERE 条件(不含 WHERE 关键字)")] string? whereCondition = null)
    {
        try
        {
            var validFunctions = new[] { "COUNT", "SUM", "AVG", "MIN", "MAX" };
            var func = aggregateFunction.Trim().ToUpperInvariant();

            if (!validFunctions.Contains(func))
            {
                return $"⚠️ 不支持的聚合函数 '{aggregateFunction}'。" +
                       $"支持的函数:{string.Join("、", validFunctions)}";
            }

            var sql = $"SELECT {func}(\"{columnName}\") AS Result FROM \"{tableName}\"";
            if (!string.IsNullOrWhiteSpace(whereCondition))
            {
                sql += $" WHERE {whereCondition}";
            }

            return await dbService.ExecuteQueryAsync(sql);
        }
        catch (Exception ex)
        {
            return $"❌ 统计失败:{ex.Message}";
        }
    }
}

3.7 配置应用入口

Program.cs

using DbQueryMCP.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var builder = Host.CreateApplicationBuilder(args);

// 配置日志输出到 stderr(MCP 协议使用 stdout 通信,日志必须走 stderr)
builder.Logging.AddConsole(consoleLogOptions =>
{
    consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});

// 注册数据库服务
builder.Services.AddSingleton<DatabaseService>();

// 配置 MCP 服务器
builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()        // 使用 stdio 传输(本地模式)
    .WithToolsFromAssembly();          // 自动扫描程序集中的工具

await builder.Build().RunAsync();

appsettings.json

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=sample.db"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning"
    }
  }
}

3.8 创建示例数据库

为了测试我们的 MCP 服务器,创建一个包含示例数据的 SQLite 数据库。在项目根目录创建 InitSampleDb.cs

InitSampleDb — 独立的初始化脚本

注意:这是一个单独的控制台项目,不能放在 DbQueryMCP 项目中(因为两者都使用顶级语句)。
创建方式:dotnet new console -n InitSampleDb,然后添加相同的 Microsoft.Data.SqliteDapper 包。
运行一次生成 sample.db 后,将该文件复制到 DbQueryMCP 的输出目录即可。

// InitSampleDb/Program.cs
// 独立控制台项目,运行一次即可生成示例数据库
using Microsoft.Data.Sqlite;
using Dapper;

var connectionString = "Data Source=sample.db";
using var connection = new SqliteConnection(connectionString);
connection.Open();

// 创建部门表
connection.Execute("""
    CREATE TABLE IF NOT EXISTS Departments (
        Id INTEGER PRIMARY KEY AUTOINCREMENT,
        Name TEXT NOT NULL,
        ManagerName TEXT,
        Budget REAL,
        CreatedAt TEXT DEFAULT (datetime('now'))
    )
""");

// 创建员工表
connection.Execute("""
    CREATE TABLE IF NOT EXISTS Employees (
        Id INTEGER PRIMARY KEY AUTOINCREMENT,
        Name TEXT NOT NULL,
        Email TEXT UNIQUE,
        DepartmentId INTEGER,
        Position TEXT,
        Salary REAL,
        HireDate TEXT,
        IsActive INTEGER DEFAULT 1,
        FOREIGN KEY (DepartmentId) REFERENCES Departments(Id)
    )
""");

// 创建项目表
connection.Execute("""
    CREATE TABLE IF NOT EXISTS Projects (
        Id INTEGER PRIMARY KEY AUTOINCREMENT,
        ProjectName TEXT NOT NULL,
        Description TEXT,
        DepartmentId INTEGER,
        StartDate TEXT,
        EndDate TEXT,
        Status TEXT DEFAULT 'Planning',
        Budget REAL,
        FOREIGN KEY (DepartmentId) REFERENCES Departments(Id)
    )
""");

// 插入示例数据 - 部门
connection.Execute("""
    INSERT OR IGNORE INTO Departments (Id, Name, ManagerName, Budget)
    VALUES
        (1, '技术研发部', '张三', 5000000),
        (2, '产品设计部', '李四', 2000000),
        (3, '市场营销部', '王五', 3000000),
        (4, '人力资源部', '赵六', 1000000),
        (5, '财务部', '钱七', 800000)
""");

// 插入示例数据 - 员工
connection.Execute("""
    INSERT OR IGNORE INTO Employees (Id, Name, Email, DepartmentId, Position, Salary, HireDate, IsActive)
    VALUES
        (1,  '张三', 'zhangsan@company.com',    1, '技术总监',   45000, '2020-03-15', 1),
        (2,  '李四', 'lisi@company.com',        2, '产品经理',   35000, '2021-06-20', 1),
        (3,  '王五', 'wangwu@company.com',      3, '营销总监',   40000, '2019-11-01', 1),
        (4,  '赵六', 'zhaoliu@company.com',     4, 'HR经理',     30000, '2022-01-10', 1),
        (5,  '钱七', 'qianqi@company.com',      5, '财务经理',   32000, '2021-08-05', 1),
        (6,  '孙八', 'sunba@company.com',       1, '高级工程师', 38000, '2020-07-22', 1),
        (7,  '周九', 'zhoujiu@company.com',     1, '前端工程师', 28000, '2022-04-18', 1),
        (8,  '吴十', 'wushi@company.com',       1, '后端工程师', 30000, '2022-09-01', 1),
        (9,  '郑一', 'zhengyi@company.com',     2, 'UI设计师',   25000, '2023-02-14', 1),
        (10, '冯二', 'fengerr@company.com',     3, '市场专员',   18000, '2023-06-01', 1),
        (11, '陈三', 'chensan@company.com',     1, '测试工程师', 26000, '2022-11-20', 1),
        (12, '褚四', 'chusi@company.com',       2, '产品助理',   15000, '2024-01-15', 1),
        (13, '卫五', 'weiwu@company.com',       3, '运营专员',   16000, '2024-03-01', 0),
        (14, '蒋六', 'jiangliu@company.com',    1, '架构师',     50000, '2018-05-10', 1),
        (15, '沈七', 'shenqi@company.com',      4, 'HR专员',     15000, '2024-06-01', 1)
""");

// 插入示例数据 - 项目
connection.Execute("""
    INSERT OR IGNORE INTO Projects (Id, ProjectName, Description, DepartmentId, StartDate, EndDate, Status, Budget)
    VALUES
        (1, '智能客服系统',   '基于AI的客户服务自动化平台',     1, '2024-01-01', '2024-12-31', 'Active',    800000),
        (2, '移动端App改版',  '公司官方App 3.0版本重构',       2, '2024-03-01', '2024-09-30', 'Completed', 500000),
        (3, '品牌推广活动',   '2024年度品牌推广计划',          3, '2024-06-01', '2024-12-31', 'Active',    1200000),
        (4, '员工培训平台',   '在线学习与技能提升平台',        4, '2024-04-01', '2025-03-31', 'Active',    300000),
        (5, '数据中台建设',   '企业级数据治理与分析平台',      1, '2024-07-01', '2025-06-30', 'Planning',  2000000),
        (6, '微服务架构迁移', '单体应用拆分为微服务架构',       1, '2023-10-01', '2024-08-31', 'Completed', 1500000)
""");

Console.WriteLine("✅ 示例数据库初始化完成!");
Console.WriteLine($"   - 部门: {connection.ExecuteScalar<int>("SELECT COUNT(*) FROM Departments")} 条");
Console.WriteLine($"   - 员工: {connection.ExecuteScalar<int>("SELECT COUNT(*) FROM Employees")} 条");
Console.WriteLine($"   - 项目: {connection.ExecuteScalar<int>("SELECT COUNT(*) FROM Projects")} 条");

四、配置 AI 客户端

4.1 在 Cursor 中配置

在项目根目录或 Cursor 的全局配置中创建 .cursor/mcp.json

{
  "mcpServers": {
    "db-query": {
      "command": "dotnet",
      "args": [
        "run",
        "--project",
        "D:\\path\\to\\DbQueryMCP\\DbQueryMCP.csproj"
      ],
      "env": {
        "DOTNET_ENVIRONMENT": "Development"
      }
    }
  }
}

4.2 在 VS Code 中配置

.vscode/mcp.json 中添加:

{
  "inputs": [],
  "servers": {
    "db-query": {
      "type": "stdio",
      "command": "dotnet",
      "args": [
        "run",
        "--project",
        "D:\\path\\to\\DbQueryMCP\\DbQueryMCP.csproj"
      ]
    }
  }
}

4.3 使用编译后的可执行文件(推荐生产方式)

先发布应用:

dotnet publish -c Release -o ./publish

然后配置使用编译后的 exe:

{
  "mcpServers": {
    "db-query": {
      "command": "D:\\path\\to\\publish\\DbQueryMCP.exe",
      "args": []
    }
  }
}

五、实际使用效果

5.1 查询数据库结构

在 Cursor / Copilot 的对话中,你可以这样问:

提问:“帮我看看数据库里有哪些表?”

AI 会自动调用 GetAllTables 工具,cursor截图:
在这里插入图片描述

5.2 分析表结构

提问:“员工表有哪些字段?”

AI 调用 GetTableColumns
在这里插入图片描述

5.3 智能查询数据

提问:“帮我查一下技术研发部所有在职员工的姓名和薪资,按薪资从高到低排列。”

AI 会自动组装 SQL 并调用 ExecuteSelectQuery

SELECT e.Name, e.Position, e.Salary
FROM Employees e
JOIN Departments d ON e.DepartmentId = d.Id
WHERE d.Name = '技术研发部' AND e.IsActive = 1
ORDER BY e.Salary DESC

在这里插入图片描述

5.4 数据统计

提问:“各部门的平均薪资是多少?”

AI 自动构造聚合查询:

SELECT d.Name AS 部门, AVG(e.Salary) AS 平均薪资, COUNT(e.Id) AS 人数
FROM Employees e
JOIN Departments d ON e.DepartmentId = d.Id
WHERE e.IsActive = 1
GROUP BY d.Name
ORDER BY 平均薪资 DESC

在这里插入图片描述

六、进阶:HTTP/SSE 传输模式

6.1 为什么需要 HTTP 传输?

stdio 模式 适合本地开发,MCP 客户端直接启动服务器进程。但在以下场景需要 HTTP 传输:

  • MCP 服务器部署在远程服务器
  • 多个用户共享一个 MCP 服务器
  • 服务器需要连接企业内网数据库
  • 需要更好的认证和授权控制

6.2 改造为 ASP.NET Core 应用

using DbQueryMCP.Services;
using ModelContextProtocol.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

// 注册服务
builder.Services.AddSingleton<DatabaseService>();

// 配置 MCP 服务器(HTTP 模式)
builder.Services
    .AddMcpServer()
    .WithHttpTransport()
    .WithToolsFromAssembly();

var app = builder.Build();

// 映射 MCP 端点
app.MapMcp();

app.Run();

对应的客户端配置(mcp.json):

{
  "servers": {
    "db-query-remote": {
      "type": "http",
      "url": "http://localhost:5000"
    }
  }
}

6.3 添加认证支持

在生产环境中,MCP 服务器应该要求认证:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer(options =>
    {
        options.Authority = "https://your-auth-server.com";
        options.Audience = "mcp-server";
    });

builder.Services.AddAuthorization();
builder.Services.AddSingleton<DatabaseService>();

builder.Services
    .AddMcpServer()
    .WithHttpTransport()
    .WithToolsFromAssembly();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapMcp().RequireAuthorization();

app.Run();

七、容器化部署

7.1 Dockerfile

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
COPY --from=build /app .
EXPOSE 5000
ENTRYPOINT ["dotnet", "DbQueryMCP.dll"]

7.2 Docker Compose(连接外部数据库)

version: '3.8'
services:
  mcp-server:
    build: .
    ports:
      - "5000:5000"
    environment:
      - ConnectionStrings__DefaultConnection=Data Source=/data/production.db
      - ASPNETCORE_ENVIRONMENT=Production
    volumes:
      - ./data:/data

7.3 在 MCP 客户端中使用 Docker 镜像

{
  "mcpServers": {
    "db-query-docker": {
      "command": "docker",
      "args": ["run", "-i", "--rm",
               "-v", "D:/data:/data",
               "your-registry/db-query-mcp:latest"]
    }
  }
}

八、最佳实践与安全考量

8.1 安全最佳实践

实践 说明
只读查询 MCP 工具仅暴露 SELECT 操作,禁止数据修改
SQL 注入防护 使用参数化查询,严格过滤危险关键字
行数限制 默认限制返回行数,防止全表扫描
连接字符串安全 使用环境变量或 Secret Manager 管理连接字符串
审计日志 记录所有查询操作,便于追溯
最小权限 数据库账户仅授予 SELECT 权限

8.2 工具描述的重要性

[Description] 特性中的文本会被直接传递给 AI 模型,它决定了 AI 何时以及如何调用你的工具。好的描述应该:

// ❌ 差的描述 - 太简短,AI 不知道何时使用
[McpServerTool, Description("查询数据")]
public static async Task<string> Query(...)

// ✅ 好的描述 - 明确说明功能、使用场景和限制
[McpServerTool, Description(
    "在数据库上执行 SQL SELECT 查询并返回结果。" +
    "仅支持 SELECT 语句,禁止数据修改操作。" +
    "结果默认限制最多返回 100 行。")]
public static async Task<string> ExecuteSelectQuery(...)

8.3 参数描述同样重要

// ❌ 没有描述
public static async Task<string> Search(string table, string col, string val)

// ✅ 每个参数都有清晰描述
public static async Task<string> SearchInTable(
    [Description("要搜索的表名,如 Employees、Departments")] string tableName,
    [Description("要搜索的列名,如 Name、Email")] string columnName,
    [Description("搜索关键字,支持部分匹配")] string keyword)

8.4 错误处理

MCP 工具应当优雅地处理错误,返回有意义的错误信息而非抛出异常:

[McpServerTool, Description("执行查询")]
public static async Task<string> ExecuteQuery(
    DatabaseService dbService,
    [Description("SQL 查询语句")] string sql)
{
    try
    {
        return await dbService.ExecuteQueryAsync(sql);
    }
    catch (InvalidOperationException ex)
    {
        // 业务逻辑错误 - 返回友好提示
        return $"⚠️ 查询被拒绝:{ex.Message}";
    }
    catch (SqliteException ex)
    {
        // SQL 执行错误 - 返回有助于 AI 修正的信息
        return $"❌ SQL 语法错误:{ex.Message}\n请检查表名和列名是否正确。";
    }
    catch (Exception ex)
    {
        // 未预期的错误
        return $"❌ 发生意外错误:{ex.Message}";
    }
}

8.5 依赖注入的巧妙运用

MCP C# SDK 完美支持依赖注入。工具方法的参数中,非基本类型会自动从 DI 容器中解析:

[McpServerToolType]
public static class MyTools
{
    // dbService 从 DI 容器自动注入
    // tableName 由 AI 客户端传入
    [McpServerTool, Description("获取指定表的统计摘要信息")]
    public static async Task<string> GetData(
        DatabaseService dbService,        // ← 自动注入
        ILogger<MyTools> logger,          // ← 自动注入
        [Description("表名")] string tableName)  // ← AI 传入
    {
        logger.LogInformation("查询表: {Table}", tableName);
        return await dbService.GetTableSummaryAsync(tableName);
    }
}

九、适配 SQL Server / PostgreSQL

上面的示例使用 SQLite,实际项目中通常使用 SQL Server 或 PostgreSQL。以下是适配要点:

9.1 SQL Server 适配

# 替换 NuGet 包
dotnet remove package Microsoft.Data.Sqlite
dotnet add package Microsoft.Data.SqlClient

修改 DatabaseService 中获取表和列信息的 SQL:

// 获取所有表
var tables = await conn.QueryAsync<string>(
    "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'");

// 获取列信息
var columns = await conn.QueryAsync<ColumnInfo>(
    @"SELECT
        COLUMN_NAME AS ColumnName,
        DATA_TYPE AS DataType,
        CASE IS_NULLABLE WHEN 'YES' THEN 1 ELSE 0 END AS IsNullable,
        CASE WHEN pk.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END AS IsPrimaryKey,
        COLUMN_DEFAULT AS DefaultValue
    FROM INFORMATION_SCHEMA.COLUMNS c
    LEFT JOIN (
        SELECT ku.COLUMN_NAME
        FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
        JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE ku
            ON tc.CONSTRAINT_NAME = ku.CONSTRAINT_NAME
        WHERE tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
            AND ku.TABLE_NAME = @TableName
    ) pk ON c.COLUMN_NAME = pk.COLUMN_NAME
    WHERE c.TABLE_NAME = @TableName",
    new { TableName = tableName });

9.2 PostgreSQL 适配

dotnet remove package Microsoft.Data.Sqlite
dotnet add package Npgsql
// 获取所有表
var tables = await conn.QueryAsync<string>(
    @"SELECT tablename FROM pg_tables
      WHERE schemaname = 'public'");

// 获取列信息
var columns = await conn.QueryAsync<ColumnInfo>(
    @"SELECT
        column_name AS ColumnName,
        data_type AS DataType,
        CASE is_nullable WHEN 'YES' THEN true ELSE false END AS IsNullable,
        CASE WHEN pk.column_name IS NOT NULL THEN true ELSE false END AS IsPrimaryKey,
        column_default AS DefaultValue
    FROM information_schema.columns c
    LEFT JOIN (
        SELECT kcu.column_name
        FROM information_schema.table_constraints tc
        JOIN information_schema.key_column_usage kcu
            ON tc.constraint_name = kcu.constraint_name
        WHERE tc.constraint_type = 'PRIMARY KEY'
            AND kcu.table_name = @TableName
    ) pk ON c.column_name = pk.column_name
    WHERE c.table_name = @TableName",
    new { TableName = tableName });

十、完整项目文件参考

DbQueryMCP.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="ModelContextProtocol" Version="0.8.0-preview.1" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
    <PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.0" />
    <PackageReference Include="Dapper" Version="2.1.66" />
  </ItemGroup>

  <ItemGroup>
    <None Update="appsettings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>

十一、总结与展望

11.1 本文要点回顾

  1. MCP 协议 是 AI 与外部工具之间的标准化通信协议
  2. C# MCP SDK 由微软与 Anthropic 联合维护,是官方级实现
  3. 通过 [McpServerToolType][McpServerTool] 特性即可快速暴露工具
  4. 依赖注入 让 MCP 工具可以方便地使用各种服务
  5. 安全至上:只读查询、SQL 注入防护、行数限制是底线
  6. 支持 stdioHTTP/SSE 两种传输模式,适配不同部署场景

11.2 MCP 生态展望

MCP 正处于快速发展阶段,值得关注的方向:

  • NuGet 分发:MCP 服务器可以打包为 NuGet 工具包直接分发
  • Azure Functions:通过 Azure Functions 构建无服务器的远程 MCP 服务器
  • .NET Aspire 集成:未来可能与 .NET Aspire 深度整合
  • Semantic Kernel 协同:MCP 工具与 Semantic Kernel 的 Plugin 体系互补

11.3 推荐资源

资源 链接
MCP 官方网站 https://modelcontextprotocol.io
C# MCP SDK(GitHub) https://github.com/modelcontextprotocol/csharp-sdk
微软官方教程 https://learn.microsoft.com/dotnet/ai/get-started-mcp
.NET 官方博客 https://devblogs.microsoft.com/dotnet/build-a-model-context-protocol-mcp-server-in-csharp
MCP 服务器示例合集 https://github.com/microsoft/mcp-dotnet-samples
NuGet 上的 MCP SDK https://www.nuget.org/packages/ModelContextProtocol

写在最后:MCP 让 AI 从"只能看代码"进化到"能操作你的系统"。作为 .NET 开发者,我们拥有强大的工具链和生态系统来构建生产级的 MCP 服务器。无论是连接企业数据库、调用内部 API,还是整合业务流程,MCP + .NET 都是一个值得投入的技术组合。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐