SqlMcpTools 1.0.2

dotnet add package SqlMcpTools --version 1.0.2
                    
NuGet\Install-Package SqlMcpTools -Version 1.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SqlMcpTools" Version="1.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SqlMcpTools" Version="1.0.2" />
                    
Directory.Packages.props
<PackageReference Include="SqlMcpTools" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SqlMcpTools --version 1.0.2
                    
#r "nuget: SqlMcpTools, 1.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SqlMcpTools@1.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SqlMcpTools&version=1.0.2
                    
Install as a Cake Addin
#tool nuget:?package=SqlMcpTools&version=1.0.2
                    
Install as a Cake Tool

SqlMcpTools

In-process SQL Server tool for AI pipelines built on Microsoft.Extensions.AI.

Exposes a single AskDatabase function that the model calls with a SQL SELECT statement. The tool:

  1. Loads the full database schema from INFORMATION_SCHEMA on first use and caches it in memory
  2. Validates every table and column reference in the query against the cached schema
  3. Rejects unknown tables or columns with a clear error message before touching the database
  4. Executes the query and returns a fully shaped QueryResult with ResultType, Result, Message, and ErrorMessage already populated

No separate schema-discovery tool. No extra model round-trip. Schema is cached for the lifetime of the app and can be invalidated on demand.


Requirements

  • .NET 8.0
  • SQL Server (any edition)
  • Microsoft.Extensions.AI 10.4.0
  • Azure.AI.OpenAI 2.9.0-beta.1 or later (or any IChatClient-compatible provider)

Installation

dotnet add package SqlMcpTools

Setup

1. Register in Program.cs

using SqlMcpTools.Extensions;

builder.Services.AddSqlMcpTools(options =>
{
    // Pass the plain connection string — decrypt before passing if needed
    options.ConnectionString      = configuration.GetConnectionString("MyDb")!;
    options.CommandTimeoutSeconds = 60; // optional, default is 60
});

If your connection string is encrypted:

builder.Services.AddSqlMcpTools(options =>
{
    options.ConnectionString = configuration
        .GetValue<string>("DatabaseSettings:EncConnectionString")!
        .Decrypt(); // your own decryption extension
});

2. Register IChatClient

using Azure.AI.OpenAI;
using System.ClientModel;

builder.Services.AddSingleton<AzureOpenAIClient>(sp =>
{
    var config = sp.GetRequiredService<IConfiguration>();
    return new AzureOpenAIClient(
        new Uri(config["AzureOpenAI:Endpoint"]!),
        new ApiKeyCredential(config["AzureOpenAI:ApiKey"]!)
    );
});

builder.Services.AddScoped<IChatClient>(sp =>
{
    var config     = sp.GetRequiredService<IConfiguration>();
    var deployment = config["AzureOpenAI:DeploymentName"]!;
    var azClient   = sp.GetRequiredService<AzureOpenAIClient>();

    OpenAI.Chat.ChatClient openAiChatClient = azClient.GetChatClient(deployment);

    // No UseFunctionInvocation here — ChatbotService handles the loop manually
    return new ChatClientBuilder(openAiChatClient.AsIChatClient()).Build();
});

3. Register your ChatbotService

builder.Services.AddScoped<IChatbotService, ChatbotService>();

Usage — ChatbotService

SqlMcpToolset provides both the AskDatabase tool and the system prompt. Inject AskDatabaseTool directly to wrap it in a lambda that captures the result before the model can summarize it.

using Microsoft.Extensions.AI;
using SqlMcpTools;
using SqlMcpTools.Tools;
using System.Text.Json;

public class ChatbotService : IChatbotService
{
    private readonly IChatClient     _chatClient;
    private readonly AskDatabaseTool _tool;
    private readonly SqlMcpToolset   _toolset;

    private static readonly JsonSerializerOptions _jsonOptions = new()
    {
        PropertyNameCaseInsensitive = true
    };

