@yugabytedb/meko-mcp 2.2.0 → 2.5.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 (67) hide show
  1. package/README.md +168 -61
  2. package/assets/hooks-handlers/lib/capture-cleanup-cli.js +2134 -0
  3. package/assets/hooks-handlers/lib/capture-notices.js +224 -14
  4. package/assets/hooks-handlers/lib/capture-state.js +206 -1
  5. package/assets/hooks-handlers/lib/capture-status-cli.js +6 -2
  6. package/assets/hooks-handlers/lib/capture.js +1364 -139
  7. package/assets/hooks-handlers/lib/checkpoint-timer.js +445 -18
  8. package/assets/hooks-handlers/lib/journal-bridge.js +218 -0
  9. package/assets/hooks-handlers/lib/kiro-handler.js +26 -4
  10. package/assets/hooks-handlers/lib/opencode-handler.js +643 -0
  11. package/assets/hooks-handlers/lib/opencode-normalizer.js +145 -0
  12. package/assets/hooks-handlers/lib/terminal-handoff.js +10 -1
  13. package/assets/opencode-plugin/meko.js +213 -0
  14. package/assets/scripts/meko-statusline.sh +102 -19
  15. package/assets/skills/meko-mcp-tools/SKILL.md +59 -16
  16. package/assets/skills/meko-mcp-tools/references/_sections.md +2 -2
  17. package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +15 -7
  18. package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +275 -19
  19. package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +7 -4
  20. package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +39 -11
  21. package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +10 -6
  22. package/assets/skills/meko-mcp-tools/references/tools-overview.md +44 -29
  23. package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +79 -14
  24. package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +47 -1
  25. package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +6 -39
  26. package/assets/skills/meko-mcp-tools-desktop/SKILL.md +57 -21
  27. package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +4 -4
  28. package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +1 -1
  29. package/assets/skills/meko-mcp-tools-desktop/references/tools-agent-id-conventions.md +8 -6
  30. package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +284 -38
  31. package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +32 -10
  32. package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +16 -12
  33. package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +46 -31
  34. package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +79 -14
  35. package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +69 -1
  36. package/assets/skills/meko-select-datapack-desktop/SKILL.md +93 -14
  37. package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +44 -12
  38. package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +14 -10
  39. package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +17 -5
  40. package/bin/create-meko-setup.mjs +186 -30
  41. package/lib/autofix.mjs +100 -0
  42. package/lib/browser-login.mjs +495 -0
  43. package/lib/capture-cleanup.mjs +63 -0
  44. package/lib/clients/claude-code.mjs +1 -0
  45. package/lib/clients/codex.mjs +80 -35
  46. package/lib/clients/common.mjs +7 -0
  47. package/lib/clients/index.mjs +2 -0
  48. package/lib/clients/kiro.mjs +5 -5
  49. package/lib/clients/opencode.mjs +537 -0
  50. package/lib/config.mjs +68 -1
  51. package/lib/installer.mjs +18 -2
  52. package/lib/migrate/adapters/claude-code.mjs +11 -2
  53. package/lib/migrate/adapters/codex.mjs +9 -2
  54. package/lib/migrate/adapters/memory-md.mjs +27 -8
  55. package/lib/migrate/adapters/opencode.mjs +148 -0
  56. package/lib/migrate/claude-parser.mjs +153 -9
  57. package/lib/migrate/datapack.mjs +254 -16
  58. package/lib/migrate/id.mjs +195 -13
  59. package/lib/migrate/mcp-client.mjs +59 -1
  60. package/lib/migrate/opencode-normalizer.mjs +132 -0
  61. package/lib/migrate/orchestrator.mjs +294 -78
  62. package/lib/migrate/progress.mjs +239 -5
  63. package/lib/migrate/registry.mjs +72 -0
  64. package/lib/migrate.mjs +97 -9
  65. package/lib/validate.mjs +111 -22
  66. package/package.json +5 -5
  67. package/scripts/bundle-assets.mjs +4 -0
@@ -1,22 +1,24 @@
1
1
  # Complete Tool Catalog and Decision Tree
2
2
 
3
- **23 Meko data tools covered by this skill.** These are the tools available in production, grouped as: Memory (8), Conversation (6), Knowledge Base (1), Datapack (5), Artifacts (2), Observability (1). Combined Meko + AMP endpoints may expose additional infrastructure tools; those are outside this skill's scope.
3
+ **28 Meko data tools covered by this skill.** These are the tools available in production, grouped as: Retrieval (1), Memory (8), Conversation (6), Knowledge Base (4), Datapack (6), Artifacts (2), Observability (1). Combined Meko + AMP endpoints may expose additional infrastructure tools; those are outside this skill's scope.
4
4
 
5
5
  ## Quick health check before using tools
6
6
 
7
7
  Before starting a session, verify that the Meko MCP tools are working:
8
8
 
9
- 1. `memory_search(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`.
10
- 2. `datapack_list(conversation_id="<this conversation's id>")` — Confirms Meko API connectivity. Returns the datapacks your token has access to.
9
+ 1. `memory_search(query="test", agent_id="claude_desktop", conversation_id="<id from conversation_create>")` — Confirms memory subsystem is up. May fail with "connection already closed" — see `tools-troubleshooting.md`.
10
+ 2. `datapack_search(conversation_id="<this conversation's id>", limit=1)`: confirms Meko API connectivity and returns one datapack your token has access to. On a server without `datapack_search`, call `datapack_list(conversation_id="<this conversation's id>")` instead.
11
11
 
12
12
  ## Decision tree: which tool do I need?
13
13
 
