@yugabytedb/meko-mcp 2.5.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.
package/README.md CHANGED
@@ -16,8 +16,11 @@ through `mcp-remote` header interpolation. Capture hooks use the same key.
16
16
  You don't need to have a key already. The installer gets one in this order:
17
17
 
18
18
  1. `--api-key <key>`.
19
- 2. The `MEKO_API_KEY` environment variable.
20
- 3. **Browser sign-in (the default).** The installer opens the Meko sign-in page,
19
+ 2. The `MEKO_API_KEY` environment variable, for any MCP URL, local ones
20
+ included. `--local` ignores it, and so does a `--yes` rerun that reuses the
21
+ URL an earlier `--local` install registered: the docker-compose dev stack
22
+ takes no key.
23
+ 3. **Browser sign-in (the default, Cloud only).** The installer opens the Meko sign-in page,
21
24
  and after you sign in it creates a key named `Meko installer (<host>)
22
25
  <timestamp>`. It uses the MCP server's OAuth flow with a `localhost`
23
26
  callback, then calls the server's `POST /api-keys`. The OAuth session is
@@ -170,6 +173,17 @@ npx @yugabytedb/meko-mcp \
170
173
  --yes
171
174
  ```
172
175
 
176
+ **Meko Local (the `meko` server, which requires its client token):**
177
+
178
+ ```bash
179
+ MEKO_API_KEY="$(meko token)" npx @yugabytedb/meko-mcp \
180
+ --url http://127.0.0.1:7778/mcp \
181
+ --yes
182
+ ```
183
+
184
+ Passing the key through `MEKO_API_KEY` keeps it off the command line, where
185
+ `ps` would show it.
186
+
173
187
  ## What It Does
174
188
 
175
189
  The installer performs up to 5 steps (some are skippable via flags):
@@ -256,9 +270,9 @@ Usage:
256
270
  |------|-------------|---------|
257
271
  | `--client <list>` | Target client(s): one or more of `claude-code`, `claude-desktop`, `cursor`, `codex`, `kiro`, `opencode`. Comma-separated, or `all`. `codex` covers both the OpenAI Codex CLI and the Codex Desktop App (they share `~/.codex/config.toml`); `kiro` covers both the Kiro IDE and `kiro-cli` (they share `~/.kiro/settings/mcp.json`). OpenCode is version-gated to CLI/TUI 1.18.x. Aliases: `claude` → `claude-code`, `kiro-cli` / `kiro-ide` → `kiro`. | `claude-code` |
258
272
  | `--url <url>` | MCP server URL (from cloud.mekodata.ai > Datapacks > Connect) | Shared existing endpoint, otherwise production; conflicts prompt or require `--url` under `--yes` |
259
- | `--api-key <key>` | Use an existing Cloud Meko API key instead of signing in. Without this flag, the installer falls back to `$MEKO_API_KEY`, then to browser sign-in, which creates a key. Fails hard when no TTY is attached, with or without `--yes`. | _(`$MEKO_API_KEY`, else browser sign-in)_ |
273
+ | `--api-key <key>` | Use an existing Meko API key (Cloud, or Meko Local's client token) instead of signing in. Without this flag, the installer falls back to `$MEKO_API_KEY` for any URL (not with `--local`), then, for Cloud URLs only, to browser sign-in, which creates a key. A Cloud URL with no key fails hard when no TTY is attached, with or without `--yes`; a local URL installs without one. | _(`$MEKO_API_KEY`, else browser sign-in for Cloud)_ |
260
274
  | `--no-browser` | Print the sign-in URL instead of opening a browser. | `false` |
261
- | `--local` | Use local Meko at `http://localhost:8000/mcp` (no API key needed) | `false` |
275
+ | `--local` | Use local Meko at `http://localhost:8000/mcp` (no API key; ignores `$MEKO_API_KEY`) | `false` |
262
276
  | `--scope <scope>` | Config scope: `user` or `project` (`claude-desktop` uses user scope config) | `user` |
263
277
  | `--name <name>` | MCP server name in config | `meko` |
264
278
  | `--cursor-hook-mode <mode>` | Cursor hook strategy: `native` or `claude-compat` | `native` |
@@ -351,7 +365,7 @@ Dry run:
351
365
 
352
366
  ### Why the migrator drives memory extraction
353
367
 
