@kingsnow129/database-mcp 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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.1`
17
17
  - MCP server name: `database-mcp`
18
- - VSIX helper: `database-mcp-helper@0.4.0`
18
+ - VSIX helper: `database-mcp-helper@0.4.1`
19
19
 
20
- ## What Is New In 0.4.0
20
+ ## What Is New In 0.4.1
21
+
22
+ - Enforced hard read-only mode for all connections (no override).
23
+ - Added stronger query safety blocks for execution patterns.
24
+ - Allowed spaces in server profile names in VSIX manager (e.g. `QA EUD`).
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.1.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
@@ -20,6 +20,7 @@ const {Pool: PostgresPool}=pg;
20
20
  const SUPPORTED_ENGINES=new Set(["sqlserver","postgres","mysql"]);
21
21
 
22
22
  const CLI_OPTION_MAP={
23
+ target: "target",
23
24
  alias: "alias",
24
25
  serverName: "serverName",
25
26
  defaultAlias: "defaultAlias",
@@ -68,7 +69,7 @@ function parseCliArgs(argv) {
68
69
 
69
70
  const rawToken=String(token).slice(2);
70
71
  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");
72
+ console.error("Supported flags: --target --alias --serverName --defaultAlias --profilesFile --currentServer --currentDatabase --engine --host --connectionString --server --port --database --user --password --encrypt --ssl --trustServerCertificate --readOnly --maxRows");
72
73
  process.exit(0);
73
74
  }
74
75
 
@@ -194,6 +195,16 @@ function resolveSavedProfile(overrides={}) {
194
195
  const profiles=loadProfiles();
195
196
  const {servers,aliases}=profiles;
196
197
 
198
+ const requestedTarget=firstDefined(overrides.target,cliOptions.target);
199
+ const matchServerByHost=(hostOrServer) => {
200
+ const wanted=normalizeHostForCompare(hostOrServer);
201
+ return Object.entries(servers).find(([,cfg]) => {
202
+ const hostA=normalizeHostForCompare(cfg?.host);
203
+ const hostB=normalizeHostForCompare(cfg?.server);
204
+ return wanted===hostA||wanted===hostB;
205
+ });
206
+ };
207
+
197
208
  const requestedAlias=firstDefined(overrides.alias,cliOptions.alias,process.env.DB_ALIAS);
198
209
  const requestedServerName=firstDefined(
199
210
  overrides.serverName,
@@ -209,6 +220,26 @@ function resolveSavedProfile(overrides={}) {
209
220
  let serverName;
210
221
  let serverProfile;
211
222
 
223
+ if(requestedTarget) {
224
+ const targetKey=String(requestedTarget).trim();
225
+ if(aliases[targetKey]&&typeof aliases[targetKey]==="object") {
226
+ source="target:alias";
227
+ aliasName=targetKey;
228
+ serverProfile=aliases[targetKey];
229
+ } else if(servers[targetKey]&&typeof servers[targetKey]==="object") {
230
+ source="target:serverName";
231
+ serverName=targetKey;
232
+ serverProfile=servers[targetKey];
233
+ } else {
234
+ const foundByTargetHost=matchServerByHost(targetKey);
235
+ if(foundByTargetHost) {
236
+ source="target:host";
237
+ serverName=foundByTargetHost[0];
238
+ serverProfile=foundByTargetHost[1];
239
+ }
240
+ }
241
+ }
242
+
212
243
  if(requestedAlias) {
213
244
  const aliasKey=String(requestedAlias).trim();
214
245
  if(aliases[aliasKey]&&typeof aliases[aliasKey]==="object") {
@@ -232,12 +263,7 @@ function resolveSavedProfile(overrides={}) {
232
263
  }
233
264
 
234
265
  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
- });
266
+ const found=matchServerByHost(requestedHost);
241
267
  if(found) {
242
268
  source="host";
243
269
  serverName=found[0];
@@ -402,7 +428,8 @@ function buildConfig(overrides={}) {
402
428
  user: integratedAuth? "":user,
403
429
  password: integratedAuth? "":password,
404
430
  connectionString,
405
- readOnly: boolFromEnv(firstDefined(overrides.readOnly,dbConfig?.readOnly,serverProfile?.readOnly),getReadOnlyDefault()),
431
+ // Enforce hard read-only mode regardless of caller overrides.
432
+ readOnly: true,
406
433
  maxRows: intFromEnv(firstDefined(overrides.maxRows,dbConfig?.maxRows,serverProfile?.maxRows),getMaxRowsDefault()),
407
434
  trustServerCertificate: boolFromEnv(
408
435
  firstDefined(
@@ -739,6 +766,9 @@ function validateQuerySafety(sqlText) {
739
766
 
740
767
  const blockedPatterns=[
741
768
  /\b(insert|update|delete|drop|alter|create|truncate|merge|exec|execute|grant|revoke)\b/i,
769
+ /\b(call|declare)\b/i,
770
+ /\b(?:sp_|xp_)[A-Za-z0-9_]*\b/i,
771
+ /\b[A-Za-z0-9_]+\.[A-Za-z0-9_]+\s*\(/i,
742
772
  /--/,
743
773
  /\/\*/
744
774
  ];
@@ -756,7 +786,9 @@ async function handleToolCall(name,args={}) {
756
786
  switch(name) {
757
787
  case TOOL_NAMES.CONNECT: {
758
788
  const overrides={
789
+ ...(args.target? {target: args.target}:{}),
759
790
  ...(args.alias? {alias: args.alias}:{}),
791
+ ...(args.serverName? {serverName: args.serverName}:{}),
760
792
  ...(args.engine? {engine: args.engine}:{}),
761
793
  ...(args.connectionString? {connectionString: args.connectionString}:{}),
762
794
  ...(args.host? {host: args.host}:{}),
@@ -765,7 +797,6 @@ async function handleToolCall(name,args={}) {
765
797
  ...(args.database? {database: args.database}:{}),
766
798
  ...(args.user? {user: args.user}:{}),
767
799
  ...(args.password? {password: args.password}:{}),
768
- ...(args.readOnly!==undefined? {readOnly: Boolean(args.readOnly)}:{}),
769
800
  ...(args.maxRows!==undefined? {maxRows: Number(args.maxRows)}:{}),
770
801
  ...(args.encrypt!==undefined? {encrypt: Boolean(args.encrypt)}:{}),
771
802
  ...(args.ssl!==undefined? {ssl: Boolean(args.ssl)}:{}),
@@ -969,6 +1000,7 @@ server.setRequestHandler(ListToolsRequestSchema,async () => {
969
1000
  inputSchema: {
970
1001
  type: "object",
971
1002
  properties: {
1003
+ target: {type: "string"},
972
1004
  alias: {type: "string"},
973
1005
  serverName: {type: "string"},
974
1006
  engine: {type: "string",enum: ["sqlserver","postgres","mysql"]},
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.1",
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",