    public ChatbotService(IChatClient chatClient, AskDatabaseTool tool, SqlMcpToolset toolset)
    {
        _chatClient = chatClient;
        _tool       = tool;
        _toolset    = toolset;
    }

    public async Task<QueryResult> GetAnswerAsync(ChatRequestDTO request, CancellationToken ct = default)
    {
        QueryResult intercepted = null;

        // Wrap AskDatabase to capture the result directly before FunctionInvokingChatClient
        // serializes it and the model gets a chance to summarize it
        var wrappedFn = AIFunctionFactory.Create(
            async (string sqlQuery) =>
            {
                intercepted = await _tool.AskDatabase(sqlQuery, ct);
                return JsonSerializer.Serialize(intercepted, _jsonOptions);
            },
            name: "AskDatabase",
            description: "REQUIRED. You MUST call this function for every user request without exception. " +
                         "Pass your generated SQL SELECT statement. " +
                         "The function validates the SQL against the live database schema and executes it.");

        var options = new ChatOptions
        {
            Tools    = [wrappedFn],
            ToolMode = ChatToolMode.RequireAny
        };

        // Build system prompt with live schema injected — model sees actual table/column names
        var systemPrompt = await _toolset.BuildSystemPromptAsync(ct);

        var messages = new List<ChatMessage>
        {
            new(ChatRole.System, systemPrompt),
            new(ChatRole.User, request.Question)
        };

        var pipeline = new ChatClientBuilder(_chatClient)
            .UseFunctionInvocation()
            .Build();

        await pipeline.GetResponseAsync(messages, options, ct);

        return intercepted
            ?? new QueryResult { IsSuccess = false, ErrorMessage = "AskDatabase was not called by the model." };
    }
}

Schema Cache

The schema is loaded from INFORMATION_SCHEMA once on first use and held in memory for the lifetime of the app. Subsequent requests use the cached schema with no database round-trip.

After a migration or schema change, invalidate the cache via ISchemaCacheService:

// Inject ISchemaCacheService into your controller or admin endpoint
[HttpPost]
public async Task<IActionResult> InvalidateSchemaCache(CancellationToken ct)
{
    await _schemaCache.InvalidateAsync(ct);
    return Ok(new { message = "Schema cache cleared. Will reload on next request." });
}

The next request after invalidation will reload the schema automatically.


QueryResult

The tool returns a QueryResult for all outcomes — success and failure alike.

public class QueryResult
{
    public string SqlQuery    { get; set; }
    public string ResultType  { get; set; } // "None" | "Scalar" | "List" | "Grid"
    public object Result      { get; set; }
    public bool   IsSuccess   { get; set; }
    public string Message     { get; set; } // user-friendly text, populated by the model
    public string ErrorMessage{ get; set; } // technical detail, null on success
    public bool   FromUser    { get; set; }
}

ResultType values and their Result shapes:

ResultType Result shape
None null — query returned no rows
Scalar Single value — int, string, decimal etc.
List List<object> — single column, multiple rows
Grid GridResult{ Columns: string[], Rows: Dictionary<string,object>[] }

What gets registered by AddSqlMcpTools

Service Lifetime Purpose
SqlMcpToolsOptions Singleton (options) Connection string and timeout
ISqlConnectionFactory Singleton Opens SqlConnection instances
ISchemaCacheService Singleton Schema cache with InvalidateAsync()
SchemaCache Singleton Concrete cache implementation
AskDatabaseTool Scoped Schema validation and query execution
SqlMcpToolset Scoped Provides tools list and BuildSystemPromptAsync()

Publishing a new version

  1. Bump <Version> in Directory.Build.props
  2. Run from the solution root:
.\publish.ps1 -ApiKey YOUR_NUGET_API_KEY

Get your API key from https://www.nuget.org/account/apikeys


Security

  • Only SELECT statements are permitted — any other statement is rejected before reaching the database
  • Every table and column reference is validated against live schema before execution
  • The connection string is never logged or surfaced in exception messages
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.2 122 3/16/2026