@eamonboyle/mssql-mcp 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +134 -22
  2. package/dist/__tests__/ExplainQueryTool.test.d.ts +2 -0
  3. package/dist/__tests__/ExplainQueryTool.test.d.ts.map +1 -0
  4. package/dist/__tests__/ExplainQueryTool.test.js +96 -0
  5. package/dist/__tests__/ExplainQueryTool.test.js.map +1 -0
  6. package/dist/__tests__/config.test.d.ts +2 -0
  7. package/dist/__tests__/config.test.d.ts.map +1 -0
  8. package/dist/__tests__/config.test.js +29 -0
  9. package/dist/__tests__/config.test.js.map +1 -0
  10. package/dist/__tests__/db.test.d.ts +1 -1
  11. package/dist/__tests__/db.test.js +203 -203
  12. package/dist/__tests__/promptRegistry.test.d.ts +2 -0
  13. package/dist/__tests__/promptRegistry.test.d.ts.map +1 -0
  14. package/dist/__tests__/promptRegistry.test.js +40 -0
  15. package/dist/__tests__/promptRegistry.test.js.map +1 -0
  16. package/dist/__tests__/resourceRegistry.test.d.ts +2 -0
  17. package/dist/__tests__/resourceRegistry.test.d.ts.map +1 -0
  18. package/dist/__tests__/resourceRegistry.test.js +40 -0
  19. package/dist/__tests__/resourceRegistry.test.js.map +1 -0
  20. package/dist/__tests__/schema.test.d.ts +2 -0
  21. package/dist/__tests__/schema.test.d.ts.map +1 -0
  22. package/dist/__tests__/schema.test.js +130 -0
  23. package/dist/__tests__/schema.test.js.map +1 -0
  24. package/dist/__tests__/sql.test.d.ts +2 -0
  25. package/dist/__tests__/sql.test.d.ts.map +1 -0
  26. package/dist/__tests__/sql.test.js +35 -0
  27. package/dist/__tests__/sql.test.js.map +1 -0
  28. package/dist/__tests__/toolRegistry.test.d.ts +2 -0
  29. package/dist/__tests__/toolRegistry.test.d.ts.map +1 -0
  30. package/dist/__tests__/toolRegistry.test.js +47 -0
  31. package/dist/__tests__/toolRegistry.test.js.map +1 -0
  32. package/dist/__tests__/validation.test.d.ts +1 -1
  33. package/dist/__tests__/validation.test.js +110 -110
  34. package/dist/__tests__/validation.test.js.map +1 -1
  35. package/dist/__tests__/writeTools.test.d.ts +2 -0
  36. package/dist/__tests__/writeTools.test.d.ts.map +1 -0
  37. package/dist/__tests__/writeTools.test.js +115 -0
  38. package/dist/__tests__/writeTools.test.js.map +1 -0
  39. package/dist/config.d.ts +5 -0
  40. package/dist/config.d.ts.map +1 -0
  41. package/dist/config.js +34 -0
  42. package/dist/config.js.map +1 -0
  43. package/dist/db.d.ts +28 -24
  44. package/dist/db.d.ts.map +1 -1
  45. package/dist/db.js +144 -112
  46. package/dist/db.js.map +1 -1
  47. package/dist/index.d.ts +2 -2
  48. package/dist/index.js +70 -122
  49. package/dist/index.js.map +1 -1
  50. package/dist/promptRegistry.d.ts +16 -0
  51. package/dist/promptRegistry.d.ts.map +1 -0
  52. package/dist/promptRegistry.js +107 -0
  53. package/dist/promptRegistry.js.map +1 -0
  54. package/dist/resourceRegistry.d.ts +12 -0
  55. package/dist/resourceRegistry.d.ts.map +1 -0
  56. package/dist/resourceRegistry.js +172 -0
  57. package/dist/resourceRegistry.js.map +1 -0
  58. package/dist/schema.d.ts +41 -0
  59. package/dist/schema.d.ts.map +1 -0
  60. package/dist/schema.js +211 -0
  61. package/dist/schema.js.map +1 -0
  62. package/dist/sql.d.ts +7 -0
  63. package/dist/sql.d.ts.map +1 -0
  64. package/dist/sql.js +73 -0
  65. package/dist/sql.js.map +1 -0
  66. package/dist/toolRegistry.d.ts +16 -0
  67. package/dist/toolRegistry.d.ts.map +1 -0
  68. package/dist/toolRegistry.js +401 -0
  69. package/dist/toolRegistry.js.map +1 -0
  70. package/dist/tools/CreateIndexTool.d.ts +30 -23
  71. package/dist/tools/CreateIndexTool.d.ts.map +1 -1
  72. package/dist/tools/CreateIndexTool.js +47 -77
  73. package/dist/tools/CreateIndexTool.js.map +1 -1
  74. package/dist/tools/CreateTableTool.d.ts +18 -11
  75. package/dist/tools/CreateTableTool.d.ts.map +1 -1
  76. package/dist/tools/CreateTableTool.js +37 -61
  77. package/dist/tools/CreateTableTool.js.map +1 -1
  78. package/dist/tools/DeleteDataTool.d.ts +22 -0
  79. package/dist/tools/DeleteDataTool.d.ts.map +1 -0
  80. package/dist/tools/DeleteDataTool.js +35 -0
  81. package/dist/tools/DeleteDataTool.js.map +1 -0
  82. package/dist/tools/DescribeObjectTool.d.ts +21 -0
  83. package/dist/tools/DescribeObjectTool.d.ts.map +1 -0
  84. package/dist/tools/DescribeObjectTool.js +31 -0
  85. package/dist/tools/DescribeObjectTool.js.map +1 -0
  86. package/dist/tools/DescribeTableTool.d.ts +19 -20
  87. package/dist/tools/DescribeTableTool.d.ts.map +1 -1
  88. package/dist/tools/DescribeTableTool.js +23 -44
  89. package/dist/tools/DescribeTableTool.js.map +1 -1
  90. package/dist/tools/DropTableTool.d.ts +13 -11
  91. package/dist/tools/DropTableTool.d.ts.map +1 -1
  92. package/dist/tools/DropTableTool.js +30 -44
  93. package/dist/tools/DropTableTool.js.map +1 -1
  94. package/dist/tools/ExplainQueryTool.d.ts +32 -0
  95. package/dist/tools/ExplainQueryTool.d.ts.map +1 -0
  96. package/dist/tools/ExplainQueryTool.js +102 -0
  97. package/dist/tools/ExplainQueryTool.js.map +1 -0
  98. package/dist/tools/InsertDataTool.d.ts +20 -16
  99. package/dist/tools/InsertDataTool.d.ts.map +1 -1
  100. package/dist/tools/InsertDataTool.js +91 -108
  101. package/dist/tools/InsertDataTool.js.map +1 -1
  102. package/dist/tools/ListObjectsTool.d.ts +20 -0
  103. package/dist/tools/ListObjectsTool.d.ts.map +1 -0
  104. package/dist/tools/ListObjectsTool.js +26 -0
  105. package/dist/tools/ListObjectsTool.js.map +1 -0
  106. package/dist/tools/ListTableTool.d.ts +20 -16
  107. package/dist/tools/ListTableTool.d.ts.map +1 -1
  108. package/dist/tools/ListTableTool.js +32 -51
  109. package/dist/tools/ListTableTool.js.map +1 -1
  110. package/dist/tools/ReadDataTool.d.ts +35 -33
  111. package/dist/tools/ReadDataTool.d.ts.map +1 -1
  112. package/dist/tools/ReadDataTool.js +92 -105
  113. package/dist/tools/ReadDataTool.js.map +1 -1
  114. package/dist/tools/SearchDataTool.d.ts +27 -0
  115. package/dist/tools/SearchDataTool.d.ts.map +1 -0
  116. package/dist/tools/SearchDataTool.js +61 -0
  117. package/dist/tools/SearchDataTool.js.map +1 -0
  118. package/dist/tools/UpdateDataTool.d.ts +21 -16
  119. package/dist/tools/UpdateDataTool.d.ts.map +1 -1
  120. package/dist/tools/UpdateDataTool.js +45 -65
  121. package/dist/tools/UpdateDataTool.js.map +1 -1
  122. package/dist/validation.d.ts +13 -13
  123. package/dist/validation.js +133 -133
  124. package/dist/validation.js.map +1 -1
  125. package/dist/writeSafety.d.ts +21 -0
  126. package/dist/writeSafety.d.ts.map +1 -0
  127. package/dist/writeSafety.js +169 -0
  128. package/dist/writeSafety.js.map +1 -0
  129. package/package.json +5 -4
package/README.md CHANGED
@@ -4,8 +4,8 @@
4
4
  [![npm version](https://img.shields.io/npm/v/@eamonboyle/mssql-mcp.svg)](https://www.npmjs.com/package/@eamonboyle/mssql-mcp)
5
5
  [![Node.js 18+](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen)](https://nodejs.org/)
6
6
 
7
- [![Add to Cursor](https://img.shields.io/badge/Add_to-Cursor-000000?style=for-the-badge&logo=cursor&logoColor=white)](https://cursor.com/en/install-mcp?name=MSSQL&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBlYW1vbmJveWxlL21zc3FsLW1jcCJdLCJlbnYiOnsiU0VSVkVSX05BTUUiOiJsb2NhbGhvc3QiLCJEQVRBQkFTRV9OQU1FIjoiWW91ckRhdGFiYXNlIiwiREJfVVNFUiI6IiIsIkRCX1BBU1NXT1JEIjoiIiwiUkVBRE9OTFkiOiJmYWxzZSJ9fQ==)
8
- [![Install in VS Code](https://img.shields.io/badge/Install_in-VS_Code-007ACC?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://intradeus.github.io/http-protocol-redirector?r=vscode%3A%2F%2Fmcp%2Finstall%3F%257B%2522name%2522%253A%2522mssql%2522%252C%2522type%2522%253A%2522stdio%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522%2540eamonboyle%252Fmssql-mcp%2522%255D%252C%2522env%2522%253A%257B%2522SERVER_NAME%2522%253A%2522localhost%2522%252C%2522DATABASE_NAME%2522%253A%2522YourDatabase%2522%252C%2522DB_USER%2522%253A%2522%2522%252C%2522DB_PASSWORD%2522%253A%2522%2522%252C%2522READONLY%2522%253A%2522false%2522%257D%257D)
7
+ [![Add to Cursor](https://img.shields.io/badge/Add_to-Cursor-000000?style=for-the-badge&logo=cursor&logoColor=white)](https://cursor.com/en/install-mcp?name=MSSQL&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBlYW1vbmJveWxlL21zc3FsLW1jcCJdLCJlbnYiOnsiU0VSVkVSX05BTUUiOiJsb2NhbGhvc3QiLCJEQVRBQkFTRV9OQU1FIjoiWW91ckRhdGFiYXNlIiwiREFUQUJBU0VTIjoiIiwiREJfVVNFUiI6IiIsIkRCX1BBU1NXT1JEIjoiIiwiUkVBRE9OTFkiOiJmYWxzZSIsIkNPTk5FQ1RJT05fVElNRU9VVCI6IjMwIiwiUVVFUllfVElNRU9VVF9NUyI6IjMwMDAwIiwiTUFYX1JPV1MiOiIxMDAwMCIsIlRSVVNUX1NFUlZFUl9DRVJUSUZJQ0FURSI6ImZhbHNlIn19)
8
+ [![Install in VS Code](https://img.shields.io/badge/Install_in-VS_Code-007ACC?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://intradeus.github.io/http-protocol-redirector?r=vscode%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522mssql%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522%2540eamonboyle%252Fmssql-mcp%2522%255D%252C%2522env%2522%253A%257B%2522SERVER_NAME%2522%253A%2522localhost%2522%252C%2522DATABASE_NAME%2522%253A%2522YourDatabase%2522%252C%2522DATABASES%2522%253A%2522%2522%252C%2522DB_USER%2522%253A%2522%2522%252C%2522DB_PASSWORD%2522%253A%2522%2522%252C%2522READONLY%2522%253A%2522false%2522%252C%2522CONNECTION_TIMEOUT%2522%253A%252230%2522%252C%2522QUERY_TIMEOUT_MS%2522%253A%252230000%2522%252C%2522MAX_ROWS%2522%253A%252210000%2522%252C%2522TRUST_SERVER_CERTIFICATE%2522%253A%2522false%2522%257D%257D)
9
9
 
10
10
  > ⚠️ **EXPERIMENTAL USE ONLY** — This MCP Server is provided for educational and experimental purposes. It is NOT intended for production use. Use appropriate security measures and test thoroughly before any deployment.
11
11
 
@@ -23,11 +23,13 @@ AI: *queries your MSSQL database and returns the results in plain English*
23
23
  ## Features 📊
24
24
 
25
25
  - **Natural language to SQL** — Ask questions in plain English
26
- - **CRUD operations** — Create, read, update, and delete data
27
- - **Schema management** — Create tables, indexes; describe and drop tables
26
+ - **Row-level CRUD support** — Read, insert, update, and delete rows with dedicated tools
27
+ - **Schema discovery** — Inspect tables, views, procedures, functions, and triggers
28
+ - **Query analysis** — Generate estimated execution plans with `explain_query`
29
+ - **MCP resources and prompts** — Expose schema snapshots and prompt templates to capable clients
28
30
  - **Multi-database support** — Connect to multiple databases on the same server
29
- - **Read-only mode** — Restrict to SELECT-only for safer environments
30
- - **Secure by default** — WHERE clauses required for reads/updates; SQL injection safeguards
31
+ - **Read-only mode** — Restrict to inspection, search, read, and explain tools for safer environments
32
+ - **Secure by default** — WHERE clauses required for updates/deletes; SQL injection safeguards for reads
31
33
 
32
34
  ## Supported AI Clients
33
35
 
@@ -40,7 +42,11 @@ AI: *queries your MSSQL database and returns the results in plain English*
40
42
 
41
43
  ### One-Click Install (Cursor / VS Code)
42
44
 
43
- Click **Add to Cursor** or **Install in VS Code** above to add the MCP server. Edit the config to add your database credentials (`DB_USER`, `DB_PASSWORD`, etc.). No cloning required—runs via `npx`.
45
+ Click **Add to Cursor** or **Install in VS Code** above to add the MCP server—no cloning required; it runs via `npx`.
46
+
47
+ **Cursor** opens a dedicated install page that lists env vars you can edit before saving (similar to a short form).
48
+
49
+ **VS Code** only applies the JSON embedded in the `vscode:mcp/install` link: you get static placeholder values, not an interactive database wizard. To be prompted for host, database, and credentials when the server starts, add [`inputs`](https://code.visualstudio.com/docs/copilot/reference/mcp-configuration#_input-variables-for-sensitive-data) to `.vscode/mcp.json` as in the **Prompted inputs** example under **VS Code (`mcp.json`)** below.
44
50
 
45
51
  ### Prerequisites
46
52
 
@@ -80,15 +86,17 @@ npm run build
80
86
  | `READONLY` | No | `"true"` for read-only mode, `"false"` for full access (default: `"false"`) |
81
87
  | `DATABASES` | No | Comma-separated allowlist for multi-database access (e.g., `ProdDB,StagingDB`) |
82
88
  | `CONNECTION_TIMEOUT` | No | Timeout in seconds (default: `30`) |
89
+ | `QUERY_TIMEOUT_MS` | No | Query timeout in milliseconds (default: `30000`) |
90
+ | `MAX_ROWS` | No | Maximum rows returned by read tools (default: `10000`) |
83
91
  | `TRUST_SERVER_CERTIFICATE` | No | `"true"` for self-signed certs (e.g., local dev) (default: `"false"`) |
84
92
 
85
93
  \* Required for SQL authentication. For Windows/Integrated authentication, consult the [mssql](https://www.npmjs.com/package/mssql) package documentation.
86
94
 
87
95
  \*\* Required for single-database setups. When `DATABASES` is provided, `DATABASE_NAME` becomes optional and is used as the default database if set.
88
96
 
89
- ### Option 1: Cursor / VS Code Setup
97
+ ### Cursor (`mcp.json`)
90
98
 
91
- 1. Create or edit `.vscode/mcp.json` in your workspace:
99
+ Use [global or project MCP config](https://cursor.com/docs/context/mcp): e.g. `~/.cursor/mcp.json` or `.cursor/mcp.json` in your repo.
92
100
 
93
101
  ```json
94
102
  {
@@ -109,9 +117,93 @@ npm run build
109
117
  }
110
118
  ```
111
119
 
112
- 2. Restart Cursor/VS Code.
120
+ Restart Cursor after changes.
121
+
122
+ ### VS Code (`mcp.json`)
113
123
 
114
- ### Option 2: Claude Desktop Setup
124
+ VS Code uses `.vscode/mcp.json` (or **MCP: Open User Configuration**) with a top-level [`servers`](https://code.visualstudio.com/docs/copilot/reference/mcp-configuration) object—**not** `mcpServers`.
125
+
126
+ **Static env** (same idea as the one-click link; edit values in the file):
127
+
128
+ ```json
129
+ {
130
+ "servers": {
131
+ "mssql": {
132
+ "type": "stdio",
133
+ "command": "npx",
134
+ "args": ["-y", "@eamonboyle/mssql-mcp"],
135
+ "env": {
136
+ "SERVER_NAME": "localhost",
137
+ "DATABASE_NAME": "AppDB",
138
+ "DATABASES": "AppDB,ReportingDB",
139
+ "DB_USER": "your_username",
140
+ "DB_PASSWORD": "your_password",
141
+ "READONLY": "false",
142
+ "CONNECTION_TIMEOUT": "30",
143
+ "QUERY_TIMEOUT_MS": "30000",
144
+ "MAX_ROWS": "10000",
145
+ "TRUST_SERVER_CERTIFICATE": "false"
146
+ }
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ **Prompted inputs** (closest to Cursor’s hosted form: VS Code asks on first start, then stores values). Use `${input:…}` in `env` and define matching entries under `inputs`:
153
+
154
+ ```json
155
+ {
156
+ "inputs": [
157
+ {
158
+ "type": "promptString",
159
+ "id": "mssql-server",
160
+ "description": "SQL Server host (e.g. localhost or my-server.database.windows.net)"
161
+ },
162
+ {
163
+ "type": "promptString",
164
+ "id": "mssql-database",
165
+ "description": "Default database name"
166
+ },
167
+ {
168
+ "type": "promptString",
169
+ "id": "mssql-databases",
170
+ "description": "Optional: comma-separated DB allowlist (e.g. AppDB,ReportingDB). Leave empty for a single database."
171
+ },
172
+ {
173
+ "type": "promptString",
174
+ "id": "mssql-user",
175
+ "description": "SQL Server login (SQL authentication)"
176
+ },
177
+ {
178
+ "type": "promptString",
179
+ "id": "mssql-password",
180
+ "description": "SQL Server password",
181
+ "password": true
182
+ }
183
+ ],
184
+ "servers": {
185
+ "mssql": {
186
+ "type": "stdio",
187
+ "command": "npx",
188
+ "args": ["-y", "@eamonboyle/mssql-mcp"],
189
+ "env": {
190
+ "SERVER_NAME": "${input:mssql-server}",
191
+ "DATABASE_NAME": "${input:mssql-database}",
192
+ "DATABASES": "${input:mssql-databases}",
193
+ "DB_USER": "${input:mssql-user}",
194
+ "DB_PASSWORD": "${input:mssql-password}",
195
+ "READONLY": "false",
196
+ "CONNECTION_TIMEOUT": "30",
197
+ "QUERY_TIMEOUT_MS": "30000",
198
+ "MAX_ROWS": "10000",
199
+ "TRUST_SERVER_CERTIFICATE": "false"
200
+ }
201
+ }
202
+ }
203
+ }
204
+ ```
205
+
206
+ ### Claude Desktop Setup
115
207
 
116
208
  1. Open **File → Settings → Developer → Edit Config**
117
209
  2. Add the MCP server configuration:
@@ -166,29 +258,49 @@ See `src/samples/` for example configs:
166
258
  Once configured, you can ask things like:
167
259
 
168
260
  - "Show me all users from New York"
261
+ - "Search the customers table for email addresses containing acme.com"
169
262
  - "Create a new table called products with columns for id, name, and price"
170
263
  - "Update all pending orders to completed status"
264
+ - "Delete inactive sessions older than 30 days"
171
265
  - "List all tables in the database"
172
266
  - "Describe the schema of the customers table"
267
+ - "List all views and procedures in the reporting database"
268
+ - "Explain why this SELECT query is slow"
173
269
 
174
270
  ## Available Tools
175
271
 
176
- | Tool | Read-only | Description |
177
- | ---------------- | --------- | ---------------------------- |
178
- | `read_data` | ✓ | Execute SELECT queries |
179
- | `list_table` | ✓ | List tables in a database |
180
- | `describe_table` | ✓ | Get table schema |
181
- | `insert_data` | | Insert rows |
182
- | `update_data` | | Update rows (requires WHERE) |
183
- | `create_table` | | Create tables |
184
- | `create_index` | | Create indexes |
185
- | `drop_table` | | Drop tables |
272
+ | Tool | Read-only | Description |
273
+ | ----------------- | --------- | ------------------------------------------------------- |
274
+ | `read_data` | ✓ | Execute validated SELECT queries |
275
+ | `search_data` | ✓ | Search one or more columns with parameterized `LIKE` |
276
+ | `explain_query` | ✓ | Get an estimated execution plan for a SELECT query |
277
+ | `list_table` | ✓ | List tables in a database |
278
+ | `describe_table` | ✓ | Get table schema (optional `schemaName`) |
279
+ | `list_objects` | ✓ | List tables, views, procedures, functions, and triggers |
280
+ | `describe_object` | ✓ | Describe an object definition and metadata |
281
+ | `insert_data` | | Insert rows |
282
+ | `update_data` | | Update rows (requires WHERE) |
283
+ | `delete_data` | | Delete rows (requires WHERE) |
284
+ | `create_table` | | Create tables |
285
+ | `create_index` | | Create indexes |
286
+ | `drop_table` | | Drop tables |
287
+
288
+ ## Resources And Prompts
289
+
290
+ Clients that support MCP resources and prompts can use additional discovery surfaces:
291
+
292
+ - **Resources** — Server config, prompt catalog, per-database table lists, per-database object lists, and dynamic table/object resources
293
+ - **Prompts** — `explore_schema`, `draft_safe_select`, and `review_write_operation`
294
+
295
+ ## Changelog
296
+
297
+ Release notes: [CHANGELOG.md](https://github.com/eamonboyle/mssql-mcp/blob/main/CHANGELOG.md).
186
298
 
187
299
  ## Security Notes
188
300
 
189
301
  - **Credentials** — Never commit `DB_USER`/`DB_PASSWORD` or config files with secrets. Use environment variables or a secrets manager.
190
302
  - **Read-only mode** — Set `READONLY: "true"` when you only need queries.
191
- - **WHERE clauses** — Read and update operations require explicit WHERE clauses to reduce accidental full-table operations.
303
+ - **WHERE clauses** — Update and delete operations require explicit WHERE clauses to reduce accidental full-table changes.
192
304
  - **SQL injection** — The server validates and restricts dangerous SQL patterns.
193
305
 
194
306
  ## Contributing
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ExplainQueryTool.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExplainQueryTool.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ExplainQueryTool.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,96 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ const explainMockState = vi.hoisted(() => ({
3
+ closeCalls: 0,
4
+ statements: [],
5
+ failOnQuery: false,
6
+ }));
7
+ const mockRequestModule = vi.hoisted(() => {
8
+ class MockRequest {
9
+ async query(statement) {
10
+ return this.run(statement);
11
+ }
12
+ async batch(statement) {
13
+ return this.run(statement);
14
+ }
15
+ async run(statement) {
16
+ explainMockState.statements.push(statement);
17
+ if (statement === "SET SHOWPLAN_XML ON") {
18
+ return { recordsets: [] };
19
+ }
20
+ if (statement === "SET SHOWPLAN_XML OFF") {
21
+ return { recordsets: [] };
22
+ }
23
+ if (explainMockState.failOnQuery) {
24
+ throw new Error("query failed");
25
+ }
26
+ return {
27
+ recordsets: [[{ planXml: "<ShowPlanXML />" }]],
28
+ };
29
+ }
30
+ }
31
+ function mockPool() {
32
+ return {
33
+ request() {
34
+ return new MockRequest();
35
+ },
36
+ async close() {
37
+ explainMockState.closeCalls += 1;
38
+ },
39
+ };
40
+ }
41
+ return { MockRequest, mockPool };
42
+ });
43
+ vi.mock("mssql", () => ({
44
+ default: {
45
+ Request: mockRequestModule.MockRequest,
46
+ },
47
+ }));
48
+ vi.mock("../db.js", () => ({
49
+ getDedicatedSqlPool: vi.fn(async () => ({
50
+ pool: mockRequestModule.mockPool(),
51
+ })),
52
+ }));
53
+ import { ExplainQueryTool } from "../tools/ExplainQueryTool.js";
54
+ describe("ExplainQueryTool", () => {
55
+ beforeEach(() => {
56
+ vi.spyOn(console, "error").mockImplementation(() => { });
57
+ explainMockState.closeCalls = 0;
58
+ explainMockState.statements.length = 0;
59
+ explainMockState.failOnQuery = false;
60
+ });
61
+ afterEach(() => {
62
+ vi.restoreAllMocks();
63
+ });
64
+ it("uses a dedicated pool and always disables SHOWPLAN_XML", async () => {
65
+ const tool = new ExplainQueryTool();
66
+ const result = await tool.run({
67
+ query: "SELECT * FROM movies",
68
+ });
69
+ expect(result).toMatchObject({
70
+ success: true,
71
+ planXml: "<ShowPlanXML />",
72
+ recordsets: undefined,
73
+ });
74
+ expect(explainMockState.statements).toEqual([
75
+ "SET SHOWPLAN_XML ON",
76
+ "SELECT * FROM movies",
77
+ "SET SHOWPLAN_XML OFF",
78
+ ]);
79
+ expect(explainMockState.closeCalls).toBe(1);
80
+ });
81
+ it("cleans up SHOWPLAN state after failures", async () => {
82
+ explainMockState.failOnQuery = true;
83
+ const tool = new ExplainQueryTool();
84
+ const result = await tool.run({
85
+ query: "SELECT * FROM movies",
86
+ });
87
+ expect(result).toMatchObject({ success: false });
88
+ expect(explainMockState.statements).toEqual([
89
+ "SET SHOWPLAN_XML ON",
90
+ "SELECT * FROM movies",
91
+ "SET SHOWPLAN_XML OFF",
92
+ ]);
93
+ expect(explainMockState.closeCalls).toBe(1);
94
+ });
95
+ });
96
+ //# sourceMappingURL=ExplainQueryTool.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExplainQueryTool.test.js","sourceRoot":"","sources":["../../src/__tests__/ExplainQueryTool.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,29 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { clampRowLimit, getDefaultSearchLimit, getMaxRows, getQueryTimeoutMs, } from "../config.js";
3
+ const originalEnv = process.env;
4
+ afterEach(() => {
5
+ process.env = { ...originalEnv };
6
+ });
7
+ describe("config helpers", () => {
8
+ it("uses defaults when env vars are absent", () => {
9
+ delete process.env.MAX_ROWS;
10
+ delete process.env.QUERY_TIMEOUT_MS;
11
+ expect(getMaxRows()).toBe(10000);
12
+ expect(getQueryTimeoutMs()).toBe(30000);
13
+ expect(getDefaultSearchLimit()).toBe(100);
14
+ });
15
+ it("reads env-based limits", () => {
16
+ process.env.MAX_ROWS = "250";
17
+ process.env.QUERY_TIMEOUT_MS = "45000";
18
+ expect(getMaxRows()).toBe(250);
19
+ expect(getQueryTimeoutMs()).toBe(45000);
20
+ expect(getDefaultSearchLimit()).toBe(100);
21
+ });
22
+ it("clamps row limits to the configured maximum", () => {
23
+ process.env.MAX_ROWS = "50";
24
+ expect(clampRowLimit(200)).toBe(50);
25
+ expect(clampRowLimit("5")).toBe(5);
26
+ expect(clampRowLimit(undefined)).toBe(50);
27
+ });
28
+ });
29
+ //# sourceMappingURL=config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,UAAU,EACV,iBAAiB,GAClB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;AAEhC,SAAS,CAAC,GAAG,EAAE;IACb,OAAO,CAAC,GAAG,GAAG,EAAE,GAAG,WAAW,EAAE,CAAC;AACnC,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC5B,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;QAEpC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,CAAC,qBAAqB,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,OAAO,CAAC,GAAG,CAAC,QAAQ,GAAG,KAAK,CAAC;QAC7B,OAAO,CAAC,GAAG,CAAC,gBAAgB,GAAG,OAAO,CAAC;QAEvC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,CAAC,qBAAqB,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,OAAO,CAAC,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC;QAE5B,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,2 +1,2 @@
1
- export {};
1
+ export {};
2
2
  //# sourceMappingURL=db.test.d.ts.map