@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.
- package/README.md +28 -19
- package/assets/hooks-handlers/before-submit-prompt.sh +2 -2
- package/assets/hooks-handlers/lib/capture.js +209 -144
- package/assets/hooks-handlers/lib/checkpoint-timer.js +23 -11
- package/assets/hooks-handlers/session-start.sh +6 -2
- package/assets/scripts/meko-statusline.sh +119 -0
- package/assets/scripts/package-desktop-skill.sh +27 -24
- package/assets/skills/meko-mcp-tools/SKILL.md +42 -50
- package/assets/skills/meko-mcp-tools/references/_sections.md +1 -1
- package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +75 -150
- package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +30 -36
- package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +6 -22
- package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +9 -8
- package/assets/skills/meko-mcp-tools/references/tools-overview.md +35 -42
- package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +28 -92
- package/assets/skills/meko-mcp-tools/references/tools-scope-permissions.md +5 -9
- package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +1 -81
- package/assets/skills/meko-mcp-tools-desktop/SKILL.md +100 -107
- package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +41 -11
- package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +1 -1
- package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +71 -150
- package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +12 -18
- package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +12 -15
- package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +35 -42
- package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +28 -92
- package/assets/skills/meko-mcp-tools-desktop/references/tools-scope-permissions.md +5 -9
- package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +1 -81
- package/assets/skills/meko-select-datapack-desktop/SKILL.md +198 -0
- package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +121 -0
- package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +66 -0
- package/bin/create-meko-setup.mjs +29 -0
- package/lib/clients/claude-code.mjs +133 -6
- package/lib/clients/claude-desktop.mjs +37 -12
- package/lib/clients/codex.mjs +306 -5
- package/lib/clients/common.mjs +41 -1
- package/lib/clients/cursor.mjs +75 -19
- package/lib/config.mjs +26 -0
- package/lib/installer.mjs +24 -0
- package/lib/mcp-transport.mjs +5 -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-write-guardrails.md +0 -30
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Troubleshooting — meko-select-datapack-desktop
|
|
2
|
+
|
|
3
|
+
## Symptom: I pinned a datapack but the next Meko call doesn't use it
|
|
4
|
+
|
|
5
|
+
**Likely causes**
|
|
6
|
+
|
|
7
|
+
1. **You forgot the per-turn `memory_search`.** Desktop has no SessionStart hook, so the skill teaches you to read the pin yourself at the top of every Meko-touching turn. If you skip that lookup, you'll send Meko calls with no `datapack_id` and the server resolves the default. Add the lookup; cache the result for the rest of the turn.
|
|
8
|
+
2. **You used `agent_id="meko_agent"` for the pin write or read.** The pin lives under `agent_id="claude_desktop"`, exclusively. Cross-project facts go under `meko_agent`, but the pin is a Desktop-specific preference and should stay scoped.
|
|
9
|
+
3. **The pin write failed silently.** Always verify-after-write — call `memory_search` for `meko_active_datapack` immediately after `memory_add` and confirm the row appears. If it doesn't, the write was lost (latency, server error, wrong scope) and the pin doesn't actually exist.
|
|
10
|
+
|
|
11
|
+
## Symptom: `memory_search` returns multiple pin memories
|
|
12
|
+
|
|
13
|
+
You (or an earlier run) created duplicates by using `memory_add` instead of `memory_update` on a switch. Resolution:
|
|
14
|
+
|
|
15
|
+
1. Pick the most recent (highest `selected_at` in the text body).
|
|
16
|
+
2. `memory_delete_by_id` the others.
|
|
17
|
+
3. From now on, switch via `memory_update` on the surviving id — never `memory_add`.
|
|
18
|
+
|
|
19
|
+
The cookbook's Flow 2 demonstrates the correct switch path.
|
|
20
|
+
|
|
21
|
+
## Symptom: A pinned datapack returns "not found" when used
|
|
22
|
+
|
|
23
|
+
The pin became stale — the datapack was deleted in another client. Tell the user:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
The pinned datapack `<name>` (`<id>`) no longer exists on the server.
|
|
27
|
+
Run `meko-select-datapack-desktop` to pick a fresh one, or `clear` to unpin.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Then either re-run the skill or delete the pin memory. Do not silently fall back to the default — the user explicitly asked for a specific datapack.
|
|
31
|
+
|
|
32
|
+
## Symptom: Substring search matches multiple datapacks
|
|
33
|
+
|
|
34
|
+
Never auto-pick when there's ambiguity. Re-render just the matching rows numbered from 1, and ask the user to pick a number from the filtered subset. Same as the Claude Code variant.
|
|
35
|
+
|
|
36
|
+
## Symptom: `datapack_list` returns an empty array
|
|
37
|
+
|
|
38
|
+
The user has zero datapacks. Print:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
You don't have any datapacks yet. Create one with:
|
|
42
|
+
|
|
43
|
+
datapack_create(scope="admin", name="<your-datapack-name>")
|
|
44
|
+
|
|
45
|
+
Or visit the Meko Cloud console (Datapacks → New datapack).
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Stop. Don't `memory_add` an empty pin.
|
|
49
|
+
|
|
50
|
+
## Symptom: User typed `cancel` after the table rendered
|
|
51
|
+
|
|
52
|
+
Leave the existing pin memory alone. Print:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
Cancelled. Active datapack unchanged.
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Do NOT delete on `cancel` — that's `clear`'s job.
|
|
59
|
+
|
|
60
|
+
## Symptom: `memory_search` for the pin times out
|
|
61
|
+
|
|
62
|
+
Memory tools are slow on prod (search 2-6s observed). If the search exceeds a reasonable budget, fall back to the server default for that single call and tell the user *"Couldn't read the active-datapack pin within budget — using server default for this Meko call."* Don't guess a UUID; don't silently retry indefinitely.
|
|
63
|
+
|
|
64
|
+
## Symptom: The pin survived a `claude_desktop` profile reset / app reinstall
|
|
65
|
+
|
|
66
|
+
That's expected — the pin is a server-side memory, not local state. To explicitly forget across reinstalls, run the skill with `clear` BEFORE wiping local state, or `memory_delete_by_id` the pin row directly.
|
|
@@ -28,6 +28,8 @@ const options = {
|
|
|
28
28
|
'cursor-hook-mode': { type: 'string', default: 'native' },
|
|
29
29
|
'skip-skills': { type: 'boolean', default: false },
|
|
30
30
|
'skip-validation': { type: 'boolean', default: false },
|
|
31
|
+
statusline: { type: 'boolean', default: false },
|
|
32
|
+
'no-statusline': { type: 'boolean', default: false },
|
|
31
33
|
'no-canary': { type: 'boolean', default: false },
|
|
32
34
|
'dry-run': { type: 'boolean', default: false },
|
|
33
35
|
'check-updates': { type: 'boolean', default: false },
|
|
@@ -111,6 +113,11 @@ Options:
|
|
|
111
113
|
--name <name> MCP server name in config (default: meko)
|
|
112
114
|
--cursor-hook-mode Cursor hook mode: native or claude-compat (default: native)
|
|
113
115
|
--skip-skills Skip plugin and skills installation
|
|
116
|
+
--statusline Wire the Meko datapack-pin indicator into Claude Code's
|
|
117
|
+
statusline (composes with any existing statusLine).
|
|
118
|
+
Default: ON under --yes, prompted otherwise.
|
|
119
|
+
Claude Code only.
|
|
120
|
+
--no-statusline Skip statusline wiring even under --yes.
|
|
114
121
|
--skip-validation Skip post-install validation
|
|
115
122
|
--no-canary Skip the canary datapack_list call. The
|
|
116
123
|
connectivity + tools/list checks still run.
|
|
@@ -398,6 +405,11 @@ async function main() {
|
|
|
398
405
|
let url = flags.url ?? null;
|
|
399
406
|
let apiKey = normalizeApiKey(flags['api-key']);
|
|
400
407
|
let skipSkills = flags['skip-skills'];
|
|
408
|
+
// Statusline: --no-statusline always wins. --statusline forces on. Otherwise
|
|
409
|
+
// default to on under --yes (programmatic), and prompt with Y default
|
|
410
|
+
// interactively. Resolved later after the interactive prompts since the
|
|
411
|
+
// flag may flip in the interactive flow.
|
|
412
|
+
let installStatusline = !flags['no-statusline'] && (flags.statusline || flags.yes);
|
|
401
413
|
// Tracks whether the full interactive flow already prompted for the API
|
|
402
414
|
// key. The Cloud-URL fallback below should only run for the
|
|
403
415
|
// semi-interactive `--url ... --client ...` paste path — otherwise
|
|
@@ -498,6 +510,19 @@ async function main() {
|
|
|
498
510
|
if (!installSkills) {
|
|
499
511
|
skipSkills = true;
|
|
500
512
|
}
|
|
513
|
+
|
|
514
|
+
// Step 5 — statusline (Claude Code only). --no-statusline / --statusline
|
|
515
|
+
// skip the prompt; otherwise ask with Y default. The prompt only matters
|
|
516
|
+
// when at least one selected client is claude-code; for desktop / cursor /
|
|
517
|
+
// codex the install() orchestrator simply skips the step (the adapters
|
|
518
|
+
// don't expose installStatusline()).
|
|
519
|
+
if (!flags['no-statusline'] && !flags.statusline && clients.includes('claude-code')) {
|
|
520
|
+
installStatusline = await askYesNo(
|
|
521
|
+
'Show the active Meko datapack in Claude Code\'s statusline? ' +
|
|
522
|
+
'(composes with any existing statusLine — non-destructive)',
|
|
523
|
+
true,
|
|
524
|
+
);
|
|
525
|
+
}
|
|
501
526
|
}
|
|
502
527
|
|
|
503
528
|
// Validation — URL must be set by now
|
|
@@ -544,6 +569,10 @@ async function main() {
|
|
|
544
569
|
skipSkills,
|
|
545
570
|
skipValidation: flags['skip-validation'],
|
|
546
571
|
skipCanary: flags['no-canary'],
|
|
572
|
+
// Statusline is Claude Code only. For other clients the orchestrator
|
|
573
|
+
// skips the step regardless of the flag because the adapter doesn't
|
|
574
|
+
// expose installStatusline(); explicit guard keeps step counts honest.
|
|
575
|
+
installStatusline: client === 'claude-code' && installStatusline,
|
|
547
576
|
dryRun: flags['dry-run'],
|
|
548
577
|
verbose: flags.verbose,
|
|
549
578
|
yes: flags.yes,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync } from "node:fs";
|
|
1
|
+
import { chmodSync, copyFileSync, mkdirSync, unlinkSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
@@ -14,8 +14,10 @@ import {
|
|
|
14
14
|
} from "../config.mjs";
|
|
15
15
|
import { detectClaude, detectExistingMeko, getClaudeCodeConfigPath } from "../detect.mjs";
|
|
16
16
|
import {
|
|
17
|
+
MEKO_MCP_USER_AGENT,
|
|
17
18
|
redactKey,
|
|
18
19
|
resolveHooksRoot,
|
|
20
|
+
resolveStatuslineSource,
|
|
19
21
|
run,
|
|
20
22
|
shellQuote,
|
|
21
23
|
writeInstallerMarker,
|
|
@@ -44,15 +46,23 @@ const MEKO_MARKETPLACE_NAME = "meko-agent-skills";
|
|
|
44
46
|
|
|
45
47
|
// Meko MCP tools to union into `permissions.allow` so Claude Code resolves
|
|
46
48
|
// them without the deferred-tools ToolSearch round-trip (issue #5). The set
|
|
47
|
-
// is intentionally tight —
|
|
48
|
-
// the meko-mcp-tools skill — to avoid bloating every user's
|
|
49
|
+
// is intentionally tight — the memory/conversation tools the agent actually
|
|
50
|
+
// reaches for per the meko-mcp-tools skill — to avoid bloating every user's
|
|
51
|
+
// settings.
|
|
52
|
+
//
|
|
53
|
+
// `memory_add` stays on this list even though the skill no longer tells the
|
|
54
|
+
// agent to fire it proactively (server-side extraction now captures
|
|
55
|
+
// conversationally-stated facts automatically). It's kept because the skill
|
|
56
|
+
// still uses memory_add for the narrow explicit-save cases — "remember this",
|
|
57
|
+
// output-only/tool-derived facts, and overwriting corrections — and pre-
|
|
58
|
+
// allowing it keeps those calls prompt-free. `conversation_add_message` is the
|
|
59
|
+
// tool that now drives capture-plus-extraction, so it must resolve instantly.
|
|
49
60
|
//
|
|
50
61
|
// NOTE: `permissions.allow` is verified to cover user-prompt bypass. Whether
|
|
51
62
|
// it also bypasses the deferred-tools schema-fetch gate must be re-verified
|
|
52
63
|
// empirically before this flag is considered load-bearing for #5. Tracked
|
|
53
64
|
// as Phase 10.1 in skills/TEST_PLAN.md.
|
|
54
65
|
const MEKO_ALWAYS_AVAILABLE_TOOLS = [
|
|
55
|
-
"mcp__meko__db_summarize_database",
|
|
56
66
|
"mcp__meko__memory_add",
|
|
57
67
|
"mcp__meko__memory_search",
|
|
58
68
|
"mcp__meko__memory_get_all",
|
|
@@ -125,7 +135,8 @@ export function createClaudeCodeAdapter(options = {}) {
|
|
|
125
135
|
buildRegisterCommand({ name, url, scope, apiKey }) {
|
|
126
136
|
let cmd =
|
|
127
137
|
`claude mcp add --transport http --scope ${shellQuote(scope)} ` +
|
|
128
|
-
`${shellQuote(name)} ${shellQuote(url)}
|
|
138
|
+
`${shellQuote(name)} ${shellQuote(url)} ` +
|
|
139
|
+
`--header ${shellQuote(`User-Agent: ${MEKO_MCP_USER_AGENT}`)}`;
|
|
129
140
|
if (apiKey) {
|
|
130
141
|
cmd += ` --header ${shellQuote(`Authorization: Bearer ${apiKey}`)}`;
|
|
131
142
|
}
|
|
@@ -207,14 +218,85 @@ export function createClaudeCodeAdapter(options = {}) {
|
|
|
207
218
|
);
|
|
208
219
|
}
|
|
209
220
|
},
|
|
221
|
+
/**
|
|
222
|
+
* Wire the Meko datapack-pin statusline contribution into ~/.claude/settings.json.
|
|
223
|
+
*
|
|
224
|
+
* Composable: if the user already has a `statusLine.command`, that command
|
|
225
|
+
* is preserved as the inner command and our wrapper appends `Meko: <name>`
|
|
226
|
+
* after its output. Idempotent: if our wrapper is already wired, no-op.
|
|
227
|
+
*
|
|
228
|
+
* Restore-on-uninstall: the original command is preserved in
|
|
229
|
+
* settings.json under `_meko.statusLine.previousCommand` so `uninstall()`
|
|
230
|
+
* can restore it. Absent that key (fresh install), uninstall removes the
|
|
231
|
+
* whole `statusLine` block.
|
|
232
|
+
*/
|
|
233
|
+
installStatusline({ dryRun, verbose, log, settingsPath }) {
|
|
234
|
+
const scriptSource = resolveStatuslineSource();
|
|
235
|
+
const scriptDest = join(homedir(), ".claude", "meko-statusline.sh");
|
|
236
|
+
|
|
237
|
+
const existing = readJsonFile(settingsPath) ?? {};
|
|
238
|
+
const existingCmd =
|
|
239
|
+
existing?.statusLine?.type === "command" && typeof existing?.statusLine?.command === "string"
|
|
240
|
+
? existing.statusLine.command.trim()
|
|
241
|
+
: "";
|
|
242
|
+
|
|
243
|
+
// Detect prior install of our wrapper. Re-wiring is a no-op for the
|
|
244
|
+
// command (already points at the wrapper); we still re-copy the script
|
|
245
|
+
// so a refreshed installer ships an updated copy.
|
|
246
|
+
const alreadyWired = existingCmd.includes("meko-statusline.sh");
|
|
247
|
+
const innerCmd =
|
|
248
|
+
alreadyWired
|
|
249
|
+
? (existing?._meko?.statusLine?.previousCommand ?? "")
|
|
250
|
+
: existingCmd;
|
|
251
|
+
|
|
252
|
+
const wrapperCmd = innerCmd
|
|
253
|
+
? `${shellQuote(scriptDest)} --inner-cmd ${shellQuote(innerCmd)}`
|
|
254
|
+
: shellQuote(scriptDest);
|
|
255
|
+
|
|
256
|
+
if (dryRun) {
|
|
257
|
+
log.dim(` Would copy ${scriptSource} → ${scriptDest}`);
|
|
258
|
+
log.dim(` Would set ${settingsPath} statusLine.command = ${wrapperCmd}`);
|
|
259
|
+
if (innerCmd) log.dim(` (preserving prior inner command: ${innerCmd})`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
mkdirSync(join(homedir(), ".claude"), { recursive: true });
|
|
264
|
+
copyFileSync(scriptSource, scriptDest);
|
|
265
|
+
chmodSync(scriptDest, 0o755);
|
|
266
|
+
|
|
267
|
+
const patch = {
|
|
268
|
+
statusLine: { type: "command", command: wrapperCmd },
|
|
269
|
+
};
|
|
270
|
+
// Stash the inner command so uninstall can restore it. Only write this
|
|
271
|
+
// namespace on the first install (when alreadyWired is false), so a
|
|
272
|
+
// re-install never overwrites the genuine original with our wrapper.
|
|
273
|
+
if (!alreadyWired) {
|
|
274
|
+
patch._meko = { statusLine: { previousCommand: innerCmd || null } };
|
|
275
|
+
}
|
|
276
|
+
mergeSettings(settingsPath, patch);
|
|
277
|
+
|
|
278
|
+
if (verbose) {
|
|
279
|
+
log.dim(` Statusline script: ${scriptDest}`);
|
|
280
|
+
log.dim(` Wrapper command: ${wrapperCmd}`);
|
|
281
|
+
if (innerCmd) log.dim(` Inner command preserved: ${innerCmd}`);
|
|
282
|
+
if (alreadyWired) log.dim(` (refreshed existing wrapper, no settings change beyond script copy)`);
|
|
283
|
+
}
|
|
284
|
+
},
|
|
210
285
|
installSkills({ scope, dryRun, verbose, log }) {
|
|
211
286
|
// NB: we deliberately do NOT pass --sparse. Claude CLI's sparse
|
|
212
287
|
// path filter silently produces an empty clone for our layout,
|
|
213
288
|
// leaving the marketplace.json missing. Full clone is small
|
|
214
289
|
// (~few MB, mostly markdown) and is the only way that reliably
|
|
215
290
|
// succeeds today.
|
|
291
|
+
// Marketplace source is normally the upstream GitHub repo. A dev/e2e
|
|
292
|
+
// override lets a test install the plugin from a LOCAL working-tree
|
|
293
|
+
// checkout (a directory path) so unreleased skill changes can be
|
|
294
|
+
// exercised end-to-end before they land on `yugabyte/meko-mcp-server`.
|
|
295
|
+
// Default is unchanged; only set MEKO_PLUGIN_MARKETPLACE_SOURCE in tests.
|
|
296
|
+
const marketplaceSource =
|
|
297
|
+
process.env.MEKO_PLUGIN_MARKETPLACE_SOURCE || "yugabyte/meko-mcp-server";
|
|
216
298
|
const marketplaceCmd =
|
|
217
|
-
`claude plugin marketplace add
|
|
299
|
+
`claude plugin marketplace add ${shellQuote(marketplaceSource)} --scope ${shellQuote(scope)}`;
|
|
218
300
|
// `claude plugin list` dropped --scope in CLI 2.1.x; it prints all
|
|
219
301
|
// scopes. The match-by-token regex used below is scope-agnostic by
|
|
220
302
|
// design — `plugin update` failing with "not found" on a
|
|
@@ -393,6 +475,44 @@ export function createClaudeCodeAdapter(options = {}) {
|
|
|
393
475
|
results.push({ key: "hooks", ok: false, message: `Could not prune Meko hook entries: ${err.message}` });
|
|
394
476
|
}
|
|
395
477
|
|
|
478
|
+
try {
|
|
479
|
+
// Restore the user's prior statusLine.command (if any) and remove our
|
|
480
|
+
// bookkeeping. If no prior command was preserved, remove the whole
|
|
481
|
+
// statusLine block — installStatusline() only added it on first
|
|
482
|
+
// install, so we own it cleanly.
|
|
483
|
+
const cfg = readJsonFile(settingsPath) ?? {};
|
|
484
|
+
const wrapperWired =
|
|
485
|
+
cfg?.statusLine?.type === "command" &&
|
|
486
|
+
typeof cfg?.statusLine?.command === "string" &&
|
|
487
|
+
cfg.statusLine.command.includes("meko-statusline.sh");
|
|
488
|
+
if (wrapperWired) {
|
|
489
|
+
const prev = cfg?._meko?.statusLine?.previousCommand;
|
|
490
|
+
if (typeof prev === "string" && prev.length > 0) {
|
|
491
|
+
cfg.statusLine = { type: "command", command: prev };
|
|
492
|
+
results.push({ key: "statusline", ok: true, message: "Restored prior statusLine.command." });
|
|
493
|
+
} else {
|
|
494
|
+
delete cfg.statusLine;
|
|
495
|
+
results.push({ key: "statusline", ok: true, message: "Removed Meko statusLine wrapper." });
|
|
496
|
+
}
|
|
497
|
+
if (cfg?._meko?.statusLine) {
|
|
498
|
+
delete cfg._meko.statusLine;
|
|
499
|
+
if (cfg._meko && Object.keys(cfg._meko).length === 0) delete cfg._meko;
|
|
500
|
+
}
|
|
501
|
+
writeJsonFile(settingsPath, cfg);
|
|
502
|
+
// Remove the copied script too — installStatusline() owns it, so
|
|
503
|
+
// leaving it behind would be a stale orphan after uninstall.
|
|
504
|
+
try {
|
|
505
|
+
unlinkSync(join(homedir(), ".claude", "meko-statusline.sh"));
|
|
506
|
+
} catch {
|
|
507
|
+
// Best-effort: absent file (ENOENT) or unreadable dir is fine.
|
|
508
|
+
}
|
|
509
|
+
} else {
|
|
510
|
+
results.push({ key: "statusline", ok: false, message: "Statusline wrapper was not installed (nothing to remove)." });
|
|
511
|
+
}
|
|
512
|
+
} catch (err) {
|
|
513
|
+
results.push({ key: "statusline", ok: false, message: `Could not restore statusLine: ${err.message}` });
|
|
514
|
+
}
|
|
515
|
+
|
|
396
516
|
try {
|
|
397
517
|
// Qualified plugin@marketplace form — CLI 2.1.x rejects the bare
|
|
398
518
|
// name (issue #63). Before: `catch` swallowed every failure as
|
|
@@ -460,6 +580,13 @@ export function createClaudeCodeAdapter(options = {}) {
|
|
|
460
580
|
}
|
|
461
581
|
|
|
462
582
|
const authHeader = getHeader(entry.headers, "Authorization");
|
|
583
|
+
const userAgent = getHeader(entry.headers, "User-Agent");
|
|
584
|
+
if (!userAgent) {
|
|
585
|
+
return {
|
|
586
|
+
ok: false,
|
|
587
|
+
message: `Server "${serverName}" in ${configPath} is missing the User-Agent header required by the Cloud WAF.`,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
463
590
|
if (apiKey) {
|
|
464
591
|
const expected = `Bearer ${apiKey}`;
|
|
465
592
|
if (authHeader !== expected) {
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
removeKeys,
|
|
10
10
|
} from "../config.mjs";
|
|
11
11
|
import {
|
|
12
|
+
MEKO_MCP_USER_AGENT,
|
|
12
13
|
getAssetRoot,
|
|
13
14
|
getInstallerVersion,
|
|
14
15
|
markerIsStale,
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
* "meko": {
|
|
32
33
|
* "command": "npx",
|
|
33
34
|
* "args": ["-y", "mcp-remote", "<url>", "--transport", "http-only",
|
|
35
|
+
* "--header", "User-Agent:meko-mcp-installer/1.0 (...)",
|
|
34
36
|
* "--header", "Authorization:Bearer ${MEKO_API_KEY}"],
|
|
35
37
|
* "env": { "MEKO_API_KEY": "<key>" }
|
|
36
38
|
* }
|
|
@@ -122,8 +124,10 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
122
124
|
mergeSettings(path, { mcpServers: { [name]: server } });
|
|
123
125
|
},
|
|
124
126
|
setHookEnvironment({ dryRun, log }) {
|
|
125
|
-
// Claude Desktop has no hook API
|
|
126
|
-
//
|
|
127
|
+
// Claude Desktop has no hook API, so the skill mandates per-turn
|
|
128
|
+
// conversation_add_message posting (the server extracts memories from
|
|
129
|
+
// those posts); proactive memory_add is no longer the model. Nothing to
|
|
130
|
+
// write here.
|
|
127
131
|
if (dryRun) {
|
|
128
132
|
log.dim(" No hook environment required for Claude Desktop (skill-driven).");
|
|
129
133
|
}
|
|
@@ -132,6 +136,8 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
132
136
|
const assetRoot = assetRootResolver();
|
|
133
137
|
const script = join(assetRoot, "scripts", "package-desktop-skill.sh");
|
|
134
138
|
const output = join(assetRoot, "meko-mcp-tools-desktop.skill");
|
|
139
|
+
const selectOutput = join(assetRoot, "meko-select-datapack-desktop.skill");
|
|
140
|
+
const outputs = [output, selectOutput];
|
|
135
141
|
// The .skill output is a ZIP file, so we can't drop a marker inside it.
|
|
136
142
|
// Stamp the marker in the parent dir instead — it signals which
|
|
137
143
|
// installer version produced the bundle sitting alongside it, so a
|
|
@@ -141,9 +147,11 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
141
147
|
|
|
142
148
|
if (dryRun) {
|
|
143
149
|
log.dim(` Would run: bash ${script}`);
|
|
144
|
-
|
|
150
|
+
for (const skillOutput of outputs) {
|
|
151
|
+
log.dim(` Would produce: ${skillOutput}`);
|
|
152
|
+
}
|
|
145
153
|
log.dim(` Would write .meko-installer marker (version=${currentVersion}) in ${markerDir}`);
|
|
146
|
-
return { marketplace: "skipped", skills: "dry-run", output };
|
|
154
|
+
return { marketplace: "skipped", skills: "dry-run", output, outputs };
|
|
147
155
|
}
|
|
148
156
|
|
|
149
157
|
if (!existsSync(script)) {
|
|
@@ -158,7 +166,7 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
158
166
|
// re-drag the .skill into Claude Desktop's Extensions panel; we can't do
|
|
159
167
|
// that step for them. Keep rebuilding either way.
|
|
160
168
|
const existingMarker = readInstallerMarker(markerDir);
|
|
161
|
-
if (existsSync(
|
|
169
|
+
if (outputs.some((skillOutput) => existsSync(skillOutput)) && existingMarker && markerIsStale(markerDir, currentVersion)) {
|
|
162
170
|
log.warn(
|
|
163
171
|
`Claude Desktop .skill bundle was produced by installer ${existingMarker.version}. ` +
|
|
164
172
|
`Rebuilding — you will need to re-drag it into Claude Desktop > Settings > Extensions.`,
|
|
@@ -172,8 +180,9 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
172
180
|
return { marketplace: "skipped", skills: "error", error: err.message };
|
|
173
181
|
}
|
|
174
182
|
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
const missingOutputs = outputs.filter((skillOutput) => !existsSync(skillOutput));
|
|
184
|
+
if (missingOutputs.length) {
|
|
185
|
+
log.warn(`Expected skill bundle(s) were not produced: ${missingOutputs.join(", ")}`);
|
|
177
186
|
return { marketplace: "skipped", skills: "error" };
|
|
178
187
|
}
|
|
179
188
|
|
|
@@ -186,12 +195,14 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
186
195
|
log.warn(`Could not write .skill installer marker: ${err.message}`);
|
|
187
196
|
}
|
|
188
197
|
|
|
189
|
-
|
|
198
|
+
for (const skillOutput of outputs) {
|
|
199
|
+
log.info(`Built Meko skill bundle: ${skillOutput}`);
|
|
200
|
+
}
|
|
190
201
|
log.info(
|
|
191
|
-
"Install
|
|
192
|
-
"or drag
|
|
202
|
+
"Install both in Claude Desktop: Settings > Extensions > Import skill, " +
|
|
203
|
+
"or drag each .skill file onto the Claude Desktop window.",
|
|
193
204
|
);
|
|
194
|
-
return { marketplace: "skipped", skills: "ok", output };
|
|
205
|
+
return { marketplace: "skipped", skills: "ok", output, outputs };
|
|
195
206
|
},
|
|
196
207
|
uninstall({ name }) {
|
|
197
208
|
const results = [];
|
|
@@ -246,6 +257,12 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
246
257
|
`Server "${serverName}" in ${path} is missing required "command" and "args" fields.`,
|
|
247
258
|
};
|
|
248
259
|
}
|
|
260
|
+
if (!entry.args.includes(`User-Agent:${MEKO_MCP_USER_AGENT}`)) {
|
|
261
|
+
return {
|
|
262
|
+
ok: false,
|
|
263
|
+
message: `Server "${serverName}" in ${path} is missing the User-Agent header required by the Cloud WAF.`,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
249
266
|
|
|
250
267
|
return { ok: true, message: `Server "${serverName}" is registered in ${path}.` };
|
|
251
268
|
},
|
|
@@ -262,7 +279,15 @@ export function createClaudeDesktopAdapter(options = {}) {
|
|
|
262
279
|
* @returns {{command: string, args: string[], env?: Record<string,string>}}
|
|
263
280
|
*/
|
|
264
281
|
export function buildMcpRemoteEntry({ url, apiKey }) {
|
|
265
|
-
const args = [
|
|
282
|
+
const args = [
|
|
283
|
+
"-y",
|
|
284
|
+
"mcp-remote",
|
|
285
|
+
url,
|
|
286
|
+
"--transport",
|
|
287
|
+
"http-only",
|
|
288
|
+
"--header",
|
|
289
|
+
`User-Agent:${MEKO_MCP_USER_AGENT}`,
|
|
290
|
+
];
|
|
266
291
|
const entry = { command: "npx", args };
|
|
267
292
|
if (apiKey) {
|
|
268
293
|
// `Authorization:Bearer ${MEKO_API_KEY}` with no space around the colon
|