14
14
  ```
15
15
  User wants to...
16
+ ├── Ask "what do you know about X?" --------> context_search (read; memory + KB + past conversations in one call)
16
17
  ├── Search personal context? ---------------> memory_search (read)
17
18
  ├── Search shared datapack knowledge? ------> knowledgebase_search (read)
18
- ├── Add documents to a knowledge base? -----> point user at Meko UI: Datapack → Actions → Add Knowledge
19
- │ (no MCP tool — Cloud uses UI ingestion only)
19
+ ├── Add a file to a knowledge base? --------> knowledgebase_upload_document (write)
20
+ │ └── Check its indexing progress? -------> knowledgebase_document_status (read)
21
+ ├── Delete ONE knowledge-base file? --------> knowledgebase_delete_document (destructive — confirm with the user first)
20
22
  ├── Store or recall information?
21
23
  │ ├── Store a fact/preference/entity? -----------> memory_add (write)
22
24
  │ ├── Store a full conversation (multi-turn)? ---> conversation_create + conversation_add_message (write)
@@ -27,50 +29,63 @@ User wants to...
27
29
  │ ├── Upload a generated file (report, CSV, PDF)? -> artifact_put (write)
28
30
  │ └── Retrieve a previously uploaded file? -------> artifact_get (read)
29
31
  └── Manage datapacks?
32
+ ├── Find a datapack id by name? -------> datapack_search(query=...) (read)
30
33
  └── CRUD datapack? --------------------> datapack_create/list/describe/update/delete
31
34
  ```
32
35
 
33
- Agent / knowledge-base lifecycle is managed **outside** the MCP surface — typically in the Meko control-plane UI — and is not exposed as tools here.
36
+ Agent lifecycle and KB **index / source lifecycle** (creating indexes, adding sources, re-indexing) are managed outside the MCP surface — typically in the Meko control-plane UI. Single KB files are handled here: adding one (`knowledgebase_upload_document`, then `knowledgebase_document_status`) and deleting one (`knowledgebase_delete_document`).
34
37
 
35
- ## Knowledge Base Tools (1)
38
+ ## Retrieval Tools (1)
36
39
 
37
40
  | Tool | Purpose |
38
41
  |------|---------|
39
- | `knowledgebase_search(query, agent_id, conversation_id, datapack_id, limit=10)` | Semantic search across KB chunks. `datapack_id` is REQUIRED (no default). |
42
+ | `context_search(query, conversation_id, agent_id=None, datapack_id=None, limit=10, force_source="auto")` | Default first call for open-ended recall. Queries memory, the knowledge base, and the conversation-history cache in parallel and returns each source in its own bucket: `{"results": {"memory": [...], "knowledge_base": [...], "conversation": {"hits": [...], "thread": [...]}}, "token_savings": {...}}`. A bucket is `null` only when that source wasn't queried: `force_source` excluded it, or the server couldn't build a query vector for it. An empty bucket is ambiguous: either that source found nothing relevant, or it failed. `context_search` turns a failed source into an empty list and records the error only in the trace. Before you tell the user nothing is stored about X, confirm with `memory_search` or `knowledgebase_search`, which return errors. `force_source` is `"auto"` (all three), `"memory"`, `"knowledge_base"`, or `"conversation"`; any other value returns `invalid_force_source`. `limit` bounds the memory and KB buckets only; the conversation bucket returns up to 10 hits plus a thread of whole turns. The conversation bucket is datapack-wide. It returns embedded turns from every user and agent on the datapack, and each hit carries its author's `user_id`. Don't present a hit as this user's own history. |
43
+
44
+ ## Knowledge Base Tools (4)
45
+
46
+ | Tool | Purpose |
47
+ |------|---------|
48
+ | `knowledgebase_search(query, conversation_id, datapack_id, agent_id=None, limit=10)` | Hybrid (semantic + keyword) search across KB chunks and promoted memories. `datapack_id` is REQUIRED (no default). Each hit has `id`, `chunk_text`, `document_id`, `metadata_filters`, `distance`, and `match_type` (`semantic`, `keyword`, or `hybrid`). |
49
+ | `knowledgebase_delete_document(document_id, conversation_id, datapack_id="")` | Permanently delete ONE KB file (chunks + metadata; stored-file delete is best-effort — check `s3_deleted`). Irreversible: confirm with the user first. `document_id` comes from `knowledgebase_search` hits (no filename in hits — echo the id + chunk text). Owner/maintainer/uploader only (status 403 otherwise); status 409 while still indexing — wait and retry. |
50
+ | `knowledgebase_upload_document(filename, size, content_hash, conversation_id, content_head_base64, datapack_id=None, agent_id=None)` | Requests a presigned S3 URL for a file (does not upload bytes itself — the caller POSTs to the returned `url`/`fields`). Returns `{datapack_id, url, fields, key, already_uploaded}` (fresh upload) or `{datapack_id, url: "", key, already_uploaded: true, existing_filename}` (dedup). Accepted extensions: pdf/txt/text/md/json/csv/xml/html/htm; `content_head_base64` (base64 of the file's first 3072 bytes, or the whole file if shorter) is **required** and is always sniffed against the extension before requesting the URL — a missing/blank value is rejected as `content_head_base64_required`, this check cannot be skipped. Idempotent by content hash. |
51
+ | `knowledgebase_document_status(document_key, conversation_id, datapack_id=None, agent_id=None)` | Poll indexing progress for a file uploaded via `knowledgebase_upload_document`, using its returned `key` (passed here as `document_key`). `state` is one of `awaiting_upload`/`indexing` (non-terminal) or `completed`/`failed`/`abandoned`/`oversized` (terminal). |
40
52
 
41
53
  ## Memory Tools (8)
42
54
 
43
55
  | Tool | Purpose |
44
56
  |------|---------|
45
- | `memory_add(text, agent_id, conversation_id, user_id=None, app_id=None, run_id=None, metadata=None, messages=None, datapack_id=None)` | Store fact/preference/entity as long-term memory |
46
- | `memory_search(query, agent_id, conversation_id="", user_id=None, limit=10, datapack_id=None)` | Hybrid search across memories (semantic + keyword + entity boost). `conversation_id` optional (omit or pass `""` for cross-conversation discovery). |
47
- | `memory_get_by_id(memory_id, agent_id, conversation_id, datapack_id=None)` | Direct pgvector row lookup by UUID. Preferred over `memory_search` for exact-id verification. |
48
- | `memory_get_all(agent_id, conversation_id, user_id=None, app_id=None, run_id=None, datapack_id=None)` | List all memories for agent |
49
- | `memory_update(memory_id, text, agent_id, conversation_id, datapack_id=None)` | Overwrite memory text |
50
- | `memory_delete_by_id(memory_id, agent_id, conversation_id, datapack_id=None)` | Delete a single memory |
51
- | `memory_delete_all(agent_id, conversation_id, user_id=None, app_id=None, run_id=None, datapack_id=None)` | Delete all memories for agent (destructive) |
52
- | `memory_promote(conversation_id, memory_ids, agent_id=None, datapack_id=None)` | Promote private memories into the datapack's shared knowledge base (moves them out of mem0, then evicts from mem0 — one-way). Owners/maintainers only; viewers/contributors get 403.
57
+ | `memory_add(text, conversation_id, agent_id=None, run_id=None, metadata=None, messages=None, datapack_id=None)` | Store fact/preference/entity as long-term memory |
58
+ | `memory_search(query, conversation_id, agent_id=None, limit=10, datapack_id=None, run_id=None)` | Hybrid search across memories (semantic + keyword + entity boost). Drops rows below a server-configured similarity floor (default 0.2), so a weak query can return an empty list. `conversation_id` is required but only nests the trace. It must be a 32-hex id: `""` currently fails (MEKO-707), so pass a freshly generated one if you have no conversation yet. `run_id` filters to one conversation's memories. |
59
+ | `memory_get_by_id(memory_id, conversation_id, agent_id=None, datapack_id=None)` | Direct pgvector row lookup by UUID. Preferred over `memory_search` for exact-id verification. Returns `not_found` for a missing id and for an id that belongs to another account. |
60
+ | `memory_get_all(conversation_id, agent_id=None, run_id=None, datapack_id=None, promoted=False)` | Recent window (about 20 rows) of the user's memories across every agent, plus the true `total`. Not a complete listing. `promoted=True` returns the recent window of promoted memories instead. `run_id` is ignored. |
61
+ | `memory_update(memory_id, text, conversation_id, agent_id=None, datapack_id=None)` | Overwrite memory text. Returns `memory_is_promoted` for a promoted memory (edit those in the Cloud UI Learnings tab). |
62
+ | `memory_delete_by_id(memory_id, conversation_id, agent_id=None, datapack_id=None)` | Delete a single memory. Returns `memory_is_promoted` for a promoted memory. |
63
+ | `memory_delete_all(conversation_id, agent_id=None, run_id=None, datapack_id=None)` | Delete all memories in the **passed `agent_id`'s bucket** (destructive). Unlike reads, this is agent-scoped: omitting `agent_id` deletes only the `meko_agent` bucket. `run_id` narrows it to one conversation; a malformed value returns `invalid_run_id`. |
64
+ | `memory_promote(conversation_id, memory_ids, agent_id=None, datapack_id=None)` | Promote private memories into the datapack's shared knowledge base (moves them out of mem0, then evicts from mem0 — one-way). Owners/maintainers only; viewers/contributors get 403. |
53
65
 
54
66
  ## Conversation Tools (6)
55
67
 
56
68
  | Tool | Purpose |
57
69
  |------|---------|
58
- | `conversation_create(agent_id, user_id=None, app_id=None, run_id=None, title=None, metadata=None, session_id="")` | Create conversation container (Langfuse session) |
59
- | `conversation_add_message(conversation_id, agent_id, input, output=None, reasoning=None, metadata=None, seed=None, trace_id="")` | Add a message turn (Langfuse trace) |
60
- | `conversation_get(conversation_id, agent_id, include_messages=False, limit=100, offset=0)` | Retrieve conversation, optionally with messages |
61
- | `conversation_list(agent_id=None, limit=20, offset=0, conversation_id="", datapack_id=None)` | List conversations for agent within a datapack. `datapack_id` (UUID) is optional — defaults to the pinned/tenant datapack; use `datapack_list` to look it up. `conversation_id` optional (omit for browse). |
62
- | `conversation_update(conversation_id, agent_id, title=None, metadata=None)` | Update title or metadata |
63
- | `conversation_delete(conversation_id, agent_id)` | Delete entire conversation (destructive) |
70
+ | `conversation_create(agent_id=None, run_id=None, title=None, metadata=None, session_id="", datapack_id=None)` | Create conversation container (Langfuse session) |
71
+ | `conversation_add_message(conversation_id, input, output, agent_id=None, reasoning=None, metadata=None, seed=None, datapack_id=None, plan=None)` | Add a message turn (Langfuse trace). `output` is required. The response may include `extracted_memories` and, when the turn wasn't embedded for conversation search, `conversation_search_skipped` (`quota_exceeded` or `datapack_opted_out`). |
72
+ | `conversation_get(conversation_id, agent_id=None, include_messages=False, limit=100, offset=0, datapack_id=None)` | Retrieve conversation, optionally with messages |
73
+ | `conversation_list(agent_id=None, limit=20, offset=0, conversation_id="", datapack_id=None)` | List the user's conversations within a datapack (`agent_id` does not filter — trace attribution only). `datapack_id` (UUID) is optional — defaults to the pinned/tenant datapack; use `datapack_search(query=...)` to look it up. `conversation_id` optional (omit for browse). |
74
+ | `conversation_update(conversation_id, agent_id=None, title=None, metadata=None, datapack_id=None)` | Update title or metadata |
75
+ | `conversation_delete(conversation_id, agent_id=None, datapack_id=None)` | Delete entire conversation (destructive) |
64
76
 
65
- ## Datapack Management Tools (5)
77
+ ## Datapack Management Tools (6)
66
78
 
67
79
  | Tool | Purpose |
68
80
  |------|---------|
69
- | `datapack_create(name)` | Create new datapack |
70
- | `datapack_list(conversation_id, datapack_id=None)` | List all datapacks. `conversation_id` is required for trace nesting; omit `datapack_id` to use the endpoint default. |
71
- | `datapack_describe(datapack_id, include_status=False)` | Describe datapack by UUID |
72
- | `datapack_update(datapack_id, name=None, description=None)` | Rename a datapack and/or update its description. Pass at least one of `name` / `description`. |
73
- | `datapack_delete(datapack_id)` | Delete datapack by UUID (irreversible) |
81
+ | `datapack_create(name, conversation_id)` | Create new datapack |
82
+ | `datapack_search(conversation_id, query="", name="", limit=20, cursor="", grant="", sort="created", include_total=False, datapack_id=None)` | Find datapacks and page through them. Returns `{datapacks, has_more, next_cursor, total}`. Use `query` for part of a name, `name` for an exact name, and `cursor` (the previous page's `next_cursor`) for the next page. `limit` is 1-100. `grant` is `owner` or `shared`; `sort` is `created` (newest first) or `name`. Pass `include_total=True` only when you must show "N of M". Rows carry no connection fields and no counts. Servers older than this tool don't have it; use `datapack_list` there. |
83
+ | `datapack_list(conversation_id, datapack_id=None, query="", limit=0)` | Return datapacks as one array. Fine for a small account. `query` filters by part of a name; more than 100 matches return a `too_many_datapacks` error. `limit` returns the first matches (at most 100) without saying whether more exist. With neither, an account that can see more than 1000 datapacks (the server default limit) gets a `too_many_datapacks` error; use `datapack_search` then. `conversation_id` is required for trace nesting. |
84
+ | `datapack_describe(conversation_id, datapack_id="", include_status=False)` | Describe a datapack by UUID (default datapack if omitted). Returns `memory_count`, `collective_memory_count` (promoted), `learnings_count` (awaiting a promotion decision), `knowledge_base_file_count`, `knowledge_chunk_count`, and `shared_knowledge_count`. `knowledge_base_file_count` and `shared_knowledge_count` are `null` when the status endpoint is unreachable; `knowledge_count` is a deprecated alias of `knowledge_chunk_count`. |
85
+ | `datapack_update(datapack_id, conversation_id, name=None, description=None, conversation_search_opt_out=None)` | Rename a datapack, update its description, and/or opt out of (or back into) conversation-search caching. Pass at least one field. Opt-out is owner/maintainer only. |
86
+ | `datapack_delete(datapack_id, conversation_id)` | Delete datapack by UUID (irreversible) |
87
+
88
+ All six datapack tools reject an empty `conversation_id` with `conversation_id_required`.
74
89
 
75
90
  ## Artifact Tools (2)
76
91
 
@@ -98,10 +113,10 @@ Some things the Meko platform can do are not wired into the MCP tool surface tod
98
113
  | Langfuse project-key generation | `yugabyte/meko` API server | REST: `POST /datapacks/:datapack_id/langfuse/project-keys` |
99
114
  | Account / billing / tier management | Meko UI only | Not available via MCP or public REST |
100
115
 
101
- The MCP-exposed `datapack_create` / `datapack_list` / `datapack_describe` / `datapack_update` / `datapack_delete` tools above are a deliberate subset — the common CRUD that an agent reasonably needs mid-conversation. Anything involving agent or KB lifecycle is control-plane territory.
116
+ The MCP-exposed `datapack_create` / `datapack_search` / `datapack_list` / `datapack_describe` / `datapack_update` / `datapack_delete` tools above are a deliberate subset — the common CRUD that an agent reasonably needs mid-conversation. Anything involving agent or KB lifecycle is control-plane territory.
102
117
 
103
- For knowledge-base content on Cloud, the canonical path is **UI upload**: Datapack → Actions → **Add Knowledge** (PDF/TXT/MD/JSON/MP4, 5MB each, 10/batch). `knowledgebase_search` queries the resulting index from MCP.
118
+ For a file already on disk or generated mid-conversation, requesting a URL with `knowledgebase_upload_document` and POSTing to it gets it into the KB without leaving MCP for a human's help. The UI upload path (Datapack → Actions → **Add Knowledge**, PDF/TXT/MD/JSON/CSV/XML/HTML, 5MB Free / 50MB Standard / 60MB Pro+ each, 10/batch) remains the way for a human to add files directly. `knowledgebase_search` queries the resulting index either way.
104
119
 
105
120
  ## Common Parameter Patterns
106
121
 
107
- Memory and KB tools accept optional `datapack_id` to target a specific datapack. Memory and conversation tools require `agent_id` for namespace isolation (see `tools-agent-id-conventions.md`).
122
+ Memory and KB tools accept optional `datapack_id` to target a specific datapack. A `datapack_id` your account can't access returns `datapack_access_denied`. `agent_id` attributes writes. Conversations are agent-owned: `conversation_get`, `conversation_add_message`, `conversation_update`, and `conversation_delete` return `agent_id_mismatch` for any value other than the creating agent's. `agent_id` does **not** filter memory reads (`memory_search`/`memory_get_all`) or `conversation_list`, which return all your agents' rows for the user. `memory_delete_all` is the exception on the memory side: it deletes only the passed `agent_id`'s bucket. See `tools-agent-id-conventions.md`.
@@ -1,16 +1,21 @@
1
1
  # Knowledge Base / RAG workflow
2
2
 
3
- `knowledgebase_search` is the only KB tool exposed via MCP. Index
4
- creation and ingestion happen out of band — through the **Meko UI**
5
- (`cloud.mekodata.ai`).
3
+ MCP exposes four KB tools: `knowledgebase_search` (retrieval),
4
+ `knowledgebase_upload_document` and `knowledgebase_document_status` (add a
5
+ file and track its indexing), and `knowledgebase_delete_document` (remove ONE
6
+ uploaded file). Creating, listing or renaming a knowledge base itself still
7
+ happens out of band — through the **Meko UI** (`cloud.mekodata.ai`).
6
8
 
7
9
  ## Decision table
8
10
 
9
11
  | Situation | Use this | Why |
10
12
  |---|---|---|
11
- | Search an existing KB | `knowledgebase_search` | The one KB tool exposed via MCP |
12
- | Add documents to a KB | **Meko UI** — Datapack → Actions → **Add Knowledge** | File-upload dialog (PDF/TXT/MD/JSON/MP4, 5 MB each, 10/batch). No MCP equivalent |
13
- | Anything else (list / delete / rename / re-index) | **UI** | No MCP tools for KB lifecycle |
13
+ | Search an existing KB | `knowledgebase_search` | Retrieval over the indexed chunks |
14
+ | Add a file to a KB | `knowledgebase_upload_document` + one direct S3 POST | Requests a presigned URL, then you POST the file to it; no need to leave MCP |
15
+ | Track a file's indexing progress | `knowledgebase_document_status` | Poll with the `key` from the upload response, passed here as `document_key`, until `state` is terminal |
16
+ | Add documents to a KB from the Cloud UI instead | **Meko UI** — Datapack → Actions → **Add Knowledge** | File-upload dialog (PDF/TXT/MD/JSON/CSV/XML/HTML, 5 MB Free / 50 MB Standard / 60 MB Pro+ each, 10/batch). Same index either way — this is the human-facing path, not required when an agent can upload directly |
17
+ | Delete ONE uploaded file | `knowledgebase_delete_document` | Destructive; `document_id` from search hits; confirm with the user first |
18
+ | Anything else (list / rename / re-index / source lifecycle) | **UI** | No MCP tools for the rest of KB lifecycle |
14
19
 
15
20
  ## knowledgebase_search — the one you'll actually call
16
21
 
@@ -23,7 +28,7 @@ knowledgebase_search(
23
28
  limit=10)
24
29
  ```
25
30
 
26
- Required: `query`, `agent_id`, `conversation_id`, `datapack_id`.
31
+ Required: `query`, `conversation_id`, `datapack_id`.
27
32
  Unlike the memory tools, `datapack_id` has no default here — you must
28
33
  pass it explicitly. `agent_id` is **ignored for filtering** on this
29
34
  tool (KB results are team-shared on the datapack), but you still pass
@@ -37,10 +42,12 @@ Populated KB:
37
42
  {
38
43
  "results": [
39
44
  {
40
- "content": "<chunk text>",
41
- "similarity": 0.87,
42
- "source_uri": "<original source URI>",
43
- "document_name": "<file key>",
45
+ "id": "<chunk id>",
46
+ "chunk_text": "<chunk text>",
47
+ "document_id": "<KB document id; pass to knowledgebase_delete_document>",
48
+ "metadata_filters": {},
49
+ "distance": 0.31,
50
+ "match_type": "semantic" | "keyword" | "hybrid",
44
51
  ...
45
52
  },
46
53
  ...
@@ -49,9 +56,64 @@ Populated KB:
49
56
  }
50
57
  ```
51
58
 
59
+ Lower `distance` is closer. Promoted memories appear as hits whose `metadata_filters.source` is `"memory"`. Hits carry no filename, so show the user the `document_id` and chunk text when they need to identify a file.
60
+
52
61
  Empty or nonexistent KB: `{"results": [], "count": 0}` (no error,
53
62
  just an empty array).
54
63
 
64
+ ## knowledgebase_upload_document — putting a file in
65
+
66
+ This tool never touches the file's bytes itself — it only requests a
67
+ presigned S3 URL. **You POST the file to that URL yourself**, as a
68
+ second step:
69
+
70
+ ```
71
+ knowledgebase_upload_document(
72
+ filename="report.pdf",
73
+ size=42000,
74
+ content_hash="<sha256 hex of the file's bytes>",
75
+ conversation_id="<uuid from conversation_create>",
76
+ agent_id="claude_desktop",
77
+ content_head_base64="<base64 of the file's first ~4KB>")
78
+ ```
79
+
80
+ Build the POST from every key in the response's `fields`, not a hand-picked list:
81
+ a missing signed field such as `x-amz-security-token` makes S3 answer 403. Use
82
+ the Python, curl, or PowerShell snippet under `knowledgebase_upload_document`
83
+ in `tools-cookbook.md`. Confirm with the user first: the file becomes visible to
84
+ every datapack member.
85
+
86
+ Required: `filename`, `size` (the exact byte count — the URL accepts no
87
+ other length), `content_hash`, `conversation_id`, `content_head_base64`. `content_hash` is the SHA-256 hex digest of the
88
+ file's bytes, computed by you (e.g. `sha256sum`) — it becomes part of
89
+ the S3 key, so it also drives the dedup check. A response with
90
+ `already_uploaded: true` means identical content already exists; skip
91
+ the S3 POST entirely, there is nothing left to upload. Otherwise POST
92
+ as above: every key in `fields` as a form field (in order), then the
93
+ bytes last as a field named `"file"`. Meko registers the object once
94
+ it lands, so no separate confirm call is needed after the POST. Accepted extensions:
95
+ pdf, txt, text, md, json, csv, xml, html, htm; `content_head_base64` is
96
+ always sniffed against the extension before requesting the URL,
97
+ rejecting an obvious mismatch immediately instead of waiting for the
98
+ watcher — a missing/blank value is rejected as
99
+ `content_head_base64_required`, this sniff cannot be skipped.
100
+ Ingestion is asynchronous — save the response's
101
+ `key` (as `document_key`) and poll `knowledgebase_document_status`.
102
+
103
+ ## knowledgebase_document_status — tracking it
104
+
105
+ ```
106
+ knowledgebase_document_status(
107
+ document_key="<the upload response's `key`>",
108
+ conversation_id="<uuid from conversation_create>",
109
+ agent_id="claude_desktop")
110
+ ```
111
+
112
+ `state` covers the whole lifecycle: `awaiting_upload` and `indexing`
113
+ are non-terminal (poll again); `completed`, `failed`, `abandoned`, and
114
+ `oversized` are terminal. `NOT_FOUND` means the `document_key` doesn't
115
+ belong to this datapack.
116
+
55
117
  ## UI ingestion — what the user sees
56
118
 
57
119
  From `cloud.mekodata.ai`:
@@ -59,11 +121,14 @@ From `cloud.mekodata.ai`:
59
121
  1. Open a datapack (`/datapacks/<name>`).
60
122
  2. Click **Actions** → **Add Knowledge**.
61
123
  3. Drag-and-drop or browse for files. Supported: PDF, TXT, MD, JSON,
62
- MP4. Per-file limit 5 MB. Up to 10 files per batch.
124
+ CSV, XML, HTML. Per-file limit 5 MB (Free) / 50 MB (Standard) / 60 MB (Pro+). Up to 10 files per batch.
63
125
  4. The UI handles upload, storage, and index build. The user does not
64
126
  pick a bucket or provide credentials.
65
127
  5. Search results from `knowledgebase_search` become available once
66
128
  indexing completes.
67
129
 
68
- If a user asks you "how do I add a document to my knowledge base?",
69
- point them at this flow.
130
+ If a *human* asks you "how do I add a document to my knowledge base?",
131
+ point them at this flow. If *you* (the agent) have the file's bytes —
132
+ already on disk, or generated mid-conversation — call
133
+ `knowledgebase_upload_document` directly instead of describing the UI
134
+ flow to yourself.
@@ -7,7 +7,7 @@ These patterns are extracted from real agent sessions. Follow them to avoid wast
7
7
  1. **Never retry the identical failed call more than once.** If it fails twice with the same error, it's not transient — diagnose the cause.
8
8
  2. **Distinguish transient vs persistent failures.** "Connection already closed" is transient (retry once). "Permission denied" is persistent (stop, don't retry).
9
9
  3. **Don't guess parameters sequentially.** If a tool fails with one parameter format, don't try 4 variations. Check this skill's docs for the correct format first.
10
- 4. **Use your session's `agent_id` consistently on writes.** For Claude Desktop the default is `claude_desktop`; use `meko_agent` when writing cross-project facts. Other clients use `<client>:<repo-basename>` (e.g. `claude_code:meko-mcp-server`). Memory reads and `conversation_list` span all of this user's agents regardless of the value passed; `conversation_get` requires the creating agent's exact value.
10
+ 4. **Use your session's `agent_id` consistently on writes.** For Claude Desktop the default is `claude_desktop`; use `meko_agent` when writing cross-project facts. Other clients use `<client>:<repo-basename>` (e.g. `claude_code:meko-mcp-server`). Memory reads and `conversation_list` span all of this user's agents regardless of the value passed; the conversation calls (`conversation_get`, `conversation_add_message`, `conversation_update`, `conversation_delete`) require the creating agent's exact value.
11
11
 
12
12
  ---
13
13
 
@@ -26,6 +26,74 @@ These patterns are extracted from real agent sessions. Follow them to avoid wast
26
26
 
27
27
  ---
28
28
 
29
+ ## Connection errors: `[Errno 111] Connection refused`
30
+
31
+ **Affects:** any tool, intermittently. Reports show `memory_add`, `conversation_create`, `memory_search`, and `context_search` failing this way while identical calls succeed minutes later.
32
+
33
+ **What it means:** the request reached no listening server, usually a server instance that was restarting or scaling. It is not a timeout and not a rejected argument.
34
+
35
+ **What to do:** wait a few seconds and retry once. Retry `conversation_add_message` only with a `seed`, and never retry `conversation_create` (see the table below). If the retry also fails, report the failure. For a write, tell the user it was not saved; don't let a failed write pass silently.
36
+
37
+ ---
38
+
39
+ ## `memory_search` hangs or times out
40
+
41
+ `memory_search` can stall for minutes under backend load while other tools respond. Don't raise the client timeout; that only makes the stall longer. Retry once. If it stalls again, report that the search failed, and if the user needs an answer now, try `context_search` or `memory_get_by_id` for a known id. `memory_get_all` only returns a recent window, so it can't replace a search.
42
+
43
+ ---
44
+
45
+ ## Quota errors that look like auth errors
46
+
47
+ `free_tier_limit_reached` can arrive with HTTP 403, and automatic capture that hits a cap shows up as capture failing with 403. Read the error code, not the status. A quota error means the account reached a lifetime cap (see `tools-known-limitations.md`). Don't rotate keys or reinstall. Tell the user which tool hit the cap and that upgrading the plan lifts it.
48
+
49
+ ---
50
+
51
+ ## Datapack and ownership errors
52
+
53
+ | Error | Meaning | What to do |
54
+ |---|---|---|
55
+ | `datapack_access_denied` | The `datapack_id` isn't a UUID, or it isn't owned by or shared with your account. The access check also fails closed on a transient server database error. | Retry once. If it repeats, stop; don't switch to another datapack. If the id came from a pin, ask the user to re-pin with the select-datapack skill. |
56
+ | `datapack_id_required` | The call couldn't resolve a datapack. Conversation reads and writes (including `conversation_list`), `datapack_describe`, `knowledgebase_upload_document`, `knowledgebase_document_status`, and `knowledgebase_delete_document` return it. | Pass the pinned `datapack_id`. Don't drop the parameter to make the call pass. |
57
+ | `not_found` from `memory_get_by_id`, `memory_update`, or `memory_delete_by_id` | The memory doesn't exist **or** belongs to another account. The server returns the same error for both on purpose. | Check the id came from a search in this datapack. |
58
+ | `memory_is_promoted` | The memory was promoted to shared knowledge and is read-only through MCP. | Point the user at the Cloud UI Learnings tab. |
59
+ | `read_only_access` | You're a viewer on this datapack. Viewers can't write conversations or memories, call `memory_promote`, `artifact_put`, or `track_token_usage`, or delete a conversation someone else started. | Don't retry, and don't switch datapacks to get around it. Tell the user; an owner or maintainer can change their role. |
60
+ | `role_lookup_failed` | The server couldn't read your role for this datapack, so it refused the write. | Retry. |
61
+ | `invalid_run_id` | `memory_delete_all` got a `run_id` that isn't a conversation id. | Pass the conversation UUID, or omit `run_id`. |
62
+ | `embedder_error` from `knowledgebase_search` | The server couldn't resolve the embedding setup for this datapack's index. | Report it; retrying won't help. |
63
+ | `invalid_force_source` from `context_search` | `force_source` isn't `auto`, `memory`, `knowledge_base`, or `conversation`. | Omit `force_source`. |
64
+
65
+ ---
66
+
67
+ ## Conversation writes fail right after `conversation_create`
68
+
69
+ `conversation_add_message` has intermittently returned `not_found`, or `agent_id_mismatch` naming agent `'None'`, seconds after `conversation_create` returned that id. It shows up most with several parallel subagents writing to one conversation. Retry once with the same `seed` after a few seconds. If it still fails, don't create a new conversation for the same work: save the durable findings with `memory_add` and tell the user the transcript is incomplete.
70
+
71
+ On a long conversation that has worked before, `not_found` can instead mean the trace grew too large to load. See `tools-known-limitations.md`.
72
+
73
+ ---
74
+
75
+ ## "No Meko account found for cognito identity ..." on every call
76
+
77
+ Every Meko tool fails with this error while the connection itself looks fine. Two users hit it for the same reason: Claude Desktop had both the Meko URL connector (Settings > Connectors, which signs in through the browser) and the API-key install from `npx @yugabytedb/meko-mcp --client claude-desktop`, and Desktop used the connector's identity. The word "provisioned" in the error points at a backend account problem, but the fix is on the client:
78
+
79
+ 1. Ask the user for the full list under Settings > Connectors, not just `claude_desktop_config.json`.
80
+ 2. Have them remove the Meko URL connector and keep only the API-key install. Use the full API key from the downloaded file.
81
+ 3. Quit Claude Desktop completely (Cmd+Q) and reopen it.
82
+
83
+ ---
84
+
85
+ ## "Server disconnected" with `SyntaxError ... node:fs/promises ... 'constants'`
86
+
87
+ The MCP bridge started under an old Node.js version. `npx` picked up a Node older than 20 from the user's `PATH` (often through `nvm`). Have the user make Node 20 or later the default, for example `nvm alias default 20`, then fully quit and reopen Claude Desktop. Current installer releases declare Node 20.18.1 or later as their minimum.
88
+
89
+ ---
90
+
91
+ ## You can't run a delete the user asked for
92
+
93
+ Some Claude Desktop deployments refuse Meko delete tools (`memory_delete_by_id`, `memory_delete_all`, `conversation_delete`, `knowledgebase_delete_document`) by policy, even when the datapack owner asks. If that applies to you, say so plainly. Give the user the exact ids with a one-line summary of each and the tool call to run from a client that allows deletes, such as Claude Code. Don't report anything as deleted.
94
+
95
+ ---
96
+
29
97
  ## agent_id errors
30
98
 
31
99
  On the Cloud multi-tenant schema, `agent_id` is a TEXT column value — not a PostgreSQL identifier — so arbitrary strings are accepted.
@@ -11,7 +11,7 @@ description: >-
11
11
  license: MIT
12
12
  metadata:
13
13
  author: Meko
14
- version: "1.2.1"
14
+ version: "1.3.1"
15
15
  tags: meko, datapack, selection, project, claude-desktop, desktop
16
16
  ---
17
17
 
@@ -28,7 +28,7 @@ text: "meko_active_datapack=<uuid> name=<name> selected_at=<iso8601>"
28
28
  metadata: { "type": "active-datapack-pin" }
29
29
  ```
30
30
 
31
- You discover it by calling `memory_search(query="meko_active_datapack", agent_id="claude_desktop", limit=1)` at the start of any turn that's about to make a Meko MCP write. If you find a pin, pass `datapack_id="<uuid>"` to every Meko call that accepts it. If you don't find one, fall back to the server default (omit the parameter).
31
+ You discover it by calling `memory_search(query="meko_active_datapack", agent_id="claude_desktop", conversation_id="<this conversation's id>", limit=1)` (`conversation_id` is required and must be a 32-hex id; before `conversation_create`, pass a freshly generated one, never `""`) at the start of any turn that's about to make a Meko MCP write. If you find a pin, pass `datapack_id="<uuid>"` to every Meko call that accepts it. If you don't find one, fall back to the server default (omit the parameter).
32
32
 
33
33
  ## When to invoke this skill
34
34
 
@@ -63,7 +63,7 @@ This is once per turn, not once per call. Cache within the turn.
63
63
  Use a **temporary** conversation for browsing only. Call
64
64
  `conversation_create(agent_id="claude_desktop", title="Claude
65
65
  Desktop datapack selection")` and use its returned ID for the read-only
66
- `datapack_list` below and for the pin `memory_add`/`memory_update` in step 4.
66
+ datapack calls below and for the pin `memory_add`/`memory_update` in step 4.
67
67
 
68
68
  **Do not reuse this conversation for later capture.** It lives in whatever
69
69
  datapack was active when it was created (typically the default), and a
@@ -71,12 +71,28 @@ conversation cannot move between datapacks. Once a datapack is pinned, capture
71
71
  must flow through a *new* conversation created in the selected datapack — see
72
72
  step 5.
73
73
 
74
+ Check your tool list. If it has `datapack_search`, use it as below. If it
75
+ doesn't (an older Meko server), use the fallback section at the end of step 3.
76
+
74
77
  ```
75
- datapack_list(conversation_id="<this conversation's id>")
78
+ datapack_search(conversation_id="<this conversation's id>", limit=10, include_total=true)
79
+ ```
80
+
81
+ The server sorts, filters and pages, so this works the same for 3 datapacks or
82
+ 1,000,000. The response is one page:
83
+
84
+ ```
85
+ {"datapacks": [...], "has_more": true, "next_cursor": "<opaque token>", "total": 14}
76
86
  ```
77
87
 
78
- The response is an array; the fields you'll use are `datapack_id`,
79
- `datapack_name`, `created_at`, `account_id`, and `grant`.
88
+ - `datapacks`: up to `limit` rows, newest first (`created_at DESC`). The
89
+ fields you'll use are `datapack_id`, `datapack_name`, `created_at`, and
90
+ `grant`.
91
+ - `has_more`: `true` when another page exists.
92
+ - `next_cursor`: pass it as `cursor` to get the next page. It is `""` on the
93
+ last page. Treat it as opaque: never build or edit one.
94
+ - `total`: how many datapacks match, present because you passed
95
+ `include_total=true`.
80
96
 
81
97
  `grant` is the caller's actual authorization on the datapack — one of
82
98
  `"owner"`, `"maintainer"`, `"contributor"`, `"viewer"`. Display it verbatim in
@@ -85,13 +101,22 @@ the Role column. Do not hardcode "Owner", do not invent or normalize the value
85
101
  response is the source of truth — see `references/role-display-future.md` for
86
102
  the taxonomy.
87
103
 
88
- If the response has zero entries, tell the user: *"You don't have any datapacks yet. Run `datapack_create(name='<name>')` to make one, or visit the Meko Cloud console."* Stop here.
104
+ If `total` is 0, tell the user: *"You don't have any datapacks yet. Run `datapack_create(name='<name>', conversation_id='<conversation id>')` to make one, or visit the Meko Cloud console."* Stop here.
105
+
106
+ If `total` is exactly **1**, **auto-select it**. Print: *"Only one datapack: `<name>`. Auto-selecting. Run `meko-select-datapack-desktop` again with `clear` to unset."* Skip the table; jump to step 4 (persist) and step 5 (confirm).
107
+
108
+ If `total` is missing or `null`, decide from the page instead: no rows and
109
+ `has_more` false means zero datapacks; one row and `has_more` false means
110
+ exactly one. Drop `of <total>` from the header in step 2.
89
111
 
90
- If the response has exactly **one** entry, **auto-select it**. Print: *"Only one datapack: `<name>`. Auto-selecting. Run `meko-select-datapack-desktop` again with `clear` to unset."* Skip the table; jump to step 4 (persist) and step 5 (confirm).
112
+ **Keep the paging state in the conversation.** Remember the current filter
113
+ (none yet), the `total`, the page number, the `next_cursor`, and a list of the
114
+ cursors you used for each page so far (page 1 used no cursor). `next` and
115
+ `prev` in step 3 need them.
91
116
 
92
117
  ### 2. Render the table
93
118
 
94
- For 2+ datapacks, render a numbered table. **Page size 10.** Default sort: `created_at DESC` (newest first).
119
+ For 2+ datapacks, render a numbered table of the current page. **Page size 10.** Row order is the server's (`created_at DESC`, newest first). Number rows by position in the whole list: page 1 shows 1-10, page 2 shows 11-20. The count line is `Your datapacks (showing <first>-<last> of <total>):`, for example:
95
120
 
96
121
  ```
97
122
  Your datapacks (showing 1-10 of 14):
@@ -112,17 +137,71 @@ The **Role** column shows each datapack's `grant` value verbatim (`owner`,
112
137
  datapacks legitimately carry other grants, and misreporting them (e.g. showing
113
138
  a `maintainer` grant as "Owner") misleads the user about their own access.
114
139
 
115
- Mark the currently-pinned row with `← (pinned)` based on the `memory_search` result (if any).
140
+ Mark the currently-pinned row with `← (pinned)` based on the `memory_search` result (if any). If the pinned datapack is not on the current page, name it in one line above the table.
141
+
142
+ In the legend, the number range is the rows on the current page (`11-20` on
143
+ page 2). Offer `next` only when `has_more` is true and `next_cursor` is
144
+ non-empty. An empty `next_cursor` with `has_more` still true means this
145
+ server cannot page further: tell the user to narrow with a substring, and
146
+ do not send an empty cursor. Offer `prev` only after page 1.
147
+
148
+ For a search (step 3), the count line is
149
+ `Datapacks matching "<text>" (showing <first>-<last> of <total>):`, numbered
150
+ from 1.
116
151
 
117
152
  ### 3. Parse the user's reply
118
153
 
119
- - **Integer in range** → that row is the choice.
120
- - **Substring** → re-render filtered. If it narrows to one row, that's the choice.
121
- - **`next` / `prev`** → re-render next/previous page.
154
+ - **Integer shown on the current page** → that row is the choice.
155
+ - **Substring** → search on the server; do not filter the rows you already have:
156
+ ```
157
+ datapack_search(conversation_id="<this conversation's id>", query="<text>", limit=10, include_total=true)
158
+ ```
159
+ This starts a new listing: reset the page number and the cursor list, and
160
+ keep `<text>` as the filter so `next` / `prev` page through the matches. If
161
+ `total` is 0, say *"No datapacks match `<text>`."* and re-prompt with the
162
+ legend. If `total` is exactly 1, that's the choice. Otherwise render the
163
+ matches with the search count line from step 2.
164
+ - **`next`** → if `has_more` is false, say this is the last page and
165
+ re-prompt. If `next_cursor` is empty, say this server cannot page further
166
+ and ask for a substring; do not send an empty cursor. Otherwise call
167
+ `datapack_search` with the same `query` (if any), `limit=10` and
168
+ `cursor="<next_cursor>"`, add that cursor to your list, and render the new
169
+ page. Reuse the `total` you already have.
170
+ - **`prev`** → on page 1, say this is the first page and re-prompt. Otherwise
171
+ drop the last cursor from your list and call `datapack_search` again with
172
+ the same `query` and the cursor now at the end of the list (no cursor for
173
+ page 1).
122
174
  - **`cancel`** → leave any existing pin in place; print *"Cancelled. Active datapack unchanged."* Stop.
123
175
  - **`clear` / `none` / `unpin`** → see step 4's clear path.
124
176
  - **Unrecognized** → re-prompt with the legend.
125
177
 
178
+ If a call with a `cursor` returns an error, the cursor is no longer usable.
179
+ Tell the user, then start again from the first page of the same filter.
180
+
181
+ When the user names a datapack in the request itself ("pin prod-research"),
182
+ skip the first listing and go straight to the substring search with that name.
183
+
184
+ ### Fallback: servers without `datapack_search`
185
+
186
+ An older Meko server has only `datapack_list`. Run the same steps with these
187
+ changes:
188
+
189
+ ```
190
+ datapack_list(conversation_id="<this conversation's id>")
191
+ ```
192
+
193
+ - The response is one array of every datapack, with the same row fields.
194
+ `total` is the array length.
195
+ - Sort it `created_at DESC` yourself, show 10 rows per page, and page
196
+ `next` / `prev` over the array you already have.
197
+ - Filter substrings client-side on `datapack_name` (case-insensitive) and
198
+ re-render the matching rows numbered from 1. If the filter narrows to one
199
+ row, that's the choice.
200
+
201
+ If `datapack_list` returns `{"error": "too_many_datapacks", ...}`, the server
202
+ does have `datapack_search`; your tool list is stale. Reload the Meko tools and
203
+ use the main flow.
204
+
126
205
  ### 4. Persist
127
206
 
128
207
  **On first pin (no existing pin memory):**
@@ -192,7 +271,7 @@ would split capture across the old and new datapacks or fail outright.
192
271
  ## Edge cases
193
272
 
194
273
  - **Multiple pin memories somehow exist** — the user (or a buggy earlier run) created duplicates. Use the most recent (highest `selected_at`); offer to delete the others.
195
- - **The pinned datapack no longer exists on the server** — the next Meko MCP call will fail with a not-found error. Re-run the skill to pick a new one or `clear` the stale pin.
274
+ - **The pinned datapack no longer exists or was unshared** — the next Meko MCP call that passes it fails with `datapack_access_denied`. Re-run the skill to pick a new one or `clear` the stale pin.
196
275
  - **memory_search times out or fails** — fall back to the server default (omit `datapack_id`) and tell the user *"Couldn't read the active-datapack pin. Using server default for this call."* Don't guess a UUID.
197
276
  - **User explicitly overrides for a single call** — pass the user's `datapack_id` for that one call. Do NOT update the pin memory.
198
277