fix: QMD 1.1+ mcporter compatibility with legacy fallback [AI-assisted]#54728
fix: QMD 1.1+ mcporter compatibility with legacy fallback [AI-assisted]#54728vincentkoc merged 12 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR fixes a silent memory search regression for users on QMD 1.1+ with The implementation is solid: it defaults to the v2 Key findings:
Confidence Score: 4/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/memory/qmd-manager.ts
Line: 1399-1403
Comment:
**Redundant guard causes concurrent-search failure**
The `this.qmdMcpToolVersion !== "v1"` guard in the catch block is meant to prevent an infinite retry loop, but that's already fully prevented by the `effectiveTool === "query"` guard: the recursive retry call passes a v1 tool name (e.g. `"deep_search"`), so `effectiveTool` on the next entry will never be `"query"`, and this branch can never be re-entered.
The redundant guard introduces a race condition: if two searches are in-flight simultaneously and both issue a `qmd.query` call while `qmdMcpToolVersion === null`, the first to catch the error sets `qmdMcpToolVersion = "v1"` and retries successfully. The second then hits the catch block with `this.qmdMcpToolVersion === "v1"`, so the guard evaluates to `false` and the error is **re-thrown** to the caller instead of being retried.
A restart clears the cache, but the caller gets an unexplained failure for that request.
```suggestion
if (
effectiveTool === "query" &&
this.isToolNotFoundError(err)
) {
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (2): Last reviewed commit: "Merge branch 'main' into fix/qmd-2.0-mcp..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f33a5d83e8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Addressed all three review findings in 187c92e: P1 — Multi-collection v1 fallback bug: Fixed by resolving P2 — callCount not asserted: Added P2 — Global state reset: Removed the spurious All 59 qmd-manager tests pass. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 187c92e7c7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Addressed latest review feedback in 94a37de (docs) and 99d1584 (code): Greptile P1 — Multi-collection v1 fallback: Already fixed in 187c92e via Codex P1 — Same multi-collection issue: Already fixed (same commit). Codex P2 — searchMode not respected in v2 queries: Fixed. Added
Version references: Corrected throughout — the breaking MCP change was QMD 1.5, not 2.0. All 59 qmd-manager tests pass. |
When `--output json` is requested and `wrapped.json()` returns null (e.g. MCP tool returns content that doesn't contain a json-typed entry), `printRaw()` was called which uses `util.inspect()` — producing output that looks like JS object notation but isn't valid JSON. This breaks downstream JSON parsers like OpenClaw's mcporter bridge, which receives `util.inspect` output and fails with: `Expected property name or '}' in JSON at position 2` Fix: when the requested format is `json`, `printRaw()` now uses `JSON.stringify()` instead of `util.inspect()`, guaranteeing the output is always valid JSON regardless of whether `wrapped.json()` found a structured JSON candidate. Companion to openclaw/openclaw#54728 (QMD 1.1+ mcporter compat).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 72f8ffe622
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Addressed latest round of feedback in a198bd4: Greptile P1 — Redundant guard / concurrent-search race: Removed Codex P2 — Overly broad error matching: Tightened All 59 qmd-manager tests pass. |
|
Pushed 42486a2 addressing Claude Code review feedback:
Other review findings (race condition on markQmdV2 is benign/idempotent, isToolNotFoundError regex is significantly tighter than the original All 59 qmd-manager tests pass. |
QMD 2.0 unified all search modes under a single 'query' MCP tool
with typed sub-queries, replacing search/vector_search/deep_search.
- Default to QMD 2.0 'query' tool with {searches: [...]} format
- Auto-detect version on first call and cache for the session
- Fall back to v1 tool names if 'query' is not found
- Backwards compatible: v1 users get one retry then cached
AI-assisted: Built with Claude (Opus 4.6) via OpenClaw. Fully tested
against QMD 2.0 with mcporter 0.7.3 daemon. v1 fallback path not
live-tested (no v1 instance available). Code reviewed and understood.
- Verify mcporter bridge uses 'query' tool with {searches: [...]} format (v2)
- Verify fallback to 'deep_search' with {query, limit} format when v2 not found
- Verify v1 fallback logs a warning for visibility
…eanup - Fix multi-collection v1 fallback: resolve effectiveTool at the top of runQmdSearchViaMcporter so stale 'query' tool names from the loop are corrected once qmdMcpToolVersion is set to 'v1' - Assert callCount in v1 fallback test (one v2 attempt + one v1 retry) - Remove spurious global state reset (qmdMcpToolVersion is per-instance)
The MCP tool removal (search/vector_search/deep_search → query) happened in QMD 1.5, not 2.0. QMD 2.0 was the SDK/library refactor. Updated all comments, test names, and documentation to reflect this.
When searchMode is 'search' (BM25), only send lex sub-query. When 'vsearch', only send vec. Default 'query' sends all three (lex + vec + hyde) for full hybrid search with reranking. Previously all three sub-queries were always sent regardless of the configured searchMode, which could trigger unnecessary vector embedding and HyDE LLM work on setups explicitly requesting lexical-only search. Addresses Codex P2 review feedback.
…he tools Per CHANGELOG.md, MCP tools search/vector_search/deep_search were removed in QMD 1.1.0 (2026-02-20), not 1.5 (which doesn't exist). Versions go 1.0.7 → 1.1.0 → 1.1.1 → 1.1.2 → 1.1.5 → 1.1.6 → 2.0.0.
1. Remove qmdMcpToolVersion !== 'v1' guard from catch block. It's redundant (effectiveTool === 'query' already prevents infinite retry) and introduces a race condition: concurrent searches that both probe with 'query' while version is null would fail after the first sets version to 'v1'. 2. Tighten isToolNotFoundError regex to require 'Tool' near 'not found' (within 40 chars, no sentence boundary). Prevents false-positives when user query text in the mcporter args contains both words. Addresses Greptile P1 (concurrent-search race) and Codex P2 (overly broad error matching).
…t switch - Restore type union for tool param instead of bare string - Add comment explaining minScore omission for v2 (QMD 1.1+ uses its own reranking pipeline, no minScore parameter) - Make buildV2Searches switch exhaustive with explicit case 'query'
- Run oxfmt to fix formatting issues - Add union return type to resolveQmdMcpTool() and runMcporterAcrossCollections() tool param to satisfy tsc (string was not assignable to the union type)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 41a7528159
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
41a7528 to
1ac2231
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fd544a8486
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
9435c30 to
124fbf4
Compare
…d] (openclaw#54728) * fix: QMD 2.0 mcporter compatibility with v1 fallback [AI-assisted] QMD 2.0 unified all search modes under a single 'query' MCP tool with typed sub-queries, replacing search/vector_search/deep_search. - Default to QMD 2.0 'query' tool with {searches: [...]} format - Auto-detect version on first call and cache for the session - Fall back to v1 tool names if 'query' is not found - Backwards compatible: v1 users get one retry then cached AI-assisted: Built with Claude (Opus 4.6) via OpenClaw. Fully tested against QMD 2.0 with mcporter 0.7.3 daemon. v1 fallback path not live-tested (no v1 instance available). Code reviewed and understood. * test: add QMD v2 tool format and v1 fallback tests - Verify mcporter bridge uses 'query' tool with {searches: [...]} format (v2) - Verify fallback to 'deep_search' with {query, limit} format when v2 not found - Verify v1 fallback logs a warning for visibility * fix: address review feedback — multi-collection v1 fallback + test cleanup - Fix multi-collection v1 fallback: resolve effectiveTool at the top of runQmdSearchViaMcporter so stale 'query' tool names from the loop are corrected once qmdMcpToolVersion is set to 'v1' - Assert callCount in v1 fallback test (one v2 attempt + one v1 retry) - Remove spurious global state reset (qmdMcpToolVersion is per-instance) * docs: correct version references — breaking change was QMD 1.5, not 2.0 The MCP tool removal (search/vector_search/deep_search → query) happened in QMD 1.5, not 2.0. QMD 2.0 was the SDK/library refactor. Updated all comments, test names, and documentation to reflect this. * fix: respect searchMode when building v2 mcporter queries When searchMode is 'search' (BM25), only send lex sub-query. When 'vsearch', only send vec. Default 'query' sends all three (lex + vec + hyde) for full hybrid search with reranking. Previously all three sub-queries were always sent regardless of the configured searchMode, which could trigger unnecessary vector embedding and HyDE LLM work on setups explicitly requesting lexical-only search. Addresses Codex P2 review feedback. * docs: correct to QMD 1.1.0 — that's the actual version that removed the tools Per CHANGELOG.md, MCP tools search/vector_search/deep_search were removed in QMD 1.1.0 (2026-02-20), not 1.5 (which doesn't exist). Versions go 1.0.7 → 1.1.0 → 1.1.1 → 1.1.2 → 1.1.5 → 1.1.6 → 2.0.0. * fix: remove redundant v1 guard (race condition) + tighten error matching 1. Remove qmdMcpToolVersion !== 'v1' guard from catch block. It's redundant (effectiveTool === 'query' already prevents infinite retry) and introduces a race condition: concurrent searches that both probe with 'query' while version is null would fail after the first sets version to 'v1'. 2. Tighten isToolNotFoundError regex to require 'Tool' near 'not found' (within 40 chars, no sentence boundary). Prevents false-positives when user query text in the mcporter args contains both words. Addresses Greptile P1 (concurrent-search race) and Codex P2 (overly broad error matching). * fix: address claude code review — type safety, minScore docs, explicit switch - Restore type union for tool param instead of bare string - Add comment explaining minScore omission for v2 (QMD 1.1+ uses its own reranking pipeline, no minScore parameter) - Make buildV2Searches switch exhaustive with explicit case 'query' * fix: resolve CI failures — oxfmt formatting + TypeScript type errors - Run oxfmt to fix formatting issues - Add union return type to resolveQmdMcpTool() and runMcporterAcrossCollections() tool param to satisfy tsc (string was not assignable to the union type) * fix(memory): align qmd query collection filters * fix(memory): narrow qmd missing-tool fallback detection * fix(memory): ignore qmd timeout text for v1 fallback --------- Co-authored-by: Vincent Koc <[email protected]>
…d] (openclaw#54728) * fix: QMD 2.0 mcporter compatibility with v1 fallback [AI-assisted] QMD 2.0 unified all search modes under a single 'query' MCP tool with typed sub-queries, replacing search/vector_search/deep_search. - Default to QMD 2.0 'query' tool with {searches: [...]} format - Auto-detect version on first call and cache for the session - Fall back to v1 tool names if 'query' is not found - Backwards compatible: v1 users get one retry then cached AI-assisted: Built with Claude (Opus 4.6) via OpenClaw. Fully tested against QMD 2.0 with mcporter 0.7.3 daemon. v1 fallback path not live-tested (no v1 instance available). Code reviewed and understood. * test: add QMD v2 tool format and v1 fallback tests - Verify mcporter bridge uses 'query' tool with {searches: [...]} format (v2) - Verify fallback to 'deep_search' with {query, limit} format when v2 not found - Verify v1 fallback logs a warning for visibility * fix: address review feedback — multi-collection v1 fallback + test cleanup - Fix multi-collection v1 fallback: resolve effectiveTool at the top of runQmdSearchViaMcporter so stale 'query' tool names from the loop are corrected once qmdMcpToolVersion is set to 'v1' - Assert callCount in v1 fallback test (one v2 attempt + one v1 retry) - Remove spurious global state reset (qmdMcpToolVersion is per-instance) * docs: correct version references — breaking change was QMD 1.5, not 2.0 The MCP tool removal (search/vector_search/deep_search → query) happened in QMD 1.5, not 2.0. QMD 2.0 was the SDK/library refactor. Updated all comments, test names, and documentation to reflect this. * fix: respect searchMode when building v2 mcporter queries When searchMode is 'search' (BM25), only send lex sub-query. When 'vsearch', only send vec. Default 'query' sends all three (lex + vec + hyde) for full hybrid search with reranking. Previously all three sub-queries were always sent regardless of the configured searchMode, which could trigger unnecessary vector embedding and HyDE LLM work on setups explicitly requesting lexical-only search. Addresses Codex P2 review feedback. * docs: correct to QMD 1.1.0 — that's the actual version that removed the tools Per CHANGELOG.md, MCP tools search/vector_search/deep_search were removed in QMD 1.1.0 (2026-02-20), not 1.5 (which doesn't exist). Versions go 1.0.7 → 1.1.0 → 1.1.1 → 1.1.2 → 1.1.5 → 1.1.6 → 2.0.0. * fix: remove redundant v1 guard (race condition) + tighten error matching 1. Remove qmdMcpToolVersion !== 'v1' guard from catch block. It's redundant (effectiveTool === 'query' already prevents infinite retry) and introduces a race condition: concurrent searches that both probe with 'query' while version is null would fail after the first sets version to 'v1'. 2. Tighten isToolNotFoundError regex to require 'Tool' near 'not found' (within 40 chars, no sentence boundary). Prevents false-positives when user query text in the mcporter args contains both words. Addresses Greptile P1 (concurrent-search race) and Codex P2 (overly broad error matching). * fix: address claude code review — type safety, minScore docs, explicit switch - Restore type union for tool param instead of bare string - Add comment explaining minScore omission for v2 (QMD 1.1+ uses its own reranking pipeline, no minScore parameter) - Make buildV2Searches switch exhaustive with explicit case 'query' * fix: resolve CI failures — oxfmt formatting + TypeScript type errors - Run oxfmt to fix formatting issues - Add union return type to resolveQmdMcpTool() and runMcporterAcrossCollections() tool param to satisfy tsc (string was not assignable to the union type) * fix(memory): align qmd query collection filters * fix(memory): narrow qmd missing-tool fallback detection * fix(memory): ignore qmd timeout text for v1 fallback --------- Co-authored-by: Vincent Koc <[email protected]>
…d] (openclaw#54728) * fix: QMD 2.0 mcporter compatibility with v1 fallback [AI-assisted] QMD 2.0 unified all search modes under a single 'query' MCP tool with typed sub-queries, replacing search/vector_search/deep_search. - Default to QMD 2.0 'query' tool with {searches: [...]} format - Auto-detect version on first call and cache for the session - Fall back to v1 tool names if 'query' is not found - Backwards compatible: v1 users get one retry then cached AI-assisted: Built with Claude (Opus 4.6) via OpenClaw. Fully tested against QMD 2.0 with mcporter 0.7.3 daemon. v1 fallback path not live-tested (no v1 instance available). Code reviewed and understood. * test: add QMD v2 tool format and v1 fallback tests - Verify mcporter bridge uses 'query' tool with {searches: [...]} format (v2) - Verify fallback to 'deep_search' with {query, limit} format when v2 not found - Verify v1 fallback logs a warning for visibility * fix: address review feedback — multi-collection v1 fallback + test cleanup - Fix multi-collection v1 fallback: resolve effectiveTool at the top of runQmdSearchViaMcporter so stale 'query' tool names from the loop are corrected once qmdMcpToolVersion is set to 'v1' - Assert callCount in v1 fallback test (one v2 attempt + one v1 retry) - Remove spurious global state reset (qmdMcpToolVersion is per-instance) * docs: correct version references — breaking change was QMD 1.5, not 2.0 The MCP tool removal (search/vector_search/deep_search → query) happened in QMD 1.5, not 2.0. QMD 2.0 was the SDK/library refactor. Updated all comments, test names, and documentation to reflect this. * fix: respect searchMode when building v2 mcporter queries When searchMode is 'search' (BM25), only send lex sub-query. When 'vsearch', only send vec. Default 'query' sends all three (lex + vec + hyde) for full hybrid search with reranking. Previously all three sub-queries were always sent regardless of the configured searchMode, which could trigger unnecessary vector embedding and HyDE LLM work on setups explicitly requesting lexical-only search. Addresses Codex P2 review feedback. * docs: correct to QMD 1.1.0 — that's the actual version that removed the tools Per CHANGELOG.md, MCP tools search/vector_search/deep_search were removed in QMD 1.1.0 (2026-02-20), not 1.5 (which doesn't exist). Versions go 1.0.7 → 1.1.0 → 1.1.1 → 1.1.2 → 1.1.5 → 1.1.6 → 2.0.0. * fix: remove redundant v1 guard (race condition) + tighten error matching 1. Remove qmdMcpToolVersion !== 'v1' guard from catch block. It's redundant (effectiveTool === 'query' already prevents infinite retry) and introduces a race condition: concurrent searches that both probe with 'query' while version is null would fail after the first sets version to 'v1'. 2. Tighten isToolNotFoundError regex to require 'Tool' near 'not found' (within 40 chars, no sentence boundary). Prevents false-positives when user query text in the mcporter args contains both words. Addresses Greptile P1 (concurrent-search race) and Codex P2 (overly broad error matching). * fix: address claude code review — type safety, minScore docs, explicit switch - Restore type union for tool param instead of bare string - Add comment explaining minScore omission for v2 (QMD 1.1+ uses its own reranking pipeline, no minScore parameter) - Make buildV2Searches switch exhaustive with explicit case 'query' * fix: resolve CI failures — oxfmt formatting + TypeScript type errors - Run oxfmt to fix formatting issues - Add union return type to resolveQmdMcpTool() and runMcporterAcrossCollections() tool param to satisfy tsc (string was not assignable to the union type) * fix(memory): align qmd query collection filters * fix(memory): narrow qmd missing-tool fallback detection * fix(memory): ignore qmd timeout text for v1 fallback --------- Co-authored-by: Vincent Koc <[email protected]>
…d] (openclaw#54728) * fix: QMD 2.0 mcporter compatibility with v1 fallback [AI-assisted] QMD 2.0 unified all search modes under a single 'query' MCP tool with typed sub-queries, replacing search/vector_search/deep_search. - Default to QMD 2.0 'query' tool with {searches: [...]} format - Auto-detect version on first call and cache for the session - Fall back to v1 tool names if 'query' is not found - Backwards compatible: v1 users get one retry then cached AI-assisted: Built with Claude (Opus 4.6) via OpenClaw. Fully tested against QMD 2.0 with mcporter 0.7.3 daemon. v1 fallback path not live-tested (no v1 instance available). Code reviewed and understood. * test: add QMD v2 tool format and v1 fallback tests - Verify mcporter bridge uses 'query' tool with {searches: [...]} format (v2) - Verify fallback to 'deep_search' with {query, limit} format when v2 not found - Verify v1 fallback logs a warning for visibility * fix: address review feedback — multi-collection v1 fallback + test cleanup - Fix multi-collection v1 fallback: resolve effectiveTool at the top of runQmdSearchViaMcporter so stale 'query' tool names from the loop are corrected once qmdMcpToolVersion is set to 'v1' - Assert callCount in v1 fallback test (one v2 attempt + one v1 retry) - Remove spurious global state reset (qmdMcpToolVersion is per-instance) * docs: correct version references — breaking change was QMD 1.5, not 2.0 The MCP tool removal (search/vector_search/deep_search → query) happened in QMD 1.5, not 2.0. QMD 2.0 was the SDK/library refactor. Updated all comments, test names, and documentation to reflect this. * fix: respect searchMode when building v2 mcporter queries When searchMode is 'search' (BM25), only send lex sub-query. When 'vsearch', only send vec. Default 'query' sends all three (lex + vec + hyde) for full hybrid search with reranking. Previously all three sub-queries were always sent regardless of the configured searchMode, which could trigger unnecessary vector embedding and HyDE LLM work on setups explicitly requesting lexical-only search. Addresses Codex P2 review feedback. * docs: correct to QMD 1.1.0 — that's the actual version that removed the tools Per CHANGELOG.md, MCP tools search/vector_search/deep_search were removed in QMD 1.1.0 (2026-02-20), not 1.5 (which doesn't exist). Versions go 1.0.7 → 1.1.0 → 1.1.1 → 1.1.2 → 1.1.5 → 1.1.6 → 2.0.0. * fix: remove redundant v1 guard (race condition) + tighten error matching 1. Remove qmdMcpToolVersion !== 'v1' guard from catch block. It's redundant (effectiveTool === 'query' already prevents infinite retry) and introduces a race condition: concurrent searches that both probe with 'query' while version is null would fail after the first sets version to 'v1'. 2. Tighten isToolNotFoundError regex to require 'Tool' near 'not found' (within 40 chars, no sentence boundary). Prevents false-positives when user query text in the mcporter args contains both words. Addresses Greptile P1 (concurrent-search race) and Codex P2 (overly broad error matching). * fix: address claude code review — type safety, minScore docs, explicit switch - Restore type union for tool param instead of bare string - Add comment explaining minScore omission for v2 (QMD 1.1+ uses its own reranking pipeline, no minScore parameter) - Make buildV2Searches switch exhaustive with explicit case 'query' * fix: resolve CI failures — oxfmt formatting + TypeScript type errors - Run oxfmt to fix formatting issues - Add union return type to resolveQmdMcpTool() and runMcporterAcrossCollections() tool param to satisfy tsc (string was not assignable to the union type) * fix(memory): align qmd query collection filters * fix(memory): narrow qmd missing-tool fallback detection * fix(memory): ignore qmd timeout text for v1 fallback --------- Co-authored-by: Vincent Koc <[email protected]>
QMD 1.1.0 (February 20, 2026) unified all search modes under a single 'query' MCP tool that accepts a
searchesarray with typed sub-queries (lex,vec,hyde), replacing the oldsearch,vector_search, anddeep_searchtool names.This commit updates the mcporter bridge in qmd-manager.ts to:
This ensures backwards compatibility: QMD 1.1+ users get the new interface immediately, while QMD 1.x users experience one retry on the first search before the v1 path is cached for the session.
Note: There is also a companion issue in mcporter (steipete/mcporter) where
--output jsonfalls through to util.inspect(depth: 2) when wrapped.json() returns null, producing invalid JSON output instead of proper JSON. See mcporter output-utils.js printCallOutput().Tested against QMD 2.0.1 with mcporter 0.7.3 daemon (keep-alive).
AI-assisted: Built with Claude (Opus 4.6) via OpenClaw. Fully tested against QMD 1.1.0 with mcporter 0.7.3 daemon. v1 fallback path not live-tested (no v1 instance available). Code reviewed and understood by contributor. All 310 memory subsystem tests pass.
Summary
memory.qmd.mcporter.enabled: truewith QMD 1.1.0, memory search silently returns empty results because OpenClaw calls MCP tools (deep_search,search,vector_search) that no longer exist in QMD 1.1.0, and sends args in the old{query, limit, minScore}format instead of the new{searches: [{type, query}], limit}format.runQmdSearchViaMcporternow defaults to the v2querytool with the new args format. On first call, if the tool is not found, it falls back to v1 tool names and caches the detected version for the session.qmd search/qmd query) is unaffected — this only touches the mcporter bridge. No config schema changes. No new dependencies.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
--output jsonbug insteipete/mcporter(output-utils.jsprintCallOutput()falls through toutil.inspectinstead ofJSON.stringify)Root Cause / Regression History (if applicable)
search,vector_search, anddeep_searchMCP tools and replaced them with a singlequerytool that uses a different args schema (searchesarray with typed sub-queries instead of a flatquerystring).qmdSearchCommand === "search" ? "search" : ...) was correct for QMD 1.x.Regression Test Plan (if applicable)
src/memory/qmd-manager.test.tsquerytool and{searches: [...]}args format. Whenquerytool succeeds, subsequent calls should skip v1 tool names entirely.runMcportercan verify both the v2-first path and the v1-fallback path without needing a real QMD instance.qmd-manager.test.tsexists but does not cover mcporter tool name resolution.User-visible / Behavior Changes
memory.qmd.mcporter.enabled: truewill now get working memory search results (previously silently empty)."QMD MCP server does not expose the v2 'query' tool; falling back to v1 tool names".Security Impact (required)
NoNoNo— same mcporter CLI calls, different tool name/argsNoNoRepro + Verification
Environment
memory.qmd.mcporter.enabled: true,memory.qmd.searchMode: "query", mcporter 0.7.3 with QMD 1.1.0 daemon (keep-alive)Steps
memory.qmd.mcporter.enabled: truelifecycle: "keep-alive")memory_searchfrom any agent sessionExpected
querytoolActual (before fix)
memory_searchreturns{ results: [], provider: "qmd" }— silently emptymcporter/qmd failed: SyntaxError: Expected property name or '}' in JSON at position 4(mcporter JSON bug) orMCP error -32602: Tool deep_search not foundEvidence
memory_searchreturns{ results: [], provider: "openai", fallback: { from: "qmd", reason: "Expected property name or '}' in JSON..." } }— falls back to OpenAI embeddings with no QMD resultsmemory_searchreturns{ results: [{ path: "memory/2026-03-07.md", score: 0.55, ... }, ...], provider: "qmd" }— full hybrid search results via warm QMD daemonmcporter call qmd.query --args '{"searches":[{"type":"lex","query":"test"}],"limit":3}' --output jsonreturning valid structured resultsHuman Verification (required)
Review Conversations
Compatibility / Migration
Yes— auto-detects QMD version and falls back to v1 if neededNoNoFailure Recovery (if this breaks)
memory.qmd.mcporter.enabled: falseto bypass mcporter and use direct CLI mode (which has its own tool resolution)src/memory/qmd-manager.tschangedRisks and Mitigations
Risk: Version detection relies on error message matching (
"not found"+"tool") which could match unrelated errors."query"AND that we haven't already fallen back to v1. False positive would cause one unnecessary retry with v1 names, which would then also fail and propagate the real error.Risk: First search on QMD 1.x incurs one extra round-trip (~200-500ms).