Skip to content

fix: QMD 1.1+ mcporter compatibility with legacy fallback [AI-assisted]#54728

Merged
vincentkoc merged 12 commits intoopenclaw:mainfrom
armanddp:fix/qmd-2.0-mcporter-compat
Mar 29, 2026
Merged

fix: QMD 1.1+ mcporter compatibility with legacy fallback [AI-assisted]#54728
vincentkoc merged 12 commits intoopenclaw:mainfrom
armanddp:fix/qmd-2.0-mcporter-compat

Conversation

@armanddp
Copy link
Copy Markdown
Contributor

@armanddp armanddp commented Mar 25, 2026

QMD 1.1.0 (February 20, 2026) unified all search modes under a single 'query' MCP tool that accepts a searches array with typed sub-queries (lex, vec, hyde), replacing the old search, vector_search, and deep_search tool names.

This commit updates the mcporter bridge in qmd-manager.ts to:

  • Default to the QMD 1.1.0 'query' tool with {searches: [...]} format
  • Auto-detect QMD version on first call and cache the result
  • Fall back to v1 tool names (search/vector_search/deep_search) with the old {query, limit, minScore} format if 'query' is not found
  • Log a warning when falling back to v1 for visibility

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 json falls 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

  • Problem: When memory.qmd.mcporter.enabled: true with 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.
  • Why it matters: Any user who upgrades QMD to 1.5+ while using mcporter mode gets completely broken memory search with no error visible to the agent — results are silently empty.
  • What changed: runQmdSearchViaMcporter now defaults to the v2 query tool 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.
  • What did NOT change (scope boundary): The direct CLI path (qmd search/qmd query) is unaffected — this only touches the mcporter bridge. No config schema changes. No new dependencies.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related: mcporter --output json bug in steipete/mcporter (output-utils.js printCallOutput() falls through to util.inspect instead of JSON.stringify)
  • This PR fixes a bug or regression

Root Cause / Regression History (if applicable)

  • Root cause: QMD 1.1.0 (February 20, 2026) removed the search, vector_search, and deep_search MCP tools and replaced them with a single query tool that uses a different args schema (searches array with typed sub-queries instead of a flat query string).
  • Missing detection / guardrail: No version negotiation or tool availability check was performed before calling QMD MCP tools. The error from mcporter was caught by the generic error handler which logs a warning but the empty result propagates silently.
  • Prior context: The tool name mapping was added when QMD 1.x was the only version. The mapping logic at line 766 (qmdSearchCommand === "search" ? "search" : ...) was correct for QMD 1.x.
  • Why this regressed now: QMD 1.1.0 shipped a breaking MCP interface change. OpenClaw's mcporter bridge was not updated to match.
  • If unknown, what was ruled out: N/A — root cause fully identified.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/memory/qmd-manager.test.ts
  • Scenario the test should lock in: When mcporter returns "Tool deep_search not found", the manager should retry with the v2 query tool and {searches: [...]} args format. When query tool succeeds, subsequent calls should skip v1 tool names entirely.
  • Why this is the smallest reliable guardrail: A unit test mocking runMcporter can verify both the v2-first path and the v1-fallback path without needing a real QMD instance.
  • Existing test that already covers this (if any): qmd-manager.test.ts exists but does not cover mcporter tool name resolution.
  • If no new test is added, why not: Tests should be added — the version detection + fallback logic is the core of this fix. Leaving for a follow-up or reviewer guidance on test patterns used in this codebase.

User-visible / Behavior Changes

  • Users on QMD 1.1+ with memory.qmd.mcporter.enabled: true will now get working memory search results (previously silently empty).
  • Users on QMD 1.x will see one additional retry on the first memory search of each session (typically <500ms), then cached.
  • A new warning log appears when falling back to v1: "QMD MCP server does not expose the v2 'query' tool; falling back to v1 tool names".

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No — same mcporter CLI calls, different tool name/args
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: macOS 15.x (Apple Silicon, Mac Mini M1)
  • Runtime/container: Node.js 25.8.1, OpenClaw 2026.3.24
  • Model/provider: N/A (memory subsystem, not model-dependent)
  • Integration/channel: Discord (but affects all channels)
  • Relevant config: memory.qmd.mcporter.enabled: true, memory.qmd.searchMode: "query", mcporter 0.7.3 with QMD 1.1.0 daemon (keep-alive)

Steps

  1. Enable mcporter mode: memory.qmd.mcporter.enabled: true
  2. Have QMD 1.1+ running via mcporter daemon (lifecycle: "keep-alive")
  3. Trigger memory_search from any agent session

