@yugabytedb/meko-mcp 1.2.0 → 2.1.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 (57) hide show
  1. package/README.md +59 -25
  2. package/assets/hooks-handlers/before-submit-prompt.sh +2 -2
  3. package/assets/hooks-handlers/codex-session-end.sh +78 -0
  4. package/assets/hooks-handlers/lib/capture.js +366 -152
  5. package/assets/hooks-handlers/lib/checkpoint-timer.js +67 -13
  6. package/assets/hooks-handlers/session-end.sh +10 -0
  7. package/assets/hooks-handlers/session-start.sh +14 -3
  8. package/assets/hooks-handlers/test_checkpoint_timer.sh +162 -0
  9. package/assets/scripts/meko-statusline.sh +119 -0
  10. package/assets/scripts/package-desktop-skill.sh +64 -30
  11. package/assets/skills/meko-mcp-tools/SKILL.md +84 -86
  12. package/assets/skills/meko-mcp-tools/references/_sections.md +2 -4
  13. package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +29 -25
  14. package/assets/skills/meko-mcp-tools/references/tools-conversation-dedup.md +5 -7
  15. package/assets/skills/meko-mcp-tools/references/tools-conversation-reasoning.md +50 -0
  16. package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +126 -184
  17. package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +31 -37
  18. package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +10 -26
  19. package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +26 -30
  20. package/assets/skills/meko-mcp-tools/references/tools-overview.md +60 -66
  21. package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +28 -93
  22. package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +5 -99
  23. package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +9 -8
  24. package/assets/skills/meko-mcp-tools-desktop/SKILL.md +138 -138
  25. package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +46 -16
  26. package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +2 -2
  27. package/assets/skills/meko-mcp-tools-desktop/references/tools-agent-id-conventions.md +28 -24
  28. package/assets/skills/meko-mcp-tools-desktop/references/tools-conversation-reasoning.md +50 -0
  29. package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +120 -174
  30. package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +16 -22
  31. package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +28 -34
  32. package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +60 -66
  33. package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +28 -93
  34. package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +4 -98
  35. package/assets/skills/meko-select-datapack-desktop/SKILL.md +208 -0
  36. package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +121 -0
  37. package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +41 -0
  38. package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +66 -0
  39. package/bin/create-meko-setup.mjs +74 -6
  40. package/lib/clients/claude-code.mjs +137 -6
  41. package/lib/clients/claude-desktop.mjs +51 -12
  42. package/lib/clients/codex.mjs +329 -13
  43. package/lib/clients/common.mjs +43 -1
  44. package/lib/clients/cursor.mjs +82 -21
  45. package/lib/config.mjs +26 -0
  46. package/lib/detect.mjs +7 -2
  47. package/lib/installer.mjs +24 -0
  48. package/lib/mcp-transport.mjs +5 -0
  49. package/lib/prompt.mjs +1 -1
  50. package/lib/url-selection.mjs +55 -0
  51. package/lib/validate.mjs +0 -1
  52. package/package.json +1 -1
  53. package/scripts/bundle-assets.mjs +8 -0
  54. package/assets/skills/meko-mcp-tools/references/tools-db-query-patterns.md +0 -38
  55. package/assets/skills/meko-mcp-tools/references/tools-scope-permissions.md +0 -75
  56. package/assets/skills/meko-mcp-tools/references/tools-write-guardrails.md +0 -30
  57. package/assets/skills/meko-mcp-tools-desktop/references/tools-scope-permissions.md +0 -75
