@yugabytedb/meko-mcp 1.1.0 → 1.2.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 (26) hide show
  1. package/README.md +68 -19
  2. package/assets/hooks-handlers/before-submit-prompt.sh +44 -0
  3. package/assets/hooks-handlers/cursor-session-start.sh +42 -0
  4. package/assets/hooks-handlers/lib/capture.js +291 -40
  5. package/assets/scripts/package-desktop-skill.sh +36 -0
  6. package/assets/skills/meko-mcp-tools/references/tools-overview.md +1 -1
  7. package/assets/skills/meko-mcp-tools-desktop/SKILL.md +251 -0
  8. package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +150 -0
  9. package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +24 -0
  10. package/assets/skills/meko-mcp-tools-desktop/references/tools-agent-id-conventions.md +99 -0
  11. package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +406 -0
  12. package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +69 -0
  13. package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +217 -0
  14. package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +112 -0
  15. package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +134 -0
  16. package/assets/skills/meko-mcp-tools-desktop/references/tools-scope-permissions.md +75 -0
  17. package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +155 -0
  18. package/bin/create-meko-setup.mjs +4 -4
  19. package/lib/clients/claude-desktop.mjs +11 -8
  20. package/lib/clients/codex.mjs +236 -4
  21. package/lib/clients/common.mjs +81 -0
  22. package/lib/clients/cursor.mjs +47 -10
  23. package/lib/config.mjs +26 -0
  24. package/lib/installer.mjs +2 -2
  25. package/package.json +1 -1
  26. package/scripts/bundle-assets.mjs +8 -0
@@ -21,6 +21,7 @@
21
21
  */
22
22
 
23
23
  const fs = require("fs");
24
+ const os = require("os");
24
25
  const path = require("path");
25
26
  const http = require("http");
26
27
  const https = require("https");
@@ -71,20 +72,46 @@ function deriveAgentId(opts) {
71
72
  * Resolution order:
72
73
  * 1. MEKO_AGENT_ID env override (verbatim).
73
74
  * 2. cwd from the transcript header (peekTranscriptMetadata).
74
- * 3. process.cwd() — last resort if no transcript metadata yet.
75
+ * 3. explicit workspace/project roots from the hook input.
76
+ * 4. cwd from the hook input.
77
+ * 5. process.cwd() — last resort if no transcript metadata yet.
75
78
  */
76
- function resolveSessionAgentId(transcriptPath) {
79
+ function resolveSessionAgentId(transcriptPath, hookInput = null) {
77
80
  const envOverride = process.env.MEKO_AGENT_ID;
78
81
  if (envOverride && envOverride.trim()) return envOverride.trim();
79
82
  const meta = transcriptPath ? peekTranscriptMetadata(transcriptPath) : null;
80
- const cwd = (meta && meta.cwd) || process.cwd();
81
- return deriveAgentId({ client: "claude_code", cwd, envOverride });
83
+ const nested = hookInput && hookInput.hookSpecificInput && typeof hookInput.hookSpecificInput === "object"
84
+ ? hookInput.hookSpecificInput
85
+ : {};
86
+ const roots = hookInput && Array.isArray(hookInput.workspace_roots)
87
+ ? hookInput.workspace_roots
88
+ : [];
89
+ const nestedRoots = Array.isArray(nested.workspace_roots) ? nested.workspace_roots : [];
90
+ const workspaceRoot = [...roots, ...nestedRoots]
91
+ .find((item) => typeof item === "string" && item.trim());
92
+ const cwd =
93
+ (meta && meta.cwd) ||
94
+ hookInput?.workspace_path ||
95
+ hookInput?.workspacePath ||
96
+ hookInput?.project_root ||
97
+ nested.workspace_path ||
98
+ nested.workspacePath ||
99
+ nested.project_root ||
100
+ workspaceRoot ||
101
+ hookInput?.cwd ||
102
+ nested.cwd ||
103
+ process.cwd();
104
+ const client = process.env.MEKO_HOOK_CLIENT === "cursor" ? "cursor" : "claude_code";
105
+ return deriveAgentId({ client, cwd, envOverride });
82
106
  }
83
107
  const MEKO_API_KEY = process.env.MEKO_API_KEY || "";
84
108
  const MEKO_API_TIMEOUT = parseInt(process.env.MEKO_API_TIMEOUT || "10", 10) * 1000;
85
109
  const WATERMARK_DIR =
86
110
  process.env.MEKO_WATERMARK_DIR ||
87
- path.join(process.env.HOME || "~", ".claude", "meko-capture");
111
+ path.join(os.homedir(), ".claude", "meko-capture");
112
+ const SESSION_CACHE_DIR =
113
+ process.env.MEKO_SESSION_CACHE_DIR ||
114
+ path.join(os.homedir(), ".cursor", "meko-session-cache");
88
115
 
89
116
  let mcpRequestId = 0;
90
117
  let mcpSessionId = null; // MCP Streamable HTTP session ID
@@ -367,7 +394,7 @@ function mcpPost(jsonBody) {
367
394
  return;
368
395
  }
369
396
  try {
370
- const parsed = JSON.parse(data);
397
+ const parsed = parseMcpResponseBody(data);
371
398
  if (parsed.error) {
372
399
  reject(new Error(JSON.stringify(parsed.error)));
373
400
  } else {
@@ -395,6 +422,37 @@ function mcpPost(jsonBody) {
395
422
  });
396
423
  }
397
424
 
425
+ function parseMcpResponseBody(data) {
426
+ const trimmed = data.trim();
427
+ if (!trimmed) return null;
428
+
429
+ if (!trimmed.startsWith("event:") && !trimmed.startsWith("data:")) {
430
+ return JSON.parse(trimmed);
431
+ }
432
+
433
+ const payloads = [];
434
+ let current = [];
435
+ for (const line of data.split(/\r?\n/)) {
436
+ if (line === "") {
437
+ if (current.length) {
438
+ payloads.push(current.join("\n"));
439
+ current = [];
440
+ }
441
+ continue;
442
+ }
443
+ if (line.startsWith("data:")) {
444
+ current.push(line.startsWith("data: ") ? line.slice(6) : line.slice(5));
445
+ }
446
+ }
447
+ if (current.length) payloads.push(current.join("\n"));
448
+
449
+ const payload = payloads.find((item) => item.trim()) || "";
450
+ if (!payload) {
451
+ throw new Error("SSE response did not include a data payload");
452
+ }
453
+ return JSON.parse(payload);
454
+ }
455
+
398
456
  /**
399
457
  * Abort all pending MCP requests. Used by the preload timeout path so the
400
458
  * SessionStart hook can return promptly instead of waiting for a slow server
@@ -580,12 +638,112 @@ function hookEventName(hookType) {
580
638
  }
581
639
 
582
640
  function hookOutput(additionalContext) {
583
- return JSON.stringify({
641
+ // Emit the native coding-agent harness field while preserving the nested
642
+ // Claude-compatible shape for clients that still consume it.
643
+ const output = {
584
644
  hookSpecificOutput: {
585
645
  hookEventName: hookEventName(process.argv[2]),
586
646
  additionalContext: additionalContext || "",
587
647
  },
588
- });
648
+ };
649
+ if (process.argv[2] === "session-start") {
650
+ output.additional_context = additionalContext || "";
651
+ }
652
+ return JSON.stringify(output);
653
+ }
654
+
655
+ function nativeOutput(additionalContext) {
656
+ return JSON.stringify({ additional_context: additionalContext || "" });
657
+ }
658
+
659
+ function beforeSubmitOutput(shouldContinue, userMessage = "") {
660
+ const output = { continue: Boolean(shouldContinue) };
661
+ if (userMessage) output.user_message = userMessage;
662
+ return JSON.stringify(output);
663
+ }
664
+
665
+ function extractSessionId(hookInput, transcriptPath) {
666
+ if (transcriptPath) return path.basename(transcriptPath, ".jsonl");
667
+ const nested = hookInput && hookInput.hookSpecificInput && typeof hookInput.hookSpecificInput === "object"
668
+ ? hookInput.hookSpecificInput
669
+ : {};
670
+ for (const value of [
671
+ hookInput && hookInput.session_id,
672
+ hookInput && hookInput.sessionId,
673
+ nested.session_id,
674
+ nested.sessionId,
675
+ ]) {
676
+ if (typeof value === "string" && value.trim()) return value.trim();
677
+ }
678
+ return null;
679
+ }
680
+
681
+ function sessionCachePath(sessionId) {
682
+ if (!sessionId) return null;
683
+ const safe = sessionId.replace(/[^A-Za-z0-9_.-]/g, "_");
684
+ return path.join(SESSION_CACHE_DIR, `${safe}.json`);
685
+ }
686
+
687
+ function writeSessionCache(sessionId, payload) {
688
+ const filePath = sessionCachePath(sessionId);
689
+ if (!filePath) return;
690
+ const workspaceKey = payload.workspace_key || "";
691
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
692
+ const record = {
693
+ session_id: sessionId,
694
+ updated_at: new Date().toISOString(),
695
+ ...payload,
696
+ };
697
+ fs.writeFileSync(filePath, JSON.stringify(record));
698
+ if (workspaceKey) {
699
+ const safe = workspaceKey.replace(/[^A-Za-z0-9_.-]/g, "_");
700
+ fs.writeFileSync(path.join(SESSION_CACHE_DIR, `workspace-${safe}.json`), JSON.stringify(record));
701
+ }
702
+ }
703
+
704
+ function readSessionCache(sessionId) {
705
+ const filePath = sessionCachePath(sessionId);
706
+ if (!filePath) return null;
707
+ try {
708
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
709
+ } catch {
710
+ return null;
711
+ }
712
+ }
713
+
714
+ function workspaceKeyFromHookInput(hookInput, transcriptPath) {
715
+ const roots = hookInput && Array.isArray(hookInput.workspace_roots)
716
+ ? hookInput.workspace_roots
717
+ : [];
718
+ const firstRoot = roots.find((item) => typeof item === "string" && item.trim());
719
+ if (firstRoot) return firstRoot.trim();
720
+ if (transcriptPath) return path.dirname(transcriptPath);
721
+ const cwd = hookInput && typeof hookInput.cwd === "string" && hookInput.cwd.trim()
722
+ ? hookInput.cwd.trim()
723
+ : "";
724
+ return cwd || process.cwd();
725
+ }
726
+
727
+ function readWorkspaceSessionCache(workspaceKey) {
728
+ if (!workspaceKey) return null;
729
+ const safe = workspaceKey.replace(/[^A-Za-z0-9_.-]/g, "_");
730
+ try {
731
+ return JSON.parse(fs.readFileSync(path.join(SESSION_CACHE_DIR, `workspace-${safe}.json`), "utf-8"));
732
+ } catch {
733
+ return null;
734
+ }
735
+ }
736
+
737
+ function selectSessionCache(sessionId, workspaceKey) {
738
+ const exactCache = sessionId ? readSessionCache(sessionId) : null;
739
+ const workspaceCache = readWorkspaceSessionCache(workspaceKey);
740
+ const cache =
741
+ exactCache && exactCache.status === "ready"
742
+ ? exactCache
743
+ : workspaceCache && workspaceCache.status === "ready"
744
+ ? workspaceCache
745
+ : exactCache || workspaceCache;
746
+ return cache;
589
747
  }
590
748
 
591
749
  // --- Main ---
@@ -688,6 +846,21 @@ The SessionStart hook could not reach the Meko MCP server to create a conversati
688
846
  When Meko MCP tools are available, ALWAYS use them instead of local file-based memory. If a Meko tool call fails, fall back to local storage and tell the user.`;
689
847
  }
690
848
 
849
+ function buildBootstrapPendingContext(agentId) {
850
+ const resolvedAgentId = agentId || COMMON_BUCKET_AGENT_ID;
851
+ return `## Meko Bootstrap Pending
852
+
853
+ The Cursor SessionStart hook has started Meko bootstrap work in the background, but the local Meko session cache is not ready yet. Do not guess a Meko conversation_id.
854
+
855
+ ### What you should do
856
+
857
+ - **agent_id**: use "${resolvedAgentId}" for project-scoped reads/writes if a Meko MCP call is necessary before bootstrap completes
858
+ - **conversation_id**: use nil UUID "00000000-0000-0000-0000-000000000000" only as a placeholder until the cache provides the real conversation_id
859
+ - **scope**: Only "read", "write", or "admin"
860
+
861
+ If the user explicitly needs Meko-backed continuity before proceeding, ask them to retry the prompt after bootstrap finishes.`;
862
+ }
863
+
691
864
  /**
692
865
  * Race a promise against a timeout; on timeout resolve to the fallback value.
693
866
  * Memory preload is best-effort — if it stalls, the SessionStart hook must
@@ -727,17 +900,51 @@ function withTimeout(promise, ms, fallback, { onTimeout } = {}) {
727
900
  });
728
901
  }
729
902
 
903
+ function handleSessionPending(hookInput) {
904
+ const transcriptPath =
905
+ hookInput.transcript_path ||
906
+ (hookInput.hookSpecificInput || {}).transcript_path;
907
+ const sessionId = extractSessionId(hookInput, transcriptPath);
908
+ const agentId = resolveSessionAgentId(transcriptPath, hookInput);
909
+ const workspaceKey = workspaceKeyFromHookInput(hookInput, transcriptPath);
910
+ if (sessionId) {
911
+ writeSessionCache(sessionId, {
912
+ status: "pending",
913
+ agent_id: agentId,
914
+ workspace_key: workspaceKey,
915
+ additional_context: buildBootstrapPendingContext(agentId),
916
+ });
917
+ }
918
+ process.stdout.write(nativeOutput(""));
919
+ }
920
+
921
+ function handleBeforeSubmitPrompt(hookInput) {
922
+ const transcriptPath =
923
+ hookInput.transcript_path ||
924
+ (hookInput.hookSpecificInput || {}).transcript_path;
925
+ const sessionId = extractSessionId(hookInput, transcriptPath);
926
+ const workspaceKey = workspaceKeyFromHookInput(hookInput, transcriptPath);
927
+ const cache = selectSessionCache(sessionId, workspaceKey);
928
+ if (cache && cache.status === "pending") {
929
+ process.stdout.write(beforeSubmitOutput(
930
+ false,
931
+ "Meko is still creating this session's conversation. Please retry in a moment so the agent starts with Meko context.",
932
+ ));
933
+ return;
934
+ }
935
+ process.stdout.write(beforeSubmitOutput(true));
936
+ }
937
+
730
938
  async function handleSessionStart(hookInput) {
731
939
  const transcriptPath =
732
940
  hookInput.transcript_path ||
733
941
  (hookInput.hookSpecificInput || {}).transcript_path;
734
942
 
735
- const sessionId = transcriptPath
736
- ? path.basename(transcriptPath, ".jsonl")
737
- : null;
943
+ const sessionId = extractSessionId(hookInput, transcriptPath);
738
944
 
739
945
  const source = hookInput.source || "";
740
- const derivedAgentId = resolveSessionAgentId(transcriptPath);
946
+ const derivedAgentId = resolveSessionAgentId(transcriptPath, hookInput);
947
+ const workspaceKey = workspaceKeyFromHookInput(hookInput, transcriptPath);
741
948
 
742
949
  // Check if watermark already exists (same session ID re-fires SessionStart, e.g. /clear, /compact)
743
950
  if (sessionId) {
@@ -753,16 +960,20 @@ async function handleSessionStart(hookInput) {
753
960
  const cached = (existing.agent_id || "").trim();
754
961
  const resumedAgentId = cached && cached !== "agent" ? cached : derivedAgentId;
755
962
  // Skip preload on resume (we don't know what's already in context).
756
- process.stdout.write(
757
- hookOutput(
758
- buildSessionStartContext(
759
- existing.conversation_id,
760
- sessionId,
761
- resumedAgentId,
762
- null,
763
- ),
764
- ),
963
+ const context = buildSessionStartContext(
964
+ existing.conversation_id,
965
+ sessionId,
966
+ resumedAgentId,
967
+ null,
765
968
  );
969
+ writeSessionCache(sessionId, {
970
+ status: "ready",
971
+ conversation_id: existing.conversation_id,
972
+ agent_id: resumedAgentId,
973
+ workspace_key: workspaceKey,
974
+ additional_context: context,
975
+ });
976
+ process.stdout.write(hookOutput(context));
766
977
  return;
767
978
  }
768
979
  }
@@ -788,17 +999,21 @@ async function handleSessionStart(hookInput) {
788
999
  priorAgentId && priorAgentId !== "agent" ? priorAgentId : agentId;
789
1000
  writeWatermark(wmPath, prior.conversation_id, currentLineCount, resumedAgentId);
790
1001
  process.stderr.write(`[meko-capture] Session ${sessionId}: resuming conversation ${prior.conversation_id} from ${prior.sessionId}, skipping ${currentLineCount} copied lines\n`);
791
- process.stdout.write(
792
- hookOutput(
793
- buildSessionStartContext(
794
- prior.conversation_id,
795
- sessionId,
796
- resumedAgentId,
797
- null,
798
- { resumed: true },
799
- ),
800
- ),
1002
+ const context = buildSessionStartContext(
1003
+ prior.conversation_id,
1004
+ sessionId,
1005
+ resumedAgentId,
1006
+ null,
1007
+ { resumed: true },
801
1008
  );
1009
+ writeSessionCache(sessionId, {
1010
+ status: "ready",
1011
+ conversation_id: prior.conversation_id,
1012
+ agent_id: resumedAgentId,
1013
+ workspace_key: workspaceKey,
1014
+ additional_context: context,
1015
+ });
1016
+ process.stdout.write(hookOutput(context));
802
1017
  return;
803
1018
  }
804
1019
  process.stderr.write(`[meko-capture] Session ${sessionId}: source=resume but no prior project watermark found; creating new conversation\n`);
@@ -813,13 +1028,33 @@ async function handleSessionStart(hookInput) {
813
1028
  convId = await createConversation(sessionId || "unknown", agentId, transcriptMetadata);
814
1029
  } catch (err) {
815
1030
  process.stderr.write(`[meko-capture] SessionStart: MCP unavailable (${err.message}). Falling back to agent-driven setup.\n`);
816
- process.stdout.write(hookOutput(buildSessionStartFallbackContext(agentId)));
1031
+ const context = buildSessionStartFallbackContext(agentId);
1032
+ if (sessionId) {
1033
+ writeSessionCache(sessionId, {
1034
+ status: "error",
1035
+ agent_id: agentId,
1036
+ workspace_key: workspaceKey,
1037
+ error: err && err.message ? String(err.message).slice(0, 240) : "MCP unavailable",
1038
+ additional_context: context,
1039
+ });
1040
+ }
1041
+ process.stdout.write(hookOutput(context));
817
1042
  return;
818
1043
  }
819
1044
 
820
1045
  if (!convId) {
821
1046
  process.stderr.write("[meko-capture] SessionStart: conversation_create returned no ID.\n");
822
- process.stdout.write(hookOutput(buildSessionStartFallbackContext(agentId)));
1047
+ const context = buildSessionStartFallbackContext(agentId);
1048
+ if (sessionId) {
1049
+ writeSessionCache(sessionId, {
1050
+ status: "error",
1051
+ agent_id: agentId,
1052
+ workspace_key: workspaceKey,
1053
+ error: "conversation_create returned no ID",
1054
+ additional_context: context,
1055
+ });
1056
+ }
1057
+ process.stdout.write(hookOutput(context));
823
1058
  return;
824
1059
  }
825
1060
 
@@ -845,19 +1080,25 @@ async function handleSessionStart(hookInput) {
845
1080
  );
846
1081
  }
847
1082
 
848
- process.stdout.write(
849
- hookOutput(
850
- buildSessionStartContext(convId, sessionId, agentId, memories),
851
- ),
852
- );
1083
+ const context = buildSessionStartContext(convId, sessionId, agentId, memories);
1084
+ if (sessionId) {
1085
+ writeSessionCache(sessionId, {
1086
+ status: "ready",
1087
+ conversation_id: convId,
1088
+ agent_id: agentId,
1089
+ workspace_key: workspaceKey,
1090
+ additional_context: context,
1091
+ });
1092
+ }
1093
+ process.stdout.write(hookOutput(context));
853
1094
  }
854
1095
 
855
1096
  // --- Main ---
856
1097
 
857
1098
  async function main() {
858
1099
  const hookType = process.argv[2];
859
- if (!hookType || !["session-start", "pre-compact", "session-end", "checkpoint"].includes(hookType)) {
860
- process.stderr.write("Usage: capture.js <session-start|pre-compact|session-end|checkpoint>\n");
1100
+ if (!hookType || !["session-start", "session-pending", "before-submit-prompt", "pre-compact", "session-end", "checkpoint"].includes(hookType)) {
1101
+ process.stderr.write("Usage: capture.js <session-start|session-pending|before-submit-prompt|pre-compact|session-end|checkpoint>\n");
861
1102
  process.exit(1);
862
1103
  }
863
1104
 
@@ -878,6 +1119,16 @@ async function main() {
878
1119
  return;
879
1120
  }
880
1121
 
1122
+ if (hookType === "session-pending") {
1123
+ handleSessionPending(hookInput);
1124
+ return;
1125
+ }
1126
+
1127
+ if (hookType === "before-submit-prompt") {
1128
+ handleBeforeSubmitPrompt(hookInput);
1129
+ return;
1130
+ }
1131
+
881
1132
  // PreCompact / SessionEnd: capture transcript exchanges
882
1133
  const transcriptPath =
883
1134
  hookInput.transcript_path ||
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # Package meko-mcp-tools-desktop as a .skill file for Claude Desktop distribution.
3
+ # Usage: bash scripts/package-desktop-skill.sh
4
+ # Output: meko-mcp-tools-desktop.skill (ZIP archive)
5
+
6
+ set -euo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+ SKILLS_ROOT="$SCRIPT_DIR/../skills"
10
+ SKILL_NAME="meko-mcp-tools-desktop"
11
+ OUTPUT="$SCRIPT_DIR/../$SKILL_NAME.skill"
12
+
13
+ if [ ! -d "$SKILLS_ROOT/$SKILL_NAME" ]; then
14
+ echo "Error: Skill directory not found at $SKILLS_ROOT/$SKILL_NAME" >&2
15
+ exit 1
16
+ fi
17
+
18
+ if [ ! -f "$SKILLS_ROOT/$SKILL_NAME/SKILL.md" ]; then
19
+ echo "Error: SKILL.md not found in $SKILLS_ROOT/$SKILL_NAME/" >&2
20
+ exit 1
21
+ fi
22
+
23
+ # Remove previous build if it exists
24
+ rm -f "$OUTPUT"
25
+
26
+ # Package skill + references (exclude OS artifacts and desktop README)
27
+ cd "$SKILLS_ROOT"
28
+ zip -r "$OUTPUT" \
29
+ "$SKILL_NAME/SKILL.md" \
30
+ "$SKILL_NAME/references/" \
31
+ -x "*.DS_Store" "*__pycache__*"
32
+
33
+ echo "Created $OUTPUT ($(du -h "$OUTPUT" | cut -f1))"
34
+ echo ""
35
+ echo "Contents:"
36
+ unzip -l "$OUTPUT" | grep -E "^\s+[0-9]" | grep -v "files$"
@@ -15,7 +15,7 @@ Before starting a session, verify that the Meko MCP tools are working:
15
15
 
16
16
  1. `memory_search(scope="read", query="test", agent_id="<your-session-agent-id>", conversation_id="<session-conversation-id>")` — Confirms memory subsystem is up. Use the `agent_id` and `conversation_id` from the SessionStart `additionalContext`. May fail with "connection already closed" — see `tools-troubleshooting.md`.
17
17
  2. `datapack_list(scope="read")` — Confirms Meko API connectivity
18
- 3. `db_summarize_database(scope="read")` — *Optional, paid-tier only.* On free-tier datapacks this returns `{"error": "not_available"}`. The old "use this for `agent_id` discovery" protocol is retired — `agent_id` is a constant.
18
+ 3. `db_summarize_database(scope="read")` — *Optional, paid-tier only.* On free-tier datapacks this returns `{"error": "not_available"}`. The old "use this for `agent_id` discovery" protocol is retired — use the session-injected `agent_id` for project-scoped calls, or `meko_agent` for the common bucket.
19
19
 
20
20
  ## Decision tree: which tool do I need?
21
21