@cmd233/mcp-database-server 1.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/LICENSE +21 -0
- package/dist/src/db/adapter.js +29 -0
- package/dist/src/db/index.js +95 -0
- package/dist/src/db/mysql-adapter.js +205 -0
- package/dist/src/db/postgresql-adapter.js +169 -0
- package/dist/src/db/sqlite-adapter.js +134 -0
- package/dist/src/db/sqlserver-adapter.js +186 -0
- package/dist/src/handlers/resourceHandlers.js +71 -0
- package/dist/src/handlers/toolHandlers.js +158 -0
- package/dist/src/index.js +259 -0
- package/dist/src/tools/insightTools.js +54 -0
- package/dist/src/tools/queryTools.js +73 -0
- package/dist/src/tools/schemaTools.js +118 -0
- package/dist/src/utils/formatUtils.js +56 -0
- package/package.json +41 -0
- package/readme.md +328 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import sql from 'mssql';
|
|
2
|
+
/**
|
|
3
|
+
* SQL Server database adapter implementation
|
|
4
|
+
*/
|
|
5
|
+
export class SqlServerAdapter {
|
|
6
|
+
constructor(connectionInfo) {
|
|
7
|
+
this.pool = null;
|
|
8
|
+
this.server = connectionInfo.server;
|
|
9
|
+
this.database = connectionInfo.database;
|
|
10
|
+
// Create SQL Server connection config
|
|
11
|
+
this.config = {
|
|
12
|
+
server: connectionInfo.server,
|
|
13
|
+
database: connectionInfo.database,
|
|
14
|
+
port: connectionInfo.port || 1433,
|
|
15
|
+
options: {
|
|
16
|
+
trustServerCertificate: connectionInfo.trustServerCertificate ?? true,
|
|
17
|
+
...connectionInfo.options
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
// Add authentication options
|
|
21
|
+
if (connectionInfo.user && connectionInfo.password) {
|
|
22
|
+
this.config.user = connectionInfo.user;
|
|
23
|
+
this.config.password = connectionInfo.password;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
// Use Windows authentication if no username/password provided
|
|
27
|
+
this.config.options.trustedConnection = true;
|
|
28
|
+
this.config.options.enableArithAbort = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Initialize SQL Server connection
|
|
33
|
+
*/
|
|
34
|
+
async init() {
|
|
35
|
+
try {
|
|
36
|
+
console.error(`[INFO] Connecting to SQL Server: ${this.server}, Database: ${this.database}`);
|
|
37
|
+
this.pool = await new sql.ConnectionPool(this.config).connect();
|
|
38
|
+
console.error(`[INFO] SQL Server connection established successfully`);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error(`[ERROR] SQL Server connection error: ${err.message}`);
|
|
42
|
+
throw new Error(`Failed to connect to SQL Server: ${err.message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Execute a SQL query and get all results
|
|
47
|
+
* @param query SQL query to execute
|
|
48
|
+
* @param params Query parameters
|
|
49
|
+
* @returns Promise with query results
|
|
50
|
+
*/
|
|
51
|
+
async all(query, params = []) {
|
|
52
|
+
if (!this.pool) {
|
|
53
|
+
throw new Error("Database not initialized");
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const request = this.pool.request();
|
|
57
|
+
// Add parameters to the request
|
|
58
|
+
params.forEach((param, index) => {
|
|
59
|
+
request.input(`param${index}`, param);
|
|
60
|
+
});
|
|
61
|
+
// Replace ? with named parameters
|
|
62
|
+
const preparedQuery = query.replace(/\?/g, (_, i) => `@param${i}`);
|
|
63
|
+
const result = await request.query(preparedQuery);
|
|
64
|
+
return result.recordset;
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
throw new Error(`SQL Server query error: ${err.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Execute a SQL query that modifies data
|
|
72
|
+
* @param query SQL query to execute
|
|
73
|
+
* @param params Query parameters
|
|
74
|
+
* @returns Promise with result info
|
|
75
|
+
*/
|
|
76
|
+
async run(query, params = []) {
|
|
77
|
+
if (!this.pool) {
|
|
78
|
+
throw new Error("Database not initialized");
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const request = this.pool.request();
|
|
82
|
+
// Add parameters to the request
|
|
83
|
+
params.forEach((param, index) => {
|
|
84
|
+
request.input(`param${index}`, param);
|
|
85
|
+
});
|
|
86
|
+
// Replace ? with named parameters
|
|
87
|
+
const preparedQuery = query.replace(/\?/g, (_, i) => `@param${i}`);
|
|
88
|
+
// Add output parameter for identity value if it's an INSERT
|
|
89
|
+
let lastID = 0;
|
|
90
|
+
if (query.trim().toUpperCase().startsWith('INSERT')) {
|
|
91
|
+
request.output('insertedId', sql.Int, 0);
|
|
92
|
+
const updatedQuery = `${preparedQuery}; SELECT @insertedId = SCOPE_IDENTITY();`;
|
|
93
|
+
const result = await request.query(updatedQuery);
|
|
94
|
+
lastID = result.output.insertedId || 0;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const result = await request.query(preparedQuery);
|
|
98
|
+
lastID = 0;
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
changes: this.getAffectedRows(query, lastID),
|
|
102
|
+
lastID: lastID
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
throw new Error(`SQL Server query error: ${err.message}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Execute multiple SQL statements
|
|
111
|
+
* @param query SQL statements to execute
|
|
112
|
+
* @returns Promise that resolves when execution completes
|
|
113
|
+
*/
|
|
114
|
+
async exec(query) {
|
|
115
|
+
if (!this.pool) {
|
|
116
|
+
throw new Error("Database not initialized");
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const request = this.pool.request();
|
|
120
|
+
await request.batch(query);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
throw new Error(`SQL Server batch error: ${err.message}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Close the database connection
|
|
128
|
+
*/
|
|
129
|
+
async close() {
|
|
130
|
+
if (this.pool) {
|
|
131
|
+
await this.pool.close();
|
|
132
|
+
this.pool = null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get database metadata
|
|
137
|
+
*/
|
|
138
|
+
getMetadata() {
|
|
139
|
+
return {
|
|
140
|
+
name: "SQL Server",
|
|
141
|
+
type: "sqlserver",
|
|
142
|
+
server: this.server,
|
|
143
|
+
database: this.database
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get database-specific query for listing tables
|
|
148
|
+
*/
|
|
149
|
+
getListTablesQuery() {
|
|
150
|
+
return "SELECT TABLE_NAME as name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME";
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get database-specific query for describing a table
|
|
154
|
+
* @param tableName Table name
|
|
155
|
+
*/
|
|
156
|
+
getDescribeTableQuery(tableName) {
|
|
157
|
+
return `
|
|
158
|
+
SELECT
|
|
159
|
+
c.COLUMN_NAME as name,
|
|
160
|
+
c.DATA_TYPE as type,
|
|
161
|
+
CASE WHEN c.IS_NULLABLE = 'NO' THEN 1 ELSE 0 END as notnull,
|
|
162
|
+
CASE WHEN pk.CONSTRAINT_TYPE = 'PRIMARY KEY' THEN 1 ELSE 0 END as pk,
|
|
163
|
+
c.COLUMN_DEFAULT as dflt_value
|
|
164
|
+
FROM
|
|
165
|
+
INFORMATION_SCHEMA.COLUMNS c
|
|
166
|
+
LEFT JOIN
|
|
167
|
+
INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON c.TABLE_NAME = kcu.TABLE_NAME AND c.COLUMN_NAME = kcu.COLUMN_NAME
|
|
168
|
+
LEFT JOIN
|
|
169
|
+
INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ON kcu.CONSTRAINT_NAME = pk.CONSTRAINT_NAME AND pk.CONSTRAINT_TYPE = 'PRIMARY KEY'
|
|
170
|
+
WHERE
|
|
171
|
+
c.TABLE_NAME = '${tableName}'
|
|
172
|
+
ORDER BY
|
|
173
|
+
c.ORDINAL_POSITION
|
|
174
|
+
`;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Helper to get the number of affected rows based on query type
|
|
178
|
+
*/
|
|
179
|
+
getAffectedRows(query, lastID) {
|
|
180
|
+
const queryType = query.trim().split(' ')[0].toUpperCase();
|
|
181
|
+
if (queryType === 'INSERT' && lastID > 0) {
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
return 0; // For SELECT, unknown for UPDATE/DELETE without additional query
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { dbAll, getListTablesQuery, getDescribeTableQuery, getDatabaseMetadata } from '../db/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Handle listing resources request
|
|
4
|
+
* @returns List of available resources
|
|
5
|
+
*/
|
|
6
|
+
export async function handleListResources() {
|
|
7
|
+
try {
|
|
8
|
+
const dbInfo = getDatabaseMetadata();
|
|
9
|
+
const dbType = dbInfo.type;
|
|
10
|
+
let resourceBaseUrl;
|
|
11
|
+
// Create appropriate URL based on database type
|
|
12
|
+
if (dbType === 'sqlite' && dbInfo.path) {
|
|
13
|
+
resourceBaseUrl = new URL(`sqlite:///${dbInfo.path}`);
|
|
14
|
+
}
|
|
15
|
+
else if (dbType === 'sqlserver' && dbInfo.server && dbInfo.database) {
|
|
16
|
+
resourceBaseUrl = new URL(`sqlserver://${dbInfo.server}/${dbInfo.database}`);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
resourceBaseUrl = new URL(`db:///database`);
|
|
20
|
+
}
|
|
21
|
+
const SCHEMA_PATH = "schema";
|
|
22
|
+
// Use adapter-specific query to list tables
|
|
23
|
+
const query = getListTablesQuery();
|
|
24
|
+
const result = await dbAll(query);
|
|
25
|
+
return {
|
|
26
|
+
resources: result.map((row) => ({
|
|
27
|
+
uri: new URL(`${row.name}/${SCHEMA_PATH}`, resourceBaseUrl).href,
|
|
28
|
+
mimeType: "application/json",
|
|
29
|
+
name: `"${row.name}" database schema`,
|
|
30
|
+
})),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw new Error(`Error listing resources: ${error.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Handle reading a specific resource
|
|
39
|
+
* @param uri URI of the resource to read
|
|
40
|
+
* @returns Resource contents
|
|
41
|
+
*/
|
|
42
|
+
export async function handleReadResource(uri) {
|
|
43
|
+
try {
|
|
44
|
+
const resourceUrl = new URL(uri);
|
|
45
|
+
const SCHEMA_PATH = "schema";
|
|
46
|
+
const pathComponents = resourceUrl.pathname.split("/");
|
|
47
|
+
const schema = pathComponents.pop();
|
|
48
|
+
const tableName = pathComponents.pop();
|
|
49
|
+
if (schema !== SCHEMA_PATH) {
|
|
50
|
+
throw new Error("Invalid resource URI");
|
|
51
|
+
}
|
|
52
|
+
// Use adapter-specific query to describe the table
|
|
53
|
+
const query = getDescribeTableQuery(tableName);
|
|
54
|
+
const result = await dbAll(query);
|
|
55
|
+
return {
|
|
56
|
+
contents: [
|
|
57
|
+
{
|
|
58
|
+
uri,
|
|
59
|
+
mimeType: "application/json",
|
|
60
|
+
text: JSON.stringify(result.map((column) => ({
|
|
61
|
+
column_name: column.name,
|
|
62
|
+
data_type: column.type
|
|
63
|
+
})), null, 2),
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
throw new Error(`Error reading resource: ${error.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { formatErrorResponse } from '../utils/formatUtils.js';
|
|
2
|
+
// Import all tool implementations
|
|
3
|
+
import { readQuery, writeQuery, exportQuery } from '../tools/queryTools.js';
|
|
4
|
+
import { createTable, alterTable, dropTable, listTables, describeTable } from '../tools/schemaTools.js';
|
|
5
|
+
import { appendInsight, listInsights } from '../tools/insightTools.js';
|
|
6
|
+
/**
|
|
7
|
+
* Handle listing available tools
|
|
8
|
+
* @returns List of available tools
|
|
9
|
+
*/
|
|
10
|
+
export function handleListTools() {
|
|
11
|
+
return {
|
|
12
|
+
tools: [
|
|
13
|
+
{
|
|
14
|
+
name: "read_query",
|
|
15
|
+
description: "Execute SELECT queries to read data from the database",
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
query: { type: "string" },
|
|
20
|
+
},
|
|
21
|
+
required: ["query"],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "write_query",
|
|
26
|
+
description: "Execute INSERT, UPDATE, or DELETE queries",
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
query: { type: "string" },
|
|
31
|
+
},
|
|
32
|
+
required: ["query"],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "create_table",
|
|
37
|
+
description: "Create new tables in the database",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
query: { type: "string" },
|
|
42
|
+
},
|
|
43
|
+
required: ["query"],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "alter_table",
|
|
48
|
+
description: "Modify existing table schema (add columns, rename tables, etc.)",
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {
|
|
52
|
+
query: { type: "string" },
|
|
53
|
+
},
|
|
54
|
+
required: ["query"],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "drop_table",
|
|
59
|
+
description: "Remove a table from the database with safety confirmation",
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: "object",
|
|
62
|
+
properties: {
|
|
63
|
+
table_name: { type: "string" },
|
|
64
|
+
confirm: { type: "boolean" },
|
|
65
|
+
},
|
|
66
|
+
required: ["table_name", "confirm"],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "export_query",
|
|
71
|
+
description: "Export query results to various formats (CSV, JSON)",
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
query: { type: "string" },
|
|
76
|
+
format: { type: "string", enum: ["csv", "json"] },
|
|
77
|
+
},
|
|
78
|
+
required: ["query", "format"],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "list_tables",
|
|
83
|
+
description: "Get a list of all tables in the database",
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: {},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "describe_table",
|
|
91
|
+
description: "View schema information for a specific table",
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
table_name: { type: "string" },
|
|
96
|
+
},
|
|
97
|
+
required: ["table_name"],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "append_insight",
|
|
102
|
+
description: "Add a business insight to the memo",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: "object",
|
|
105
|
+
properties: {
|
|
106
|
+
insight: { type: "string" },
|
|
107
|
+
},
|
|
108
|
+
required: ["insight"],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "list_insights",
|
|
113
|
+
description: "List all business insights in the memo",
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Handle tool call requests
|
|
124
|
+
* @param name Name of the tool to call
|
|
125
|
+
* @param args Arguments for the tool
|
|
126
|
+
* @returns Tool execution result
|
|
127
|
+
*/
|
|
128
|
+
export async function handleToolCall(name, args) {
|
|
129
|
+
try {
|
|
130
|
+
switch (name) {
|
|
131
|
+
case "read_query":
|
|
132
|
+
return await readQuery(args.query);
|
|
133
|
+
case "write_query":
|
|
134
|
+
return await writeQuery(args.query);
|
|
135
|
+
case "create_table":
|
|
136
|
+
return await createTable(args.query);
|
|
137
|
+
case "alter_table":
|
|
138
|
+
return await alterTable(args.query);
|
|
139
|
+
case "drop_table":
|
|
140
|
+
return await dropTable(args.table_name, args.confirm);
|
|
141
|
+
case "export_query":
|
|
142
|
+
return await exportQuery(args.query, args.format);
|
|
143
|
+
case "list_tables":
|
|
144
|
+
return await listTables();
|
|
145
|
+
case "describe_table":
|
|
146
|
+
return await describeTable(args.table_name);
|
|
147
|
+
case "append_insight":
|
|
148
|
+
return await appendInsight(args.insight);
|
|
149
|
+
case "list_insights":
|
|
150
|
+
return await listInsights();
|
|
151
|
+
default:
|
|
152
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
return formatErrorResponse(error);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
// Import database utils
|
|
6
|
+
import { initDatabase, closeDatabase, getDatabaseMetadata } from './db/index.js';
|
|
7
|
+
// Import handlers
|
|
8
|
+
import { handleListResources, handleReadResource } from './handlers/resourceHandlers.js';
|
|
9
|
+
import { handleListTools, handleToolCall } from './handlers/toolHandlers.js';
|
|
10
|
+
// Setup a logger that uses stderr instead of stdout to avoid interfering with MCP communications
|
|
11
|
+
const logger = {
|
|
12
|
+
log: (...args) => console.error('[INFO]', ...args),
|
|
13
|
+
error: (...args) => console.error('[ERROR]', ...args),
|
|
14
|
+
warn: (...args) => console.error('[WARN]', ...args),
|
|
15
|
+
info: (...args) => console.error('[INFO]', ...args),
|
|
16
|
+
};
|
|
17
|
+
// Configure the server
|
|
18
|
+
const server = new Server({
|
|
19
|
+
name: "executeautomation/database-server",
|
|
20
|
+
version: "1.1.0",
|
|
21
|
+
}, {
|
|
22
|
+
capabilities: {
|
|
23
|
+
resources: {},
|
|
24
|
+
tools: {},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
// Parse command line arguments
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
if (args.length === 0) {
|
|
30
|
+
logger.error("Please provide database connection information");
|
|
31
|
+
logger.error("Usage for SQLite: node index.js <database_file_path>");
|
|
32
|
+
logger.error("Usage for SQL Server: node index.js --sqlserver --server <server> --database <database> [--user <user> --password <password>]");
|
|
33
|
+
logger.error("Usage for PostgreSQL: node index.js --postgresql --host <host> --database <database> [--user <user> --password <password> --port <port>]");
|
|
34
|
+
logger.error("Usage for MySQL: node index.js --mysql --host <host> --database <database> [--user <user> --password <password> --port <port>]");
|
|
35
|
+
logger.error("Usage for MySQL with AWS IAM: node index.js --mysql --aws-iam-auth --host <rds-endpoint> --database <database> --user <aws-username> --aws-region <region>");
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
// Parse arguments to determine database type and connection info
|
|
39
|
+
let dbType = 'sqlite';
|
|
40
|
+
let connectionInfo = null;
|
|
41
|
+
// Check if using SQL Server
|
|
42
|
+
if (args.includes('--sqlserver')) {
|
|
43
|
+
dbType = 'sqlserver';
|
|
44
|
+
connectionInfo = {
|
|
45
|
+
server: '',
|
|
46
|
+
database: '',
|
|
47
|
+
user: undefined,
|
|
48
|
+
password: undefined
|
|
49
|
+
};
|
|
50
|
+
// Parse SQL Server connection parameters
|
|
51
|
+
for (let i = 0; i < args.length; i++) {
|
|
52
|
+
if (args[i] === '--server' && i + 1 < args.length) {
|
|
53
|
+
connectionInfo.server = args[i + 1];
|
|
54
|
+
}
|
|
55
|
+
else if (args[i] === '--database' && i + 1 < args.length) {
|
|
56
|
+
connectionInfo.database = args[i + 1];
|
|
57
|
+
}
|
|
58
|
+
else if (args[i] === '--user' && i + 1 < args.length) {
|
|
59
|
+
connectionInfo.user = args[i + 1];
|
|
60
|
+
}
|
|
61
|
+
else if (args[i] === '--password' && i + 1 < args.length) {
|
|
62
|
+
connectionInfo.password = args[i + 1];
|
|
63
|
+
}
|
|
64
|
+
else if (args[i] === '--port' && i + 1 < args.length) {
|
|
65
|
+
connectionInfo.port = parseInt(args[i + 1], 10);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Validate SQL Server connection info
|
|
69
|
+
if (!connectionInfo.server || !connectionInfo.database) {
|
|
70
|
+
logger.error("Error: SQL Server requires --server and --database parameters");
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Check if using PostgreSQL
|
|
75
|
+
else if (args.includes('--postgresql') || args.includes('--postgres')) {
|
|
76
|
+
dbType = 'postgresql';
|
|
77
|
+
connectionInfo = {
|
|
78
|
+
host: '',
|
|
79
|
+
database: '',
|
|
80
|
+
user: undefined,
|
|
81
|
+
password: undefined,
|
|
82
|
+
port: undefined,
|
|
83
|
+
ssl: undefined,
|
|
84
|
+
connectionTimeout: undefined
|
|
85
|
+
};
|
|
86
|
+
// Parse PostgreSQL connection parameters
|
|
87
|
+
for (let i = 0; i < args.length; i++) {
|
|
88
|
+
if (args[i] === '--host' && i + 1 < args.length) {
|
|
89
|
+
connectionInfo.host = args[i + 1];
|
|
90
|
+
}
|
|
91
|
+
else if (args[i] === '--database' && i + 1 < args.length) {
|
|
92
|
+
connectionInfo.database = args[i + 1];
|
|
93
|
+
}
|
|
94
|
+
else if (args[i] === '--user' && i + 1 < args.length) {
|
|
95
|
+
connectionInfo.user = args[i + 1];
|
|
96
|
+
}
|
|
97
|
+
else if (args[i] === '--password' && i + 1 < args.length) {
|
|
98
|
+
connectionInfo.password = args[i + 1];
|
|
99
|
+
}
|
|
100
|
+
else if (args[i] === '--port' && i + 1 < args.length) {
|
|
101
|
+
connectionInfo.port = parseInt(args[i + 1], 10);
|
|
102
|
+
}
|
|
103
|
+
else if (args[i] === '--ssl' && i + 1 < args.length) {
|
|
104
|
+
connectionInfo.ssl = args[i + 1] === 'true';
|
|
105
|
+
}
|
|
106
|
+
else if (args[i] === '--connection-timeout' && i + 1 < args.length) {
|
|
107
|
+
connectionInfo.connectionTimeout = parseInt(args[i + 1], 10);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Validate PostgreSQL connection info
|
|
111
|
+
if (!connectionInfo.host || !connectionInfo.database) {
|
|
112
|
+
logger.error("Error: PostgreSQL requires --host and --database parameters");
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Check if using MySQL
|
|
117
|
+
else if (args.includes('--mysql')) {
|
|
118
|
+
dbType = 'mysql';
|
|
119
|
+
connectionInfo = {
|
|
120
|
+
host: '',
|
|
121
|
+
database: '',
|
|
122
|
+
user: undefined,
|
|
123
|
+
password: undefined,
|
|
124
|
+
port: undefined,
|
|
125
|
+
ssl: undefined,
|
|
126
|
+
connectionTimeout: undefined,
|
|
127
|
+
awsIamAuth: false,
|
|
128
|
+
awsRegion: undefined
|
|
129
|
+
};
|
|
130
|
+
// Parse MySQL connection parameters
|
|
131
|
+
for (let i = 0; i < args.length; i++) {
|
|
132
|
+
if (args[i] === '--host' && i + 1 < args.length) {
|
|
133
|
+
connectionInfo.host = args[i + 1];
|
|
134
|
+
}
|
|
135
|
+
else if (args[i] === '--database' && i + 1 < args.length) {
|
|
136
|
+
connectionInfo.database = args[i + 1];
|
|
137
|
+
}
|
|
138
|
+
else if (args[i] === '--user' && i + 1 < args.length) {
|
|
139
|
+
connectionInfo.user = args[i + 1];
|
|
140
|
+
}
|
|
141
|
+
else if (args[i] === '--password' && i + 1 < args.length) {
|
|
142
|
+
connectionInfo.password = args[i + 1];
|
|
143
|
+
}
|
|
144
|
+
else if (args[i] === '--port' && i + 1 < args.length) {
|
|
145
|
+
connectionInfo.port = parseInt(args[i + 1], 10);
|
|
146
|
+
}
|
|
147
|
+
else if (args[i] === '--ssl' && i + 1 < args.length) {
|
|
148
|
+
const sslVal = args[i + 1];
|
|
149
|
+
if (sslVal === 'true')
|
|
150
|
+
connectionInfo.ssl = true;
|
|
151
|
+
else if (sslVal === 'false')
|
|
152
|
+
connectionInfo.ssl = false;
|
|
153
|
+
else
|
|
154
|
+
connectionInfo.ssl = sslVal;
|
|
155
|
+
}
|
|
156
|
+
else if (args[i] === '--connection-timeout' && i + 1 < args.length) {
|
|
157
|
+
connectionInfo.connectionTimeout = parseInt(args[i + 1], 10);
|
|
158
|
+
}
|
|
159
|
+
else if (args[i] === '--aws-iam-auth') {
|
|
160
|
+
connectionInfo.awsIamAuth = true;
|
|
161
|
+
}
|
|
162
|
+
else if (args[i] === '--aws-region' && i + 1 < args.length) {
|
|
163
|
+
connectionInfo.awsRegion = args[i + 1];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Validate MySQL connection info
|
|
167
|
+
if (!connectionInfo.host || !connectionInfo.database) {
|
|
168
|
+
logger.error("Error: MySQL requires --host and --database parameters");
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
// Additional validation for AWS IAM authentication
|
|
172
|
+
if (connectionInfo.awsIamAuth) {
|
|
173
|
+
if (!connectionInfo.user) {
|
|
174
|
+
logger.error("Error: AWS IAM authentication requires --user parameter");
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
if (!connectionInfo.awsRegion) {
|
|
178
|
+
logger.error("Error: AWS IAM authentication requires --aws-region parameter");
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
// Automatically enable SSL for AWS IAM authentication (required)
|
|
182
|
+
connectionInfo.ssl = true;
|
|
183
|
+
logger.info("AWS IAM authentication enabled - SSL automatically configured");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
// SQLite mode (default)
|
|
188
|
+
dbType = 'sqlite';
|
|
189
|
+
connectionInfo = args[0]; // First argument is the SQLite file path
|
|
190
|
+
logger.info(`Using SQLite database at path: ${connectionInfo}`);
|
|
191
|
+
}
|
|
192
|
+
// Set up request handlers
|
|
193
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
194
|
+
return await handleListResources();
|
|
195
|
+
});
|
|
196
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
197
|
+
return await handleReadResource(request.params.uri);
|
|
198
|
+
});
|
|
199
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
200
|
+
return handleListTools();
|
|
201
|
+
});
|
|
202
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
203
|
+
return await handleToolCall(request.params.name, request.params.arguments);
|
|
204
|
+
});
|
|
205
|
+
// Handle shutdown gracefully
|
|
206
|
+
process.on('SIGINT', async () => {
|
|
207
|
+
logger.info('Shutting down gracefully...');
|
|
208
|
+
await closeDatabase();
|
|
209
|
+
process.exit(0);
|
|
210
|
+
});
|
|
211
|
+
process.on('SIGTERM', async () => {
|
|
212
|
+
logger.info('Shutting down gracefully...');
|
|
213
|
+
await closeDatabase();
|
|
214
|
+
process.exit(0);
|
|
215
|
+
});
|
|
216
|
+
// Add global error handler
|
|
217
|
+
process.on('uncaughtException', (error) => {
|
|
218
|
+
logger.error('Uncaught exception:', error);
|
|
219
|
+
});
|
|
220
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
221
|
+
logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
222
|
+
});
|
|
223
|
+
/**
|
|
224
|
+
* Start the server
|
|
225
|
+
*/
|
|
226
|
+
async function runServer() {
|
|
227
|
+
try {
|
|
228
|
+
logger.info(`Initializing ${dbType} database...`);
|
|
229
|
+
if (dbType === 'sqlite') {
|
|
230
|
+
logger.info(`Database path: ${connectionInfo}`);
|
|
231
|
+
}
|
|
232
|
+
else if (dbType === 'sqlserver') {
|
|
233
|
+
logger.info(`Server: ${connectionInfo.server}, Database: ${connectionInfo.database}`);
|
|
234
|
+
}
|
|
235
|
+
else if (dbType === 'postgresql') {
|
|
236
|
+
logger.info(`Host: ${connectionInfo.host}, Database: ${connectionInfo.database}`);
|
|
237
|
+
}
|
|
238
|
+
else if (dbType === 'mysql') {
|
|
239
|
+
logger.info(`Host: ${connectionInfo.host}, Database: ${connectionInfo.database}`);
|
|
240
|
+
}
|
|
241
|
+
// Initialize the database
|
|
242
|
+
await initDatabase(connectionInfo, dbType);
|
|
243
|
+
const dbInfo = getDatabaseMetadata();
|
|
244
|
+
logger.info(`Connected to ${dbInfo.name} database`);
|
|
245
|
+
logger.info('Starting MCP server...');
|
|
246
|
+
const transport = new StdioServerTransport();
|
|
247
|
+
await server.connect(transport);
|
|
248
|
+
logger.info('Server running. Press Ctrl+C to exit.');
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
logger.error("Failed to initialize:", error);
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Start the server
|
|
256
|
+
runServer().catch(error => {
|
|
257
|
+
logger.error("Server initialization failed:", error);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
});
|