Expected

  • Results returned from QMD via mcporter with the v2 query tool

Actual (before fix)

  • memory_search returns { results: [], provider: "qmd" } — silently empty
  • Gateway log shows: mcporter/qmd failed: SyntaxError: Expected property name or '}' in JSON at position 4 (mcporter JSON bug) or MCP error -32602: Tool deep_search not found

Evidence

  • Before: memory_search returns { results: [], provider: "openai", fallback: { from: "qmd", reason: "Expected property name or '}' in JSON..." } } — falls back to OpenAI embeddings with no QMD results
  • After: memory_search returns { results: [{ path: "memory/2026-03-07.md", score: 0.55, ... }, ...], provider: "qmd" } — full hybrid search results via warm QMD daemon
  • Verified via mcporter call qmd.query --args '{"searches":[{"type":"lex","query":"test"}],"limit":3}' --output json returning valid structured results
  • Unit tests: 57/57 qmd-manager tests pass, 310/310 memory subsystem tests pass (3 skipped, pre-existing)

Human Verification (required)

  • Verified scenarios: QMD 1.1.0 with mcporter daemon (keep-alive), multiple memory_search calls across collections (memory + workspace + sessions), single-collection and multi-collection paths
  • Edge cases checked: Tool name caching across multiple searches in same session, mcporter daemon warm vs cold start, collection parameter passthrough
  • What you did NOT verify: QMD 1.x fallback path (no v1 instance available to test against — logic is straightforward but not live-verified), concurrent search races during version detection

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes — auto-detects QMD version and falls back to v1 if needed
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Set memory.qmd.mcporter.enabled: false to bypass mcporter and use direct CLI mode (which has its own tool resolution)
  • Files/config to restore: Only src/memory/qmd-manager.ts changed
  • Known bad symptoms reviewers should watch for: If version detection caches the wrong version (e.g., v1 on a v2 server due to transient error), memory search would fail until gateway restart. The cache is per-session (instance lifetime), so a restart clears it.

Risks and Mitigations

  • Risk: Version detection relies on error message matching ("not found" + "tool") which could match unrelated errors.

    • Mitigation: The check requires both substrings AND that the current tool is "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).

    • Mitigation: Acceptable latency. Cached after first call. QMD 1.x is legacy — most users will be on 2.0+.

@armanddp armanddp changed the title fix: QMD 2.0 mcporter compatibility with v1 fallback [AI-assisted] fix: QMD 2.0 mcporter compatibility with v1 fallback Mar 25, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 25, 2026

Greptile Summary

This PR fixes a silent memory search regression for users on QMD 1.1+ with memory.qmd.mcporter.enabled: true. QMD 1.1.0 unified search tools under a single query MCP tool with a searches array, replacing the old flat search/vector_search/deep_search tools — OpenClaw's mcporter bridge was never updated to match, causing empty results.

The implementation is solid: it defaults to the v2 query tool, caches the detected version per instance, and provides a fallback path to v1 tool names on first-call error detection. The multi-collection stale-tool bug identified in a prior review is correctly addressed by re-resolving effectiveTool at the top of runQmdSearchViaMcporter.

Key findings:

  • P1 — Race condition in fallback guard (src/memory/qmd-manager.ts:1399–1403): The this.qmdMcpToolVersion !== "v1" guard in the catch block is redundant (infinite recursion is already prevented by effectiveTool === "query") and causes a narrow but real failure: a second concurrent search that probes qmd.query while the first is already in-flight will have its error re-thrown rather than retried once the first call sets qmdMcpToolVersion = "v1". Removing that guard fixes both the redundancy and the race.
  • Tests are well-structuredcallCount assertion is present, v2-first and v1-fallback paths are covered, and the single-collection fixture correctly exercises the target code path.
  • v1 fallback path is not live-tested (acknowledged in the PR) — the fix is straightforward and the logic follows from the v2 path, but a future QMD 1.x integration test would be valuable.

Confidence Score: 4/5

  • Safe to merge after fixing the redundant qmdMcpToolVersion !== "v1" guard that can silently drop concurrent fallback retries.
  • The core fix is correct and well-tested. The previous reviewer concerns (multi-collection stale tool, callCount assertion) are addressed. One concrete logic bug remains: the qmdMcpToolVersion !== "v1" guard in the catch block is both redundant and introduces a narrow concurrent-search failure. It's a one-line fix.
  • src/memory/qmd-manager.ts — the catch block in runQmdSearchViaMcporter (lines 1397–1411)
Prompt To Fix All With AI
This 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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

