Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault

No arguments

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
check_dependenciesA

Run the dependency audit of the installation. Read-only, and it creates no file.

Call it when get_project_status or another tool reports a problem of the installation, or before the first fw-context init. It is not a call for each session: it runs every check of the installation.

Each entry gives what to do in action: a message for a human, and the shell command that repairs it. status="skipped" means a prerequisite is missing (e.g. libclang-so skipped because libclang-python is absent) — not a failure.

In a project that is not initialized, only the checks that need no project config run: the Python packages and libclang. The Ollama, model, database and build checks need the config.

The check config names a config file that cannot be used. The checks that read the config are then skipped. When a config file cannot be used, the tool cannot know if the project is initialized, thus it runs every check.

Args: project_root: Project root directory. Auto-detected from CWD if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both.

Returns: list[dict]: one dict per check, each with name, status, critical (bool), message, and an optional action ({message, command}).

check_ollamaA

Check whether the LLM backend is running and the configured embedding/chat model is installed.

Read-only: yes. No side effects. Call before smart_search, semantic_search, or explain_symbol (when on-demand fallback is expected — pre-computed analysis returns instantly without the LLM backend).

Args: project_root: Project root. Auto-detected if omitted. Used to locate the project's LLM configuration. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both.

Returns: dict: {ollama_enabled (bool), status (str — "ok"|"disabled"| "not_configured"|"model_missing"|"embedding_unavailable"|"error"), ollama_running (bool), ollama_url (str), configured_model (str), num_ctx (int), installed_models (list[str]), chat_api (dict — {configured (bool), model (str)}: whether an external chat API replaces Ollama, and the model it names; a configured one adds endpoint, format, and compliance_warning for an external host), configured_embed_model (str), embedding_installed (bool), embedding_backend (str — "ollama", or "local" for a sentence-transformers or ft:// model, which needs no Ollama: it counts as installed, and with chat on an external API a stopped Ollama still gives "ok"), message (str, when status is not "ok"), model_details (list[dict]), suggest_cloud (bool), vec_available (bool), vec_error (str, optional), debug_log (str, optional — only when debug logging is enabled)}

The chat model is looked for on the Ollama server of the chat
calls: ``ollama_url``, or the Ollama server that ``chat_api_base``
names.  An OpenAI-compatible ``chat_api_base`` runs the model
itself, and no Ollama server is asked for it.  ``"model_missing"``:
``message`` names a command for each missing model, which sets
``OLLAMA_HOST`` to the server that must hold the model.
``"error"`` also means that the Ollama server of ``chat_api_base``
gives no model list.  When the server of ``ollama_url`` does not
answer, the chat model is not looked for.

ollama_url, num_ctx, installed_models, model_details,
configured_embed_model, and embedding_installed are present only
when Ollama runs; configured_model also needs chat on Ollama.
``"disabled"`` gives only status, ollama_enabled,
ollama_running, configured_model, num_ctx, and message;
embedding_backend is present on every other status.
configure_llmA

Configure LLM settings for the current project.

Writes to <project>/.fw-context/local.toml ONLY (gitignored, per-developer). Does NOT modify the global config or the shared project config.toml. After writing, tests the configuration by making a simple API call (skipped when LLM is disabled). When the test fails, the previous local.toml is restored and status is "error". A call with no change returns "error" and writes nothing.

IMPORTANT: When chat_api_base points to an external host, source code snippets in chat prompts will be sent to that endpoint. Ensure this complies with your organization's data security policies. Consider using local Ollama or an internal API proxy first.

Args: project_root: Project root directory. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. chat_api_base: Chat API URL (see description for format details). chat_api_key: Bearer token for cloud/proxy APIs. chat_api_format: Override auto-detection: "ollama" or "openai"; "auto" returns to detection from the URL. None keeps the current value. model: Chat model name. embed_model: Embedding model: an Ollama tag, a sentence-transformers name (BAAI/..., needs the st extra), or ft://. auto_pull: Whether to auto-pull models on 404. None keeps the current value. stream: Stream chat responses via SSE. True avoids reverse-proxy idle timeouts.

Returns: dict: {status ("ok"|"error"), chat_api (dict — configured, endpoint, format, model), model (str), auto_pull (bool), stream (bool), test_latency_s (float, on success), test_response (str, on success), compliance_warning (str, when chat_api_base is external), message (str)}

get_build_infoA

Return the detail of ONE build: its identity, its health, its memory map and its defines.

Read-only with the two exceptions that get_project_status also has: it removes the marker of a missing build when it finds that build again, and a reindex.pid or a reindex.pending of a process that no longer runs. It spawns no subprocess. On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. get_project_status lists the builds in builds. Without the arguments the answer is an error that lists the builds. A project of one build needs no selector.

The health fields (status, reindex_state, reindex_needed, reindex_reasons, index_message, modified_files_count and the others) have the meaning that get_project_status gives them, for this build only. The version fields (update_notice, a restart after an upgrade) are in get_project_status.

About memory: origin and length hold the expression the script writes, thus they differ by platform — an mbed script writes 0xefe00 and a Zephyr script writes ((673792) - 0xe6). origin_value and length_value hold the number, and both are None for an expression that names a symbol, such as ORIGIN(RAM) + LENGTH(RAM). A name that the link command defines with --defsym counts as a number: the STM32 Arduino core writes LENGTH = LD_MAX_DATA_SIZE and gets length_value 98304. The end of a region is origin_value + length_value.

memory is empty for a build system that records no linker script, such as Keil or IAR. A PlatformIO project records its link only in fw-context index --build: SCons writes no file with the link command, thus the build asks pio run -t envdump. An empty list for PlatformIO means that no build recorded the link yet, and fw-context index --build is the step. An empty list means "not recorded", never "no memory".

defines holds only the names that EVERY translation unit of the build carries with the same value, so the tool never shows the defines of one file as the defines of the build. defines_varying counts the names left out, thus a name absent from defines is either not defined at all or not defined everywhere. Measured on the Mbed project, 27 names are in all 881 units and 59 in only some, where the three assembly files get a shorter set.

This is the configuration the BUILD states, not every macro the preprocessor saw. The second is three orders of magnitude larger — 27800 distinct names on the STM32 project — and almost all of it comes from the headers and the compiler. A Zephyr build keeps its real configuration in autoconf.h (740 CONFIG_* names) and passes few -D flags, so defines says little there and a great deal on an mbed build, where it holds APPLICATION_ADDR, APPLICATION_SIZE, and CMSIS_VECTAB_VIRTUAL.

Args: project_root: Project root directory. Auto-detected from CWD if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name. fast: When True (default), the header check reuses the cached manifest hashes. False recomputes them.

Returns: dict: {variant, image, board, config_hash, project_id, project_root, build_system, compile_commands, indexed_at (str — UTC), first_indexed_at, symbol_count, file_count, reference_count, modified_files_count, header_affected_tus, manifest_verification, analysis, description, vendor_paths, project_paths, effective_vendor_patterns (list[str] — the SQL LIKE patterns that this build really used to mark vendor code, for example ["mbed-os/%"]; empty when the manifest cannot be read. The two lists above hold what the config asks for, this one what the index did), status, reindex_state (str or None — see get_project_status), reindex_needed, reindex_reasons, stale, index_message, entry_point (str — the ENTRY() of the linker script of the build, empty when no script names one), memory (list[dict] — the MEMORY regions of the build: {name, attributes, origin, length, origin_value, length_value, file_path, line}), defines (dict — the -D flags of the build), defines_varying (int), bg_reindex_running (bool), reindex_progress (str or None), vec_available (bool), vec_error (str, optional), _warning (str, optional), client_restart_required (bool, optional), client_restart_reason (str, optional)}

A refusal of the selector, or an index with no build config, gives
a dict that holds ``error`` alone.  A project that is not
initialized, that has no index, or whose config file cannot be used
(``"config_error"``) gets the answer of ``get_project_status``.
``config_warnings`` (a list) comes as in ``get_project_status``.
get_project_infoA

Return project metadata (name, type, root_path) for a project ID.

Looks up the global project registry at ~/.fw-context/projects.db. Use this to identify a project from its UUID4 — find out what build system it uses, its name, and where it was last indexed.

Read-only. No side effects.

Args: project_id: Project ID (UUID4 hex) to look up.

Returns: dict: {project_id, name, project_type, root_path, created_at, updated_at} or {"error": "..."} when the project_id is not registered.

On failure the dict holds only ``error`` with the reason.
get_project_statusA

MANDATORY FIRST CALL for C/C++ projects. Return the health of the index of the project, and the list of its builds — check it before you use any other fw-context tool, and call it again while you wait for a reindex.

Read-only with two exceptions, and it spawns no subprocess — the startup daemon thread and the file watcher own the background reindex. The exceptions: the last index run can leave a marker that says that the build is not there, and when the tool finds that build again, it removes the marker. It also removes a reindex.pid or a reindex.pending of a process that no longer runs. It creates no .fw-context file in a project that is not initialized. It takes no variant and no image: it reports every build. For the detail of one build (its config_hash, memory map, defines), call get_build_info.

builds lists every build of the index. A project of more than one build has no active build: every query tool must get the parts of one entry that are not empty, its variant, its image, or the two. Select the build from the task, and ask the user when the task does not identify one.

Act on reindex_state of the build that the task needs, in builds (a project of one build has it at the top level too):

  • null — the index of the build is current. Continue.

  • "reindexing" or "pending" — the index of the build is not current, and an index run is active or scheduled. Wait, and call this tool again after about 30 s. Continue with the old index only when the user tells you to.

  • "not_scheduled" — the index of the build is not current, and no run updates it. Tell the user the command that the reasons name.

  • "failed" — the last index run failed. Give its text to the user: it names the command that repairs it.

Each query answer carries the state of its build as index_state, from quick checks only. A query thus can miss a changed header, a changed file that its answer does not name, or a new source file when its answer is not empty. This tool does the full check.

status gives the state of the project:

  • "ready" — up to date. Continue.

  • "reindexing" — background reindex running or scheduled, or fw-context builds new source files on its own. reindex_state of a build tells whether that build waits for the run. reindex_progress holds the last log line of a running reindex.

  • "reindex_needed" — the last index run failed, the build is not there, schema mismatch, an old row format, a changed or missing compile_commands.json, a checkout of another branch, tag or commit since the index, or a source file that compile_commands.json does not cover and fw-context will not build itself. A changed compile_commands.json includes one that fw-context cannot compare with the index. Queries still work on existing data. Read reindex_reasons: each reason names the action that it needs.

    Read bg_reindex_running before you name any command here. A run already under way does the work, thus a second one only waits on the same lock, and index_message and the checkout reason both say so. That field covers a run of the daemon AND a run the operator started, and also work that the daemon has scheduled and not yet started — the arguments of no run are readable, thus a reason that needs --build can outlive a run without it. This status wins over "reindexing" deliberately: the answers of this moment come from the rows of the last FINISHED run.

  • "no_index" — initialized, never indexed. Run fw-context index.

  • "not_initialized" — run fw-context init.

  • "config_error" — a config file cannot be used: it cannot be read, it is not UTF-8 or not valid TOML, a section is not a table, a key that selects the data or where data goes has a value of the wrong type, or a URL is not valid. config_file names it, and index_message gives the problem. Correct the file, or run fw-context doctor --fix --only config when the message names it: it removes each value that cannot be used. fw-context init cannot repair it.

A project of several builds gets the worst status of its builds. A reason that every build gives comes one time, and any other reason names its build: build <variant>/<image>: <reason>.

A failure sets NO status. An index with no build config gives a dict that holds error alone. Read that key first: a reader that waits for status == "error" waits for a value this tool does not produce. A database that fw-context cannot read (damage, no access) makes the call fail with the error of SQLite. The message of the check db-integrity of check_dependencies tells if it is damaged.

Seven conditions set reindex_needed: a failed last index run, a build that is not there, an outdated schema, an outdated ROW FORMAT, a changed or missing compile_commands.json, a checkout of another branch, tag or commit since the index, and a source file that is on disk but absent from compile_commands.json when fw-context will not build it on its own. A missing build, a missing compile_commands.json, a checkout and such a source file need the action that makes compile_commands.json again: a build, or for a database of the user ([index] compile_commands) a new file from the user. A plain reindex reads the same file again, and it has no translation unit for a new source file and skips it without a word. A failed run needs the repair that its reason names first. Modified source files are something else: they are handled per query, and never set it.

row_format_mismatch means that the same columns hold text with an older meaning. Take it seriously: an index written before fw-context-rows/1 keeps every inactive #ifdef branch, thus a body or a file from it can show code that the compiler never sees. An index written before fw-context-rows/3 is wrong the other way: it ANSWERS LESS than it should. A definition that begins and ends on one line has no stored body there, thus search_bodies cannot reach an inline accessor; no macro is marked function-like; and no instance links to its template, thus get_template_instances gives an empty list for every template. Each of those looks like a legitimate empty answer. The value of a macro there also holds its parameter list glued to its replacement text, thus neither can be read out of it. An index written before fw-context-rows/4 parsed a GCC build with guessed toolchain headers and the macros of clang: an ESP32 or AVR index there holds host headers from /usr/include and takes the #if branches of the host, for example #if __XTENSA__ as false, and an ARM index saw __GNUC__ 4. A build whose GCC driver does not answer the query keeps that fault also after /4; the index run names such a compiler in a warning.

client_restart_required is the OPPOSITE case, and no command repairs it. This server process does not run the code that it must run. Two causes set it, and client_restart_reason names each one that applies:

  • The index carries a NEWER row format than this process reads, thus the index is the correct one and this process is the old reader. A reindex makes it worse than useless: the indexer writes the same new format again, and the message comes back over an index that had nothing wrong with it.

  • The operator installed a different fw-context after the LLM client started this process. This cause does not need an index, thus it shows also for not_initialized, no_index and config_error.

status therefore stays as it is and reindex_needed stays False — every query keeps working. Do NOT run a reindex for this field. Tell the operator to restart the LLM client — Claude Code, opencode, or whichever one is in use. The MCP server is a child process of that client, thus nobody can restart the server by itself. client_restart_reason holds the wording, and index_message opens with it.

update_notice is not a status either. It shows when PyPI has a newer fw-context than the installed one. Tell the operator one time, and do NOT upgrade fw-context yourself: the operator knows the tool that installed it. After a request that succeeded, the server asks PyPI again only after 24 hours. The request runs in a background thread, and no tool waits for it. [updates] check = false in the global config, or FW_CONTEXT_NO_UPDATE_CHECK=1, stops it.

indexed_at and first_indexed_at are UTC; file mtimes are local time. Never compare the two directly — in UTC+2 a correctly indexed file looks 2 hours newer than indexed_at. Read modified_files_count to find modified files.

analysis splits the LLM-analysis coverage into project and vendor symbols:

  • model — the model of the analysis, or None. One model only, even when several were used.

  • analyze_vendor — the value at index time, not the current config.

  • project / vendor — {analyzed, skipped, total}. skipped = tried, but not analyzable (body larger than the model context, an unparseable answer, or a body that was not readable).

  • complete — no work left: every project symbol is analyzed or skipped, and every vendor symbol too when analyze_vendor is True. Vendor symbols excluded by analyze_vendor=False never block it, thus vendor.total large with vendor.analyzed=0 is expected, not a defect. index_message ends with the same counts as text.

Args: project_root: Project root directory. Auto-detected from CWD if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. fast: When True (default), the header check reuses the cached manifest hashes. Both modes run the per-file scan, thus modified_files_count is accurate either way — a tool that reported "ready" while the search tools warned about the same file gave the caller two readings and no way to choose. False recomputes the header hashes and costs several times more.

Returns: dict: {project_id, project_root, build_system, schema_version (int — DB schema version), current_schema (int — code expects), vendor_paths (list[str] — config index.vendor_paths), project_paths (list[str] — config index.project_paths), status (str — "ready"|"reindexing"|"reindex_needed"|"no_index"| "not_initialized"|"config_error"; a failure sets no status and gives error alone), config_file (str — only with "config_error": the file that cannot be used), reindex_needed (bool), reindex_reasons (list[str] — why a reindex is needed, empty when False, except that a new source file that fw-context builds on its own is listed while reindex_needed is False. Some of them ask for a build rather than a plain reindex. For example, after a checkout of another branch, tag or commit, compile_commands.json belongs to the OLD checkout and carries its file list and its compiler flags, so only a build regenerates it. Read the reason text — it names the command it needs), stale (bool — True when a build needs a reindex or has stale header dependencies), index_message (str — human-readable summary of index state), builds (list[dict] — every build: {variant, image, board, symbol_count, file_count, indexed_at, status, reindex_state (str or None — "reindexing", "pending", "not_scheduled", "failed", or None for a current index), reindex_needed, stale, modified_files_count, header_affected_tus, reasons}, and manifest_verification on a build whose manifest is not "full"), declared_not_indexed (list[dict], optional — each variant that the config declares and the index does not hold: {variant, command}; no query reaches it until the command ran), bg_reindex_running (bool), reindex_progress (str or None — last log line when reindex is running), _warning (str, optional — when the manifest verification of a build is not "full"), vec_available (bool), vec_error (str, optional), client_restart_required (bool, optional — only when True), client_restart_reason (str, optional — with the field above), update_notice (str, optional — a newer release is on PyPI)}

A project of ONE build also gets the fields of that build at the
top level: indexed_at (str — "YYYY-MM-DD HH:MM:SS" in UTC, the
completion time of the last full index), first_indexed_at,
symbol_count, file_count, reference_count, modified_files_count
(int — files whose content no longer matches the index; counted in
both modes), header_affected_tus (int — number of source files with
stale header dependencies; a file that compile_commands.json lists
more than once counts once), manifest_verification (str — "full"
when manifest.json exists, "partial" when it exists and the
embeddings are not complete, "none" without one), analysis (dict — LLM
analysis coverage split by project/vendor: {model, analyze_vendor,
project: {analyzed, skipped, total}, vendor: {analyzed, skipped,
total}, complete}), description (str).  A project of several
builds gets no sums: the counts of each build are in ``builds``,
and ``reference_count``, ``analysis``, ``description`` and
``first_indexed_at`` of a build are in ``get_build_info``.

For a project that is not initialized, the result holds only
``status``, ``project_root``, and ``index_message``.  When no index
exists, the result adds ``project_id``.  For ``"config_error"``, it
adds ``config_file``.  The two version fields
(``client_restart_*`` for an upgrade, ``update_notice``) can come
with every result, these two included.  ``config_warnings`` (a
list) can come with each result that has a ``status`` other than
``"config_error"``: values of a config file that fw-context did not
use.  Tell the user each line.
list_projectsA

List all indexed firmware projects with their statistics.

Read-only. No side effects. Use it to find another project; call get_project_status for the builds and the health of one project.

indexed_at and first_indexed_at are UTC, in "YYYY-MM-DD HH:MM:SS" format — the same format that get_project_status returns. indexed_at is the time of the newest build.

A project of several builds gets no counts here (symbol_count, file_count, description and analysis are None): a file that two images compile is in the index of each, thus a sum counts shared code once for each build. get_project_status gives the counts of each build.

analysis holds the project and vendor counts only. For the model, analyze_vendor, and complete fields, call get_build_info for that build.

Args: project_root: Project root whose config names the index directory (index.db_dir). Omitted, the global config does. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both.

Returns: list of dicts, each with: project_id, name, root_path, build_system, symbol_count, file_count, indexed_at (str — UTC), description (str), first_indexed_at (str — UTC), schema_version, current_schema, reindex_needed (bool — the schema is outdated, the compile_commands.json of a build changed or is missing, or a build holds rows of an older row format; each (variant, image) build counts), status (str — "ready" or "reindex_needed"), db (path to SQLite database file), build_count (int — number of builds, each a (variant, image) pair), analysis (dict — LLM-analysis coverage {project: {analyzed, skipped, total}, vendor: {analyzed, skipped, total}}, or None when no build is indexed).

When no project has an index, the result is a single dict with an
``info`` key.  When fw-context cannot read a database, the result
holds a dict with ``db`` and ``error`` keys for that file.
reindex_fileA

Re-parse a single source file with libclang and update its symbols in the index.

Not read-only — uses the exact compiler flags from compile_commands.json. Use after editing a file to keep the index current without a full rebuild.

Without variant and image the file is re-parsed in EACH build of the project that compiles it: an edit on disk changes the file for every build of the tree. A build that does not compile the file is named in builds_without_file. With the two arguments of one build, as get_project_status lists them, only that build is re-parsed; a part of a pair gets an error that lists the builds.

The call runs in a thread of the server and sends a notice each 5 s. It holds the lock of the query tools: another query waits 5 s, then gets "server busy". After 300 s the call returns an error, and a part of the work can continue or fail. Call get_project_status for the state of each build, and repeat the call for a build that is not current. The answer holds no index_state.

A source file must be listed in compile_commands.json. A header is not listed there — compile_commands.json names translation units — so it is re-parsed through one unit that includes it, taken from the manifest of each build. That answer describes a single compilation context, thus the result carries a warning: another unit can see the header under a different set of #define values and still hold stale symbols. Only a full fw-context index covers every context. One unit and not all of them is a cost decision — an application header reaches a median of 3 units but as many as 266 on a real project, at tens of seconds each.

Also rebuilds the method override relationships, the PageRank scores and the hotspot cache when reference indexing is on ([index] index_refs), with and without with_analysis. When with_analysis=True and the LLM is on ([llm] enabled), it also regenerates the LLM analysis and the embeddings of the affected symbols. The analysis also needs [llm] analyze_symbols. It is content-addressed, thus an unchanged symbol is never re-analysed.

Args: file_path: Path to the file to re-parse. A source file must be in compile_commands.json; a header goes through one including unit. project_root: Project root directory. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. variant: The variant of the one build to re-parse. Omit it, and image, to re-parse each build that compiles the file. image: The image of the one build to re-parse. with_analysis: When True (default), also regenerates the LLM symbol analysis and the embeddings, which need a model. Set False for a fast update: the status tools then report the symbols that wait for the two.

A file that is gone from disk but still in the index has its records removed in each build that holds it; the result is then {file, symbols_removed, action: "deleted", builds}.

Returns: dict: {file, translation_units, symbols_updated, elapsed_s, builds}, plus builds_without_file when a build does not compile the file. builds has one entry for each build that compiles the file: {variant, image, translation_units, symbols_updated, elapsed_s}, plus when they apply: skipped_tus and skipped_count (units that failed to parse), warning (header coverage), analysis_updated and embeddings_updated (counts for the whole build, not this file), and analysis_warning, overrides_warning, pagerank_warning, or embedding_warning when a phase failed, or error when the re-parse of that build failed.

When one build compiles the file, the top level holds the full
result of that build.  When several do, the top level holds the
sums of the builds that did not fail, ``elapsed_s`` of the whole
call, the sum of ``skipped_count``, ``warning`` only when each of
those builds has it, and ``failed_builds`` with the label of each
build whose entry holds ``error``.

On failure the dict holds ``error`` with the reason: a refusal of
the selector, no build that compiles the file, or an update that
failed in each build.  It can add ``skipped_tus`` or ``action``.
reset_indexA

Delete the entire symbol index for a project.

Not read-only — permanently deletes the SQLite database and WAL files. Call with confirm=False first (dry-run) to see what would be deleted. Re-index with fw-context index afterwards.

Handles corrupt databases gracefully — you can delete a corrupt index without needing to open it first.

Args: project_root: Project root directory. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. confirm: Must be True to execute. Call without first as dry-run.

Returns: dict: {project_root, db, project_id, action: "dry_run"|"deleted", message, build_count, symbol_count, indexed_at}. build_count counts the completed builds of the project, as get_project_status lists them. symbol_count counts every symbol row in the database, the rows that the reset deletes: also the rows of a build that an index run writes now, and of an older build of a pair that an interrupted run left behind. indexed_at is the time of the newest build. The three are present only when a build config is readable.

A ``warning`` key means that the database is corrupt — the
integrity check or a read of the database failed, thus the tool
counts nothing and the three count fields are absent.

On failure the dict holds only ``error`` with the reason.
lookup_symbolA

Look up a C/C++ symbol by name via libclang index — exact or prefix matching. Finds symbols text-based search can miss: build-conditional code, template instantiations, macro-expanded names. Macros are extracted via clang -dM -E during indexing so #ifdef-conditional macros resolve correctly for the build of the query. Prefer this over search_code when you know the exact symbol name or a prefix (uart_ finds all UART symbols). Use search_code for keyword/concept search.

name matches the name or the qualified name. A name with :: that matches nothing is tried once more by its last part, kept where the qualified name ends with name: Foo::bar finds ns::Foo::bar. Macros are searched only when no symbol matches.

Read-only: yes. May auto-reindex stale files (non-blocking).

Args: name: Symbol name (exact match) or prefix (set exact=False). E.g. 'uart_init' finds the exact function; 'uart_' finds all symbols starting with 'uart_'. project_root: Project directory. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. exact: True = exact name match, False = prefix LIKE match (default). limit: Maximum results of one page (default 50, max 100). offset: Skip this many results (default 0). A common method name lives in many classes — read and write match dozens of symbols — and this walks past the ones already seen. The page notice names the offset to use. The order is stable (a definition first, then the line, then the file and the USR), thus two pages never overlap and never skip a symbol. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: list[dict]: The page notice leads the answer — total, offset, shown, more — where total counts every symbol the name matches. Read it before you conclude that a page holds them all. Each symbol that follows has name, qualified_name, kind, file, line, signature, docstring, is_definition, is_template, is_virtual, is_pure_virtual fields, and class — the class, struct or union that declares the symbol, absent for a free function. class is what tells two same-name methods apart at a glance. Enum constants include enum_value with the integer value. Macro results include kind="macro", signature (how the macro is invoked: #define NAME, #define NAME() or #define NAME(a, b)), is_function_like, value (the replacement text ALONE — the parameter list is not part of it), and expanded_value (preprocessor-resolved value). May also include template_usr, parent_usr, and llm_analysis ({summary, inputs, outputs}) when available. A model wrote the text in llm_analysis, and the code did not — use it to find a symbol, and quote signature, docstring, or get_source instead. When nothing matches, a trailing dict may hold _did_you_mean with suggested names. The symbols of the first suggestion that matches exactly then come back, each with _fallback: True — its name is NOT name. With no match and no suggestion, the list is empty and has no page notice, because there is no page. An info entry comes back for one case only: an offset past the end of an answer that does hold rows. When a file that the answer names changed on disk, a dict with warning comes first.

**Note:** C++ constructors share their name with the enclosing
class, so ``lookup_symbol("Foo")`` may return both ``class Foo``
and ``constructor Foo::Foo()``.  Use the ``kind`` field to
filter when you need a specific symbol type.

A list with one dict that holds an ``error`` key means that the
project has no index, that the variant or image is wrong, or that
the lookup failed.  Read that key
before you read the result fields.
search_codeA

Find C/C++ symbols by name — searches function/class/enum NAMES.

Searches symbol names, qualified names, signatures, docstrings, and pre-computed name tokens (CamelCase/snake_case split). Does NOT search function bodies — for patterns in code like .attach(, interrupt handler registrations, callback attachments use search_bodies instead.

Use when you know the concept but not the exact name ("interrupt handler", "modem init"). Prefer lookup_symbol when you already know the exact or prefix name.

Results hold the metadata of each symbol — name, location, signature, docstring — not its implementation code.

FTS5 syntax:

  • Every bare term gets a trailing * and the terms are OR-joined: modem init goes to FTS5 as modem* OR init* and answers with the symbols that hold EITHER word. search_bodies does the opposite — it takes the query literally, where a space is an AND. A query that holds a double quote, AND, OR, NEAR or a column filter goes to FTS5 as written.

  • init* matches init, init_uart, initialize (trailing wildcard)

  • "spi init" matches the exact phrase "spi init"

  • Do NOT use an underscore in a query. The tokenizer splits modem_init into two tokens and looks for them NEXT TO EACH OTHER. That is a phrase and not an AND, thus the query misses modem_parser_oob_init. Measured on one firmware index, serial_write gave 1 result and serial write gave 200. Write modem init instead.

  • Punctuation is not searchable. The tokenizer drops it, thus .attach( becomes a phrase that looks for the token attach. The query is repaired, never rejected.

Progressive relaxation: when a step matches nothing, the next one runs. Six steps can run. Step 1 is the primary path and its results carry NO _fallback key; each step after it names itself there:

  1. FTS5 with the kind filter. No _fallback key.

  2. FTS5 without the kind, when the kind matched nothing; operators often guess the wrong kind — _fallback="fts5".

  3. name_tokens substring match over the pre-computed CamelCase / snake_case tokens (BuildType is indexed as "build type"). Needs N−1 of N query terms — "name_tokens_like".

  4. LIKE over the docstring column, for a single-term query that the token steps missed — "docstring_like".

  5. FTS5 per query word, results merged — "individual_terms".

  6. macros_fts for #define names and values, kind="macro" — "macros_fts".

Steps 3-6 ignore kind.

Kind filter values: function, method, constructor, destructor, class, struct, union, enum, enum_constant, typedef, varglobal, varlocal, variable, field, namespace.

Local variables are out. FTS5 indexes the qualified name, thus a local matches through the function that holds it: a query for sensor used to answer with V, ret and tmp_value from inside read_sensor_value, 4 of 20 results on one measured query. A local is never the answer to "which symbol is about X", thus varlocal and the legacy variable kind are excluded. varglobal stays — a global carries architectural weight. Ask for them explicitly with kind="varlocal", or use find_variables.

After fw-context index --analyze, a result also holds llm_analysis — {summary, inputs, outputs}. A model wrote that text, and the code did not. Treat it as a hint that points you at a symbol, never as a fact to quote. Quote source from get_source, signature, or docstring.

Read-only: yes. May auto-reindex stale files (non-blocking). When a file that the answer names changed on disk, this tool starts the watcher daemon in the background and answers from the index it has.

Args: query: FTS5 search terms. Keep queries short — 1–3 words. project_root: Project root directory. Auto-detected from CWD if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. kind: Optional filter to return only symbols of this kind. limit: Maximum results of one page (default 20, max 100). offset: Skip this many results. Reads the next page of a topic that many symbols carry; the page notice names the offset to use. One relaxation step owns the whole answer, thus a walk never changes the step under the reader. project_only: When True, exclude vendor SDK directories and return only application code. Default False. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: list of dicts. The page notice leads the answer — total, offset, shown, more — where total counts the answer of the step that answered. Each symbol that follows has name, qualified_name, kind, file, line, is_definition, signature, docstring, is_template, is_virtual, is_pure_virtual. Enum constants include enum_value with the integer value. May also include template_usr, parent_usr, and llm_analysis ({summary, inputs, outputs} — written by a model, not by the code. get_build_info().analysis.model names it). Fallback results include _fallback with the method name. A macro result also holds _macro_value and _macro_expanded_value when they are not empty.

No match gives ``[]``.  A dict with ``error`` means the query
failed.  When FTS5 refuses the query and no LIKE step finds a row,
the answer is one dict with ``warning`` + ``hint``.  When a file
that the answer names changed on disk, a dict with ``warning``
comes BEFORE the page notice — find the notice by its keys, and
not by its position.  An empty answer gets that ``warning`` too
when indexed files changed: the code can be in them.
search_bodiesA

Find patterns in the TEXT OF A DEFINITION — the code inside its extent.

Searches ifdef-filtered text — only the code that compiles for the build of the query. A line of an inactive #if branch holds nothing, thus a pattern that lives only in a dead branch gives no result here. That empty answer is the correct one: the code does not compile.

Searches the stored text of every definition (is_definition=1), and a definition is not only a callable. Measured on one project of 60,877 symbols, the text covers:

  • Callables — function, method, constructor, destructor. Call patterns (.attach(, .rise(, callback(&), ISR registration, one case label of a long switch.

  • Types — class, struct, union, enum, namespace. An enum constant, a bit field, a member declaration such as InterruptIn _pin; — all inside the body of the type that holds them.

  • Definitions of data — varglobal, varlocal, typedef. A table with a multi-line initializer is found by its content.

A match on a type reports the type as the result, thus a query for one enum constant answers with the enum, and match_lines gives the line of the constant itself.

Only the text matches. The query is bound to the stored body: a hit in the NAME, the signature, the docstring or the llm_analysis of a symbol is not a hit here. Measured on one project, sensor used to give 36 results of which 22 matched only through a summary that a model wrote — untrusted text that cannot be cited, and a _match_snippet with no match in it. Use search_code to reach a name or a concept. A column filter you write yourself (summary : sensor) overrides the binding.

When to use search_bodies and when search_code:

  • search_bodies — patterns in the code (what the code DOES or DECLARES): self test, attach, SELF_TEST.

  • search_code — symbols by NAME (what the code IS): modem init, interrupt handler.

The query goes to FTS5 as you wrote it. This tool alone adds no wildcard, and that is what keeps a pattern precise:

  • A space is an AND of two exact tokens, NOT an OR. CommandType NUM answers with the definitions that hold both.

  • No prefix is implied. SELF_TEST matches the tokens self test and misses Self tester; write SELF_TEST* to reach the second. Measured on one project, the wildcard added the one caller that the bare query missed.

  • Punctuation is not searchable. FTS5 cannot parse .attach( at all, thus the query is repaired into the phrase ".attach(" — and the tokenizer inside a phrase drops the punctuation too, so what runs is the word attach. Such a result carries _fallback: "sanitized" and _query_used. The hits whose body really holds .attach( are the ones with match_lines.

  • search_code and search_content behave the OTHER way: each of their terms gets a trailing * and the terms are OR-joined.

Limitation — the extent of a definition is the boundary. Text that belongs to no definition is out of reach:

  • #include, #define, #ifdef — preprocessor directives. search_code covers a macro name and value. search_content covers the directive as text.

  • extern "C" — a linkage specifier is no symbol.

  • A comment or a declaration at file scope, outside every definition.

For those, use search_content, which indexes the full file text.

Set project_only=True for a question about YOUR code ("where do we register interrupt handlers?"). Leave it False (default) when the vendor SDK code — the framework or OS code that your team did not write — is also relevant.

Results include _match_snippet — a highlighted excerpt that shows each match in context (e.g. _timeout.<b>attach</b>(callback(...))) — and match_lines, the line numbers of the matches inside the definition. line is where the definition starts, which for a large function is far from the match. Cite from match_lines instead. Project code sorts before vendor code in the output.

Read-only: yes. Requires the FTS5 index. May auto-reindex stale files (non-blocking) — see search_code.

Args: query: FTS5 search terms, 1-3 words. A bare multi-word query is an AND of exact tokens, and no wildcard is added — see the query rules above. A single word is the broadest form: 'attach' reaches every .attach(...) pattern. Add * for a prefix ('attach*'), and double quotes for a phrase ('"attach callback"'). project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. kind: Optional filter to return only symbols of this kind. limit: Maximum results of one page (default 20, max 100). offset: Skip this many results. Reads the next page of a pattern with many hits; the page notice names the offset to use. project_only: When True, exclude vendor SDK directories and return only application code. Default False. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: list of dicts. The page notice leads the answer — total, offset, shown, more — and each that follows holds: name, qualified_name, kind, file, line (first line of the definition), is_definition, signature, _match_snippet (excerpt around the match), source (the text of the definition).

Also, when they carry an answer:

* ``match_lines`` (list[int]) — absolute line numbers of the
  matches, up to 20.  Computed from the full text, thus a match
  after the cut below still has a number.  Use these to cite
  ``file:line``, and not the ``line`` of the definition.  The name
  carries no leading underscore for a reason: a field the caller
  must cite is an answer, while ``_``-prefixed fields
  (``_match_snippet``, ``_fallback``, ``_source_truncated``) tell
  where the answer came from.
* ``_source_truncated`` (True) — ``source`` is cut.  A callable
  keeps 2000 characters, any other kind 500, because the body of a
  type is mostly members that the match has nothing to do with.
  ``get_source`` gives the whole text.
* ``_fallback`` (``"sanitized"``) with ``_query_used`` — FTS5 could
  not parse the query as written, thus a repaired one ran.  The
  repair drops punctuation, so the answer is wider than the text
  that was asked for.  Every query FTS5 accepts runs untouched and
  carries neither field.

``source`` here is bare text with no line-number prefix.  Only
``get_source`` numbers its lines.

No match gives ``[]``.  A dict with ``error`` means the query
failed.  A query that FTS5 refuses to parse, also after the
repair, gives one dict with ``warning`` + ``hint`` — an empty list
always means "no such code", never "bad query".  When a file that
the answer names changed on disk, a dict with ``warning`` comes
first; an empty answer gets it too when indexed files changed.
search_contentA

Find patterns in FULL file content — the whole file, not only the text that belongs to a definition.

Searches ifdef-filtered file text — only code that actually compiles for the build of the query. Inactive #ifdef branches are replaced with blank lines (preserving original line numbers). A pattern that lives only in a dead branch therefore gives no result, and that empty answer is the correct one: the code does not compile.

Covers the text that belongs to no definition, which is what search_bodies cannot see: #include, #define, #ifdef, extern "C", and a comment or declaration at file scope. It covers the text of definitions too. To find a symbol by NAME (modem init, interrupt handler), use search_code.

Not a fallback of search_bodies — its complement. The two answer different questions and reach different text:

  • search_bodies answers WHICH DEFINITION holds the pattern, and takes the query literally (no wildcard, space = AND).

  • search_content answers WHICH FILES the topic touches, and widens the query: every term gets a trailing * and the terms are OR-joined. The wider query reaches text the literal one misses — measured on one project, SELF_TEST found 6 files here and the same word found 5 through search_bodies, the extra file holding the comment Self tester.

For the footprint of one feature, run both.

Results are file-level — one entry per matching file, with match_lines for the lines that hold a query term. project_only=True filters to is_project = 1 files; the default False includes the vendor SDK files.

When files_fts is missing (legacy index), falls back to LIKE search on files.content — results include _fallback: "like" and no snippet highlighting. Run fw-context index to upgrade. The LIKE search splits the query on spaces and underscores and needs EVERY word as a substring (AND, not OR).

Read-only: yes. Requires the FTS5 index with file content. May auto-reindex stale files (non-blocking) — see search_code.

Args: query: FTS5 search terms. 1-3 words. Bare multi-word queries are OR-joined (prefix-wildcarded). Prefer single-word queries. E.g. 'InterruptIn', 'extern C', '#define'. project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. limit: Maximum results of one page (default 20, max 100). offset: Skip this many results. Reads the next page of a topic that many files touch; the page notice names the offset to use. project_only: When True, filter to project code only (files with is_project = 1). variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: list of dicts. The page notice leads the answer — total, offset, shown, more — and each that follows holds: file, language, mtime, _match_snippet (highlighted excerpt around the match).

Also, when it carries an answer:

* ``match_lines`` (list[int]) — line numbers of the lines that hold
  a query term, up to 20.  They are the line numbers of the file
  itself: an inactive ``#ifdef`` branch is a blank line, thus the
  count never shifts.  Cite ``file:line`` from here.

  The field is absent when FTS5 matched a variant of the token that
  the term is not a substring of — ``SELF_TEST`` matches the file
  that writes ``Self tester``, and no line holds ``self_test``.
  Read ``_match_snippet`` in that case.

No match gives ``[]``.  A dict with ``error`` means the query
failed.  A query that FTS5 refuses to parse prepends a dict with
``warning`` + ``hint`` — the answer then comes from the LIKE path
and carries ``_fallback: "like"``.  When a file that the answer
names changed on disk, a dict with ``warning`` comes first; an
empty answer gets it too when indexed files changed.
semantic_searchA

Semantic search using pre-computed libclang symbol embeddings. Finds symbols by meaning, not by text — matches concepts even when query words don't appear literally in the code. Uses cosine similarity over variable-dimension embeddings generated during fw-context index. Dimensions vary by model: mxbai-embed-large → 1024, qwen3-embedding → 4096.

When to prefer over search_code: When you're describing a concept rather than searching for a known keyword. Examples:

  • "parcel locker state" finds door-state and shipment methods even though "parcel" and "locker" don't appear in their names.

  • "cell modem" finds _socket_t and ModemMsg* classes.

  • "delivery box" finds set_shipment and get_zrtdata.

  • "power consumption" finds get_load_power and INA260 class.

When to prefer search_code instead: When you know the exact keyword or symbol name ("fram_write", "cbor encode"). FTS5 is faster and more precise for lexical matches.

Threshold guidance (mxbai-embed-large model):

  • 0.50 — exploratory: more results, lower precision

  • 0.55 — balanced (~1000 results)

  • 0.60 — precise: ~175 avg, high precision (default)

  • 0.65 — strict: few results, may miss relevant symbols

Source-aware ranking: the similarity of a project symbol is multiplied by 1.2, and the similarity of every other symbol by 0.85. The index marks each file as project code or not, thus the two tiers are all there are. The multiplied score sets the order. _similarity in the result holds the raw cosine similarity, thus it is on the same scale as threshold and never above 1.0.

Requires [llm] enabled = true, embeddings in the index, and for an Ollama embedding model a running Ollama server at llm.ollama_url. A sentence-transformers or ft:// model embeds the query locally and needs no Ollama. Without one of them, the tool falls back to a lexical search with a warning.

Read-only. No side effects.

Args: query: Natural language description of what you're looking for. Be specific — 5–15 words works best. project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. threshold: Minimum cosine similarity (0.0-1.0). Default 0.60. The tool applies it to the raw cosine similarity, before the source boost. limit: Maximum number of results on one page (default 20, max 100). offset: Skip this many results. Reads the next page; the page notice names the offset to use. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: The page notice {total, offset, shown, more, hint} comes before the symbols. total counts every symbol above threshold, as far as one KNN query reaches (4096 vector rows; a symbol of several chunks takes several of them). When the KNN stopped there with its last row still above the threshold, the notice also holds total_capped: True, and total is a lower bound. A page after the last symbol gives one info dict that names the total.

Then a list of dicts, each with: name, qualified_name, kind, file, line,
is_definition, signature, docstring, plus ``_method``
(``"embedding"`` or ``"search_code_fallback"``), ranked in windows
of 200 raw ranks: inside a window by the boosted similarity, then
by the symbol id.  Thus the boost moves a symbol only inside its
window.  The reranker (when
``llm.reranker_model`` is set) reorders the first symbols of a
page, and moves no symbol to another page.  An ``"embedding"``
result also holds ``_similarity``, the raw cosine similarity.  A
symbol also holds ``summary``, ``inputs`` and ``outputs`` when
``fw-context index --analyze`` wrote them — model text, never a
fact to quote — and ``_rerank_score`` when
``llm.reranker_model`` is set.

When the index of the build of the query is not current (for
example, compile_commands.json changed after the last index run),
a leading dict holds ``warning`` and ``index_state``.  The answer
then comes from the last index run.

Fallback: when the LLM is disabled or not running, or the index
holds no embeddings, or the embedding model fails, this tool runs
a plain FTS5 symbol search (the first step of ``search_code``,
with no kind and no relaxation), one page of it.  A leading dict
holds a ``warning`` with the reason, the page notice follows, and
each symbol carries ``_method: "search_code_fallback"``.  A
changed result file or compile_commands.json adds another leading
``warning``.  When the lexical search finds nothing too, the answer
is one ``warning``.

No match above ``threshold`` runs the same lexical fallback, with a
``warning`` that suggests a lower threshold.  When a phase of the
search failed, that ``warning`` gives the error of the phase.  When
the best raw similarity of all matches is below 0.68, the answer is
the page notice and one dict: a ``warning`` that the matches are
likely unrelated, ``_fallback_suggestion: "search_code"``,
``_best_similarity``, and the matches of the page in ``_results``.

One dict with ``error`` means the query failed — check that key
first.
smart_searchA

Natural-language search: an LLM generates FTS5 keywords, then searches the libclang index. Finds concepts by meaning rather than exact text match. Prefer this when you don't know the exact keywords and want to describe what you're looking for ("how does the modem connect?", "handle BLE pairing failure").

Read-only: yes. Slow (10-30 s) — delegates to the full SMART_SEARCH pipeline (translate → rough_search → llm_query → fts5_search → refine → embedding → adaptive_fusion → deduplicate → expand_context → format).

Multi-phase approach:

  1. Translate non-English queries

  2. Rough search to gather sample symbols for naming conventions

  3. LLM sees those samples + query and generates FTS5 terms

  4. FTS5 search with generated terms

  5. Refine: LLM checks results and course-corrects query terms

  6. Semantic embedding search (cosine similarity re-rank)

  7. Deduplicate, score, and format results

When to prefer over search_code: When you don't know the exact keywords and want to describe what you're looking for ("how does the modem connect?", "handle BLE pairing failure").

Fallback: When LLM is unavailable, falls back to direct FTS5 search with word-split terms from the query. When the LLM call or the embedding step fails, a metadata dict gives the reason in warning or detail.

Args: query: Natural language description of what you're looking for. Be specific — 5–15 words works best. project_root: Project root directory. Auto-detected from CWD if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. limit: Maximum number of results on one page (default 20, max 100). The search itself always ranks up to 100 symbols. offset: Skip this many results of the previous search of the same query. 0, the default, is a NEW search. A page with an offset comes out of the stored answer of the last search; when the last search was of another query (or another project, or a build that a reindex changed), the tool searches again first. An index run that keeps the build keeps the stored answer. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: list of dicts with metadata entries (_generated_queries, _rough_queries, _translated_from + _translated_to), then the page notice {total, offset, shown, more, hint}, followed by symbol results with name, qualified_name, kind, file, line, is_definition, signature, docstring. total counts the symbols of the answer (at most 100). A page after the last symbol gives one info dict that names the total. A symbol also holds summary, inputs and outputs when fw-context index --analyze wrote them. A model wrote that text, and the code did not: never quote it as a fact.

When the pipeline passes the LLM timeout (``[llm] timeout`` of the
project), a leading dict holds ``_partial: True``, a ``warning``
with the timeout, and a ``hint``.  The symbols that follow are the
ones that the steps before the timeout found, without the later
re-rank steps; there can be none.  This answer has no page notice
and is not stored, thus a next page searches again.  Make the query
more specific, or increase the LLM timeout.

When the index of the build of the query is not current (for
example, compile_commands.json changed after the last index run),
a leading dict holds ``warning`` and ``index_state``.  The answer
then comes from the last index run.

No match gives the metadata entries and a dict with ``info``.  One
dict with ``error`` means the query failed — check that key first.
find_all_callers_recursiveA

Find all transitive C/C++ callers — who calls name, directly or indirectly, through the libclang call graph including function-pointer edges, implicit constructors, and synthetic dispatch edges. libclang-powered: follows function-pointer assignments and ISR vector registrations across the full call tree.

Use for impact analysis: "if I change this function, how far does the ripple go?" Returns callers at depth 1 (direct), depth 2 (callers of callers), up to max_depth (default 5). Results are deduplicated — each caller appears once at its shortest distance to the target.

Edge types traversed: Includes call, indirect (function pointers / ISRs), implicit_construct (constructors reachable through file-scope global objects), and dispatch (synthetic edges through event loops and thread starts).

Limitation — ambiguous name resolution: When a source-line fallback cannot disambiguate which method is called (e.g. attach() matching both Timeout::attach and SerialBase::attach), the edge is conservatively omitted to avoid false callers. If you suspect a missing caller, verify with search_bodies("target_name") and find_indirect_targets.

For a flat, single-level caller list use find_callers (faster). For the reverse direction use find_callees_recursive.

Read-only. No side effects. Requires the reference index (fw-context index — refs on by default). BFS from the target outward; performance scales with call-graph fan-out.

Args: name: Symbol name to find transitive callers of. project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. max_depth: Maximum BFS depth for transitive search (default 5). limit: Maximum results (default 50). offset: Skip this many results. Reads the next page of a deep call tree; the page notice names the offset to use. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: list of dicts, each with: name (str — the caller), qualified_name (str), kind (str), signature (str), depth (int — distance from the target), file (str — absolute).

This tool gives no line, because one caller can hold several call
sites.  For the line of each call use ``find_callers`` on the name
that this tool reports.

When *name* matches more than one symbol, such as two classes with
a method of the same name, the answer holds the callers of all of
them.  A ``warning`` dict then comes first and names the symbols,
and each result carries ``target_qualified_name``, which tells the
symbol that it calls.  Give the full qualified name to ask about
one symbol only.

The page notice ``{total, offset, shown, more, hint}`` comes
before the rows of the answer.  ``total``
counts every row of the whole answer.  When ``more`` is true, the
``hint`` names the call that reads the next page.  A page after
the last row gives one ``info`` dict that names the total.

Never empty: one dict with ``error`` (cannot resolve) or ``info``
(no results) replaces the results.  Check both keys first.
find_call_pathA

Find call paths between two C/C++ functions via BFS in the libclang call graph, including function-pointer edges, ISR vector registrations, implicit constructors, and synthetic dispatch edges (event loops, thread starts). libclang-powered: follows function-pointer edges and ISR vector registrations that text-based search cannot resolve.

Use to answer "how does A reach B?" — e.g. tracing how a high-level event handler eventually calls a low-level driver. Returns up to 5 distinct paths in BFS order (short paths first), each with depth (edge count) and chain (e.g. "main → app_run → modem_init").

Edge types traversed: The BFS includes call, indirect (function pointers / ISRs), implicit_construct (global/static object constructors), and dispatch (synthetic edges through event loops like EventQueue::dispatch_forever and thread starts like Thread::start).

Limitations:

  • Dispatch bridges: callbacks registered through EventQueue::call_every, k_work_submit, or xTimerStart reach their dispatch entry point (dispatch_forever, z_work_q_main) through a built-in map for mbed-os, Zephyr, and FreeRTOS. Add other RTOS patterns in [call_graph.dispatch_bridges] (.fw-context/config.toml); a bridge whose entry symbol is not in the index is skipped silently.

  • Ambiguous fallback names: for a call that libclang cannot resolve (template-obscured _timeout.attach(...)), a source-line regex matches the method name. When several methods share that unqualified name and neither the receiver field type nor the caller class disambiguates, fw-context creates NO edge — conservative, to avoid false paths.

  • Global constructors: file-scope implicit_construct edges hang off a synthetic <global ctors> node between main and every global constructor. Any query that can reach main uses it, not only a query that starts at main.

On an empty result that you expected to hold a path: look for async dispatch (search_bodies("call_every"), search_bodies("attach")), trace the intermediate symbols with find_callers, raise max_depth, and check the function-pointer wiring with find_indirect_call_sites / find_indirect_targets.

For one-sided exploration use find_all_callers_recursive (who reaches this?) or find_callees_recursive (what does this reach?). For exact call-graph verification use find_callers or find_references.

Read-only. No side effects. Requires both symbols to be in the index and refs enabled (fw-context index — refs on by default).

Args: from_name: Starting symbol for path search. to_name: Target symbol to find path to. project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. max_depth: Maximum BFS depth for path search (default 10). The search runs from both ends, and each end goes up to max_depth hops, thus a path can hold up to 2 × max_depth edges. No clamp holds this number. What bounds a deep search is the node budget of the walk — 5000 expansions — thus a large depth gives up on that budget and not on the depth. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: At most 5 paths, each a dict with: depth (edge count, int), chain (str — e.g. "main → app_run → modem_init"), target_usr (str — the USR of the symbol the path ends at, which tells two overloads apart). When no path exists within the depth limit, the list holds one info dict.

The number of paths cannot be counted, thus this tool has no page
notice and no ``offset``.  A trailing ``info`` dict says when paths
can be left out: the search found 5 paths and stopped, it spent
its budget of node expansions, or a direct call answered and the
longer paths were not searched.  After a spent budget an empty
answer says that a path can still exist, and NOT "no path".  The
paths come in a fixed order: the same query gives the same paths.

When *to_name* matches more than one symbol, the search reaches
all of them.  A ``warning`` dict then comes first and names the
symbols, and each path carries ``target_qualified_name`` next to
``target_usr``.  Give the full qualified name to ask about one
symbol only.

Never empty: one dict with ``error`` (cannot resolve) or ``info``
(no results) replaces the results.  Check both keys first.
get_vector_tableA

Read the interrupt vector table, and say what services each interrupt.

The vector table is how an interrupt reaches code. Nothing CALLS a handler — the hardware reads a slot and jumps — so a handler has no caller, and every other tool shows it as unreferenced. This tool reads the table itself, from the assembly the build compiles.

Use it to answer "which interrupts does this firmware service", to find the handler for one interrupt, or to find the interrupts that reach the trap loop.

The slot number is the position in the table. What that position means belongs to the architecture, not to the index. On Cortex-M slots 0 to 15 are the system exceptions and slot 16 + n is external interrupt n, so TIM2_IRQHandler in slot 44 is TIM2_IRQn = 28. On other architectures the same position means something else.

The status field says what services the interrupt:

  • "c" — a definition outside assembly. Code runs. When the index also holds the weak definition that this one replaced, the row has overridden with its file and line.

  • "assembly" — an assembly definition that is not an alias. Assembly services the interrupt.

  • "unhandled" — an assembly name that is an alias (.thumb_set) of another symbol, and nothing overrode it. The row holds aliases, the name, file and line of the alias target. A CMSIS startup file aliases each unserviced interrupt to Default_Handler, which is an infinite loop. If the interrupt fires, the device stops.

  • "runtime" — the image holds that same alias, and the code installs a real handler into this slot by calling NVIC_SetVector. A target that defines CMSIS_VECTAB_VIRTUAL keeps its vector table in RAM and fills it that way, so the interrupt IS serviced once the registering code has run — and not before it. The row holds installed, one entry per call site with the handler name, its file and line, and at, where the registration happens. Follow at to see WHEN it happens: on the Mbed project us_ticker_irq_handler reaches slot 25 from us_ticker_init, so the tick source is unserviced until the ticker starts. A row with a real static definition keeps its own status and still carries installed.

  • "data" — the slot holds an address BUILT from the symbol it names (.word z_main_stack + CONFIG_MAIN_STACK_SIZE), so the symbol is a base and nothing jumps to it. On Cortex-M this is slot 0 of a Zephyr table: the initial stack pointer. Do not read it as code.

  • "linker" — the linker script gives the address and no compiled file defines the name. Slot 0 holds the initial stack pointer, not a handler, and looks like this. When the index read the script, file and line name the assignment in it — on the Mbed project, __StackTop at .link_script.ld:148. Do not read this row as code: there is no function to follow.

  • "dispatcher" — the slot reaches a function that holds more than one slot of this table AND calls through a pointer. It cannot be servicing one particular interrupt; it decides at run time where to go. Zephyr fills every external IRQ slot with _isr_wrapper, which reads the interrupt number and jumps through _sw_isr_table. Follow it: get_symbol_context on the name, then find_references on the table it uses. A handler that merely calls one registered callback is NOT this — it holds a single slot and keeps "c".

A "c" row with overridden is the CMSIS pattern: the startup file defines each handler weakly, the project defines the same name again, and the linker keeps the strong one.

Three sources are read, and source says which one a row came from:

  • "assembly" — a table of address words, .word or .long in a vector section, which is what a CMSIS startup file writes.

  • "c" — an array whose elements are addresses of functions, which is what a build that generates its table produces. Zephyr writes its external interrupts this way, with gen_isr_tables.py. These rows also carry table_name, the array the slot belongs to.

  • "build" — the registration the build itself recorded, for a slot the other two could not name. A generator writes a resolved ADDRESS into every slot that is in use, so those slots have no name in the source at all — and they are the interrupts the firmware actually services. Measured on an nRF54L application: 284 of 290 slots name the spurious stub, and the 6 without a name are IRQ 89, 198, 219, 228, 269 and 270, which these rows fill in.

    Such a row can carry argument, the symbol the build passes to the handler. Read it as an argument and not as a second handler: behind the nrfx_isr shim it is the real worker (nrfx_power_clock_irq_handler), while for another driver it is the device (__device_dts_ord_116). When the build enables run-time registration (CONFIG_DYNAMIC_INTERRUPTS), the interrupts dict says so, because an interrupt connected at run time leaves nothing to read and the rows are then not all of them. A "build" row has file empty and line 0 when the handler name has no single definition in the index.

Recognition is by shape, never by name, so any array of function addresses is reported and the row names its table. A table of interrupt handlers and a table of state machine steps are the same construct, and table_name is how they are told apart.

Slot numbers are not joined across tables. Each slot is the index inside its own table, so two tables both start at 0 — read slot together with table_name and source. They are not renumbered into one run because the index does not hold the length of the assembly table, only its occupied slots, and an offset derived from that would be silently wrong for every entry of a 290-entry table.

A coverage row follows the slots for each table longer than the number of slots that name a function. It says how many of the declared elements were named and which slot numbers were not, because a name is not always there to be read: an element can be a zero, or an address the linker resolved before the table was written.

Read it in both directions. A hole in a table of handlers is a vector nothing services. A hole in Zephyr's _sw_isr_table is the opposite — measured on an nRF54L application, 284 of 290 slots name the spurious stub and the 6 without a name are the interrupts in use. The tool reports where to look; which meaning applies depends on the table.

An interrupts row answers "which are unserviced" wherever the build recorded its registrations, and it is the answer under unhandled_only too. The row-level unhandled status is read from an alias edge, which a CMSIS startup writes and a generator does not — measured, zero unhandled rows on all eleven images of a Zephyr project against 39 to 72 on four CMSIS and Mbed ones. The registrations settle it from the other side: what the build connected is the whole list, so anything else has nothing servicing it, and no handler has to be recognised by name.

The complement is taken over the length of the table, NOT over the slots that hold a stub. Measured on an mcuboot image: its software table names 44 of 48 slots, and one of those 44 is uarte_0_direct_isr, an interrupt wired straight into the vector table. It IS serviced, and counting stubs would report it as not.

What is still not covered: an architecture that builds its table from branch instructions (arm64, Xtensa, MIPS) writes no table of addresses in either form. A handler whose address the build resolved at link time has no name to report either — coverage names its slot but not the function. For an interrupt this tool cannot show, find_references on the handler name still gives every reference the index holds.

Read-only. No side effects. Requires an index of the assembly and the reference index (fw-context index — refs on by default).

Args: project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. unhandled_only: When True, return only the "unhandled" slots. limit: Maximum slots (default 400, max 1000). offset: Skip this many slots. Reads the next page; the page notice names the offset to use. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: The page notice {total, offset, shown, more, hint} comes before the rows of the answer: total counts every slot of every table. Then a list of slot dicts sorted by source, then table, then slot. Each holds: slot (int), name, file, line, source ("assembly", "c" or "build"), status ("c", "assembly", "unhandled", "runtime", "data", "linker" or "dispatcher"), and table_file and table_line (where the slot is written). Every path in this answer is absolute, at included. A "c" source row also holds table_name and table_usr. A "c" status row can hold overridden, a dict with file and line. An "unhandled" row holds aliases, a dict with name, file and line. Any assembly row can hold installed, a list of dicts with name, file, line and at.

Never empty: one dict with ``error`` (no index) or ``info`` (no
vector table in this build).  Check both keys first.  A dict with
``coverage`` follows the slots for each table that has unnamed
elements, and a dict with ``interrupts`` says which are connected
and which are not — the latter in both modes.  Neither is subject
to the page: they describe the whole table, the longest table is
where they matter most, and they come after the slots of each
page.  A page after the last slot gives one ``info`` dict that
names the total, and then those two.
find_callees_recursiveA

Find all transitive C/C++ callees — what name calls, directly or indirectly, through the libclang call graph including function-pointer edges, implicit constructors, and synthetic dispatch edges. libclang-powered: follows function-pointer calls and indirect invocations across the full dependency tree.

Use for dependency analysis: "what does this function depend on to do its job?" Returns callees at depth 1 (direct), depth 2 (callees of callees), up to max_depth (default 5). Results are deduplicated by shortest distance.

Edge types traversed: Includes call, indirect (function pointers / ISRs), implicit_construct (constructors reachable through file-scope global objects), and dispatch (synthetic edges through event loops and thread starts).

Limitation — ambiguous name resolution: When a source-line fallback cannot disambiguate which method is called, the edge is conservatively omitted to avoid false callees. If you suspect a missing callee, verify with search_bodies("target_name").

For direct callees only, get_symbol_context gives a faster flat list along with the function body and callers. For the reverse direction use find_all_callers_recursive.

Read-only. No side effects. Requires the reference index (fw-context index — refs on by default).

Args: name: Symbol name to find transitive callees of. project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. max_depth: Maximum BFS depth for transitive search (default 5). limit: Maximum results (default 50). offset: Skip this many results. Reads the next page of a deep call tree; the page notice names the offset to use. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: list of dicts, each with: name (str — the callee), qualified_name (str), kind (str), signature (str), depth (int — distance from the source), file (str — absolute).

This tool gives no line, because one function can call the same
callee several times.  For the line of each call use
``find_callers`` on the name that this tool reports.

When *name* matches more than one symbol, the answer holds the
callees of all of them.  A ``warning`` dict then comes first and
names the symbols, and each result carries
``target_qualified_name``, which tells the symbol that calls it.
Give the full qualified name to ask about one symbol only.

The page notice ``{total, offset, shown, more, hint}`` comes
before the rows of the answer.  ``total``
counts every row of the whole answer.  When ``more`` is true, the
``hint`` names the call that reads the next page.  A page after
the last row gives one ``info`` dict that names the total.

Never empty: one dict with ``error`` (cannot resolve) or ``info``
(no results) replaces the results.  Check both keys first.
find_callersA

Find who calls a C/C++ function — direct calls AND indirect via function pointers, callbacks, and struct/array init lists (such as a vector table that the build generates in C). libclang-powered: detects function-pointer assignments that text-based search cannot see. A slot of an assembly vector table (ref_kind "vector") and an NVIC_SetVector install ("runtime_vector") are not callers: use find_references or get_vector_table for those.

Falls back to macro lookup when the symbol is not found as a function/method: returns the macro definition (kind="macro") and files that use it (ref_kind="macro_use").

Use when you need a quick, flat list of immediate callers. For the full transitive call tree (who calls this indirectly through other functions), use find_all_callers_recursive. For all references including reads and member accesses, use find_references. For a path between two specific symbols, use find_call_path.

Read-only. No side effects. Requires the reference index (fw-context index — refs are on by default). Only direct call sites are returned; callers more than one hop away are not included.

Indirect edges (ref_kind: "indirect") are detected when a function pointer references a function through:

  • Call arguments: callback(&Class::method, this), EventQueue::call_every(ms, obj, &handler)

  • Assignments: driver.onData = &handleData, global_cb = &handler

  • Variable initializers: static void (*fp)(int) = &handler

  • Struct/array init lists: {.on_data = &handler}, {&fn_a, &fn_b}

Args: name: Symbol name to find callers of. Uses the same three-tier resolution as find_references (exact name, exact qualified, suffix LIKE). project_root: Project root directory. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. limit: Maximum results of one page (default 50, max 200). offset: Skip this many results. Reads the next page of a symbol with many call sites; the page notice names the offset to use. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: The page notice first — total, offset, shown, more — then a dict per call site with: file, line, ref_kind ("call", "indirect", "implicit_construct", or "macro_use"), caller (enclosing function name), caller_kind ("function", "method", …). Macro fallback puts a dict with kind="macro", signature (#define free: NAME or NAME(a, b)), is_function_like, value (the replacement text ALONE) and expanded_value between the notice and the rows; that answer pages too, and its total counts the uses in active code only — a use inside a comment is not one.

When *name* matches more than one symbol, such as two classes with
a method of the same name, the answer holds the call sites of all
of them.  A ``warning`` dict then comes first and names the
symbols, and each result carries ``target_qualified_name``.  Give
the full qualified name to ask about one symbol only.

A virtual method with no call site of its own answers with the
call sites of its base method and of the other overrides of that
base.  A ``warning`` dict leads such an answer, and each row
carries ``recorded_against`` (the symbol the index records the
call against) and ``reaches_this_symbol`` (true for a call on the
base method, which reaches this override at run time; false for a
call on a sibling override, which does not).  Read both before you
report a caller count.

Never empty: one dict with ``error`` (symbol not resolved) or
``info`` (no references of this kind).  Check both keys first.
find_dead_codeA

Find C/C++ functions that are defined but never called — libclang-powered dead code detection across the entire indexed codebase. Distinguishes called from uncalled symbols globally, not just within a single file — text-based search cannot determine whether a function is actually reachable.

What "dead" means: zero references of any ref_kind in the index — no call, no read, no function-pointer use, no vector slot. Only functions, methods, constructors, and destructors with a definition are checked. This is a single-layer reference check, NOT a reachability analysis from the entry points (main, ISR, exported symbols): a function that only a second dead function calls still has a reference, thus this tool does not mark it. For transitive reachability, trace from your entry points with find_callees_recursive.

The status field splits the results:

  • "dead" — no reference at all. Likely unused.

  • "possibly_dead" — assigned to a function pointer (ref_kind="indirect") and never called directly, but no call site through that pointer resolved. Unindexed code or a type-erased API can still call it. Treat it as uncertain, and check each hit with find_indirect_targets before you delete anything.

fw-context detects a constructor call through global/static object and member-field initialization as an implicit_construct reference.

ISRs: a handler that an assembly vector table names (.word) has a vector reference, and one that NVIC_SetVector installs has a runtime_vector reference, thus neither is reported. A handler in a table that the build generates in C has an indirect reference and can show as possibly_dead. A handler that no readable table names (a slot with a linker-resolved address; arm64, Xtensa, MIPS tables of branch instructions) shows as dead. Use get_vector_table to check.

Other known false positives: constructors from factories, virtual method overrides, and weak-aliased symbols. Always verify before you delete.

project_only=True (default) excludes the SDK and vendor paths through the is_project column, which follows the vendor_paths and project_paths config. Set project_only=False to see the vendor results too. No config default is added to exclude_paths.

Read-only. No side effects. Requires the reference index (fw-context index — refs on by default).

Args: project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. limit: Maximum results of one page (default 100, max 200). offset: Skip this many results. Reads the next page; the page notice names the offset to use. exclude_paths: Additional LIKE patterns to exclude (user-supplied tool parameter, not config). E.g. ['lib/%']. project_only: When True (default), filters to is_project = 1 symbols. Set False to see all results. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: The page notice first — total, offset, shown, more — then a dict per function with: name, qualified_name, kind, signature, file (str — absolute), line, status ("dead" or "possibly_dead"), and reason (str — explains why the function is classified as dead or possibly dead). A "possibly_dead" row also holds indirect_refs (str — up to 3 file:line pointer uses, absolute paths, joined with ", "). "dead" rows come first.

Never empty: one dict with ``error`` or ``info`` replaces an empty
result.  Check both keys first.
find_hotspotsA

Find the most-called C/C++ functions ranked by caller count — libclang call-graph hotspot detection. Identifies functions with the most architectural weight — good targets for refactoring, optimization, or extra testing. Text-based search cannot aggregate caller statistics across the full call graph.

Use for high-level impact assessment: changing a hotspot affects many call sites. The result tells you which functions carry the most "architectural weight" across the entire codebase.

By default, SDK/vendor paths are auto-excluded so hotspots reflect project code. Use project_only=False to see all results including vendor code.

For the callers of a specific hotspot, follow up with find_callers or find_all_callers_recursive.

Read-only. No side effects. Requires the reference index (fw-context index — refs on by default).

Args: project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. limit: Number of top-called functions per page (default 20, max 50). offset: Skip this many results. Reads further down the ranking; the page notice names the offset to use. project_only: When True (default), filters to is_project = 1 symbols so hotspots reflect project code. exclude_paths: Additional LIKE patterns to exclude (user-supplied tool parameter). E.g. ['lib/%']. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: The page notice first — total, offset, shown, more — then a dict per function with: name, qualified_name, kind, signature, file (str — absolute), line, caller_count (int — number of call and indirect references to it).

Never empty: one dict with ``error`` or ``info`` replaces an empty
result.  Check both keys first.
find_indirect_call_sitesA

Find indirect call sites where a C/C++ function pointer field or variable is invoked. libclang-powered: resolves calls through function pointers (e.g. driver.onData(buf, len)), which text-based search cannot detect.

Returns locations where a function pointer is called through a field access (driver.onData(buf, len)) or variable dereference (stored_callback(42)).

Read-only. No side effects. Use this to answer "where is this function pointer invoked?" as opposed to find_callers which answers "who calls this function?" and find_references which answers "where is this symbol read or assigned?"

For the reverse query — which functions are assigned to a given field or parameter — use find_indirect_targets.

Requires the reference index (fw-context index — refs on by default).

Args: name: Name of the function pointer field or variable. E.g. "onData" finds every call through a field named onData. Uses three-tier resolution: exact name, exact qualified, suffix LIKE; a function pointer parameter matches by its exact name. project_root: Project root directory. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. limit: Maximum results (default 50, max 200). offset: Skip this many results. Reads the next page; the page notice names the offset to use. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: list of dicts, each with: file, line, expr_text (the callee expression, e.g. "driver.onData"), target_usr, target_name, fn_ptr_type (the function pointer type signature), caller (enclosing function name), caller_kind.

The page notice ``{total, offset, shown, more, hint}`` comes
before the rows of the answer.  ``total`` counts every row of the whole answer.  When
``more`` is true, the ``hint`` names the call that reads the next
page.  A page after the last row gives one ``info`` dict that
names the total.

Never empty: one dict with ``error`` (cannot resolve) or ``info``
(no results) replaces the results.  Check both keys first.
find_indirect_targetsA

Find functions assigned to a C/C++ function pointer field or variable. libclang-powered: links assignment sites to call sites via the field's unique symbol reference, which text-based search cannot resolve.

Links assignment sites (driver.onData = &handler) to call sites (driver.onData(buf, len)) via the field's USR.

Returns each function that could be invoked through the named function pointer, showing both the assignment location and the call site(s). When a function is assigned but no call site is found, call_file and call_line are null — the assignment exists but the invocation may be in unindexed code.

For the reverse query — where is this field or parameter called — use find_indirect_call_sites.

Read-only. No side effects. Requires the reference index (fw-context index — refs on by default).

Args: name: Name of the function pointer field, variable, or parameter. E.g. "onData" finds every function assigned to a field named onData. Matches the pointer by exact name, exact qualified name, or ::name suffix. It also matches the ASSIGNED function by exact name or substring, thus a row can be an assignment of a function whose name holds name. project_root: Project root directory. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. limit: Maximum results (default 50, max 200). offset: Skip this many results. Reads the next page; the page notice names the offset to use. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: list of dicts, each with: rhs_name (assigned function), rhs_qname, fn_ptr_type, method (assignment/call_arg/var_init/ init_list), assign_file, assign_line, assign_caller, call_file, call_line, call_expr_text.

An entry can carry ``_note`` (str) when the callee is
template-obscured: the call site is then missing, or comes from
the type-based fallback.  ``_note`` also marks a call site that
the ``fn_ptr_type`` fallback found: a call through another pointer
of the same type.  A ``call_expr_text`` of ``"<inferred via
class hierarchy>"`` marks a call site that a fallback found, not
the field's USR.  Read both before you act on ``call_file`` and
``call_line``.

The page notice ``{total, offset, shown, more, hint}`` comes
before the rows of the answer.  ``total`` counts every row of the whole answer.  When
``more`` is true, the ``hint`` names the call that reads the next
page.  A page after the last row gives one ``info`` dict that
names the total.

Never empty: one dict with ``error`` (cannot resolve) or ``info``
(no results) replaces the results.  Check both keys first.
find_referencesA

Find ALL references to a C/C++ symbol — calls, reads, member accesses, function pointer registrations, vector table slots, and macro usages. libclang-powered: detects function-pointer registrations (vector table entries, callback attachments, NVIC_SetVector installs) that text-based search cannot see.

Falls back to macro lookup when the symbol is not found as a function/method: returns the macro definition (kind="macro") and files that reference it (ref_kind="macro_use").

Read-only. No side effects. Returns every reference in the indexed codebase, including call sites, variable reads, struct member accesses, indirect function-pointer references, and macro usages. Requires the reference index (fw-context index — refs on by default).

For direct callers only use find_callers. For transitive callers use find_all_callers_recursive. For call paths between two symbols use find_call_path.

Args: name: Symbol name to find all references of. project_root: Project root directory. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. limit: Maximum results of one page (default 50, max 200). offset: Skip this many results. Reads the next page of a symbol with many references; the page notice names the offset to use. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: The page notice first — total, offset, shown, more — then a dict per reference with: file, line, ref_kind, caller, caller_kind. ref_kind is one of: "call", "ref", "member", "indirect" (function-pointer reference in arguments, assignments, initializers, or init lists), "implicit_construct" (implicit constructor call from global/static object or member-field initialization), "dispatch" (synthetic edge from a dispatch bridge such as EventQueue::call_every), "vector" (a slot of an assembly vector table names the symbol; file and line are the table entry), "vector_data" (a slot holds an address computed from the symbol, such as the initial stack pointer), "runtime_vector" (an NVIC_SetVector call installs the symbol at run time), "alias" (.thumb_set: another assembly name for the symbol), "macro_use" (macro usage in file). Macro fallback puts a dict with kind="macro", signature (NAME or NAME(a, b)), is_function_like, value (the replacement text ALONE) and expanded_value between the notice and the rows; that answer pages too, and its total counts the uses in active code only — a use inside a comment is not one.

When *name* matches more than one symbol, the answer holds the
references of all of them.  A ``warning`` dict then comes first and
names the symbols, and each result carries
``target_qualified_name``.  Give the full qualified name to ask
about one symbol only.

A virtual method with no reference of its own answers with the
references of its base method and of the other overrides of that
base.  A ``warning`` dict leads such an answer, and each row
carries ``recorded_against`` and ``reaches_this_symbol`` (true only
for a row on the base method), as in ``find_callers``.  Read both
before you report a reference count.

Never empty: one dict with ``error`` (symbol not resolved) or
``info`` (no references).  Check both keys first.
find_wrapper_callersA

Find C/C++ wrapper classes that call methods of a driver class — libclang-powered adapter pattern detection. Traces method ownership across class boundaries to reveal the wrapper/adapter architecture (e.g. UART wraps UART_DRIVER). Text-based search cannot distinguish which class owns each method call.

Returns wrapper methods grouped by wrapper class, showing which driver methods each wrapper calls. Useful for understanding the adapter/wrapper architecture (e.g. UART wraps UART_DRIVER).

For the reverse perspective — finding who calls a specific driver method — use find_callers. For class member listing use get_class_members.

Read-only. No side effects. Requires the reference index (fw-context index — refs on by default).

Args: class_name: Driver class name to find wrappers for. E.g. 'UART_DRIVER' or 'hal::UART_DRIVER'. The name is case-sensitive. A bare name that names no top-level class matches the class of that name in each namespace, but not a class whose name only ends with it. The methods of a class nested in the driver class count as driver methods. project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. limit: Maximum wrapper classes on one page (default 20, max 100). Each class comes with all of its methods and calls into the driver (call and indirect references), thus a class is never split over two pages. offset: Skip this many wrapper classes. Reads the next page; the page notice names the offset to use. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: list of dicts, each with: wrapper_class (str — "(global)" for a free function), method_count (int), methods (list of dicts — each with method, qualified_name, kind, file (str — absolute path of the file that holds the body of that method), and calls (list of dicts — driver_method (str) and line (int) of each call into the driver))).

The path sits on the method, not on the class, because one wrapper
class often spans several files.

The page notice ``{total, offset, shown, more, hint}`` comes
before the rows of the answer: ``total`` counts every wrapper class.  A page after the last
class gives one ``info`` dict that names the total.

Never empty: one dict with ``error`` (cannot resolve) or ``info``
(no results) replaces the results.  Check both keys first.
trace_data_flowA

Trace how C/C++ data of a given type flows to a target function via libclang call paths. libclang-powered: finds functions by type signature and maps call paths through the full call graph, which text-based search cannot trace across translation units.

Finds definitions whose signature mentions type_name (substring match, any symbol kind, most-called first), then looks for call paths from those to to_symbol. Returns a data flow map — useful for understanding how a data structure travels through the system to its destination.

Works best for synchronous driver stacks (e.g. sensor read → I2C write). Cannot follow async flows (message queues, interrupts, RS485 callbacks). For exact call-graph queries use the find_* family; verify specific paths with find_call_path.

Read-only. No side effects. Requires the reference index (fw-context index — refs on by default).

Args: type_name: Type name to trace. E.g. 'SensorData' or 'Config::SensorData'. to_symbol: Target symbol name. E.g. 'uart_send' or 'UART_DRIVER::send'. project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. max_depth: Maximum call path depth (default 8, max 20). limit: Maximum source functions to trace (default 15, max 15). offset: Skip this many source functions. Reads the next page; the page notice names the offset to use. timeout_ms: Maximum total execution time in milliseconds (default 30000). Clamped to 1000–300000. The budget is for one page. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: The page notice {total, offset, shown, more, hint} comes before the rows of the answer: total counts every source function that matches. Then a _summary entry for this page: {_summary (str), _type (str), _target (str)}, followed by source entries each with: source_name, source_qualified_name, source_kind, source_file (absolute), source_line, caller_count, reachable (bool), and paths (up to 3 path dicts as find_call_path gives them; the key is absent when unreachable). When to_symbol matches more than one symbol, a source entry also holds the warning that names them, and each path its target_qualified_name.

A source entry with ``timed_out: True`` means that the time budget
ran out before the search for that source began.  Its
``reachable: False`` thus means "not checked", not "proved
unreachable".  Such a source counts as shown, thus the hint goes
past it.  To check it, call again with the same ``offset``: each
page gets the whole time budget.

A source entry with ``budget_spent: True`` means that the path
search of that source spent its budget of node expansions.  Its
``reachable: False`` thus means "not found", not "proved
unreachable".

A page after the last source function gives one ``info`` dict
that names the total.  Never empty: one dict with ``error`` or
``info`` replaces an empty result.  Check both keys first.
explain_symbolA

Explain what a C/C++ symbol does in plain English — libclang-aware analysis. Uses pre-computed LLM analysis when available (instant), falls back to on-demand LLM. Falls back to macro explanation when the name matches a #define.

Read-only. No side effects — uses pre-computed LLM analysis when available (instant, generated during fw-context index --analyze), falls back to calling an LLM on-demand. Returns the symbol's purpose, inputs, outputs, and side effects.

For raw source code use get_source. For symbol metadata without explanation use lookup_symbol. For body + callers + callees use get_symbol_context.

Args: name: Symbol name to explain. E.g. uart_init, ModemMsg::send. project_root: Project root directory. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. context_lines: Lines of source context around the symbol definition (default 40, max 200). Only used when no pre-computed analysis exists. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: dict: {name, kind, file, line, signature, explanation, llm_analysis (if pre-computed)}. With no pre-computed analysis and no local LLM (disabled), the dict holds explain_prompt and no explanation. Macro fallback returns kind="macro", signature (as #define NAME or #define NAME(a, b)), is_function_like, value (the replacement text ALONE), and expanded_value.

A ``warning`` key means that the local LLM gave no explanation —
the request timed out, or the model is not available.  The dict then
holds ``source`` and ``explain_prompt``: read the source, and answer
the prompt yourself.

When the file changed after the last index run, the dict adds
``stale`` (True) and ``stale_warning`` (str).  ``stale_warning`` is
separate from ``warning``, which the LLM error paths use.  A symbol
that moved gives its indexed body, not the code that now sits at the
stored line number.

The on-demand path reads ``context_lines`` around the symbol from
the DISK.  That window is NOT ifdef-filtered: it holds every
``#if`` branch.  This tool gives no ``source_origin``.

An ``ambiguous_warning`` key means that *name* matched more than one
symbol, such as two classes with a method of the same name.  This
answer is about ONE of them, and the key names it.  ``candidates``
(max 20) lists the others and ``candidates_total`` counts them.
Give a ``qualified_name`` from it to ask about one symbol only.
It is separate from ``warning``, which the LLM error paths use.

A symbol in a file outside the project root, such as an SDK
header, is explained and not refused.

On failure the dict holds only ``error`` with the reason.
get_file_mapA

Fast structural map of all C/C++ symbols in a file grouped by kind — libclang-powered table of contents. Like a table of contents before reading a chapter: see what functions, classes, and enums a file defines at a glance.

The index decides which file a path may reach: a path that no file of this build answers for is refused, and a header of the SDK that the build compiles is reachable like any file of the application.

Pass a path relative to the project root (src/main.cpp) or just the filename (main.cpp). The exact path wins; else the path matches whole trailing path segments (config.h matches src/config.h, not hw_config.h), a project file before a vendor file, then the shortest path. Returns symbols keyed by kind (function, method, class, struct, enum, ...). Each kind has count (total) and items (first N, default 30). Set max_per_kind=0 for unlimited, signatures=true for full sigs.

Enum constants (enum_constant) are grouped into subgroups by parent enum. Each subgroup has name, count, and constants (list of {name, qualified_name, line, enum_value}). The subgroup count reflects the real total even when max_per_kind limits the constants list.

For detailed symbol information use get_symbol_context or lookup_symbol.

Read-only. No side effects. Use before reading a large file to orient yourself — see what functions, classes, and enums it defines.

Args: file_path: Path relative to project root, or just the filename. project_root: Project directory. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. signatures: Include full function signatures. Default: False. max_per_kind: Max items per kind group (default 30, 0 = unlimited). With kind, the size of the page. kind: One symbol kind, for example "function" or "enum_constant". Gives one page of the items of that kind instead of the map. offset: With kind: skip this many items of that kind. Reads the next page; page names the offset to use. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: dict: {file, total_symbols, symbols: {kind: {count, items[], subgroups?[]}}}

Each item holds ``name``, ``qualified_name``, and ``line``, plus
``end_line`` when the symbol is a definition.  The two line numbers
are the extent, thus ``file:line-end_line`` is the citation.  A
group that ``max_per_kind`` cut holds ``hint``, the call that pages
its items.

With ``kind``: {file, kind, count, items[], page}, where ``items``
is one flat page of that kind, enum constants too, and ``page`` is
the page notice ``{total, offset, shown, more, hint}``.  A page
after the last item gives one ``info`` dict that names the total.

When the file changed after the last index run, the dict adds
``stale`` (True) and ``stale_warning`` (str).

On failure the dict holds ``error`` with the reason (plus the stale
keys when indexed files changed).
get_sourceA

Read a C/C++ function/method/enum/macro body using libclang exact extents — no guessing line numbers. Uses AST-precise {start, end} extents so you get exactly the function body. Generic file readers don't know where a function actually ends — libclang tracks exact {start, end} from the AST.

The body is ifdef-filtered: a line of an inactive #if branch comes back blank, thus the text holds only the code that compiles for this build. The line numbers do not move. source_origin says where the text came from — "index" is the filtered copy, "disk" is the file itself and holds EVERY branch. A body reaches you from the disk when the file changed after the last index run (stale_warning says so), or when the index holds no body for the symbol, such as a declaration.

For enums, includes a constants array of the member constants with their values (at most 200; constants_total counts them all, and constants_hint names the call that pages them). For macros, returns kind="macro" with signature (#define NAME or #define NAME(a, b)), is_function_like, value (the replacement text ALONE — the parameter list is not part of it) and expanded_value (preprocessor-resolved).

For rich context (who calls this, what does it call) use get_symbol_context instead — it returns body, callers, and callees in a single call. For the full file, use read_file.

Read-only. No side effects.

Args: name: Symbol name, or qualified name (Class::method) to select one symbol. Returns exact function body via libclang extent. project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: dict: {name, qualified_name, kind, file, line, signature, docstring, is_definition, is_template, is_virtual, is_pure_virtual, source (str — the function/enum/macro body, truncated at 8000 chars), warning (str, optional — when source file cannot be read)}. May also include end_line (the last line of the extent), template_usr, parent_usr, enum_value, constants (list for enums), value (macro replacement text), expanded_value (preprocessor-resolved macro value) when applicable. A declaration has no extent, thus it gets no end_line.

``line`` and ``end_line`` are the extent of the symbol, thus they
are the citation: quote ``file:line-end_line``.  Do not count the
lines of ``source`` to find the end.

``source`` carries a line-number prefix on every line — four
columns, right-aligned, then two spaces (``"  20     bool ..."``).
This tool always numbers its text.  ``read_file`` numbers its
``content`` when you pass ``line_numbers=True``, and the ``source``
of ``search_bodies`` is always bare.  Strip the prefix before you
compare the text with anything.

When the file changed after the last index run, the dict adds
``stale`` (True) and ``stale_warning`` (str).  ``source_origin`` then
tells where the body comes from: ``"disk"`` when the symbol did not
move, ``"index"`` when it did and the body comes from the index
instead.  A moved symbol never gives the code of another symbol.

``_source_truncated`` (True) marks a body that a cap cut:
``index.max_symbol_body_lines`` bounds the number of lines, and a
second cap bounds the characters (8000).  The character cut lands in the
middle of a line, thus a body with this mark can end in an
unbalanced brace.  Read the rest with ``read_file`` and a range.

An ``ambiguous_warning`` key means that *name* matched more than one
symbol, such as two classes with a method of the same name.  This
body belongs to ONE of them, and the key names it.  ``candidates``
(max 20, each with ``in_project_root``) lists the others and
``candidates_total`` counts them.  Give a ``qualified_name`` from it
to get one symbol only.  It is separate from ``warning``, which
reports a body that could not be read from the disk.

A body in a file outside the project root, such as an SDK header,
is returned and not refused.

On failure the dict holds only ``error`` with the reason.
get_symbol_contextA

Rich one-shot context for a C/C++ symbol: body, signature, the direct callers and callees. Answers "what does this do and how does it fit in the system?" in a single response — libclang powers the call graph, not regex. Falls back to macro display when the symbol is not found.

Prefer this over get_source when you also need callers, callees, indirect call sites, or LLM analysis — all returned in a single call. If you only need the raw function body (no metadata), get_source is slightly faster. For transitive call-graph exploration use find_all_callers_recursive or find_callees_recursive.

The callers and callees include vendor/SDK code — the call graph naturally spans project and vendor boundaries in both directions (project → vendor API, vendor callback → project handler). Each list is cut (callers at 50, callees at 100), and its *_total says how many there are; *_hint names the tool that pages a cut list.

The body is ifdef-filtered, the same as in get_source: a line of an inactive #if branch comes back blank and the line numbers do not move. source_origin says whether the text is the filtered copy ("index") or the file itself ("disk", every branch present).

Read-only. No side effects.

Args: name: Symbol name. Returns body, signature, the direct callers and callees (each list with its total). project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: dict with: name, qualified_name, kind, file, line, signature, docstring (raw Doxygen comment text), is_definition, callers (list, max 50 — call and indirect references, each with its line), callees (list, max 100 — one row for each callee and kind of reference), source (body text), indirect_call_sites (list, max 200 — for a field/variable symbol, where the function pointer is invoked; for a function/method, the calls through function pointers that it makes). callers_total, callees_total and indirect_call_sites_total count each whole list. When a list is cut, callers_hint, callees_hint or indirect_call_sites_hint names the tool that pages it. For field and variable symbols that have function pointer type, also includes resolution: {assignments_found, call_sites_found, resolved, note} indicating whether assignments and call sites are linked (Phase 3). resolved=False with a note when parts are missing — LLM can detect uncertainty. For an enum also returns constants (at most 200), constants_total and, when the list is cut, constants_hint; for an enum constant, enum_value. For macros returns kind="macro", signature (#define NAME or #define NAME(a, b)), is_function_like, value (the replacement text ALONE) and expanded_value (preprocessor-resolved). When LLM analysis has been generated (fw-context index --analyze), includes llm_analysis: {summary, inputs, outputs, model, analyzed_at} with a structured description of the symbol's purpose, parameters, and return values/side effects.

When the file changed after the last index run, the dict adds
``stale`` (True) and ``stale_warning`` (str), and ``source_origin``
tells where the body comes from: ``"disk"`` when the symbol did not
move, ``"index"`` when it did.  The callers and callees come from the
index in all cases, thus a stale dict can hold an incomplete list.
``_source_truncated`` (True) marks a body that a cap cut, as in
``get_source``.

The dict also carries the libclang flags of the symbol:
is_virtual, is_pure_virtual, is_template, parent_usr, and
template_usr.  For a virtual method it adds ``overrides`` (the base
methods that this method overrides) and ``overridden_by`` (the
derived methods that override it) — use these two before you change
a virtual method.

An ``ambiguous_warning`` key means that *name* matched more than one
symbol, such as two classes with a method of the same name.  Every
part of this answer — body, callers and callees — is about the ONE
symbol that the key names.  ``candidates`` (max 20) lists the others
and ``candidates_total`` counts them.  Give a ``qualified_name`` from
it to ask about one symbol only.

A symbol in a file outside the project root, such as an SDK header,
is returned and not refused.

On failure the dict holds only ``error`` with the reason.
read_fileA

Read a C/C++ source file, one page at a time, with ifdef-filtered content — only code that actually compiles for the build of the query. Inactive #ifdef branches are replaced with blank lines (preserving original line numbers).

Use this to read a file without leaving the fw-context ecosystem. Unlike generic file readers, this tool returns build-accurate content: code gated behind #ifdef BOARD_V2 stays visible only when BOARD_V2 is actually defined for this build. Line numbers match the original file — inactive branches appear as blank lines, and the text spans the whole file, thus lines is the length of the file.

A blank line is an answer, and not a defect: it says that the line is in a branch the build does not take. When EVERY line of the file is blank, the dict carries all_lines_inactive and a warning — such a file holds code, and the build of the query compiles none of it.

content is bare text by default and carries NO line-number prefix — unlike the source of get_source, which numbers every line. Never count the lines here to find a number. Take it from a field instead: the match_lines of search_bodies or search_content, the line / end_line of get_source and get_file_map, or pass line_numbers=True and read the number off the line.

start_line and end_line cut a window out of the file (1-based, both ends inclusive, 0 = no bound on that side). Reading around a known line costs a fraction of the whole file — 40 lines around a match instead of 2000 lines of a header.

Without end_line the answer is ONE PAGE of at most 2000 lines from start_line on, and page holds the page notice: total is the number of lines of the file, offset the number of lines before the page, shown the lines on it. When more is true, the hint names the read_file call with the next start_line. An explicit end_line gives the whole range that it names.

A comment and a preprocessor directive are part of the answer. Both are text that the file holds and the build reads, thus both stay — an include guard, a #define, and the description of a register in a vendor header included. A blank line is therefore an inactive line, or a line that is blank on disk, and nothing else.

For reading a single function body with libclang exact extents use get_source. For body + callers + callees in one call use get_symbol_context. For a structural overview without content use get_file_map. For searching patterns across files use search_content.

Read-only. No side effects. Falls back to raw disk content (with a warning) when the indexed files.content column is empty — e.g. on a legacy index that predates this feature. Run fw-context index to populate the ifdef-filtered content.

Args: file_path: Path relative to project root, or just the filename. E.g. src/main.cpp or main.cpp. project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. line_numbers: Prefix every line with its number, right-aligned and followed by two spaces, as get_source does. Default False. start_line: First line to return, 1-based inclusive. 0 = file start. end_line: Last line to return, 1-based inclusive. 0 = one page of at most 2000 lines. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: dict: {file (str), language (str — "c" or "cpp"), mtime (float), lines (int — total line count of the WHOLE file, whatever range was asked for), content (str — the ifdef-filtered text, bare unless line_numbers was set), warning (str, optional — when reading from raw disk instead of indexed content, or when no line of the file is active), all_lines_inactive (True, optional)}.

``all_lines_inactive`` marks a file of which the build of the query
compiles no line: every line is inside an inactive ``#if`` branch, thus
``content`` holds the correct number of lines and no text.  Without
this field that answer reads as an empty file, and the two mean
opposite things.

A range, or a page that is not the whole file, adds ``start_line``
and ``end_line`` — the first and last line the ``content`` really
holds, after the end was clamped to the length of the file.
Without ``end_line`` the dict also holds ``page``, the page notice
``{total, offset, shown, more, hint}`` in lines.

When the file changed after the last index run, the dict adds
``stale`` (True) and ``stale_warning`` (str): the content comes
from the index, not from the disk.

The path rules are those of ``get_file_map``.  On failure the dict
holds only ``error`` with the reason: a path the index does not
hold, an ``end_line`` before ``start_line``, or a ``start_line``
past the end of the file.
get_class_membersA

Return all methods, fields, and nested types of a C/C++ class/struct — libclang-powered member table. Groups members by kind (method, constructor, field, enum, etc.), distinguishing class members from free functions across the entire codebase.

Members are grouped by kind (method, constructor, destructor, field, enum, typedef, class, struct). Each member includes its signature, virtual flags, and source line. Works for C structs too — they just won't have methods.

For inheritance hierarchy use get_inheritance_chain. For individual method details use get_symbol_context.

Read-only. No side effects.

Args: class_name: Class or struct name. E.g. 'ModemManager' or 'comm::MODEM'. project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. limit: Maximum members on one page (default 200, max 500). offset: Skip this many members. Reads the next page; page names the offset to use. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: dict: {name, qualified_name, kind, file, line, members: {kind: [{name, qualified_name, signature, is_virtual, is_pure_virtual, line}]}, member_count (int — every member), page}

``page`` is the page notice ``{total, offset, shown, more,
hint}``.  A page holds the members in the order kind, name and
line, and groups them by kind after the cut, thus one kind can go
on on the next page.  A page after the last member gives one
``info`` dict that names the total.

When a file of the answer changed after the last index run, the
dict adds ``stale`` (True) and ``stale_warning`` (str).

On failure the dict holds ``error`` with the reason.
get_inheritance_chainA

Return the C++ inheritance chain for a class or struct — libclang-aware hierarchy. Resolves base/derived class relationships across all translation units, which single-file reading cannot do.

Shows direct base classes (what this inherits from) and direct derived classes (what inherits from this), along with access level and virtual flag for each edge.

When transitive=True, walks the full hierarchy up to all ancestors and down to all descendants (bounded by max_depth). Uses BFS with cycle detection to handle diamond inheritance.

For class members use get_class_members. For virtual method override chains use get_method_overrides.

Read-only. No side effects.

Args: class_name: Class or struct name to get inheritance information for. E.g. 'UART_DRIVER' or 'comm::MODEM'. project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. transitive: When True, walk the full inheritance tree both up (ancestors) and down (descendants). Default: False (direct bases and derived only). max_depth: Maximum BFS depth for transitive walk (default 10). The schema holds it between 1 and 50, thus a number outside that range is REFUSED and not cut down to fit. limit: Maximum classes in all_bases and in all_derived on one page (default 100, max 500). Only with transitive=True. offset: Skip this many classes in all_bases and in all_derived. One offset pages the two lists together. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: dict: { name, qualified_name, kind, file, line, bases: [{name, usr, access, is_virtual, file}], derived: [{name, usr, access, is_virtual, file}], all_bases: [{name, usr, access, is_virtual, depth, file, kind}] (when transitive=True, ancestors sorted by depth, then name), all_derived: [...] (when transitive=True, descendants, same shape) }

With ``transitive=True`` each of the two lists is one page, and
``all_bases_page`` and ``all_derived_page`` hold its page notice
``{total, offset, shown, more, hint}``.  A hierarchy below a root
class of a framework can hold thousands of classes.

When a file of the answer changed after the last index run, the
dict adds ``stale`` (True) and ``stale_warning`` (str).

On failure the dict holds ``error`` with the reason.
get_method_overridesA

Return C++ virtual method override information — libclang-powered vtable analysis. Resolves virtual dispatch across class hierarchies: shows which base-class method this overrides, and which derived-class methods override this one. Text-based search cannot resolve virtual dispatch across translation units.

Shows what base-class method this method overrides, and what derived-class methods override this one. Built from the overrides table which is populated during fw-context index via post-processing of the inheritance graph and virtual method signatures.

For class-level inheritance, use get_inheritance_chain. For symbol details, use get_symbol_context.

Read-only. No side effects.

Args: method_name: Method name to get override information for. Use qualified name for disambiguation, e.g. 'UART_DRIVER::write'. project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: dict: { name, qualified_name, kind, file, line, signature, overrides: [{usr, name, qualified_name, kind, file, line}], overridden_by: [{usr, name, qualified_name, kind, file, line}] }

When a file of the answer changed after the last index run, the
dict adds ``stale`` (True) and ``stale_warning`` (str).

On failure the dict holds ``error`` with the reason.
get_template_instancesA

Find all template instantiations for a C/C++ class or function template — libclang template-aware lookup. Finds concrete instantiations spread across all translation units, each with its full type signature. Text-based search cannot resolve template specializations across translation units.

Returns concrete instantiations of the template — each with its full type signature (e.g. Callback<void(int)>). They are nested in the instances list of one dict that describes the template itself.

Uses the template_usr column populated during indexing via libclang's cursor.specialized_template.

Known limitation: libclang's specialized_template does not reliably resolve implicit instantiations or template methods of template classes. Header-only templates (e.g. RingBuffer<T>) may report zero instances even when used in the codebase. Explicit specializations and class/struct instantiations are detected more reliably than method-level instantiations.

For finding the template declaration itself use lookup_symbol.

Read-only. No side effects.

Args: template_name: Template name to find instantiations for. E.g. 'Callback' or 'mbed::Callback'. project_root: Project root. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. limit: Maximum results (default 50, max 200). offset: Skip this many instances. Reads the next page; the page notice names the offset to use. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: list[dict]: the page notice {total, offset, shown, more, hint} for the instances, then one element wrapping the template declaration: {name, qualified_name, kind, file, line, is_definition, signature, instances (list of dicts, each with name, qualified_name, kind, file, line, signature, is_definition — one page of them), instance_count (int — all instances, as total)}

A page after the last instance gives one ``info`` dict that names
the total.

A dict with ``error`` means that the name was not found, is not a
template, or the query failed — check that key first.  When a file
of the answer changed after the last index run, a ``warning`` dict
comes first.
find_variablesA

Find C/C++ variables by name or part of it and trace who reads or writes them through the call graph. libclang-powered: splits variables into global (varglobal — file/namespace/class-scope) and local (varlocal — inside a function body).

Each result includes a type signature (bool timeSet, const IPAddress modbus_ip), the enclosing function for locals ("<file scope>" for globals), and a references list of the functions that read or write the variable — the same ref_kind values as find_references ("call", "ref", "member"). The list is capped at 30 per variable, and holds only references of the selected build; references_total gives the whole count. Only definitions are returned, globals first, then by name.

Use when you need to understand shared state, find who modifies a global variable, trace side effects, or distinguish important globals from loop counters. For general symbol search use search_code or lookup_symbol. For all references to a specific variable (including reads in expressions), use find_references.

This tool is the way to a LOCAL variable: search_code drops the varlocal kind, because a local matches every topic query aimed at the function around it. search_code(..., kind="varlocal") reaches them as well.

Legacy indexes with kind="variable" (pre-split) are detected and included in results — reindex to fully benefit from the split.

Read-only. No side effects.

Args: name: Variable name or part of it. Substring match on name and qualified name (e.g. g_ finds g_debug_level, g_state). project_root: Project root directory. Auto-detected if omitted. project: Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. kind: Optional kind filter — "varglobal", "varlocal", "field", or None (all). Default None. The legacy "variable" is also accepted, for an index made before the kind was split. limit: Maximum results (default 20, max 100). offset: Skip this many variables. Reads the next page; the page notice names the offset to use. variant: Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build. One query answers for ONE build. image: Image of the build: a separate program, such as the application or the bootloader. Give it when the build has an image name.

Returns: list of dicts, each with: name (str), qualified_name (str), kind (str — "varglobal", "varlocal", "field", or "variable" on an index made before the kind was split), file (str), line (int), signature (str — e.g. "const IPAddress modbus_ip"), enclosing_function (str — function name for varlocal, "<file scope>" for varglobal), enclosing_class (str — class or struct name for fields and static members, empty otherwise), references (list[dict] — function, file, line, ref_kind; at most 30), references_total (int — every reference of the variable). When the list is cut, references_hint names the find_references call that lists them all.

The page notice ``{total, offset, shown, more, hint}`` comes
before the rows of the answer.  ``total`` counts every variable that matches.  When
``more`` is true, the ``hint`` names the call that reads the next
page.  A page after the last variable gives one ``info`` dict that
names the total.

No match gives ``[]``.  One dict with ``error`` means the query
failed — check that key first.
A ``warning`` dict that comes first means that indexed files
changed after the last index run: the results can be out of date.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription
resource_statsReturn a human-readable markdown summary of all indexed projects. Read-only. Aggregates stats from every project database found under the configured index directory.
resource_projectsReturn project list as a JSON string. Read-only. Uses the same data as ``list_projects``, serialized as indented JSON.
resource_embedded_review_skillReturn the fw-review SKILL.md as an MCP resource. Read-only. Makes the embedded firmware review methodology available to any MCP client via a well-known resource URI.

TDQS

A4/5.0

Scored across 37 tools

Disambiguation3/5

Many tools share broad purposes—six search tools and a large family of call-graph/reference tools—so an agent can confuse semantic_search with smart_search or search_code, and get_source with get_symbol_context. The extensive descriptions and explicit cross-references help, but overlap remains substantial, especially in search.

Naming Consistency4/5

All names are snake_case, mostly following a verb_noun pattern (get_, find_, search_, check_, list_, reset_). Minor deviations are adjective_noun forms such as semantic_search and smart_search, but the set is highly predictable overall.

Tool Count2/5

37 tools is far beyond the 3-15 sweet spot and exceeds the 25-tool threshold that indicates overload. Many are narrow variants of search, reference, and call-graph queries, making the set difficult to navigate at a glance.

Completeness3/5

The query surface is very comprehensive—symbol search, call graph, references, vector table, dead code, class hierarchy, variables—but lifecycle coverage has notable gaps. There is no tool to initialize a project or trigger a full index/build; only reset_index and single-file reindex_file exist, forcing agents to fall back to CLI instructions for central setup operations.

Maintenance

ActivityActive
ResponsivenessResponsive