capybara-db-mcp 1.1.1 → 1.1.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 -7
  2. package/dist/index.js +109 -22
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -34,12 +34,12 @@ git remote set-url origin https://github.com/ajgreyling/capybara-db-mcp.git
34
34
 
35
35
  ```mermaid
36
36
  flowchart LR
37
- subgraph clients["MCP Clients"]
37
+ subgraph clients["MCP Clients - Supported"]
38
38
  A[Claude Desktop]
39
39
  B[Claude Code]
40
40
  C[Cursor]
41
- D[VS Code]
42
- E[Copilot CLI]
41
+ D[Codex]
42
+ E[Gemini]
43
43
  end
44
44
 
45
45
  subgraph server["MCP Server"]
@@ -67,6 +67,12 @@ flowchart LR
67
67
  M --> Ma
68
68
  ```
69
69
 
70
+ ### Unsupported: VS Code / GitHub Copilot
71
+
72
+ **VS Code and GitHub Copilot are not supported** for capybara-db-mcp for security reasons. There is no project-level ignore file (such as `.cursorignore` or `.aiexclude`) that Copilot consistently reads to exclude `.safe-sql-results/` from AI context. Query result files may therefore be exposed to the LLM when using VS Code/Copilot, undermining the PII isolation design.
73
+
74
+ **Use of capybara-db-mcp in VS Code/Copilot is not recommended.** For PII-safe database workflows, use one of the supported editors that provide ignore mechanisms: **Cursor**, **Claude Code**, **Codex**, or **Gemini**.
75
+
70
76
  ## Security Model Overview
71
77
 
72
78
  capybara-db-mcp is designed to reduce the likelihood of transmitting query result data to an LLM by isolating result sets to the local filesystem and returning status-oriented metadata to the MCP client.
@@ -74,7 +80,7 @@ capybara-db-mcp is designed to reduce the likelihood of transmitting query resul
74
80
  - **1) LLM generates SQL**: The MCP client sends an `execute_sql` request containing SQL text.
75
81
  - **2) Connector is read-only**: Database connections are opened in read-only mode (PostgreSQL: `default_transaction_read_only`; SQLite: readonly file mode). Write attempts fail at the database level.
76
82
  - **3) Query executes against the database**: The query runs using the configured connector.
77
- - **4) Results are written locally**: Result sets are written to `.safe-sql-results/` and opened in the editor (configurable).
83
+ - **4) Results are written locally**: Result sets are written to `.safe-sql-results/` and opened in the editor when running in a supported AI editor (Cursor, Claude Code, Codex, Gemini).
78
84
  - **5) LLM receives metadata only**: The MCP tool response is formatted to avoid including raw query results in the response payload.
79
85
  - **6) Logging remains local**: Operational logs and diagnostic details are written locally.
80
86
 
@@ -119,14 +125,14 @@ capybara-db-mcp is a zero-dependency, token-efficient MCP server implementing th
119
125
 
120
126
  **Read-only enforcement**: Database connections are opened in read-only mode (PostgreSQL: `default_transaction_read_only`; SQLite: readonly file mode). UPDATE, DELETE, INSERT, and other write operations fail at the connection level. This reduces the risk of accidental writes but does not replace database-level RBAC or permissions configuration.
121
127
 
122
- **Output isolation controls**: By default, query results are written to local files (`.safe-sql-results/`) and opened in the editor; tool responses are formatted to avoid returning result sets. Error responses return generic messages only (e.g. "Execution failed. See server logs for details."); no SQL, parameter values, or database error text are returned. Logs never include SQL or parameter values. These mechanisms are designed to reduce LLM data exposure risk when used appropriately, and do not constitute regulatory compliance or replace enterprise data governance and DLP controls.
128
+ **Output isolation controls**: By default, query results are written to local files (`.safe-sql-results/`) and opened in the editor when running in a supported client (Cursor, Claude Code, Codex, Gemini); tool responses are formatted to avoid returning result sets. Error responses return generic messages only (e.g. "Execution failed. See server logs for details."); no SQL, parameter values, or database error text are returned. Logs never include SQL or parameter values. These mechanisms are designed to reduce LLM data exposure risk when used appropriately, and do not constitute regulatory compliance or replace enterprise data governance and DLP controls.
123
129
 
124
130
  - **Local Development First**: Zero dependency, token efficient with just two MCP tools to maximize context window
125
131
  - **Multi-Database**: PostgreSQL, MySQL, MariaDB, SQL Server, and SQLite through a single interface
126
132
  - **Multi-Connection**: Connect to multiple databases simultaneously with TOML configuration
127
133
  - **Default schema**: Use `--schema` (or TOML `schema = "..."`) so PostgreSQL uses that schema for `execute_sql` and `search_objects` is restricted to it (see below)
