@connorbritain/mssql-mcp-reader 0.1.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.
Files changed (74) hide show
  1. package/README.md +106 -0
  2. package/dist/audit/AuditLogger.d.ts +37 -0
  3. package/dist/audit/AuditLogger.d.ts.map +1 -0
  4. package/dist/audit/AuditLogger.js +145 -0
  5. package/dist/audit/AuditLogger.js.map +1 -0
  6. package/dist/config/EnvironmentManager.d.ts +70 -0
  7. package/dist/config/EnvironmentManager.d.ts.map +1 -0
  8. package/dist/config/EnvironmentManager.js +301 -0
  9. package/dist/config/EnvironmentManager.js.map +1 -0
  10. package/dist/config/ScriptManager.d.ts +69 -0
  11. package/dist/config/ScriptManager.d.ts.map +1 -0
  12. package/dist/config/ScriptManager.js +166 -0
  13. package/dist/config/ScriptManager.js.map +1 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +569 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/tools/DescribeTableTool.d.ts +32 -0
  19. package/dist/tools/DescribeTableTool.d.ts.map +1 -0
  20. package/dist/tools/DescribeTableTool.js +108 -0
  21. package/dist/tools/DescribeTableTool.js.map +1 -0
  22. package/dist/tools/ExplainQueryTool.d.ts +24 -0
  23. package/dist/tools/ExplainQueryTool.d.ts.map +1 -0
  24. package/dist/tools/ExplainQueryTool.js +98 -0
  25. package/dist/tools/ExplainQueryTool.js.map +1 -0
  26. package/dist/tools/InspectDependenciesTool.d.ts +45 -0
  27. package/dist/tools/InspectDependenciesTool.d.ts.map +1 -0
  28. package/dist/tools/InspectDependenciesTool.js +215 -0
  29. package/dist/tools/InspectDependenciesTool.js.map +1 -0
  30. package/dist/tools/ListDatabasesTool.d.ts +27 -0
  31. package/dist/tools/ListDatabasesTool.d.ts.map +1 -0
  32. package/dist/tools/ListDatabasesTool.js +107 -0
  33. package/dist/tools/ListDatabasesTool.js.map +1 -0
  34. package/dist/tools/ListEnvironmentsTool.d.ts +49 -0
  35. package/dist/tools/ListEnvironmentsTool.d.ts.map +1 -0
  36. package/dist/tools/ListEnvironmentsTool.js +73 -0
  37. package/dist/tools/ListEnvironmentsTool.js.map +1 -0
  38. package/dist/tools/ListScriptsTool.d.ts +41 -0
  39. package/dist/tools/ListScriptsTool.d.ts.map +1 -0
  40. package/dist/tools/ListScriptsTool.js +86 -0
  41. package/dist/tools/ListScriptsTool.js.map +1 -0
  42. package/dist/tools/ListTableTool.d.ts +24 -0
  43. package/dist/tools/ListTableTool.d.ts.map +1 -0
  44. package/dist/tools/ListTableTool.js +85 -0
  45. package/dist/tools/ListTableTool.js.map +1 -0
  46. package/dist/tools/ProfileTableTool.d.ts +78 -0
  47. package/dist/tools/ProfileTableTool.d.ts.map +1 -0
  48. package/dist/tools/ProfileTableTool.js +372 -0
  49. package/dist/tools/ProfileTableTool.js.map +1 -0
  50. package/dist/tools/ReadDataTool.d.ts +61 -0
  51. package/dist/tools/ReadDataTool.d.ts.map +1 -0
  52. package/dist/tools/ReadDataTool.js +299 -0
  53. package/dist/tools/ReadDataTool.js.map +1 -0
  54. package/dist/tools/RelationshipInspectorTool.d.ts +46 -0
  55. package/dist/tools/RelationshipInspectorTool.d.ts.map +1 -0
  56. package/dist/tools/RelationshipInspectorTool.js +155 -0
  57. package/dist/tools/RelationshipInspectorTool.js.map +1 -0
  58. package/dist/tools/RunScriptTool.d.ts +215 -0
  59. package/dist/tools/RunScriptTool.d.ts.map +1 -0
  60. package/dist/tools/RunScriptTool.js +177 -0
  61. package/dist/tools/RunScriptTool.js.map +1 -0
  62. package/dist/tools/SearchSchemaTool.d.ts +88 -0
  63. package/dist/tools/SearchSchemaTool.d.ts.map +1 -0
  64. package/dist/tools/SearchSchemaTool.js +236 -0
  65. package/dist/tools/SearchSchemaTool.js.map +1 -0
  66. package/dist/tools/TestConnectionTool.d.ts +36 -0
  67. package/dist/tools/TestConnectionTool.d.ts.map +1 -0
  68. package/dist/tools/TestConnectionTool.js +155 -0
  69. package/dist/tools/TestConnectionTool.js.map +1 -0
  70. package/dist/tools/ValidateEnvironmentConfigTool.d.ts +37 -0
  71. package/dist/tools/ValidateEnvironmentConfigTool.d.ts.map +1 -0
  72. package/dist/tools/ValidateEnvironmentConfigTool.js +230 -0
  73. package/dist/tools/ValidateEnvironmentConfigTool.js.map +1 -0
  74. package/package.json +42 -0
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # MSSQL MCP Reader
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@connorbritain/mssql-mcp-reader.svg)](https://www.npmjs.com/package/@connorbritain/mssql-mcp-reader)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ **Read-only Model Context Protocol server for Microsoft SQL Server.**
7
+
8
+ Safe schema discovery, profiling, and querying with zero risk of data modification. Ideal for analysts, auditors, and anyone who needs database exploration without write access.
9
+
10
+ ## Package Tiers
11
+
12
+ | Package | npm | Tools | Use Case |
13
+ |---------|-----|-------|----------|
14
+ | **mssql-mcp-reader** (this) | `@connorbritain/mssql-mcp-reader` | 14 read-only | Analysts, auditors, safe exploration |
15
+ | **[mssql-mcp-writer](https://github.com/ConnorBritain/mssql-mcp-writer)** | `@connorbritain/mssql-mcp-writer` | 17 (reader + data ops) | Data engineers, ETL developers |
16
+ | **[mssql-mcp-server](https://github.com/ConnorBritain/mssql-mcp-server)** | `@connorbritain/mssql-mcp-server` | 20 (all tools) | DBAs, full admin access |
17
+
18
+ ---
19
+
20
+ ## Tools Included
21
+
22
+ | Category | Tools |
23
+ |----------|-------|
24
+ | **Discovery** | `search_schema`, `describe_table`, `list_table`, `list_databases`, `list_environments` |
25
+ | **Profiling** | `profile_table`, `inspect_relationships`, `inspect_dependencies`, `explain_query` |
26
+ | **Querying** | `read_data` (SELECT only) |
27
+ | **Scripts** | `list_scripts`, `run_script` (readonly scripts only) |
28
+ | **Operations** | `test_connection`, `validate_environment_config` |
29
+
30
+ **Not included:** `insert_data`, `update_data`, `delete_data`, `create_table`, `create_index`, `drop_table`
31
+
32
+ ---
33
+
34
+ ## Quick Start
35
+
36
+ ### Install
37
+
38
+ ```bash
39
+ npm install -g @connorbritain/mssql-mcp-reader@latest
40
+ ```
41
+
42
+ ### MCP Client Configuration
43
+
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "mssql": {
48
+ "command": "npx",
49
+ "args": ["@connorbritain/mssql-mcp-reader@latest"],
50
+ "env": {
51
+ "SERVER_NAME": "127.0.0.1",
52
+ "DATABASE_NAME": "mydb",
53
+ "SQL_AUTH_MODE": "sql",
54
+ "SQL_USERNAME": "readonly_user",
55
+ "SQL_PASSWORD": "YourPassword123"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Configuration
65
+
66
+ | Variable | Required | Notes |
67
+ |----------|----------|-------|
68
+ | `SERVER_NAME` | Yes | SQL Server hostname/IP |
69
+ | `DATABASE_NAME` | Yes | Target database |
70
+ | `SQL_AUTH_MODE` | | `sql`, `windows`, or `aad` (default: `aad`) |
71
+ | `SQL_USERNAME` / `SQL_PASSWORD` | | Required for `sql`/`windows` modes |
72
+ | `ENVIRONMENTS_CONFIG_PATH` | | Path to multi-environment JSON config |
73
+ | `SCRIPTS_PATH` | | Path to named SQL scripts directory |
74
+ | `AUDIT_LOG_PATH` | | Custom audit log path |
75
+
76
+ ---
77
+
78
+ ## Features
79
+
80
+ All packages in the MSSQL MCP family share:
81
+
82
+ - **Multi-environment support** - Named database environments (prod, staging, dev) with per-environment policies
83
+ - **Governance controls** - `allowedTools`, `deniedTools`, `allowedSchemas`, `deniedSchemas`, `requireApproval`
84
+ - **Audit logging** - JSON Lines logs with session IDs and auto-redaction
85
+ - **Secret management** - `${secret:NAME}` placeholders for secure credential handling
86
+ - **Named SQL scripts** - Pre-approved parameterized queries with governance controls
87
+
88
+ ---
89
+
90
+ ## Documentation
91
+
92
+ Full documentation, configuration examples, and governance details are available in the main repository:
93
+
94
+ **[MSSQL MCP Server Documentation](https://github.com/ConnorBritain/mssql-mcp-server#readme)**
95
+
96
+ ---
97
+
98
+ ## License
99
+
100
+ MIT License. See [LICENSE](./LICENSE) for details.
101
+
102
+ ---
103
+
104
+ **Repository:** https://github.com/ConnorBritain/mssql-mcp-reader
105
+ **Issues:** https://github.com/ConnorBritain/mssql-mcp-reader/issues
106
+ **npm:** https://www.npmjs.com/package/@connorbritain/mssql-mcp-reader
@@ -0,0 +1,37 @@
1
+ export type AuditLevel = "none" | "basic" | "verbose";
2
+ export interface AuditLogEntry {
3
+ timestamp: string;
4
+ toolName: string;
5
+ environment?: string;
6
+ arguments?: Record<string, any>;
7
+ result?: {
8
+ success: boolean;
9
+ recordCount?: number;
10
+ error?: string;
11
+ data?: any;
12
+ };
13
+ durationMs?: number;
14
+ sessionId?: string;
15
+ userId?: string;
16
+ }
17
+ export declare class AuditLogger {
18
+ private readonly logFilePath;
19
+ private readonly enabled;
20
+ private readonly redactSensitiveData;
21
+ constructor();
22
+ private ensureLogDirectory;
23
+ private redactArguments;
24
+ log(entry: AuditLogEntry): void;
25
+ logToolInvocation(toolName: string, args: any, result: any, durationMs: number, options?: {
26
+ sessionId?: string;
27
+ userId?: string;
28
+ environment?: string;
29
+ auditLevel?: AuditLevel;
30
+ }): void;
31
+ /**
32
+ * Truncate result data for verbose logging to prevent huge log entries
33
+ */
34
+ private truncateResultData;
35
+ }
36
+ export declare const auditLogger: AuditLogger;
37
+ //# sourceMappingURL=AuditLogger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuditLogger.d.ts","sourceRoot":"","sources":["../../src/audit/AuditLogger.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChC,MAAM,CAAC,EAAE;QACP,OAAO,EAAE,OAAO,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,GAAG,CAAC;KACZ,CAAC;IACF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAU;;IAoB9C,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,eAAe;IA6BvB,GAAG,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAkB/B,iBAAiB,CACf,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,GAAG,EACX,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,UAAU,CAAC;KACzB,GACA,IAAI;IA+CP;;OAEG;IACH,OAAO,CAAC,kBAAkB;CA2B3B;AAGD,eAAO,MAAM,WAAW,aAAoB,CAAC"}
@@ -0,0 +1,145 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ export class AuditLogger {
4
+ constructor() {
5
+ // Read config from env vars
6
+ const logPath = process.env.AUDIT_LOG_PATH;
7
+ this.enabled = process.env.AUDIT_LOGGING !== "false"; // Enabled by default
8
+ this.redactSensitiveData = process.env.AUDIT_REDACT_SENSITIVE !== "false"; // Redact by default
9
+ if (this.enabled && logPath) {
10
+ this.logFilePath = path.resolve(logPath);
11
+ this.ensureLogDirectory();
12
+ }
13
+ else if (this.enabled) {
14
+ // Default to logs/audit.jsonl in the project root
15
+ this.logFilePath = path.resolve(process.cwd(), "logs", "audit.jsonl");
16
+ this.ensureLogDirectory();
17
+ }
18
+ else {
19
+ this.logFilePath = "";
20
+ }
21
+ }
22
+ ensureLogDirectory() {
23
+ if (!this.logFilePath)
24
+ return;
25
+ const dir = path.dirname(this.logFilePath);
26
+ if (!fs.existsSync(dir)) {
27
+ fs.mkdirSync(dir, { recursive: true });
28
+ }
29
+ }
30
+ redactArguments(args) {
31
+ if (!this.redactSensitiveData) {
32
+ return args;
33
+ }
34
+ const redacted = { ...args };
35
+ const sensitiveKeys = [
36
+ "password",
37
+ "secret",
38
+ "token",
39
+ "key",
40
+ "authorization",
41
+ "auth",
42
+ "credential",
43
+ ];
44
+ for (const [key, value] of Object.entries(redacted)) {
45
+ const lowerKey = key.toLowerCase();
46
+ if (sensitiveKeys.some((sensitive) => lowerKey.includes(sensitive))) {
47
+ redacted[key] = "[REDACTED]";
48
+ }
49
+ else if (typeof value === "string" && value.length > 500) {
50
+ // Truncate very long strings (likely large query results)
51
+ redacted[key] = value.substring(0, 500) + "... [TRUNCATED]";
52
+ }
53
+ }
54
+ return redacted;
55
+ }
56
+ log(entry) {
57
+ if (!this.enabled || !this.logFilePath) {
58
+ return;
59
+ }
60
+ try {
61
+ const logEntry = {
62
+ ...entry,
63
+ arguments: entry.arguments ? this.redactArguments(entry.arguments) : undefined,
64
+ };
65
+ const logLine = JSON.stringify(logEntry) + "\n";
66
+ fs.appendFileSync(this.logFilePath, logLine, { encoding: "utf-8" });
67
+ }
68
+ catch (error) {
69
+ console.error("Failed to write audit log:", error);
70
+ }
71
+ }
72
+ logToolInvocation(toolName, args, result, durationMs, options) {
73
+ const auditLevel = options?.auditLevel ?? "basic";
74
+ // Skip logging entirely for 'none' level
75
+ if (auditLevel === "none") {
76
+ return;
77
+ }
78
+ // Basic level: minimal info (tool name, success, timing, environment)
79
+ if (auditLevel === "basic") {
80
+ const entry = {
81
+ timestamp: new Date().toISOString(),
82
+ toolName,
83
+ environment: options?.environment,
84
+ result: {
85
+ success: result?.success ?? false,
86
+ recordCount: result?.recordCount ?? result?.rowsAffected,
87
+ error: result?.error,
88
+ },
89
+ durationMs: Math.round(durationMs),
90
+ sessionId: options?.sessionId,
91
+ userId: options?.userId,
92
+ };
93
+ this.log(entry);
94
+ return;
95
+ }
96
+ // Verbose level: full arguments and result data
97
+ const entry = {
98
+ timestamp: new Date().toISOString(),
99
+ toolName,
100
+ environment: options?.environment,
101
+ arguments: args || {},
102
+ result: {
103
+ success: result?.success ?? false,
104
+ recordCount: result?.recordCount ?? result?.rowsAffected,
105
+ error: result?.error,
106
+ data: this.truncateResultData(result?.data),
107
+ },
108
+ durationMs: Math.round(durationMs),
109
+ sessionId: options?.sessionId,
110
+ userId: options?.userId,
111
+ };
112
+ this.log(entry);
113
+ }
114
+ /**
115
+ * Truncate result data for verbose logging to prevent huge log entries
116
+ */
117
+ truncateResultData(data) {
118
+ if (!data)
119
+ return undefined;
120
+ // If it's an array, limit to first 10 items
121
+ if (Array.isArray(data)) {
122
+ if (data.length > 10) {
123
+ return {
124
+ _truncated: true,
125
+ _totalCount: data.length,
126
+ items: data.slice(0, 10),
127
+ };
128
+ }
129
+ return data;
130
+ }
131
+ // If it's a large object (stringified > 10KB), truncate
132
+ const stringified = JSON.stringify(data);
133
+ if (stringified.length > 10000) {
134
+ return {
135
+ _truncated: true,
136
+ _originalSize: stringified.length,
137
+ preview: stringified.substring(0, 1000) + "...",
138
+ };
139
+ }
140
+ return data;
141
+ }
142
+ }
143
+ // Singleton instance
144
+ export const auditLogger = new AuditLogger();
145
+ //# sourceMappingURL=AuditLogger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuditLogger.js","sourceRoot":"","sources":["../../src/audit/AuditLogger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ import sql from "mssql";
2
+ export type AccessLevel = "server" | "database";
3
+ export type TierLevel = "reader" | "writer" | "admin";
4
+ export type AuditLevel = "none" | "basic" | "verbose";
5
+ export interface EnvironmentConfig {
6
+ name: string;
7
+ description?: string;
8
+ server: string;
9
+ database: string;
10
+ port?: number;
11
+ authMode: "sql" | "windows" | "aad";
12
+ username?: string;
13
+ password?: string;
14
+ domain?: string;
15
+ trustServerCertificate?: boolean;
16
+ connectionTimeout?: number;
17
+ readonly?: boolean;
18
+ allowedTools?: string[];
19
+ deniedTools?: string[];
20
+ maxRowsDefault?: number;
21
+ requireApproval?: boolean;
22
+ auditLevel?: AuditLevel;
23
+ accessLevel?: AccessLevel;
24
+ allowedDatabases?: string[] | "*";
25
+ deniedDatabases?: string[];
26
+ allowedSchemas?: string[];
27
+ deniedSchemas?: string[];
28
+ tier?: TierLevel;
29
+ }
30
+ export interface EnvironmentsConfig {
31
+ defaultEnvironment?: string;
32
+ environments: EnvironmentConfig[];
33
+ scriptsPath?: string;
34
+ }
35
+ export declare class EnvironmentManager {
36
+ private readonly environments;
37
+ private defaultEnvironment?;
38
+ private readonly connections;
39
+ constructor(configPath?: string);
40
+ private loadFromFile;
41
+ private loadFromEnvVars;
42
+ getEnvironment(name?: string): EnvironmentConfig;
43
+ listEnvironments(): EnvironmentConfig[];
44
+ /**
45
+ * Check if the environment allows access to a specific database.
46
+ * For database-level access, only the configured database is allowed.
47
+ * For server-level access, checks allowedDatabases/deniedDatabases.
48
+ */
49
+ isDatabaseAllowed(environmentName: string | undefined, databaseName: string): {
50
+ allowed: boolean;
51
+ reason?: string;
52
+ };
53
+ /**
54
+ * Check if a schema.table reference is allowed based on allowedSchemas/deniedSchemas.
55
+ * Pattern matching supports wildcards (e.g., "audit.*", "*.sensitive_*")
56
+ */
57
+ isSchemaAllowed(environmentName: string | undefined, schemaName: string, tableName?: string): {
58
+ allowed: boolean;
59
+ reason?: string;
60
+ };
61
+ /**
62
+ * Simple wildcard pattern matching (supports * as wildcard)
63
+ */
64
+ private matchesPattern;
65
+ getConnection(environmentName?: string): Promise<sql.ConnectionPool>;
66
+ private createSqlConfig;
67
+ closeAll(): Promise<void>;
68
+ }
69
+ export declare function getEnvironmentManager(): EnvironmentManager;
70
+ //# sourceMappingURL=EnvironmentManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EnvironmentManager.d.ts","sourceRoot":"","sources":["../../src/config/EnvironmentManager.ts"],"names":[],"mappings":"AAGA,OAAO,GAAG,MAAM,OAAO,CAAC;AAExB,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC;AAChD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AACtD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,GAAG,SAAS,GAAG,KAAK,CAAC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAG3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,CAAC,EAAE,UAAU,CAAC;IAGxB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAG3B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IAGzB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAmCD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiC;IAC9D,OAAO,CAAC,kBAAkB,CAAC,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA8D;gBAE9E,UAAU,CAAC,EAAE,MAAM;IAa/B,OAAO,CAAC,YAAY;IA2BpB,OAAO,CAAC,eAAe;IA+BvB,cAAc,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,iBAAiB;IAahD,gBAAgB,IAAI,iBAAiB,EAAE;IAIvC;;;;OAIG;IACH,iBAAiB,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IA4CnH;;;OAGG;IACH,eAAe,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IAiCnI;;OAEG;IACH,OAAO,CAAC,cAAc;IAQhB,aAAa,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;YA2B5D,eAAe;IAoFvB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAShC;AAKD,wBAAgB,qBAAqB,IAAI,kBAAkB,CAM1D"}
@@ -0,0 +1,301 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { InteractiveBrowserCredential } from "@azure/identity";
4
+ import sql from "mssql";
5
+ /**
6
+ * Resolves secret placeholders in the format ${secret:NAME}
7
+ * Currently supports environment variables; extensible for Key Vault, etc.
8
+ */
9
+ function resolveSecrets(value) {
10
+ if (!value)
11
+ return value;
12
+ const secretPattern = /\$\{secret:([^}]+)\}/g;
13
+ return value.replace(secretPattern, (match, secretName) => {
14
+ const envValue = process.env[secretName];
15
+ if (envValue === undefined) {
16
+ console.warn(`Secret '${secretName}' not found in environment variables`);
17
+ return match; // Return original placeholder if not found
18
+ }
19
+ return envValue;
20
+ });
21
+ }
22
+ /**
23
+ * Recursively resolves secrets in an object's string values
24
+ */
25
+ function resolveSecretsInConfig(config) {
26
+ const resolved = { ...config };
27
+ for (const [key, value] of Object.entries(resolved)) {
28
+ if (typeof value === "string") {
29
+ resolved[key] = resolveSecrets(value);
30
+ }
31
+ else if (value && typeof value === "object" && !Array.isArray(value)) {
32
+ resolved[key] = resolveSecretsInConfig(value);
33
+ }
34
+ }
35
+ return resolved;
36
+ }
37
+ export class EnvironmentManager {
38
+ constructor(configPath) {
39
+ this.environments = new Map();
40
+ this.connections = new Map();
41
+ // Try to load from config file first
42
+ if (configPath) {
43
+ this.loadFromFile(configPath);
44
+ }
45
+ else {
46
+ // Fall back to environment variables for single environment
47
+ this.loadFromEnvVars();
48
+ }
49
+ }
50
+ loadFromFile(configPath) {
51
+ try {
52
+ const resolvedPath = path.resolve(configPath);
53
+ if (!fs.existsSync(resolvedPath)) {
54
+ console.warn(`Environment config file not found at ${resolvedPath}, falling back to env vars`);
55
+ this.loadFromEnvVars();
56
+ return;
57
+ }
58
+ const configContent = fs.readFileSync(resolvedPath, "utf-8");
59
+ const config = JSON.parse(configContent);
60
+ this.defaultEnvironment = config.defaultEnvironment;
61
+ for (const env of config.environments) {
62
+ // Resolve any secret placeholders in the config
63
+ const resolvedEnv = resolveSecretsInConfig(env);
64
+ this.environments.set(resolvedEnv.name, resolvedEnv);
65
+ }
66
+ console.log(`Loaded ${this.environments.size} environment(s) from ${resolvedPath}`);
67
+ }
68
+ catch (error) {
69
+ console.error(`Failed to load environment config: ${error}`);
70
+ this.loadFromEnvVars();
71
+ }
72
+ }
73
+ loadFromEnvVars() {
74
+ const server = process.env.SERVER_NAME;
75
+ const database = process.env.DATABASE_NAME;
76
+ if (!server || !database) {
77
+ throw new Error("No environment config file provided and SERVER_NAME/DATABASE_NAME env vars not set");
78
+ }
79
+ const defaultEnv = {
80
+ name: "default",
81
+ server,
82
+ database,
83
+ port: process.env.SQL_PORT ? parseInt(process.env.SQL_PORT, 10) : undefined,
84
+ authMode: process.env.SQL_AUTH_MODE?.toLowerCase() ?? "aad",
85
+ username: process.env.SQL_USERNAME,
86
+ password: process.env.SQL_PASSWORD,
87
+ domain: process.env.SQL_DOMAIN,
88
+ trustServerCertificate: process.env.TRUST_SERVER_CERTIFICATE?.toLowerCase() === "true",
89
+ connectionTimeout: process.env.CONNECTION_TIMEOUT
90
+ ? parseInt(process.env.CONNECTION_TIMEOUT, 10)
91
+ : 30,
92
+ readonly: process.env.READONLY === "true",
93
+ };
94
+ this.environments.set("default", defaultEnv);
95
+ this.defaultEnvironment = "default";
96
+ console.log("Loaded default environment from environment variables");
97
+ }
98
+ getEnvironment(name) {
99
+ const targetName = name || this.defaultEnvironment || "default";
100
+ const env = this.environments.get(targetName);
101
+ if (!env) {
102
+ throw new Error(`Environment '${targetName}' not found. Available: ${Array.from(this.environments.keys()).join(", ")}`);
103
+ }
104
+ return env;
105
+ }
106
+ listEnvironments() {
107
+ return Array.from(this.environments.values());
108
+ }
109
+ /**
110
+ * Check if the environment allows access to a specific database.
111
+ * For database-level access, only the configured database is allowed.
112
+ * For server-level access, checks allowedDatabases/deniedDatabases.
113
+ */
114
+ isDatabaseAllowed(environmentName, databaseName) {
115
+ const env = this.getEnvironment(environmentName);
116
+ const accessLevel = env.accessLevel ?? "database";
117
+ // Database-level access: only the configured database is allowed
118
+ if (accessLevel === "database") {
119
+ if (databaseName.toLowerCase() !== env.database.toLowerCase()) {
120
+ return {
121
+ allowed: false,
122
+ reason: `Environment '${env.name}' has database-level access and is restricted to database '${env.database}'. Cannot access '${databaseName}'.`,
123
+ };
124
+ }
125
+ return { allowed: true };
126
+ }
127
+ // Server-level access: check allow/deny lists
128
+ const deniedDatabases = env.deniedDatabases ?? [];
129
+ const allowedDatabases = env.allowedDatabases;
130
+ // Check denied list first (takes precedence)
131
+ if (deniedDatabases.some((db) => db.toLowerCase() === databaseName.toLowerCase())) {
132
+ return {
133
+ allowed: false,
134
+ reason: `Database '${databaseName}' is in the denied list for environment '${env.name}'.`,
135
+ };
136
+ }
137
+ // Check allowed list
138
+ if (allowedDatabases === "*") {
139
+ return { allowed: true };
140
+ }
141
+ if (Array.isArray(allowedDatabases) && allowedDatabases.length > 0) {
142
+ if (!allowedDatabases.some((db) => db.toLowerCase() === databaseName.toLowerCase())) {
143
+ return {
144
+ allowed: false,
145
+ reason: `Database '${databaseName}' is not in the allowed list for environment '${env.name}'. Allowed: ${allowedDatabases.join(", ")}.`,
146
+ };
147
+ }
148
+ }
149
+ return { allowed: true };
150
+ }
151
+ /**
152
+ * Check if a schema.table reference is allowed based on allowedSchemas/deniedSchemas.
153
+ * Pattern matching supports wildcards (e.g., "audit.*", "*.sensitive_*")
154
+ */
155
+ isSchemaAllowed(environmentName, schemaName, tableName) {
156
+ const env = this.getEnvironment(environmentName);
157
+ const fullRef = tableName ? `${schemaName}.${tableName}` : schemaName;
158
+ const deniedSchemas = env.deniedSchemas ?? [];
159
+ const allowedSchemas = env.allowedSchemas;
160
+ // Check denied patterns first
161
+ for (const pattern of deniedSchemas) {
162
+ if (this.matchesPattern(fullRef, pattern) || this.matchesPattern(schemaName, pattern)) {
163
+ return {
164
+ allowed: false,
165
+ reason: `Schema/table '${fullRef}' matches denied pattern '${pattern}' in environment '${env.name}'.`,
166
+ };
167
+ }
168
+ }
169
+ // If allowedSchemas is specified, check against it
170
+ if (allowedSchemas && allowedSchemas.length > 0) {
171
+ const isAllowed = allowedSchemas.some((pattern) => this.matchesPattern(fullRef, pattern) || this.matchesPattern(schemaName, pattern));
172
+ if (!isAllowed) {
173
+ return {
174
+ allowed: false,
175
+ reason: `Schema/table '${fullRef}' does not match any allowed pattern in environment '${env.name}'. Allowed: ${allowedSchemas.join(", ")}.`,
176
+ };
177
+ }
178
+ }
179
+ return { allowed: true };
180
+ }
181
+ /**
182
+ * Simple wildcard pattern matching (supports * as wildcard)
183
+ */
184
+ matchesPattern(value, pattern) {
185
+ const regexPattern = pattern
186
+ .replace(/[.+?^${}()|[\]\\]/g, "\\$&") // Escape special regex chars except *
187
+ .replace(/\*/g, ".*"); // Convert * to .*
188
+ const regex = new RegExp(`^${regexPattern}$`, "i");
189
+ return regex.test(value);
190
+ }
191
+ async getConnection(environmentName) {
192
+ const env = this.getEnvironment(environmentName);
193
+ const cached = this.connections.get(env.name);
194
+ // Check if we have a valid cached connection
195
+ if (cached &&
196
+ cached.pool.connected &&
197
+ (!cached.expiresOn || cached.expiresOn > new Date(Date.now() + 2 * 60 * 1000))) {
198
+ return cached.pool;
199
+ }
200
+ // Create new connection
201
+ const { config, expiresOn } = await this.createSqlConfig(env);
202
+ // Close old connection if exists
203
+ if (cached?.pool && cached.pool.connected) {
204
+ await cached.pool.close();
205
+ }
206
+ const pool = await sql.connect(config);
207
+ this.connections.set(env.name, { pool, expiresOn });
208
+ return pool;
209
+ }
210
+ async createSqlConfig(env) {
211
+ const baseConfig = {
212
+ server: env.server,
213
+ database: env.database,
214
+ port: env.port,
215
+ connectionTimeout: (env.connectionTimeout || 30) * 1000,
216
+ };
217
+ if (env.authMode === "sql") {
218
+ if (!env.username || !env.password) {
219
+ throw new Error(`Environment '${env.name}' requires username and password for SQL auth`);
220
+ }
221
+ return {
222
+ config: {
223
+ ...baseConfig,
224
+ user: env.username,
225
+ password: env.password,
226
+ options: {
227
+ encrypt: false,
228
+ trustServerCertificate: env.trustServerCertificate ?? false,
229
+ },
230
+ },
231
+ };
232
+ }
233
+ if (env.authMode === "windows") {
234
+ if (!env.username || !env.password) {
235
+ throw new Error(`Environment '${env.name}' requires username and password for Windows auth`);
236
+ }
237
+ return {
238
+ config: {
239
+ ...baseConfig,
240
+ options: {
241
+ encrypt: false,
242
+ trustServerCertificate: env.trustServerCertificate ?? false,
243
+ },
244
+ authentication: {
245
+ type: "ntlm",
246
+ options: {
247
+ userName: env.username,
248
+ password: env.password,
249
+ domain: env.domain || "",
250
+ },
251
+ },
252
+ },
253
+ };
254
+ }
255
+ // Azure AD auth
256
+ const credential = new InteractiveBrowserCredential({
257
+ redirectUri: "http://localhost",
258
+ });
259
+ const accessToken = await credential.getToken("https://database.windows.net/.default");
260
+ if (!accessToken?.token) {
261
+ throw new Error(`Failed to acquire Azure AD token for environment '${env.name}'`);
262
+ }
263
+ return {
264
+ config: {
265
+ ...baseConfig,
266
+ options: {
267
+ encrypt: true,
268
+ trustServerCertificate: env.trustServerCertificate ?? false,
269
+ },
270
+ authentication: {
271
+ type: "azure-active-directory-access-token",
272
+ options: {
273
+ token: accessToken.token,
274
+ },
275
+ },
276
+ },
277
+ expiresOn: accessToken?.expiresOnTimestamp
278
+ ? new Date(accessToken.expiresOnTimestamp)
279
+ : new Date(Date.now() + 30 * 60 * 1000),
280
+ };
281
+ }
282
+ async closeAll() {
283
+ for (const [name, { pool }] of this.connections.entries()) {
284
+ if (pool.connected) {
285
+ await pool.close();
286
+ console.log(`Closed connection for environment '${name}'`);
287
+ }
288
+ }
289
+ this.connections.clear();
290
+ }
291
+ }
292
+ // Singleton instance
293
+ let environmentManager;
294
+ export function getEnvironmentManager() {
295
+ if (!environmentManager) {
296
+ const configPath = process.env.ENVIRONMENTS_CONFIG_PATH;
297
+ environmentManager = new EnvironmentManager(configPath);
298
+ }
299
+ return environmentManager;
300
+ }
301
+ //# sourceMappingURL=EnvironmentManager.js.map