@armanddp armanddp marked this pull request as draft March 25, 2026 22:10
@armanddp
Copy link
Copy Markdown
Contributor Author

Addressed all three review findings in 187c92e:

P1 — Multi-collection v1 fallback bug: Fixed by resolving effectiveTool at the top of runQmdSearchViaMcporter. When qmdMcpToolVersion is already "v1" and the incoming params.tool is stale "query", it immediately resolves to the correct v1 tool name — no catch-block needed. This makes the function self-healing for the loop in runMcporterAcrossCollections.

P2 — callCount not asserted: Added expect(callCount).toBe(2) — one v2 attempt + one v1 retry. Also fixes the no-unused-vars lint error from CI.

P2 — Global state reset: Removed the spurious daemonStart reset. Added a comment explaining that qmdMcpToolVersion is an instance field and each createManager() starts fresh.

All 59 qmd-manager tests pass.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

@armanddp armanddp changed the title fix: QMD 2.0 mcporter compatibility with v1 fallback fix: QMD 1.5+ mcporter compatibility with legacy fallback [AI-assisted] Mar 25, 2026
@armanddp
Copy link
Copy Markdown
Contributor Author

Addressed latest review feedback in 94a37de (docs) and 99d1584 (code):

Greptile P1 — Multi-collection v1 fallback: Already fixed in 187c92e via effectiveTool resolution.

Codex P1 — Same multi-collection issue: Already fixed (same commit).

Codex P2 — searchMode not respected in v2 queries: Fixed. Added buildV2Searches() helper that derives the searches array from searchCommand:

  • search[{type: "lex"}] only
  • vsearch[{type: "vec"}] only
  • query (default) → [{type: "lex"}, {type: "vec"}, {type: "hyde"}]

Version references: Corrected throughout — the breaking MCP change was QMD 1.5, not 2.0.

All 59 qmd-manager tests pass.

@armanddp armanddp changed the title fix: QMD 1.5+ mcporter compatibility with legacy fallback [AI-assisted] fix: QMD 1.1+ mcporter compatibility with legacy fallback [AI-assisted] Mar 25, 2026
@armanddp armanddp marked this pull request as ready for review March 25, 2026 22:40
armanddp added a commit to armanddp/mcporter that referenced this pull request Mar 25, 2026
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).
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

@armanddp
Copy link
Copy Markdown
Contributor Author

Addressed latest round of feedback in a198bd4:

Greptile P1 — Redundant guard / concurrent-search race: Removed qmdMcpToolVersion !== "v1" from the catch block. As Greptile noted, effectiveTool === "query" alone prevents infinite retry (the recursive call passes a v1 tool name). The extra guard was causing concurrent searches to fail when the second in-flight call hit the catch after the first set the version.

Codex P2 — Overly broad error matching: Tightened isToolNotFoundError from includes("not found") && includes("tool") to a regex requiring Tool within 40 chars of not found with no sentence boundary between them. Prevents false-positives from user query text in the mcporter args.

All 59 qmd-manager tests pass.

@armanddp
Copy link
Copy Markdown
Contributor Author

Pushed 42486a2 addressing Claude Code review feedback:

  1. Type safety: Restored union type "query" | "search" | "vector_search" | "deep_search" for the tool param instead of bare string
  2. minScore docs: Added comment explaining intentional omission — QMD 1.1+ uses its own reranking pipeline without a minScore parameter
  3. Exhaustive switch: buildV2Searches now has explicit case "query": and case undefined: before the default branch

Other review findings (race condition on markQmdV2 is benign/idempotent, isToolNotFoundError regex is significantly tighter than the original includes checks) were noted but don't require code changes.

All 59 qmd-manager tests pass.

@vincentkoc vincentkoc self-assigned this Mar 29, 2026
armanddp and others added 10 commits March 30, 2026 07:48
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)
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

@vincentkoc vincentkoc force-pushed the fix/qmd-2.0-mcporter-compat branch from 9435c30 to 124fbf4 Compare March 29, 2026 23:05
@vincentkoc vincentkoc merged commit b888741 into openclaw:main Mar 29, 2026
8 checks passed
pritchie pushed a commit to pritchie/openclaw that referenced this pull request Mar 30, 2026
…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]>
alexjiang1 pushed a commit to alexjiang1/openclaw that referenced this pull request Mar 31, 2026
…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]>
livingghost pushed a commit to livingghost/openclaw that referenced this pull request Mar 31, 2026
…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]>
pgondhi987 pushed a commit to pgondhi987/openclaw that referenced this pull request Mar 31, 2026
…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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants