mssql-mcp 2.1.1 → 2.3.1
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.
- package/LICENSE +21 -0
- package/README.md +161 -89
- package/dist/src/config.d.ts +39 -0
- package/dist/src/config.js +37 -0
- package/dist/src/constants.d.ts +15 -0
- package/dist/src/constants.js +15 -0
- package/dist/src/db/connection.d.ts +8 -0
- package/dist/src/db/connection.js +80 -0
- package/dist/src/db/query-builders.d.ts +3 -0
- package/dist/src/db/query-builders.js +58 -0
- package/dist/src/db/validators.d.ts +5 -0
- package/dist/src/db/validators.js +25 -0
- package/dist/src/index.js +26 -0
- package/dist/src/resources/connection.d.ts +2 -0
- package/dist/src/resources/connection.js +19 -0
- package/dist/src/resources/metadata.d.ts +2 -0
- package/dist/src/resources/metadata.js +58 -0
- package/dist/src/schemas/outputs.d.ts +153 -0
- package/dist/src/schemas/outputs.js +54 -0
- package/dist/src/server.d.ts +2 -0
- package/dist/src/server.js +27 -0
- package/dist/src/tools/connect.d.ts +2 -0
- package/dist/src/tools/connect.js +45 -0
- package/dist/src/tools/databases.d.ts +2 -0
- package/dist/src/tools/databases.js +53 -0
- package/dist/src/tools/procedure.d.ts +2 -0
- package/dist/src/tools/procedure.js +106 -0
- package/dist/src/tools/query.d.ts +2 -0
- package/dist/src/tools/query.js +92 -0
- package/dist/src/tools/schema.d.ts +2 -0
- package/dist/src/tools/schema.js +96 -0
- package/dist/src/tools/status.d.ts +2 -0
- package/dist/src/tools/status.js +17 -0
- package/dist/src/tools/table.d.ts +2 -0
- package/dist/src/tools/table.js +261 -0
- package/dist/src/transports/http.d.ts +3 -0
- package/dist/src/transports/http.js +54 -0
- package/dist/src/transports/stdio.d.ts +2 -0
- package/dist/src/transports/stdio.js +23 -0
- package/dist/src/types.d.ts +37 -0
- package/dist/src/types.js +1 -0
- package/dist/src/utils/errors.d.ts +19 -0
- package/dist/src/utils/errors.js +29 -0
- package/dist/src/utils/format.d.ts +6 -0
- package/dist/src/utils/format.js +27 -0
- package/dist/src/utils/markdown.d.ts +3 -0
- package/dist/src/utils/markdown.js +33 -0
- package/dist/src/utils/pagination.d.ts +3 -0
- package/dist/src/utils/pagination.js +18 -0
- package/dist/tests/unit/markdown.test.d.ts +1 -0
- package/dist/tests/unit/markdown.test.js +70 -0
- package/dist/tests/unit/query-builders.test.d.ts +1 -0
- package/dist/tests/unit/query-builders.test.js +63 -0
- package/dist/tests/unit/tool-contracts.test.d.ts +1 -0
- package/dist/tests/unit/tool-contracts.test.js +62 -0
- package/dist/tests/unit/validators.test.d.ts +1 -0
- package/dist/tests/unit/validators.test.js +51 -0
- package/package.json +10 -6
- package/dist/index.js +0 -648
- /package/dist/{index.d.ts → src/index.d.ts} +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2025 BYMCS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
# MS SQL Server MCP Server v2.
|
|
1
|
+
# MS SQL Server MCP Server v2.3.1
|
|
2
2
|
|
|
3
3
|
🚀 **Model Context Protocol (MCP) server** for Microsoft SQL Server - compatible with Claude Desktop, Cursor, Windsurf and VS Code.
|
|
4
4
|
|
|
5
|
+
[](https://github.com/BYMCS/mssql-mcp/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/mssql-mcp)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
## Standards Alignment
|
|
10
|
+
|
|
11
|
+
- Protocol target: [MCP draft/latest spec](https://modelcontextprotocol.io/specification/draft/server/tools)
|
|
12
|
+
- Transport spec: [Transports](https://modelcontextprotocol.io/specification/draft/basic/transports)
|
|
13
|
+
- Security: [Security best practices](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices)
|
|
14
|
+
- SDK: `@modelcontextprotocol/sdk` pinned to `1.28.0`
|
|
15
|
+
|
|
5
16
|
## 🚀 Quick Start
|
|
6
17
|
|
|
7
18
|
### 1. Install
|
|
@@ -52,118 +63,179 @@ npm install -g mssql-mcp
|
|
|
52
63
|
}
|
|
53
64
|
```
|
|
54
65
|
|
|
55
|
-
|
|
66
|
+
**HTTP transport** (remote/hosted scenarios):
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"servers": {
|
|
70
|
+
"mssql": {
|
|
71
|
+
"type": "http",
|
|
72
|
+
"url": "http://127.0.0.1:3001",
|
|
73
|
+
"env": {}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> Replace with your actual database credentials. Credentials are read from the **server environment only** — never passed as tool parameters.
|
|
80
|
+
|
|
81
|
+
## ��️ Tool Catalog
|
|
82
|
+
|
|
83
|
+
### Primary tools (use these)
|
|
84
|
+
|
|
85
|
+
| Tool | Read-only | Description |
|
|
86
|
+
|------|-----------|-------------|
|
|
87
|
+
| `mssql_connect_database` | No | Connect using env variables. Idempotent. |
|
|
88
|
+
| `mssql_disconnect_database` | No | Close connection. Idempotent. |
|
|
89
|
+
| `mssql_connection_status` | ✅ | Connection state and pool metrics |
|
|
90
|
+
| `mssql_run_sql_query` | ⚠️ No | Execute arbitrary SQL. **May mutate data.** |
|
|
91
|
+
| `mssql_list_schema_objects` | ✅ | List tables/views/procedures/functions with pagination |
|
|
92
|
+
| `mssql_describe_table_columns` | ✅ | Column definitions for a table |
|
|
93
|
+
| `mssql_read_table_rows` | ✅ | Paginated rows with projection and safe WHERE |
|
|
94
|
+
| `mssql_execute_stored_procedure` | ⚠️ No | Execute a stored procedure |
|
|
95
|
+
| `mssql_list_databases` | ✅ | List all databases on the instance |
|
|
96
|
+
|
|
97
|
+
All data tools accept a `response_format` parameter (`"json"` | `"markdown"`, default `"json"`). Use `"markdown"` to get human-readable table output.
|
|
98
|
+
|
|
99
|
+
### Deprecated aliases (still work for backward compatibility)
|
|
100
|
+
|
|
101
|
+
| Old name | Use instead |
|
|
102
|
+
|----------|-------------|
|
|
103
|
+
| `connect_database` | `mssql_connect_database` |
|
|
104
|
+
| `disconnect_database` | `mssql_disconnect_database` |
|
|
105
|
+
| `connection_status` | `mssql_connection_status` |
|
|
106
|
+
| `execute_query` | `mssql_run_sql_query` |
|
|
107
|
+
| `run_sql_query` | `mssql_run_sql_query` |
|
|
108
|
+
| `get_schema` | `mssql_list_schema_objects` |
|
|
109
|
+
| `list_schema_objects` | `mssql_list_schema_objects` |
|
|
110
|
+
| `describe_table` | `mssql_describe_table_columns` |
|
|
111
|
+
| `describe_table_columns` | `mssql_describe_table_columns` |
|
|
112
|
+
| `get_table_data` | `mssql_read_table_rows` |
|
|
113
|
+
| `read_table_rows` | `mssql_read_table_rows` |
|
|
114
|
+
| `execute_procedure` | `mssql_execute_stored_procedure` |
|
|
115
|
+
| `execute_stored_procedure` | `mssql_execute_stored_procedure` |
|
|
116
|
+
| `list_databases` | `mssql_list_databases` |
|
|
117
|
+
|
|
118
|
+
## 🚌 Transport Modes
|
|
119
|
+
|
|
120
|
+
| Mode | Use when |
|
|
121
|
+
|------|----------|
|
|
122
|
+
| `stdio` (default) | Local IDE integration (Claude Desktop, Cursor, VS Code) |
|
|
123
|
+
| `http` | Remote/hosted deployment, testing with MCP Inspector |
|
|
56
124
|
|
|
57
|
-
|
|
125
|
+
```bash
|
|
126
|
+
# stdio (default)
|
|
127
|
+
node dist/src/index.js
|
|
58
128
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
| `get_schema` | List database objects (tables, views, procedures) |
|
|
66
|
-
| `describe_table` | Get detailed table structure |
|
|
67
|
-
| `get_table_data` | Retrieve data with pagination |
|
|
68
|
-
| `execute_procedure` | Execute stored procedures |
|
|
69
|
-
| `list_databases` | List all databases |
|
|
129
|
+
# HTTP on 127.0.0.1:3001
|
|
130
|
+
MCP_TRANSPORT=http node dist/src/index.js
|
|
131
|
+
|
|
132
|
+
# Custom HTTP host/port
|
|
133
|
+
MCP_TRANSPORT=http MCP_HOST=0.0.0.0 MCP_PORT=8080 node dist/src/index.js
|
|
134
|
+
```
|
|
70
135
|
|
|
71
136
|
## 🔧 Environment Variables
|
|
72
137
|
|
|
138
|
+
### Database connection
|
|
139
|
+
|
|
73
140
|
| Variable | Required | Default | Description |
|
|
74
141
|
|----------|----------|---------|-------------|
|
|
75
|
-
| `DB_SERVER` | ✅ |
|
|
76
|
-
| `DB_DATABASE` | ❌ |
|
|
77
|
-
| `DB_USER` | ❌ |
|
|
78
|
-
| `DB_PASSWORD` | ❌ |
|
|
79
|
-
| `DB_PORT` | ❌ | 1433 |
|
|
80
|
-
| `DB_ENCRYPT` | ❌ | true | Enable TLS
|
|
81
|
-
| `DB_TRUST_SERVER_CERTIFICATE` | ❌ | false | Trust self-signed
|
|
82
|
-
| `DB_CONNECTION_TIMEOUT` | ❌ | 30000 | Connection timeout
|
|
83
|
-
| `DB_REQUEST_TIMEOUT` | ❌ | 30000 |
|
|
84
|
-
|
|
85
|
-
### Azure SQL Configuration
|
|
86
|
-
For Azure SQL Database, use these settings:
|
|
87
|
-
```json
|
|
88
|
-
{
|
|
89
|
-
"DB_ENCRYPT": "true",
|
|
90
|
-
"DB_TRUST_SERVER_CERTIFICATE": "false"
|
|
91
|
-
}
|
|
92
|
-
```
|
|
142
|
+
| `DB_SERVER` | ✅ | — | SQL Server hostname or IP |
|
|
143
|
+
| `DB_DATABASE` | ❌ | — | Database name |
|
|
144
|
+
| `DB_USER` | ❌ | — | Login username |
|
|
145
|
+
| `DB_PASSWORD` | ❌ | — | Login password |
|
|
146
|
+
| `DB_PORT` | ❌ | 1433 | TCP port |
|
|
147
|
+
| `DB_ENCRYPT` | ❌ | true | Enable TLS (required for Azure SQL) |
|
|
148
|
+
| `DB_TRUST_SERVER_CERTIFICATE` | ❌ | false | Trust self-signed certs |
|
|
149
|
+
| `DB_CONNECTION_TIMEOUT` | ❌ | 30000 | Connection timeout ms |
|
|
150
|
+
| `DB_REQUEST_TIMEOUT` | ❌ | 30000 | Query timeout ms |
|
|
93
151
|
|
|
94
|
-
###
|
|
95
|
-
For local development with self-signed certificates:
|
|
96
|
-
```json
|
|
97
|
-
{
|
|
98
|
-
"DB_ENCRYPT": "true",
|
|
99
|
-
"DB_TRUST_SERVER_CERTIFICATE": "true"
|
|
100
|
-
}
|
|
101
|
-
```
|
|
152
|
+
### Transport
|
|
102
153
|
|
|
103
|
-
|
|
154
|
+
| Variable | Default | Description |
|
|
155
|
+
|----------|---------|-------------|
|
|
156
|
+
| `MCP_TRANSPORT` | `stdio` | `stdio` or `http` |
|
|
157
|
+
| `MCP_HOST` | `127.0.0.1` | HTTP bind address |
|
|
158
|
+
| `MCP_PORT` | `3001` | HTTP port |
|
|
104
159
|
|
|
105
|
-
|
|
106
|
-
- ✅ **Azure SQL Compatible**: TLS encryption enabled by default
|
|
107
|
-
- ✅ **Complete SQL Support**: All database operations
|
|
108
|
-
- ✅ **Parameterized Queries**: SQL injection protection
|
|
109
|
-
- ✅ **Connection Pooling**: Efficient resource management
|
|
110
|
-
- ✅ **Performance Monitoring**: Execution time tracking
|
|
160
|
+
## 🔒 Security Model
|
|
111
161
|
|
|
112
|
-
|
|
162
|
+
- **No credential parameters**: All connection settings come from environment variables only. Tool inputs cannot override connection config.
|
|
163
|
+
- **Identifier validation**: Schema, table, and procedure names are validated against a safe identifier pattern before interpolation into SQL.
|
|
164
|
+
- **Parameterized queries**: All user-supplied values (WHERE clause values, column values) must be passed as named parameters via `@paramName` — never embedded in query strings.
|
|
165
|
+
- **Origin validation**: HTTP transport validates `Origin` header and only allows localhost by default.
|
|
166
|
+
- **SQL risk labeling**: `run_sql_query` and `execute_stored_procedure` are explicitly labeled as non-read-only and open-world.
|
|
113
167
|
|
|
114
|
-
###
|
|
115
|
-
```
|
|
116
|
-
Use the connect_database tool to establish a connection.
|
|
117
|
-
```
|
|
168
|
+
### ⚠️ SQL Risk Notes
|
|
118
169
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
```
|
|
170
|
+
`run_sql_query` accepts arbitrary SQL including DDL and DML. To minimize risk:
|
|
171
|
+
- Use a least-privilege SQL login (SELECT-only where possible)
|
|
172
|
+
- Never run the server with a `sysadmin` or `sa` account
|
|
173
|
+
- Consider network firewall rules to limit what the server can reach
|
|
124
174
|
|
|
125
|
-
|
|
126
|
-
```
|
|
127
|
-
Use describe_table with tableName: "Customers" to see column details.
|
|
128
|
-
```
|
|
175
|
+
## 📄 Pagination
|
|
129
176
|
|
|
130
|
-
|
|
177
|
+
All list tools return a `pagination` object:
|
|
131
178
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"count": 20,
|
|
182
|
+
"limit": 20,
|
|
183
|
+
"offset": 0,
|
|
184
|
+
"has_more": true,
|
|
185
|
+
"next_offset": 20,
|
|
186
|
+
"total_count": 150
|
|
187
|
+
}
|
|
188
|
+
```
|
|
136
189
|
|
|
137
|
-
|
|
138
|
-
- For Azure SQL: Set `DB_ENCRYPT=true` (default)
|
|
139
|
-
- For self-signed certs: Set `DB_TRUST_SERVER_CERTIFICATE=true`
|
|
140
|
-
- For local dev without encryption: Set `DB_ENCRYPT=false`
|
|
190
|
+
Default page size: **20 rows**. Maximum: **200 rows**.
|
|
141
191
|
|
|
142
|
-
|
|
192
|
+
Results are also truncated if the serialized payload exceeds 100KB, with a `truncation_message` explaining how many rows were dropped.
|
|
143
193
|
|
|
144
|
-
|
|
145
|
-
- ✅ Added `DB_ENCRYPT` environment variable (Issue #1)
|
|
146
|
-
- ✅ Azure SQL Database compatibility improved
|
|
147
|
-
- ✅ Encryption enabled by default (`DB_ENCRYPT=true`)
|
|
148
|
-
- ✅ Fixed `DB_TRUST_SERVER_CERTIFICATE` default to `false`
|
|
194
|
+
## 🏗️ Architecture
|
|
149
195
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
196
|
+
```
|
|
197
|
+
src/
|
|
198
|
+
index.ts ← bootstrap (env, transport selection)
|
|
199
|
+
server.ts ← createServer() factory
|
|
200
|
+
constants.ts ← limits, defaults, protocol strings
|
|
201
|
+
config.ts ← env parsing
|
|
202
|
+
types.ts ← shared TypeScript interfaces
|
|
203
|
+
db/
|
|
204
|
+
connection.ts ← connection pool singleton
|
|
205
|
+
validators.ts ← SQL identifier validation
|
|
206
|
+
query-builders.ts ← safe parameterized query construction
|
|
207
|
+
tools/ ← one file per tool group
|
|
208
|
+
resources/ ← MCP resource handlers
|
|
209
|
+
transports/ ← stdio and HTTP transports
|
|
210
|
+
utils/
|
|
211
|
+
errors.ts ← error normalization helpers
|
|
212
|
+
format.ts ← JSON formatting, payload truncation
|
|
213
|
+
markdown.ts ← markdown table/list rendering helpers
|
|
214
|
+
pagination.ts ← pagination metadata helpers
|
|
215
|
+
```
|
|
154
216
|
|
|
155
|
-
|
|
156
|
-
- ✅ Documentation improvements
|
|
217
|
+
## 🧪 Inspector Smoke Test
|
|
157
218
|
|
|
158
|
-
|
|
219
|
+
```bash
|
|
220
|
+
npx @modelcontextprotocol/inspector
|
|
221
|
+
```
|
|
159
222
|
|
|
160
|
-
|
|
223
|
+
Expected:
|
|
224
|
+
- stdio server connects
|
|
225
|
+
- Tool list renders with all tools
|
|
226
|
+
- `mssql_connection_status` returns JSON without a connection
|
|
227
|
+
- `mssql_connect_database` works when env variables are set
|
|
161
228
|
|
|
162
|
-
##
|
|
229
|
+
## 🔨 Development
|
|
163
230
|
|
|
164
|
-
|
|
165
|
-
|
|
231
|
+
```bash
|
|
232
|
+
npm install
|
|
233
|
+
npm run typecheck # type check only
|
|
234
|
+
npm run build # compile TypeScript
|
|
235
|
+
npm test # run unit tests
|
|
236
|
+
npm run ci # typecheck + build + test
|
|
237
|
+
```
|
|
166
238
|
|
|
167
|
-
|
|
239
|
+
## License
|
|
168
240
|
|
|
169
|
-
|
|
241
|
+
[MIT](LICENSE) © BYMCS
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { DatabaseConfig } from "./types.js";
|
|
3
|
+
export declare const ConfigSchema: z.ZodObject<{
|
|
4
|
+
server: z.ZodString;
|
|
5
|
+
database: z.ZodOptional<z.ZodString>;
|
|
6
|
+
user: z.ZodOptional<z.ZodString>;
|
|
7
|
+
password: z.ZodOptional<z.ZodString>;
|
|
8
|
+
port: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
9
|
+
encrypt: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
10
|
+
trustServerCertificate: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
11
|
+
connectionTimeout: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
12
|
+
requestTimeout: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
13
|
+
}, "strip", z.ZodTypeAny, {
|
|
14
|
+
server: string;
|
|
15
|
+
port: number;
|
|
16
|
+
encrypt: boolean;
|
|
17
|
+
trustServerCertificate: boolean;
|
|
18
|
+
connectionTimeout: number;
|
|
19
|
+
requestTimeout: number;
|
|
20
|
+
database?: string | undefined;
|
|
21
|
+
user?: string | undefined;
|
|
22
|
+
password?: string | undefined;
|
|
23
|
+
}, {
|
|
24
|
+
server: string;
|
|
25
|
+
database?: string | undefined;
|
|
26
|
+
port?: number | undefined;
|
|
27
|
+
user?: string | undefined;
|
|
28
|
+
password?: string | undefined;
|
|
29
|
+
encrypt?: boolean | undefined;
|
|
30
|
+
trustServerCertificate?: boolean | undefined;
|
|
31
|
+
connectionTimeout?: number | undefined;
|
|
32
|
+
requestTimeout?: number | undefined;
|
|
33
|
+
}>;
|
|
34
|
+
export declare function loadConfigFromEnv(): DatabaseConfig;
|
|
35
|
+
export interface HttpConfig {
|
|
36
|
+
host: string;
|
|
37
|
+
port: number;
|
|
38
|
+
}
|
|
39
|
+
export declare function loadHttpConfig(): HttpConfig;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { DEFAULT_PORT, DEFAULT_ENCRYPT, DEFAULT_TRUST_SERVER_CERTIFICATE, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_REQUEST_TIMEOUT, } from "./constants.js";
|
|
3
|
+
export const ConfigSchema = z.object({
|
|
4
|
+
server: z.string().min(1, "Server address is required"),
|
|
5
|
+
database: z.string().optional(),
|
|
6
|
+
user: z.string().optional(),
|
|
7
|
+
password: z.string().optional(),
|
|
8
|
+
port: z.number().int().min(1).max(65535).optional().default(DEFAULT_PORT),
|
|
9
|
+
encrypt: z.boolean().optional().default(DEFAULT_ENCRYPT),
|
|
10
|
+
trustServerCertificate: z.boolean().optional().default(DEFAULT_TRUST_SERVER_CERTIFICATE),
|
|
11
|
+
connectionTimeout: z.number().int().min(1000).max(60000).optional().default(DEFAULT_CONNECTION_TIMEOUT),
|
|
12
|
+
requestTimeout: z.number().int().min(1000).max(300000).optional().default(DEFAULT_REQUEST_TIMEOUT),
|
|
13
|
+
});
|
|
14
|
+
export function loadConfigFromEnv() {
|
|
15
|
+
const raw = {
|
|
16
|
+
server: process.env.DB_SERVER,
|
|
17
|
+
database: process.env.DB_DATABASE,
|
|
18
|
+
user: process.env.DB_USER,
|
|
19
|
+
password: process.env.DB_PASSWORD,
|
|
20
|
+
port: process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : DEFAULT_PORT,
|
|
21
|
+
encrypt: process.env.DB_ENCRYPT !== "false",
|
|
22
|
+
trustServerCertificate: process.env.DB_TRUST_SERVER_CERTIFICATE === "true",
|
|
23
|
+
connectionTimeout: process.env.DB_CONNECTION_TIMEOUT
|
|
24
|
+
? parseInt(process.env.DB_CONNECTION_TIMEOUT, 10)
|
|
25
|
+
: DEFAULT_CONNECTION_TIMEOUT,
|
|
26
|
+
requestTimeout: process.env.DB_REQUEST_TIMEOUT
|
|
27
|
+
? parseInt(process.env.DB_REQUEST_TIMEOUT, 10)
|
|
28
|
+
: DEFAULT_REQUEST_TIMEOUT,
|
|
29
|
+
};
|
|
30
|
+
return ConfigSchema.parse(raw);
|
|
31
|
+
}
|
|
32
|
+
export function loadHttpConfig() {
|
|
33
|
+
return {
|
|
34
|
+
host: process.env.MCP_HOST ?? "127.0.0.1",
|
|
35
|
+
port: process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 3001,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const SERVER_NAME = "mssql-mcp-server";
|
|
2
|
+
export declare const SERVER_VERSION = "2.3.1";
|
|
3
|
+
export declare const DEFAULT_PORT = 1433;
|
|
4
|
+
export declare const DEFAULT_ENCRYPT = true;
|
|
5
|
+
export declare const DEFAULT_TRUST_SERVER_CERTIFICATE = false;
|
|
6
|
+
export declare const DEFAULT_CONNECTION_TIMEOUT = 30000;
|
|
7
|
+
export declare const DEFAULT_REQUEST_TIMEOUT = 30000;
|
|
8
|
+
export declare const POOL_MAX = 10;
|
|
9
|
+
export declare const POOL_MIN = 0;
|
|
10
|
+
export declare const POOL_IDLE_TIMEOUT_MS = 30000;
|
|
11
|
+
export declare const DEFAULT_PAGE_SIZE = 20;
|
|
12
|
+
export declare const MAX_PAGE_SIZE = 200;
|
|
13
|
+
export declare const MAX_TEXT_PAYLOAD_BYTES = 100000;
|
|
14
|
+
export declare const HTTP_DEFAULT_HOST = "127.0.0.1";
|
|
15
|
+
export declare const HTTP_DEFAULT_PORT = 3001;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const SERVER_NAME = "mssql-mcp-server";
|
|
2
|
+
export const SERVER_VERSION = "2.3.1";
|
|
3
|
+
export const DEFAULT_PORT = 1433;
|
|
4
|
+
export const DEFAULT_ENCRYPT = true;
|
|
5
|
+
export const DEFAULT_TRUST_SERVER_CERTIFICATE = false;
|
|
6
|
+
export const DEFAULT_CONNECTION_TIMEOUT = 30000;
|
|
7
|
+
export const DEFAULT_REQUEST_TIMEOUT = 30000;
|
|
8
|
+
export const POOL_MAX = 10;
|
|
9
|
+
export const POOL_MIN = 0;
|
|
10
|
+
export const POOL_IDLE_TIMEOUT_MS = 30000;
|
|
11
|
+
export const DEFAULT_PAGE_SIZE = 20;
|
|
12
|
+
export const MAX_PAGE_SIZE = 200;
|
|
13
|
+
export const MAX_TEXT_PAYLOAD_BYTES = 100_000;
|
|
14
|
+
export const HTTP_DEFAULT_HOST = "127.0.0.1";
|
|
15
|
+
export const HTTP_DEFAULT_PORT = 3001;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import sql from "mssql";
|
|
2
|
+
import type { DatabaseConfig, ConnectionState } from "../types.js";
|
|
3
|
+
export declare function connectPool(config: DatabaseConfig): Promise<void>;
|
|
4
|
+
export declare function disconnectPool(): Promise<void>;
|
|
5
|
+
export declare function getPool(): sql.ConnectionPool | null;
|
|
6
|
+
export declare function requirePool(): sql.ConnectionPool;
|
|
7
|
+
export declare function getConnectionState(): ConnectionState;
|
|
8
|
+
export declare function closePoolOnShutdown(): Promise<void>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import sql from "mssql";
|
|
2
|
+
import { POOL_MAX, POOL_MIN, POOL_IDLE_TIMEOUT_MS } from "../constants.js";
|
|
3
|
+
let pool = null;
|
|
4
|
+
let currentConfig = null;
|
|
5
|
+
export async function connectPool(config) {
|
|
6
|
+
if (pool) {
|
|
7
|
+
console.error("Closing existing connection...");
|
|
8
|
+
await pool.close();
|
|
9
|
+
pool = null;
|
|
10
|
+
}
|
|
11
|
+
console.error(`Connecting to ${config.server}:${config.port}`);
|
|
12
|
+
const newPool = new sql.ConnectionPool({
|
|
13
|
+
server: config.server,
|
|
14
|
+
database: config.database,
|
|
15
|
+
user: config.user,
|
|
16
|
+
password: config.password,
|
|
17
|
+
port: config.port,
|
|
18
|
+
options: {
|
|
19
|
+
encrypt: config.encrypt,
|
|
20
|
+
trustServerCertificate: config.trustServerCertificate,
|
|
21
|
+
enableArithAbort: true,
|
|
22
|
+
},
|
|
23
|
+
connectionTimeout: config.connectionTimeout,
|
|
24
|
+
requestTimeout: config.requestTimeout,
|
|
25
|
+
pool: { max: POOL_MAX, min: POOL_MIN, idleTimeoutMillis: POOL_IDLE_TIMEOUT_MS },
|
|
26
|
+
});
|
|
27
|
+
newPool.on("error", (err) => {
|
|
28
|
+
console.error("Pool error:", err);
|
|
29
|
+
});
|
|
30
|
+
try {
|
|
31
|
+
await newPool.connect();
|
|
32
|
+
pool = newPool;
|
|
33
|
+
currentConfig = config;
|
|
34
|
+
console.error(`Connected to ${config.server}${config.database ? `/${config.database}` : ""}`);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
try {
|
|
38
|
+
await newPool.close();
|
|
39
|
+
}
|
|
40
|
+
catch { /* ignore */ }
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export async function disconnectPool() {
|
|
45
|
+
if (pool) {
|
|
46
|
+
await pool.close();
|
|
47
|
+
pool = null;
|
|
48
|
+
currentConfig = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function getPool() {
|
|
52
|
+
return pool;
|
|
53
|
+
}
|
|
54
|
+
export function requirePool() {
|
|
55
|
+
if (!pool?.connected) {
|
|
56
|
+
throw new Error("No active database connection. Use connect_database first.");
|
|
57
|
+
}
|
|
58
|
+
return pool;
|
|
59
|
+
}
|
|
60
|
+
export function getConnectionState() {
|
|
61
|
+
return {
|
|
62
|
+
connected: pool?.connected ?? false,
|
|
63
|
+
config: currentConfig
|
|
64
|
+
? { server: currentConfig.server, database: currentConfig.database, port: currentConfig.port }
|
|
65
|
+
: null,
|
|
66
|
+
pool_info: pool
|
|
67
|
+
? { size: pool.size, available: pool.available, pending: pool.pending, borrowed: pool.borrowed }
|
|
68
|
+
: null,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export async function closePoolOnShutdown() {
|
|
72
|
+
if (pool) {
|
|
73
|
+
try {
|
|
74
|
+
await pool.close();
|
|
75
|
+
}
|
|
76
|
+
catch { /* ignore */ }
|
|
77
|
+
pool = null;
|
|
78
|
+
currentConfig = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function buildSelectQuery(schemaName: string, tableName: string, columns: string[] | null, orderBy: string | null, offset: number, limit: number): string;
|
|
2
|
+
export declare function buildSelectWithWhereQuery(schemaName: string, tableName: string, columns: string[] | null, whereClause: string, orderBy: string | null, offset: number, limit: number): string;
|
|
3
|
+
export declare function buildSchemaObjectsQuery(objectType: "tables" | "views" | "procedures" | "functions" | "all", schemaName?: string): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { bracketIdentifier } from "./validators.js";
|
|
2
|
+
export function buildSelectQuery(schemaName, tableName, columns, orderBy, offset, limit) {
|
|
3
|
+
const safeSchema = bracketIdentifier(schemaName);
|
|
4
|
+
const safeTable = bracketIdentifier(tableName);
|
|
5
|
+
const projection = columns && columns.length > 0 ? columns.map(bracketIdentifier).join(", ") : "*";
|
|
6
|
+
const order = orderBy ?? "(SELECT NULL)";
|
|
7
|
+
return (`SELECT ${projection} FROM ${safeSchema}.${safeTable}` +
|
|
8
|
+
` ORDER BY ${order}` +
|
|
9
|
+
` OFFSET ${offset} ROWS FETCH NEXT ${limit} ROWS ONLY`);
|
|
10
|
+
}
|
|
11
|
+
export function buildSelectWithWhereQuery(schemaName, tableName, columns, whereClause, orderBy, offset, limit) {
|
|
12
|
+
const safeSchema = bracketIdentifier(schemaName);
|
|
13
|
+
const safeTable = bracketIdentifier(tableName);
|
|
14
|
+
const projection = columns && columns.length > 0 ? columns.map(bracketIdentifier).join(", ") : "*";
|
|
15
|
+
const order = orderBy ?? "(SELECT NULL)";
|
|
16
|
+
return (`SELECT ${projection} FROM ${safeSchema}.${safeTable}` +
|
|
17
|
+
` WHERE ${whereClause}` +
|
|
18
|
+
` ORDER BY ${order}` +
|
|
19
|
+
` OFFSET ${offset} ROWS FETCH NEXT ${limit} ROWS ONLY`);
|
|
20
|
+
}
|
|
21
|
+
export function buildSchemaObjectsQuery(objectType, schemaName) {
|
|
22
|
+
const parts = [];
|
|
23
|
+
const schemaFilter = schemaName ? "TABLE_SCHEMA = @schemaName" : null;
|
|
24
|
+
const routineSchemaFilter = schemaName ? "ROUTINE_SCHEMA = @schemaName" : null;
|
|
25
|
+
if (objectType === "tables" || objectType === "all") {
|
|
26
|
+
parts.push(`
|
|
27
|
+
SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE, 'table' as OBJECT_TYPE
|
|
28
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
29
|
+
${schemaFilter ? `WHERE ${schemaFilter}` : ""}
|
|
30
|
+
`);
|
|
31
|
+
}
|
|
32
|
+
if (objectType === "views" || objectType === "all") {
|
|
33
|
+
parts.push(`
|
|
34
|
+
SELECT TABLE_SCHEMA, TABLE_NAME, 'VIEW' as TABLE_TYPE, 'view' as OBJECT_TYPE
|
|
35
|
+
FROM INFORMATION_SCHEMA.VIEWS
|
|
36
|
+
${schemaFilter ? `WHERE ${schemaFilter}` : ""}
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
if (objectType === "procedures" || objectType === "all") {
|
|
40
|
+
parts.push(`
|
|
41
|
+
SELECT ROUTINE_SCHEMA as TABLE_SCHEMA, ROUTINE_NAME as TABLE_NAME,
|
|
42
|
+
'PROCEDURE' as TABLE_TYPE, 'procedure' as OBJECT_TYPE
|
|
43
|
+
FROM INFORMATION_SCHEMA.ROUTINES
|
|
44
|
+
WHERE ROUTINE_TYPE = 'PROCEDURE'
|
|
45
|
+
${routineSchemaFilter ? `AND ${routineSchemaFilter}` : ""}
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
if (objectType === "functions" || objectType === "all") {
|
|
49
|
+
parts.push(`
|
|
50
|
+
SELECT ROUTINE_SCHEMA as TABLE_SCHEMA, ROUTINE_NAME as TABLE_NAME,
|
|
51
|
+
'FUNCTION' as TABLE_TYPE, 'function' as OBJECT_TYPE
|
|
52
|
+
FROM INFORMATION_SCHEMA.ROUTINES
|
|
53
|
+
WHERE ROUTINE_TYPE = 'FUNCTION'
|
|
54
|
+
${routineSchemaFilter ? `AND ${routineSchemaFilter}` : ""}
|
|
55
|
+
`);
|
|
56
|
+
}
|
|
57
|
+
return parts.join(" UNION ALL ") + " ORDER BY TABLE_SCHEMA, TABLE_NAME";
|
|
58
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const SAFE_IDENTIFIER_RE: RegExp;
|
|
2
|
+
export declare function isValidIdentifier(name: string): boolean;
|
|
3
|
+
export declare function validateIdentifier(name: string, label: string): string;
|
|
4
|
+
export declare function bracketIdentifier(name: string): string;
|
|
5
|
+
export declare function validateOrderBy(orderBy: string): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Safe SQL identifier validation.
|
|
2
|
+
// Only validates identifier names (schema, table, procedure) — never user values.
|
|
3
|
+
// User values must always be passed as mssql parameters, never interpolated.
|
|
4
|
+
export const SAFE_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_$#@]{0,127}$/;
|
|
5
|
+
export function isValidIdentifier(name) {
|
|
6
|
+
return SAFE_IDENTIFIER_RE.test(name);
|
|
7
|
+
}
|
|
8
|
+
export function validateIdentifier(name, label) {
|
|
9
|
+
if (!isValidIdentifier(name)) {
|
|
10
|
+
throw new Error(`Invalid ${label} "${name}". Identifiers must start with a letter or underscore and contain only letters, digits, underscores, $, #, @.`);
|
|
11
|
+
}
|
|
12
|
+
return name;
|
|
13
|
+
}
|
|
14
|
+
export function bracketIdentifier(name) {
|
|
15
|
+
validateIdentifier(name, "identifier");
|
|
16
|
+
return `[${name}]`;
|
|
17
|
+
}
|
|
18
|
+
// ORDER BY: allows column names (optionally bracketed), dotted refs, ASC/DESC
|
|
19
|
+
const ORDER_BY_RE = /^(\[?[a-zA-Z_][a-zA-Z0-9_$#@.]*\]?(\s+(ASC|DESC))?)((\s*,\s*)\[?[a-zA-Z_][a-zA-Z0-9_$#@.]*\]?(\s+(ASC|DESC))?)*$/i;
|
|
20
|
+
export function validateOrderBy(orderBy) {
|
|
21
|
+
if (!ORDER_BY_RE.test(orderBy.trim())) {
|
|
22
|
+
throw new Error("Invalid ORDER BY clause. Only column names, commas, and ASC/DESC direction keywords are allowed.");
|
|
23
|
+
}
|
|
24
|
+
return orderBy.trim();
|
|
25
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import dotenv from "dotenv";
|
|
3
|
+
import { createServer } from "./server.js";
|
|
4
|
+
import { runStdioTransport } from "./transports/stdio.js";
|
|
5
|
+
import { runHttpTransport } from "./transports/http.js";
|
|
6
|
+
import { loadHttpConfig } from "./config.js";
|
|
7
|
+
import { SERVER_VERSION } from "./constants.js";
|
|
8
|
+
dotenv.config();
|
|
9
|
+
async function main() {
|
|
10
|
+
const transport = process.env.MCP_TRANSPORT ?? "stdio";
|
|
11
|
+
const server = createServer();
|
|
12
|
+
if (transport === "http") {
|
|
13
|
+
const httpConfig = loadHttpConfig();
|
|
14
|
+
console.error(`MSSQL MCP Server v${SERVER_VERSION} starting (HTTP mode)...`);
|
|
15
|
+
await runHttpTransport(server, httpConfig);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.error(`MSSQL MCP Server v${SERVER_VERSION} starting (stdio mode)...`);
|
|
19
|
+
await runStdioTransport(server);
|
|
20
|
+
console.error("Server ready.");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
main().catch((err) => {
|
|
24
|
+
console.error("Fatal startup error:", err);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getConnectionState } from "../db/connection.js";
|
|
2
|
+
export function registerConnectionResource(server) {
|
|
3
|
+
server.registerResource("connection-info", "mssql://connection/info", {
|
|
4
|
+
title: "Connection Info",
|
|
5
|
+
description: "Current MSSQL connection status and configuration (no credentials exposed).",
|
|
6
|
+
mimeType: "application/json",
|
|
7
|
+
}, async () => {
|
|
8
|
+
const state = getConnectionState();
|
|
9
|
+
return {
|
|
10
|
+
contents: [
|
|
11
|
+
{
|
|
12
|
+
uri: "mssql://connection/info",
|
|
13
|
+
text: JSON.stringify(state, null, 2),
|
|
14
|
+
mimeType: "application/json",
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
}
|