@yugabytedb/meko-mcp 1.2.0 → 2.0.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 (44) hide show
  1. package/README.md +28 -19
  2. package/assets/hooks-handlers/before-submit-prompt.sh +2 -2
  3. package/assets/hooks-handlers/lib/capture.js +209 -144
  4. package/assets/hooks-handlers/lib/checkpoint-timer.js +23 -11
  5. package/assets/hooks-handlers/session-start.sh +6 -2
  6. package/assets/scripts/meko-statusline.sh +119 -0
  7. package/assets/scripts/package-desktop-skill.sh +27 -24
  8. package/assets/skills/meko-mcp-tools/SKILL.md +42 -50
  9. package/assets/skills/meko-mcp-tools/references/_sections.md +1 -1
  10. package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +75 -150
  11. package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +30 -36
  12. package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +6 -22
  13. package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +9 -8
  14. package/assets/skills/meko-mcp-tools/references/tools-overview.md +35 -42
  15. package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +28 -92
  16. package/assets/skills/meko-mcp-tools/references/tools-scope-permissions.md +5 -9
  17. package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +1 -81
  18. package/assets/skills/meko-mcp-tools-desktop/SKILL.md +100 -107
  19. package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +41 -11
  20. package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +1 -1
  21. package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +71 -150
  22. package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +12 -18
  23. package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +12 -15
  24. package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +35 -42
  25. package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +28 -92
  26. package/assets/skills/meko-mcp-tools-desktop/references/tools-scope-permissions.md +5 -9
  27. package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +1 -81
  28. package/assets/skills/meko-select-datapack-desktop/SKILL.md +198 -0
  29. package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +121 -0
  30. package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +66 -0
  31. package/bin/create-meko-setup.mjs +29 -0
  32. package/lib/clients/claude-code.mjs +133 -6
  33. package/lib/clients/claude-desktop.mjs +37 -12
  34. package/lib/clients/codex.mjs +306 -5
  35. package/lib/clients/common.mjs +41 -1
  36. package/lib/clients/cursor.mjs +75 -19
  37. package/lib/config.mjs +26 -0
  38. package/lib/installer.mjs +24 -0
  39. package/lib/mcp-transport.mjs +5 -0
  40. package/lib/validate.mjs +0 -1
  41. package/package.json +1 -1
  42. package/scripts/bundle-assets.mjs +8 -0
  43. package/assets/skills/meko-mcp-tools/references/tools-db-query-patterns.md +0 -38
  44. package/assets/skills/meko-mcp-tools/references/tools-write-guardrails.md +0 -30
@@ -16,8 +16,11 @@ import {
16
16
  writeJsonFile,
17
17
  } from "../config.mjs";
