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.
- package/README.md +13 -7
- package/dist/index.js +109 -22
- 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[
|
|
42
|
-
E[
|
|
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 (
|
|
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
|
|
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
|
|
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("
|
|
3995
|
-
return "
|
|
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
|
|
4069
|
-
import
|
|
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 =
|
|
4073
|
-
if (!
|
|
4074
|
-
|
|
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 =
|
|
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
|
-
|
|
4143
|
-
const resolvedPath =
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
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 =
|
|
5138
|
+
var __dirname2 = path5.dirname(__filename2);
|
|
5066
5139
|
var SERVER_VERSION = "0.0.0";
|
|
5067
5140
|
try {
|
|
5068
|
-
const packageJsonPath =
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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.
|
|
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": {
|