@kingsnow129/database-mcp 0.4.0 → 0.4.2

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 (3) hide show
  1. package/README.md +13 -5
  2. package/dist/server.js +90 -18
  3. package/package.json +4 -1
package/README.md CHANGED
@@ -13,11 +13,17 @@ It provides tools for:
13
13
  ## Release
14
14
 
15
15
  Current release:
16
- - NPM package: `@kingsnow129/database-mcp@0.4.0`
16
+ - NPM package: `@kingsnow129/database-mcp@0.4.2`
17
17
  - MCP server name: `database-mcp`
18
- - VSIX helper: `database-mcp-helper@0.4.0`
18
+ - VSIX helper: `database-mcp-helper@0.4.2`
19
19
 
20
- ## What Is New In 0.4.0
20
+ ## What Is New In 0.4.2
21
+
22
+ - Added SQL Server Windows integrated auth support with `integratedAuth`.
23
+ - Added `--integratedAuth` CLI override handling in `connect` flow.
24
+ - Added optional dependency `msnodesqlv8` for integrated auth SQL Server driver.
25
+
26
+ ## What Was Introduced In 0.4.0
21
27
 
22
28
  - Multi-database profile model (`servers` + `databases`) is now the primary config.
23
29
  - Automatic profile resolution during `connect`:
@@ -111,14 +117,16 @@ Build and install locally:
111
117
  cd vscode-extension
112
118
  npm install
113
119
  npm run package
