@yugabytedb/meko-mcp 2.3.0 → 2.5.1

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 (42) hide show
  1. package/README.md +88 -35
  2. package/assets/hooks-handlers/lib/capture-cleanup-cli.js +735 -74
  3. package/assets/hooks-handlers/lib/capture-notices.js +94 -1
  4. package/assets/hooks-handlers/lib/capture-state.js +111 -0
  5. package/assets/hooks-handlers/lib/capture.js +130 -4
  6. package/assets/hooks-handlers/lib/checkpoint-timer.js +394 -8
  7. package/assets/hooks-handlers/lib/opencode-handler.js +29 -6
  8. package/assets/scripts/meko-statusline.sh +17 -1
  9. package/assets/skills/meko-mcp-tools/SKILL.md +6 -5
  10. package/assets/skills/meko-mcp-tools/references/_sections.md +1 -1
  11. package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +194 -10
  12. package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +6 -3
  13. package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +11 -6
  14. package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +4 -4
  15. package/assets/skills/meko-mcp-tools/references/tools-overview.md +19 -14
  16. package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +69 -8
  17. package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +3 -1
  18. package/assets/skills/meko-mcp-tools-desktop/SKILL.md +8 -7
  19. package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +4 -4
  20. package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +1 -1
  21. package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +194 -10
  22. package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +10 -5
  23. package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +4 -4
  24. package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +19 -14
  25. package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +68 -8
  26. package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +3 -1
  27. package/assets/skills/meko-select-datapack-desktop/SKILL.md +91 -12
  28. package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +39 -7
  29. package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +8 -6
  30. package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +13 -1
  31. package/bin/create-meko-setup.mjs +152 -35
  32. package/lib/autofix.mjs +100 -0
  33. package/lib/browser-login.mjs +495 -0
  34. package/lib/clients/codex.mjs +1 -24
  35. package/lib/clients/common.mjs +25 -0
  36. package/lib/clients/opencode.mjs +6 -1
  37. package/lib/migrate/datapack.mjs +254 -16
  38. package/lib/migrate/mcp-client.mjs +34 -0
  39. package/lib/migrate/orchestrator.mjs +1 -1
  40. package/lib/migrate.mjs +2 -6
  41. package/lib/validate.mjs +111 -22
  42. package/package.json +5 -5
@@ -1,16 +1,19 @@
1
1
  # Knowledge Base / RAG workflow
2
2
 
3
- MCP exposes two KB tools: `knowledgebase_search` (retrieval) and
4
- `knowledgebase_delete_document` (remove ONE uploaded file). Index
5
- creation and ingestion happen out of band — through the **Meko UI**
6
- (`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`).
7
8
 
8
9
  ## Decision table
9
10
 
10
11
  | Situation | Use this | Why |
11
12
  |---|---|---|
12
13
  | Search an existing KB | `knowledgebase_search` | Retrieval over the indexed chunks |
13
- | 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 |
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 |
14
17
  | Delete ONE uploaded file | `knowledgebase_delete_document` | Destructive; `document_id` from search hits; confirm with the user first |
15
18
  | Anything else (list / rename / re-index / source lifecycle) | **UI** | No MCP tools for the rest of KB lifecycle |
16
19
 
@@ -58,6 +61,59 @@ Lower `distance` is closer. Promoted memories appear as hits whose `metadata_fil
58
61
  Empty or nonexistent KB: `{"results": [], "count": 0}` (no error,
59
62
  just an empty array).
60
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
+
61
117
  ## UI ingestion — what the user sees
62
118
 
63
119
  From `cloud.mekodata.ai`:
@@ -65,11 +121,15 @@ From `cloud.mekodata.ai`:
65
121
  1. Open a datapack (`/datapacks/<name>`).
66
122
  2. Click **Actions** → **Add Knowledge**.
67
123
  3. Drag-and-drop or browse for files. Supported: PDF, TXT, MD, JSON,
68
- 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.
69
125
  4. The UI handles upload, storage, and index build. The user does not
70
126
  pick a bucket or provide credentials.
71
127
  5. Search results from `knowledgebase_search` become available once
72
128
  indexing completes.
73
129
 
74
- If a user asks you "how do I add a document to my knowledge base?",
75
- 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 — and can send them in an
133
+ HTTPS POST to an external URL yourself (a local shell, or a code tool with
134
+ internet access), call `knowledgebase_upload_document` directly instead
135
+ of describing the UI flow to yourself.
@@ -53,9 +53,11 @@ These patterns are extracted from real agent sessions. Follow them to avoid wast
53
53
  | Error | Meaning | What to do |
54
54
  |---|---|---|
55
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`, and `knowledgebase_delete_document` return it. | Pass the pinned `datapack_id`. Don't drop the parameter to make the call pass. |
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
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
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. |
59
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`. |
60
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. |
61
63
  | `invalid_force_source` from `context_search` | `force_source` isn't `auto`, `memory`, `knowledge_base`, or `conversation`. | Omit `force_source`. |
@@ -11,7 +11,7 @@ description: >-
11
11
  license: MIT
12
12
  metadata:
13
13
  author: Meko
14
- version: "1.2.2"
14
+ version: "1.3.1"
15
15
  tags: meko, datapack, selection, project, claude-desktop, desktop
16
16
  ---
17
17
 
@@ -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>', conversation_id='<conversation id>')` 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):**
@@ -1,6 +1,6 @@
1
1
  # Cookbook — meko-select-datapack-desktop
2
2
 
3
- Concrete transcripts of the four flows the skill supports on Claude Desktop.
3
+ Concrete transcripts of the flows the skill supports on Claude Desktop.
4
4
 
5
5
  ## Flow 1 — First pin (no existing pin memory)
6
6
 
@@ -11,8 +11,8 @@ Concrete transcripts of the four flows the skill supports on Claude Desktop.
11
11
  > memory_search(query="meko_active_datapack", agent_id="claude_desktop", conversation_id="<conv-id>", limit=1)
12
12
  [empty]
13
13
 
14
- > datapack_list(conversation_id="<conv-id>")
15
- [14 entries returned, filtering by "prod-research"]
14
+ > datapack_search(conversation_id="<conv-id>", query="prod-research", limit=10, include_total=true)
15
+ [1 row, has_more=false, next_cursor="", total=1]
16
16
 
17
17
  One match: `prod-research`. Pinning.
18
18
 
@@ -44,8 +44,8 @@ datapack_id flows through automatically.
44
44
  > memory_search(query="meko_active_datapack", agent_id="claude_desktop", conversation_id="<conv-id>", limit=1)
45
45
  [returns id=mem_abc123, text="meko_active_datapack=8b1c…f4a2 name=prod-research …"]
46
46
 
47
- > datapack_list(conversation_id="<conv-id>")
48
- [14 entries returned, filtering by "q2-roadmap-deck"]
47
+ > datapack_search(conversation_id="<conv-id>", query="q2-roadmap-deck", limit=10, include_total=true)
48
+ [1 row, has_more=false, next_cursor="", total=1]
49
49
 
50
50
  One match: `q2-roadmap-deck`. Switching pin.
51
51
 
@@ -69,8 +69,8 @@ The `memory_update` is critical here — using `memory_add` again would create a
69
69
  > memory_search(query="meko_active_datapack", agent_id="claude_desktop", conversation_id="<conv-id>", limit=1)
70
70
  [empty]
71
71
 
72
- > datapack_list(conversation_id="<conv-id>")
73
- [1 entry returned]
72
+ > datapack_search(conversation_id="<conv-id>", limit=10, include_total=true)
73
+ [1 row, has_more=false, next_cursor="", total=1]
74
74
 
75
75
  Only one datapack: `meko-local-setup`. Auto-selecting.
76
76
 
@@ -119,3 +119,35 @@ This isn't a user-triggered flow — it's the per-turn lookup the skill teaches
119
119
  ```
120
120
 
121
121
  Cache the UUID for the rest of the turn — don't call `memory_search` once per Meko tool call.
122
+
123
+ ## Flow 6: Browse and page
124
+
125
+ **User:** `list my datapacks`
126
+
127
+ **Skill:**
128
+ ```
129
+ > datapack_search(conversation_id="<conv-id>", limit=10, include_total=true)
130
+ [10 rows, has_more=true, next_cursor="eyJ2Ijox…", total=14]
131
+
132
+ Your datapacks (showing 1-10 of 14):
133
+ …
134
+ ```
135
+
136
+ **User:** `next`
137
+
138
+ ```
139
+ > datapack_search(conversation_id="<conv-id>", limit=10, cursor="eyJ2Ijox…")
140
+ [4 rows, has_more=false, next_cursor=""]
141
+
142
+ Your datapacks (showing 11-14 of 14):
143
+ …
144
+
145
+ Reply with: a number (11-14) to pin · a substring to search · `prev` to page
146
+ · `clear` to unset · `cancel` to leave the current pin alone
147
+ ```
148
+
149
+ The skill remembers each cursor it used. On `prev` it drops the last one and repeats the call for page 1, which had no cursor. The total (14) is reused from page 1. A substring reply calls `datapack_search(query="<text>", limit=10, include_total=true)` and starts a new listing.
150
+
151
+ ## Flow 7: Older server without `datapack_search`
152
+
153
+ The tool list has `datapack_list` but no `datapack_search`. The skill calls `datapack_list(conversation_id="<conv-id>")` once, then sorts, pages and filters that array in the conversation. The table and replies look the same.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Today: render `grant` verbatim
4
4
 
5
- The deployed Meko `GET /datapacks` response includes a `grant` field on every datapack. Possible values (per maintainer guidance, 2026-05-26):
5
+ The deployed Meko `GET /datapacks` response includes a `grant` field on every datapack, and so does every row that `datapack_search` and `datapack_list` return. Possible values (per maintainer guidance, 2026-05-26):
6
6
 
7
7
  - `"owner"` — datapacks the caller created.
8
8
  - `"maintainer"` — write access to a shared datapack including memories AND collective learnings/knowledge.
@@ -20,11 +20,11 @@ Use the field directly in the **Role** column:
20
20
 
21
21
  Do not invent values, do not normalize the casing, do not display `"Owner"` if the field says `"owner"`. The deployed schema is the source of truth.
22
22
 
23
- Note that the upstream Go source (the `Datapack` struct in `api_server/internal/models/models.go` of the `meko` API-server repo) does NOT declare `grant` — the deployed server enriches the response beyond what the public Go code lists. Don't rely on the public struct for the wire schema; trust the actual response.
23
+ Note that the server's `Datapack` model doesn't declare `grant`: the deployed server enriches the response beyond what the public Go code lists. Don't rely on the public struct for the wire schema; trust the actual response.
24
24
 
25
- ## Other fields the live response includes
25
+ ## Counts are not on the list response
26
26
 
27
- `datapack_list` doesn't compute counts; its list response carries no count fields. If the user asks for counts, call `datapack_describe(datapack_id=<id>, conversation_id="<conversation id>")` and read them from that response:
27
+ Neither `datapack_search` nor `datapack_list` computes counts; their rows carry no count fields. If the user asks for counts, call `datapack_describe(datapack_id=<id>, conversation_id="<conversation id>")` and read them from that response:
28
28
 
29
29
  - `memory_count`: all memories stored in the datapack.
30
30
  - `collective_memory_count`: memories promoted to shared knowledge.
@@ -34,9 +34,11 @@ Note that the upstream Go source (the `Datapack` struct in `api_server/internal/
34
34
 
35
35
  Don't add counts to the table silently, and never invent the numbers.
36
36
 
37
- ## Sharing UI vs API state
37
+ ## Sharing a datapack
38
38
 
39
- The Cloud console "Share <datapack>" page (`meko_ui/src/features/datapacks/pages/DatapackSharePage.tsx`) shows interactive controls with "Coming Soon" overlays on a few specific actions (e.g. invite-by-email link sharing, transfer-ownership). Those overlays mean *those specific UI affordances* aren't shipped — NOT that the underlying `grant`/sharing model is absent. Contributor/maintainer/viewer grants are functional in the data layer today; only the user-facing self-serve invite flow is gated.
39
+ An owner or maintainer shares a datapack from the Cloud console's **Share** page. Each datapack has one share link. The owner or maintainer can turn it on or off, reset it, and choose whether people who open it join as viewer or contributor. Anyone signed in who opens an active link joins straight away. There are no MCP tools to manage sharing.
40
+
41
+ A viewer is read-only, and the server enforces it: over MCP, conversation and memory writes return `read_only_access`. If the user picks a datapack where they're a viewer, tell them that Meko won't record conversations or save memories there.
40
42
 
41
43
  ## Why the skill ships without Mine / Shared / All filter tabs
42
44
 
@@ -33,7 +33,7 @@ Then either re-run the skill or delete the pin memory. Do not silently fall back
33
33
 
34
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
35
 
36
- ## Symptom: `datapack_list` returns an empty array
36
+ ## Symptom: The account has no datapacks (`total` is 0, or `datapack_list` returns an empty array)
37
37
 
38
38
  The user has zero datapacks. Print:
39
39
 
@@ -47,6 +47,18 @@ Or visit the Meko Cloud console (Datapacks → New datapack).
47
47
 
48
48
  Stop. Don't `memory_add` an empty pin.
49
49
 
50
+ ## Symptom: `next` or `prev` returns an error about the cursor
51
+
52
+ The cursor is tied to the search it came from. It stops working if you change `query`, `sort` or `grant` while paging, or if you edit it. Always send the cursor back unchanged, with the same filters as the call that returned it. Tell the user the page could not be loaded and start again from page 1 of the same search (no cursor).
53
+
54
+ ## Symptom: `datapack_list` returns `too_many_datapacks`
55
+
56
+ The account can see more datapacks than `datapack_list` returns in one array (1000 by default). The server that sends this error also has `datapack_search`. Use the main flow with `datapack_search`; if it is missing from your tool list, the list is stale, so reload the Meko tools.
57
+
58
+ ## Symptom: The tool list has no `datapack_search`
59
+
60
+ The Meko server is older than that tool. Use the skill's fallback flow with `datapack_list`: one call, then sort, page and filter the array in the conversation. On an older server, this call can fail for an account with a very large number of datapacks; that account needs a server that has `datapack_search`.
61
+
50
62
  ## Symptom: User typed `cancel` after the table rendered
51
63
 
52
64
  Leave the existing pin memory alone. Print: