From 8e68c56915db63978d1c1624c9f4a3cec9acda17 Mon Sep 17 00:00:00 2001 From: Adarsh Tiwari <103358618+eltonjohnfanboy@users.noreply.github.com> Date: Thu, 2 Jul 2026 10:06:13 +0000 Subject: [PATCH] Add backward compatibility for search_docs_tool tool --- .../qiskit_docs_mcp_server/data_fetcher.py | 40 ++++++++++++------- .../src/qiskit_docs_mcp_server/server.py | 5 ++- .../tests/test_data_fetcher.py | 22 ++++++++++ 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/qiskit-docs-mcp-server/src/qiskit_docs_mcp_server/data_fetcher.py b/qiskit-docs-mcp-server/src/qiskit_docs_mcp_server/data_fetcher.py index d8ba69a..20134f3 100644 --- a/qiskit-docs-mcp-server/src/qiskit_docs_mcp_server/data_fetcher.py +++ b/qiskit-docs-mcp-server/src/qiskit_docs_mcp_server/data_fetcher.py @@ -324,21 +324,31 @@ def _normalize_result_url(url_val: Any, base: str) -> Any: def _build_result_entry(item: dict[str, Any], query: str, detail: str, base: str) -> dict[str, Any]: - """Build a single search-result entry with a snippet (or full text).""" - entry: dict[str, Any] = { - key: item[key] for key in _RESULT_META_FIELDS if item.get(key) is not None - } + """Build a single search-result entry. + + In ``snippet`` mode (default) the entry is trimmed to a small set of + navigation fields plus a short ``snippet`` — token-economical for agent use. + In ``full`` mode the original upstream fields are preserved (backwards + compatible with the pre-snippet response) and the full page body is returned + as ``text``. Either way the upstream ``item`` is not mutated (dict/comprehension + copies), so cached search responses stay intact. + """ + if detail == "full": + # Preserve all upstream fields for backwards compatibility. + entry: dict[str, Any] = dict(item) + raw_text = item.get("text") + if isinstance(raw_text, str): + entry["text"] = _strip_html_tags(raw_text) + else: + entry = {key: item[key] for key in _RESULT_META_FIELDS if item.get(key) is not None} + raw_text = item.get("text") + clean_text = _strip_html_tags(raw_text) if isinstance(raw_text, str) else "" + entry["snippet"] = _make_snippet(clean_text, query) + if isinstance(entry.get("title"), str): entry["title"] = _strip_html_tags(entry["title"]) if "url" in entry: entry["url"] = _normalize_result_url(entry["url"], base) - - raw_text = item.get("text") - clean_text = _strip_html_tags(raw_text) if isinstance(raw_text, str) else "" - if detail == "full": - entry["text"] = clean_text - else: - entry["snippet"] = _make_snippet(clean_text, query) return entry @@ -360,9 +370,11 @@ async def search_qiskit_docs( 'all', 'documentation', 'api', 'learning', 'tutorials' top_k: Maximum number of results to return (clamped to [1, MAX_SEARCH_TOP_K]). Defaults to DEFAULT_SEARCH_TOP_K. - detail: 'snippet' (default) returns a short excerpt per result; - 'full' returns each result's full page body (heavier — prefer - get_page for full content). + detail: 'snippet' (default) returns a short excerpt per result and a + trimmed field set; 'full' returns each result's full page body as + 'text' and preserves the original upstream fields (backwards + compatible with the pre-snippet response — heavier, so prefer + get_page for a single page's full content). Returns: Search results with matching entries, counts, and metadata. diff --git a/qiskit-docs-mcp-server/src/qiskit_docs_mcp_server/server.py b/qiskit-docs-mcp-server/src/qiskit_docs_mcp_server/server.py index 94c75dd..a7f2363 100644 --- a/qiskit-docs-mcp-server/src/qiskit_docs_mcp_server/server.py +++ b/qiskit-docs-mcp-server/src/qiskit_docs_mcp_server/server.py @@ -113,8 +113,9 @@ async def search_docs_tool( 'tutorials' — Tutorial content only top_k: Maximum number of results to return (default: 5, capped at 10). detail: Per-result content level. 'snippet' (default) returns a short - excerpt; 'full' returns each result's full page body — heavier, so - prefer get_page_tool when you only need one page in full. + excerpt and a trimmed field set; 'full' returns each result's full + page body as 'text' and preserves all original fields (backwards + compatible) — heavier, so prefer get_page_tool for a single page. Returns: Matching documentation entries (id, url, title, pageTitle, module, diff --git a/qiskit-docs-mcp-server/tests/test_data_fetcher.py b/qiskit-docs-mcp-server/tests/test_data_fetcher.py index c576231..c6225b3 100644 --- a/qiskit-docs-mcp-server/tests/test_data_fetcher.py +++ b/qiskit-docs-mcp-server/tests/test_data_fetcher.py @@ -903,6 +903,28 @@ async def test_detail_full_returns_full_body(self, mock_fetch): assert "snippet" not in entry assert len(entry["text"]) > SNIPPET_MAX_CHARS + @patch("qiskit_docs_mcp_server.data_fetcher.fetch_text_json") + async def test_detail_full_preserves_upstream_fields(self, mock_fetch): + """detail='full' is backwards compatible: original fields pass through.""" + mock_fetch.return_value = [ + { + "url": "/docs/api/qiskit/circuit", + "title": "circuit", + "text": "body", + "language": "en", + "package": "qiskit", + "package-version": "current", + }, + ] + result = await search_qiskit_docs("circuit", detail="full") + entry = result["results"][0] + # Fields dropped in snippet mode are retained in full mode. + assert entry["language"] == "en" + assert entry["package"] == "qiskit" + assert entry["package-version"] == "current" + # URL is still normalized to an absolute URL. + assert entry["url"].startswith("https://") + @patch("qiskit_docs_mcp_server.data_fetcher.fetch_text_json") async def test_detail_full_strips_html(self, mock_fetch): """detail='full' still strips HTML tags from the body."""