-
-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathMcpSqlFileTestFixture.cs
More file actions
93 lines (79 loc) · 3.51 KB
/
Copy pathMcpSqlFileTestFixture.cs
File metadata and controls
93 lines (79 loc) · 3.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using NpgsqlRest.Mcp;
using NpgsqlRest.SqlFileSource;
namespace NpgsqlRestTests.Setup;
[CollectionDefinition("McpSqlFileFixture")]
public class McpSqlFileFixtureCollection : ICollectionFixture<McpSqlFileTestFixture> { }
/// <summary>
/// Confirms MCP works with the SqlFileSource (endpoints generated from .sql files), not just
/// database routines: a single-command and a multi-command SQL file, each opted in with `@mcp`.
/// </summary>
public class McpSqlFileTestFixture : IDisposable
{
private readonly WebApplication _app;
private readonly HttpClient _client;
private readonly string _sqlDir;
private readonly Mcp _mcp = new(new McpOptions { Enabled = true });
public HttpClient Client => _client;
public IReadOnlyDictionary<string, JsonObject> Tools => _mcp.Tools;
public McpSqlFileTestFixture()
{
var connectionString = Database.Create();
_sqlDir = Path.Combine(Path.GetTempPath(), "npgsqlrest_mcp_sql_" + Guid.NewGuid().ToString("N")[..8]);
Directory.CreateDirectory(_sqlDir);
// Single command.
File.WriteAllText(Path.Combine(_sqlDir, "mcp_sql_single.sql"), """
-- HTTP GET
-- @mcp Single-command SQL file tool.
select 42 as answer;
""");
// Multiple commands in one file.
File.WriteAllText(Path.Combine(_sqlDir, "mcp_sql_multi.sql"), """
-- HTTP GET
-- @mcp Multi-command SQL file tool.
select 1 as a;
select 2 as b;
""");
// MCP-ONLY: bare @mcp, NO HTTP tag. The file must pass the SqlFileSource pre-gate (the `mcp`
// annotation is endpoint-requesting), become a tool, and default to internal-only (no REST route).
File.WriteAllText(Path.Combine(_sqlDir, "mcp_sql_mcp_only.sql"), """
-- @mcp MCP-only SQL file tool.
select 7 as lucky;
""");
// No HTTP tag, no endpoint-requesting annotation: a utility script matching the glob. Must be
// skipped by the pre-gate — no endpoint, no tool.
File.WriteAllText(Path.Combine(_sqlDir, "not_an_endpoint.sql"), """
-- just a utility script, not an endpoint
select 'should never be exposed';
""");
var builder = WebApplication.CreateBuilder();
builder.WebHost.UseUrls("http://127.0.0.1:0");
_app = builder.Build();
_app.UseNpgsqlRest(new NpgsqlRestOptions(connectionString)
{
CommentsMode = CommentsMode.OnlyAnnotated,
EndpointSources =
[
new SqlFileSource(new SqlFileSourceOptions
{
FilePattern = _sqlDir.Replace('\\', '/') + "/**/*.sql",
CommentsMode = CommentsMode.OnlyAnnotated,
})
],
EndpointCreateHandlers = [_mcp],
});
_app.StartAsync().GetAwaiter().GetResult();
_client = new HttpClient { BaseAddress = new Uri(_app.Urls.First()), Timeout = TimeSpan.FromHours(1) };
}
#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize
public void Dispose()
#pragma warning restore CA1816 // Dispose methods should call SuppressFinalize
{
_client.Dispose();
_app.StopAsync().GetAwaiter().GetResult();
_app.DisposeAsync().GetAwaiter().GetResult();
try { Directory.Delete(_sqlDir, recursive: true); } catch { /* best effort */ }
}
}