354
- The Meko MCP server does **not** currently auto-extract memories on `conversation_add_message` (that call only writes to Langfuse). See [yugabyte/meko#120](https://github.com/yugabyte/meko/issues/120) — auto-extraction on the live write path was deferred because of LLM cost, but batch extraction for *previously-existing* conversations was explicitly endorsed. Until #120 lands, the migrator closes that gap itself by calling `memory_add(messages=...)` once per paired turn.
368
+ The Meko MCP server does **not** currently auto-extract memories on `conversation_add_message` (that call only writes to Langfuse). See [yugabyte/agentic-cloud#120](https://github.com/yugabyte/agentic-cloud/issues/120) — auto-extraction on the live write path was deferred because of LLM cost, but batch extraction for *previously-existing* conversations was explicitly endorsed. Until #120 lands, the migrator closes that gap itself by calling `memory_add(messages=...)` once per paired turn.
355
369
 
356
370
  `--no-extract` skips step 3 above, turning migration into conversation-only replay. Use it when you're re-running for any reason other than seeding memories, when extraction cost is a concern, or if / when #120 lands (the migrator will be updated to default to `--no-extract` then).
357
371
 
@@ -2007,6 +2007,7 @@ function peekTranscriptMetadata(transcriptPath) {
2007
2007
  async function fetchRecentMemories(agentId, datapackId = null, { limit = 10, budget = 2000 } = {}) {
2008
2008
  // memory_get_all takes no `limit`, and FastMCP rejects an unknown argument
2009
2009
  // with an isError result, so `limit` caps the page client-side instead.
2010
+ // Don't send `page_size` either: a server older than MEKO-453 rejects it.
2010
2011
  const payload = {
2011
2012
  agent_id: agentId,
2012
2013
  conversation_id: "00000000-0000-0000-0000-000000000000",
@@ -4,7 +4,7 @@ description: Behavioral guide for AI agents using Meko MCP tools. Triggers when
4
4
  license: MIT
5
5
  metadata:
6
6
  author: Meko
7
- version: "3.4.0"
7
+ version: "3.5.0"
8
8
  tags: mcp, tools, datapack, memory, conversation, rag, meko
9
9
  ---
10
10
 
@@ -102,7 +102,8 @@ Recall first; save only what capture cannot reach; never re-store a just-stated
102
102
  | User asks "what do we (as a team) know about X?" or about uploaded documents | `knowledgebase_search` (returns uploaded docs + promoted memories). |
103
103
  | User asks to share private memories | Follow the exact-ID `memory_promote` confirmation workflow below. |
104
104
  | User provides structured/tabular data (CSV, data dictionary) | If it's an actual file the user wants in the team's knowledge base, `knowledgebase_upload_document` accepts `.csv` directly (confirm first; see below). If it's pasted/described data with no file behind it, still don't `memory_add` it row-by-row — ask the user for it as a file, or point them at the UI's Add Knowledge upload. |
105
- | User wants to add documents to the team's knowledge base | Uploading adds the file to the datapack's shared knowledge base, which every member can read, so ask first: name the file and the datapack, say the team will see it, and upload only after the user says yes. If you (the agent) have the file's bytes, call `knowledgebase_upload_document`. Otherwise point the user at the Cloud UI flow — Datapack → Actions → **Add Knowledge** (PDF/TXT/MD/JSON/CSV/XML/HTML, 5MB Free / 50MB Standard / 60MB Pro+ each, 10/batch). KB *lifecycle* (index/source management) is still UI-only. |
105
+ | User wants a file kept in Meko but didn't say how | KB (`knowledgebase_upload_document`) if its content should be searchable later — docs, manuals, policies, notes people will ask about. Artifact (`artifact_put`) to keep it exactly as-is and fetch it back whole — a generated report, a result CSV. If it is not clear whether the user needs semantic search or whole-file retrieval, ask. |
106
+ | User wants to add documents to the team's knowledge base | Uploading adds the file to the datapack's shared knowledge base, which every member can read, so ask first: name the file and the datapack, say the team will see it, and upload only after the user says yes. If you have the file's bytes — already on disk, or generated mid-conversation — and a shell, check that you can reach S3 first: run `curl -sS -o /dev/null -m 10 -w '%{http_code}' https://s3.amazonaws.com`; in Windows PowerShell, run `curl.exe` with `NUL` in place of `/dev/null`. A 2xx or 3xx status means yes (S3's root normally answers 307). `000`, a connection, DNS or proxy error, or any other status, such as a proxy's 403 or 407, means no. A pass shows outbound HTTPS to S3 works, not that the presigned bucket host does, so if the POST still fails, report it and stop. If it passes, call `knowledgebase_upload_document`. A sandboxed session may have the file but no outbound network; if the check fails, don't call it, since an unused URL holds storage quota for up to 15 minutes. Otherwise point the user at the Cloud UI flow — Datapack → Actions → **Add Knowledge** (PDF/TXT/MD/JSON/CSV/XML/HTML, 5MB Free / 50MB Standard / 60MB Pro+ each, 10/batch). KB *lifecycle* (index/source management) is still UI-only. |
106
107
 
107
108
  ### When to call memory_add
108
109
 
@@ -296,7 +296,7 @@ Returns `{"document_id", "chunks_deleted", "s3_deleted"}` — if `s3_deleted` is
296
296
 
297
297
  ### knowledgebase_upload_document
298
298
 
299
- **When to use:** Get a file's bytes into the datapack's KB for later `knowledgebase_search` retrieval — a file already on disk, or one generated mid-conversation. This tool never touches the file's bytes itself; it only requests a presigned S3 URL. Meko registers the file once it lands, so there's no separate confirm call. **The caller POSTs the file to the returned `url` directly** — multipart/form-data, every key in `fields` as a form field (in order), then the file's bytes last as a field named `"file"`.
299
+ **When to use:** Get a file's bytes into the datapack's KB for later `knowledgebase_search` retrieval — a file already on disk, or one generated mid-conversation. Use it when the content should be searchable by meaning; to keep a file exactly as-is and fetch it back whole, use `artifact_put`. Before calling it, check that you can reach S3 first: run `curl -sS -o /dev/null -m 10 -w '%{http_code}' https://s3.amazonaws.com`; in Windows PowerShell, run `curl.exe` with `NUL` in place of `/dev/null`. A 2xx or 3xx status means yes (S3's root normally answers 307). `000`, a connection, DNS or proxy error, or any other status, such as a proxy's 403 or 407, means no. A pass shows outbound HTTPS to S3 works, not that the presigned bucket host does, so if the POST still fails, report it and stop; a sandboxed session may have the file but no outbound network. If the check fails, don't call it — point the user at the UI. This tool never touches the file's bytes itself; it only requests a presigned S3 URL. Meko registers the file once it lands, so there's no separate confirm call. **The caller POSTs the file to the returned `url` directly** — multipart/form-data, every key in `fields` as a form field (in order), then the file's bytes last as a field named `"file"`.
300
300
 
301
301
  **Confirm first.** The file goes into the datapack's shared knowledge base, which every member can read. Before calling the tool, tell the user which file and datapack, say the team will see it, and wait for a yes.
302
302
 
@@ -465,7 +465,7 @@ Poll until `state` is `completed` or `failed` (or the pre-registration terminals
465
465
 
466
466
  ### artifact_put
467
467
 
468
- **When to use:** An agent has generated a file (report, CSV, PDF, code output, image) and wants to persist it in Meko for later retrieval or cross-agent sharing. Files < 1 MB are stored inline in the DB; files ≥ 1 MB go to the datapack's S3 bucket. Idempotent — uploading the same bytes twice returns the same `content_hash`.
468
+ **When to use:** An agent has generated a file (report, CSV, PDF, code output, image) and wants to persist it in Meko for later retrieval or cross-agent sharing. To make its content searchable by meaning instead, use `knowledgebase_upload_document`. Files < 1 MB are stored inline in the DB; files ≥ 1 MB go to the datapack's S3 bucket. Idempotent — uploading the same bytes twice returns the same `content_hash`.
469
469
 
470
470
  ```
471
471
  artifact_put(
@@ -604,4 +604,4 @@ Agent and KB lifecycle does not ship as MCP tools. If the user asks to create/li
604
604
  - REST: `POST/GET/DELETE /datapacks/:datapack_id/agents` (agents), `POST/GET/DELETE /datapacks/:datapack_id/knowledge-bases` (KB sources).
605
605
  - Or the Meko Cloud UI at `cloud.mekodata.ai` → Datapacks → agents / knowledge-bases.
606
606
 
607
- Uploading a *file* into an existing KB is not part of that lifecycle restriction — use `knowledgebase_upload_document` (see above) rather than the UI for anything driven from MCP. Agents querying a populated index use `knowledgebase_search`, and can remove a single uploaded file with `knowledgebase_delete_document` — see `tools-rag-workflow.md`.
607
+ Uploading a *file* into an existing KB is not part of that lifecycle restriction — use `knowledgebase_upload_document` (see above) rather than the UI for anything driven from MCP, when you can POST the file's bytes yourself. Agents querying a populated index use `knowledgebase_search`, and can remove a single uploaded file with `knowledgebase_delete_document` — see `tools-rag-workflow.md`.
@@ -35,7 +35,7 @@ Provisioning agents and knowledge-base *sources* (registering/indexing a new sou
35
35
 
36
36
  If the user asks to set those up mid-session, point them at the control plane rather than inventing an MCP call that will fail.
37
37
 
38
- Uploading a *file* into an existing KB is a different, already-MCP-exposed operation — use `knowledgebase_upload_document` (see `tools-cookbook.md`) rather than routing the user to the Add Knowledge UI.
38
+ Uploading a *file* into an existing KB is a different, already-MCP-exposed operation — use `knowledgebase_upload_document` (see `tools-cookbook.md`) rather than routing the user to the Add Knowledge UI, when you can POST the file's bytes yourself.
39
39
 
40
40
  ### Targeting a specific datapack on subsequent calls
41
41
 
@@ -60,7 +60,7 @@ The one exception is a single uploaded **file**: `knowledgebase_delete_document`
60
60
 
61
61
  Manually creating a vector index, adding a source to an existing one, or polling raw build status is not exposed via MCP. Use the Cloud UI for those (Datapack → Actions → Add Knowledge).
62
62
 
63
- **Adding a file** is a registered MCP tool: call `knowledgebase_upload_document` directly (accepted extensions PDF/TXT/MD/JSON/CSV/XML/HTML, up to 5 MB Free / 50 MB Standard / 60 MB Pro+ per file) to get a presigned URL, then POST the bytes to it yourself; Meko registers and indexes the file once it lands, so no separate index-management call is needed. Confirm with the user first, because every datapack member can read the knowledge base. The Cloud UI's **Add Knowledge** flow (10 files per batch) remains the human-facing path for the same upload.
63
+ **Adding a file** is a registered MCP tool: if you can send the file's bytes in an HTTPS POST to an external URL yourself (a sandboxed session may have the file but no outbound network), call `knowledgebase_upload_document` directly (accepted extensions PDF/TXT/MD/JSON/CSV/XML/HTML, up to 5 MB Free / 50 MB Standard / 60 MB Pro+ per file) to get a presigned URL, then POST the bytes to it yourself; Meko registers and indexes the file once it lands, so no separate index-management call is needed. Confirm with the user first, because every datapack member can read the knowledge base. The Cloud UI's **Add Knowledge** flow (10 files per batch) remains the human-facing path for the same upload.
64
64
 
65
65
  To **query** a built index, use `knowledgebase_search` — it returns both uploaded documents and memories the user promoted from the Learnings tab.
66
66
 
@@ -85,7 +85,7 @@ It works **poorly** for:
85
85
  conversation_id="<session conversation_id>",
86
86
  text="Customer Complaints dataset has fields: Complaint ID (int PK), Date Submitted (date), Product (text, e.g. Mortgage/Credit Card), Sub-product (text), Issue (text), Company Response (text), State (text, 2-letter code), ZIP Code (text)")
87
87
  ```
88
- 2. **Upload the file itself to the knowledge base** with `knowledgebase_upload_document` — `.csv`, `.json` and the other accepted types go in as they are — and query it with `knowledgebase_search`. (The Cloud UI's **Add Knowledge** flow does the same for a person.)
88
+ 2. **Upload the file itself to the knowledge base** with `knowledgebase_upload_document`, if you can POST its bytes yourself — `.csv`, `.json` and the other accepted types go in as they are — and query it with `knowledgebase_search`. (The Cloud UI's **Add Knowledge** flow does the same for a person.)
89
89
 
90
90
  Never ingest CSV row-by-row into memory — each row becomes a fragmented fact with lost context.
91
91
 
@@ -85,7 +85,7 @@ Agents on Cloud Meko have two distinct read surfaces. Pick the right one for the
85
85
  - **Personal memories** — written by `memory_add`. The `agent_id` records the writer but does not restrict reads; memories are scoped **per-user**, so any of that user's agents can read them via MCP, and no other user can.
86
86
  - **Team-shared Shared Knowledge** — arrives two ways:
87
87
  1. An agent calls `memory_promote` for exact, user-confirmed memory UUIDs, or the user promotes them from the Cloud UI's Learnings tab. Promotion moves the memories into shared knowledge, strips `user_id`, preserves originating `agent_id` as provenance, and evicts the private mem0 records.
88
- 2. The user uploads a file via Datapack → Actions → **Add Knowledge** in the Cloud UI. PDF/TXT/MD/JSON/CSV/XML/HTML up to 5MB (Free) / 50MB (Standard) / 60MB (Pro+) each.
88
+ 2. A file is uploaded — by an agent with `knowledgebase_upload_document`, or by the user via Datapack → Actions → **Add Knowledge** in the Cloud UI. PDF/TXT/MD/JSON/CSV/XML/HTML up to 5MB (Free) / 50MB (Standard) / 60MB (Pro+) each.
89
89
 
90
90
  Both show up in `knowledgebase_search`, tagged `metadata_filters.source: "memory"` vs other values so the agent can distinguish provenance in responses.
91
91
 
@@ -102,7 +102,7 @@ Call `context_search`. One call returns your personal memories (every agent, no
102
102
  There is no MCP-side "just put it in a database table" option — raw SQL access isn't part of the MCP surface. Workarounds:
103
103
 
104
104
  1. **Write a single narrative summary** as a memory: `"The Customer Complaints dataset has fields: Complaint ID (int), Date Submitted (date), Product (text), Sub-product (text), Issue (text)..."` — one call, not row-by-row.
105
- 2. **Suggest the user upload the file** via the Cloud UI's Add Knowledge flow if it's a document (PDF/TXT/MD/JSON/CSV/XML/HTML are supported).
105
+ 2. **Upload the file** with `knowledgebase_upload_document` if you have it and can POST its bytes to an external URL yourself, or suggest the user upload it via the Cloud UI's Add Knowledge flow, if it's a document (PDF/TXT/MD/JSON/CSV/XML/HTML are supported).
106
106
  3. **Never ingest CSV row-by-row** into memory — each row becomes a fragmented fact with lost context.
107
107
 
108
108
  ## Memory Storage: automatic, not proactive
@@ -108,14 +108,14 @@ Some things the Meko platform can do are not wired into the MCP tool surface tod
108
108
 
109
109
  | Capability | Where it lives | How to reach it |
110
110
  |---|---|---|
111
- | Create / list / delete **agents** within a datapack | `yugabyte/meko` API server | REST: `POST/GET/DELETE /datapacks/:datapack_id/agents` (`POST/GET/DELETE /agents/:agent` for targeted deletes), or the Meko control-plane UI |
112
- | Add / list / delete **knowledge-base sources** on a datapack | `yugabyte/meko` API server | REST: `POST/GET/DELETE /datapacks/:datapack_id/knowledge-bases`, plus `upload-url`, `upload-complete`, `create`, `status` subroutes. Also available in the Meko UI. |
113
- | Langfuse project-key generation | `yugabyte/meko` API server | REST: `POST /datapacks/:datapack_id/langfuse/project-keys` |
111
+ | Create / list / delete **agents** within a datapack | Meko API server | REST: `POST/GET/DELETE /datapacks/:datapack_id/agents` (`POST/GET/DELETE /agents/:agent` for targeted deletes), or the Meko control-plane UI |
112
+ | Add / list / delete **knowledge-base sources** on a datapack | Meko API server | REST: `POST/GET/DELETE /datapacks/:datapack_id/knowledge-bases`, plus `upload-url`, `upload-complete`, `create`, `status` subroutes. Also available in the Meko UI. |
113
+ | Langfuse project-key generation | Meko API server | REST: `POST /datapacks/:datapack_id/langfuse/project-keys` |
114
114
  | Account / billing / tier management | Meko UI only | Not available via MCP or public REST |
115
115
 
116
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.
117
117
 
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.
118
+ For a file already on disk or generated mid-conversation, and when you can send the file's bytes in an HTTPS POST to an external URL yourself (a sandboxed session may have the file but no outbound network), 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.
119
119
 
120
120
  ## Common Parameter Patterns
121
121
 
@@ -129,6 +129,8 @@ From `cloud.mekodata.ai`:
129
129
 
130
130
  If a *human* asks you "how do I add a document to my knowledge base?",
131
131
  point them at this flow. If *you* (the agent) have the file's bytes —
132
- already on disk, or generated mid-conversation — call
132
+ already on disk, or generated mid-conversation — and can send them in an
133
+ HTTPS POST to an external URL yourself, call
133
134
  `knowledgebase_upload_document` directly instead of describing the UI
134
- flow to yourself.
135
+ flow to yourself. A sandboxed session may have the file but no outbound
136
+ network; point the user at the UI then.
@@ -17,7 +17,7 @@ description: >-
17
17
  license: MIT
18
18
  metadata:
19
19
  author: Meko
20
- version: "2.11.0"
20
+ version: "2.12.0"
21
21
  tags: mcp, yugabytedb, tools, datapack, memory, conversation, rag, meko, desktop
22
22
  ---
23
23
 
@@ -146,7 +146,8 @@ Your job is not to decide fact-by-fact what to `memory_add`. It's to **post ever
146
146
  | User asks "what do we (as a team) know about X?" or about uploaded documents | `knowledgebase_search` (returns uploaded docs + promoted memories). |
147
147
  | User asks to share specific private memories with the team | Follow the `memory_promote` confirmation workflow below. Never promote based on a fuzzy search result or implied consent. |
148
148
  | User provides structured/tabular data (CSV, data dictionary) | If it's an actual file the user wants in the team's knowledge base, `knowledgebase_upload_document` accepts `.csv` directly (confirm first; see below). If it's pasted/described data with no file behind it, still don't `memory_add` it row-by-row — ask the user for it as a file, or point them at the UI's Add Knowledge upload. |
149
- | User wants to add documents to the team's knowledge base | Uploading adds the file to the datapack's shared knowledge base, which every member can read, so ask first: name the file and the datapack, say the team will see it, and upload only after the user says yes. If you (the agent) have the file's bytes and can make an HTTP POST from a shell (Claude Desktop usually can't; see `tools-cookbook.md`), call `knowledgebase_upload_document`. Otherwise point the user at the Cloud UI flow — Datapack → Actions → **Add Knowledge** (PDF/TXT/MD/JSON/CSV/XML/HTML, 5MB Free / 50MB Standard / 60MB Pro+ each, 10/batch). KB *lifecycle* (index/source management) is still UI-only. |
149
+ | User wants a file kept in Meko but didn't say how | KB (`knowledgebase_upload_document`) if its content should be searchable later — docs, manuals, policies, notes people will ask about. Artifact (`artifact_put`) to keep it exactly as-is and fetch it back whole — a generated report, a result CSV. If it is not clear whether the user needs semantic search or whole-file retrieval, ask. |
150
+ | User wants to add documents to the team's knowledge base | Uploading adds the file to the datapack's shared knowledge base, which every member can read, so ask first: name the file and the datapack, say the team will see it, and upload only after the user says yes. If you have the file's bytes — already on disk, or generated mid-conversation — and a shell or code tool, check that you can reach S3 first: run `curl -sS -o /dev/null -m 10 -w '%{http_code}' https://s3.amazonaws.com`; in Windows PowerShell, run `curl.exe` with `NUL` in place of `/dev/null`. A 2xx or 3xx status means yes (S3's root normally answers 307). `000`, a connection, DNS or proxy error, or any other status, such as a proxy's 403 or 407, means no. A pass shows outbound HTTPS to S3 works, not that the presigned bucket host does, so if the POST still fails, report it and stop. If it passes, call `knowledgebase_upload_document`. Claude Desktop usually has no shell; with no shell or code tool, or a failed check, don't call it, since an unused URL holds storage quota for up to 15 minutes. Otherwise point the user at the Cloud UI flow — Datapack → Actions → **Add Knowledge** (PDF/TXT/MD/JSON/CSV/XML/HTML, 5MB Free / 50MB Standard / 60MB Pro+ each, 10/batch). KB *lifecycle* (index/source management) is still UI-only. |
150
151
 
151
152
  ## Conversation capture — mechanics
152
153
 
@@ -269,7 +270,7 @@ The Cloud UI's Learnings tab remains an alternative user-driven path.
269
270
  3. **agent_id is multi-agent**: For Claude Desktop, use `agent_id="claude_desktop"` for normal writes and `agent_id="meko_agent"` for cross-project facts. Writes are attributed to `(datapack_id, user_id, agent_id)`. Personal memory reads and `conversation_list` span all of this user's agents; `conversation_get` remains agent-owned. `knowledgebase_search` ignores `agent_id` entirely. See `tools-agent-id-conventions.md`.
270
271
  4. **Personal memory vs. team-shared knowledge**: Un-promoted memories are scoped per-user — readable across all of that user's agents, but no other user can see them. With explicit confirmation, `memory_promote` moves exact memories into Shared Knowledge; the Cloud UI's Learnings tab is an alternative. Promoted content is visible to every datapack member. `knowledgebase_search` is the MCP read path for both Shared Knowledge and uploaded documents.
271
272
  5. **conversation_id IS the Langfuse trace ID**: every MCP tool call inside a conversation becomes a span under that trace. Observable in the Meko UI's Observe hub. See `tools-memory-vs-conversation.md`.
272
- 6. **Adding a file to a knowledge base**: the file becomes visible to every datapack member, so confirm with the user first. Call `knowledgebase_upload_document` only if you have the file's bytes and can POST them to the returned URL from a shell; Claude Desktop usually can't, so point the user at the UI instead. A human uploads via Datapack → Actions → **Add Knowledge** (PDF/TXT/MD/JSON/CSV/XML/HTML, 5MB Free / 50MB Standard / 60MB Pro+ each) instead. KB *lifecycle* (creating/renaming/deleting an index, adding a source manually) has no MCP tool and is still UI-only. See `tools-rag-workflow.md`.
273
+ 6. **Adding a file to a knowledge base**: the file becomes visible to every datapack member, so confirm with the user first. Call `knowledgebase_upload_document` only if you have the file's bytes and can send them in an HTTPS POST to an external URL yourself (a local shell, or a code tool with internet access); Claude Desktop usually can't, so point the user at the UI instead. Use the KB when the content should be searchable later, `artifact_put` to keep a file whole. A human uploads via Datapack → Actions → **Add Knowledge** (PDF/TXT/MD/JSON/CSV/XML/HTML, 5MB Free / 50MB Standard / 60MB Pro+ each) instead. KB *lifecycle* (creating/renaming/deleting an index, adding a source manually) has no MCP tool and is still UI-only. See `tools-rag-workflow.md`.
273
274
  7. **Manual conversation capture**: Claude Desktop does not have automatic capture hooks. Create and save conversations explicitly when sessions are valuable.
274
275
  8. **Memory latencies are not cheap**: observed on prod 2026-05-07 — `memory_add` 12-21s/call (calls OpenAI), `memory_search` 2-6s/call. Budget accordingly. On the Free tier, `conversation_add_message` and `context_search` are each capped at 1,000 lifetime calls, and `memory_search` and `knowledgebase_search` at 1,000. Per-turn capture draws down the `conversation_add_message` cap, so on Free skip trivial turns. When a call returns `free_tier_limit_reached`, report it and stop calling that tool; it can arrive as HTTP 403 but isn't an authentication problem.
275
276
  9. **Known limitations**: see `tools-known-limitations.md` before attempting write operations on tools that appear in the catalog but aren't available on Cloud.
@@ -295,7 +295,7 @@ Returns `{"document_id", "chunks_deleted", "s3_deleted"}` — if `s3_deleted` is
295
295
 
296
296
  ### knowledgebase_upload_document
297
297
 
298
- **When to use:** Get a file's bytes into the datapack's KB for later `knowledgebase_search` retrieval — a file already on disk, or one generated mid-conversation. This tool never touches the file's bytes itself; it only requests a presigned S3 URL. Meko registers the file once it lands, so there's no separate confirm call. **The caller POSTs the file to the returned `url` directly** — multipart/form-data, every key in `fields` as a form field (in order), then the file's bytes last as a field named `"file"`.
298
+ **When to use:** Get a file's bytes into the datapack's KB for later `knowledgebase_search` retrieval — a file already on disk, or one generated mid-conversation. Use it when the content should be searchable by meaning; to keep a file exactly as-is and fetch it back whole, use `artifact_put`. Before calling it, check that you can reach S3 first: run `curl -sS -o /dev/null -m 10 -w '%{http_code}' https://s3.amazonaws.com`; in Windows PowerShell, run `curl.exe` with `NUL` in place of `/dev/null`. A 2xx or 3xx status means yes (S3's root normally answers 307). `000`, a connection, DNS or proxy error, or any other status, such as a proxy's 403 or 407, means no. A pass shows outbound HTTPS to S3 works, not that the presigned bucket host does, so if the POST still fails, report it and stop. With no shell or code tool, or a failed check, don't call it — point the user at the UI. This tool never touches the file's bytes itself; it only requests a presigned S3 URL. Meko registers the file once it lands, so there's no separate confirm call. **The caller POSTs the file to the returned `url` directly** — multipart/form-data, every key in `fields` as a form field (in order), then the file's bytes last as a field named `"file"`.
299
299
 
300
300
  **Confirm first.** The file goes into the datapack's shared knowledge base, which every member can read. Before calling the tool, tell the user which file and datapack, say the team will see it, and wait for a yes.
301
301
 
@@ -464,7 +464,7 @@ Poll until `state` is `completed` or `failed` (or the pre-registration terminals
464
464
 
465
465
  ### artifact_put
466
466
 
467
- **When to use:** An agent has generated a file (report, CSV, PDF, code output, image) and wants to persist it in Meko for later retrieval or cross-agent sharing. Files < 1 MB are stored inline in the DB; files ≥ 1 MB go to the datapack's S3 bucket. Idempotent — uploading the same bytes twice returns the same `content_hash`.
467
+ **When to use:** An agent has generated a file (report, CSV, PDF, code output, image) and wants to persist it in Meko for later retrieval or cross-agent sharing. To make its content searchable by meaning instead, use `knowledgebase_upload_document`. Files < 1 MB are stored inline in the DB; files ≥ 1 MB go to the datapack's S3 bucket. Idempotent — uploading the same bytes twice returns the same `content_hash`.
468
468
 
469
469
  ```
470
470
  artifact_put(
@@ -596,4 +596,4 @@ Agent and KB lifecycle does not ship as MCP tools. If the user asks to create/li
596
596
  - REST: `POST/GET/DELETE /datapacks/:datapack_id/agents` (agents), `POST/GET/DELETE /datapacks/:datapack_id/knowledge-bases` (KB sources).
597
597
  - Or the Meko Cloud UI at `cloud.mekodata.ai` → Datapacks → agents / knowledge-bases.
598
598
 
599
- Uploading a *file* into an existing KB is not part of that lifecycle restriction — use `knowledgebase_upload_document` (see above) rather than the UI for anything driven from MCP. Agents querying a populated index use `knowledgebase_search`, and can remove a single uploaded file with `knowledgebase_delete_document` — see `tools-rag-workflow.md`.
599
+ Uploading a *file* into an existing KB is not part of that lifecycle restriction — use `knowledgebase_upload_document` (see above) rather than the UI for anything driven from MCP, when you can POST the file's bytes yourself. Agents querying a populated index use `knowledgebase_search`, and can remove a single uploaded file with `knowledgebase_delete_document` — see `tools-rag-workflow.md`.
@@ -18,7 +18,7 @@ The one exception is a single uploaded **file**: `knowledgebase_delete_document`
18
18
 
19
19
  Manually creating a vector index, adding a source to an existing one, or polling raw build status is not exposed via MCP. Use the Cloud UI for those (Datapack → Actions → Add Knowledge).
20
20
 
21
- **Adding a file** is a registered MCP tool: call `knowledgebase_upload_document` directly (accepted extensions PDF/TXT/MD/JSON/CSV/XML/HTML, up to 5 MB Free / 50 MB Standard / 60 MB Pro+ per file) to get a presigned URL, then POST the bytes to it yourself; Meko registers and indexes the file once it lands, so no separate index-management call is needed. Confirm with the user first, because every datapack member can read the knowledge base. The Cloud UI's **Add Knowledge** flow (10 files per batch) remains the human-facing path for the same upload.
21
+ **Adding a file** is a registered MCP tool: if you can send the file's bytes in an HTTPS POST to an external URL yourself (a local shell, or a code tool with internet access), call `knowledgebase_upload_document` directly (accepted extensions PDF/TXT/MD/JSON/CSV/XML/HTML, up to 5 MB Free / 50 MB Standard / 60 MB Pro+ per file) to get a presigned URL, then POST the bytes to it yourself; Meko registers and indexes the file once it lands, so no separate index-management call is needed. Confirm with the user first, because every datapack member can read the knowledge base. The Cloud UI's **Add Knowledge** flow (10 files per batch) remains the human-facing path for the same upload.
22
22
 
23
23
  To **query** a built index, use `knowledgebase_search` — it returns both uploaded documents and memories the user promoted from the Learnings tab.
24
24
 
@@ -43,7 +43,7 @@ It works **poorly** for:
43
43
  conversation_id="<session conversation_id>",
44
44
  text="Customer Complaints dataset has fields: Complaint ID (int PK), Date Submitted (date), Product (text, e.g. Mortgage/Credit Card), Sub-product (text), Issue (text), Company Response (text), State (text, 2-letter code), ZIP Code (text)")
45
45
  ```
46
- 2. **Upload the file itself to the knowledge base** with `knowledgebase_upload_document` — `.csv`, `.json` and the other accepted types go in as they are — and query it with `knowledgebase_search`. (The Cloud UI's **Add Knowledge** flow does the same for a person.)
46
+ 2. **Upload the file itself to the knowledge base** with `knowledgebase_upload_document`, if you can POST its bytes yourself — `.csv`, `.json` and the other accepted types go in as they are — and query it with `knowledgebase_search`. (The Cloud UI's **Add Knowledge** flow does the same for a person.)
47
47
 
48
48
  Never ingest CSV row-by-row into memory — each row becomes a fragmented fact with lost context.
49
49
 
@@ -85,7 +85,7 @@ One `memory_search` already spans this user's memories across all agents; no per
85
85
  - **Personal memories** — written by `memory_add`. The `agent_id` records the writer but does not restrict reads; memories are scoped per-user, so any of that user's agents can read them and no other user can.
86
86
  - **Team-shared Shared Knowledge** — arrives two ways:
87
87
  1. An agent calls `memory_promote` for exact, user-confirmed memory UUIDs, or the user promotes them from the Cloud UI's Learnings tab. Promotion moves the memories into shared knowledge, strips `user_id`, preserves originating `agent_id` as provenance, and evicts the private mem0 records.
88
- 2. The user uploads a file via Datapack → Actions → **Add Knowledge** in the Cloud UI. PDF/TXT/MD/JSON/CSV/XML/HTML up to 5MB (Free) / 50MB (Standard) / 60MB (Pro+) each.
88
+ 2. A file is uploaded — by an agent with `knowledgebase_upload_document`, or by the user via Datapack → Actions → **Add Knowledge** in the Cloud UI. PDF/TXT/MD/JSON/CSV/XML/HTML up to 5MB (Free) / 50MB (Standard) / 60MB (Pro+) each.
89
89
 
90
90
  Both show up in `knowledgebase_search`, tagged `metadata_filters.source: "memory"` vs other values so the agent can distinguish provenance in responses.
91
91
 
@@ -114,7 +114,7 @@ When you answer, be explicit about scope so the user knows why something is or i
114
114
  There is no MCP-side "just put it in a database table" option — raw SQL access isn't part of the MCP surface. Workarounds:
115
115
 
116
116
  1. **Write a single narrative summary** as a memory: `"The Customer Complaints dataset has fields: Complaint ID (int), Date Submitted (date), Product (text), Sub-product (text), Issue (text)..."` — one call, not row-by-row.
117
- 2. **Suggest the user upload the file** via the Cloud UI's Add Knowledge flow if it's a document (PDF/TXT/MD/JSON/CSV/XML/HTML are supported).
117
+ 2. **Upload the file** with `knowledgebase_upload_document` if you have it and can POST its bytes to an external URL yourself, or suggest the user upload it via the Cloud UI's Add Knowledge flow, if it's a document (PDF/TXT/MD/JSON/CSV/XML/HTML are supported).
118
118
  3. **Never ingest CSV row-by-row** into memory — each row becomes a fragmented fact with lost context.
119
119
 
120
120
  ## Memory Storage: capture the turn, don't proactively memory_add
@@ -108,14 +108,14 @@ Some things the Meko platform can do are not wired into the MCP tool surface tod
108
108
 
109
109
  | Capability | Where it lives | How to reach it |
110
110
  |---|---|---|
111
- | Create / list / delete **agents** within a datapack | `yugabyte/meko` API server | REST: `POST/GET/DELETE /datapacks/:datapack_id/agents` (`POST/GET/DELETE /agents/:agent` for targeted deletes), or the Meko control-plane UI |
112
- | Add / list / delete **knowledge-base sources** on a datapack | `yugabyte/meko` API server | REST: `POST/GET/DELETE /datapacks/:datapack_id/knowledge-bases`, plus `upload-url`, `upload-complete`, `create`, `status` subroutes. Also available in the Meko UI. |
113
- | Langfuse project-key generation | `yugabyte/meko` API server | REST: `POST /datapacks/:datapack_id/langfuse/project-keys` |
111
+ | Create / list / delete **agents** within a datapack | Meko API server | REST: `POST/GET/DELETE /datapacks/:datapack_id/agents` (`POST/GET/DELETE /agents/:agent` for targeted deletes), or the Meko control-plane UI |
112
+ | Add / list / delete **knowledge-base sources** on a datapack | Meko API server | REST: `POST/GET/DELETE /datapacks/:datapack_id/knowledge-bases`, plus `upload-url`, `upload-complete`, `create`, `status` subroutes. Also available in the Meko UI. |
113
+ | Langfuse project-key generation | Meko API server | REST: `POST /datapacks/:datapack_id/langfuse/project-keys` |
114
114
  | Account / billing / tier management | Meko UI only | Not available via MCP or public REST |
115
115
 
116
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.
117
117
 
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.
118
+ For a file already on disk or generated mid-conversation, and when you can send the file's bytes in an HTTPS POST to an external URL yourself (a local shell, or a code tool with internet access), 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.
119
119
 
120
120
  ## Common Parameter Patterns
121
121
 
@@ -129,6 +129,7 @@ From `cloud.mekodata.ai`:
129
129
 
130
130
  If a *human* asks you "how do I add a document to my knowledge base?",
131
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.
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.
@@ -10,6 +10,7 @@ import { askChoice, askMultiChoice, askText, askSecret, askYesNo, close as close
10
10
  import { banner, error, dim, info, success, warn } from '../lib/logger.mjs';
11
11
  import { BrowserLoginError, consoleUrlFor, loginAndCreateApiKey } from '../lib/browser-login.mjs';
12
12
  import {
13
+ isLocalUrl,
13
14
  isPackagedInstall,
14
15
  MEKO_PRODUCTION_CONSOLE_URL,
15
16
  selfInvocation,
@@ -86,6 +87,9 @@ function readVersion() {
86
87
  return pkg.version;
87
88
  }
88
89
 
90
+ // The docker-compose dev stack that --local points at. It takes no API key.
91
+ const LOCAL_DEV_MCP_URL = 'http://localhost:8000/mcp';
92
+
89
93
  const ALL_CLIENTS = ['claude-code', 'claude-desktop', 'cursor', 'codex', 'kiro', 'opencode'];
90
94
 
91
95
  function printHelp() {
@@ -139,11 +143,13 @@ Options:
139
143
  untrusted IDE workspaces and non-managed CLI agents fall
140
144
  back to agent-driven persistence.
141
145
  --url <url> MCP server URL (from cloud.mekodata.ai > Datapacks > Connect)
142
- --api-key <key> Use an existing Cloud Meko API key instead of signing in
143
- through the browser. Also read from $MEKO_API_KEY.
144
- Required when no terminal is attached (CI, scripts).
146
+ --api-key <key> Use an existing Meko API key (Cloud, or Meko Local's
147
+ client token) instead of signing in through the
148
+ browser. Also read from $MEKO_API_KEY for any URL
149
+ (ignored with --local). Required for Cloud when no
150
+ terminal is attached (CI, scripts).
145
151
  --no-browser Print the sign-in URL instead of opening a browser
146
- --local Use local Meko at http://localhost:8000/mcp
152
+ --local Use local Meko at http://localhost:8000/mcp (no API key)
147
153
  --scope <scope> Config scope: user or project (default: user)
148
154
  codex always installs at user scope (no project-level
149
155
  config); --scope=project degrades with a warning.
@@ -225,10 +231,6 @@ Migrate existing conversations and memories into Meko:
225
231
  console.log(text);
226
232
  }
227
233
 
228
- function isCloudUrl(url) {
229
- return url && !url.includes('localhost') && !url.includes('127.0.0.1');
230
- }
231
-
232
234
  /**
233
235
  * Sanitize a raw API key from any source (CLI flag, env var, askSecret prompt).
234
236
  *
@@ -479,7 +481,7 @@ async function main() {
479
481
  }
480
482
  let url = flags.url ?? process.env.MEKO_MCP_URL ?? null;
481
483
  let apiKey = normalizeApiKey(flags['api-key'] ?? process.env.MEKO_API_KEY);
482
- if (flags.local) { url = 'http://localhost:8000/mcp'; apiKey = null; }
484
+ if (flags.local) { url = LOCAL_DEV_MCP_URL; apiKey = null; }
483
485
 
484
486
  let parentRate;
485
487
  try { parentRate = parseRate(flags.rate); }
@@ -558,11 +560,17 @@ async function main() {
558
560
  // prompt. Someone answered that prompt, so sign-in runs even when stdin
559
561
  // is not a TTY (e.g. piped answers in the smoke harness).
560
562
  let choseBrowserSignIn = false;
563
+ // Set for --local, the interactive "Local Meko" choice, and a --yes run
564
+ // that reuses an existing LOCAL_DEV_MCP_URL entry: the docker-compose dev
565
+ // stack takes no key, so $MEKO_API_KEY is not read for it. An explicit
566
+ // --url, local or not, still reads it.
567
+ let keylessLocal = false;
561
568
 
562
569
  // --local shortcut
563
570
  if (flags.local) {
564
- url = 'http://localhost:8000/mcp';
571
+ url = LOCAL_DEV_MCP_URL;
565
572
  apiKey = null;
573
+ keylessLocal = true;
566
574
  }
567
575
 
568
576
  // Client picker — runs for any interactive invocation (including
@@ -628,6 +636,11 @@ async function main() {
628
636
  process.exit(1);
629
637
  }
630
638
  url = suggestedUrl.url;
639
+ // Reusing the dev stack an earlier --local install registered: keep it
640
+ // keyless, as --local would.
641
+ if (suggestedUrl.source === 'existing' && url === LOCAL_DEV_MCP_URL) {
642
+ keylessLocal = true;
643
+ }
631
644
  }
632
645
  }
633
646
 
@@ -689,8 +702,9 @@ async function main() {
689
702
  }
690
703
  }
691
704
  } else {
692
- url = 'http://localhost:8000/mcp';
705
+ url = LOCAL_DEV_MCP_URL;
693
706
  apiKey = null;
707
+ keylessLocal = true;
694
708
  }
695
709
 
696
710
  // Step 4 — skills
@@ -724,22 +738,32 @@ async function main() {
724
738
  process.exit(1);
725
739
  }
726
740
 
727
- // Validation — Cloud URL requires an API key.
728
- // Resolution order: --api-key (already in `apiKey`) → $MEKO_API_KEY →
729
- // browser sign-in, which creates a key through the server's
730
- // POST /api-keys. Sign-in also covers the cloud one-liner from the UI,
731
- // which only shows --url and --client. It is skipped when the full
732
- // interactive flow already took a pasted key (an empty paste is an
733
- // error, not a silent switch to the browser), and whenever stdin is not
734
- // a terminal, with or without --yes: a script or CI step may have nobody
735
- // at the browser, and a 5-minute callback wait would stall it. The one
736
- // exception is an explicit "Sign in with browser" answer at the prompt.
737
- if (isCloudUrl(url) && !apiKey) {
741
+ // API key resolution order: --api-key (already in `apiKey`) →
742
+ // $MEKO_API_KEY → browser sign-in (Cloud only).
743
+ //
744
+ // $MEKO_API_KEY applies to every URL, local ones included: Meko Local
745
+ // (the `meko` server, MCP on 127.0.0.1:7778) requires its client token,
746
+ // and AgentK passes that token only through the environment so it stays
747
+ // out of `ps`. Only the keyless dev stack (see `keylessLocal`) skips it.
748
+ if (!apiKey && !keylessLocal) {
738
749
  apiKey = normalizeApiKey(process.env.MEKO_API_KEY);
739
- let signInFailed = false;
740
750
  if (apiKey) {
741
751
  dim('Using API key from MEKO_API_KEY environment variable.');
742
- } else if (!promptedForApiKey && (process.stdin.isTTY || choseBrowserSignIn)) {
752
+ }
753
+ }
754
+
755
+ // Validation — Cloud URL requires an API key. Browser sign-in creates one
756
+ // through the server's POST /api-keys. Sign-in also covers the cloud
757
+ // one-liner from the UI, which only shows --url and --client. It is
758
+ // skipped when the full interactive flow already took a pasted key (an
759
+ // empty paste is an error, not a silent switch to the browser), and
760
+ // whenever stdin is not a terminal, with or without --yes: a script or CI
761
+ // step may have nobody at the browser, and a 5-minute callback wait would
762
+ // stall it. The one exception is an explicit "Sign in with browser" answer
763
+ // at the prompt. A local URL with no key installs without one.
764
+ if (!isLocalUrl(url) && !apiKey) {
765
+ let signInFailed = false;
766
+ if (!promptedForApiKey && (process.stdin.isTTY || choseBrowserSignIn)) {
743
767
  apiKey = await signInWithBrowser({
744
768
  url,
745
769
  noBrowser: flags['no-browser'],
@@ -38,6 +38,7 @@ import {
38
38
  import {
39
39
  getInstallerVersion,
40
40
  installStableTree,
41
+ isLocalUrl,
41
42
  isPackagedInstall,
42
43
  isDanglingSymlink,
43
44
  markerIsStale,
@@ -909,30 +910,6 @@ function buildCliRegisterCommand(name, url, bearerEnv) {
909
910
  return cmd;
910
911
  }
911
912
 
912
- /**
913
- * True for localhost / loopback MCP URLs (no cloud WAF in front). Parses the
914
- * URL and compares the HOSTNAME exactly — a substring test would wrongly
915
- * classify `https://localhost.example.com` (a real remote host) as local and
916
- * skip the WAF User-Agent header. Unparseable input is treated as non-local
917
- * (cloud), i.e. we err toward setting the header.
918
- */
919
- function isLocalUrl(url) {
920
- let host;
921
- try {
922
- host = new URL(String(url ?? "")).hostname;
923
- } catch {
924
- return false;
925
- }
926
- // `new URL` strips the brackets from IPv6 literals, so `[::1]` → `::1`.
927
- host = host.replace(/^\[|\]$/g, "").toLowerCase();
928
- return (
929
- host === "localhost" ||
930
- host === "::1" ||
931
- // Entire 127.0.0.0/8 block is loopback, not just 127.0.0.1.
932
- /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host)
933
- );
934
- }
935
-
936
913
  const HTTP_HEADER_USER_AGENT_KEY = "User-Agent";
937
914
 
938
915
  // Matches one `KEY = "VALUE"` (or single-quoted) pair. KEY may be
@@ -114,6 +114,31 @@ export function run(cmd) {
114
114
  return execSync(cmd, { encoding: "utf8", stdio: "pipe" }).trim();
115
115
  }
116
116
 
117
+ /**
118
+ * True for localhost / loopback MCP URLs (no cloud WAF in front, no browser
119
+ * sign-in). Parses the URL and compares the HOSTNAME exactly — a substring
120
+ * test would wrongly classify `https://localhost.example.com` (a real remote
121
+ * host) as local and `http://[::1]/mcp` as cloud. Unparseable input is
122
+ * treated as non-local (cloud): Codex errs toward setting the WAF User-Agent
123
+ * header, and the installer toward requiring an API key.
124
+ */
125
+ export function isLocalUrl(url) {
126
+ let host;
127
+ try {
128
+ host = new URL(String(url ?? "")).hostname;
129
+ } catch {
130
+ return false;
131
+ }
132
+ // `new URL` strips the brackets from IPv6 literals, so `[::1]` → `::1`.
133
+ host = host.replace(/^\[|\]$/g, "").toLowerCase();
134
+ return (
135
+ host === "localhost" ||
136
+ host === "::1" ||
137
+ // Entire 127.0.0.0/8 block is loopback, not just 127.0.0.1.
138
+ /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host)
139
+ );
140
+ }
141
+
117
142
  /**
118
143
  * @param {string|null|undefined} apiKey
119
144
  * @returns {string}
package/lib/migrate.mjs CHANGED
@@ -20,7 +20,7 @@ import * as progress from "./migrate/progress.mjs";
20
20
  import { datapackKeyFromUrl } from "./migrate/id.mjs";
21
21
  import { resolveDestinationDatapack } from "./migrate/datapack.mjs";
22
22
  import { McpClient } from "./migrate/mcp-client.mjs";
23
- import { selfInvocation } from "./clients/common.mjs";
23
+ import { isLocalUrl, selfInvocation } from "./clients/common.mjs";
24
24
  import { askYesNo } from "./prompt.mjs";
25
25
 
26
26
  function home() {
@@ -764,7 +764,3 @@ export async function stopMigration() {
764
764
  message: `Force-killed pid=${holder.pid} (run-id=${holder.runId}).`,
765
765
  };
766
766
  }
767
-
768
- function isLocalUrl(url) {
769
- return /localhost|127\.0\.0\.1/.test(url ?? "");
770
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yugabytedb/meko-mcp",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "Installer for Meko across Claude, Cursor, Codex, and Kiro clients",
5
5
  "type": "module",
6
6
  "bin": {