114
- code --install-extension database-mcp-helper-0.4.0.vsix --force
120
+ code --install-extension database-mcp-helper-0.4.2.vsix --force
115
121
  ```
116
122
 
117
123
  ## Safety Defaults
118
124
 
119
- - Read-only mode defaults to `true`
125
+ - Read-only mode is hard-enforced (`connect.readOnly` overrides are ignored)
120
126
  - Query tool only accepts one SELECT statement
121
127
  - Semicolons are blocked
128
+ - Stored procedure execution (`exec`, `execute`, `sp_*`, `xp_*`) is blocked
129
+ - Function-call style execution patterns like `schema.fn(...)` are blocked
122
130
  - Returned rows are capped (default `200`)
123
131
 
124
132
  ## Development
package/dist/server.js CHANGED
@@ -17,9 +17,12 @@ dotenv.config();
17
17
 
18
18
  const {Pool: PostgresPool}=pg;
19
19
 
20
+ let sqlMsnodesqlv8Cache;
21
+
20
22
  const SUPPORTED_ENGINES=new Set(["sqlserver","postgres","mysql"]);
21
23
 
22
24
  const CLI_OPTION_MAP={
25
+ target: "target",
23
26
  alias: "alias",
24
27
  serverName: "serverName",
25
28
  defaultAlias: "defaultAlias",
@@ -34,6 +37,7 @@ const CLI_OPTION_MAP={
34
37
  database: "database",
35
38
  user: "user",
36
39
  password: "password",
40
+ integratedAuth: "integratedAuth",
37
41
  encrypt: "encrypt",
38
42
  ssl: "ssl",
39
43
  trustServerCertificate: "trustServerCertificate",
@@ -68,7 +72,7 @@ function parseCliArgs(argv) {
68
72
 
69
73
  const rawToken=String(token).slice(2);
70
74
  if(rawToken==="help") {
71
- console.error("Supported flags: --alias --serverName --defaultAlias --profilesFile --currentServer --currentDatabase --engine --host --connectionString --server --port --database --user --password --encrypt --ssl --trustServerCertificate --readOnly --maxRows");
75
+ console.error("Supported flags: --target --alias --serverName --defaultAlias --profilesFile --currentServer --currentDatabase --engine --host --connectionString --server --port --database --user --password --integratedAuth --encrypt --ssl --trustServerCertificate --readOnly --maxRows");
72
76
  process.exit(0);
73
77
  }
74
78
 
@@ -194,6 +198,16 @@ function resolveSavedProfile(overrides={}) {
194
198
  const profiles=loadProfiles();
195
199
  const {servers,aliases}=profiles;
196
200
 
201
+ const requestedTarget=firstDefined(overrides.target,cliOptions.target);
202
+ const matchServerByHost=(hostOrServer) => {
203
+ const wanted=normalizeHostForCompare(hostOrServer);
204
+ return Object.entries(servers).find(([,cfg]) => {
205
+ const hostA=normalizeHostForCompare(cfg?.host);
206
+ const hostB=normalizeHostForCompare(cfg?.server);
207
+ return wanted===hostA||wanted===hostB;
208
+ });
209
+ };
210
+
197
211
  const requestedAlias=firstDefined(overrides.alias,cliOptions.alias,process.env.DB_ALIAS);
198
212
  const requestedServerName=firstDefined(
199
213
  overrides.serverName,
@@ -209,6 +223,26 @@ function resolveSavedProfile(overrides={}) {
209
223
  let serverName;
210
224
  let serverProfile;
211
225
 
226
+ if(requestedTarget) {
227
+ const targetKey=String(requestedTarget).trim();
228
+ if(aliases[targetKey]&&typeof aliases[targetKey]==="object") {
229
+ source="target:alias";
230
+ aliasName=targetKey;
231
+ serverProfile=aliases[targetKey];
232
+ } else if(servers[targetKey]&&typeof servers[targetKey]==="object") {
233
+ source="target:serverName";
234
+ serverName=targetKey;
235
+ serverProfile=servers[targetKey];
236
+ } else {
237
+ const foundByTargetHost=matchServerByHost(targetKey);
238
+ if(foundByTargetHost) {
239
+ source="target:host";
240
+ serverName=foundByTargetHost[0];
241
+ serverProfile=foundByTargetHost[1];
242
+ }
243
+ }
244
+ }
245
+
212
246
  if(requestedAlias) {
213
247
  const aliasKey=String(requestedAlias).trim();
214
248
  if(aliases[aliasKey]&&typeof aliases[aliasKey]==="object") {
@@ -232,12 +266,7 @@ function resolveSavedProfile(overrides={}) {
232
266
  }
233
267
 
234
268
  if(!serverProfile&&requestedHost) {
235
- const wanted=normalizeHostForCompare(requestedHost);
236
- const found=Object.entries(servers).find(([,cfg]) => {
237
- const hostA=normalizeHostForCompare(cfg?.host);
238
- const hostB=normalizeHostForCompare(cfg?.server);
239
- return wanted===hostA||wanted===hostB;
240
- });
269
+ const found=matchServerByHost(requestedHost);
241
270
  if(found) {
242
271
  source="host";
243
272
  serverName=found[0];
@@ -386,7 +415,12 @@ function buildConfig(overrides={}) {
386
415
  );
387
416
 
388
417
  const integratedAuth=boolFromEnv(
389
- firstDefined(overrides.integratedAuth,serverProfile?.integratedAuth,process.env.DB_INTEGRATED_AUTH),
418
+ firstDefined(
419
+ overrides.integratedAuth,
420
+ cliOptions.integratedAuth,
421
+ serverProfile?.integratedAuth,
422
+ process.env.DB_INTEGRATED_AUTH
423
+ ),
390
424
  false
391
425
  );
392
426
 
@@ -402,7 +436,8 @@ function buildConfig(overrides={}) {
402
436
  user: integratedAuth? "":user,
403
437
  password: integratedAuth? "":password,
404
438
  connectionString,
405
- readOnly: boolFromEnv(firstDefined(overrides.readOnly,dbConfig?.readOnly,serverProfile?.readOnly),getReadOnlyDefault()),
439
+ // Enforce hard read-only mode regardless of caller overrides.
440
+ readOnly: true,
406
441
  maxRows: intFromEnv(firstDefined(overrides.maxRows,dbConfig?.maxRows,serverProfile?.maxRows),getMaxRowsDefault()),
407
442
  trustServerCertificate: boolFromEnv(
408
443
  firstDefined(
@@ -476,12 +511,9 @@ function createSqlServerConfig(config) {
476
511
  };
477
512
 
478
513
  if(config.integratedAuth) {
479
- baseConfig.authentication={
480
- type: "default",
481
- options: {
482
- userName: undefined,
483
- password: undefined
484
- }
514
+ baseConfig.options={
515
+ ...baseConfig.options,
516
+ trustedConnection: true
485
517
  };
486
518
  } else {
487
519
  baseConfig.user=config.user;
@@ -491,6 +523,36 @@ function createSqlServerConfig(config) {
491
523
  return baseConfig;
492
524
  }
493
525
 
526
+ function getSqlServerDriver(config) {
527
+ if(!config.integratedAuth) {
528
+ return {
529
+ module: sql,
530
+ driverName: "tedious"
531
+ };
532
+ }
533
+
534
+ if(process.platform!=="win32") {
535
+ throw new Error("SQL Server integratedAuth is only supported on Windows hosts.");
536
+ }
537
+
538
+ if(sqlMsnodesqlv8Cache===undefined) {
539
+ try {
540
+ sqlMsnodesqlv8Cache=_require("mssql/msnodesqlv8");
541
+ } catch {
542
+ sqlMsnodesqlv8Cache=null;
543
+ }
544
+ }
545
+
546
+ if(!sqlMsnodesqlv8Cache) {
547
+ throw new Error("Integrated auth requires the optional dependency 'msnodesqlv8'. Install it and restart MCP.");
548
+ }
549
+
550
+ return {
551
+ module: sqlMsnodesqlv8Cache,
552
+ driverName: "msnodesqlv8"
553
+ };
554
+ }
555
+
494
556
  function createPostgresConfig(config) {
495
557
  if(config.connectionString) {
496
558
  return {
@@ -643,9 +705,10 @@ async function connectPool(overrides={}) {
643
705
  await closePoolIfAny();
644
706
 
645
707
  if(config.engine==="sqlserver") {
708
+ const sqlDriver=getSqlServerDriver(config);
646
709
  const sqlConfig=createSqlServerConfig(config);
647
- const client=await new sql.ConnectionPool(sqlConfig).connect();
648
- connection={engine: "sqlserver",client};
710
+ const client=await new sqlDriver.module.ConnectionPool(sqlConfig).connect();
711
+ connection={engine: "sqlserver",client,sqlDriver: sqlDriver.driverName};
649
712
  } else if(config.engine==="postgres") {
650
713
  const pgConfig=createPostgresConfig(config);
651
714
  const client=new PostgresPool(pgConfig);
@@ -670,8 +733,10 @@ async function connectPool(overrides={}) {
670
733
  serverName: config.serverName,
671
734
  profileSource: config.profileSource,
672
735
  engine: config.engine,
736
+ sqlDriver: connection?.sqlDriver,
673
737
  host: config.host,
674
738
  database: config.database,
739
+ integratedAuth: config.integratedAuth,
675
740
  readOnly: runtimeSettings.readOnly,
676
741
  maxRows: runtimeSettings.maxRows
677
742
  };
@@ -739,6 +804,9 @@ function validateQuerySafety(sqlText) {
739
804
 
740
805
  const blockedPatterns=[
741
806
  /\b(insert|update|delete|drop|alter|create|truncate|merge|exec|execute|grant|revoke)\b/i,
807
+ /\b(call|declare)\b/i,
808
+ /\b(?:sp_|xp_)[A-Za-z0-9_]*\b/i,
809
+ /\b[A-Za-z0-9_]+\.[A-Za-z0-9_]+\s*\(/i,
742
810
  /--/,
743
811
  /\/\*/
744
812
  ];
@@ -756,7 +824,9 @@ async function handleToolCall(name,args={}) {
756
824
  switch(name) {
757
825
  case TOOL_NAMES.CONNECT: {
758
826
  const overrides={
827
+ ...(args.target? {target: args.target}:{}),
759
828
  ...(args.alias? {alias: args.alias}:{}),
829
+ ...(args.serverName? {serverName: args.serverName}:{}),
760
830
  ...(args.engine? {engine: args.engine}:{}),
761
831
  ...(args.connectionString? {connectionString: args.connectionString}:{}),
762
832
  ...(args.host? {host: args.host}:{}),
@@ -765,7 +835,7 @@ async function handleToolCall(name,args={}) {
765
835
  ...(args.database? {database: args.database}:{}),
766
836
  ...(args.user? {user: args.user}:{}),
767
837
  ...(args.password? {password: args.password}:{}),
768
- ...(args.readOnly!==undefined? {readOnly: Boolean(args.readOnly)}:{}),
838
+ ...(args.integratedAuth!==undefined? {integratedAuth: Boolean(args.integratedAuth)}:{}),
769
839
  ...(args.maxRows!==undefined? {maxRows: Number(args.maxRows)}:{}),
770
840
  ...(args.encrypt!==undefined? {encrypt: Boolean(args.encrypt)}:{}),
771
841
  ...(args.ssl!==undefined? {ssl: Boolean(args.ssl)}:{}),
@@ -969,6 +1039,7 @@ server.setRequestHandler(ListToolsRequestSchema,async () => {
969
1039
  inputSchema: {
970
1040
  type: "object",
971
1041
  properties: {
1042
+ target: {type: "string"},
972
1043
  alias: {type: "string"},
973
1044
  serverName: {type: "string"},
974
1045
  engine: {type: "string",enum: ["sqlserver","postgres","mysql"]},
@@ -979,6 +1050,7 @@ server.setRequestHandler(ListToolsRequestSchema,async () => {
979
1050
  database: {type: "string"},
980
1051
  user: {type: "string"},
981
1052
  password: {type: "string"},
1053
+ integratedAuth: {type: "boolean"},
982
1054
  encrypt: {type: "boolean"},
983
1055
  ssl: {type: "boolean"},
984
1056
  trustServerCertificate: {type: "boolean"},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kingsnow129/database-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "mcpName": "io.github.kingsnow129/database-mcp",
5
5
  "description": "Database MCP server for SQL Server, PostgreSQL, and MySQL with profile-based auto resolution",
6
6
  "author": "kingsnow129",
@@ -51,5 +51,8 @@
51
51
  "mysql2": "^3.11.3",
52
52
  "mssql": "^11.0.1",
53
53
  "pg": "^8.13.1"
54
+ },
55
+ "optionalDependencies": {
56
+ "msnodesqlv8": "^4.4.0"
54
57
  }
55
58
  }