TopicForge
PYPI · TOPICFORGE
Read-only MCP server for ROS2 and DDS: topics, participants, QoS mismatches, bags.
Available components
Last scanned (20 hours ago)
Scores higher than 98% of PyPI servers
Category IoT and Hardware
How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. How we score → Why this is hard to score →
Supply Chain Security100
- No malware found by supply-chain analysis.Pass
- No known CVEs affecting this package version or its production dependencies.Pass
- Runs hatchling.build at install time, a recognised build step with no custom scripting around it. View diagnostics → Pass
- 3 of 42 dependencies flagged as unhealthy. View diagnostics → Partial
Provenance & Transparency97
- Source repository is publicly reachable at the declared URL. View diagnostics → Pass
- Cryptographically verified build provenance (signed, bound to yaniswav/TopicForge). View diagnostics → Pass
- Clear OSI-approved license (MIT).Pass
- Actively maintained (last published 1 days ago).Pass
- Security-disclosure policy not yet verified: we couldn't inspect the source repository.Unverified
Schema Quality & AI Usability74
- 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).Pass
- AI-judged instruction clarity (excellent).Pass
- Context-footprint check failed: tool/resource definitions use about 7809 tokens (~459/item across 17 items; 17 tools + 0 resources), over budget; trim descriptions and params. See how to fix → Fail
- Usage-examples check failed: none of the tools include examples. See how to fix → Fail
Stability & Change Management100
- No destabilizing schema changes in the last 30 days.Pass
Tool Coverage100
- 100% of tools have a non-trivial description (not blank, and not just the tool's name).Pass
- 100% of tool parameters carry a description.Pass
- Structured output schemas are declared (100% of tools); any adoption earns full credit.Pass
Tool Safety100
- No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.Pass
- We read all 17 captured tool definition(s), and no name or description among them implies an irreversible operation.Pass
- An AI judge read all 18 captured unit(s) of tool text and found none that tries to manipulate the model reading it.Pass
Capabilities100
- Implements a current MCP spec version (2026-07-28).Pass
Every change we have recorded for this component, newest first. Security-relevant changes are always shown. ▲ marks a change for the better, ▼ a change for the worse; unmarked changes are neutral.
- 4 Nov 26 0
- Security disclosure: pass → unverified ▼ functional
- 3 Nov 26 +15
- Malware scan: unverified → pass ▲ security
- 2 Nov 26 0
- Package version: 0.8.1 → 0.9.0 functional
- 26 Oct 26 −15
- Malware scan: pass → unverified ▼ security
- Package version: 0.8.0 → 0.8.1 functional
- 22 Oct 26 +15
- Malware scan: unverified → pass ▲ security
- 21 Oct 26 −15
- Malware scan: pass → unverified ▼ security
- Package version: 0.7.2 → 0.8.0 functional
- 19 Oct 26 0
- Stability: 0.97 → pass security
- 18 Oct 26 0
- Package version: 0.7.1 → 0.7.2 functional
Diagnostic detail from the automated scan of this channel: what the scanner observed at each step, so you can see exactly where a check passed or failed. It is informational only and never changes the trust score.
Captured 4 Nov 2026 · Analysed pypi/topicforge@0.9.0
Provenance Verified
A signed build attestation was found and verified, binding this exact artifact to the source repository it claims to come from.
| Result | Verified |
|---|---|
| Ecosystem | pypi |
| Reason | Verified |
| Discovered via | Registry attestation endpoint |
| Source repo | yaniswav/TopicForge |
| Certificate issuer | https://token.actions.githubusercontent.com |
| Certificate SAN | https://github.com/yaniswav/TopicForge/.github/workflows/publish.yml@refs/tags/v0.9.0 |
| Rekor log index | 3642347848 |
| Predicate type | PyPI publish attestation https://docs.pypi.org/attestations/publish/v1 |
| Subject digest | sha256:87f9598f43fd2fb3978809f2539c7a7f98a14f7d4c1e9713d557b3b8aee709b7 |
Background: How many MCP packages publish verified provenance →
Install scripts 1 script
| Hook | Tier | Command |
|---|---|---|
| build_backend | allowlisted | hatchling.build |
Background: Why install scripts are a supply-chain risk →
Dependencies 42 packages
| Packages resolved | 42 |
|---|---|
| Stale | 2 |
| No linked repository | 1 |
| Tree resolution | Complete |
Background: SBOMs and build attestations, explained →
The tools this component advertises to a client, with an estimated token cost for each. Expand a tool to see its parameters and schema. The per-tool counts are indicative and are not scored directly; the schema's total context footprint is one signal in Schema Quality & AI Usability. A tool's description is untrusted text the model reads on every call, which is what makes this list a security surface and not just an inventory: how tool poisoning works →
analyze_bag ~344
Summarize a ROS 2 bag at `path` as a `BagAnalysis`: storage format, duration, message count, per-topic stats and `mode_effective`. Per topic `frequency_hz` is `(n - 1) / (last - first message time)` (`frequency_basis` `topic_span`), with `first_timestamp_ns`, `last_timestamp_ns` and `latched`; a latched topic whose messages all fall within 1 s (e.g. `/tf_static`) has a null `frequency_hz`. When the bag cannot be read locally or is a `.mcap` over 200 MiB, the rate falls back to count / bag duration (`bag_duration`) and `note` says why. **Live mode** runs `ros2 bag info` on `.mcap`, `.db3` or `rosbag2_*` directories (for ROS 1 `.bag` use `peek_bag_samples`); **mock mode** returns fixture data for any bag-like path, and only mock detects anomalies. **Raises an MCP error** for a malformed or missing path (live), unparseable output, or without a `ros2` CLI. **Duration**: ROS lock, one call at a time, ends within 45 s; `busy` if held.
| Name | Type | Req | Description |
|---|---|---|---|
| path | string | yes | Bag path: a `.mcap` or `.db3` file or a `rosbag2_*` directory (`peek_bag_samples` also reads ROS 1 `.bag`; `analyze_bag` does not). Existence and format are checked live; mock accepts any well-formed… |
| Name | Type | Req | Description |
|---|---|---|---|
| anomalies | array | – | Notes on gaps or clock jumps; mock mode only. |
| bag_format | string|null | – | Detected container: `mcap`, `db3`, `bag` or `unknown`; `None` for `ros2 bag info` text. |
| duration_s | number | yes | Bag duration in seconds. |
| message_count | integer | yes | Total number of messages across all recorded topics. |
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| note | string|null | – | Why the result is less detailed than usual (e.g. rates over the whole bag duration); else `None`. |
| path | string | yes | Bag path as supplied. |
| start_ns | integer|null | – | Record ns of the bag's first message (rosbag2 `starting_time`), the zero of `start_s`/`end_s`; `null` if unreadable. |
| storage_format | string|null | – | `mcap`, `sqlite3`, or other storage identifier when known. |
| topics | array | yes | Per-topic statistics for every topic present in the bag. |
No examples provided.
detect_qos_mismatches ~472
Explain why DDS readers and writers on a topic do not talk, and who will. Pairs every reader with every writer per topic and returns a `MismatchScan`: `summary` (one line, read it first), `matched` (pairs DDS will connect given the announced QoS; data flow is not observed), `reports` (incompatible or risky QoS pairs, with participant names, requested vs offered values and the failed rule in `details`), `not_matched` (pairs DDS never matches: different partitions or type names; the QoS rules are NOT evaluated for them, and `latent_incompatible_policies` lists what would also break once fixed), `hints` (probable typos in orphan topic names, type id notes), plus `pairs_checked`, `topics_scanned`, `policies_checked` and `policies_unchecked`. Checked: Partition (`*` and `?` wildcards), type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership, DestinationOrder, DataRepresentation, History (risky only, where announced). **An empty `reports` with a non-empty `not_matched` still means no data flows**, and an all-empty result does not prove the bus healthy: discovery shows the QoS DECLARED, not runtime behavior (a 'deadline missed' log with compatible QoS means the writer's real period exceeds the deadline, which TopicForge cannot see). Pass `topic` to scope the scan. The lists are capped at 200 each (`truncated`; `*_total` keep the counts). A `late_joiner` match is a VOLATILE writer whose reader joined later on the same host: normal. Reports and partition splits carry `suggested_fixes`: text for a human, never applied by TopicForge; `verify_with` names the call that confirms the repair. **Read-only by architecture**. **Raises an MCP error** when no DDS module is active; mock returns fixtures. **Duration**: DDS lock, one call at a time, ends within 45 s; `busy` if held.
| Name | Type | Req | Description |
|---|---|---|---|
| topic | – | – | Optional DDS topic to scope the scan to: `scan` or `rt/scan`. Omit to scan all topics. |
| Name | Type | Req | Description |
|---|---|---|---|
| hints | array | yes | Leads that are not findings: probable typos, type id notes, incomplete checks. |
| matched | array | – | Pairs DDS will connect given the announced QoS (data flow is not observed). |
| matched_total | integer | – | Matched pairs before the size cap. |
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| not_matched | array | yes | Pairs DDS never matches (partition or type name). Non-empty with empty `reports` is NOT all-clear: no data flows. |
| not_matched_total | integer | – | Not-matched pairs before the size cap. |
| note | string|null | – | One sentence of context, or `None`. |
| pairs_checked | integer | yes | Same-topic (reader, writer) pairs examined. |
| policies_checked | array | yes | Policies compared on every pair. |
| policies_unchecked | array | yes | What the scan does not cover, each with a reason; a clean result says nothing about them. |
| reports | array | yes | Pairs sharing partition and type with incompatible or risky QoS. |
| reports_total | integer | – | Reports before the size cap. |
| summary | string|null | – | One-line reading of the scan: counts of incompatible, not matched and matched pairs. |
| topics_scanned | integer | yes | Topics that had at least one endpoint in scope. |
| truncated | boolean | – | True when `reports`, `matched` or `not_matched` hit the 200-entry cap (see `*_total`); narrow with `topic`. |
No examples provided.
diagnose ~288
Answer "is anything wrong": sweep the ROS 2 graph and DDS bus in one call and rank what is found as a `Diagnosis`. Checks, in order: `environment`, `nodes`, `topics`, `dds` (QoS pairs, restarts), `tf` (8 s window), `logs` (`/rosout` errors), `responsiveness` (parameter reads, `node` first); `scope` runs one group. `findings` are `critical`, `warning` or `info`, with `evidence` and, for QoS, `suggested_fixes`; `verdict` is one line. A check that errors or runs out of `budget_s` is in `checks_skipped`, never a tool error, and the verdict then says it is not verified. A clean result does not prove data flows. **Mock mode**: fixtures. **Duration**: holds the ROS lock up to `budget_s` and ends within 45 s; other ROS tools get `busy` meanwhile, `health_check` stays free.
| Name | Type | Req | Description |
|---|---|---|---|
| budget_s | number | – | Seconds for the sweep, 10..40, default 30. |
| node | – | – | Node read first (6 s); a failed read is critical. |
| scope | string | – | `all`, or one group: `graph`, `tf`, `dds`, `logs`. |
| Name | Type | Req | Description |
|---|---|---|---|
| budget_s | number | yes | – |
| checks_run | array | yes | – |
| checks_skipped | array | yes | Not verified. |
| critical_count | integer | yes | Counts cover all findings, not the capped list. |
| elapsed_s | number | yes | – |
| findings | array | yes | Critical first; at most 50. |
| findings_total | integer | yes | – |
| info_count | integer | yes | – |
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| note | string|null | – | One sentence of context, or `None`. |
| verdict | string | yes | One line; never an all-clear after a skipped check. |
| warning_count | integer | yes | – |
No examples provided.
get_node_info ~485
ROS 2 graph only; on a DDS-only setup use `list_participants`. Describe one ROS 2 node as a `NodeInfo`: the topics it publishes and subscribes to, the services and actions it serves or calls (each `{name, type}`), its parameters and `use_sim_time`. Interfaces come from the graph (`ros2 node info`), which never waits for the node. Parameters come from a read (`ros2 param dump`) bounded by `timeout_s`: **a node that does not answer in time is a finding, not an error**: `parameters` is `null`, `parameters_note` says its executor is probably blocked, and the interfaces are still returned. A value whose name contains password, secret, token, api_key or credential is replaced by `<masked>` and a long value is cut (`truncated`, `original_size`), both said in `parameters_note`. With `params_file`, `param_diff` lists declared values that differ on the node, are `declared_only` or `masked`. With several nodes of that name `duplicate_count` is above 1 and `note` warns. **Raises an MCP error** when the node is not on the graph (the message lists close matches), the name is malformed, or without a `ros2` CLI. **Read-only**: the only request TopicForge ever sends is a parameter read (list/get); it sends no other request to a node. **Duration**: `ros2` CLI behind the ROS lock, one call at a time; up to three CLI calls (node list and info, 8 s each at most; the parameter read, at most `timeout_s`, 20 s); ends within 45 s, less time for the read after a wait; `busy` error if the lock is held (retry).
| Name | Type | Req | Description |
|---|---|---|---|
| node | string | yes | Fully qualified node name as `list_nodes` gives it, e.g. `/robot1/lidar_driver` (a missing leading `/` is added). |
| params_file | – | – | Server path of a ROS 2 parameter `.yaml` (1 MiB max). |
| timeout_s | number | – | Seconds to wait for the parameter read, 1..20, default 8. A node that does not answer is reported in `parameters_note`, not as an error. |
| Name | Type | Req | Description |
|---|---|---|---|
| action_clients | array | yes | Actions the node calls. |
| action_servers | array | yes | Actions the node serves. |
| duplicate_count | integer | yes | Nodes using this exact full name; above 1, the data above belongs to one of them. |
| full_name | string | yes | Fully qualified node name, e.g. `/lidar_driver`. |
| lifecycle | boolean | – | True when `service_servers` has `<full_name>/get_state` (GetState). |
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| note | string|null | – | One sentence of context, or `None`. |
| param_diff | array|null | – | File against node: differences only. |
| param_diff_note | string|null | – | – |
| param_diff_summary | string|null | – | Left out of `param_diff`: same, live_only, with names. |
| parameters | array|null | – | Parameters sorted by name; `None` when unreadable (see `parameters_note`); an empty list means none. |
| parameters_note | string|null | – | Why `parameters` is `None`, or what was masked or cut; `None` when complete. |
| publishers | array | yes | Topics the node publishes. |
| service_clients | array | yes | Services the node calls. |
| service_servers | array | yes | Services the node offers, its own parameter services included. |
| subscribers | array | yes | Topics the node subscribes to. |
| use_sim_time | boolean|null | – | Value of the node's `use_sim_time` parameter. `None` when unknown: see `use_sim_time_note`. |
| use_sim_time_note | string|null | – | Why `use_sim_time` is `None`. `None` when it is set. |
No examples provided.
get_topic_info ~220
ROS 2 graph only; on a DDS-only setup use `list_endpoints`. Describe one ROS 2 `topic` (fully qualified, e.g. `/cmd_vel`) as a `TopicInfo`: counts and, in live mode, `publisher_qos` / `subscription_qos` (`reliability`, `durability` where `transient_local` marks a latched topic such as `/tf_static`, `endpoint_count`; a `null` side says why in its `_note`) and `publisher_nodes` / `subscriber_nodes`. **Raises an MCP error** for a malformed or unknown topic, or without a `ros2` CLI. **Duration**: ROS lock, one call at a time, ends within 45 s; `busy` if held.
| Name | Type | Req | Description |
|---|---|---|---|
| topic | string | yes | Fully qualified ROS 2 topic name starting with `/`, e.g. `/cmd_vel`. Segments are letters, digits and underscores (not starting with a digit); anything else is rejected before reaching the `ros2` CLI. |
| Name | Type | Req | Description |
|---|---|---|---|
| message_type | string | yes | – |
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| name | string | yes | – |
| note | string|null | – | One sentence of context, or `None`. |
| publisher_count | integer | yes | – |
| publisher_nodes | array | – | Publisher node names (`ros2 topic info --verbose`); empty when unknown. |
| publisher_qos | – | – | QoS of the publishers. `None` when it was not read: see `publisher_qos_note`. |
| publisher_qos_note | string|null | – | Why `publisher_qos` is `None`. `None` when it is set. |
| subscriber_count | integer | yes | – |
| subscriber_nodes | array | – | Subscriber node names; empty when unknown. |
| subscription_qos | – | – | QoS of the subscriptions. `None` when it was not read: see `subscription_qos_note`. |
| subscription_qos_note | string|null | – | Why `subscription_qos` is `None`. `None` when it is set. |
No examples provided.
graph_diff ~262
ROS 2 graph only. Answer "what changed since it last worked": snapshot the graph (or `bag_path`) and compare it with `baseline`, as a `GraphDiff`. `changes` are node, topic, type, qos, rate and count differences; `added` = present now, absent from the baseline. `not_compared` names what a pair cannot support: nodes and counts need live against live, rates bag against bag, writer QoS a DDS backend. `/rosout` and `/parameter_events` count noise is only tallied in `builtin_changes_count`. The first call stores a snapshot and says so in `note`. Snapshots stay in memory (16, lost on restart). **Raises an MCP error** for an unknown id, a bad bag path or without a `ros2` CLI. **Mock mode**: the first call reports 3 changes, the next 0. **Duration**: ROS lock, one call at a time, ends within 45 s; `busy` if held.
| Name | Type | Req | Description |
|---|---|---|---|
| bag_path | – | – | Snapshot this bag instead of the live graph. |
| baseline | string | – | `previous` (last snapshot), a `snap-N` id from `snapshot_ids`, or a bag path. |
| Name | Type | Req | Description |
|---|---|---|---|
| added_count | integer | yes | – |
| baseline | – | – | `null` without baseline. |
| baseline_id | string|null | – | `null` without baseline (`note`). |
| builtin_changes_count | integer | yes | Count changes on `/rosout`, `/parameter_events`: not in `changes`. |
| changed_count | integer | yes | – |
| changes | array | yes | – |
| current | – | yes | – |
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| not_compared | object | yes | Skipped comparison and why. |
| note | string|null | – | One sentence of context, or `None`. |
| removed_count | integer | yes | – |
| snapshot_id | string | yes | Stored snapshot id. |
| snapshot_ids | array | yes | Held ids, oldest first. |
No examples provided.
health_check ~223
Report TopicForge environment state as a `HealthReport`: `mode`, `ros_backend`, `dds_backend`, `ros_tools_available`, `ros2_distro`, `rmw_implementation`, `dds_domain_id` and `observed_domain_note` (only the domain joined at startup is observed), server version and sample cap; for a live DDS backend also `observer_started_ns` / `now_ns` (nothing earlier was watched) and the discovery tracker status `tracker_*` (errors, evictions or a stale pass mean gaps). `live` with `ros_backend` `none` is a DDS-only setup: use `list_endpoints`. With `dds_backend` `none`, `dds_inactive_note` says why. `payload_decoding` is `disabled` (DDS user-topic payloads are not decoded); `dds_security` is `not_supported`. **Always succeeds**: call it first when something looks wrong. **Duration**: never waits for a lock and never runs the `ros2` CLI; it answers at once.
Input schema present but exposes no named parameters.
| Name | Type | Req | Description |
|---|---|---|---|
| contract_version | integer | – | Output contract version; changes when a field is renamed, removed or retyped. |
| dds_backend | string | – | DDS backend serving: `mock`, `cyclone` or `none` (DDS module inactive); `fast` is retired. |
| dds_domain_id | integer|null | – | DDS domain id observed when the DDS module is active. |
| dds_inactive_note | string|null | – | Why `dds_backend` is `none` while ROS 2 serves; else `null`. |
| dds_security | string | – | DDS Security is not handled: protected endpoints and data are invisible. |
| max_sample_count | integer | yes | Server cap on samples per call; larger requests are clamped. |
| middleware_available | boolean | – | True when a DDS backend serves; else whether the configured binding is importable. |
| mode | string | yes | Mode serving: `mock` or `live` (`live` + `ros_backend` `none`: DDS tools only); may differ from `requested_mode`. |
| note | string|null | – | One sentence of context, or `None`. |
| now_ns | integer|null | – | Server wall-clock time (ns since epoch) when this report was built. |
| observed_domain_note | string|null | – | Which DDS domain is observed: only the one joined at startup; others are invisible. |
| observer_started_ns | integer|null | – | ns when the DDS observer joined; nothing earlier was watched. `None` without one. |
| payload_decoding | string | – | `disabled`: DDS user-topic payloads are not decoded. |
| payload_decoding_note | string|null | – | Reason for `payload_decoding`. |
| requested_mode | string | yes | Mode requested by configuration (may be `auto`). |
| rmw_implementation | string|null | – | ROS 2 middleware, e.g. `rmw_fastrtps_cpp`; `None` when `rmw_source` is `none`. |
| rmw_source | string | – | `env`, `ros2_cli`, `distro_default` (not verified on the graph) or `none`. |
| ros2_available | boolean | yes | Whether a `ros2` CLI is on PATH. |
| ros2_distro | string|null | – | `ROS_DISTRO` if set (disclosed by design under the local-trust model). |
| ros_backend | string | – | ROS 2 backend: `ros2_cli`, `mock` or `none`. |
| ros_security_enabled | boolean|null | – | `ROS_SECURITY_ENABLE` is `true`; `None` unless the ros2 CLI serves. |
| ros_security_keystore_set | boolean|null | – | Whether a keystore variable is set (the path is never shown). |
| ros_security_note | string|null | – | Set when `ros_security_enabled`: what stays invisible. |
| ros_security_strategy | string|null | – | `ROS_SECURITY_STRATEGY` when set to a valid value. |
| ros_tools_available | boolean | – | True when the ROS 2 tools can run (`ros_backend` is not `none`); on a DDS-only setup use `list_endpoints`. |
| server_version | string | yes | TopicForge version. |
| sim_clock_published | boolean|null | – | Whether `/clock` had a publisher at the last graph read (stamps may be sim time); `None` in mock or unread. |
| tracker_cache_evictions | integer|null | – | Discovery entries dropped from a full tracker cache; non-zero means the bus is bigger than listed. |
| tracker_errors | integer|null | – | Tracker passes that raised; non-zero means gaps. |
| tracker_last_pass_ns | integer|null | – | ns of the last tracker pass; far older than `now_ns` means stale lifecycle. |
| tracker_passes | integer|null | – | Completed discovery tracker passes since start. |
| tracker_running | boolean|null | – | Whether the discovery tracker thread is alive (Cyclone); `None` without a tracker. |
No examples provided.
inspect_tf ~431
ROS 2 graph only. Rebuild the TF tree from the transforms on `/tf_static` (latched) and `/tf` (listened to for `window_s`) and flag what breaks lookups. Returns a `TfReport`: `frames` (parent, `kind` static/dynamic/both, `age_s`, `stale`, `observed_frequency_hz` on wall time, `sim_frequency_hz` on stamps), `roots`, counts and `findings` `{kind, frames, text}`: `stale` (a frame trails the newest `/tf` stamp by more than `stale_after_s`), `two_parents`, `disconnected` (several roots), `static_and_dynamic`, `cycle`, `no_path`. Ages and rates use header stamps, so they hold on sim time (`real_time_factor` far from 1 means sim). Give `source_frame` and `target_frame` for the `path` between them. It also reads `/robot_description` and flags URDF links with no frame (`declared_not_published`). **Empty `frames`** when nothing is published (read `note`). The listening is capped (3000 messages): `echo` can drop messages at high rates, so the tree is exact but frequencies are approximate (`truncated`). **Raises an MCP error** without a `ros2` CLI. **Mock mode**: a fixed 5 s capture of the demo robot with one stale frame. **Duration**: ROS lock, one call at a time, ends within 45 s; `busy` if held.
| Name | Type | Req | Description |
|---|---|---|---|
| source_frame | – | – | Frame name, e.g. `base_link`. Give `source_frame` and `target_frame` together to get the chain between them in `path`. |
| stale_after_s | number | – | A frame is stale past this many seconds of stamp time, default 2. |
| target_frame | – | – | See `source_frame`. |
| window_s | number | – | Seconds to listen to `/tf`, 1..20, default 5. |
| Name | Type | Req | Description |
|---|---|---|---|
| declared_frames_missing | array | – | URDF links with no TF frame. |
| dynamic_count | integer | yes | Frames with a dynamic transform. |
| findings | array | yes | Empty when the tree is sound. |
| frame_count | integer | yes | – |
| frames | array | yes | Sorted by name. |
| frames_not_in_urdf | array | – | TF frames not in the URDF (`map`, `odom` are normal). |
| hints | array | yes | – |
| message_count | integer | yes | Transforms captured, repeats included. |
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| note | string|null | – | One sentence of context, or `None`. |
| path | array|null | – | Frames from `source_frame` to `target_frame`. |
| path_note | string|null | – | Why `path` is `None`, or context. |
| real_time_factor | number|null | yes | Stamp time over wall time: about 1 on a real robot, other on sim. |
| reference_ns | integer|null | yes | Newest `/tf` stamp: the clock for `age_s`. |
| roots | array | yes | Frames with no parent; one in a sound tree. |
| stale_count | integer | yes | – |
| stamp_span_s | number|null | yes | – |
| static_count | integer | yes | Frames with a static transform. |
| transform_count | integer | yes | Distinct parent-child pairs. |
| truncated | boolean | yes | Messages were dropped or the cap was hit. |
| urdf_link_count | integer|null | – | – |
| urdf_note | string|null | – | – |
| urdf_source | string | – | – |
| window_actual_s | number | yes | – |
| window_s | number | yes | – |
No examples provided.
list_endpoints ~841
List every DDS endpoint (writer and reader) announced on the bus as an `EndpointListing`; one `EndpointInfo` each with `role`, `dds_topic` (raw, e.g. `rt/scan`), `ros_topic` (`/scan`; `null` with `ros_topic_note` for a non-ROS topic), `type_name`, `type_id`, the owning `participant_guid` with `participant_name`, and a structured `qos`. Use it instead of parsing `peek_dds_samples` and joining GUID prefixes. **Large buses**: pass `fields` `compact` to replace each `qos` by a one-line `qos_summary` and drop `type_id`, `announced_ns` and `ros_topic_note`; the default `full` is verbose, so scope with `topic` or `participant_guid` to read one topic's full QoS. **Orphans**: `by_topic` rolls endpoints up per topic with `writer_count`, `reader_count`, `orphan` (`no_reader` = a writer nobody subscribes to, `no_writer` = a reader nobody publishes to) and the union of partitions. In `qos`, a duration or policy of `None` is infinite/unset or not announced. Among EXCLUSIVE writers the live one with the highest `ownership_strength` delivers; which one owns an instance is runtime state TopicForge cannot observe. **Departed endpoints**: a leaving participant's endpoints are remembered (last 200, 1 h) and shown in `by_topic` as `departed_writers` / `departed_readers` (listed in `endpoints` only with `include_departed`), so a topic that lost its only writer is explained in one call. **Topic filter**: `rt/scan` and `scan` match each other; a filter matching nothing returns a `note` with the closest topics. **This lists discovery facts, not data flow**: it cannot tell a silent or hung writer from a healthy one; pair it with `detect_qos_mismatches`. The observer's own endpoints are excluded unless `include_observer`; ROS 2 service and action endpoints (`rq/`, `rr/`, `rs/`, `rp/`, `ra/`, `ros_discovery_info`) unless `include_internal`. Capped at 500 endpoints (`truncated`, `total`); `by_topic` covers all matches. **Raises an MCP error** when no DDS module is active; mock returns a fixtur…
| Name | Type | Req | Description |
|---|---|---|---|
| domain_id | integer | – | Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` fla… |
| fields | string | – | `full` (default) or `compact`: one-line `qos_summary` instead of the full `qos`, no `type_id`, `announced_ns` or `ros_topic_note`. Use `compact` on large buses. |
| include_departed | boolean | – | Also list endpoints whose participant left (flagged with `gone_ns`); `by_topic` reports them either way. Default false. |
| include_internal | boolean | – | Also list ROS 2 service and action endpoints (`rq/`, `rr/`, `rs/`, `rp/`, `ra/`, `ros_discovery_info`), most of a small robot's endpoints. Default false. |
| include_observer | boolean | – | Include TopicForge's own observer endpoints. Default false. |
| participant_guid | – | – | Only endpoints of this participant (a guid from `list_participants`). Omit for all. |
| topic | – | – | Only endpoints on this DDS topic: `scan` or `rt/scan` (exact name first, then the alternate form). Omit for every topic. |
| Name | Type | Req | Description |
|---|---|---|---|
| by_topic | array | yes | Roll-up over every matching endpoint. |
| departed_endpoint_count | integer | – | Departed endpoints matching the filters; listed in `endpoints` only with `include_departed`. |
| domain_id | integer | yes | – |
| endpoints | array | yes | Matching endpoints, capped (see `truncated`). |
| excluded_observer_endpoint_count | integer | – | TopicForge's own endpoints left out of `endpoints` (counted in `total`). |
| fields | string | – | Detail level served: `compact` drops per-endpoint QoS detail, type id and notes. |
| hidden_internal_endpoint_count | integer | – | Service/action endpoints (`rq/`, `rr/`, `rs/`, `rp/`, `ra/`, `ros_discovery_info`) hidden unless `include_internal`. |
| hints | array | – | Facts true of every endpoint, stated once (e.g. liveness is not observed). |
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| note | string|null | – | Set when a `topic` filter matched nothing: the closest known topics. |
| observer_guid | string|null | – | GUID of TopicForge's own participant, `None` in mock. |
| returned | integer | yes | – |
| snapshot_ns | integer | yes | Wall-clock time of the snapshot, ns since epoch. |
| total | integer | yes | Endpoints in the discovery cache before any filter. |
| truncated | boolean | yes | True when matching endpoints exceeded the cap. |
No examples provided.
list_nodes ~208
ROS 2 graph only; on a DDS-only setup use `list_participants`. List the nodes on the ROS 2 graph (or the mock graph) from one `ros2 node list` call as a `NodeListing`: `name`, `namespace`, `full_name`, `duplicate_count`, `lifecycle` (managed node; state not read, `lifecycle_note` if unknown), `by_namespace` counts. `duplicates` lists full names used by several nodes (a per-node tool then answers for one of them, unidentified). Nodes starting with an underscore are hidden and a node on another `ROS_DOMAIN_ID` is invisible. **Empty `nodes`** when the graph has none. Use `get_node_info` for one node's interfaces. **Raises an MCP error** without a `ros2` CLI. Cheap (two CLI calls). **Duration**: ROS lock, one call at a time, ends within 45 s; `busy` if held.
Input schema present but exposes no named parameters.
| Name | Type | Req | Description |
|---|---|---|---|
| by_namespace | object | – | Distinct full names per namespace (`/` is the root), sorted by namespace. |
| duplicates | array | yes | Full names that appear more than once on the graph. Empty when every name is unique. |
| lifecycle_count | integer | – | Nodes with `lifecycle` true. |
| lifecycle_note | string|null | – | Why `lifecycle` could not be determined (service list failed), else `None`. |
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| nodes | array | yes | Nodes on the graph, one entry per distinct full name, sorted by name. |
| note | string|null | – | One sentence of context, or `None`. |
| returned | integer | yes | Length of `nodes`. |
| total | integer | yes | Distinct node names on the graph (no cap applies). |
| truncated | boolean | yes | True when `returned` is less than `total` because of a cap. |
No examples provided.
list_participants ~523
List DDS participants observed on the bus as a `ParticipantListing`: `guid`, `vendor` (read from the GUID prefix, `unknown` for vendors that do not put theirs there, e.g. Dust DDS and RTI Connext; see `vendor_source`), optional `name` (the announced EntityName, e.g. `lidar_driver`; a participant with none is normal, not a fault: identify it by its topics), `hostname`, `domain_id` and `is_observer` (TopicForge's own read-only participant, listed like any other). Lifecycle: `status` (`active` / `left`), `first_seen_ns` / `last_seen_ns` (TopicForge's clock), `announced_ns` (DDS source timestamp) and, once left, `lost_ns` + `lost_time_source`: an upper bound of the death (exact after a clean shutdown; after a crash the lease expiry, so up to one lease earlier: 10 s Cyclone, 20 s Fast DDS, 100 s RTI). **Restarts**: a restarted node is a new GUID with the same name; `restarts_by_name` gives `lives` (GUIDs) and `departures` per name, so do not count GUIDs by hand (`seen_count` is not a restart count). Cyclone tracks discovery in the background; right after start the call waits up to 3 s for it. **Only the domain joined at startup is observed** (`health_check` `dds_domain_id`): a participant on another domain is INVISIBLE here, and `domain_id` does not switch domains (restart with `TOPICFORGE_DDS_DOMAIN_ID`). Works at the raw DDS layer, so non-ROS participants appear too. **Read-only by architecture**: it cannot publish, modify QoS or alter the bus. **Raises an MCP error** when no DDS module is active (install `topicforge[dds]`, set `TOPICFORGE_DDS_BACKEND=cyclone`); the mock backend returns fixtures. **Duration**: DDS lock, one call at a time, ends within 45 s; `busy` if held.
| Name | Type | Req | Description |
|---|---|---|---|
| domain_id | integer | – | Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` fla… |
| Name | Type | Req | Description |
|---|---|---|---|
| domain_id | integer | yes | DDS domain observed (joined at startup). |
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| note | string|null | – | One sentence of context, or `None`. |
| participants | array | yes | – |
| restarts_by_name | object | – | Names seen with several GUIDs or lost at least once (absent: ran once). A restart is a new GUID, same name. |
| restarts_by_node | object | – | `restarts_by_name` keyed by ROS 2 node full name. |
| restarts_note | string|null | – | Set when unnamed participants were left out of `restarts_by_name`. |
| returned | integer | yes | – |
| total | integer | yes | – |
| truncated | boolean | yes | – |
No examples provided.
list_topics ~144
ROS 2 graph only; on a DDS-only setup use `list_endpoints`. List the topics on the ROS 2 graph (or the mock graph) as a `TopicListing`: each has `name`, `message_type`, `publisher_count` and `subscriber_count`; `mode_effective` tells a real graph from fixtures. QoS is not listed: use `get_topic_info`. **Empty `topics`** when the graph has none or live discovery times out. **Raises an MCP error** without a `ros2` CLI. **Duration**: ROS lock, one call at a time, ends within 45 s; `busy` if held.
Input schema present but exposes no named parameters.
| Name | Type | Req | Description |
|---|---|---|---|
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| note | string|null | – | One sentence of context, or `None`. |
| returned | integer | yes | – |
| topics | array | yes | – |
| total | integer | yes | – |
| truncated | boolean | yes | – |
No examples provided.
participant_events ~500
Return DDS participant lifecycle events (`discovered` / `lost`) from a recent window, e.g. 'who left in the last 5 minutes?', as a `ParticipantEventListing`, newest first: `guid`, `event_type`, `vendor`, `timestamp_ns`, `time_source`, `observed_ns`, `age_s` (seconds before the response; live only), optional `name` (announced name), `hostname` and `domain_id`. `time_source` is `dds_source_timestamp` (timestamp of the announcement or dispose) or `observed_local` (when TopicForge noticed, weakest). **Crash caveat**: a `lost` timestamp is an upper bound of the death: exact after a clean shutdown, the lease expiry after a crash, so the process died up to one lease earlier (10 s Cyclone, 20 s Fast DDS, 100 s RTI); the two cases cannot be told apart. **Restarts**: a restarted node is a new participant (one `lost` and one `discovered`, different `guid`, same `name`); `restarts_by_name` gives `lives` and `departures` per name over the window, so do not count GUIDs by hand. Capped at 200 events (`truncated`, `note`; reduce `lookback_s`). TopicForge only knows what happened since it started watching (`health_check.observer_started_ns`). **Backends**: Cyclone polls discovery in the background every 0.5 s, so a participant cycle faster than the reader's history depth between two passes can be missed; mock returns a fixture timeline. Right after start the call waits up to 3 s for discovery. **Read-only by architecture**. **Raises an MCP error** when no DDS module is active. **Duration**: DDS lock, one call at a time, ends within 45 s; `busy` if held.
| Name | Type | Req | Description |
|---|---|---|---|
| domain_id | integer | – | Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` fla… |
| lookback_s | integer | – | Window in seconds, 1..86400, default 300. A larger window may hit the 200-event cap. |
| Name | Type | Req | Description |
|---|---|---|---|
| domain_id | integer | yes | DDS domain observed (joined at startup). |
| events | array | yes | – |
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| note | string|null | – | One sentence of context, or `None`. |
| restarts_by_name | object | – | Names seen with several GUIDs or lost at least once (absent: ran once). A restart is a new GUID, same name. Window only. |
| restarts_note | string|null | – | Set when unnamed participants were left out of `restarts_by_name`. |
| returned | integer | yes | – |
| total | integer | yes | – |
| truncated | boolean | yes | True when cut to the 200-event cap. |
No examples provided.
peek_bag_samples ~863
Peek up to `count` samples from a recorded bag file as a `SampleResult`: offline, for post-mortem (`peek_dds_samples` and `sample_messages` are live). Formats, by extension: MCAP (`.mcap`), rosbag2 SQLite (`.db3`), ROS 1 (`.bag`). Returns the **first** `count` messages of the topic in recording order, not the last (`sample_messages` keeps the newest log matches). `start_s` / `end_s` limit it to a bag-relative time range, empty with a `note` past the end; on `/rosout`, `min_log_level` and `node` keep the first `count` matching logs in range. **Clocks**: `timestamp_ns` is `header.stamp` when the message has a top-level header (`stamp_source` `header`), else the time in the body of `Clock`, `TFMessage` or `Log` (`payload`), else the bag record time (`recorded`); `recorded_ns` is always the record time. Each `payload` carries `_decode_status` (`full` / `partial` / `raw`). `count` defaults to 5, clamped to 50. **Uses the `rosbags` library** and needs the ROS 2 side of the runtime (an error on a DDS-only setup). Mock returns fixtures on canned paths. A bag without embedded definitions (Humble `.db3`) is decoded with those of its recorded distro, or Humble; `note` says which. Arrays over 4096 elements are cut and `note` lists them. **Read-only by architecture**. **Raises an MCP error** when the path does not exist, the topic is not in the bag, or `rosbags` is missing. **Summary and rate**: `LaserScan`, `Odometry`, `Imu`, `Image` and `PointCloud2` samples carry a `summary` of the whole message (other types: null); `rate` gives frequency, interval spread and a `verdict` measured on `recorded_ns` (the bag record time of the returned messages). Use `count` >= 10 (50 above 20 Hz); a multi-publisher topic such as `/tf` reads `erratic` without being broken. **Duration**: takes no lock (in-process file read, so it runs while a ROS 2 or DDS call is busy); the time follows the bag's size and how deep the first samples are.
| Name | Type | Req | Description |
|---|---|---|---|
| count | integer | – | Messages to return, default 5, at most 50 (`health_check.max_sample_count`): more is capped to 50, negative raises an error. The result's `count` may be lower. |
| end_s | – | – | Only messages recorded before this bag-relative second (exclusive), default to the end. |
| min_log_level | – | – | `/rosout` (`rcl_interfaces/msg/Log`) only: keep messages at or above this level (debug 10, info 20, warn 30, error 40, fatal 50). Other topics: error. |
| node | – | – | `/rosout` only: keep logs of this node and its child loggers, e.g. `/robot1/driver` (also `driver`, `robot1.driver`). Exact name, never a prefix. Other topics: error. |
| path | string | yes | Bag path: a `.mcap` or `.db3` file or a `rosbag2_*` directory (`peek_bag_samples` also reads ROS 1 `.bag`; `analyze_bag` does not). Existence and format are checked live; mock accepts any well-formed… |
| start_s | – | – | Only messages recorded at or after this many seconds from the bag's first message (`analyze_bag.start_ns`), default from the start. |
| topic | string | yes | Fully qualified ROS 2 topic name starting with `/`, e.g. `/cmd_vel`. Segments are letters, digits and underscores (not starting with a digit); anything else is rejected before reaching the `ros2` CLI. |
| Name | Type | Req | Description |
|---|---|---|---|
| count | integer | yes | Samples in `samples`: fewer than requested when the topic gave fewer; a request above 50 is capped to 50. |
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| note | string|null | – | Why `samples` is empty or limited, when not obvious; else `None`. |
| rate | – | – | How the topic delivered the messages, with a verdict; `null` for `peek_dds_samples`. Use `count` >= 10. |
| samples | array | yes | The samples, in backend order. |
| topic | string | yes | – |
No examples provided.
peek_dds_samples ~392
Peek recent samples on a raw DDS topic as a `SampleResult`, like `sample_messages` but read from the DDS layer (works without ROS 2). `count` defaults to 5, clamped to 50. (a) The builtin discovery topics (`DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`) return structured discovery records: on Cyclone the CURRENT state (one per live participant or endpoint), not recent events (use `participant_events`); `DCPSPublication` / `DCPSSubscription` are the raw data behind `list_endpoints`. The topic may be `/scan`, `scan` or `rt/scan`. (b) User topics: payload decoding is DISABLED on every backend, so the call returns `count` 0, empty `samples` and a `note`; that does NOT mean the topic is silent. Use `list_endpoints` for its presence, writers, readers and QoS. A user topic not announced on the bus raises an error. Right after start the call waits up to 3 s for discovery. **Read-only by architecture**: it cannot publish. **Raises an MCP error** when no DDS module is active or the topic is not announced. **Duration**: DDS lock, one call at a time, ends within 45 s; `busy` if held.
| Name | Type | Req | Description |
|---|---|---|---|
| count | integer | – | Messages to return, default 5, at most 50 (`health_check.max_sample_count`): more is capped to 50, negative raises an error. The result's `count` may be lower. |
| topic | string | yes | DDS topic name: bare (`scan`), ROS 2 mangled (`rt/scan`) or a builtin discovery topic (`DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`). Letters, digits, `_`, `/` and `::` only. |
| Name | Type | Req | Description |
|---|---|---|---|
| count | integer | yes | Samples in `samples`: fewer than requested when the topic gave fewer; a request above 50 is capped to 50. |
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| note | string|null | – | Why `samples` is empty or limited, when not obvious; else `None`. |
| rate | – | – | How the topic delivered the messages, with a verdict; `null` for `peek_dds_samples`. Use `count` >= 10. |
| samples | array | yes | The samples, in backend order. |
| topic | string | yes | – |
No examples provided.
sample_messages ~979
ROS 2 graph only; on a DDS-only setup use `list_endpoints` or `peek_dds_samples` on `DCPSPublication` / `DCPSSubscription`. Collect up to `count` live messages from a ROS 2 `topic`, waiting at most `timeout_s` seconds, as a `SampleResult`. **Live mode** streams `ros2 topic echo` with the publishers' QoS (latched topics work) and returns what arrived by the deadline: `count` is the actual number and `note` says `N of M messages` and why it is short (no publisher, silent, slow or too large to print in time; a latched topic usually holds one message, so use `count` 1). Each sample has `payload` (nested named values), `timestamp_ns` (the publisher's clock, sim time on a simulation, 0 for a headerless message such as `std_msgs/String`; see `stamp_source`) and `received_ns` (wall clock when the CLI printed it). **Arrays**: arrays, strings and bytes are cut at 128 elements by default (a 541-beam `LaserScan` loses beams 128 and up); cut fields are listed in `payload._truncated_fields` and `note`. Raise `max_array_length` (up to 65536, or null) or set `arrays_summary_only`. A message over the 1 MiB cap (`TOPICFORGE_MAX_SAMPLE_BYTES`) is dropped with a `note`. **Mock mode** returns demo-robot samples instantly. **Raises an MCP error** without a `ros2` CLI, for an unknown topic, or when the CLI fails. Never publishes. **Logs**: `min_log_level` / `node` (`/rosout` only) stream until `timeout_s` (at most 2000 messages) and return the NEWEST `count` matches in stamp order; `peek_bag_samples` returns the FIRST matches. **Summary and rate**: `LaserScan`, `Odometry`, `Imu`, `Image` and `PointCloud2` samples carry a `summary` of the whole message (other types: null); `rate` gives frequency, interval spread and a `verdict` measured on `received_ns` (when the CLI printed each message). Use `count` >= 10 (50 above 20 Hz); a multi-publisher topic such as `/tf` reads `erratic` without being broken. **Duration**: `ros2` CLI behind the ROS lock, one call at a time; returns within `timeout_…
| Name | Type | Req | Description |
|---|---|---|---|
| arrays_summary_only | boolean | – | When true, arrays become a type-and-length summary: use it to read the non-array fields of large messages. Default false. |
| count | integer | – | Messages to wait for, default 5, at most 50 (more is capped and `note` says so); 0 returns nothing. Fewer arrive when the topic is slow or `timeout_s` runs out. |
| max_array_length | – | – | Longest array, string or bytes value returned in full, 1..65536, default 128 (a 541-beam `LaserScan` is cut to 128 ranges; its `summary` still covers all). Longer values are cut and listed in `_trunc… |
| min_log_level | – | – | `/rosout` (`rcl_interfaces/msg/Log`) only: keep messages at or above this level (debug 10, info 20, warn 30, error 40, fatal 50). Other topics: error. |
| node | – | – | `/rosout` only: keep logs of this node and its child loggers, e.g. `/robot1/driver` (also `driver`, `robot1.driver`). Exact name, never a prefix. Other topics: error. |
| timeout_s | number | – | Seconds to wait, 1..40, default 10, covering topic lookup and CLI start-up. The call returns within `timeout_s` plus a few seconds, with what arrived and a `note`. Raise it for a topic slower than 1… |
| topic | string | yes | Fully qualified ROS 2 topic name starting with `/`, e.g. `/cmd_vel`. Segments are letters, digits and underscores (not starting with a digit); anything else is rejected before reaching the `ros2` CLI. |
| Name | Type | Req | Description |
|---|---|---|---|
| count | integer | yes | Samples in `samples`: fewer than requested when the topic gave fewer; a request above 50 is capped to 50. |
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| note | string|null | – | Why `samples` is empty or limited, when not obvious; else `None`. |
| rate | – | – | How the topic delivered the messages, with a verdict; `null` for `peek_dds_samples`. Use `count` >= 10. |
| samples | array | yes | The samples, in backend order. |
| topic | string | yes | – |
No examples provided.
topic_metrics ~405
Return temporal metrics (frequency, sequence gaps, latency percentiles) for a DDS topic over a recent window as a `TopicMetrics`. **Read `status` first**: `unsupported_user_topic` means the payload is not decoded, so no metric exists: every null or 0 is not a measurement; `no_samples_yet` is a builtin topic with nothing buffered; `ok` means computed. **Limits**: the buffer is filled only when `peek_dds_samples` runs on the topic, so `observed_frequency_hz` reflects how often it was called, not the publish rate: a coarse presence signal. `declared_frequency_hz` is `1 / deadline` of the shortest writer Deadline announced in discovery (declared, not measured; null when none). Also `sequence_gaps_count`, `latency_ns_p50/p95/p99` and availability flags. **Read-only by architecture**. **Raises an MCP error** when no DDS module is active or `window_s` is outside 1..3600. Right after start the call waits up to 3 s for discovery. **Duration**: DDS lock, one call at a time, ends within 45 s; `busy` if held.
| Name | Type | Req | Description |
|---|---|---|---|
| domain_id | integer | – | Ignored: it does not switch domains and is only range-checked (0..232). The domain observed is the one joined at startup (`TOPICFORGE_DDS_DOMAIN_ID`), reported in the response `domain_id`; `note` fla… |
| topic | string | yes | DDS topic name: bare (`scan`), ROS 2 mangled (`rt/scan`) or a builtin discovery topic (`DCPSParticipant`, `DCPSSubscription`, `DCPSPublication`). Letters, digits, `_`, `/` and `::` only. |
| window_s | integer | – | Window in seconds, 1..3600, default 60. |
| Name | Type | Req | Description |
|---|---|---|---|
| declared_frequency_hz | number|null | – | `1 / deadline` of the shortest announced writer Deadline: promised, not observed; `None` when none announced. |
| latency_available | boolean | – | True when a sample carried both a publish and a receive timestamp. |
| latency_ns_p50 | integer|null | – | Median publish-to-receive latency in ns; `None` unless `latency_available`. |
| latency_ns_p95 | integer|null | – | 95th-percentile publish-to-receive latency (ns). |
| latency_ns_p99 | integer|null | – | 99th-percentile publish-to-receive latency (ns). |
| mode_effective | string | yes | `live` or `mock` (fixtures). |
| note | string|null | – | One sentence of context, or `None`. |
| observed_frequency_hz | number|null | – | `samples_observed / window_actual_s`; `None` under 2 samples. |
| samples_observed | integer | yes | Samples buffered in the window. `0` does NOT mean no publisher, only that no `peek_dds_samples` call captured one. |
| sequence_gaps_count | integer | – | Missing sequence numbers in the buffered samples; `0` also when none exist (see `sequence_numbers_available`). |
| sequence_numbers_available | boolean | – | True when a sequence number could be read from a sample. |
| status | string | – | `ok`; `no_samples_yet` (empty buffer); `unsupported_user_topic` (payload not decoded: null fields are not measurements). |
| topic | string | yes | – |
| window_actual_s | number | yes | Seconds actually covered; shorter than `window_s` after a recent start; `0.0` without samples. |
| window_s | integer | yes | Requested window in seconds (echoed). |
No examples provided.
How do I install the TopicForge MCP server?
TopicForge runs locally as a PyPI package, launched with uvx topicforge. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.
pypi · topicforge
claude mcp add yaniswav-topicforge -- uvx topicforge
What is the TopicForge MCP server?
TopicForge is an MCP server listed in the public MCP registry as io.github.yaniswav/topicforge. Read-only MCP server for ROS2 and DDS: topics, participants, QoS mismatches, bags. This page covers its PyPI package (topicforge).
Is the TopicForge MCP server safe to use?
TopicForge scores 95 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 4 November 2026. Its build provenance is signed and verified. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.
What tools does the TopicForge MCP server expose?
TopicForge exposes 17 tools: health_check, list_topics, get_topic_info, list_nodes, get_node_info, and 12 more. Their descriptions and schemas cost roughly 7,580 tokens of context every time the server is loaded.
Is the TopicForge MCP server still maintained?
TopicForge is still listed as active in the MCP registry. We last reached this channel on 4 November 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.
What licence is the TopicForge MCP server under?
TopicForge declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.