@connorbritain/mssql-mcp-writer 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 (86) hide show
  1. package/README.md +109 -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 +705 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/tools/DeleteDataTool.d.ts +56 -0
  19. package/dist/tools/DeleteDataTool.d.ts.map +1 -0
  20. package/dist/tools/DeleteDataTool.js +103 -0
  21. package/dist/tools/DeleteDataTool.js.map +1 -0
  22. package/dist/tools/DescribeTableTool.d.ts +32 -0
  23. package/dist/tools/DescribeTableTool.d.ts.map +1 -0
  24. package/dist/tools/DescribeTableTool.js +108 -0
  25. package/dist/tools/DescribeTableTool.js.map +1 -0
  26. package/dist/tools/ExplainQueryTool.d.ts +24 -0
  27. package/dist/tools/ExplainQueryTool.d.ts.map +1 -0
  28. package/dist/tools/ExplainQueryTool.js +98 -0
  29. package/dist/tools/ExplainQueryTool.js.map +1 -0
  30. package/dist/tools/InsertDataTool.d.ts +17 -0
  31. package/dist/tools/InsertDataTool.d.ts.map +1 -0
  32. package/dist/tools/InsertDataTool.js +102 -0
  33. package/dist/tools/InsertDataTool.js.map +1 -0
  34. package/dist/tools/InspectDependenciesTool.d.ts +45 -0
  35. package/dist/tools/InspectDependenciesTool.d.ts.map +1 -0
  36. package/dist/tools/InspectDependenciesTool.js +215 -0
  37. package/dist/tools/InspectDependenciesTool.js.map +1 -0
  38. package/dist/tools/ListDatabasesTool.d.ts +27 -0
  39. package/dist/tools/ListDatabasesTool.d.ts.map +1 -0
  40. package/dist/tools/ListDatabasesTool.js +107 -0
  41. package/dist/tools/ListDatabasesTool.js.map +1 -0
  42. package/dist/tools/ListEnvironmentsTool.d.ts +49 -0
  43. package/dist/tools/ListEnvironmentsTool.d.ts.map +1 -0
  44. package/dist/tools/ListEnvironmentsTool.js +73 -0
  45. package/dist/tools/ListEnvironmentsTool.js.map +1 -0
  46. package/dist/tools/ListScriptsTool.d.ts +41 -0
  47. package/dist/tools/ListScriptsTool.d.ts.map +1 -0
  48. package/dist/tools/ListScriptsTool.js +86 -0
  49. package/dist/tools/ListScriptsTool.js.map +1 -0
  50. package/dist/tools/ListTableTool.d.ts +24 -0
  51. package/dist/tools/ListTableTool.d.ts.map +1 -0
  52. package/dist/tools/ListTableTool.js +85 -0
  53. package/dist/tools/ListTableTool.js.map +1 -0
  54. package/dist/tools/ProfileTableTool.d.ts +78 -0
  55. package/dist/tools/ProfileTableTool.d.ts.map +1 -0
  56. package/dist/tools/ProfileTableTool.js +372 -0
  57. package/dist/tools/ProfileTableTool.js.map +1 -0
  58. package/dist/tools/ReadDataTool.d.ts +61 -0
  59. package/dist/tools/ReadDataTool.d.ts.map +1 -0
  60. package/dist/tools/ReadDataTool.js +299 -0
  61. package/dist/tools/ReadDataTool.js.map +1 -0
  62. package/dist/tools/RelationshipInspectorTool.d.ts +46 -0
  63. package/dist/tools/RelationshipInspectorTool.d.ts.map +1 -0
  64. package/dist/tools/RelationshipInspectorTool.js +155 -0
  65. package/dist/tools/RelationshipInspectorTool.js.map +1 -0
  66. package/dist/tools/RunScriptTool.d.ts +215 -0
  67. package/dist/tools/RunScriptTool.d.ts.map +1 -0
  68. package/dist/tools/RunScriptTool.js +177 -0
  69. package/dist/tools/RunScriptTool.js.map +1 -0
  70. package/dist/tools/SearchSchemaTool.d.ts +88 -0
  71. package/dist/tools/SearchSchemaTool.d.ts.map +1 -0
  72. package/dist/tools/SearchSchemaTool.js +236 -0
  73. package/dist/tools/SearchSchemaTool.js.map +1 -0
  74. package/dist/tools/TestConnectionTool.d.ts +36 -0
  75. package/dist/tools/TestConnectionTool.d.ts.map +1 -0
  76. package/dist/tools/TestConnectionTool.js +155 -0
  77. package/dist/tools/TestConnectionTool.js.map +1 -0
  78. package/dist/tools/UpdateDataTool.d.ts +61 -0
  79. package/dist/tools/UpdateDataTool.d.ts.map +1 -0
  80. package/dist/tools/UpdateDataTool.js +117 -0
  81. package/dist/tools/UpdateDataTool.js.map +1 -0
  82. package/dist/tools/ValidateEnvironmentConfigTool.d.ts +37 -0
  83. package/dist/tools/ValidateEnvironmentConfigTool.d.ts.map +1 -0
  84. package/dist/tools/ValidateEnvironmentConfigTool.js +230 -0
  85. package/dist/tools/ValidateEnvironmentConfigTool.js.map +1 -0
  86. package/package.json +42 -0
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # MSSQL MCP Writer
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@connorbritain/mssql-mcp-writer.svg)](https://www.npmjs.com/package/@connorbritain/mssql-mcp-writer)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ **Model Context Protocol server for Microsoft SQL Server with read and data operations.**
7
+
8
+ Full data manipulation capabilities (INSERT, UPDATE, DELETE) with preview/confirm safeguards, but no DDL operations. Ideal for data engineers and ETL workflows where you need to modify data but want to prevent schema changes.
9
+
10
+ ## Package Tiers
11
+
12
+ | Package | npm | Tools | Use Case |
13
+ |---------|-----|-------|----------|
14
+ | **[mssql-mcp-reader](https://github.com/ConnorBritain/mssql-mcp-reader)** | `@connorbritain/mssql-mcp-reader` | 14 read-only | Analysts, auditors, safe exploration |
15
+ | **mssql-mcp-writer** (this) | `@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
+ | **Data Read** | `read_data` (SELECT only) |
27
+ | **Data Write** | `insert_data`, `update_data`, `delete_data` (with preview/confirm) |
28
+ | **Scripts** | `list_scripts`, `run_script` |
29
+ | **Operations** | `test_connection`, `validate_environment_config` |
30
+
31
+ **Not included:** `create_table`, `create_index`, `drop_table` (DDL operations)
32
+
33
+ ---
34
+
35
+ ## Quick Start
36
+
37
+ ### Install
38
+
39
+ ```bash
40
+ npm install -g @connorbritain/mssql-mcp-writer@latest
41
+ ```
42
+
43
+ ### MCP Client Configuration
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "mssql": {
49
+ "command": "npx",
50
+ "args": ["@connorbritain/mssql-mcp-writer@latest"],
51
+ "env": {
52
+ "SERVER_NAME": "127.0.0.1",
53
+ "DATABASE_NAME": "mydb",
54
+ "SQL_AUTH_MODE": "sql",
55
+ "SQL_USERNAME": "app_user",
56
+ "SQL_PASSWORD": "YourPassword123"
57
+ }
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Configuration
66
+
67
+ | Variable | Required | Notes |
68
+ |----------|----------|-------|
69
+ | `SERVER_NAME` | Yes | SQL Server hostname/IP |
70
+ | `DATABASE_NAME` | Yes | Target database |
71
+ | `SQL_AUTH_MODE` | | `sql`, `windows`, or `aad` (default: `aad`) |
72
+ | `SQL_USERNAME` / `SQL_PASSWORD` | | Required for `sql`/`windows` modes |
73
+ | `READONLY` | | `true` disables write tools |
74
+ | `ENVIRONMENTS_CONFIG_PATH` | | Path to multi-environment JSON config |
75
+ | `SCRIPTS_PATH` | | Path to named SQL scripts directory |
76
+ | `AUDIT_LOG_PATH` | | Custom audit log path |
77
+
78
+ ---
79
+
80
+ ## Features
81
+
82
+ All packages in the MSSQL MCP family share:
83
+
84
+ - **Multi-environment support** - Named database environments (prod, staging, dev) with per-environment policies
85
+ - **Governance controls** - `allowedTools`, `deniedTools`, `allowedSchemas`, `deniedSchemas`, `requireApproval`
86
+ - **Audit logging** - JSON Lines logs with session IDs and auto-redaction
87
+ - **Secret management** - `${secret:NAME}` placeholders for secure credential handling
88
+ - **Named SQL scripts** - Pre-approved parameterized queries with governance controls
89
+ - **Preview/confirm for mutations** - `update_data` and `delete_data` show affected rows before execution
90
+
91
+ ---
92
+
93
+ ## Documentation
94
+
95
+ Full documentation, configuration examples, and governance details are available in the main repository:
96
+
97
+ **[MSSQL MCP Server Documentation](https://github.com/ConnorBritain/mssql-mcp-server#readme)**
98
+
99
+ ---
100
+
101
+ ## License
102
+
103
+ MIT License. See [LICENSE](./LICENSE) for details.
104
+
105
+ ---
106
+
107
+ **Repository:** https://github.com/ConnorBritain/mssql-mcp-writer
108
+ **Issues:** https://github.com/ConnorBritain/mssql-mcp-writer/issues
109
+ **npm:** https://www.npmjs.com/package/@connorbritain/mssql-mcp-writer
@@ -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