Tool formatting

Tool-related utilities for Klea

File: klea_utils/tools.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.tools.build_tool_description(t: mcp.types.Tool) → tuple[str, str][source]

Build the full and compact LLM-facing descriptions for an MCP tool.

Returns a (full, short) tuple. klea_utils.mcp.schemas.ToolInfo stores both: the full form (heading + docstring + compact parameter list) is what the tool picker needs to emit calls, while the short form (heading + docstring only) is for planner-style nodes that reason about which tools exist without the argument detail (tiered disclosure).

Klea expects MCP tool descriptions to follow the docstring-first convention (see docs/concepts/mcp.rst, “Tool description length and style”): the LLM-facing description is the opening text block of the function docstring, written as a one-sentence summary followed by “Use when:” / “Do not use for:” bullet sections and a single example line, bounded to roughly 100-250 tokens. Anthropic recommends at least 3-4 sentences covering what a tool does and when it should (and should not) be used; opencode keeps its tool descriptions to roughly 100-600 tokens with a one-line summary first.

Parameter descriptions are given in a Google-style Args: section that fastmcp parses into the schema, so they must not be repeated in the description. MCP servers must not set the tool description to the raw full docstring (see neuroml_mcp.utils.register_tools), otherwise the prompt carries duplicated Args:/Returns: prose in both the description and the compact parameter list built here.

klea_utils.tools.clean_tool_meta(meta: dict[str, Any] | None) → dict[str, Any] | None[source]

Drop developer tags from tool metadata before storing it.

FastMCP populates meta with {"fastmcp": {"tags": [...]}}; the tags are mostly developer labels (e.g. testing) that are not used by the tool picker, so they are stripped to keep stored metadata lean.

klea_utils.tools.last_tool_error_text(tool_results: list[fastmcp.client.client.CallToolResult] | None, max_len: int = 500) → str[source]

Return the text of the last error result in a batch, or "".

Used to give the tool picker concrete feedback when a round failed (for example an edit_file ambiguity), so it can adjust the call instead of repeating it. Only the last error is returned: the picker retries one round at a time, so the most recent failure is the one to correct.

Parameters:
  • tool_results – The batch’s tool results (None treated as empty).

  • max_len – Truncate the extracted text to this many characters.

Returns:

The final error result’s textual content, or "" if none.

klea_utils.tools.textualize_tool_results(tool_results: list[fastmcp.client.client.CallToolResult], max_len_per_tool: int | None = None, include_header: bool = True) → str[source]

Format tool call results as LLM-ready text for use in prompt context.

Tool return values are typically JSON-structured data (dicts, lists) which LLMs handle naturally. We wrap them in markdown code blocks for clear separation from surrounding prompt text.

Parameters:
  • tool_results – List of tool call results

  • max_len_per_tool – If set, truncate each tool’s textual content to this many characters (per-tool cap, not total). Ensures a large first result doesn’t starve later tools’ outputs.

  • include_header – When True (default) emit the batch header (## Tool Results) and a ### Result i/n label per result. Set False to render the bare content of a single result, e.g. when a caller supplies its own per-result heading.

Returns:

Formatted string suitable for inclusion in an LLM prompt

klea_utils.tools.tool_call_errors(tool_calls: Sequence[Any] | None, tool_results: Sequence[fastmcp.client.client.CallToolResult] | None, *, max_value_chars: int = 300, max_error_chars: int = 300, max_total_chars: int = 2000) → str[source]

Render the failed calls in a tool batch as a compact LLM-facing digest.

CallToolResult does not carry the tool name or arguments, so calls and results are aligned by index. dispatch_tool_calls guarantees one result per input call in input order; a missing call is rendered as an unknown tool. Only the latest batch is described (the caller passes the current tool_calls/tool_results).

Values are included only for arguments with an explicitly path/URI-like name (plus the picker’s declared paths), and are redacted/truncated. Arbitrary free-text arguments (file contents, edit strings, code, shell commands) are deliberately omitted: this is an allowlist, not a guess at whether a string “looks like” a path.

Parameters:
  • tool_calls – Calls that produced tool_results, in dispatch order.

  • tool_results – Results for the batch.

  • max_value_chars – Per-argument value cap.

  • max_error_chars – Per-error text cap.

  • max_total_chars – Total digest cap.

Returns:

One line per failed call, or "" when nothing failed.