A .NET MCP server that provides AI agents with safe, flexible SQL database access using MongoDB-style filters and a schema-driven query builder.
LLMs can't safely access databases directly, and traditional solutions like GraphQL/OData aren't LLM-friendly. This project combines:
- Model Context Protocol (MCP) — Standardized LLM ↔ tool communication
- Schema-Driven Query Builder — Generalized, secure SQL generation from JSON config
Read the full architecture deep-dive: Building a Schema-Driven MCP Server
- 🔍 MongoDB-style filters — Syntax LLMs already know
- 🔗 Nested relationships —
Customer → Subscription → Productwith automatic JOINs - 🛡️ Security by design — Field-level allowlists prevent unauthorized access
- 🔌 Dual interface — MCP for AI agents, REST for traditional apps
- 🗄️ Multi-database — SQLite (default) or SQL Server
- 📊 OpenTelemetry — Built-in observability
git clone https://github.com/sanchar10/mcp-sql-query-dotnet.git
cd mcp-sql-query-dotnet
dotnet runThat's it! SQLite database is created and seeded automatically.
| Endpoint | URL |
|---|---|
| MCP | http://localhost:5000/mcp |
| REST API | http://localhost:5000/api/customer/* |
| Swagger | http://localhost:5000/swagger |
curl -X POST http://localhost:5000/api/customer/360 \
-H "Content-Type: application/json" \
-d '{"profile": {"email": "john.doe@example.com"}}'| Tool | Description |
|---|---|
get_customer_360 |
Complete customer view with subscriptions, products, interactions |
get_customer_subscriptions |
Subscriptions with nested products |
get_customer_products |
Products across all subscriptions |
get_customer_interactions |
Customer interaction history |
get_customer_profile |
Profile only |
Uses MongoDB-style operators that LLMs understand:
{
"profile": { "email": "john@example.com" },
"subscription": { "status": "active", "$limit": 5 },
"product": { "price": { "$gte": 100 } }
}| Operator | SQL | Example |
|---|---|---|
$eq |
= |
{ "status": { "$eq": "active" } } |
$ne |
!= |
{ "status": { "$ne": "cancelled" } } |
$gt/$gte |
>/>= |
{ "price": { "$gte": 100 } } |
$lt/$lte |
</<= |
{ "quantity": { "$lt": 5 } } |
$in/$nin |
IN/NOT IN |
{ "status": { "$in": ["active", "pending"] } } |
$like |
LIKE |
{ "name": { "$like": "%John%" } } |
$limit |
LIMIT |
{ "$limit": 10 } |
SQLite is the default. To use SQL Server, update appsettings.json:
{
"Database": {
"Provider": "SqlServer"
},
"ConnectionStrings": {
"SqlServer": "Server=localhost\\SQLEXPRESS;Database=CustomerMCP;Trusted_Connection=True;TrustServerCertificate=True;"
}
}The database is auto-created on first run.
- Add entity definition to
entities.json:
{
"Invoice": {
"tableName": "Invoice",
"identifierField": "id",
"fields": {
"id": { "type": "integer" },
"customer_id": { "type": "string" },
"amount": { "type": "decimal" }
},
"allowedFilterFields": ["id", "customer_id", "amount"],
"relationships": {
"CustomerProfile": { "foreignKey": "customer_id" }
}
}
}- Add a tool method (~10 lines):
[McpServerTool, Description("Get invoices for a customer")]
public async Task<DomainQueryResult> GetCustomerInvoices(
EntityFilter profile,
EntityFilter? invoice = null,
CancellationToken ct = default)
{
return await _queryBuilder.Create()
.From("CustomerProfile")
.Where(profile)
.WithRelated("Invoice", invoice)
.ExecuteAsync(ct);
}├── Program.cs # Entry point
├── entities.json # Entity schema definitions
├── appsettings.json # Configuration
├── Api/ # REST endpoints
├── Data/ # Database providers & initialization
├── Models/ # DTOs and schema models
├── Services/ # Query builder implementation
└── Tools/ # MCP tool definitions
- Architecture Deep-Dive — Full explanation of the two-pattern approach
- MCP Protocol — Model Context Protocol specification
- C# MCP SDK — Official .NET SDK
- .NET 8 SDK
- (Optional) SQL Server Express for production use
MIT
Questions? Open an issue or check the detailed documentation.