mssql-mcp 2.0.2 → 2.1.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/README.md +83 -36
- package/dist/index.js +198 -434
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# MS SQL Server MCP Server v2.
|
|
1
|
+
# MS SQL Server MCP Server v2.1.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
5
|
## 🚀 Quick Start
|
|
6
6
|
|
|
@@ -24,6 +24,7 @@ npm install -g mssql-mcp
|
|
|
24
24
|
"DB_DATABASE": "your-database",
|
|
25
25
|
"DB_USER": "your-username",
|
|
26
26
|
"DB_PASSWORD": "your-password",
|
|
27
|
+
"DB_ENCRYPT": "true",
|
|
27
28
|
"DB_TRUST_SERVER_CERTIFICATE": "true"
|
|
28
29
|
}
|
|
29
30
|
}
|
|
@@ -43,6 +44,7 @@ npm install -g mssql-mcp
|
|
|
43
44
|
"DB_DATABASE": "your-database",
|
|
44
45
|
"DB_USER": "your-username",
|
|
45
46
|
"DB_PASSWORD": "your-password",
|
|
47
|
+
"DB_ENCRYPT": "true",
|
|
46
48
|
"DB_TRUST_SERVER_CERTIFICATE": "true"
|
|
47
49
|
}
|
|
48
50
|
}
|
|
@@ -50,63 +52,108 @@ npm install -g mssql-mcp
|
|
|
50
52
|
}
|
|
51
53
|
```
|
|
52
54
|
|
|
53
|
-
> Replace with your actual database credentials.
|
|
55
|
+
> Replace with your actual database credentials.
|
|
54
56
|
|
|
55
57
|
## 🛠️ Available Tools
|
|
56
58
|
|
|
57
59
|
| Tool | Description |
|
|
58
60
|
|------|-------------|
|
|
61
|
+
| `connect_database` | Connect to database using environment variables |
|
|
62
|
+
| `disconnect_database` | Close current database connection |
|
|
63
|
+
| `connection_status` | Check connection state with pool info |
|
|
59
64
|
| `execute_query` | Execute any SQL query with parameters |
|
|
60
65
|
| `get_schema` | List database objects (tables, views, procedures) |
|
|
61
66
|
| `describe_table` | Get detailed table structure |
|
|
62
67
|
| `get_table_data` | Retrieve data with pagination |
|
|
63
68
|
| `execute_procedure` | Execute stored procedures |
|
|
64
69
|
| `list_databases` | List all databases |
|
|
65
|
-
| `connection_status` | Check connection state |
|
|
66
|
-
| `connect_database` | Manual connection (rarely needed) |
|
|
67
|
-
| `disconnect_database` | Close connection |
|
|
68
|
-
| `clear_cache` | Clear query cache |
|
|
69
|
-
|
|
70
|
-
All tools auto-connect using environment variables.
|
|
71
70
|
|
|
72
71
|
## 🔧 Environment Variables
|
|
73
72
|
|
|
74
|
-
| Variable | Required | Default |
|
|
75
|
-
|
|
76
|
-
| `DB_SERVER` | ✅ | - |
|
|
77
|
-
| `DB_DATABASE` |
|
|
78
|
-
| `DB_USER` |
|
|
79
|
-
| `DB_PASSWORD` |
|
|
80
|
-
| `DB_PORT` | ❌ | 1433 |
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
73
|
+
| Variable | Required | Default | Description |
|
|
74
|
+
|----------|----------|---------|-------------|
|
|
75
|
+
| `DB_SERVER` | ✅ | - | SQL Server hostname |
|
|
76
|
+
| `DB_DATABASE` | ❌ | - | Database name |
|
|
77
|
+
| `DB_USER` | ❌ | - | Username |
|
|
78
|
+
| `DB_PASSWORD` | ❌ | - | Password |
|
|
79
|
+
| `DB_PORT` | ❌ | 1433 | SQL Server port |
|
|
80
|
+
| `DB_ENCRYPT` | ❌ | true | Enable TLS encryption (required for Azure SQL) |
|
|
81
|
+
| `DB_TRUST_SERVER_CERTIFICATE` | ❌ | false | Trust self-signed certificates |
|
|
82
|
+
| `DB_CONNECTION_TIMEOUT` | ❌ | 30000 | Connection timeout (ms) |
|
|
83
|
+
| `DB_REQUEST_TIMEOUT` | ❌ | 30000 | Request timeout (ms) |
|
|
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
|
+
```
|
|
88
93
|
|
|
89
|
-
|
|
94
|
+
### Local SQL Server (Self-signed cert)
|
|
95
|
+
For local development with self-signed certificates:
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"DB_ENCRYPT": "true",
|
|
99
|
+
"DB_TRUST_SERVER_CERTIFICATE": "true"
|
|
100
|
+
}
|
|
101
|
+
```
|
|
90
102
|
|
|
91
103
|
## 🏆 Features
|
|
92
104
|
|
|
93
|
-
- ✅ **
|
|
105
|
+
- ✅ **MCP SDK 1.25.1**: Latest Model Context Protocol SDK
|
|
106
|
+
- ✅ **Azure SQL Compatible**: TLS encryption enabled by default
|
|
94
107
|
- ✅ **Complete SQL Support**: All database operations
|
|
95
|
-
- ✅ **
|
|
96
|
-
- ✅ **
|
|
108
|
+
- ✅ **Parameterized Queries**: SQL injection protection
|
|
109
|
+
- ✅ **Connection Pooling**: Efficient resource management
|
|
97
110
|
- ✅ **Performance Monitoring**: Execution time tracking
|
|
98
|
-
|
|
111
|
+
|
|
112
|
+
## 📋 Usage Examples
|
|
113
|
+
|
|
114
|
+
### Connect to Database
|
|
115
|
+
```
|
|
116
|
+
Use the connect_database tool to establish a connection.
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Execute a Query
|
|
120
|
+
```sql
|
|
121
|
+
SELECT TOP 10 * FROM Customers WHERE Country = @country
|
|
122
|
+
-- With parameters: { "country": "USA" }
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Get Table Schema
|
|
126
|
+
```
|
|
127
|
+
Use describe_table with tableName: "Customers" to see column details.
|
|
128
|
+
```
|
|
99
129
|
|
|
100
130
|
## 🔍 Troubleshooting
|
|
101
131
|
|
|
102
|
-
**❌
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
132
|
+
**❌ Connection failed**
|
|
133
|
+
- Verify all required environment variables are set
|
|
134
|
+
- Check server accessibility and credentials
|
|
135
|
+
- Ensure network connectivity to SQL Server
|
|
136
|
+
|
|
137
|
+
**❌ SSL/Certificate errors**
|
|
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`
|
|
141
|
+
|
|
142
|
+
## 📋 Version History
|
|
143
|
+
|
|
144
|
+
### v2.1.1 - Latest
|
|
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`
|
|
149
|
+
|
|
150
|
+
### v2.1.0
|
|
151
|
+
- ✅ Updated to MCP SDK 1.25.1
|
|
152
|
+
- ✅ Migrated to `registerTool()` / `registerResource()` API
|
|
153
|
+
- ✅ Added tool titles for better UI display
|
|
106
154
|
|
|
107
|
-
|
|
108
|
-
-
|
|
109
|
-
- Database operations should work normally
|
|
155
|
+
### v2.0.3
|
|
156
|
+
- ✅ Documentation improvements
|
|
110
157
|
|
|
111
158
|
## 📄 License
|
|
112
159
|
|
|
@@ -119,4 +166,4 @@ MIT License
|
|
|
119
166
|
|
|
120
167
|
---
|
|
121
168
|
|
|
122
|
-
**🎉 v2.
|
|
169
|
+
**🎉 v2.1.1: Azure SQL compatibility with DB_ENCRYPT support**
|
package/dist/index.js
CHANGED
|
@@ -13,156 +13,33 @@ const ConfigSchema = z.object({
|
|
|
13
13
|
user: z.string().optional(),
|
|
14
14
|
password: z.string().optional(),
|
|
15
15
|
port: z.number().int().min(1).max(65535).optional().default(1433),
|
|
16
|
-
|
|
16
|
+
encrypt: z.boolean().optional().default(true),
|
|
17
|
+
trustServerCertificate: z.boolean().optional().default(false),
|
|
17
18
|
connectionTimeout: z.number().int().min(1000).max(60000).optional().default(30000),
|
|
18
19
|
requestTimeout: z.number().int().min(1000).max(300000).optional().default(30000),
|
|
19
20
|
});
|
|
20
|
-
// Enhanced error class for better error handling with MCP compliance
|
|
21
|
-
class MCPServerError extends Error {
|
|
22
|
-
code;
|
|
23
|
-
details;
|
|
24
|
-
category;
|
|
25
|
-
constructor(message, code, details, category = 'SYSTEM') {
|
|
26
|
-
super(message);
|
|
27
|
-
this.code = code;
|
|
28
|
-
this.details = details;
|
|
29
|
-
this.category = category;
|
|
30
|
-
this.name = 'MCPServerError';
|
|
31
|
-
}
|
|
32
|
-
toCallToolResult() {
|
|
33
|
-
return {
|
|
34
|
-
content: [{
|
|
35
|
-
type: "text",
|
|
36
|
-
text: `Error: ${this.message} (Code: ${this.code})`
|
|
37
|
-
}],
|
|
38
|
-
isError: true,
|
|
39
|
-
_meta: {
|
|
40
|
-
errorCategory: this.category,
|
|
41
|
-
errorCode: this.code,
|
|
42
|
-
timestamp: new Date().toISOString(),
|
|
43
|
-
details: this.details
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
// Query result cache for performance optimization
|
|
49
|
-
class QueryCache {
|
|
50
|
-
cache = new Map();
|
|
51
|
-
defaultTTL = 300000; // 5 minutes
|
|
52
|
-
set(key, data, ttl = this.defaultTTL) {
|
|
53
|
-
this.cache.set(key, { data, timestamp: Date.now(), ttl });
|
|
54
|
-
}
|
|
55
|
-
get(key) {
|
|
56
|
-
const item = this.cache.get(key);
|
|
57
|
-
if (!item)
|
|
58
|
-
return null;
|
|
59
|
-
if (Date.now() - item.timestamp > item.ttl) {
|
|
60
|
-
this.cache.delete(key);
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
return item.data;
|
|
64
|
-
}
|
|
65
|
-
clear() {
|
|
66
|
-
this.cache.clear();
|
|
67
|
-
}
|
|
68
|
-
cleanup() {
|
|
69
|
-
const now = Date.now();
|
|
70
|
-
for (const [key, item] of this.cache.entries()) {
|
|
71
|
-
if (now - item.timestamp > item.ttl) {
|
|
72
|
-
this.cache.delete(key);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
// No rate limiting - trust-based approach
|
|
78
|
-
class RateLimiter {
|
|
79
|
-
constructor() {
|
|
80
|
-
// Silent - no console output for MCP protocol
|
|
81
|
-
}
|
|
82
|
-
checkLimit(clientId, operation) {
|
|
83
|
-
// Always allow - user has provided credentials and is trusted
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
21
|
class MSSQLMCPServer {
|
|
88
22
|
server;
|
|
89
23
|
pool = null;
|
|
90
24
|
config = null;
|
|
91
|
-
queryCache = new QueryCache();
|
|
92
|
-
rateLimiter = new RateLimiter();
|
|
93
|
-
clientId = `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
94
25
|
constructor() {
|
|
95
26
|
this.server = new McpServer({
|
|
96
27
|
name: "mssql-mcp-server",
|
|
97
|
-
version: "2.
|
|
98
|
-
instructions: "🚀 UNRESTRICTED SQL SERVER MCP SERVER v2.0.2 - TRUST-BASED!\n\n✨ FOR AI ASSISTANTS: COMPLETE FREEDOM!\n• All SQL operations allowed (SELECT, INSERT, UPDATE, DELETE, DROP, CREATE, etc.)\n• No rate limiting - unlimited operations\n• Auto-connects automatically with environment variables\n• Just use any tool directly (execute_query, get_schema, etc.)\n\n🔧 SETUP: Set DB_SERVER, DB_DATABASE, DB_USER, DB_PASSWORD environment variables\n\n🛡️ MINIMAL SECURITY: Only blocks server-level operations (SHUTDOWN, XP_CMDSHELL)\n⚡ FEATURES: Query caching, performance monitoring, dry-run validation",
|
|
28
|
+
version: "2.1.1",
|
|
99
29
|
});
|
|
100
30
|
this.setupTools();
|
|
101
31
|
this.setupResources();
|
|
102
|
-
// Set up periodic cache cleanup (every 5 minutes)
|
|
103
|
-
setInterval(() => {
|
|
104
|
-
this.queryCache.cleanup();
|
|
105
|
-
}, 300000);
|
|
106
|
-
}
|
|
107
|
-
// Enhanced error handling method with MCP compliance
|
|
108
|
-
handleToolError(error, toolName) {
|
|
109
|
-
if (error instanceof MCPServerError) {
|
|
110
|
-
console.error(`MCP Error [${error.code}] in ${toolName}: ${error.message}`, error.details);
|
|
111
|
-
return error.toCallToolResult();
|
|
112
|
-
}
|
|
113
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
114
|
-
console.error(`Tool error in ${toolName}:`, errorMessage);
|
|
115
|
-
return {
|
|
116
|
-
content: [{
|
|
117
|
-
type: "text",
|
|
118
|
-
text: `Error: ${errorMessage}`
|
|
119
|
-
}],
|
|
120
|
-
isError: true,
|
|
121
|
-
_meta: {
|
|
122
|
-
errorCategory: 'EXECUTION',
|
|
123
|
-
toolName,
|
|
124
|
-
timestamp: new Date().toISOString()
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
// Helper method to generate cache keys
|
|
129
|
-
generateCacheKey(operation, params) {
|
|
130
|
-
return `${operation}_${JSON.stringify(params)}`;
|
|
131
|
-
}
|
|
132
|
-
// Trust-based SQL validation - only block truly dangerous system operations
|
|
133
|
-
validateSQLSecurity(query, context = { operation: 'unknown' }) {
|
|
134
|
-
// User has already provided database credentials and connection
|
|
135
|
-
// Only block operations that could compromise the database server itself
|
|
136
|
-
const criticalSystemPatterns = [
|
|
137
|
-
{ pattern: /\b(SHUTDOWN|KILL)\b/i, category: 'SERVER_CONTROL' },
|
|
138
|
-
{ pattern: /\b(XP_CMDSHELL|SP_OACREATE|SP_OAMETHOD)\b/i, category: 'SYSTEM_EXEC' },
|
|
139
|
-
{ pattern: /\b(RECONFIGURE|DISK\s+INIT)\b/i, category: 'SERVER_CONFIG' }
|
|
140
|
-
];
|
|
141
|
-
for (const { pattern, category } of criticalSystemPatterns) {
|
|
142
|
-
if (pattern.test(query)) {
|
|
143
|
-
throw new MCPServerError(`System-level operation not allowed: ${category}`, "SYSTEM_OPERATION_BLOCKED", {
|
|
144
|
-
pattern: pattern.source,
|
|
145
|
-
category,
|
|
146
|
-
query: query.substring(0, 100) + (query.length > 100 ? '...' : ''),
|
|
147
|
-
context,
|
|
148
|
-
note: "This operation could affect the database server itself"
|
|
149
|
-
}, 'SECURITY');
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
32
|
}
|
|
153
33
|
setupTools() {
|
|
154
|
-
// Tool: Connect to database with enhanced security validation
|
|
155
|
-
this.server.
|
|
156
|
-
title: "Connect
|
|
157
|
-
description: "
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
}, async (extra) => {
|
|
34
|
+
// Tool: Connect to database with enhanced security validation (only uses environment variables)
|
|
35
|
+
this.server.registerTool("connect_database", {
|
|
36
|
+
title: "Connect Database",
|
|
37
|
+
description: "Connect to MS SQL Server database with security validation (uses only environment variables for security)",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
// No parameters - only environment variables will be used for security
|
|
40
|
+
},
|
|
41
|
+
}, async () => {
|
|
164
42
|
try {
|
|
165
|
-
// No rate limiting - user is trusted
|
|
166
43
|
// SECURITY: Only use environment variables, ignore all user parameters
|
|
167
44
|
const config = ConfigSchema.parse({
|
|
168
45
|
server: process.env.DB_SERVER,
|
|
@@ -170,129 +47,53 @@ class MSSQLMCPServer {
|
|
|
170
47
|
user: process.env.DB_USER,
|
|
171
48
|
password: process.env.DB_PASSWORD,
|
|
172
49
|
port: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 1433,
|
|
50
|
+
encrypt: process.env.DB_ENCRYPT !== 'false', // Default true for Azure SQL compatibility
|
|
173
51
|
trustServerCertificate: process.env.DB_TRUST_SERVER_CERTIFICATE === 'true',
|
|
174
52
|
connectionTimeout: process.env.DB_CONNECTION_TIMEOUT ? parseInt(process.env.DB_CONNECTION_TIMEOUT) : 30000,
|
|
175
53
|
requestTimeout: process.env.DB_REQUEST_TIMEOUT ? parseInt(process.env.DB_REQUEST_TIMEOUT) : 30000,
|
|
176
54
|
});
|
|
177
55
|
if (!config.server) {
|
|
178
|
-
throw new
|
|
56
|
+
throw new Error("Server is required. Provide it as parameter or set DB_SERVER environment variable.");
|
|
179
57
|
}
|
|
180
|
-
const startTime = Date.now();
|
|
181
58
|
await this.connect(config);
|
|
182
|
-
const connectionTime = Date.now() - startTime;
|
|
183
59
|
return {
|
|
184
|
-
content: [
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
185
62
|
type: "text",
|
|
186
|
-
text: `✅ Successfully connected to SQL Server: ${config.server}${config.database ? ` (Database: ${config.database})` : ""}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
connectionTime: `${connectionTime}ms`,
|
|
190
|
-
server: config.server,
|
|
191
|
-
database: config.database,
|
|
192
|
-
port: config.port,
|
|
193
|
-
sslEnabled: !config.trustServerCertificate,
|
|
194
|
-
timestamp: new Date().toISOString()
|
|
195
|
-
}
|
|
63
|
+
text: `✅ Successfully connected to SQL Server: ${config.server}${config.database ? ` (Database: ${config.database})` : ""}`,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
196
66
|
};
|
|
197
67
|
}
|
|
198
68
|
catch (error) {
|
|
199
|
-
|
|
69
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
70
|
+
console.error("❌ Database connection failed:", errorMessage);
|
|
71
|
+
return {
|
|
72
|
+
content: [
|
|
73
|
+
{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: `❌ Failed to connect: ${errorMessage}`,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
isError: true,
|
|
79
|
+
};
|
|
200
80
|
}
|
|
201
81
|
});
|
|
202
|
-
// Tool: Execute SQL query with enhanced security
|
|
203
|
-
this.server.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
annotations: {
|
|
212
|
-
destructiveHint: false,
|
|
213
|
-
idempotentHint: true,
|
|
214
|
-
readOnlyHint: true
|
|
215
|
-
}
|
|
216
|
-
}, async ({ query, parameters, useCache, dryRun }, extra) => {
|
|
82
|
+
// Tool: Execute SQL query with enhanced security
|
|
83
|
+
this.server.registerTool("execute_query", {
|
|
84
|
+
title: "Execute Query",
|
|
85
|
+
description: "Execute a SQL query against the connected database with security validation",
|
|
86
|
+
inputSchema: {
|
|
87
|
+
query: z.string().min(1, "Query cannot be empty").describe("SQL query to execute"),
|
|
88
|
+
parameters: z.record(z.any()).optional().describe("Query parameters (key-value pairs) - always use parameters for user input"),
|
|
89
|
+
},
|
|
90
|
+
}, async ({ query, parameters }) => {
|
|
217
91
|
try {
|
|
218
|
-
// No rate limiting - user is trusted
|
|
219
|
-
// Auto-connection logic for better AI experience
|
|
220
92
|
if (!this.pool) {
|
|
221
|
-
|
|
222
|
-
try {
|
|
223
|
-
// Try to auto-connect using environment variables
|
|
224
|
-
const config = ConfigSchema.parse({
|
|
225
|
-
server: process.env.DB_SERVER,
|
|
226
|
-
database: process.env.DB_DATABASE,
|
|
227
|
-
user: process.env.DB_USER,
|
|
228
|
-
password: process.env.DB_PASSWORD,
|
|
229
|
-
port: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 1433,
|
|
230
|
-
trustServerCertificate: process.env.DB_TRUST_SERVER_CERTIFICATE === 'true',
|
|
231
|
-
connectionTimeout: process.env.DB_CONNECTION_TIMEOUT ? parseInt(process.env.DB_CONNECTION_TIMEOUT) : 30000,
|
|
232
|
-
requestTimeout: process.env.DB_REQUEST_TIMEOUT ? parseInt(process.env.DB_REQUEST_TIMEOUT) : 30000,
|
|
233
|
-
});
|
|
234
|
-
if (!config.server) {
|
|
235
|
-
throw new MCPServerError("❌ Auto-connection failed: DB_SERVER environment variable not set. Please set database connection environment variables or use connect_database tool first.", "AUTO_CONNECTION_FAILED", {
|
|
236
|
-
requiredEnvVars: ['DB_SERVER', 'DB_DATABASE', 'DB_USER', 'DB_PASSWORD'],
|
|
237
|
-
suggestion: "Use connect_database tool manually or set environment variables"
|
|
238
|
-
}, 'CONNECTION');
|
|
239
|
-
}
|
|
240
|
-
await this.connect(config);
|
|
241
|
-
console.error("Auto-connected");
|
|
242
|
-
}
|
|
243
|
-
catch (autoConnectError) {
|
|
244
|
-
throw new MCPServerError("❌ No database connection available and auto-connection failed. Please use connect_database tool first or check your environment variables.", "NO_CONNECTION_AUTO_FAILED", {
|
|
245
|
-
autoConnectError: autoConnectError instanceof Error ? autoConnectError.message : String(autoConnectError),
|
|
246
|
-
suggestion: "1. Use connect_database tool first, OR\n2. Set environment variables: DB_SERVER, DB_DATABASE, DB_USER, DB_PASSWORD"
|
|
247
|
-
}, 'CONNECTION');
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
// Enhanced security validation with context
|
|
251
|
-
this.validateSQLSecurity(query, { operation: 'execute_query', userProvided: true });
|
|
252
|
-
// Check cache for SELECT queries
|
|
253
|
-
const queryType = query.trim().toUpperCase().split(/\s+/)[0];
|
|
254
|
-
const isSelectQuery = queryType === 'SELECT';
|
|
255
|
-
let cacheKey = null;
|
|
256
|
-
if (isSelectQuery && useCache && !dryRun) {
|
|
257
|
-
cacheKey = this.generateCacheKey('execute_query', { query, parameters });
|
|
258
|
-
const cachedResult = this.queryCache.get(cacheKey);
|
|
259
|
-
if (cachedResult) {
|
|
260
|
-
return {
|
|
261
|
-
content: [{
|
|
262
|
-
type: "text",
|
|
263
|
-
text: JSON.stringify({
|
|
264
|
-
...cachedResult,
|
|
265
|
-
fromCache: true,
|
|
266
|
-
executionTime: "0ms (cached)",
|
|
267
|
-
cacheHit: true
|
|
268
|
-
}, null, 2)
|
|
269
|
-
}],
|
|
270
|
-
_meta: {
|
|
271
|
-
cached: true,
|
|
272
|
-
cacheKey,
|
|
273
|
-
timestamp: new Date().toISOString()
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
// Dry run validation
|
|
279
|
-
if (dryRun) {
|
|
280
|
-
return {
|
|
281
|
-
content: [{
|
|
282
|
-
type: "text",
|
|
283
|
-
text: `✅ Query validation passed:\n• Query type: ${queryType}\n• Parameters: ${parameters ? Object.keys(parameters).length : 0}\n• Estimated complexity: ${query.length > 200 ? 'High' : 'Low'}\n• Security: ✓ Passed\n• Ready for execution`
|
|
284
|
-
}],
|
|
285
|
-
_meta: {
|
|
286
|
-
dryRun: true,
|
|
287
|
-
queryType,
|
|
288
|
-
parameterCount: parameters ? Object.keys(parameters).length : 0,
|
|
289
|
-
validationPassed: true,
|
|
290
|
-
timestamp: new Date().toISOString()
|
|
291
|
-
}
|
|
292
|
-
};
|
|
93
|
+
throw new Error("No database connection. Please connect first using connect_database tool.");
|
|
293
94
|
}
|
|
294
95
|
const request = this.pool.request();
|
|
295
|
-
// Add parameters
|
|
96
|
+
// Add parameters if provided (recommended for security)
|
|
296
97
|
if (parameters) {
|
|
297
98
|
for (const [key, value] of Object.entries(parameters)) {
|
|
298
99
|
request.input(key, value);
|
|
@@ -301,190 +102,139 @@ class MSSQLMCPServer {
|
|
|
301
102
|
const startTime = Date.now();
|
|
302
103
|
const result = await request.query(query);
|
|
303
104
|
const executionTime = Date.now() - startTime;
|
|
304
|
-
const response = {
|
|
305
|
-
recordset: result.recordset,
|
|
306
|
-
rowsAffected: result.rowsAffected,
|
|
307
|
-
output: result.output,
|
|
308
|
-
executionTime: `${executionTime}ms`,
|
|
309
|
-
parametersUsed: parameters ? Object.keys(parameters).length : 0,
|
|
310
|
-
fromCache: false,
|
|
311
|
-
queryType,
|
|
312
|
-
rowCount: result.recordset.length
|
|
313
|
-
};
|
|
314
|
-
// Cache SELECT query results
|
|
315
|
-
if (isSelectQuery && useCache && cacheKey) {
|
|
316
|
-
this.queryCache.set(cacheKey, response);
|
|
317
|
-
}
|
|
318
105
|
return {
|
|
319
|
-
content: [
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
320
108
|
type: "text",
|
|
321
|
-
text: JSON.stringify(
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
109
|
+
text: JSON.stringify({
|
|
110
|
+
recordset: result.recordset,
|
|
111
|
+
rowsAffected: result.rowsAffected,
|
|
112
|
+
output: result.output,
|
|
113
|
+
executionTime: `${executionTime}ms`,
|
|
114
|
+
parametersUsed: parameters ? Object.keys(parameters).length : 0,
|
|
115
|
+
}, null, 2),
|
|
116
|
+
},
|
|
117
|
+
],
|
|
331
118
|
};
|
|
332
119
|
}
|
|
333
120
|
catch (error) {
|
|
334
|
-
|
|
121
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
122
|
+
console.error("❌ Query execution failed:", errorMessage);
|
|
123
|
+
return {
|
|
124
|
+
content: [
|
|
125
|
+
{
|
|
126
|
+
type: "text",
|
|
127
|
+
text: `❌ Query execution failed: ${errorMessage}`,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
isError: true,
|
|
131
|
+
};
|
|
335
132
|
}
|
|
336
133
|
});
|
|
337
|
-
// Tool: Get database schema
|
|
338
|
-
this.server.
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
destructiveHint: false,
|
|
347
|
-
idempotentHint: true,
|
|
348
|
-
readOnlyHint: true
|
|
349
|
-
}
|
|
350
|
-
}, async ({ objectType, schemaName, includeMetadata }, extra) => {
|
|
134
|
+
// Tool: Get database schema
|
|
135
|
+
this.server.registerTool("get_schema", {
|
|
136
|
+
title: "Get Schema",
|
|
137
|
+
description: "Get database schema information (tables, columns, etc.)",
|
|
138
|
+
inputSchema: {
|
|
139
|
+
objectType: z.enum(["tables", "views", "procedures", "functions", "all"]).optional().default("tables"),
|
|
140
|
+
schemaName: z.string().optional().describe("Specific schema name to filter"),
|
|
141
|
+
},
|
|
142
|
+
}, async ({ objectType, schemaName }) => {
|
|
351
143
|
try {
|
|
352
|
-
// Auto-connection logic for seamless AI experience
|
|
353
144
|
if (!this.pool) {
|
|
354
|
-
|
|
355
|
-
const config = ConfigSchema.parse({
|
|
356
|
-
server: process.env.DB_SERVER,
|
|
357
|
-
database: process.env.DB_DATABASE,
|
|
358
|
-
user: process.env.DB_USER,
|
|
359
|
-
password: process.env.DB_PASSWORD,
|
|
360
|
-
port: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 1433,
|
|
361
|
-
trustServerCertificate: process.env.DB_TRUST_SERVER_CERTIFICATE === 'true',
|
|
362
|
-
connectionTimeout: process.env.DB_CONNECTION_TIMEOUT ? parseInt(process.env.DB_CONNECTION_TIMEOUT) : 30000,
|
|
363
|
-
requestTimeout: process.env.DB_REQUEST_TIMEOUT ? parseInt(process.env.DB_REQUEST_TIMEOUT) : 30000,
|
|
364
|
-
});
|
|
365
|
-
if (config.server) {
|
|
366
|
-
await this.connect(config);
|
|
367
|
-
}
|
|
368
|
-
else {
|
|
369
|
-
throw new MCPServerError("Database connection required. Set environment variables or use connect_database tool.", "NO_CONNECTION", { requiredEnvVars: ['DB_SERVER', 'DB_DATABASE', 'DB_USER', 'DB_PASSWORD'] }, 'CONNECTION');
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
catch (error) {
|
|
373
|
-
throw new MCPServerError("Database connection required. Use connect_database tool or set environment variables.", "NO_CONNECTION_AUTO_FAILED", undefined, 'CONNECTION');
|
|
374
|
-
}
|
|
145
|
+
throw new Error("No database connection. Please connect first.");
|
|
375
146
|
}
|
|
376
147
|
let query = "";
|
|
377
|
-
const startTime = Date.now();
|
|
378
148
|
if (objectType === "tables" || objectType === "all") {
|
|
379
|
-
query +=
|
|
380
|
-
SELECT
|
|
381
|
-
TABLE_SCHEMA,
|
|
382
|
-
TABLE_NAME,
|
|
383
|
-
TABLE_TYPE,
|
|
384
|
-
'table' as OBJECT_TYPE,
|
|
385
|
-
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS c
|
|
386
|
-
WHERE c.TABLE_NAME = t.TABLE_NAME AND c.TABLE_SCHEMA = t.TABLE_SCHEMA) as COLUMN_COUNT
|
|
387
|
-
FROM INFORMATION_SCHEMA.TABLES t
|
|
388
|
-
${schemaName ? `WHERE TABLE_SCHEMA = @schemaName` : ""}
|
|
389
|
-
` : `
|
|
390
|
-
SELECT
|
|
149
|
+
query += `
|
|
150
|
+
SELECT
|
|
391
151
|
TABLE_SCHEMA,
|
|
392
152
|
TABLE_NAME,
|
|
393
153
|
TABLE_TYPE,
|
|
394
154
|
'table' as OBJECT_TYPE
|
|
395
155
|
FROM INFORMATION_SCHEMA.TABLES
|
|
396
|
-
${schemaName ? `WHERE TABLE_SCHEMA =
|
|
156
|
+
${schemaName ? `WHERE TABLE_SCHEMA = '${schemaName}'` : ""}
|
|
397
157
|
`;
|
|
398
158
|
}
|
|
399
159
|
if (objectType === "views" || objectType === "all") {
|
|
400
160
|
if (query)
|
|
401
161
|
query += " UNION ALL ";
|
|
402
162
|
query += `
|
|
403
|
-
SELECT
|
|
163
|
+
SELECT
|
|
404
164
|
TABLE_SCHEMA,
|
|
405
165
|
TABLE_NAME,
|
|
406
166
|
'VIEW' as TABLE_TYPE,
|
|
407
167
|
'view' as OBJECT_TYPE
|
|
408
|
-
${includeMetadata ? ", (SELECT COUNT(*) FROM INFORMATION_SCHEMA.VIEW_COLUMNS v WHERE v.TABLE_NAME = TABLE_NAME AND v.TABLE_SCHEMA = TABLE_SCHEMA) as COLUMN_COUNT" : ""}
|
|
409
168
|
FROM INFORMATION_SCHEMA.VIEWS
|
|
410
|
-
${schemaName ? `WHERE TABLE_SCHEMA =
|
|
169
|
+
${schemaName ? `WHERE TABLE_SCHEMA = '${schemaName}'` : ""}
|
|
411
170
|
`;
|
|
412
171
|
}
|
|
413
172
|
if (objectType === "procedures" || objectType === "all") {
|
|
414
173
|
if (query)
|
|
415
174
|
query += " UNION ALL ";
|
|
416
175
|
query += `
|
|
417
|
-
SELECT
|
|
176
|
+
SELECT
|
|
418
177
|
ROUTINE_SCHEMA as TABLE_SCHEMA,
|
|
419
178
|
ROUTINE_NAME as TABLE_NAME,
|
|
420
179
|
'PROCEDURE' as TABLE_TYPE,
|
|
421
180
|
'procedure' as OBJECT_TYPE
|
|
422
|
-
${includeMetadata ? ", (SELECT COUNT(*) FROM INFORMATION_SCHEMA.PARAMETERS p WHERE p.SPECIFIC_NAME = ROUTINE_NAME AND p.SPECIFIC_SCHEMA = ROUTINE_SCHEMA) as PARAMETER_COUNT" : ""}
|
|
423
181
|
FROM INFORMATION_SCHEMA.ROUTINES
|
|
424
182
|
WHERE ROUTINE_TYPE = 'PROCEDURE'
|
|
425
|
-
${schemaName ? `AND ROUTINE_SCHEMA =
|
|
183
|
+
${schemaName ? `AND ROUTINE_SCHEMA = '${schemaName}'` : ""}
|
|
426
184
|
`;
|
|
427
185
|
}
|
|
428
186
|
if (objectType === "functions" || objectType === "all") {
|
|
429
187
|
if (query)
|
|
430
188
|
query += " UNION ALL ";
|
|
431
189
|
query += `
|
|
432
|
-
SELECT
|
|
190
|
+
SELECT
|
|
433
191
|
ROUTINE_SCHEMA as TABLE_SCHEMA,
|
|
434
192
|
ROUTINE_NAME as TABLE_NAME,
|
|
435
193
|
'FUNCTION' as TABLE_TYPE,
|
|
436
194
|
'function' as OBJECT_TYPE
|
|
437
|
-
${includeMetadata ? ", (SELECT COUNT(*) FROM INFORMATION_SCHEMA.PARAMETERS p WHERE p.SPECIFIC_NAME = ROUTINE_NAME AND p.SPECIFIC_SCHEMA = ROUTINE_SCHEMA) as PARAMETER_COUNT" : ""}
|
|
438
195
|
FROM INFORMATION_SCHEMA.ROUTINES
|
|
439
196
|
WHERE ROUTINE_TYPE = 'FUNCTION'
|
|
440
|
-
${schemaName ? `AND ROUTINE_SCHEMA =
|
|
197
|
+
${schemaName ? `AND ROUTINE_SCHEMA = '${schemaName}'` : ""}
|
|
441
198
|
`;
|
|
442
199
|
}
|
|
443
200
|
query += " ORDER BY TABLE_SCHEMA, TABLE_NAME";
|
|
444
|
-
const
|
|
445
|
-
if (schemaName) {
|
|
446
|
-
request.input('schemaName', schemaName);
|
|
447
|
-
}
|
|
448
|
-
const result = await request.query(query);
|
|
449
|
-
const executionTime = Date.now() - startTime;
|
|
201
|
+
const result = await this.pool.request().query(query);
|
|
450
202
|
return {
|
|
451
|
-
content: [
|
|
203
|
+
content: [
|
|
204
|
+
{
|
|
452
205
|
type: "text",
|
|
453
|
-
text: JSON.stringify(
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
objectType,
|
|
457
|
-
schemaName: schemaName || 'all',
|
|
458
|
-
count: result.recordset.length,
|
|
459
|
-
executionTime: `${executionTime}ms`,
|
|
460
|
-
includeMetadata
|
|
461
|
-
}
|
|
462
|
-
}, null, 2)
|
|
463
|
-
}],
|
|
464
|
-
_meta: {
|
|
465
|
-
objectType,
|
|
466
|
-
count: result.recordset.length,
|
|
467
|
-
executionTime: `${executionTime}ms`,
|
|
468
|
-
schemaFiltered: !!schemaName,
|
|
469
|
-
timestamp: new Date().toISOString()
|
|
470
|
-
}
|
|
206
|
+
text: JSON.stringify(result.recordset, null, 2),
|
|
207
|
+
},
|
|
208
|
+
],
|
|
471
209
|
};
|
|
472
210
|
}
|
|
473
211
|
catch (error) {
|
|
474
|
-
return
|
|
212
|
+
return {
|
|
213
|
+
content: [
|
|
214
|
+
{
|
|
215
|
+
type: "text",
|
|
216
|
+
text: `Schema query failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
isError: true,
|
|
220
|
+
};
|
|
475
221
|
}
|
|
476
222
|
});
|
|
477
223
|
// Tool: Get table structure
|
|
478
|
-
this.server.
|
|
479
|
-
|
|
480
|
-
|
|
224
|
+
this.server.registerTool("describe_table", {
|
|
225
|
+
title: "Describe Table",
|
|
226
|
+
description: "Get detailed structure of a specific table",
|
|
227
|
+
inputSchema: {
|
|
228
|
+
tableName: z.string().describe("Name of the table"),
|
|
229
|
+
schemaName: z.string().optional().default("dbo").describe("Schema name"),
|
|
230
|
+
},
|
|
481
231
|
}, async ({ tableName, schemaName }) => {
|
|
482
232
|
try {
|
|
483
233
|
if (!this.pool) {
|
|
484
|
-
throw new
|
|
234
|
+
throw new Error("No database connection. Please connect first.");
|
|
485
235
|
}
|
|
486
236
|
const query = `
|
|
487
|
-
SELECT
|
|
237
|
+
SELECT
|
|
488
238
|
COLUMN_NAME,
|
|
489
239
|
DATA_TYPE,
|
|
490
240
|
CHARACTER_MAXIMUM_LENGTH,
|
|
@@ -494,7 +244,7 @@ class MSSQLMCPServer {
|
|
|
494
244
|
COLUMN_DEFAULT,
|
|
495
245
|
ORDINAL_POSITION
|
|
496
246
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
497
|
-
WHERE TABLE_NAME = @tableName
|
|
247
|
+
WHERE TABLE_NAME = @tableName
|
|
498
248
|
AND TABLE_SCHEMA = @schemaName
|
|
499
249
|
ORDER BY ORDINAL_POSITION
|
|
500
250
|
`;
|
|
@@ -512,11 +262,23 @@ class MSSQLMCPServer {
|
|
|
512
262
|
};
|
|
513
263
|
}
|
|
514
264
|
catch (error) {
|
|
515
|
-
return
|
|
265
|
+
return {
|
|
266
|
+
content: [
|
|
267
|
+
{
|
|
268
|
+
type: "text",
|
|
269
|
+
text: `Table description failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
isError: true,
|
|
273
|
+
};
|
|
516
274
|
}
|
|
517
275
|
});
|
|
518
276
|
// Tool: Get enhanced connection status
|
|
519
|
-
this.server.
|
|
277
|
+
this.server.registerTool("connection_status", {
|
|
278
|
+
title: "Connection Status",
|
|
279
|
+
description: "Check current database connection status with detailed information",
|
|
280
|
+
inputSchema: {},
|
|
281
|
+
}, async () => {
|
|
520
282
|
const isConnected = this.pool?.connected || false;
|
|
521
283
|
const status = {
|
|
522
284
|
connected: isConnected,
|
|
@@ -526,12 +288,6 @@ class MSSQLMCPServer {
|
|
|
526
288
|
connectionTime: isConnected ? new Date().toISOString() : null,
|
|
527
289
|
securityFeatures: {
|
|
528
290
|
sqlInjectionProtection: "Enabled",
|
|
529
|
-
rateLimiting: "Enabled",
|
|
530
|
-
caching: "Enabled"
|
|
531
|
-
},
|
|
532
|
-
cacheStats: {
|
|
533
|
-
cacheSize: this.queryCache['cache'].size,
|
|
534
|
-
clientInfo: this.clientId
|
|
535
291
|
},
|
|
536
292
|
poolInfo: this.pool ? {
|
|
537
293
|
size: this.pool.size,
|
|
@@ -550,48 +306,63 @@ class MSSQLMCPServer {
|
|
|
550
306
|
};
|
|
551
307
|
});
|
|
552
308
|
// Tool: Disconnect from database
|
|
553
|
-
this.server.
|
|
309
|
+
this.server.registerTool("disconnect_database", {
|
|
310
|
+
title: "Disconnect Database",
|
|
311
|
+
description: "Disconnect from the current database",
|
|
312
|
+
inputSchema: {},
|
|
313
|
+
}, async () => {
|
|
554
314
|
try {
|
|
555
315
|
if (this.pool) {
|
|
556
316
|
await this.pool.close();
|
|
557
317
|
this.pool = null;
|
|
558
318
|
this.config = null;
|
|
559
|
-
this.queryCache.clear(); // Clear cache on disconnect
|
|
560
319
|
}
|
|
561
320
|
return {
|
|
562
321
|
content: [
|
|
563
322
|
{
|
|
564
323
|
type: "text",
|
|
565
|
-
text: "Successfully disconnected from database
|
|
324
|
+
text: "Successfully disconnected from database",
|
|
566
325
|
},
|
|
567
326
|
],
|
|
568
327
|
};
|
|
569
328
|
}
|
|
570
329
|
catch (error) {
|
|
571
|
-
return
|
|
330
|
+
return {
|
|
331
|
+
content: [
|
|
332
|
+
{
|
|
333
|
+
type: "text",
|
|
334
|
+
text: `Disconnect failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
isError: true,
|
|
338
|
+
};
|
|
572
339
|
}
|
|
573
340
|
});
|
|
574
341
|
// Tool: Get table data with enhanced security and validation
|
|
575
|
-
this.server.
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
342
|
+
this.server.registerTool("get_table_data", {
|
|
343
|
+
title: "Get Table Data",
|
|
344
|
+
description: "Get data from a specific table with optional filtering, pagination and input validation",
|
|
345
|
+
inputSchema: {
|
|
346
|
+
tableName: z.string().min(1).regex(/^[a-zA-Z0-9_]+$/, "Table name can only contain letters, numbers, and underscores").describe("Name of the table"),
|
|
347
|
+
schemaName: z.string().regex(/^[a-zA-Z0-9_]+$/, "Schema name can only contain letters, numbers, and underscores").optional().default("dbo").describe("Schema name"),
|
|
348
|
+
limit: z.number().int().min(1).max(10000).optional().default(100).describe("Maximum number of rows to return (1-10000)"),
|
|
349
|
+
offset: z.number().int().min(0).optional().default(0).describe("Number of rows to skip"),
|
|
350
|
+
whereClause: z.string().optional().describe("WHERE clause (without the WHERE keyword) - use parameters for values"),
|
|
351
|
+
orderBy: z.string().optional().describe("ORDER BY clause (without the ORDER BY keyword)"),
|
|
352
|
+
parameters: z.record(z.any()).optional().describe("Parameters for WHERE clause"),
|
|
353
|
+
},
|
|
583
354
|
}, async ({ tableName, schemaName, limit, offset, whereClause, orderBy, parameters }) => {
|
|
584
355
|
try {
|
|
585
356
|
if (!this.pool) {
|
|
586
|
-
throw new
|
|
357
|
+
throw new Error("No database connection. Please connect first.");
|
|
587
358
|
}
|
|
588
359
|
// Security: Validate table and schema names to prevent SQL injection
|
|
589
360
|
const tableNamePattern = /^[a-zA-Z0-9_]+$/;
|
|
590
361
|
if (!tableNamePattern.test(tableName)) {
|
|
591
|
-
throw new
|
|
362
|
+
throw new Error("Invalid table name. Only letters, numbers, and underscores are allowed.");
|
|
592
363
|
}
|
|
593
364
|
if (!tableNamePattern.test(schemaName)) {
|
|
594
|
-
throw new
|
|
365
|
+
throw new Error("Invalid schema name. Only letters, numbers, and underscores are allowed.");
|
|
595
366
|
}
|
|
596
367
|
// Build query using parameterized approach
|
|
597
368
|
let query = `SELECT * FROM [${schemaName}].[${tableName}]`;
|
|
@@ -607,9 +378,10 @@ class MSSQLMCPServer {
|
|
|
607
378
|
}
|
|
608
379
|
if (orderBy) {
|
|
609
380
|
// Validate ORDER BY clause for basic security
|
|
610
|
-
|
|
381
|
+
// Allow dotted identifiers, bracketed identifiers, commas, spaces and optional ASC/DESC per column
|
|
382
|
+
const orderByPattern = /^([\[\]a-zA-Z0-9_.]+(\s+(ASC|DESC))?)((\s*,\s*)[\[\]a-zA-Z0-9_.]+(\s+(ASC|DESC))?)*$/i;
|
|
611
383
|
if (!orderByPattern.test(orderBy)) {
|
|
612
|
-
throw new
|
|
384
|
+
throw new Error("Invalid ORDER BY clause. Only column names, commas, spaces, ASC, and DESC are allowed.");
|
|
613
385
|
}
|
|
614
386
|
query += ` ORDER BY ${orderBy}`;
|
|
615
387
|
}
|
|
@@ -642,18 +414,32 @@ class MSSQLMCPServer {
|
|
|
642
414
|
};
|
|
643
415
|
}
|
|
644
416
|
catch (error) {
|
|
645
|
-
|
|
417
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
418
|
+
console.error("❌ Get table data failed:", errorMessage);
|
|
419
|
+
return {
|
|
420
|
+
content: [
|
|
421
|
+
{
|
|
422
|
+
type: "text",
|
|
423
|
+
text: `❌ Get table data failed: ${errorMessage}`,
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
isError: true,
|
|
427
|
+
};
|
|
646
428
|
}
|
|
647
429
|
});
|
|
648
430
|
// Tool: Execute stored procedure
|
|
649
|
-
this.server.
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
431
|
+
this.server.registerTool("execute_procedure", {
|
|
432
|
+
title: "Execute Procedure",
|
|
433
|
+
description: "Execute a stored procedure with parameters",
|
|
434
|
+
inputSchema: {
|
|
435
|
+
procedureName: z.string().describe("Name of the stored procedure"),
|
|
436
|
+
schemaName: z.string().optional().default("dbo").describe("Schema name"),
|
|
437
|
+
parameters: z.record(z.any()).optional().describe("Procedure parameters (key-value pairs)"),
|
|
438
|
+
},
|
|
653
439
|
}, async ({ procedureName, schemaName, parameters }) => {
|
|
654
440
|
try {
|
|
655
441
|
if (!this.pool) {
|
|
656
|
-
throw new
|
|
442
|
+
throw new Error("No database connection. Please connect first.");
|
|
657
443
|
}
|
|
658
444
|
const request = this.pool.request();
|
|
659
445
|
// Add parameters if provided
|
|
@@ -678,17 +464,29 @@ class MSSQLMCPServer {
|
|
|
678
464
|
};
|
|
679
465
|
}
|
|
680
466
|
catch (error) {
|
|
681
|
-
return
|
|
467
|
+
return {
|
|
468
|
+
content: [
|
|
469
|
+
{
|
|
470
|
+
type: "text",
|
|
471
|
+
text: `Procedure execution failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
472
|
+
},
|
|
473
|
+
],
|
|
474
|
+
isError: true,
|
|
475
|
+
};
|
|
682
476
|
}
|
|
683
477
|
});
|
|
684
478
|
// Tool: Get database list
|
|
685
|
-
this.server.
|
|
479
|
+
this.server.registerTool("list_databases", {
|
|
480
|
+
title: "List Databases",
|
|
481
|
+
description: "List all databases on the connected SQL Server instance",
|
|
482
|
+
inputSchema: {},
|
|
483
|
+
}, async () => {
|
|
686
484
|
try {
|
|
687
485
|
if (!this.pool) {
|
|
688
|
-
throw new
|
|
486
|
+
throw new Error("No database connection. Please connect first.");
|
|
689
487
|
}
|
|
690
488
|
const query = `
|
|
691
|
-
SELECT
|
|
489
|
+
SELECT
|
|
692
490
|
name,
|
|
693
491
|
database_id,
|
|
694
492
|
create_date,
|
|
@@ -713,35 +511,25 @@ class MSSQLMCPServer {
|
|
|
713
511
|
};
|
|
714
512
|
}
|
|
715
513
|
catch (error) {
|
|
716
|
-
return this.handleToolError(error);
|
|
717
|
-
}
|
|
718
|
-
});
|
|
719
|
-
// Tool: Clear query cache
|
|
720
|
-
this.server.tool("clear_cache", "Clear the query result cache", {}, async () => {
|
|
721
|
-
try {
|
|
722
|
-
const cacheSize = this.queryCache['cache'].size;
|
|
723
|
-
this.queryCache.clear();
|
|
724
514
|
return {
|
|
725
515
|
content: [
|
|
726
516
|
{
|
|
727
517
|
type: "text",
|
|
728
|
-
text:
|
|
729
|
-
message: "Query cache cleared successfully",
|
|
730
|
-
clearedEntries: cacheSize,
|
|
731
|
-
timestamp: new Date().toISOString()
|
|
732
|
-
}, null, 2),
|
|
518
|
+
text: `List databases failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
733
519
|
},
|
|
734
520
|
],
|
|
521
|
+
isError: true,
|
|
735
522
|
};
|
|
736
523
|
}
|
|
737
|
-
catch (error) {
|
|
738
|
-
return this.handleToolError(error);
|
|
739
|
-
}
|
|
740
524
|
});
|
|
741
525
|
}
|
|
742
526
|
setupResources() {
|
|
743
527
|
// Resource: Current connection info
|
|
744
|
-
this.server.
|
|
528
|
+
this.server.registerResource("connection-info", "mssql://connection/info", {
|
|
529
|
+
title: "Connection Info",
|
|
530
|
+
description: "Current MSSQL connection information",
|
|
531
|
+
mimeType: "application/json",
|
|
532
|
+
}, async () => {
|
|
745
533
|
const info = {
|
|
746
534
|
connected: this.pool?.connected || false,
|
|
747
535
|
config: this.config ? {
|
|
@@ -749,14 +537,6 @@ class MSSQLMCPServer {
|
|
|
749
537
|
database: this.config.database,
|
|
750
538
|
port: this.config.port,
|
|
751
539
|
} : null,
|
|
752
|
-
cacheStats: {
|
|
753
|
-
cacheSize: this.queryCache['cache'].size,
|
|
754
|
-
lastCleanup: new Date().toISOString()
|
|
755
|
-
},
|
|
756
|
-
clientInfo: {
|
|
757
|
-
clientId: this.clientId,
|
|
758
|
-
rateLimitsEnabled: true
|
|
759
|
-
}
|
|
760
540
|
};
|
|
761
541
|
return {
|
|
762
542
|
contents: [
|
|
@@ -768,22 +548,6 @@ class MSSQLMCPServer {
|
|
|
768
548
|
],
|
|
769
549
|
};
|
|
770
550
|
});
|
|
771
|
-
// Dynamic resource for query results cache status
|
|
772
|
-
this.server.resource("cache-status", "mssql://cache/status", async () => {
|
|
773
|
-
const status = {
|
|
774
|
-
cacheSize: this.queryCache['cache'].size,
|
|
775
|
-
rateLimiting: "DISABLED - Trust-based approach",
|
|
776
|
-
clientId: this.clientId,
|
|
777
|
-
timestamp: new Date().toISOString()
|
|
778
|
-
};
|
|
779
|
-
return {
|
|
780
|
-
contents: [{
|
|
781
|
-
uri: "mssql://cache/status",
|
|
782
|
-
text: JSON.stringify(status, null, 2),
|
|
783
|
-
mimeType: "application/json"
|
|
784
|
-
}]
|
|
785
|
-
};
|
|
786
|
-
});
|
|
787
551
|
}
|
|
788
552
|
async connect(config) {
|
|
789
553
|
try {
|
|
@@ -801,9 +565,9 @@ class MSSQLMCPServer {
|
|
|
801
565
|
password: config.password,
|
|
802
566
|
port: config.port,
|
|
803
567
|
options: {
|
|
568
|
+
encrypt: config.encrypt,
|
|
804
569
|
trustServerCertificate: config.trustServerCertificate,
|
|
805
570
|
enableArithAbort: true,
|
|
806
|
-
encrypt: !config.trustServerCertificate, // Enable encryption when not trusting server certificate
|
|
807
571
|
},
|
|
808
572
|
connectionTimeout: config.connectionTimeout,
|
|
809
573
|
requestTimeout: config.requestTimeout,
|
|
@@ -871,7 +635,7 @@ class MSSQLMCPServer {
|
|
|
871
635
|
console.error('Unhandled Rejection:', reason);
|
|
872
636
|
shutdown('unhandledRejection');
|
|
873
637
|
});
|
|
874
|
-
console.error("MSSQL MCP Server v2.
|
|
638
|
+
console.error("MSSQL MCP Server v2.1.1 starting...");
|
|
875
639
|
await this.server.connect(transport);
|
|
876
640
|
console.error("Server ready");
|
|
877
641
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mssql-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "MCP Server for MS SQL Server integration with Claude Desktop, Cursor, Windsurf and VS Code",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"author": "BYMCS <hello@bymcs.com>",
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
49
49
|
"dotenv": "^16.3.1",
|
|
50
50
|
"mssql": "^11.0.1",
|
|
51
|
-
"zod": "^3.
|
|
51
|
+
"zod": "^3.25.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/mssql": "^9.1.7",
|
|
@@ -56,4 +56,4 @@
|
|
|
56
56
|
"rimraf": "^5.0.0",
|
|
57
57
|
"typescript": "^5.3.0"
|
|
58
58
|
}
|
|
59
|
-
}
|
|
59
|
+
}
|