@bytebase/dbhub 0.0.5 → 0.0.6
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/dist/connectors/sqlserver/index.js +186 -0
- package/dist/index.js +1 -0
- package/package.json +6 -2
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import sql from 'mssql';
|
|
2
|
+
import { ConnectorRegistry } from '../interface.js';
|
|
3
|
+
/**
|
|
4
|
+
* SQL Server DSN parser
|
|
5
|
+
* Expected format: mssql://username:password@host:port/database
|
|
6
|
+
*/
|
|
7
|
+
export class SQLServerDSNParser {
|
|
8
|
+
parse(dsn) {
|
|
9
|
+
// Remove the protocol prefix
|
|
10
|
+
if (!this.isValidDSN(dsn)) {
|
|
11
|
+
throw new Error('Invalid SQL Server DSN format. Expected: mssql://username:password@host:port/database');
|
|
12
|
+
}
|
|
13
|
+
// Parse the DSN
|
|
14
|
+
const url = new URL(dsn);
|
|
15
|
+
const host = url.hostname;
|
|
16
|
+
const port = url.port ? parseInt(url.port, 10) : 1433; // Default SQL Server port
|
|
17
|
+
const database = url.pathname.substring(1); // Remove leading slash
|
|
18
|
+
const user = url.username;
|
|
19
|
+
const password = url.password;
|
|
20
|
+
// Parse additional options from query parameters
|
|
21
|
+
const options = {};
|
|
22
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
23
|
+
if (key === 'encrypt') {
|
|
24
|
+
options.encrypt = value;
|
|
25
|
+
}
|
|
26
|
+
else if (key === 'trustServerCertificate') {
|
|
27
|
+
options.trustServerCertificate = value === 'true';
|
|
28
|
+
}
|
|
29
|
+
else if (key === 'connectTimeout') {
|
|
30
|
+
options.connectTimeout = parseInt(value, 10);
|
|
31
|
+
}
|
|
32
|
+
else if (key === 'requestTimeout') {
|
|
33
|
+
options.requestTimeout = parseInt(value, 10);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Construct and return the config
|
|
37
|
+
return {
|
|
38
|
+
user,
|
|
39
|
+
password,
|
|
40
|
+
server: host,
|
|
41
|
+
port,
|
|
42
|
+
database,
|
|
43
|
+
options: {
|
|
44
|
+
encrypt: options.encrypt ?? true, // Default to encrypted connection
|
|
45
|
+
trustServerCertificate: options.trustServerCertificate === true, // Need explicit conversion to boolean
|
|
46
|
+
connectTimeout: options.connectTimeout ?? 15000,
|
|
47
|
+
requestTimeout: options.requestTimeout ?? 15000,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
getSampleDSN() {
|
|
52
|
+
return 'mssql://username:password@localhost:1433/database?encrypt=true';
|
|
53
|
+
}
|
|
54
|
+
isValidDSN(dsn) {
|
|
55
|
+
try {
|
|
56
|
+
const url = new URL(dsn);
|
|
57
|
+
return url.protocol === 'mssql:';
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* SQL Server connector
|
|
66
|
+
*/
|
|
67
|
+
export class SQLServerConnector {
|
|
68
|
+
constructor() {
|
|
69
|
+
this.id = 'sqlserver';
|
|
70
|
+
this.name = 'SQL Server';
|
|
71
|
+
this.dsnParser = new SQLServerDSNParser();
|
|
72
|
+
}
|
|
73
|
+
async connect(dsn) {
|
|
74
|
+
try {
|
|
75
|
+
this.config = this.dsnParser.parse(dsn);
|
|
76
|
+
if (!this.config.options) {
|
|
77
|
+
this.config.options = {};
|
|
78
|
+
}
|
|
79
|
+
this.connection = await new sql.ConnectionPool(this.config).connect();
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async disconnect() {
|
|
86
|
+
if (this.connection) {
|
|
87
|
+
await this.connection.close();
|
|
88
|
+
this.connection = undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async getTables() {
|
|
92
|
+
if (!this.connection) {
|
|
93
|
+
throw new Error('Not connected to SQL Server database');
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const result = await this.connection.request().query(`
|
|
97
|
+
SELECT TABLE_NAME
|
|
98
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
99
|
+
ORDER BY TABLE_NAME
|
|
100
|
+
`);
|
|
101
|
+
return result.recordset.map((row) => row.TABLE_NAME);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
throw new Error(`Failed to get tables: ${error.message}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async tableExists(tableName) {
|
|
108
|
+
if (!this.connection) {
|
|
109
|
+
throw new Error('Not connected to SQL Server database');
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const result = await this.connection.request()
|
|
113
|
+
.input('tableName', sql.VarChar, tableName)
|
|
114
|
+
.query(`
|
|
115
|
+
SELECT COUNT(*) as count
|
|
116
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
117
|
+
WHERE TABLE_NAME = @tableName
|
|
118
|
+
`);
|
|
119
|
+
return result.recordset[0].count > 0;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
throw new Error(`Failed to check if table exists: ${error.message}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async getTableSchema(tableName) {
|
|
126
|
+
if (!this.connection) {
|
|
127
|
+
throw new Error('Not connected to SQL Server database');
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const result = await this.connection.request()
|
|
131
|
+
.input('tableName', sql.VarChar, tableName)
|
|
132
|
+
.query(`
|
|
133
|
+
SELECT
|
|
134
|
+
COLUMN_NAME as column_name,
|
|
135
|
+
DATA_TYPE as data_type,
|
|
136
|
+
IS_NULLABLE as is_nullable,
|
|
137
|
+
COLUMN_DEFAULT as column_default
|
|
138
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
139
|
+
WHERE TABLE_NAME = @tableName
|
|
140
|
+
ORDER BY ORDINAL_POSITION
|
|
141
|
+
`);
|
|
142
|
+
return result.recordset;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
throw new Error(`Failed to get schema for table ${tableName}: ${error.message}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async executeQuery(query) {
|
|
149
|
+
if (!this.connection) {
|
|
150
|
+
throw new Error('Not connected to SQL Server database');
|
|
151
|
+
}
|
|
152
|
+
const safetyCheck = this.validateQuery(query);
|
|
153
|
+
if (!safetyCheck.isValid) {
|
|
154
|
+
throw new Error(safetyCheck.message || "Query validation failed");
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const result = await this.connection.request().query(query);
|
|
158
|
+
return {
|
|
159
|
+
rows: result.recordset || [],
|
|
160
|
+
fields: result.recordset && result.recordset.length > 0
|
|
161
|
+
? Object.keys(result.recordset[0]).map(key => ({
|
|
162
|
+
name: key,
|
|
163
|
+
}))
|
|
164
|
+
: [],
|
|
165
|
+
rowCount: result.rowsAffected[0] || 0,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
throw new Error(`Failed to execute query: ${error.message}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
validateQuery(query) {
|
|
173
|
+
// Basic check to prevent non-SELECT queries
|
|
174
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
175
|
+
if (!normalizedQuery.startsWith('select')) {
|
|
176
|
+
return {
|
|
177
|
+
isValid: false,
|
|
178
|
+
message: "Only SELECT queries are allowed for security reasons."
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return { isValid: true };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Create and register the connector
|
|
185
|
+
const sqlServerConnector = new SQLServerConnector();
|
|
186
|
+
ConnectorRegistry.register(sqlServerConnector);
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Import connector modules to register them
|
|
3
3
|
import './connectors/postgres/index.js'; // Register PostgreSQL connector
|
|
4
|
+
import './connectors/sqlserver/index.js'; // Register SQL Server connector
|
|
4
5
|
// import './connectors/sqlite/index.js'; // Uncomment to enable SQLite
|
|
5
6
|
// Import main function from server.ts
|
|
6
7
|
import { main } from './server.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytebase/dbhub",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Universal Database MCP Server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -19,13 +19,16 @@
|
|
|
19
19
|
"@modelcontextprotocol/sdk": "^1.6.1",
|
|
20
20
|
"dotenv": "^16.4.7",
|
|
21
21
|
"express": "^4.18.2",
|
|
22
|
+
"mssql": "^11.0.1",
|
|
22
23
|
"pg": "^8.13.3",
|
|
23
24
|
"zod": "^3.24.2"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
27
|
"@types/express": "^4.17.21",
|
|
28
|
+
"@types/mssql": "^9.1.7",
|
|
27
29
|
"@types/node": "^22.13.10",
|
|
28
30
|
"@types/pg": "^8.11.11",
|
|
31
|
+
"cross-env": "^7.0.3",
|
|
29
32
|
"ts-node": "^10.9.2",
|
|
30
33
|
"tsx": "^4.19.3",
|
|
31
34
|
"typescript": "^5.8.2"
|
|
@@ -45,6 +48,7 @@
|
|
|
45
48
|
"scripts": {
|
|
46
49
|
"build": "tsc",
|
|
47
50
|
"start": "node dist/index.js",
|
|
48
|
-
"dev": "NODE_ENV=development tsx src/index.ts"
|
|
51
|
+
"dev": "NODE_ENV=development tsx src/index.ts",
|
|
52
|
+
"crossdev": "cross-env NODE_ENV=development tsx src/index.ts"
|
|
49
53
|
}
|
|
50
54
|
}
|