@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.
@@ -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.5",
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
  }