diff --git a/mcp-server/src/legal_mcp/services/claude_session.py b/mcp-server/src/legal_mcp/services/claude_session.py index f6b077b..668d230 100644 --- a/mcp-server/src/legal_mcp/services/claude_session.py +++ b/mcp-server/src/legal_mcp/services/claude_session.py @@ -82,6 +82,7 @@ async def query( system: str | None = None, model: str | None = None, effort: str | None = None, + tools: str | None = None, ) -> str: """Send a prompt to Claude Code headless and return the text response. @@ -104,6 +105,12 @@ async def query( effort: Optional effort level (``low``/``medium``/``high``/``xhigh``/ ``max``). When set, passed as ``--effort``. Pairs with ``model``; an empty string is treated as "unset" (CLI default). + tools: Optional available-tools spec, passed as ``--tools``. Pass an + empty string (``""``) to disable ALL tools — for pure text→JSON + extraction the model has no reason to call a tool, and leaving + tools enabled makes it occasionally emit ``stop_reason: tool_use`` + which trips ``--max-turns 1`` → ``error_max_turns`` and forces a + retry (slow). ``None`` leaves the CLI default (all tools). Returns: The text response from Claude. @@ -126,6 +133,8 @@ async def query( cmd += ["--model", model] if effort: cmd += ["--effort", effort] + if tools is not None: # "" → disable all tools (no tool_use → no max-turns trip) + cmd += ["--tools", tools] size_info = f"; prompt_len={len(full_prompt):,} chars" if len(full_prompt) > 100_000 else "" last_err = "unknown error" @@ -204,13 +213,15 @@ async def query_json( system: str | None = None, model: str | None = None, effort: str | None = None, + tools: str | None = None, ) -> dict | list | None: """Send a prompt and parse the response as JSON. Uses parse_llm_json for robust parsing (handles markdown wrapping, truncation). - ``model``/``effort`` are forwarded to :func:`query` (see its docstring). + ``model``/``effort``/``tools`` are forwarded to :func:`query` (see its docstring). + Pure text→JSON extractors should pass ``tools=""`` to avoid ``error_max_turns``. """ - raw = await query(prompt, timeout=timeout, system=system, model=model, effort=effort) + raw = await query(prompt, timeout=timeout, system=system, model=model, effort=effort, tools=tools) return parse_llm_json(raw) diff --git a/mcp-server/src/legal_mcp/services/digest_metadata_extractor.py b/mcp-server/src/legal_mcp/services/digest_metadata_extractor.py index 6745439..48cb52b 100644 --- a/mcp-server/src/legal_mcp/services/digest_metadata_extractor.py +++ b/mcp-server/src/legal_mcp/services/digest_metadata_extractor.py @@ -101,6 +101,8 @@ async def extract(raw_text: str, model: str | None = None) -> dict: result = await claude_session.query_json( user_msg, system=DIGEST_EXTRACTION_PROMPT, model=(model or config.DIGEST_EXTRACT_MODEL or None), + tools="", # pure text→JSON: disable tools so the model never emits + # stop_reason=tool_use and trips --max-turns (error_max_turns). ) except Exception as e: # surfaced as warning, not swallowed silently (§6) logger.warning("digest_metadata_extractor: query failed: %s", e)