@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.
- package/README.md +59 -25
- package/assets/hooks-handlers/before-submit-prompt.sh +2 -2
- package/assets/hooks-handlers/codex-session-end.sh +78 -0
- package/assets/hooks-handlers/lib/capture.js +366 -152
- package/assets/hooks-handlers/lib/checkpoint-timer.js +67 -13
- package/assets/hooks-handlers/session-end.sh +10 -0
- package/assets/hooks-handlers/session-start.sh +14 -3
- package/assets/hooks-handlers/test_checkpoint_timer.sh +162 -0
- package/assets/scripts/meko-statusline.sh +119 -0
- package/assets/scripts/package-desktop-skill.sh +64 -30
- package/assets/skills/meko-mcp-tools/SKILL.md +84 -86
- package/assets/skills/meko-mcp-tools/references/_sections.md +2 -4
- package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +29 -25
- package/assets/skills/meko-mcp-tools/references/tools-conversation-dedup.md +5 -7
- package/assets/skills/meko-mcp-tools/references/tools-conversation-reasoning.md +50 -0
- package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +126 -184
- package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +31 -37
- package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +10 -26
- package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +26 -30
- package/assets/skills/meko-mcp-tools/references/tools-overview.md +60 -66
- package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +28 -93
- package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +5 -99
- package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +9 -8
- package/assets/skills/meko-mcp-tools-desktop/SKILL.md +138 -138
- package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +46 -16
- package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +2 -2
- package/assets/skills/meko-mcp-tools-desktop/references/tools-agent-id-conventions.md +28 -24
- package/assets/skills/meko-mcp-tools-desktop/references/tools-conversation-reasoning.md +50 -0
- package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +120 -174
- package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +16 -22
- package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +28 -34
- package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +60 -66
- package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +28 -93
- package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +4 -98
- package/assets/skills/meko-select-datapack-desktop/SKILL.md +208 -0
- package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +121 -0
- package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +41 -0
- package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +66 -0
- package/bin/create-meko-setup.mjs +74 -6
- package/lib/clients/claude-code.mjs +137 -6
- package/lib/clients/claude-desktop.mjs +51 -12
- package/lib/clients/codex.mjs +329 -13
- package/lib/clients/common.mjs +43 -1
- package/lib/clients/cursor.mjs +82 -21
- package/lib/config.mjs +26 -0
- package/lib/detect.mjs +7 -2
- package/lib/installer.mjs +24 -0
- package/lib/mcp-transport.mjs +5 -0
- package/lib/prompt.mjs +1 -1
- package/lib/url-selection.mjs +55 -0
- package/lib/validate.mjs +0 -1
- package/package.json +1 -1
- package/scripts/bundle-assets.mjs +8 -0
- package/assets/skills/meko-mcp-tools/references/tools-db-query-patterns.md +0 -38
- package/assets/skills/meko-mcp-tools/references/tools-scope-permissions.md +0 -75
- package/assets/skills/meko-mcp-tools/references/tools-write-guardrails.md +0 -30
- package/assets/skills/meko-mcp-tools-desktop/references/tools-scope-permissions.md +0 -75
package/lib/clients/cursor.mjs
CHANGED
|
@@ -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
|
|
74
|
-
|
|
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 =
|
|
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({
|
|
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({
|
|
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 =
|
|
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
|
-
|
|
221
|
-
|
|
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:
|
|
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
|
|
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({
|
|
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 =
|
|
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 !
|
|
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 !
|
|
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
|
-
|
|
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.");
|
package/lib/mcp-transport.mjs
CHANGED
|
@@ -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
|
|
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
package/package.json
CHANGED
|
@@ -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.
|