mssql-mcp 2.0.2 ā 2.0.3
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 +16 -2
- package/dist/index.js +135 -413
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# MS SQL Server MCP Server v2.0.
|
|
1
|
+
# MS SQL Server MCP Server v2.0.3
|
|
2
2
|
|
|
3
3
|
š **Smart Trust-Based Model Context Protocol (MCP) server** for Microsoft SQL Server with intelligent auto-connection and AI-friendly design.
|
|
4
4
|
|
|
@@ -96,6 +96,7 @@ All tools auto-connect using environment variables.
|
|
|
96
96
|
- ā
**Query Caching**: 5-minute TTL for SELECT queries
|
|
97
97
|
- ā
**Performance Monitoring**: Execution time tracking
|
|
98
98
|
- ā
**Latest MCP SDK**: v1.20.2 with 2025 protocol
|
|
99
|
+
- ā
**Clean Logging**: Minimal console output, MCP protocol compatible
|
|
99
100
|
|
|
100
101
|
## š Troubleshooting
|
|
101
102
|
|
|
@@ -108,6 +109,19 @@ All tools auto-connect using environment variables.
|
|
|
108
109
|
- Only server operations are blocked
|
|
109
110
|
- Database operations should work normally
|
|
110
111
|
|
|
112
|
+
## š Version History
|
|
113
|
+
|
|
114
|
+
### v2.0.3 - Latest
|
|
115
|
+
- ā
Updated documentation with modern styling
|
|
116
|
+
- ā
Improved troubleshooting section
|
|
117
|
+
- ā
Enhanced feature descriptions
|
|
118
|
+
|
|
119
|
+
### v2.0.2 - Performance & Compatibility
|
|
120
|
+
- ā
Simplified logging for MCP protocol compatibility
|
|
121
|
+
- ā
All console output moved to stderr
|
|
122
|
+
- ā
Clean, professional log messages
|
|
123
|
+
- ā
Version bump to 2.0.2
|
|
124
|
+
|
|
111
125
|
## š License
|
|
112
126
|
|
|
113
127
|
MIT License
|
|
@@ -119,4 +133,4 @@ MIT License
|
|
|
119
133
|
|
|
120
134
|
---
|
|
121
135
|
|
|
122
|
-
**š v2.0.
|
|
136
|
+
**š v2.0.3: Enhanced documentation and user experience**
|
package/dist/index.js
CHANGED
|
@@ -17,152 +17,24 @@ const ConfigSchema = z.object({
|
|
|
17
17
|
connectionTimeout: z.number().int().min(1000).max(60000).optional().default(30000),
|
|
18
18
|
requestTimeout: z.number().int().min(1000).max(300000).optional().default(30000),
|
|
19
19
|
});
|
|
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
20
|
class MSSQLMCPServer {
|
|
88
21
|
server;
|
|
89
22
|
pool = null;
|
|
90
23
|
config = null;
|
|
91
|
-
queryCache = new QueryCache();
|
|
92
|
-
rateLimiter = new RateLimiter();
|
|
93
|
-
clientId = `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
94
24
|
constructor() {
|
|
95
25
|
this.server = new McpServer({
|
|
96
26
|
name: "mssql-mcp-server",
|
|
97
|
-
version: "2.0.
|
|
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",
|
|
27
|
+
version: "2.0.3",
|
|
99
28
|
});
|
|
100
29
|
this.setupTools();
|
|
101
30
|
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
31
|
}
|
|
153
32
|
setupTools() {
|
|
154
|
-
// Tool: Connect to database with enhanced security validation
|
|
155
|
-
this.server.tool("connect_database", "Connect to MS SQL Server database with security validation
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
annotations: {
|
|
159
|
-
destructiveHint: false,
|
|
160
|
-
idempotentHint: true,
|
|
161
|
-
readOnlyHint: true
|
|
162
|
-
}
|
|
163
|
-
}, async (extra) => {
|
|
33
|
+
// Tool: Connect to database with enhanced security validation (only uses environment variables)
|
|
34
|
+
this.server.tool("connect_database", "Connect to MS SQL Server database with security validation (uses only environment variables for security)", {
|
|
35
|
+
// No parameters - only environment variables will be used for security
|
|
36
|
+
}, async (args) => {
|
|
164
37
|
try {
|
|
165
|
-
// No rate limiting - user is trusted
|
|
166
38
|
// SECURITY: Only use environment variables, ignore all user parameters
|
|
167
39
|
const config = ConfigSchema.parse({
|
|
168
40
|
server: process.env.DB_SERVER,
|
|
@@ -175,124 +47,43 @@ class MSSQLMCPServer {
|
|
|
175
47
|
requestTimeout: process.env.DB_REQUEST_TIMEOUT ? parseInt(process.env.DB_REQUEST_TIMEOUT) : 30000,
|
|
176
48
|
});
|
|
177
49
|
if (!config.server) {
|
|
178
|
-
throw new
|
|
50
|
+
throw new Error("Server is required. Provide it as parameter or set DB_SERVER environment variable.");
|
|
179
51
|
}
|
|
180
|
-
const startTime = Date.now();
|
|
181
52
|
await this.connect(config);
|
|
182
|
-
const connectionTime = Date.now() - startTime;
|
|
183
53
|
return {
|
|
184
|
-
content: [
|
|
54
|
+
content: [
|
|
55
|
+
{
|
|
185
56
|
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
|
-
}
|
|
57
|
+
text: `ā
Successfully connected to SQL Server: ${config.server}${config.database ? ` (Database: ${config.database})` : ""}`,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
196
60
|
};
|
|
197
61
|
}
|
|
198
62
|
catch (error) {
|
|
199
|
-
|
|
63
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
64
|
+
console.error("ā Database connection failed:", errorMessage);
|
|
65
|
+
return {
|
|
66
|
+
content: [
|
|
67
|
+
{
|
|
68
|
+
type: "text",
|
|
69
|
+
text: `ā Failed to connect: ${errorMessage}`,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
isError: true,
|
|
73
|
+
};
|
|
200
74
|
}
|
|
201
75
|
});
|
|
202
|
-
// Tool: Execute SQL query with enhanced security
|
|
203
|
-
this.server.tool("execute_query", "Execute SQL
|
|
204
|
-
query: z.string().min(1, "Query cannot be empty").describe("SQL query to execute
|
|
205
|
-
parameters: z.record(z.any()).optional().describe("Query parameters
|
|
206
|
-
|
|
207
|
-
dryRun: z.boolean().optional().default(false).describe("Validate query without execution"),
|
|
208
|
-
}, {
|
|
209
|
-
title: "Execute SQL Query",
|
|
210
|
-
description: "Executes SQL queries with comprehensive security validation and performance optimization",
|
|
211
|
-
annotations: {
|
|
212
|
-
destructiveHint: false,
|
|
213
|
-
idempotentHint: true,
|
|
214
|
-
readOnlyHint: true
|
|
215
|
-
}
|
|
216
|
-
}, async ({ query, parameters, useCache, dryRun }, extra) => {
|
|
76
|
+
// Tool: Execute SQL query with enhanced security
|
|
77
|
+
this.server.tool("execute_query", "Execute a SQL query against the connected database with security validation", {
|
|
78
|
+
query: z.string().min(1, "Query cannot be empty").describe("SQL query to execute"),
|
|
79
|
+
parameters: z.record(z.any()).optional().describe("Query parameters (key-value pairs) - always use parameters for user input"),
|
|
80
|
+
}, async ({ query, parameters }) => {
|
|
217
81
|
try {
|
|
218
|
-
// No rate limiting - user is trusted
|
|
219
|
-
// Auto-connection logic for better AI experience
|
|
220
82
|
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
|
-
};
|
|
83
|
+
throw new Error("No database connection. Please connect first using connect_database tool.");
|
|
293
84
|
}
|
|
294
85
|
const request = this.pool.request();
|
|
295
|
-
// Add parameters
|
|
86
|
+
// Add parameters if provided (recommended for security)
|
|
296
87
|
if (parameters) {
|
|
297
88
|
for (const [key, value] of Object.entries(parameters)) {
|
|
298
89
|
request.input(key, value);
|
|
@@ -301,190 +92,131 @@ class MSSQLMCPServer {
|
|
|
301
92
|
const startTime = Date.now();
|
|
302
93
|
const result = await request.query(query);
|
|
303
94
|
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
95
|
return {
|
|
319
|
-
content: [
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
320
98
|
type: "text",
|
|
321
|
-
text: JSON.stringify(
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
99
|
+
text: JSON.stringify({
|
|
100
|
+
recordset: result.recordset,
|
|
101
|
+
rowsAffected: result.rowsAffected,
|
|
102
|
+
output: result.output,
|
|
103
|
+
executionTime: `${executionTime}ms`,
|
|
104
|
+
parametersUsed: parameters ? Object.keys(parameters).length : 0,
|
|
105
|
+
}, null, 2),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
331
108
|
};
|
|
332
109
|
}
|
|
333
110
|
catch (error) {
|
|
334
|
-
|
|
111
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
112
|
+
console.error("ā Query execution failed:", errorMessage);
|
|
113
|
+
return {
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: `ā Query execution failed: ${errorMessage}`,
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
isError: true,
|
|
121
|
+
};
|
|
335
122
|
}
|
|
336
123
|
});
|
|
337
|
-
// Tool: Get database schema
|
|
338
|
-
this.server.tool("get_schema", "
|
|
339
|
-
objectType: z.enum(["tables", "views", "procedures", "functions", "all"]).optional().default("tables")
|
|
340
|
-
schemaName: z.string().optional().describe("
|
|
341
|
-
|
|
342
|
-
}, {
|
|
343
|
-
title: "Get Database Schema",
|
|
344
|
-
description: "Retrieves comprehensive database schema information with filtering options",
|
|
345
|
-
annotations: {
|
|
346
|
-
destructiveHint: false,
|
|
347
|
-
idempotentHint: true,
|
|
348
|
-
readOnlyHint: true
|
|
349
|
-
}
|
|
350
|
-
}, async ({ objectType, schemaName, includeMetadata }, extra) => {
|
|
124
|
+
// Tool: Get database schema
|
|
125
|
+
this.server.tool("get_schema", "Get database schema information (tables, columns, etc.)", {
|
|
126
|
+
objectType: z.enum(["tables", "views", "procedures", "functions", "all"]).optional().default("tables"),
|
|
127
|
+
schemaName: z.string().optional().describe("Specific schema name to filter"),
|
|
128
|
+
}, async ({ objectType, schemaName }) => {
|
|
351
129
|
try {
|
|
352
|
-
// Auto-connection logic for seamless AI experience
|
|
353
130
|
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
|
-
}
|
|
131
|
+
throw new Error("No database connection. Please connect first.");
|
|
375
132
|
}
|
|
376
133
|
let query = "";
|
|
377
|
-
const startTime = Date.now();
|
|
378
134
|
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
|
|
135
|
+
query += `
|
|
136
|
+
SELECT
|
|
391
137
|
TABLE_SCHEMA,
|
|
392
138
|
TABLE_NAME,
|
|
393
139
|
TABLE_TYPE,
|
|
394
140
|
'table' as OBJECT_TYPE
|
|
395
141
|
FROM INFORMATION_SCHEMA.TABLES
|
|
396
|
-
${schemaName ? `WHERE TABLE_SCHEMA =
|
|
142
|
+
${schemaName ? `WHERE TABLE_SCHEMA = '${schemaName}'` : ""}
|
|
397
143
|
`;
|
|
398
144
|
}
|
|
399
145
|
if (objectType === "views" || objectType === "all") {
|
|
400
146
|
if (query)
|
|
401
147
|
query += " UNION ALL ";
|
|
402
148
|
query += `
|
|
403
|
-
SELECT
|
|
149
|
+
SELECT
|
|
404
150
|
TABLE_SCHEMA,
|
|
405
151
|
TABLE_NAME,
|
|
406
152
|
'VIEW' as TABLE_TYPE,
|
|
407
153
|
'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
154
|
FROM INFORMATION_SCHEMA.VIEWS
|
|
410
|
-
${schemaName ? `WHERE TABLE_SCHEMA =
|
|
155
|
+
${schemaName ? `WHERE TABLE_SCHEMA = '${schemaName}'` : ""}
|
|
411
156
|
`;
|
|
412
157
|
}
|
|
413
158
|
if (objectType === "procedures" || objectType === "all") {
|
|
414
159
|
if (query)
|
|
415
160
|
query += " UNION ALL ";
|
|
416
161
|
query += `
|
|
417
|
-
SELECT
|
|
162
|
+
SELECT
|
|
418
163
|
ROUTINE_SCHEMA as TABLE_SCHEMA,
|
|
419
164
|
ROUTINE_NAME as TABLE_NAME,
|
|
420
165
|
'PROCEDURE' as TABLE_TYPE,
|
|
421
166
|
'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
167
|
FROM INFORMATION_SCHEMA.ROUTINES
|
|
424
168
|
WHERE ROUTINE_TYPE = 'PROCEDURE'
|
|
425
|
-
${schemaName ? `AND ROUTINE_SCHEMA =
|
|
169
|
+
${schemaName ? `AND ROUTINE_SCHEMA = '${schemaName}'` : ""}
|
|
426
170
|
`;
|
|
427
171
|
}
|
|
428
172
|
if (objectType === "functions" || objectType === "all") {
|
|
429
173
|
if (query)
|
|
430
174
|
query += " UNION ALL ";
|
|
431
175
|
query += `
|
|
432
|
-
SELECT
|
|
176
|
+
SELECT
|
|
433
177
|
ROUTINE_SCHEMA as TABLE_SCHEMA,
|
|
434
178
|
ROUTINE_NAME as TABLE_NAME,
|
|
435
179
|
'FUNCTION' as TABLE_TYPE,
|
|
436
180
|
'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
181
|
FROM INFORMATION_SCHEMA.ROUTINES
|
|
439
182
|
WHERE ROUTINE_TYPE = 'FUNCTION'
|
|
440
|
-
${schemaName ? `AND ROUTINE_SCHEMA =
|
|
183
|
+
${schemaName ? `AND ROUTINE_SCHEMA = '${schemaName}'` : ""}
|
|
441
184
|
`;
|
|
442
185
|
}
|
|
443
186
|
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;
|
|
187
|
+
const result = await this.pool.request().query(query);
|
|
450
188
|
return {
|
|
451
|
-
content: [
|
|
189
|
+
content: [
|
|
190
|
+
{
|
|
452
191
|
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
|
-
}
|
|
192
|
+
text: JSON.stringify(result.recordset, null, 2),
|
|
193
|
+
},
|
|
194
|
+
],
|
|
471
195
|
};
|
|
472
196
|
}
|
|
473
197
|
catch (error) {
|
|
474
|
-
return
|
|
198
|
+
return {
|
|
199
|
+
content: [
|
|
200
|
+
{
|
|
201
|
+
type: "text",
|
|
202
|
+
text: `Schema query failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
isError: true,
|
|
206
|
+
};
|
|
475
207
|
}
|
|
476
208
|
});
|
|
477
209
|
// Tool: Get table structure
|
|
478
|
-
this.server.tool("describe_table", "Get
|
|
210
|
+
this.server.tool("describe_table", "Get detailed structure of a specific table", {
|
|
479
211
|
tableName: z.string().describe("Name of the table"),
|
|
480
212
|
schemaName: z.string().optional().default("dbo").describe("Schema name"),
|
|
481
213
|
}, async ({ tableName, schemaName }) => {
|
|
482
214
|
try {
|
|
483
215
|
if (!this.pool) {
|
|
484
|
-
throw new
|
|
216
|
+
throw new Error("No database connection. Please connect first.");
|
|
485
217
|
}
|
|
486
218
|
const query = `
|
|
487
|
-
SELECT
|
|
219
|
+
SELECT
|
|
488
220
|
COLUMN_NAME,
|
|
489
221
|
DATA_TYPE,
|
|
490
222
|
CHARACTER_MAXIMUM_LENGTH,
|
|
@@ -494,7 +226,7 @@ class MSSQLMCPServer {
|
|
|
494
226
|
COLUMN_DEFAULT,
|
|
495
227
|
ORDINAL_POSITION
|
|
496
228
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
497
|
-
WHERE TABLE_NAME = @tableName
|
|
229
|
+
WHERE TABLE_NAME = @tableName
|
|
498
230
|
AND TABLE_SCHEMA = @schemaName
|
|
499
231
|
ORDER BY ORDINAL_POSITION
|
|
500
232
|
`;
|
|
@@ -512,7 +244,15 @@ class MSSQLMCPServer {
|
|
|
512
244
|
};
|
|
513
245
|
}
|
|
514
246
|
catch (error) {
|
|
515
|
-
return
|
|
247
|
+
return {
|
|
248
|
+
content: [
|
|
249
|
+
{
|
|
250
|
+
type: "text",
|
|
251
|
+
text: `Table description failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
isError: true,
|
|
255
|
+
};
|
|
516
256
|
}
|
|
517
257
|
});
|
|
518
258
|
// Tool: Get enhanced connection status
|
|
@@ -526,12 +266,6 @@ class MSSQLMCPServer {
|
|
|
526
266
|
connectionTime: isConnected ? new Date().toISOString() : null,
|
|
527
267
|
securityFeatures: {
|
|
528
268
|
sqlInjectionProtection: "Enabled",
|
|
529
|
-
rateLimiting: "Enabled",
|
|
530
|
-
caching: "Enabled"
|
|
531
|
-
},
|
|
532
|
-
cacheStats: {
|
|
533
|
-
cacheSize: this.queryCache['cache'].size,
|
|
534
|
-
clientInfo: this.clientId
|
|
535
269
|
},
|
|
536
270
|
poolInfo: this.pool ? {
|
|
537
271
|
size: this.pool.size,
|
|
@@ -556,19 +290,26 @@ class MSSQLMCPServer {
|
|
|
556
290
|
await this.pool.close();
|
|
557
291
|
this.pool = null;
|
|
558
292
|
this.config = null;
|
|
559
|
-
this.queryCache.clear(); // Clear cache on disconnect
|
|
560
293
|
}
|
|
561
294
|
return {
|
|
562
295
|
content: [
|
|
563
296
|
{
|
|
564
297
|
type: "text",
|
|
565
|
-
text: "Successfully disconnected from database
|
|
298
|
+
text: "Successfully disconnected from database",
|
|
566
299
|
},
|
|
567
300
|
],
|
|
568
301
|
};
|
|
569
302
|
}
|
|
570
303
|
catch (error) {
|
|
571
|
-
return
|
|
304
|
+
return {
|
|
305
|
+
content: [
|
|
306
|
+
{
|
|
307
|
+
type: "text",
|
|
308
|
+
text: `Disconnect failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
isError: true,
|
|
312
|
+
};
|
|
572
313
|
}
|
|
573
314
|
});
|
|
574
315
|
// Tool: Get table data with enhanced security and validation
|
|
@@ -583,15 +324,15 @@ class MSSQLMCPServer {
|
|
|
583
324
|
}, async ({ tableName, schemaName, limit, offset, whereClause, orderBy, parameters }) => {
|
|
584
325
|
try {
|
|
585
326
|
if (!this.pool) {
|
|
586
|
-
throw new
|
|
327
|
+
throw new Error("No database connection. Please connect first.");
|
|
587
328
|
}
|
|
588
329
|
// Security: Validate table and schema names to prevent SQL injection
|
|
589
330
|
const tableNamePattern = /^[a-zA-Z0-9_]+$/;
|
|
590
331
|
if (!tableNamePattern.test(tableName)) {
|
|
591
|
-
throw new
|
|
332
|
+
throw new Error("Invalid table name. Only letters, numbers, and underscores are allowed.");
|
|
592
333
|
}
|
|
593
334
|
if (!tableNamePattern.test(schemaName)) {
|
|
594
|
-
throw new
|
|
335
|
+
throw new Error("Invalid schema name. Only letters, numbers, and underscores are allowed.");
|
|
595
336
|
}
|
|
596
337
|
// Build query using parameterized approach
|
|
597
338
|
let query = `SELECT * FROM [${schemaName}].[${tableName}]`;
|
|
@@ -607,9 +348,10 @@ class MSSQLMCPServer {
|
|
|
607
348
|
}
|
|
608
349
|
if (orderBy) {
|
|
609
350
|
// Validate ORDER BY clause for basic security
|
|
351
|
+
// Allow dotted identifiers, bracketed identifiers, commas, spaces and optional ASC/DESC per column
|
|
610
352
|
const orderByPattern = /^([\[\]a-zA-Z0-9_.]+(\s+(ASC|DESC))?)(\s*,\s*[\[\]a-zA-Z0-9_.]+(\s+(ASC|DESC))?)*$/i;
|
|
611
353
|
if (!orderByPattern.test(orderBy)) {
|
|
612
|
-
throw new
|
|
354
|
+
throw new Error("Invalid ORDER BY clause. Only column names, commas, spaces, ASC, and DESC are allowed.");
|
|
613
355
|
}
|
|
614
356
|
query += ` ORDER BY ${orderBy}`;
|
|
615
357
|
}
|
|
@@ -642,7 +384,17 @@ class MSSQLMCPServer {
|
|
|
642
384
|
};
|
|
643
385
|
}
|
|
644
386
|
catch (error) {
|
|
645
|
-
|
|
387
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
388
|
+
console.error("ā Get table data failed:", errorMessage);
|
|
389
|
+
return {
|
|
390
|
+
content: [
|
|
391
|
+
{
|
|
392
|
+
type: "text",
|
|
393
|
+
text: `ā Get table data failed: ${errorMessage}`,
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
isError: true,
|
|
397
|
+
};
|
|
646
398
|
}
|
|
647
399
|
});
|
|
648
400
|
// Tool: Execute stored procedure
|
|
@@ -653,7 +405,7 @@ class MSSQLMCPServer {
|
|
|
653
405
|
}, async ({ procedureName, schemaName, parameters }) => {
|
|
654
406
|
try {
|
|
655
407
|
if (!this.pool) {
|
|
656
|
-
throw new
|
|
408
|
+
throw new Error("No database connection. Please connect first.");
|
|
657
409
|
}
|
|
658
410
|
const request = this.pool.request();
|
|
659
411
|
// Add parameters if provided
|
|
@@ -678,17 +430,25 @@ class MSSQLMCPServer {
|
|
|
678
430
|
};
|
|
679
431
|
}
|
|
680
432
|
catch (error) {
|
|
681
|
-
return
|
|
433
|
+
return {
|
|
434
|
+
content: [
|
|
435
|
+
{
|
|
436
|
+
type: "text",
|
|
437
|
+
text: `Procedure execution failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
438
|
+
},
|
|
439
|
+
],
|
|
440
|
+
isError: true,
|
|
441
|
+
};
|
|
682
442
|
}
|
|
683
443
|
});
|
|
684
444
|
// Tool: Get database list
|
|
685
445
|
this.server.tool("list_databases", "List all databases on the connected SQL Server instance", {}, async () => {
|
|
686
446
|
try {
|
|
687
447
|
if (!this.pool) {
|
|
688
|
-
throw new
|
|
448
|
+
throw new Error("No database connection. Please connect first.");
|
|
689
449
|
}
|
|
690
450
|
const query = `
|
|
691
|
-
SELECT
|
|
451
|
+
SELECT
|
|
692
452
|
name,
|
|
693
453
|
database_id,
|
|
694
454
|
create_date,
|
|
@@ -713,30 +473,16 @@ class MSSQLMCPServer {
|
|
|
713
473
|
};
|
|
714
474
|
}
|
|
715
475
|
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
476
|
return {
|
|
725
477
|
content: [
|
|
726
478
|
{
|
|
727
479
|
type: "text",
|
|
728
|
-
text:
|
|
729
|
-
message: "Query cache cleared successfully",
|
|
730
|
-
clearedEntries: cacheSize,
|
|
731
|
-
timestamp: new Date().toISOString()
|
|
732
|
-
}, null, 2),
|
|
480
|
+
text: `List databases failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
733
481
|
},
|
|
734
482
|
],
|
|
483
|
+
isError: true,
|
|
735
484
|
};
|
|
736
485
|
}
|
|
737
|
-
catch (error) {
|
|
738
|
-
return this.handleToolError(error);
|
|
739
|
-
}
|
|
740
486
|
});
|
|
741
487
|
}
|
|
742
488
|
setupResources() {
|
|
@@ -749,14 +495,6 @@ class MSSQLMCPServer {
|
|
|
749
495
|
database: this.config.database,
|
|
750
496
|
port: this.config.port,
|
|
751
497
|
} : 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
498
|
};
|
|
761
499
|
return {
|
|
762
500
|
contents: [
|
|
@@ -768,22 +506,6 @@ class MSSQLMCPServer {
|
|
|
768
506
|
],
|
|
769
507
|
};
|
|
770
508
|
});
|
|
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
509
|
}
|
|
788
510
|
async connect(config) {
|
|
789
511
|
try {
|
|
@@ -871,7 +593,7 @@ class MSSQLMCPServer {
|
|
|
871
593
|
console.error('Unhandled Rejection:', reason);
|
|
872
594
|
shutdown('unhandledRejection');
|
|
873
595
|
});
|
|
874
|
-
console.error("MSSQL MCP Server v2.0.
|
|
596
|
+
console.error("MSSQL MCP Server v2.0.3 starting...");
|
|
875
597
|
await this.server.connect(transport);
|
|
876
598
|
console.error("Server ready");
|
|
877
599
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mssql-mcp",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
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,7 +45,7 @@
|
|
|
45
45
|
"author": "BYMCS <hello@bymcs.com>",
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.17.1",
|
|
49
49
|
"dotenv": "^16.3.1",
|
|
50
50
|
"mssql": "^11.0.1",
|
|
51
51
|
"zod": "^3.22.4"
|