18
18
  import {
19
+ MEKO_MCP_USER_AGENT,
19
20
  getInstallerVersion,
21
+ installStableTree,
20
22
  isDanglingSymlink,
23
+ isPackagedInstall,
21
24
  markerIsStale,
22
25
  readInstallerMarker,
23
26
  resolveHooksRoot,
@@ -31,7 +34,6 @@ const SKILLS = [{ name: "meko-mcp-tools" }];
31
34
 
32
35
  const HOOKS = [
33
36
  { event: "sessionStart", script: "cursor-session-start.sh", timeout: 15000 },
34
- { event: "beforeSubmitPrompt", script: "before-submit-prompt.sh", matcher: "UserPromptSubmit", timeout: 2000 },
35
37
  { event: "preCompact", script: "pre-compact.sh" },
36
38
  { event: "sessionEnd", script: "session-end.sh", timeout: 30000 },
37
39
  ];
@@ -40,6 +42,9 @@ const HOOKS = [
40
42
  * @returns {object}
41
43
  */
42
44
  export function createCursorAdapter(customPaths = {}) {
45
+ const resolveHooksRootPath = customPaths.resolveHooksRoot ?? resolveHooksRoot;
46
+ const resolveSkillSourcePath = customPaths.resolveSkillSource ?? resolveSkillSource;
47
+ const packagedInstallDetector = customPaths.isPackagedInstall ?? isPackagedInstall;
43
48
  const paths = {
44
49
  getMcpPath: customPaths.getMcpPath ?? getCursorMcpPath,
45
50
  getHooksPath: customPaths.getHooksPath ?? getCursorHooksPath,
@@ -85,6 +90,7 @@ export function createCursorAdapter(customPaths = {}) {
85
90
  if (dryRun) {
86
91
  log.dim(` Would update ${mcpPath}:`);
87
92
  log.dim(` mcpServers.${name}.url = ${url}`);
93
+ log.dim(` mcpServers.${name}.headers.User-Agent = ${MEKO_MCP_USER_AGENT}`);
88
94
  if (apiKey) {
89
95
  log.dim(" mcpServers.<name>.headers.Authorization = <set>");
90
96
  }
@@ -95,16 +101,18 @@ export function createCursorAdapter(customPaths = {}) {
95
101
  if (!cfg.mcpServers || typeof cfg.mcpServers !== "object") {
96
102
  cfg.mcpServers = {};
97
103
  }
98
- const server = { url };
104
+ const server = { url, headers: { "User-Agent": MEKO_MCP_USER_AGENT } };
99
105
  if (apiKey) {
100
- server.headers = { Authorization: `Bearer ${apiKey}` };
106
+ server.headers.Authorization = `Bearer ${apiKey}`;
101
107
  }
102
108
  cfg.mcpServers[name] = server;
103
109
  writeJsonFile(mcpPath, cfg);
104
110
  },
105
111
  setHookEnvironment({ url, apiKey, dryRun, log, scope, projectRoot, cursorHookMode }) {
106
112
  const mode = cursorHookMode || "native";
113
+ const installedHooksRoot = getCursorHooksInstallRoot(paths, scope, projectRoot);
107
114
  if (dryRun) {
115
+ log.dim(` Would copy Cursor hook scripts to ${installedHooksRoot}`);
108
116
  if (mode === "native") {
109
117
  log.dim(` Would configure Cursor hooks at ${paths.getHooksPath(scope, projectRoot)}`);
110
118
  }
@@ -114,11 +122,27 @@ export function createCursorAdapter(customPaths = {}) {
114
122
  return;
115
123
  }
116
124
 
125
+ installStableTree(resolveHooksRootPath(), installedHooksRoot);
126
+
117
127
  if (mode === "native") {
118
- installNativeCursorHooks({ scope, projectRoot, url, apiKey, paths });
128
+ installNativeCursorHooks({
129
+ scope,
130
+ projectRoot,
131
+ url,
132
+ apiKey,
133
+ paths,
134
+ handlersRoot: installedHooksRoot,
135
+ });
119
136
  }
120
137
  if (mode === "claude-compat") {
121
- installClaudeCompatHooks({ scope, projectRoot, url, apiKey, paths });
138
+ installClaudeCompatHooks({
139
+ scope,
140
+ projectRoot,
141
+ url,
142
+ apiKey,
143
+ paths,
144
+ handlersRoot: installedHooksRoot,
145
+ });
122
146
  }
123
147
  },
124
148
  installSkills({ dryRun, log, scope, projectRoot }) {
@@ -132,7 +156,7 @@ export function createCursorAdapter(customPaths = {}) {
132
156
 
133
157
  mkdirSync(skillDir, { recursive: true });
134
158
  for (const skill of SKILLS) {
135
- const source = resolveSkillSource(skill.name);
159
+ const source = resolveSkillSourcePath(skill.name);
136
160
  const destination = join(skillDir, skill.name);
137
161
 
138
162
  if (existsSync(destination)) {
@@ -172,7 +196,9 @@ export function createCursorAdapter(customPaths = {}) {
172
196
  removePath(destination);
173
197
  }
174
198
 
175
- const strategy = symlinkOrCopy(source, destination);
199
+ const strategy = symlinkOrCopy(source, destination, {
200
+ preferCopy: packagedInstallDetector(),
201
+ });
176
202
  // Only stamp a marker when we materialized a real copy. Writing into
177
203
  // a symlinked source dir would mutate the shipped asset tree, which
178
204
  // is confusing at best and a permission error at worst. Symlinks
@@ -217,10 +243,20 @@ export function createCursorAdapter(customPaths = {}) {
217
243
  async validateConfig({ serverName, scope, projectRoot }) {
218
244
  const mcpPath = paths.getMcpPath(scope, projectRoot);
219
245
  const cfg = readJsonFile(mcpPath);
220
- if (cfg?.mcpServers?.[serverName]) {
221
- return { ok: true, message: `Server "${serverName}" is registered in ${mcpPath}.` };
246
+ const entry = cfg?.mcpServers?.[serverName];
247
+ if (!entry) {
248
+ return { ok: false, message: `Server "${serverName}" not found in ${mcpPath}.` };
249
+ }
250
+ const hasUserAgent = Object.keys(entry.headers ?? {}).some(
251
+ (key) => key.toLowerCase() === "user-agent",
252
+ );
253
+ if (!hasUserAgent) {
254
+ return {
255
+ ok: false,
256
+ message: `Server "${serverName}" in ${mcpPath} is missing the User-Agent header required by the Cloud WAF.`,
257
+ };
222
258
  }
223
- return { ok: false, message: `Server "${serverName}" not found in ${mcpPath}.` };
259
+ return { ok: true, message: `Server "${serverName}" is registered in ${mcpPath}.` };
224
260
  },
225
261
  };
226
262
  }
@@ -245,7 +281,22 @@ function ensureEnvBlock(cfg, url, apiKey) {
245
281
  }
246
282
  }
247
283
 
248
- function installNativeCursorHooks({ scope, projectRoot, url, apiKey, paths }) {
284
+ function getCursorHooksInstallRoot(paths, scope, projectRoot) {
285
+ return join(
286
+ dirname(paths.getHooksPath(scope, projectRoot)),
287
+ "meko-hooks",
288
+ "hooks-handlers",
289
+ );
290
+ }
291
+
292
+ function installNativeCursorHooks({
293
+ scope,
294
+ projectRoot,
295
+ url,
296
+ apiKey,
297
+ paths,
298
+ handlersRoot,
299
+ }) {
249
300
  const hooksPath = paths.getHooksPath(scope, projectRoot);
250
301
  const cfg = readJsonFile(hooksPath);
251
302
  if (!cfg.version) cfg.version = 1;
@@ -253,7 +304,6 @@ function installNativeCursorHooks({ scope, projectRoot, url, apiKey, paths }) {
253
304
  cfg.hooks = {};
254
305
  }
255
306
 
256
- const handlersRoot = resolveHooksRoot();
257
307
  pruneNativeCursorMekoHooks(cfg.hooks);
258
308
  for (const hook of HOOKS) {
259
309
  const scriptPath = join(handlersRoot, hook.script);
@@ -281,14 +331,20 @@ function installNativeCursorHooks({ scope, projectRoot, url, apiKey, paths }) {
281
331
  // will be pruned; the user must re-run the Cursor installer to restore
282
332
  // them. pruneMekoHookEntries emits a warning on any removal so the user
283
333
  // has a chance to notice before hooks silently disappear.
284
- function installClaudeCompatHooks({ scope, projectRoot, url, apiKey, paths }) {
334
+ function installClaudeCompatHooks({
335
+ scope,
336
+ projectRoot,
337
+ url,
338
+ apiKey,
339
+ paths,
340
+ handlersRoot,
341
+ }) {
285
342
  const settingsPath = paths.getClaudeCompatSettingsPath(scope, projectRoot);
286
343
  const cfg = readJsonFile(settingsPath);
287
344
  if (!cfg.hooks || typeof cfg.hooks !== "object") {
288
345
  cfg.hooks = {};
289
346
  }
290
347
 
291
- const handlersRoot = resolveHooksRoot();
292
348
  const compatMap = {
293
349
  SessionStart: { script: "session-start.sh", timeout: 15000 },
294
350
  PreCompact: { script: "pre-compact.sh" },
@@ -354,9 +410,7 @@ function upsertClaudeCompatHook(hooksObj, event, entry) {
354
410
  }
355
411
 
356
412
  function uninstallCursorHooks({ scope, projectRoot, mode, paths }) {
357
- const handlersRoot = resolveHooksRoot();
358
- const scripts = ["session-start.sh", "cursor-session-start.sh", "before-submit-prompt.sh", "pre-compact.sh", "session-end.sh"]
359
- .map((name) => join(handlersRoot, name));
413
+ const handlersRoot = getCursorHooksInstallRoot(paths, scope, projectRoot);
360
414
 
361
415
  if (mode === "native") {
362
416
  const hooksPath = paths.getHooksPath(scope, projectRoot);
@@ -366,7 +420,7 @@ function uninstallCursorHooks({ scope, projectRoot, mode, paths }) {
366
420
  if (!Array.isArray(cfg.hooks[event])) continue;
367
421
  cfg.hooks[event] = cfg.hooks[event].filter((item) => {
368
422
  if (!item?.command) return true;
369
- return !scripts.some((script) => item.command.includes(script));
423
+ return !isMekoHookCommand(item.command);
370
424
  });
371
425
  }
372
426
  writeJsonFile(hooksPath, cfg);
@@ -389,7 +443,7 @@ function uninstallCursorHooks({ scope, projectRoot, mode, paths }) {
389
443
  if (!Array.isArray(group?.hooks)) return group;
390
444
  const filtered = group.hooks.filter((item) => {
391
445
  if (!item?.command) return true;
392
- return !scripts.some((script) => item.command.includes(script));
446
+ return !isMekoHookCommand(item.command);
393
447
  });
394
448
  return { ...group, hooks: filtered };
395
449
  }).filter((group) => {
@@ -400,6 +454,8 @@ function uninstallCursorHooks({ scope, projectRoot, mode, paths }) {
400
454
  writeJsonFile(settingsPath, cfg);
401
455
  }
402
456
  }
457
+
458
+ removePath(dirname(handlersRoot));
403
459
  }
404
460
 
405
461
  /**
package/lib/config.mjs CHANGED
@@ -143,6 +143,32 @@ export function getCodexMekoEnvFilePath() {
143
143
  return join(codexHome, "meko-env.sh");
144
144
  }
145
145
 
146
+ /**
147
+ * Return the stable directory the Codex hook scripts are copied into at
148
+ * install time.
149
+ *
150
+ * Why a dedicated copy instead of referencing the shipped asset root
151
+ * directly: when the installer runs via `npx @yugabytedb/meko-mcp`, the
152
+ * asset root resolves to an ephemeral npm cache dir
153
+ * (`~/.npm/_npx/<hash>/node_modules/@yugabytedb/meko-mcp/assets/...`).
154
+ * npx garbage-collects those hashed dirs, so a hooks.json that pins the
155
+ * npx path becomes a dangling pointer on the next npx run — Codex then
156
+ * spawns `bash '<missing script>'`, which exits 127 ("command not found")
157
+ * and surfaces as a failed SessionStart hook + the misleading "could not
158
+ * reach Meko" fallback. Copying the scripts under $CODEX_HOME gives the
159
+ * hook a home-dir-stable target that outlives the npx cache.
160
+ *
161
+ * The `hooks-handlers` leaf is preserved so the command string still
162
+ * matches `isMekoHookCommand`'s prune regex — that lets a reinstall
163
+ * auto-heal any stale npx-pinned entry left by an older installer.
164
+ *
165
+ * @returns {string}
166
+ */
167
+ export function getCodexHooksInstallRoot() {
168
+ const codexHome = process.env.CODEX_HOME || join(homedir(), ".codex");
169
+ return join(codexHome, "meko-hooks", "hooks-handlers");
170
+ }
171
+
146
172
  /**
147
173
  * Return path to the shared agent-skills directory used by Codex
148
174
  * (and other agent runtimes that follow the convention).
package/lib/installer.mjs CHANGED
@@ -33,6 +33,7 @@ export async function install(options) {
33
33
  skipSkills = false,
34
34
  skipValidation = false,
35
35
  skipCanary = false,
36
+ installStatusline = false,
36
37
  dryRun = false,
37
38
  verbose = false,
38
39
  yes = false,
@@ -50,6 +51,7 @@ export async function install(options) {
50
51
 
51
52
  let totalSteps = 3; // register + env/hooks + validate
52
53
  if (!skipSkills) totalSteps += 1;
54
+ if (installStatusline && typeof adapter.installStatusline === "function") totalSteps += 1;
53
55
  if (skipValidation) totalSteps -= 1;
54
56
 
55
57
  banner(`Meko Setup for ${adapter.displayName}`);
@@ -171,6 +173,28 @@ export async function install(options) {
171
173
  currentStep++;
172
174
  }
173
175
 
176
+ if (installStatusline && typeof adapter.installStatusline === "function") {
177
+ try {
178
+ adapter.installStatusline({
179
+ scope,
180
+ dryRun,
181
+ verbose,
182
+ log: LOG,
183
+ settingsPath,
184
+ projectRoot,
185
+ });
186
+ stepOk(
187
+ currentStep,
188
+ totalSteps,
189
+ dryRun ? "Wiring datapack statusline (dry-run)." : "Datapack statusline wired.",
190
+ );
191
+ } catch (err) {
192
+ stepFail(currentStep, totalSteps, "Failed to wire datapack statusline.");
193
+ warn(`Non-critical: ${err.message}`);
194
+ }
195
+ currentStep++;
196
+ }
197
+
174
198
  if (!skipValidation) {
175
199
  if (dryRun) {
176
200
  dim("Would validate config, MCP connectivity, and tool availability.");
@@ -39,6 +39,11 @@ export function jsonRpcRequest(url, payload, apiKey, sessionId, opts = {}) {
39
39
  const headers = {
40
40
  "Content-Type": "application/json",
41
41
  Accept: "application/json, text/event-stream",
42
+ // Cloud Meko sits behind a WAF that 403s requests with no User-Agent
43
+ // (Node's http/https send none by default). This transport powers install
44
+ // validation and migration against cloud URLs, so it hits the same WAF as
45
+ // the capture hooks — send an explicit UA so those calls aren't rejected.
46
+ "User-Agent": "meko-installer/1.0 (+https://github.com/yugabyte/meko-mcp-server)",
42
47
  };
43
48
  if (apiKey) {
44
49
  headers["Authorization"] = `Bearer ${apiKey}`;
package/lib/validate.mjs CHANGED
@@ -32,7 +32,6 @@ const CORE_TOOLS = [
32
32
  "memory_search",
33
33
  "memory_get_by_id",
34
34
  "conversation_create",
35
- "db_summarize_database",
36
35
  "datapack_list",
37
36
  ];
38
37
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yugabytedb/meko-mcp",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "description": "Single-line installer for Meko MCP Server across Claude and Cursor clients",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,6 +22,10 @@ const PAYLOAD = [
22
22
  from: join(repoRoot, "skills", "skills", "meko-mcp-tools-desktop"),
23
23
  to: join(assetsDir, "skills", "meko-mcp-tools-desktop"),
24
24
  },
25
+ {
26
+ from: join(repoRoot, "skills", "skills", "meko-select-datapack-desktop"),
27
+ to: join(assetsDir, "skills", "meko-select-datapack-desktop"),
28
+ },
25
29
  {
26
30
  from: join(repoRoot, "skills", "hooks-handlers"),
27
31
  to: join(assetsDir, "hooks-handlers"),
@@ -30,6 +34,10 @@ const PAYLOAD = [
30
34
  from: join(repoRoot, "skills", "scripts", "package-desktop-skill.sh"),
31
35
  to: join(assetsDir, "scripts", "package-desktop-skill.sh"),
32
36
  },
37
+ {
38
+ from: join(repoRoot, "skills", "scripts", "meko-statusline.sh"),
39
+ to: join(assetsDir, "scripts", "meko-statusline.sh"),
40
+ },
33
41
  ];
34
42
 
35
43
  const cleaning = process.argv.includes("--clean");
@@ -1,38 +0,0 @@
1
- # Read-Only vs Write Query Execution Patterns
2
-
3
- **WRONG:** `db_run_write_query(scope="write", query="SELECT * FROM users")` — requires write scope unnecessarily, triggers guardrail validation, returns `{"rows_affected": -1}` instead of data.
4
- **WRONG:** `db_run_read_only_query(scope="read", query="INSERT INTO ...")` — fails: "cannot execute INSERT in a read-only transaction."
5
-
6
- ## How Each Tool Works
7
-
8
- **`db_run_read_only_query`:** `BEGIN READ ONLY` → execute → `ROLLBACK`. Returns JSON array of row objects.
9
-
10
- **`db_run_write_query`:** Guardrail validation → execute → `COMMIT`. Returns `{"rows_affected": N}`.
11
-
12
- ## When to Use db_summarize_database
13
-
14
- Use instead of writing your own `information_schema` query when you need a table overview:
15
-
16
- ```
17
- db_summarize_database(scope="read", schema="public")
18
- -- Returns table names, column schemas, AND row counts in one call
19
- ```
20
-
21
- ## SQL Best Practices
22
-
23
- **Schema qualification:** For non-public schemas, use `analytics."monthly_revenue"` and pass `schema="analytics"` to `db_summarize_database`.
24
-
25
- **String escaping:** Queries are passed directly to `cur.execute()` — no parameter placeholders. Escape values in SQL: `WHERE name = 'O''Brien'`.
26
-
27
- **LIMIT on exploratory queries:**
28
- ```
29
- db_run_read_only_query(scope="read", query="SELECT * FROM large_table LIMIT 25")
30
- ```
31
-
32
- **Batch large writes:** Due to the 1000-row INSERT cap, split large inserts. Or use `INSERT ... SELECT` for unlimited rows from an existing table.
33
-
34
- **One statement per call:**
35
- ```
36
- WRONG: "CREATE TABLE a (id INT); CREATE TABLE b (id INT);"
37
- RIGHT: Two separate db_run_write_query calls
38
- ```
@@ -1,30 +0,0 @@
1
- # Write Query Guardrails and Blocked Operations
2
-
3
- **WRONG:** `"INSERT INTO a (...); INSERT INTO b (...)"` → "Multi-statement queries not allowed." Send one statement per call.
4
- **WRONG:** `"DROP SCHEMA analytics CASCADE"` → "DROP SCHEMA is not allowed."
5
- **WRONG:** `"\\d users"` → psql meta-commands not supported. Use `information_schema` queries via `db_run_read_only_query`.
6
-
7
- ## Blocked Operations
8
-
9
- **Database/schema destruction:** `DROP DATABASE`, `DROP SCHEMA`, `ALTER DATABASE`, `CREATE DATABASE`
10
- **Role/privilege manipulation:** `GRANT`, `REVOKE`, `CREATE/ALTER/DROP ROLE`, `CREATE/ALTER/DROP USER`
11
- **Filesystem/code execution:** `COPY TO/FROM`, `LOAD`, `DO $$`, `CREATE EXTENSION`
12
- **Server configuration:** `ALTER SYSTEM`, `RESET ALL`
13
- **Dangerous functions:** `pg_sleep`, `pg_read_file`, `pg_write_file`, `lo_import`, `lo_export`, `dblink`
14
- **Structural rules:** One SQL statement per call (multi-statement rejected). No psql `\` commands.
15
-
16
- ## Configurable Limits
17
-
18
- **Bulk INSERT row cap** (default 1000, env: `YB_MCP_MAX_INSERT_ROWS`): Counts parenthesized tuples in VALUES. Does NOT apply to `INSERT ... SELECT`. Split large inserts into batches of 1000 or fewer.
19
-
20
- **WHERE clause enforcement** (off by default):
21
- - `YB_MCP_REQUIRE_WHERE_ON_UPDATE=true` → rejects `UPDATE` without top-level `WHERE`
22
- - `YB_MCP_REQUIRE_WHERE_ON_DELETE=true` → rejects `DELETE` without top-level `WHERE`
23
-
24
- A `WHERE` inside a subquery does NOT satisfy the check.
25
-
26
- ## What IS Allowed
27
-
28
- `INSERT INTO ... VALUES`, `INSERT INTO ... SELECT` (no row limit), `UPDATE ... SET ... WHERE`, `DELETE FROM ... WHERE`, `CREATE TABLE`, `ALTER TABLE`, `DROP TABLE`, `CREATE INDEX`, `TRUNCATE`, `CALL procedure_name(...)`.
29
-
30
- SQL comments are stripped before guardrail checks — patterns cannot be hidden in comments.