@bytebase/dbhub 0.0.8 → 0.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 +34 -33
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1489 -13
- package/dist/resources/employee-sqlite/employee.sql +117 -0
- package/dist/resources/employee-sqlite/load_department.sql +10 -0
- package/dist/resources/employee-sqlite/load_dept_emp.sql +1103 -0
- package/dist/resources/employee-sqlite/load_dept_manager.sql +17 -0
- package/dist/resources/employee-sqlite/load_employee.sql +1000 -0
- package/dist/resources/employee-sqlite/load_salary1.sql +9488 -0
- package/dist/resources/employee-sqlite/load_title.sql +1470 -0
- package/dist/resources/employee-sqlite/object.sql +74 -0
- package/dist/resources/employee-sqlite/show_elapsed.sql +4 -0
- package/dist/resources/employee-sqlite/test_employee_md5.sql +119 -0
- package/package.json +5 -4
- package/dist/config/demo-loader.js +0 -45
- package/dist/config/env.js +0 -146
- package/dist/connectors/interface.js +0 -55
- package/dist/connectors/manager.js +0 -91
- package/dist/connectors/mysql/index.js +0 -169
- package/dist/connectors/postgres/index.js +0 -172
- package/dist/connectors/sqlite/index.js +0 -208
- package/dist/connectors/sqlserver/index.js +0 -186
- package/dist/prompts/db-explainer.js +0 -201
- package/dist/prompts/index.js +0 -11
- package/dist/prompts/sql-generator.js +0 -114
- package/dist/resources/index.js +0 -12
- package/dist/resources/schema.js +0 -36
- package/dist/resources/tables.js +0 -17
- package/dist/server.js +0 -140
- package/dist/tools/index.js +0 -11
- package/dist/tools/list-connectors.js +0 -23
- package/dist/tools/run-query.js +0 -32
- package/dist/utils/response-formatter.js +0 -109
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
-- SQLite implementation of views and functions
|
|
2
|
+
-- This is simplified compared to the MySQL version
|
|
3
|
+
|
|
4
|
+
-- Drop views if they exist
|
|
5
|
+
DROP VIEW IF EXISTS v_full_employee;
|
|
6
|
+
DROP VIEW IF EXISTS v_full_department;
|
|
7
|
+
DROP VIEW IF EXISTS emp_dept_current;
|
|
8
|
+
|
|
9
|
+
-- Create helper view to get current department for employees
|
|
10
|
+
CREATE VIEW emp_dept_current AS
|
|
11
|
+
SELECT
|
|
12
|
+
e.emp_no,
|
|
13
|
+
de.dept_no
|
|
14
|
+
FROM
|
|
15
|
+
employee e
|
|
16
|
+
JOIN
|
|
17
|
+
dept_emp de ON e.emp_no = de.emp_no
|
|
18
|
+
JOIN (
|
|
19
|
+
SELECT
|
|
20
|
+
emp_no,
|
|
21
|
+
MAX(from_date) AS max_from_date
|
|
22
|
+
FROM
|
|
23
|
+
dept_emp
|
|
24
|
+
GROUP BY
|
|
25
|
+
emp_no
|
|
26
|
+
) latest ON de.emp_no = latest.emp_no AND de.from_date = latest.max_from_date;
|
|
27
|
+
|
|
28
|
+
-- View that shows employee with their current department name
|
|
29
|
+
CREATE VIEW v_full_employee AS
|
|
30
|
+
SELECT
|
|
31
|
+
e.emp_no,
|
|
32
|
+
e.first_name,
|
|
33
|
+
e.last_name,
|
|
34
|
+
e.birth_date,
|
|
35
|
+
e.gender,
|
|
36
|
+
e.hire_date,
|
|
37
|
+
d.dept_name AS department
|
|
38
|
+
FROM
|
|
39
|
+
employee e
|
|
40
|
+
LEFT JOIN
|
|
41
|
+
emp_dept_current edc ON e.emp_no = edc.emp_no
|
|
42
|
+
LEFT JOIN
|
|
43
|
+
department d ON edc.dept_no = d.dept_no;
|
|
44
|
+
|
|
45
|
+
-- View to get current managers for departments
|
|
46
|
+
CREATE VIEW current_managers AS
|
|
47
|
+
SELECT
|
|
48
|
+
d.dept_no,
|
|
49
|
+
d.dept_name,
|
|
50
|
+
e.first_name || ' ' || e.last_name AS manager
|
|
51
|
+
FROM
|
|
52
|
+
department d
|
|
53
|
+
LEFT JOIN
|
|
54
|
+
dept_manager dm ON d.dept_no = dm.dept_no
|
|
55
|
+
JOIN (
|
|
56
|
+
SELECT
|
|
57
|
+
dept_no,
|
|
58
|
+
MAX(from_date) AS max_from_date
|
|
59
|
+
FROM
|
|
60
|
+
dept_manager
|
|
61
|
+
GROUP BY
|
|
62
|
+
dept_no
|
|
63
|
+
) latest ON dm.dept_no = latest.dept_no AND dm.from_date = latest.max_from_date
|
|
64
|
+
LEFT JOIN
|
|
65
|
+
employee e ON dm.emp_no = e.emp_no;
|
|
66
|
+
|
|
67
|
+
-- Create a view showing departments with their managers
|
|
68
|
+
CREATE VIEW v_full_department AS
|
|
69
|
+
SELECT
|
|
70
|
+
dept_no,
|
|
71
|
+
dept_name,
|
|
72
|
+
manager
|
|
73
|
+
FROM
|
|
74
|
+
current_managers;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
-- Sample employee database
|
|
2
|
+
-- See changelog table for details
|
|
3
|
+
-- Copyright (C) 2007,2008, MySQL AB
|
|
4
|
+
--
|
|
5
|
+
-- Original data created by Fusheng Wang and Carlo Zaniolo
|
|
6
|
+
-- http://www.cs.aau.dk/TimeCenter/software.htm
|
|
7
|
+
-- http://www.cs.aau.dk/TimeCenter/Data/employeeTemporalDataSet.zip
|
|
8
|
+
--
|
|
9
|
+
-- Current schema by Giuseppe Maxia
|
|
10
|
+
-- Data conversion from XML to relational by Patrick Crews
|
|
11
|
+
-- SQLite adaptation by Claude Code
|
|
12
|
+
--
|
|
13
|
+
-- This work is licensed under the
|
|
14
|
+
-- Creative Commons Attribution-Share Alike 3.0 Unported License.
|
|
15
|
+
-- To view a copy of this license, visit
|
|
16
|
+
-- http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to
|
|
17
|
+
-- Creative Commons, 171 Second Street, Suite 300, San Francisco,
|
|
18
|
+
-- California, 94105, USA.
|
|
19
|
+
--
|
|
20
|
+
-- DISCLAIMER
|
|
21
|
+
-- To the best of our knowledge, this data is fabricated, and
|
|
22
|
+
-- it does not correspond to real people.
|
|
23
|
+
-- Any similarity to existing people is purely coincidental.
|
|
24
|
+
--
|
|
25
|
+
|
|
26
|
+
SELECT 'TESTING INSTALLATION' as 'INFO';
|
|
27
|
+
|
|
28
|
+
DROP TABLE IF EXISTS expected_value;
|
|
29
|
+
DROP TABLE IF EXISTS found_value;
|
|
30
|
+
|
|
31
|
+
CREATE TABLE expected_value (
|
|
32
|
+
table_name TEXT NOT NULL PRIMARY KEY,
|
|
33
|
+
recs INTEGER NOT NULL,
|
|
34
|
+
crc_md5 TEXT NOT NULL
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CREATE TABLE found_value (
|
|
38
|
+
table_name TEXT NOT NULL PRIMARY KEY,
|
|
39
|
+
recs INTEGER NOT NULL,
|
|
40
|
+
crc_md5 TEXT NOT NULL
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
INSERT INTO expected_value VALUES
|
|
44
|
+
('employee', 1000, '595460127fb609c2b110b1796083e242'),
|
|
45
|
+
('department', 9, 'd1af5e170d2d1591d776d5638d71fc5f'),
|
|
46
|
+
('dept_manager', 16, '8ff425d5ad6dc56975998d1893b8dca9'),
|
|
47
|
+
('dept_emp', 1103, 'e302aa5b56a69b49e40eb0d60674addc'),
|
|
48
|
+
('title', 1470, 'ba77dd331ce00f76c1643a7d73cdcee6'),
|
|
49
|
+
('salary', 9488, '61f22cfece4d34f5bb94c9f05a3da3ef');
|
|
50
|
+
|
|
51
|
+
SELECT table_name, recs AS expected_record, crc_md5 AS expected_crc FROM expected_value;
|
|
52
|
+
|
|
53
|
+
DROP TABLE IF EXISTS tchecksum;
|
|
54
|
+
CREATE TABLE tchecksum (chk TEXT);
|
|
55
|
+
|
|
56
|
+
-- For SQLite, we need to use a different approach for MD5 calculation
|
|
57
|
+
-- Insert employee checksums
|
|
58
|
+
INSERT INTO found_value
|
|
59
|
+
SELECT 'employee', COUNT(*),
|
|
60
|
+
(SELECT hex(md5(group_concat(emp_no||birth_date||first_name||last_name||gender||hire_date, '#')))
|
|
61
|
+
FROM (SELECT * FROM employee ORDER BY emp_no))
|
|
62
|
+
FROM employee;
|
|
63
|
+
|
|
64
|
+
-- Insert department checksums
|
|
65
|
+
INSERT INTO found_value
|
|
66
|
+
SELECT 'department', COUNT(*),
|
|
67
|
+
(SELECT hex(md5(group_concat(dept_no||dept_name, '#')))
|
|
68
|
+
FROM (SELECT * FROM department ORDER BY dept_no))
|
|
69
|
+
FROM department;
|
|
70
|
+
|
|
71
|
+
-- Insert dept_manager checksums
|
|
72
|
+
INSERT INTO found_value
|
|
73
|
+
SELECT 'dept_manager', COUNT(*),
|
|
74
|
+
(SELECT hex(md5(group_concat(dept_no||emp_no||from_date||to_date, '#')))
|
|
75
|
+
FROM (SELECT * FROM dept_manager ORDER BY dept_no, emp_no))
|
|
76
|
+
FROM dept_manager;
|
|
77
|
+
|
|
78
|
+
-- Insert dept_emp checksums
|
|
79
|
+
INSERT INTO found_value
|
|
80
|
+
SELECT 'dept_emp', COUNT(*),
|
|
81
|
+
(SELECT hex(md5(group_concat(dept_no||emp_no||from_date||to_date, '#')))
|
|
82
|
+
FROM (SELECT * FROM dept_emp ORDER BY dept_no, emp_no))
|
|
83
|
+
FROM dept_emp;
|
|
84
|
+
|
|
85
|
+
-- Insert title checksums
|
|
86
|
+
INSERT INTO found_value
|
|
87
|
+
SELECT 'title', COUNT(*),
|
|
88
|
+
(SELECT hex(md5(group_concat(emp_no||title||from_date||IFNULL(to_date,''), '#')))
|
|
89
|
+
FROM (SELECT * FROM title ORDER BY emp_no, title, from_date))
|
|
90
|
+
FROM title;
|
|
91
|
+
|
|
92
|
+
-- Insert salary checksums
|
|
93
|
+
INSERT INTO found_value
|
|
94
|
+
SELECT 'salary', COUNT(*),
|
|
95
|
+
(SELECT hex(md5(group_concat(emp_no||amount||from_date||to_date, '#')))
|
|
96
|
+
FROM (SELECT * FROM salary ORDER BY emp_no, from_date, to_date))
|
|
97
|
+
FROM salary;
|
|
98
|
+
|
|
99
|
+
SELECT table_name, recs as 'found_records', crc_md5 as found_crc FROM found_value;
|
|
100
|
+
|
|
101
|
+
-- Compare expected vs found
|
|
102
|
+
SELECT
|
|
103
|
+
e.table_name,
|
|
104
|
+
CASE WHEN e.recs=f.recs THEN 'OK' ELSE 'not ok' END AS records_match,
|
|
105
|
+
CASE WHEN e.crc_md5=f.crc_md5 THEN 'ok' ELSE 'not ok' END AS crc_match
|
|
106
|
+
FROM
|
|
107
|
+
expected_value e
|
|
108
|
+
JOIN found_value f USING (table_name);
|
|
109
|
+
|
|
110
|
+
-- Check for failures
|
|
111
|
+
SELECT
|
|
112
|
+
'CRC' as summary,
|
|
113
|
+
CASE WHEN (SELECT COUNT(*) FROM expected_value e JOIN found_value f USING(table_name) WHERE f.crc_md5 != e.crc_md5) = 0
|
|
114
|
+
THEN 'OK' ELSE 'FAIL' END as 'result'
|
|
115
|
+
UNION ALL
|
|
116
|
+
SELECT
|
|
117
|
+
'count',
|
|
118
|
+
CASE WHEN (SELECT COUNT(*) FROM expected_value e JOIN found_value f USING(table_name) WHERE f.recs != e.recs) = 0
|
|
119
|
+
THEN 'OK' ELSE 'FAIL' END;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytebase/dbhub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Universal Database MCP Server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -17,22 +17,23 @@
|
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@modelcontextprotocol/sdk": "^1.6.1",
|
|
20
|
-
"
|
|
20
|
+
"better-sqlite3": "^11.9.0",
|
|
21
21
|
"dotenv": "^16.4.7",
|
|
22
22
|
"express": "^4.18.2",
|
|
23
23
|
"mssql": "^11.0.1",
|
|
24
24
|
"mysql2": "^3.13.0",
|
|
25
25
|
"pg": "^8.13.3",
|
|
26
|
-
"sqlite3": "^5.1.7",
|
|
27
26
|
"zod": "^3.24.2"
|
|
28
27
|
},
|
|
29
28
|
"devDependencies": {
|
|
29
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
30
30
|
"@types/express": "^4.17.21",
|
|
31
31
|
"@types/mssql": "^9.1.7",
|
|
32
32
|
"@types/node": "^22.13.10",
|
|
33
33
|
"@types/pg": "^8.11.11",
|
|
34
34
|
"cross-env": "^7.0.3",
|
|
35
35
|
"ts-node": "^10.9.2",
|
|
36
|
+
"tsup": "^8.4.0",
|
|
36
37
|
"tsx": "^4.19.3",
|
|
37
38
|
"typescript": "^5.8.2"
|
|
38
39
|
},
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
"src/**/*"
|
|
50
51
|
],
|
|
51
52
|
"scripts": {
|
|
52
|
-
"build": "
|
|
53
|
+
"build": "tsup",
|
|
53
54
|
"start": "node dist/index.js",
|
|
54
55
|
"dev": "NODE_ENV=development tsx src/index.ts",
|
|
55
56
|
"crossdev": "cross-env NODE_ENV=development tsx src/index.ts"
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Demo data loader for SQLite in-memory database
|
|
3
|
-
*
|
|
4
|
-
* This module loads the sample employee database into the SQLite in-memory database
|
|
5
|
-
* when the --demo flag is specified.
|
|
6
|
-
*/
|
|
7
|
-
import fs from 'fs';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
import { fileURLToPath } from 'url';
|
|
10
|
-
// Create __dirname equivalent for ES modules
|
|
11
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
-
const __dirname = path.dirname(__filename);
|
|
13
|
-
// Path to sample data files
|
|
14
|
-
const DEMO_DATA_DIR = path.join(__dirname, '..', '..', 'resources', 'employee-sqlite');
|
|
15
|
-
/**
|
|
16
|
-
* Load SQL file contents
|
|
17
|
-
*/
|
|
18
|
-
export function loadSqlFile(fileName) {
|
|
19
|
-
const filePath = path.join(DEMO_DATA_DIR, fileName);
|
|
20
|
-
return fs.readFileSync(filePath, 'utf8');
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Get SQLite DSN for in-memory database
|
|
24
|
-
*/
|
|
25
|
-
export function getInMemorySqliteDSN() {
|
|
26
|
-
return 'sqlite::memory:';
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Load SQL files sequentially
|
|
30
|
-
*/
|
|
31
|
-
export function getSqliteInMemorySetupSql() {
|
|
32
|
-
// First, load the schema
|
|
33
|
-
let sql = loadSqlFile('employee.sql');
|
|
34
|
-
// Replace .read directives with the actual file contents
|
|
35
|
-
// This is necessary because in-memory SQLite can't use .read
|
|
36
|
-
const readRegex = /\.read\s+([a-zA-Z0-9_]+\.sql)/g;
|
|
37
|
-
let match;
|
|
38
|
-
while ((match = readRegex.exec(sql)) !== null) {
|
|
39
|
-
const includePath = match[1];
|
|
40
|
-
const includeContent = loadSqlFile(includePath);
|
|
41
|
-
// Replace the .read line with the file contents
|
|
42
|
-
sql = sql.replace(match[0], includeContent);
|
|
43
|
-
}
|
|
44
|
-
return sql;
|
|
45
|
-
}
|
package/dist/config/env.js
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import dotenv from "dotenv";
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
// Create __dirname equivalent for ES modules
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
-
const __dirname = path.dirname(__filename);
|
|
8
|
-
// Parse command line arguments
|
|
9
|
-
export function parseCommandLineArgs() {
|
|
10
|
-
// Check if any args start with '--' (the way tsx passes them)
|
|
11
|
-
const args = process.argv.slice(2);
|
|
12
|
-
const parsedManually = {};
|
|
13
|
-
for (let i = 0; i < args.length; i++) {
|
|
14
|
-
const arg = args[i];
|
|
15
|
-
if (arg.startsWith('--')) {
|
|
16
|
-
const [key, value] = arg.substring(2).split('=');
|
|
17
|
-
if (value) {
|
|
18
|
-
// Handle --key=value format
|
|
19
|
-
parsedManually[key] = value;
|
|
20
|
-
}
|
|
21
|
-
else if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
|
|
22
|
-
// Handle --key value format
|
|
23
|
-
parsedManually[key] = args[i + 1];
|
|
24
|
-
i++; // Skip the next argument as it's the value
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
// Handle --key format (boolean flag)
|
|
28
|
-
parsedManually[key] = 'true';
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
// Just use the manually parsed args - removed parseArgs dependency for Node.js <18.3.0 compatibility
|
|
33
|
-
return parsedManually;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Load environment files from various locations
|
|
37
|
-
* Returns the name of the file that was loaded, or null if none was found
|
|
38
|
-
*/
|
|
39
|
-
export function loadEnvFiles() {
|
|
40
|
-
// Determine if we're in development or production mode
|
|
41
|
-
const isDevelopment = process.env.NODE_ENV === 'development' || process.argv[1]?.includes('tsx');
|
|
42
|
-
// Select environment file names based on environment
|
|
43
|
-
const envFileNames = isDevelopment
|
|
44
|
-
? ['.env.local', '.env'] // In development, try .env.local first, then .env
|
|
45
|
-
: ['.env']; // In production, only look for .env
|
|
46
|
-
// Build paths to check for environment files
|
|
47
|
-
const envPaths = [];
|
|
48
|
-
for (const fileName of envFileNames) {
|
|
49
|
-
envPaths.push(fileName, // Current working directory
|
|
50
|
-
path.join(__dirname, '..', '..', fileName), // Two levels up (src/config -> src -> root)
|
|
51
|
-
path.join(process.cwd(), fileName) // Explicit current working directory
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
// Try to load the first env file found from the prioritized locations
|
|
55
|
-
for (const envPath of envPaths) {
|
|
56
|
-
console.error(`Checking for env file: ${envPath}`);
|
|
57
|
-
if (fs.existsSync(envPath)) {
|
|
58
|
-
dotenv.config({ path: envPath });
|
|
59
|
-
// Return the name of the file that was loaded
|
|
60
|
-
return path.basename(envPath);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Check if demo mode is enabled from command line args
|
|
67
|
-
* Returns true if --demo flag is provided
|
|
68
|
-
*/
|
|
69
|
-
export function isDemoMode() {
|
|
70
|
-
const args = parseCommandLineArgs();
|
|
71
|
-
return args.demo === 'true';
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Resolve DSN from command line args, environment variables, or .env files
|
|
75
|
-
* Returns the DSN and its source, or null if not found
|
|
76
|
-
*/
|
|
77
|
-
export function resolveDSN() {
|
|
78
|
-
// Get command line arguments
|
|
79
|
-
const args = parseCommandLineArgs();
|
|
80
|
-
// Check for demo mode first (highest priority)
|
|
81
|
-
if (isDemoMode()) {
|
|
82
|
-
// Will use in-memory SQLite with demo data
|
|
83
|
-
return {
|
|
84
|
-
dsn: 'sqlite::memory:',
|
|
85
|
-
source: 'demo mode',
|
|
86
|
-
isDemo: true
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
// 1. Check command line arguments
|
|
90
|
-
if (args.dsn) {
|
|
91
|
-
return { dsn: args.dsn, source: 'command line argument' };
|
|
92
|
-
}
|
|
93
|
-
// 2. Check environment variables before loading .env
|
|
94
|
-
if (process.env.DSN) {
|
|
95
|
-
return { dsn: process.env.DSN, source: 'environment variable' };
|
|
96
|
-
}
|
|
97
|
-
// 3. Try loading from .env files
|
|
98
|
-
const loadedEnvFile = loadEnvFiles();
|
|
99
|
-
if (loadedEnvFile && process.env.DSN) {
|
|
100
|
-
return { dsn: process.env.DSN, source: `${loadedEnvFile} file` };
|
|
101
|
-
}
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Resolve transport type from command line args or environment variables
|
|
106
|
-
* Returns 'stdio' or 'sse', with 'stdio' as the default
|
|
107
|
-
*/
|
|
108
|
-
export function resolveTransport() {
|
|
109
|
-
// Get command line arguments
|
|
110
|
-
const args = parseCommandLineArgs();
|
|
111
|
-
// 1. Check command line arguments first (highest priority)
|
|
112
|
-
if (args.transport) {
|
|
113
|
-
const type = args.transport === 'sse' ? 'sse' : 'stdio';
|
|
114
|
-
return { type, source: 'command line argument' };
|
|
115
|
-
}
|
|
116
|
-
// 2. Check environment variables
|
|
117
|
-
if (process.env.TRANSPORT) {
|
|
118
|
-
const type = process.env.TRANSPORT === 'sse' ? 'sse' : 'stdio';
|
|
119
|
-
return { type, source: 'environment variable' };
|
|
120
|
-
}
|
|
121
|
-
// 3. Default to stdio
|
|
122
|
-
return { type: 'stdio', source: 'default' };
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Resolve port from command line args or environment variables
|
|
126
|
-
* Returns port number with 8080 as the default
|
|
127
|
-
*
|
|
128
|
-
* Note: The port option is only applicable when using --transport=sse
|
|
129
|
-
* as it controls the HTTP server port for SSE connections.
|
|
130
|
-
*/
|
|
131
|
-
export function resolvePort() {
|
|
132
|
-
// Get command line arguments
|
|
133
|
-
const args = parseCommandLineArgs();
|
|
134
|
-
// 1. Check command line arguments first (highest priority)
|
|
135
|
-
if (args.port) {
|
|
136
|
-
const port = parseInt(args.port, 10);
|
|
137
|
-
return { port, source: 'command line argument' };
|
|
138
|
-
}
|
|
139
|
-
// 2. Check environment variables
|
|
140
|
-
if (process.env.PORT) {
|
|
141
|
-
const port = parseInt(process.env.PORT, 10);
|
|
142
|
-
return { port, source: 'environment variable' };
|
|
143
|
-
}
|
|
144
|
-
// 3. Default to 8080
|
|
145
|
-
return { port: 8080, source: 'default' };
|
|
146
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Registry for available database connectors
|
|
3
|
-
*/
|
|
4
|
-
export class ConnectorRegistry {
|
|
5
|
-
/**
|
|
6
|
-
* Register a new connector
|
|
7
|
-
*/
|
|
8
|
-
static register(connector) {
|
|
9
|
-
ConnectorRegistry.connectors.set(connector.id, connector);
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Get a connector by ID
|
|
13
|
-
*/
|
|
14
|
-
static getConnector(id) {
|
|
15
|
-
return ConnectorRegistry.connectors.get(id) || null;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Get connector for a DSN string
|
|
19
|
-
* Tries to find a connector that can handle the given DSN format
|
|
20
|
-
*/
|
|
21
|
-
static getConnectorForDSN(dsn) {
|
|
22
|
-
for (const connector of ConnectorRegistry.connectors.values()) {
|
|
23
|
-
if (connector.dsnParser.isValidDSN(dsn)) {
|
|
24
|
-
return connector;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Get all available connector IDs
|
|
31
|
-
*/
|
|
32
|
-
static getAvailableConnectors() {
|
|
33
|
-
return Array.from(ConnectorRegistry.connectors.keys());
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Get sample DSN for a specific connector
|
|
37
|
-
*/
|
|
38
|
-
static getSampleDSN(connectorId) {
|
|
39
|
-
const connector = ConnectorRegistry.getConnector(connectorId);
|
|
40
|
-
if (!connector)
|
|
41
|
-
return null;
|
|
42
|
-
return connector.dsnParser.getSampleDSN();
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Get all available sample DSNs
|
|
46
|
-
*/
|
|
47
|
-
static getAllSampleDSNs() {
|
|
48
|
-
const samples = {};
|
|
49
|
-
for (const [id, connector] of ConnectorRegistry.connectors.entries()) {
|
|
50
|
-
samples[id] = connector.dsnParser.getSampleDSN();
|
|
51
|
-
}
|
|
52
|
-
return samples;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
ConnectorRegistry.connectors = new Map();
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { ConnectorRegistry } from './interface.js';
|
|
2
|
-
// Singleton instance for global access
|
|
3
|
-
let managerInstance = null;
|
|
4
|
-
/**
|
|
5
|
-
* Manages database connectors and provides a unified interface to work with them
|
|
6
|
-
*/
|
|
7
|
-
export class ConnectorManager {
|
|
8
|
-
constructor() {
|
|
9
|
-
this.activeConnector = null;
|
|
10
|
-
this.connected = false;
|
|
11
|
-
if (!managerInstance) {
|
|
12
|
-
managerInstance = this;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Initialize and connect to the database using a DSN
|
|
17
|
-
*/
|
|
18
|
-
async connectWithDSN(dsn, initScript) {
|
|
19
|
-
// First try to find a connector that can handle this DSN
|
|
20
|
-
let connector = ConnectorRegistry.getConnectorForDSN(dsn);
|
|
21
|
-
if (!connector) {
|
|
22
|
-
throw new Error(`No connector found that can handle the DSN: ${dsn}`);
|
|
23
|
-
}
|
|
24
|
-
this.activeConnector = connector;
|
|
25
|
-
// Connect to the database
|
|
26
|
-
await this.activeConnector.connect(dsn, initScript);
|
|
27
|
-
this.connected = true;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Initialize and connect to the database using a specific connector type
|
|
31
|
-
*/
|
|
32
|
-
async connectWithType(connectorType, dsn) {
|
|
33
|
-
// Get the connector from the registry
|
|
34
|
-
const connector = ConnectorRegistry.getConnector(connectorType);
|
|
35
|
-
if (!connector) {
|
|
36
|
-
throw new Error(`Connector "${connectorType}" not found`);
|
|
37
|
-
}
|
|
38
|
-
this.activeConnector = connector;
|
|
39
|
-
// Use provided DSN or get sample DSN
|
|
40
|
-
const connectionString = dsn || connector.dsnParser.getSampleDSN();
|
|
41
|
-
// Connect to the database
|
|
42
|
-
await this.activeConnector.connect(connectionString);
|
|
43
|
-
this.connected = true;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Close the database connection
|
|
47
|
-
*/
|
|
48
|
-
async disconnect() {
|
|
49
|
-
if (this.activeConnector && this.connected) {
|
|
50
|
-
await this.activeConnector.disconnect();
|
|
51
|
-
this.connected = false;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Get the active connector
|
|
56
|
-
*/
|
|
57
|
-
getConnector() {
|
|
58
|
-
if (!this.activeConnector) {
|
|
59
|
-
throw new Error('No active connector. Call connectWithDSN() or connectWithType() first.');
|
|
60
|
-
}
|
|
61
|
-
return this.activeConnector;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Check if there's an active connection
|
|
65
|
-
*/
|
|
66
|
-
isConnected() {
|
|
67
|
-
return this.connected;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Get all available connector types
|
|
71
|
-
*/
|
|
72
|
-
static getAvailableConnectors() {
|
|
73
|
-
return ConnectorRegistry.getAvailableConnectors();
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Get sample DSNs for all available connectors
|
|
77
|
-
*/
|
|
78
|
-
static getAllSampleDSNs() {
|
|
79
|
-
return ConnectorRegistry.getAllSampleDSNs();
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Get the current active connector instance
|
|
83
|
-
* This is used by resource and tool handlers
|
|
84
|
-
*/
|
|
85
|
-
static getCurrentConnector() {
|
|
86
|
-
if (!managerInstance) {
|
|
87
|
-
throw new Error('ConnectorManager not initialized');
|
|
88
|
-
}
|
|
89
|
-
return managerInstance.getConnector();
|
|
90
|
-
}
|
|
91
|
-
}
|