@@ -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,
@@ -70,8 +75,13 @@ export function createCursorAdapter(customPaths = {}) {
70
75
  detectExisting(name, { scope, projectRoot }) {
71
76
  const path = paths.getMcpPath(scope, projectRoot);
72
77
  const cfg = readJsonFile(path);
73
- const found = Boolean(cfg?.mcpServers?.[name]);
74
- return { found, status: found ? "Configured" : "" };
78
+ const entry = cfg?.mcpServers?.[name];
79
+ const found = Boolean(entry);
80
+ return {
81
+ found,
82
+ status: found ? "Configured" : "",
83
+ ...(typeof entry?.url === "string" ? { url: entry.url } : {}),
84
+ };
75
85
  },
76
86
  removeRegistration(name, { scope, projectRoot }) {
77
87
  const mcpPath = paths.getMcpPath(scope, projectRoot);
@@ -85,6 +95,7 @@ export function createCursorAdapter(customPaths = {}) {
85
95
  if (dryRun) {
86
96
  log.dim(` Would update ${mcpPath}:`);
87
97
  log.dim(` mcpServers.${name}.url = ${url}`);
98
+ log.dim(` mcpServers.${name}.headers.User-Agent = ${MEKO_MCP_USER_AGENT}`);
88
99
  if (apiKey) {
89
100
  log.dim(" mcpServers.<name>.headers.Authorization = <set>");
90
101
  }
@@ -95,16 +106,18 @@ export function createCursorAdapter(customPaths = {}) {
95
106
  if (!cfg.mcpServers || typeof cfg.mcpServers !== "object") {
96
107
  cfg.mcpServers = {};
97
108
  }
98
- const server = { url };
109
+ const server = { url, headers: { "User-Agent": MEKO_MCP_USER_AGENT } };
99
110
  if (apiKey) {
100
- server.headers = { Authorization: `Bearer ${apiKey}` };
111
+ server.headers.Authorization = `Bearer ${apiKey}`;
101
112
  }
102
113
  cfg.mcpServers[name] = server;
103
114
  writeJsonFile(mcpPath, cfg);
104
115
  },
105
116
  setHookEnvironment({ url, apiKey, dryRun, log, scope, projectRoot, cursorHookMode }) {
106
117
  const mode = cursorHookMode || "native";
118
+ const installedHooksRoot = getCursorHooksInstallRoot(paths, scope, projectRoot);
107
119
  if (dryRun) {
120
+ log.dim(` Would copy Cursor hook scripts to ${installedHooksRoot}`);
108
121
  if (mode === "native") {
109
122
  log.dim(` Would configure Cursor hooks at ${paths.getHooksPath(scope, projectRoot)}`);
110
123
  }
@@ -114,11 +127,27 @@ export function createCursorAdapter(customPaths = {}) {
114
127
  return;
115
128
  }
116
129
 
130
+ installStableTree(resolveHooksRootPath(), installedHooksRoot);
131
+
117
132
  if (mode === "native") {
118
- installNativeCursorHooks({ scope, projectRoot, url, apiKey, paths });
133
+ installNativeCursorHooks({
134
+ scope,
135
+ projectRoot,
136
+ url,
137
+ apiKey,
138
+ paths,
139
+ handlersRoot: installedHooksRoot,
140
+ });
119
141
  }
120
142
  if (mode === "claude-compat") {
121
- installClaudeCompatHooks({ scope, projectRoot, url, apiKey, paths });
143
+ installClaudeCompatHooks({
144
+ scope,
145
+ projectRoot,
146
+ url,
147
+ apiKey,
148
+ paths,
149
+ handlersRoot: installedHooksRoot,
150
+ });
122
151
  }
123
152
  },
124
153
  installSkills({ dryRun, log, scope, projectRoot }) {
@@ -132,7 +161,7 @@ export function createCursorAdapter(customPaths = {}) {
132
161
 
133
162
  mkdirSync(skillDir, { recursive: true });
134
163
  for (const skill of SKILLS) {
135
- const source = resolveSkillSource(skill.name);
164
+ const source = resolveSkillSourcePath(skill.name);
136
165
  const destination = join(skillDir, skill.name);
137
166
 
138
167
  if (existsSync(destination)) {
@@ -172,7 +201,9 @@ export function createCursorAdapter(customPaths = {}) {
172
201
  removePath(destination);
173
202
  }
174
203
 
175
- const strategy = symlinkOrCopy(source, destination);
204
+ const strategy = symlinkOrCopy(source, destination, {
205
+ preferCopy: packagedInstallDetector(),
206
+ });
176
207
  // Only stamp a marker when we materialized a real copy. Writing into
177
208
  // a symlinked source dir would mutate the shipped asset tree, which
178
209
  // is confusing at best and a permission error at worst. Symlinks
@@ -217,10 +248,20 @@ export function createCursorAdapter(customPaths = {}) {
217
248
  async validateConfig({ serverName, scope, projectRoot }) {
218
249
  const mcpPath = paths.getMcpPath(scope, projectRoot);
219
250
  const cfg = readJsonFile(mcpPath);
220
- if (cfg?.mcpServers?.[serverName]) {
221
- return { ok: true, message: `Server "${serverName}" is registered in ${mcpPath}.` };
251
+ const entry = cfg?.mcpServers?.[serverName];
252
+ if (!entry) {
253
+ return { ok: false, message: `Server "${serverName}" not found in ${mcpPath}.` };
254
+ }
255
+ const hasUserAgent = Object.keys(entry.headers ?? {}).some(
256
+ (key) => key.toLowerCase() === "user-agent",
257
+ );
258
+ if (!hasUserAgent) {
259
+ return {
260
+ ok: false,
261
+ message: `Server "${serverName}" in ${mcpPath} is missing the User-Agent header required by the Cloud WAF.`,
262
+ };
222
263
  }
223
- return { ok: false, message: `Server "${serverName}" not found in ${mcpPath}.` };
264
+ return { ok: true, message: `Server "${serverName}" is registered in ${mcpPath}.` };
224
265
  },
225
266
  };
226
267
  }
@@ -245,7 +286,22 @@ function ensureEnvBlock(cfg, url, apiKey) {
245
286
  }
246
287
  }
247
288
 
248
- function installNativeCursorHooks({ scope, projectRoot, url, apiKey, paths }) {
289
+ function getCursorHooksInstallRoot(paths, scope, projectRoot) {
290
+ return join(
291
+ dirname(paths.getHooksPath(scope, projectRoot)),
292
+ "meko-hooks",
293
+ "hooks-handlers",
294
+ );
295
+ }
296
+
297
+ function installNativeCursorHooks({
298
+ scope,
299
+ projectRoot,
300
+ url,
301
+ apiKey,
302
+ paths,
303
+ handlersRoot,
304
+ }) {
249
305
  const hooksPath = paths.getHooksPath(scope, projectRoot);
250
306
  const cfg = readJsonFile(hooksPath);
251
307
  if (!cfg.version) cfg.version = 1;
@@ -253,7 +309,6 @@ function installNativeCursorHooks({ scope, projectRoot, url, apiKey, paths }) {
253
309
  cfg.hooks = {};
254
310
  }
255
311
 
256
- const handlersRoot = resolveHooksRoot();
257
312
  pruneNativeCursorMekoHooks(cfg.hooks);
258
313
  for (const hook of HOOKS) {
259
314
  const scriptPath = join(handlersRoot, hook.script);
@@ -281,14 +336,20 @@ function installNativeCursorHooks({ scope, projectRoot, url, apiKey, paths }) {
281
336
  // will be pruned; the user must re-run the Cursor installer to restore
282
337
  // them. pruneMekoHookEntries emits a warning on any removal so the user
283
338
  // has a chance to notice before hooks silently disappear.
284
- function installClaudeCompatHooks({ scope, projectRoot, url, apiKey, paths }) {
339
+ function installClaudeCompatHooks({
340
+ scope,
341
+ projectRoot,
342
+ url,
343
+ apiKey,
344
+ paths,
345
+ handlersRoot,
346
+ }) {
285
347
  const settingsPath = paths.getClaudeCompatSettingsPath(scope, projectRoot);
286
348
  const cfg = readJsonFile(settingsPath);
287
349
  if (!cfg.hooks || typeof cfg.hooks !== "object") {
288
350
  cfg.hooks = {};
289
351
  }
290
352
 
291
- const handlersRoot = resolveHooksRoot();
292
353
  const compatMap = {
293
354
  SessionStart: { script: "session-start.sh", timeout: 15000 },
294
355
  PreCompact: { script: "pre-compact.sh" },
@@ -354,9 +415,7 @@ function upsertClaudeCompatHook(hooksObj, event, entry) {
354
415
  }
355
416
 
356
417
  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));
418
+ const handlersRoot = getCursorHooksInstallRoot(paths, scope, projectRoot);
360
419
 
361
420
  if (mode === "native") {
362
421
  const hooksPath = paths.getHooksPath(scope, projectRoot);
@@ -366,7 +425,7 @@ function uninstallCursorHooks({ scope, projectRoot, mode, paths }) {
366
425
  if (!Array.isArray(cfg.hooks[event])) continue;
367
426
  cfg.hooks[event] = cfg.hooks[event].filter((item) => {
368
427
  if (!item?.command) return true;
369
- return !scripts.some((script) => item.command.includes(script));
428
+ return !isMekoHookCommand(item.command);
370
429
  });
371
430
  }
372
431
  writeJsonFile(hooksPath, cfg);
@@ -389,7 +448,7 @@ function uninstallCursorHooks({ scope, projectRoot, mode, paths }) {
389
448
  if (!Array.isArray(group?.hooks)) return group;
390
449
  const filtered = group.hooks.filter((item) => {
391
450
  if (!item?.command) return true;
392
- return !scripts.some((script) => item.command.includes(script));
451
+ return !isMekoHookCommand(item.command);
393
452
  });
394
453
  return { ...group, hooks: filtered };
395
454
  }).filter((group) => {
@@ -400,6 +459,8 @@ function uninstallCursorHooks({ scope, projectRoot, mode, paths }) {
400
459
  writeJsonFile(settingsPath, cfg);
401
460
  }
402
461
  }
462
+
463
+ removePath(dirname(handlersRoot));
403
464
  }
404
465
 
405
466
  /**
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/detect.mjs CHANGED
@@ -110,7 +110,7 @@ export function getClaudeCodeConfigPath() {
110
110
  * `scope` — "user" (default) or "project". Project scope looks under `projects[projectRoot].mcpServers`.
111
111
  * `projectRoot` — absolute path used as the key into `projects[...]` when `scope === "project"`.
112
112
  * Ignored for user scope. Must be supplied by the caller for project scope.
113
- * @returns {{ found: boolean, status: string }}
113
+ * @returns {{ found: boolean, status: string, url?: string }}
114
114
  * `found` - true if a matching MCP entry exists at the scope-appropriate location.
115
115
  * `status` - "Configured" when found, "" otherwise. (Kept as a string for
116
116
  * API compatibility with the prior implementation; callers use
@@ -129,5 +129,10 @@ export function detectExistingMeko(serverName = "meko", options = {}) {
129
129
  if (!entry) {
130
130
  return { found: false, status: "" };
131
131
  }
132
- return { found: true, status: "Configured" };
132
+ const url = typeof entry.url === "string" ? entry.url : undefined;
133
+ return {
134
+ found: true,
135
+ status: "Configured",
136
+ ...(url ? { url } : {}),
137
+ };
133
138
  }
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/prompt.mjs CHANGED
@@ -50,7 +50,7 @@ function question(query) {
50
50
  *
51
51
  * @example
52
52
  * const choice = await askChoice('Connection type:', [
53
- * { label: 'Cloud Meko', description: 'Hosted at mekodev.com' },
53
+ * { label: 'Cloud Meko', description: 'Hosted at mekodata.ai' },
54
54
  * { label: 'Local Meko', description: 'docker-compose stack' },
55
55
  * ]);
56
56
  */
@@ -0,0 +1,55 @@
1
+ import { MEKO_PRODUCTION_MCP_URL } from "./clients/common.mjs";
2
+
3
+ function validHttpUrl(value) {
4
+ if (typeof value !== "string" || !value.trim()) return null;
5
+ const candidate = value.trim();
6
+ try {
7
+ const parsed = new URL(candidate);
8
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
9
+ parsed.pathname = parsed.pathname.replace(/\/+$/, "") || "/";
10
+ return { value: candidate, comparisonKey: parsed.toString() };
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+
16
+ /**
17
+ * Select the URL offered by interactive/non-interactive setup.
18
+ * Explicit CLI URLs are resolved before this helper is called.
19
+ */
20
+ export function resolveSuggestedMcpUrl({
21
+ adapters,
22
+ clients,
23
+ name = "meko",
24
+ scope = "user",
25
+ projectRoot = process.cwd(),
26
+ }) {
27
+ const existingUrlsByKey = new Map();
28
+ for (const client of clients) {
29
+ const adapter = adapters[client];
30
+ if (!adapter?.detectExisting) continue;
31
+ try {
32
+ const existing = adapter.detectExisting(name, { scope, projectRoot });
33
+ const url = existing?.found ? validHttpUrl(existing.url) : null;
34
+ if (url && !existingUrlsByKey.has(url.comparisonKey)) {
35
+ existingUrlsByKey.set(url.comparisonKey, url.value);
36
+ }
37
+ } catch {
38
+ // Existing-config discovery is advisory. Installation performs its own
39
+ // prerequisite and config checks later with actionable errors.
40
+ }
41
+ }
42
+
43
+ const uniqueUrls = [...existingUrlsByKey.values()];
44
+ if (uniqueUrls.length === 1) {
45
+ return { url: uniqueUrls[0], source: "existing", existingUrls: uniqueUrls };
46
+ }
47
+ if (uniqueUrls.length > 1) {
48
+ return { url: null, source: "conflict", existingUrls: uniqueUrls };
49
+ }
50
+ return {
51
+ url: MEKO_PRODUCTION_MCP_URL,
52
+ source: "production-default",
53
+ existingUrls: [],
54
+ };
55
+ }
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.1.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,75 +0,0 @@
1
- # Scope Permission Hierarchy and Per-Tool Requirements
2
-
3
- **WRONG:** `db_run_write_query(scope="read", ...)` → PermissionError: requires 'write' or higher.
4
- **WRONG:** `memory_delete_all(scope="write", ...)` → PermissionError: requires 'admin'.
5
-
6
- ## Scope Hierarchy
7
-
8
- ```
9
- admin (level 2) -- can do everything
10
- ↓
11
- write (level 1) -- can read + write, not admin-only
12
- ↓
13
- read (level 0) -- can only read
14
- ```
15
-
16
- Higher scopes include lower scopes. An unrecognized scope string always fails.
17
-
18
- ## Per-Tool Requirements
19
-
20
- ### read scope (11 tools)
21
-
22
- | Tool | Group |
23
- |------|-------|
24
- | `db_summarize_database` | Database |
25
- | `db_run_read_only_query` | Database |
26
- | `knowledgebase_check_index_status` | RAG |
27
- | `memory_search` | Memory |
28
- | `memory_get_by_id` | Memory |
29
- | `memory_get_all` | Memory |
30
- | `flush_pending_memory_candidates` | Memory |
31
- | `conversation_get` | Conversation |
32
- | `conversation_list` | Conversation |
33
- | `datapack_list` | Datapack |
34
- | `datapack_describe` | Datapack |
35
-
36
- ### write scope (11 tools)
37
-
38
- | Tool | Group |
39
- |------|-------|
40
- | `db_run_write_query` | Database |
41
- | `knowledgebase_trigger_index_creation` | RAG |
42
- | `knowledgebase_add_source_to_index` | RAG |
43
- | `memory_add` | Memory |
44
- | `memory_update` | Memory |
45
- | `memory_delete_by_id` | Memory |
46
- | `conversation_create` | Conversation |
47
- | `conversation_add_message` | Conversation |
48
- | `conversation_update` | Conversation |
49
- | `datapack_create` | Datapack |
50
- | `datapack_update` | Datapack |
51
-
52
- ### admin scope (3 tools — destructive, irreversible)
53
-
54
- | Tool | Group |
55
- |------|-------|
56
- | `memory_delete_all` | Memory |
57
- | `conversation_delete` | Conversation |
58
- | `datapack_delete` | Datapack |
59
-
60
- Agent and knowledge-base lifecycle tools are not MCP-exposed — see "Platform capabilities NOT exposed via MCP" in `tools-overview.md`.
61
-
62
- ## Best Practices
63
-
64
- 1. **Use the minimum scope needed.** `scope="read"` for reads documents intent and prevents accidental writes.
65
- 2. **The scope is a parameter, not an auth token.** When OAuth is enabled, the actual permission check happens against the OAuth token's claims. Both `scope` parameter and OAuth permissions must be satisfied.
66
-
67
- ## Common Mistakes
68
-
69
- **WRONG:** `scope="all"` — there is no "all" scope. Valid values are exactly: `"read"`, `"write"`, `"admin"`. Nothing else.
70
-
71
- Other invalid values that will fail: `"readwrite"`, `"rw"`, `"full"`, `"any"`.
72
-
73
- The error `Insufficient scope: 'all'. This tool requires 'read' or higher.` means the scope string was not recognized as a valid value. Always use one of the three valid strings.
74
-
75
- **Default choice:** Start with `scope="read"` unless you specifically need to write or delete.
@@ -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.
@@ -1,75 +0,0 @@
1
- # Scope Permission Hierarchy and Per-Tool Requirements
2
-
3
- **WRONG:** `db_run_write_query(scope="read", ...)` → PermissionError: requires 'write' or higher.
4
- **WRONG:** `memory_delete_all(scope="write", ...)` → PermissionError: requires 'admin'.
5
-
6
- ## Scope Hierarchy
7
-
8
- ```
9
- admin (level 2) -- can do everything
10
- ↓
11
- write (level 1) -- can read + write, not admin-only
12
- ↓
13
- read (level 0) -- can only read
14
- ```
15
-
16
- Higher scopes include lower scopes. An unrecognized scope string always fails.
17
-
18
- ## Per-Tool Requirements
19
-
20
- ### read scope (11 tools)
21
-
22
- | Tool | Group |
23
- |------|-------|
24
- | `db_summarize_database` | Database |
25
- | `db_run_read_only_query` | Database |
26
- | `knowledgebase_check_index_status` | RAG |
27
- | `memory_search` | Memory |
28
- | `memory_get_by_id` | Memory |
29
- | `memory_get_all` | Memory |
30
- | `flush_pending_memory_candidates` | Memory |
31
- | `conversation_get` | Conversation |
32
- | `conversation_list` | Conversation |
33
- | `datapack_list` | Datapack |
34
- | `datapack_describe` | Datapack |
35
-
36
- ### write scope (11 tools)
37
-
38
- | Tool | Group |
39
- |------|-------|
40
- | `db_run_write_query` | Database |
41
- | `knowledgebase_trigger_index_creation` | RAG |
42
- | `knowledgebase_add_source_to_index` | RAG |
43
- | `memory_add` | Memory |
44
- | `memory_update` | Memory |
45
- | `memory_delete_by_id` | Memory |
46
- | `conversation_create` | Conversation |
47
- | `conversation_add_message` | Conversation |
48
- | `conversation_update` | Conversation |
49
- | `datapack_create` | Datapack |
50
- | `datapack_update` | Datapack |
51
-
52
- ### admin scope (3 tools — destructive, irreversible)
53
-
54
- | Tool | Group |
55
- |------|-------|
56
- | `memory_delete_all` | Memory |
57
- | `conversation_delete` | Conversation |
58
- | `datapack_delete` | Datapack |
59
-
60
- Agent and knowledge-base lifecycle tools are not MCP-exposed — see "Platform capabilities NOT exposed via MCP" in `tools-overview.md`.
61
-
62
- ## Best Practices
63
-
64
- 1. **Use the minimum scope needed.** `scope="read"` for reads documents intent and prevents accidental writes.
65
- 2. **The scope is a parameter, not an auth token.** When OAuth is enabled, the actual permission check happens against the OAuth token's claims. Both `scope` parameter and OAuth permissions must be satisfied.
66
-
67
- ## Common Mistakes
68
-
69
- **WRONG:** `scope="all"` — there is no "all" scope. Valid values are exactly: `"read"`, `"write"`, `"admin"`. Nothing else.
70
-
71
- Other invalid values that will fail: `"readwrite"`, `"rw"`, `"full"`, `"any"`.
72
-
73
- The error `Insufficient scope: 'all'. This tool requires 'read' or higher.` means the scope string was not recognized as a valid value. Always use one of the three valid strings.
74
-
75
- **Default choice:** Start with `scope="read"` unless you specifically need to write or delete.