128
134
  - **Guardrails**: Connector-level read-only connections, row limiting, and a 60-second query timeout default (overridable per source via `query_timeout` in `dbhub.toml`) to reduce runaway operations
129
- - **Designed to reduce LLM data exposure**: Results are written to `.safe-sql-results/` and opened in the editor; tool responses return only success/failure metadata (no file path, row data, row counts, or column names). Error responses use generic messages only; no SQL, parameter values, or database error text reach the client. Logs are redacted to avoid SQL and parameter values.
135
+ - **Designed to reduce LLM data exposure**: Results are written to `.safe-sql-results/` and opened only in supported editors (Cursor, Claude Code, Codex, Gemini); tool responses return only success/failure metadata (no file path, row data, row counts, or column names). Error responses use generic messages only; no SQL, parameter values, or database error text reach the client. Logs are redacted to avoid SQL and parameter values.
130
136
  - **Secure Access**: SSH tunneling and SSL/TLS encryption
131
137
 
132
138
  ## Why Capybara?
@@ -187,7 +193,7 @@ Full DBHub docs (including TOML and command-line options) apply; see [dbhub.ai](
187
193
 
188
194
  ### Output isolation (designed to reduce LLM exposure)
189
195
 
190
- By default, `execute_sql` writes query results to `.safe-sql-results/` in your project directory and opens them in the editor. The MCP tool response sent back to the MCP client is formatted to return success/failure metadata rather than result sets. This reduces the likelihood of transmitting result data to an LLM, but it does not eliminate data handling risk and does not by itself satisfy regulatory or compliance requirements.
196
+ By default, `execute_sql` writes query results to `.safe-sql-results/` in your project directory and opens them in the editor when running in a supported AI editor (Cursor, Claude Code, Codex, Gemini). The MCP tool response sent back to the MCP client is formatted to return success/failure metadata rather than result sets. This reduces the likelihood of transmitting result data to an LLM, but it does not eliminate data handling risk and does not by itself satisfy regulatory or compliance requirements.
191
197
 
192
198
  To reduce exfiltration risk via dynamic SQL (e.g. `SELECT secret AS "password_is_hunter2"`), tool responses are formatted to avoid including file paths, row data, row counts, or column names. Error responses return generic messages only (e.g. "Execution failed. See server logs for details."); no SQL, parameter values, or database error text are returned. Logs never include SQL or parameter values.
193
199
 
package/dist/index.js CHANGED
@@ -2461,7 +2461,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2461
2461
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
2462
2462
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2463
2463
  import express from "express";
2464
- import path4 from "path";
2464
+ import path5 from "path";
2465
2465
  import { readFileSync as readFileSync3 } from "fs";
2466
2466
  import { fileURLToPath as fileURLToPath2 } from "url";
2467
2467
 
@@ -3972,8 +3972,10 @@ function getOutputFormat() {
3972
3972
 
3973
3973
  // src/config/editor-command.ts
3974
3974
  var DEFAULT_EDITOR = "cursor";
3975
+ var SUPPORTED_EDITORS = ["cursor", "claude", "codex", "gemini"];
3975
3976
  var editorCommand = DEFAULT_EDITOR;
3976
3977
  var explicitlySet = false;
3978
+ var openingResultsSupported = false;
3977
3979
  function setEditorCommand(cmd) {
3978
3980
  editorCommand = cmd;
3979
3981
  }
@@ -3986,17 +3988,82 @@ function setEditorExplicitly(value) {
3986
3988
  function isEditorExplicitlySet() {
3987
3989
  return explicitlySet;
3988
3990
  }
3991
+ function setOpeningResultsSupported(value) {
3992
+ openingResultsSupported = value;
3993
+ }
3994
+ function isOpeningResultsSupported() {
3995
+ return openingResultsSupported;
3996
+ }
3997
+ function isEditorSupportedForOpening(editor) {
3998
+ return SUPPORTED_EDITORS.includes(editor.toLowerCase());
3999
+ }
3989
4000
  function detectEditorFromClientName(name) {
3990
4001
  const lower = name.toLowerCase();
3991
4002
  if (lower.includes("cursor")) {
3992
4003
  return "cursor";
3993
4004
  }
3994
- if (lower.includes("vscode") || lower.includes("vs code") || lower.includes("visual studio code")) {
3995
- return "code";
4005
+ if (lower.includes("claude")) {
4006
+ return "claude";
4007
+ }
4008
+ if (lower.includes("codex")) {
4009
+ return "codex";
4010
+ }
4011
+ if (lower.includes("gemini")) {
4012
+ return "gemini";
4013
+ }
4014
+ if (lower.includes("vscode") || lower.includes("vs code") || lower.includes("visual studio code") || lower.includes("copilot")) {
4015
+ return null;
3996
4016
  }
3997
4017
  return null;
3998
4018
  }
3999
4019
 
4020
+ // src/utils/ignore-file-sync.ts
4021
+ import fs3 from "fs";
4022
+ import path3 from "path";
4023
+ var AI_IGNORE_FILES = [
4024
+ ".cursorignore",
4025
+ ".claudeignore",
4026
+ ".codeiumignore",
4027
+ ".geminiignore"
4028
+ ];
4029
+ var ENTRY = ".safe-sql-results/";
4030
+ var ENTRY_COMMENT = "# Query result files \u2014 contains PII data (names, SSNs, emails, DOBs, etc.)\n# These files must never be indexed, read, or included in AI context.";
4031
+ var NEGATION_PATTERN = /^\s*!\.safe-sql-results(\/.*)?\s*$/gm;
4032
+ var ENTRY_PRESENT = /\.safe-sql-results\/?(\s|$)/m;
4033
+ function removeNegationOverrides(content) {
4034
+ return content.replace(NEGATION_PATTERN, "").replace(/\n{3,}/g, "\n\n");
4035
+ }
4036
+ function hasEntry(content) {
4037
+ return ENTRY_PRESENT.test(content);
4038
+ }
4039
+ function ensureSafeSqlResultsInIgnoreFiles(projectRoot) {
4040
+ for (const fileName of AI_IGNORE_FILES) {
4041
+ const filePath = path3.join(projectRoot, fileName);
4042
+ const exists = fs3.existsSync(filePath);
4043
+ if (exists) {
4044
+ let content = fs3.readFileSync(filePath, "utf-8");
4045
+ content = removeNegationOverrides(content);
4046
+ if (!hasEntry(content)) {
4047
+ const trimmed = content.trimEnd();
4048
+ const appended = trimmed ? `${trimmed}
4049
+
4050
+ ${ENTRY}
4051
+ ` : `${ENTRY_COMMENT}
4052
+ ${ENTRY}
4053
+ `;
4054
+ fs3.writeFileSync(filePath, appended, "utf-8");
4055
+ } else {
4056
+ fs3.writeFileSync(filePath, content, "utf-8");
4057
+ }
4058
+ } else {
4059
+ const content = `${ENTRY_COMMENT}
4060
+ ${ENTRY}
4061
+ `;
4062
+ fs3.writeFileSync(filePath, content, "utf-8");
4063
+ }
4064
+ }
4065
+ }
4066
+
4000
4067
  // src/tools/execute-sql.ts
4001
4068
  import { z } from "zod";
4002
4069
 
@@ -4065,13 +4132,13 @@ function createToolSuccessResponse(data, meta = {}) {
4065
4132
 
4066
4133
  // src/utils/result-writer.ts
4067
4134
  import { exec } from "child_process";
4068
- import fs3 from "fs";
4069
- import path3 from "path";
4135
+ import fs4 from "fs";
4136
+ import path4 from "path";
4070
4137
  var OUTPUT_DIR = ".safe-sql-results";
4071
4138
  function ensureOutputDir() {
4072
- const dir = path3.join(process.cwd(), OUTPUT_DIR);
4073
- if (!fs3.existsSync(dir)) {
4074
- fs3.mkdirSync(dir, { recursive: true });
4139
+ const dir = path4.join(process.cwd(), OUTPUT_DIR);
4140
+ if (!fs4.existsSync(dir)) {
4141
+ fs4.mkdirSync(dir, { recursive: true });
4075
4142
  }
4076
4143
  return dir;
4077
4144
  }
@@ -4126,7 +4193,7 @@ function writeResultFile(rows, toolName, format) {
4126
4193
  const dir = ensureOutputDir();
4127
4194
  const ext = format === "markdown" ? "md" : format;
4128
4195
  const sanitizedName = toolName.replace(/[^a-zA-Z0-9_-]/g, "_");
4129
- const filePath = path3.join(dir, `${timestamp()}_${sanitizedName}.${ext}`);
4196
+ const filePath = path4.join(dir, `${timestamp()}_${sanitizedName}.${ext}`);
4130
4197
  let content;
4131
4198
  switch (format) {
4132
4199
  case "csv":
@@ -4139,14 +4206,20 @@ function writeResultFile(rows, toolName, format) {
4139
4206
  content = toMarkdownTable(rows);
4140
4207
  break;
4141
4208
  }
4142
- fs3.writeFileSync(filePath, content, "utf-8");
4143
- const resolvedPath = path3.resolve(filePath);
4144
- const editorCmd = getEditorCommand();
4145
- exec(`${editorCmd} "${resolvedPath}"`, { timeout: 5e3 }, (error) => {
4146
- if (error) {
4147
- console.error(`[result-writer] Failed to open result file in editor: ${error.message}`);
4148
- }
4149
- });
4209
+ fs4.writeFileSync(filePath, content, "utf-8");
4210
+ const resolvedPath = path4.resolve(filePath);
4211
+ if (isOpeningResultsSupported()) {
4212
+ const editorCmd = getEditorCommand();
4213
+ exec(`${editorCmd} "${resolvedPath}"`, { timeout: 5e3 }, (error) => {
4214
+ if (error) {
4215
+ console.error(`[result-writer] Failed to open result file in editor: ${error.message}`);
4216
+ }
4217
+ });
4218
+ } else {
4219
+ console.error(
4220
+ "[result-writer] Result written to .safe-sql-results/ (not opened \u2014 client does not support secure result handling)"
4221
+ );
4222
+ }
4150
4223
  return resolvedPath;
4151
4224
  }
4152
4225
 
@@ -5062,10 +5135,10 @@ function buildSourceDisplayInfo(sourceConfigs, getToolsForSource2, isDemo) {
5062
5135
 
5063
5136
  // src/server.ts
5064
5137
  var __filename2 = fileURLToPath2(import.meta.url);
5065
- var __dirname2 = path4.dirname(__filename2);
5138
+ var __dirname2 = path5.dirname(__filename2);
5066
5139
  var SERVER_VERSION = "0.0.0";
5067
5140
  try {
5068
- const packageJsonPath = path4.join(__dirname2, "..", "package.json");
5141
+ const packageJsonPath = path5.join(__dirname2, "..", "package.json");
5069
5142
  const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
5070
5143
  if (typeof packageJson.version === "string") {
5071
5144
  SERVER_VERSION = packageJson.version;
@@ -5127,11 +5200,19 @@ See documentation for more details on configuring database connections.
5127
5200
  });
5128
5201
  console.error("Tool registry initialized");
5129
5202
  setOutputFormat(resolveOutputFormat());
5203
+ ensureSafeSqlResultsInIgnoreFiles(process.cwd());
5130
5204
  const editorResult = resolveEditorCommand();
5131
5205
  if (editorResult) {
5132
5206
  setEditorCommand(editorResult.editor);
5133
5207
  setEditorExplicitly(true);
5134
- console.error(`Editor for result files: ${editorResult.editor} (${editorResult.source})`);
5208
+ setOpeningResultsSupported(isEditorSupportedForOpening(editorResult.editor));
5209
+ if (!isEditorSupportedForOpening(editorResult.editor)) {
5210
+ console.error(
5211
+ `Editor ${editorResult.editor} does not support secure result opening; results will not be opened automatically (${editorResult.source})`
5212
+ );
5213
+ } else {
5214
+ console.error(`Editor for result files: ${editorResult.editor} (${editorResult.source})`);
5215
+ }
5135
5216
  }
5136
5217
  const createServer2 = () => {
5137
5218
  const mcpServer = new McpServer({
@@ -5146,7 +5227,13 @@ See documentation for more details on configuring database connections.
5146
5227
  const detected = detectEditorFromClientName(clientName);
5147
5228
  if (detected) {
5148
5229
  setEditorCommand(detected);
5230
+ setOpeningResultsSupported(true);
5149
5231
  console.error(`Editor for result files: ${detected} (detected from MCP client: ${clientName})`);
5232
+ } else if (clientName) {
5233
+ setOpeningResultsSupported(false);
5234
+ console.error(
5235
+ `MCP client "${clientName}" does not support secure result opening; results will not be opened automatically`
5236
+ );
5150
5237
  }
5151
5238
  }
5152
5239
  };
@@ -5196,7 +5283,7 @@ See documentation for more details on configuring database connections.
5196
5283
  }
5197
5284
  next();
5198
5285
  });
5199
- const frontendPath = path4.join(__dirname2, "public");
5286
+ const frontendPath = path5.join(__dirname2, "public");
5200
5287
  app.use(express.static(frontendPath));
5201
5288
  app.get("/healthz", (req, res) => {
5202
5289
  res.status(200).send("OK");
@@ -5230,7 +5317,7 @@ See documentation for more details on configuring database connections.
5230
5317
  });
5231
5318
  if (process.env.NODE_ENV !== "development") {
5232
5319
  app.get("*", (req, res) => {
5233
- res.sendFile(path4.join(frontendPath, "index.html"));
5320
+ res.sendFile(path5.join(frontendPath, "index.html"));
5234
5321
  });
5235
5322
  }
5236
5323
  app.listen(port, bindAddress.host, () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capybara-db-mcp",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "mcpName": "io.github.ajgreyling/capybara-db-mcp",
5
5
  "description": "Minimal, token-efficient Database Read-Only PPI-safe MCP Server for PostgreSQL, MySQL, SQL Server, SQLite, MariaDB. Fork of DBHub with default-schema support.",
6
6
  "repository": {