@pilat/mcp-datalink 1.0.3 → 1.2.0
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 +30 -387
- package/dist/adapters/factory.d.ts +0 -4
- package/dist/adapters/factory.d.ts.map +1 -1
- package/dist/adapters/factory.js +3 -8
- package/dist/adapters/factory.js.map +1 -1
- package/dist/adapters/mysql/adapter.d.ts +2 -31
- package/dist/adapters/mysql/adapter.d.ts.map +1 -1
- package/dist/adapters/mysql/adapter.js +27 -283
- package/dist/adapters/mysql/adapter.js.map +1 -1
- package/dist/adapters/postgresql/adapter.d.ts +2 -22
- package/dist/adapters/postgresql/adapter.d.ts.map +1 -1
- package/dist/adapters/postgresql/adapter.js +18 -175
- package/dist/adapters/postgresql/adapter.js.map +1 -1
- package/dist/adapters/sqlite/adapter.d.ts +3 -25
- package/dist/adapters/sqlite/adapter.d.ts.map +1 -1
- package/dist/adapters/sqlite/adapter.js +65 -364
- package/dist/adapters/sqlite/adapter.js.map +1 -1
- package/dist/adapters/sqlite/pragma-check.d.ts +19 -0
- package/dist/adapters/sqlite/pragma-check.d.ts.map +1 -0
- package/dist/adapters/sqlite/pragma-check.js +25 -0
- package/dist/adapters/sqlite/pragma-check.js.map +1 -0
- package/dist/adapters/sqlite/url-parser.d.ts +34 -0
- package/dist/adapters/sqlite/url-parser.d.ts.map +1 -0
- package/dist/adapters/sqlite/url-parser.js +73 -0
- package/dist/adapters/sqlite/url-parser.js.map +1 -0
- package/dist/adapters/types.d.ts +10 -113
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/config/loader.d.ts +10 -7
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +20 -97
- package/dist/config/loader.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -9
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +113 -40
- package/dist/server.js.map +1 -1
- package/dist/tools/describe-table.d.ts +5 -6
- package/dist/tools/describe-table.d.ts.map +1 -1
- package/dist/tools/describe-table.js +65 -9
- package/dist/tools/describe-table.js.map +1 -1
- package/dist/tools/execute.d.ts +5 -6
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/execute.js +13 -11
- package/dist/tools/execute.js.map +1 -1
- package/dist/tools/explain.d.ts +5 -10
- package/dist/tools/explain.d.ts.map +1 -1
- package/dist/tools/explain.js +25 -26
- package/dist/tools/explain.js.map +1 -1
- package/dist/tools/list-databases.d.ts +5 -7
- package/dist/tools/list-databases.d.ts.map +1 -1
- package/dist/tools/list-databases.js +31 -0
- package/dist/tools/list-databases.js.map +1 -1
- package/dist/tools/list-tables.d.ts +5 -10
- package/dist/tools/list-tables.d.ts.map +1 -1
- package/dist/tools/list-tables.js +27 -9
- package/dist/tools/list-tables.js.map +1 -1
- package/dist/tools/query.d.ts +1 -6
- package/dist/tools/query.d.ts.map +1 -1
- package/dist/tools/query.js +7 -27
- package/dist/tools/query.js.map +1 -1
- package/dist/types.d.ts +39 -12
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/formatter.d.ts +7 -4
- package/dist/utils/formatter.d.ts.map +1 -1
- package/dist/utils/formatter.js +20 -10
- package/dist/utils/formatter.js.map +1 -1
- package/dist/utils/sql-parser.d.ts +51 -0
- package/dist/utils/sql-parser.d.ts.map +1 -0
- package/dist/utils/sql-parser.js +310 -0
- package/dist/utils/sql-parser.js.map +1 -0
- package/dist/utils/truncate.d.ts +4 -13
- package/dist/utils/truncate.d.ts.map +1 -1
- package/dist/utils/truncate.js +38 -18
- package/dist/utils/truncate.js.map +1 -1
- package/dist/utils/validation.d.ts +35 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +89 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +11 -13
- package/databases.example.json +0 -18
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite URL and Path Parsing Utilities
|
|
3
|
+
*
|
|
4
|
+
* Handles parsing of SQLite connection URLs and path validation/resolution.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Parse SQLite URL to extract file path
|
|
8
|
+
*
|
|
9
|
+
* Supported formats:
|
|
10
|
+
* - sqlite:///absolute/path.db
|
|
11
|
+
* - sqlite://./relative/path.db
|
|
12
|
+
* - sqlite://:memory:
|
|
13
|
+
* - /absolute/path.db (plain file path)
|
|
14
|
+
* - ./relative/path.db (plain file path)
|
|
15
|
+
*
|
|
16
|
+
* @param url - SQLite connection URL or file path
|
|
17
|
+
* @returns Resolved absolute file path or :memory:
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseSqliteUrl(url: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Validate and resolve SQLite database path
|
|
22
|
+
*
|
|
23
|
+
* SECURITY: Prevents path traversal attacks by:
|
|
24
|
+
* 1. Resolving to absolute path
|
|
25
|
+
* 2. Ensuring path doesn't contain suspicious patterns
|
|
26
|
+
* 3. Checking file exists (unless :memory:)
|
|
27
|
+
*
|
|
28
|
+
* @param rawPath - Raw path from config
|
|
29
|
+
* @param basePath - Base directory for relative paths (process.cwd())
|
|
30
|
+
* @returns Validated absolute path
|
|
31
|
+
* @throws DbMcpError if path is invalid or file not found
|
|
32
|
+
*/
|
|
33
|
+
export declare function validateAndResolvePath(rawPath: string, basePath: string): string;
|
|
34
|
+
//# sourceMappingURL=url-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-parser.d.ts","sourceRoot":"","sources":["../../../src/adapters/sqlite/url-parser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAelD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAqChF"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite URL and Path Parsing Utilities
|
|
3
|
+
*
|
|
4
|
+
* Handles parsing of SQLite connection URLs and path validation/resolution.
|
|
5
|
+
*/
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import { DbMcpError, ErrorCode } from '../../utils/errors.js';
|
|
9
|
+
/**
|
|
10
|
+
* Parse SQLite URL to extract file path
|
|
11
|
+
*
|
|
12
|
+
* Supported formats:
|
|
13
|
+
* - sqlite:///absolute/path.db
|
|
14
|
+
* - sqlite://./relative/path.db
|
|
15
|
+
* - sqlite://:memory:
|
|
16
|
+
* - /absolute/path.db (plain file path)
|
|
17
|
+
* - ./relative/path.db (plain file path)
|
|
18
|
+
*
|
|
19
|
+
* @param url - SQLite connection URL or file path
|
|
20
|
+
* @returns Resolved absolute file path or :memory:
|
|
21
|
+
*/
|
|
22
|
+
export function parseSqliteUrl(url) {
|
|
23
|
+
if (url.startsWith('sqlite://')) {
|
|
24
|
+
const pathPart = url.slice('sqlite://'.length);
|
|
25
|
+
// Special case: :memory:
|
|
26
|
+
if (pathPart === ':memory:') {
|
|
27
|
+
return ':memory:';
|
|
28
|
+
}
|
|
29
|
+
// Handle both absolute (/path) and relative (./path) paths
|
|
30
|
+
return pathPart;
|
|
31
|
+
}
|
|
32
|
+
// Plain file path (no sqlite:// prefix)
|
|
33
|
+
return url;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Validate and resolve SQLite database path
|
|
37
|
+
*
|
|
38
|
+
* SECURITY: Prevents path traversal attacks by:
|
|
39
|
+
* 1. Resolving to absolute path
|
|
40
|
+
* 2. Ensuring path doesn't contain suspicious patterns
|
|
41
|
+
* 3. Checking file exists (unless :memory:)
|
|
42
|
+
*
|
|
43
|
+
* @param rawPath - Raw path from config
|
|
44
|
+
* @param basePath - Base directory for relative paths (process.cwd())
|
|
45
|
+
* @returns Validated absolute path
|
|
46
|
+
* @throws DbMcpError if path is invalid or file not found
|
|
47
|
+
*/
|
|
48
|
+
export function validateAndResolvePath(rawPath, basePath) {
|
|
49
|
+
// :memory: is always valid
|
|
50
|
+
if (rawPath === ':memory:') {
|
|
51
|
+
return ':memory:';
|
|
52
|
+
}
|
|
53
|
+
// Resolve to absolute path
|
|
54
|
+
const absolutePath = path.isAbsolute(rawPath) ? rawPath : path.resolve(basePath, rawPath);
|
|
55
|
+
// Normalize to remove any .. or . components
|
|
56
|
+
const normalizedPath = path.normalize(absolutePath);
|
|
57
|
+
// SECURITY: Check for path traversal attempts
|
|
58
|
+
// After normalization, path should not go above the base directory for relative paths
|
|
59
|
+
if (!path.isAbsolute(rawPath)) {
|
|
60
|
+
// For relative paths, ensure the resolved path is still within reasonable bounds
|
|
61
|
+
// This prevents sqlite://../../etc/passwd type attacks
|
|
62
|
+
const relativeToCwd = path.relative(basePath, normalizedPath);
|
|
63
|
+
if (relativeToCwd.startsWith('..')) {
|
|
64
|
+
throw new DbMcpError(ErrorCode.CONFIG_INVALID, 'Path traversal detected. SQLite path must not escape the working directory.', { path: rawPath, resolved: normalizedPath });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Check file exists
|
|
68
|
+
if (!fs.existsSync(normalizedPath)) {
|
|
69
|
+
throw new DbMcpError(ErrorCode.CONNECTION_FAILED, `SQLite database file not found: ${normalizedPath}`, { path: rawPath, resolved: normalizedPath });
|
|
70
|
+
}
|
|
71
|
+
return normalizedPath;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=url-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-parser.js","sourceRoot":"","sources":["../../../src/adapters/sqlite/url-parser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAE/C,yBAAyB;QACzB,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;YAC5B,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,2DAA2D;QAC3D,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,wCAAwC;IACxC,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAe,EAAE,QAAgB;IACtE,2BAA2B;IAC3B,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;QAC3B,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,2BAA2B;IAC3B,MAAM,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE1F,6CAA6C;IAC7C,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IAEpD,8CAA8C;IAC9C,sFAAsF;IACtF,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,iFAAiF;QACjF,uDAAuD;QACvD,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;QAC9D,IAAI,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,UAAU,CAClB,SAAS,CAAC,cAAc,EACxB,6EAA6E,EAC7E,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,CAC5C,CAAC;QACJ,CAAC;IACH,CAAC;IAED,oBAAoB;IACpB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,UAAU,CAClB,SAAS,CAAC,iBAAiB,EAC3B,mCAAmC,cAAc,EAAE,EACnD,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,CAC5C,CAAC;IACJ,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC"}
|
package/dist/adapters/types.d.ts
CHANGED
|
@@ -4,152 +4,49 @@
|
|
|
4
4
|
* Core abstractions for multi-database support.
|
|
5
5
|
*/
|
|
6
6
|
import type { ParsedQuery, TableInfo, TableDescription, DefaultsConfig, DatabaseConfig } from '../types.js';
|
|
7
|
-
/**
|
|
8
|
-
* Result from a raw query execution
|
|
9
|
-
* Normalized format across all database drivers
|
|
10
|
-
*/
|
|
7
|
+
/** Normalized result format across all database drivers */
|
|
11
8
|
export interface RawQueryResult {
|
|
12
|
-
/** Column metadata */
|
|
13
9
|
fields: Array<{
|
|
14
10
|
name: string;
|
|
15
11
|
}>;
|
|
16
|
-
/** Row data as arrays (positional, not named) */
|
|
17
12
|
rows: unknown[][];
|
|
18
|
-
/** Number of rows returned or affected */
|
|
19
13
|
rowCount: number;
|
|
20
14
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Database adapter interface - implemented by each database driver
|
|
23
|
-
*
|
|
24
|
-
* Combines connection management with SQL dialect operations.
|
|
25
|
-
*/
|
|
15
|
+
/** Implemented by each database driver */
|
|
26
16
|
export interface DatabaseAdapter {
|
|
27
|
-
/** Unique identifier for this adapter type */
|
|
28
17
|
readonly type: 'postgresql' | 'mysql' | 'sqlite';
|
|
29
|
-
/**
|
|
30
|
-
* Execute a function with a managed connection.
|
|
31
|
-
* Connection is created at start and destroyed at end.
|
|
32
|
-
*/
|
|
33
18
|
withConnection<T>(fn: (conn: AdapterConnection) => Promise<T>): Promise<T>;
|
|
34
|
-
/**
|
|
35
|
-
* Get the default schema name for this database type
|
|
36
|
-
*
|
|
37
|
-
* PostgreSQL: "public"
|
|
38
|
-
* MySQL: database name from connection URL
|
|
39
|
-
* SQLite: "main"
|
|
40
|
-
*/
|
|
19
|
+
/** PostgreSQL: "public", MySQL: db name from URL, SQLite: "main" */
|
|
41
20
|
getDefaultSchema(): string;
|
|
42
|
-
/**
|
|
43
|
-
* Clean up any resources (called on server shutdown)
|
|
44
|
-
*/
|
|
45
21
|
dispose(): Promise<void>;
|
|
46
|
-
/**
|
|
47
|
-
* Parse a SQL statement and validate it
|
|
48
|
-
*
|
|
49
|
-
* SECURITY: This validates:
|
|
50
|
-
* - Single statement only (no multi-statement attacks)
|
|
51
|
-
* - No dangerous operations (DROP, TRUNCATE, etc.)
|
|
52
|
-
*
|
|
53
|
-
* @param sql - Raw SQL to parse
|
|
54
|
-
* @throws DbMcpError if SQL is invalid or multi-statement
|
|
55
|
-
*/
|
|
22
|
+
/** Parse and validate SQL (rejects multi-statement, dangerous ops) */
|
|
56
23
|
parseQuery(sql: string): ParsedQuery;
|
|
57
|
-
/**
|
|
58
|
-
* Inject LIMIT clause if query doesn't have one
|
|
59
|
-
*
|
|
60
|
-
* Used to enforce maxRows limit on SELECT queries.
|
|
61
|
-
*
|
|
62
|
-
* @param sql - Original SQL
|
|
63
|
-
* @param limit - Limit value to inject
|
|
64
|
-
* @returns Modified SQL with LIMIT, or original if already has LIMIT
|
|
65
|
-
*/
|
|
24
|
+
/** Add LIMIT if missing */
|
|
66
25
|
injectLimit(sql: string, limit: number): string;
|
|
67
|
-
/**
|
|
68
|
-
* Validate that a SQL query is appropriate for a specific tool
|
|
69
|
-
*
|
|
70
|
-
* @param sql - The SQL query string
|
|
71
|
-
* @param tool - Either 'query' (SELECT only) or 'execute' (INSERT/UPDATE/DELETE)
|
|
72
|
-
* @throws DbMcpError with QUERY_BLOCKED if query type is not allowed
|
|
73
|
-
*/
|
|
26
|
+
/** Validate query is appropriate for tool (query=SELECT, execute=INSERT/UPDATE/DELETE) */
|
|
74
27
|
validateQueryForTool(sql: string, tool: 'query' | 'execute'): void;
|
|
75
|
-
/**
|
|
76
|
-
* Get the EXPLAIN prefix for this dialect
|
|
77
|
-
*
|
|
78
|
-
* @param analyze - Whether to include ANALYZE
|
|
79
|
-
* @returns Prefix to prepend to SQL for EXPLAIN
|
|
80
|
-
*
|
|
81
|
-
* PostgreSQL: "EXPLAIN " or "EXPLAIN ANALYZE "
|
|
82
|
-
* MySQL: "EXPLAIN " or "EXPLAIN ANALYZE "
|
|
83
|
-
* SQLite: "EXPLAIN QUERY PLAN "
|
|
84
|
-
*/
|
|
85
28
|
getExplainPrefix(analyze: boolean): string;
|
|
86
|
-
/**
|
|
87
|
-
* Convert parameter placeholders to dialect-specific format
|
|
88
|
-
*
|
|
89
|
-
* PostgreSQL uses $1, $2, $3 (no conversion needed)
|
|
90
|
-
* MySQL/SQLite use ? placeholders (convert $1 -> ?)
|
|
91
|
-
*
|
|
92
|
-
* @param sql - SQL with placeholders
|
|
93
|
-
* @returns SQL with converted placeholders
|
|
94
|
-
*/
|
|
29
|
+
/** PostgreSQL: no-op, MySQL/SQLite: $1 -> ? */
|
|
95
30
|
convertPlaceholders(sql: string): string;
|
|
96
31
|
}
|
|
97
|
-
/**
|
|
98
|
-
* Active connection handle - passed to tool implementations
|
|
99
|
-
*
|
|
100
|
-
* Provides database operations during a single request lifecycle.
|
|
101
|
-
* Connection is automatically recycled after the request completes.
|
|
102
|
-
*/
|
|
32
|
+
/** Active connection handle for a single request */
|
|
103
33
|
export interface AdapterConnection {
|
|
104
|
-
/**
|
|
105
|
-
* Execute a parameterized query
|
|
106
|
-
*
|
|
107
|
-
* SECURITY: All user SQL MUST go through this method with parameters
|
|
108
|
-
*
|
|
109
|
-
* @param sql - SQL with placeholders ($1, $2 for PG; ? for MySQL/SQLite)
|
|
110
|
-
* @param params - Parameter values (type-safe, prevents injection)
|
|
111
|
-
*/
|
|
112
34
|
query(sql: string, params?: unknown[]): Promise<RawQueryResult>;
|
|
113
|
-
/**
|
|
114
|
-
* Execute a raw SQL statement (for SET, BEGIN, etc.)
|
|
115
|
-
*
|
|
116
|
-
* Only use for validated internal commands.
|
|
117
|
-
* Never pass user-provided values through this method.
|
|
118
|
-
*/
|
|
35
|
+
/** For internal commands only (SET, BEGIN, COMMIT) - never user input */
|
|
119
36
|
execute(sql: string): Promise<void>;
|
|
120
|
-
/**
|
|
121
|
-
* List tables in a schema (adapter-specific implementation)
|
|
122
|
-
*
|
|
123
|
-
* @param schema - Schema name (ignored for SQLite)
|
|
124
|
-
* @param maxTables - Maximum number of tables to return
|
|
125
|
-
*/
|
|
126
37
|
listTables(schema: string, maxTables: number): Promise<ListTablesInternalResult>;
|
|
127
|
-
/**
|
|
128
|
-
* Describe a table structure (adapter-specific implementation)
|
|
129
|
-
*
|
|
130
|
-
* @param table - Table name
|
|
131
|
-
* @param schema - Schema name (ignored for SQLite)
|
|
132
|
-
* @param limits - Limits for columns and indexes
|
|
133
|
-
*/
|
|
134
38
|
describeTable(table: string, schema: string, limits: {
|
|
135
39
|
maxColumns: number;
|
|
136
40
|
maxIndexes: number;
|
|
137
41
|
}): Promise<TableDescription>;
|
|
138
42
|
}
|
|
139
|
-
/**
|
|
140
|
-
* Internal result from listTables before tool formatting
|
|
141
|
-
*/
|
|
142
43
|
export interface ListTablesInternalResult {
|
|
143
44
|
tables: TableInfo[];
|
|
45
|
+
truncated?: boolean;
|
|
144
46
|
totalAvailable: number;
|
|
145
47
|
}
|
|
146
|
-
/**
|
|
147
|
-
* Configuration passed to adapter constructors
|
|
148
|
-
*/
|
|
149
48
|
export interface AdapterConfig {
|
|
150
|
-
/** Database configuration (url, readonly, etc.) */
|
|
151
49
|
database: DatabaseConfig;
|
|
152
|
-
/** Default settings (timeout, limits) */
|
|
153
50
|
defaults: DefaultsConfig;
|
|
154
51
|
}
|
|
155
52
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,cAAc,EACf,MAAM,aAAa,CAAC;AAErB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,cAAc,EACf,MAAM,aAAa,CAAC;AAErB,2DAA2D;AAC3D,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,GAAG,QAAQ,CAAC;IAEjD,cAAc,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAE3E,oEAAoE;IACpE,gBAAgB,IAAI,MAAM,CAAC;IAE3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAMzB,sEAAsE;IACtE,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAAC;IAErC,2BAA2B;IAC3B,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAEhD,0FAA0F;IAC1F,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC;IAEnE,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;IAE3C,+CAA+C;IAC/C,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1C;AAED,oDAAoD;AACpD,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAEhE,yEAAyE;IACzE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAEjF,aAAa,CACX,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GACjD,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,cAAc,CAAC;IACzB,QAAQ,EAAE,cAAc,CAAC;CAC1B"}
|
package/dist/config/loader.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { Config } from '../types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Load configuration
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* Load configuration from environment variables.
|
|
4
|
+
*
|
|
5
|
+
* Environment variables:
|
|
6
|
+
* DATALINK_{NAME}_URL - Database connection URL (required)
|
|
7
|
+
* DATALINK_{NAME}_READONLY - Set to "true" for read-only mode (optional)
|
|
8
|
+
*
|
|
9
|
+
* Example:
|
|
10
|
+
* DATALINK_PROD_URL=postgresql://user:pass@host:5432/db
|
|
11
|
+
* DATALINK_PROD_READONLY=true
|
|
9
12
|
*/
|
|
10
|
-
export declare function loadConfig(
|
|
13
|
+
export declare function loadConfig(): Config;
|
|
11
14
|
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAkC,MAAM,aAAa,CAAC;AAuC1E;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAWnC"}
|
package/dist/config/loader.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { readFile } from 'fs/promises';
|
|
2
|
-
import { homedir } from 'os';
|
|
3
|
-
import { resolve, join } from 'path';
|
|
4
1
|
import { DbMcpError, ErrorCode } from '../utils/errors.js';
|
|
5
2
|
const DEFAULT_CONFIG = {
|
|
6
3
|
maxRows: 100,
|
|
@@ -12,116 +9,42 @@ const DEFAULT_CONFIG = {
|
|
|
12
9
|
timeout: 30000,
|
|
13
10
|
};
|
|
14
11
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*/
|
|
18
|
-
async function tryReadConfigFile(path) {
|
|
19
|
-
try {
|
|
20
|
-
const content = await readFile(path, 'utf-8');
|
|
21
|
-
return JSON.parse(content);
|
|
22
|
-
}
|
|
23
|
-
catch (err) {
|
|
24
|
-
if (err.code === 'ENOENT') {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
throw new DbMcpError(ErrorCode.CONFIG_INVALID, `Failed to parse config file ${path}: ${err.message}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Resolve ${ENV_VAR} placeholders in a string.
|
|
32
|
-
*/
|
|
33
|
-
function resolveEnvPlaceholders(value) {
|
|
34
|
-
return value.replace(/\$\{([^}]+)\}/g, (_, envVar) => {
|
|
35
|
-
const envValue = process.env[envVar];
|
|
36
|
-
if (envValue === undefined) {
|
|
37
|
-
throw new DbMcpError(ErrorCode.CONFIG_INVALID, `Environment variable ${envVar} is not defined`);
|
|
38
|
-
}
|
|
39
|
-
return envValue;
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Extract databases from DB_MCP_{NAME}_URL environment variables.
|
|
12
|
+
* Extract databases from DATALINK_{NAME}_URL environment variables.
|
|
13
|
+
* Also supports DATALINK_{NAME}_READONLY=true for read-only mode.
|
|
44
14
|
*/
|
|
45
15
|
function getDatabasesFromEnv() {
|
|
46
16
|
const databases = {};
|
|
47
|
-
const
|
|
17
|
+
const urlPattern = /^DATALINK_([A-Z0-9_]+)_URL$/;
|
|
48
18
|
for (const [key, value] of Object.entries(process.env)) {
|
|
49
|
-
const match = key.match(
|
|
19
|
+
const match = key.match(urlPattern);
|
|
50
20
|
if (match && value) {
|
|
51
21
|
const name = match[1].toLowerCase();
|
|
22
|
+
const readonlyKey = `DATALINK_${match[1]}_READONLY`;
|
|
23
|
+
const readonlyValue = process.env[readonlyKey];
|
|
52
24
|
databases[name] = {
|
|
53
25
|
url: value,
|
|
54
|
-
readonly:
|
|
26
|
+
readonly: readonlyValue === 'true' || readonlyValue === '1',
|
|
55
27
|
};
|
|
56
28
|
}
|
|
57
29
|
}
|
|
58
30
|
return databases;
|
|
59
31
|
}
|
|
60
32
|
/**
|
|
61
|
-
* Load configuration
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
33
|
+
* Load configuration from environment variables.
|
|
34
|
+
*
|
|
35
|
+
* Environment variables:
|
|
36
|
+
* DATALINK_{NAME}_URL - Database connection URL (required)
|
|
37
|
+
* DATALINK_{NAME}_READONLY - Set to "true" for read-only mode (optional)
|
|
38
|
+
*
|
|
39
|
+
* Example:
|
|
40
|
+
* DATALINK_PROD_URL=postgresql://user:pass@host:5432/db
|
|
41
|
+
* DATALINK_PROD_READONLY=true
|
|
67
42
|
*/
|
|
68
|
-
export
|
|
69
|
-
|
|
70
|
-
// 1. CLI argument
|
|
71
|
-
if (cliConfigPath) {
|
|
72
|
-
const resolvedPath = resolve(cliConfigPath);
|
|
73
|
-
rawConfig = await tryReadConfigFile(resolvedPath);
|
|
74
|
-
if (!rawConfig) {
|
|
75
|
-
throw new DbMcpError(ErrorCode.CONFIG_NOT_FOUND, `Config file not found: ${resolvedPath}`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
// 2. Environment variable
|
|
79
|
-
if (!rawConfig && process.env.DB_MCP_CONFIG) {
|
|
80
|
-
const envPath = resolve(process.env.DB_MCP_CONFIG);
|
|
81
|
-
rawConfig = await tryReadConfigFile(envPath);
|
|
82
|
-
if (!rawConfig) {
|
|
83
|
-
throw new DbMcpError(ErrorCode.CONFIG_NOT_FOUND, `Config file not found: ${envPath}`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// 3. Current directory
|
|
87
|
-
if (!rawConfig) {
|
|
88
|
-
rawConfig = await tryReadConfigFile(resolve('./databases.json'));
|
|
89
|
-
}
|
|
90
|
-
// 4. Home directory
|
|
91
|
-
if (!rawConfig) {
|
|
92
|
-
const homePath = join(homedir(), '.config', 'db-mcp', 'databases.json');
|
|
93
|
-
rawConfig = await tryReadConfigFile(homePath);
|
|
94
|
-
}
|
|
95
|
-
// 5. ENV-only mode
|
|
96
|
-
const envDatabases = getDatabasesFromEnv();
|
|
97
|
-
// Build final config
|
|
98
|
-
const databases = {};
|
|
99
|
-
// Add databases from config file (with env placeholder resolution)
|
|
100
|
-
if (rawConfig?.databases) {
|
|
101
|
-
for (const [name, dbConfig] of Object.entries(rawConfig.databases)) {
|
|
102
|
-
databases[name] = {
|
|
103
|
-
url: resolveEnvPlaceholders(dbConfig.url),
|
|
104
|
-
readonly: dbConfig.readonly ?? false,
|
|
105
|
-
maxRows: dbConfig.maxRows,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
// Add databases from environment (ENV-only mode)
|
|
110
|
-
// These don't override config file databases
|
|
111
|
-
for (const [name, dbConfig] of Object.entries(envDatabases)) {
|
|
112
|
-
if (!(name in databases)) {
|
|
113
|
-
databases[name] = dbConfig;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
// Validate at least one database is configured
|
|
43
|
+
export function loadConfig() {
|
|
44
|
+
const databases = getDatabasesFromEnv();
|
|
117
45
|
if (Object.keys(databases).length === 0) {
|
|
118
|
-
throw new DbMcpError(ErrorCode.CONFIG_NOT_FOUND, 'No databases configured.
|
|
46
|
+
throw new DbMcpError(ErrorCode.CONFIG_NOT_FOUND, 'No databases configured. Set DATALINK_{NAME}_URL environment variables.');
|
|
119
47
|
}
|
|
120
|
-
|
|
121
|
-
const defaults = {
|
|
122
|
-
...DEFAULT_CONFIG,
|
|
123
|
-
...rawConfig?.defaults,
|
|
124
|
-
};
|
|
125
|
-
return { databases, defaults };
|
|
48
|
+
return { databases, defaults: DEFAULT_CONFIG };
|
|
126
49
|
}
|
|
127
50
|
//# sourceMappingURL=loader.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,cAAc,GAAmB;IACrC,OAAO,EAAE,GAAG;IACZ,aAAa,EAAE,GAAG;IAClB,YAAY,EAAE,KAAK,EAAE,OAAO;IAC5B,UAAU,EAAE,EAAE;IACd,SAAS,EAAE,GAAG;IACd,UAAU,EAAE,EAAE;IACd,OAAO,EAAE,KAAK;CACf,CAAC;AAEF;;;GAGG;AACH,SAAS,mBAAmB;IAC1B,MAAM,SAAS,GAAmC,EAAE,CAAC;IACrD,MAAM,UAAU,GAAG,6BAA6B,CAAC;IAEjD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACvD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACpC,IAAI,KAAK,IAAI,KAAK,EAAE,CAAC;YACnB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YACpC,MAAM,WAAW,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC;YACpD,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YAE/C,SAAS,CAAC,IAAI,CAAC,GAAG;gBAChB,GAAG,EAAE,KAAK;gBACV,QAAQ,EAAE,aAAa,KAAK,MAAM,IAAI,aAAa,KAAK,GAAG;aAC5D,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,SAAS,GAAG,mBAAmB,EAAE,CAAC;IAExC,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,UAAU,CAClB,SAAS,CAAC,gBAAgB,EAC1B,yEAAyE,CAC1E,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;AACjD,CAAC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;GAEG"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Entry point for mcp-datalink server
|
|
4
|
-
*
|
|
5
|
-
* Parses CLI arguments and starts the MCP server.
|
|
6
4
|
*/
|
|
7
5
|
import { parseArgs } from 'node:util';
|
|
8
6
|
import { loadConfig } from './config/loader.js';
|
|
@@ -10,7 +8,6 @@ import { runServer } from './server.js';
|
|
|
10
8
|
async function main() {
|
|
11
9
|
const { values } = parseArgs({
|
|
12
10
|
options: {
|
|
13
|
-
config: { type: 'string', short: 'c' },
|
|
14
11
|
help: { type: 'boolean', short: 'h' },
|
|
15
12
|
},
|
|
16
13
|
});
|
|
@@ -18,17 +15,21 @@ async function main() {
|
|
|
18
15
|
console.log(`
|
|
19
16
|
mcp-datalink - MCP server for secure database access
|
|
20
17
|
|
|
21
|
-
Usage: mcp-datalink
|
|
18
|
+
Usage: mcp-datalink
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
Configuration via environment variables:
|
|
21
|
+
DATALINK_{NAME}_URL Database connection URL
|
|
22
|
+
DATALINK_{NAME}_READONLY Set to "true" for read-only mode
|
|
26
23
|
|
|
27
|
-
|
|
24
|
+
Example:
|
|
25
|
+
DATALINK_PROD_URL=postgresql://user:pass@host:5432/db
|
|
26
|
+
DATALINK_PROD_READONLY=true
|
|
27
|
+
|
|
28
|
+
Supported databases: PostgreSQL, MySQL, SQLite
|
|
28
29
|
`);
|
|
29
30
|
process.exit(0);
|
|
30
31
|
}
|
|
31
|
-
const config =
|
|
32
|
+
const config = loadConfig();
|
|
32
33
|
await runServer(config);
|
|
33
34
|
}
|
|
34
35
|
main().catch((error) => {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,KAAK,UAAU,IAAI;IACjB,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;QAC3B,OAAO,EAAE;YACP,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;SACtC;KACF,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;CAcf,CAAC,CAAC;QACC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;AAC1B,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAY,EAAE,EAAE;IAC5B,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAInE,OAAO,KAAK,EACV,MAAM,EAMP,MAAM,YAAY,CAAC;AAyBpB;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA0OnD;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
|