| health_checkA | Report TopicForge environment state as a HealthReport: effective runtime mode (live or mock), ros_backend and dds_backend, ros_tools_available, ros2_available, ros2_distro (fed by the ROS_DISTRO env var), dds_domain_id and observed_domain_note (only the DDS domain joined at startup is observed; programs on other domains are invisible), the server version and the server-side sample cap. Reading mode: live with ros_backend none means the DDS tools are live and the ROS 2 tools are not available (a DDS-only setup: use list_endpoints for topics and wiring). With dds_backend none, dds_inactive_reason says why: backend not selected, binding not installed, or adapter failed to start. payload_decoding is disabled: DDS user-topic payloads are not decoded. dds_security is not_supported: on a secured domain participants show up but protected endpoints and data do not. For a live DDS backend it also reports dds_domain_id, observer_started_ns and now_ns (how long TopicForge has been watching: nothing before observer_started_ns was observed) and the discovery tracker status tracker_running / tracker_passes / tracker_errors / tracker_last_pass_ns / tracker_cache_evictions (errors or evictions above 0, or a stale last pass, mean the discovery data has gaps). Always succeeds: call it first when something looks wrong. Read-only; no side effects. |
| list_topicsA | ROS 2 graph only; on a DDS-only setup use list_endpoints. List every ROS 2 topic on the current graph (or the mock graph in mock mode). Returns list[TopicInfo]: each entry carries name, message_type, publisher_count, subscriber_count, and mode_effective (live or mock) to tell a real graph from fixtures. Live mode leaves qos_reliability and qos_durability null here: call get_topic_info for a topic's QoS. Empty list when the graph has no topics or when live discovery times out. Raises an MCP error when no ros2 CLI is available (DDS-only setup). Read-only; no side effects. |
| get_topic_infoA | ROS 2 graph only; on a DDS-only setup use list_endpoints. Return info for a single ROS 2 topic. topic must be a fully qualified name, e.g. /cmd_vel. Returns a TopicInfo with mode_effective (live or mock) and, in live mode, the publishers' qos_reliability (reliable / best_effort / mixed) and qos_durability (volatile / transient_local / mixed; transient_local marks a latched topic such as /tf_static). Raises an MCP error if the topic name is malformed, the topic is unknown to the active graph, or no ros2 CLI is available. Read-only; no side effects. |
| sample_messagesA | ROS 2 graph only; on a DDS-only setup use list_endpoints (topics and wiring) or peek_dds_samples on DCPSPublication / DCPSSubscription (raw discovery records). Peek up to count recent ROS 2 messages from topic. count defaults to 5 and is silently clamped to 50. Returns a SampleResult {topic, count, samples, mode_effective, note} where count is the actual number of samples returned (may be 0) and mode_effective is live or mock. Live mode runs ros2 topic echo --csv --once with a short timeout, so the result is empty when no publisher is active and at most one message comes back. Arrays: by default ros2 topic echo cuts arrays at 128 elements (a 541-beam LaserScan loses beams 128 and up); the cut is listed under _truncated_after_columns in the sample payload and in note (cut strings and bytes are listed under _truncated_columns). Raise max_array_length (up to 65536, or null for no cut) to read more, or set arrays_summary_only to see only the non-array fields. A message over the 1 MiB size cap (TOPICFORGE_MAX_SAMPLE_BYTES) is dropped and note says so. samples[i].timestamp_ns is the message's header.stamp (publish time) when the message is Header-stamped, and 0 for headerless types (e.g. std_msgs/String). The live parser exposes fields as positional CSV columns under samples[i].payload keys col_0, col_1, ..., with the verbatim CSV row under the reserved _raw_text key. Mock mode returns structured samples for the fictional demo robot. Raises an MCP error when no ros2 CLI is available. Read-only; never publishes. Distinct from peek_dds_samples, which reads the raw DDS layer. |
| analyze_bagA | Summarize a ROS 2 bag at path. Returns a BagAnalysis with storage format, duration, message count, per-topic stats, detected anomalies and mode_effective (live or mock). Per topic, frequency_hz is (n - 1) / (last - first message time) of that topic (frequency_basis topic_span), with first_timestamp_ns, last_timestamp_ns and latched; a latched topic whose messages all fall within 1 second (e.g. /tf_static, a start-up burst) has a null frequency_hz, while a latched topic published over a longer span keeps its rate. When the bag cannot be read locally, or is a large .mcap (over 200 MiB), the rate falls back to count / bag duration (bag_duration) and note says why. Live mode runs ros2 bag info and accepts .mcap and .db3 files plus rosbag2_* directories (ROS 1 .bag files are not readable by ros2 bag info; use peek_bag_samples for those); mock mode returns fixture data for any path suffix except clearly non-bag ones. Raises an MCP error if the path is malformed, missing in live mode, or unparseable, or if no ros2 CLI is available. Anomaly detection is available in mock mode only. Read-only; no side effects. |
| list_participantsA | List DDS participants observed on the bus. Returns list[ParticipantInfo]: each entry carries guid, vendor (cyclone/fast/rti/rti_micro/opensplice/opendds/coredx/intercom/dust/mock/unknown) with vendor_source, optional name (announced EntityName QoS, e.g. lidar_driver), optional hostname, domain_id, is_observer and mode_effective (live/mock). Why vendor can be unknown: the vendor is read from the participant GUID prefix (vendor_source guid_prefix; none when unknown). Some vendors, e.g. Dust DDS and RTI Connext, do not put their vendor id there, and the Cyclone Python binding does not expose the RTPS header vendor id, so those participants are listed as unknown. is_observer is true for TopicForge's own read-only participant, which is listed like any other. Lifecycle fields: status (active/left), first_seen_ns / last_seen_ns (TopicForge's local clock), seen_count, announced_ns (DDS source timestamp of the announcement), and once left lost_ns + lost_time_source. lost_ns is an upper bound of when the participant died: exact after a clean shutdown, the lease expiry after a crash (the two cannot be told apart), so a crashed process died up to one lease before it (10 s Cyclone default, 20 s Fast DDS, 100 s RTI; the dead participant's lease, not ours). Cyclone tracks discovery continuously in the background, so these stay correct between calls; right after server start the call waits up to 3 s for discovery to warm up. Only the domain joined at startup is observed (see health_check dds_domain_id): a participant on another DDS domain is INVISIBLE here, so a missing participant may be on a different domain; domain_id does not switch domains (restart with TOPICFORGE_DDS_DOMAIN_ID). Works at the raw DDS layer beneath ROS, so it also sees non-ROS participants. Read-only by architecture: it cannot publish, modify QoS, or alter the bus. Raises an MCP error when no DDS module is active (install pip install topicforge[dds] and set TOPICFORGE_DDS_BACKEND=cyclone). The mock backend returns fixtures. |
| detect_qos_mismatchesA | Explain why DDS readers and writers on the same topic do not talk, and who will. Pairs every reader with every writer per topic and returns a MismatchScan: matched (pairs DDS will connect given the announced QoS; data flow is not observed), reports (incompatible or risky QoS pairs, each 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, so a partition split is not blamed on Reliability; latent_incompatible_policies lists the RxO policies that would ALSO be incompatible once the partition/type issue is fixed), hints (orphan topics with a near-identical name, i.e. probable typos, and type id notes), plus pairs_checked, topics_scanned, policies_checked and policies_unchecked. Checked: Partition (with * and ? wildcards), type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership, DestinationOrder, DataRepresentation, History (risky only, and only where announced: discovery does not carry it). Not checked: see policies_unchecked. 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 reader logging 'deadline missed' with compatible QoS means the writer's real period exceeds the deadline, which TopicForge cannot observe). Pass topic to scope to one topic; omit to scan all. reports, matched and not_matched are capped at 200 entries each (truncated is true, the *_total fields keep the real counts, incompatible reports come first). A matched pair flagged late_joiner is a VOLATILE writer whose reader joined later on the same host: normal, not a fault. Read-only by architecture. Raises an MCP error when no DDS module is active; the mock backend returns fixtures. |
| peek_dds_samplesA | Peek recent samples on a raw DDS topic. Unlike sample_messages (which uses the ros2 CLI), this reads the DDS layer directly and works without ROS 2. Returns a SampleResult {topic, count, samples, mode_effective, note}, the same shape as sample_messages. count defaults to 5 and is silently clamped to 50. Topic categories: (a) The 3 builtin discovery topics (DCPSParticipant, DCPSSubscription, DCPSPublication) return structured discovery payloads: on Cyclone the CURRENT discovery state (one record per live participant or endpoint), not a stream of recent events (use participant_events for history). DCPSPublication and DCPSSubscription are the raw writers and readers behind list_endpoints. The topic may be given as /scan, scan or rt/scan: all three resolve to the same topic. (b) User-defined topics: payload decoding is DISABLED on every backend. The call returns count 0, samples empty and a note saying so; that does NOT mean the topic is silent. Use list_endpoints for the topic's presence, writers, readers and QoS. A user topic that is not announced on the bus raises an error. Right after server start the call waits up to 3 s for discovery to warm up. Read-only by architecture: it cannot publish. Raises an MCP error when no DDS module is active or the topic is not announced on the bus. |
| participant_eventsA | Return DDS participant lifecycle events (discovered / lost) from a recent window, e.g. 'who was on the bus 5 minutes ago and left?' or 'when did this participant first appear?'. Returns list[ParticipantEvent]: each entry carries guid, event_type, vendor, timestamp_ns (wall-clock ns since epoch), time_source, observed_ns, optional name (the participant's announced DDS name), optional hostname, domain_id, and mode_effective (live/mock). time_source says what timestamp_ns is: dds_source_timestamp (the DDS timestamp of the announcement or dispose) or observed_local (when TopicForge noticed, weakest). observed_ns is when TopicForge noticed, always at or after a DDS-derived timestamp_ns. Crash caveat: a lost timestamp is an upper bound of the death. After a clean shutdown it is exact; after a crash it is when the lease expired, so the process died between timestamp_ns minus the dead participant's lease and timestamp_ns (10 s Cyclone default, 20 s Fast DDS, 100 s RTI), and the two cases cannot be told apart. A restarted node is a new participant: expect one lost and one discovered per restart, with different guids and the same name. Sorted newest-first. Capped at 200 events, silently (reduce lookback_seconds if you hit it). TopicForge only knows what happened since it started watching (see health_check.observer_started_ns). Backend caveats: Fast DDS captures arrivals and removals through listener callbacks; Cyclone tracks discovery in the background (a pass every 0.5 s, independent of tool calls), so restarts and crashes are recorded as they happen, but a participant cycle faster than the discovery reader's history depth between two passes can be missed; mock returns a fixture timeline. Right after server start the call waits up to 3 s for discovery to warm up. Read-only by architecture. Raises an MCP error when no DDS module is active (install pip install topicforge[dds] and set TOPICFORGE_DDS_BACKEND=cyclone|fast). |
| topic_metricsA | Return temporal metrics (frequency, sequence gaps, latency percentiles) for a DDS topic over a recent time window. Returns a TopicMetrics payload carrying status, samples_observed, frequency_hz_observed, frequency_hz_declared, sequence_gaps_count, latency_ns_p50/p95/p99, and boolean availability flags. Read status first: unsupported_user_topic means the topic is a user topic, whose payload is not decoded, so there are no metrics: every number is null or 0 and none of it is a measurement. no_samples_yet means a builtin topic with nothing buffered in the window. ok means metrics were computed. Limits: the buffer is filled only when peek_dds_samples runs on the topic, so frequency_hz_observed reflects how often it was called, not the real publish rate: treat it as a coarse presence signal. frequency_hz_declared is declared, not measured: 1 / deadline of the shortest QoS Deadline a writer on the topic announced in discovery, null when none announced one. Read-only by architecture. Raises an MCP error when no DDS module is active or window_seconds is out of range (1..3600). Right after server start the call waits up to 3 s for discovery to warm up. |
| peek_bag_samplesA | Peek up to count samples from a recorded bag file. Unlike peek_dds_samples (live DDS) and sample_messages (live ROS 2 graph), this reads offline bag content for post-mortem analysis. Supported formats: MCAP (.mcap), ROS 2 rosbag2 SQLite (.db3), ROS 1 legacy chunked binary (.bag), detected from the file extension. Returns a SampleResult in the same shape as peek_dds_samples: each sample's payload carries a _decode_status annotation (full / partial / raw). count defaults to 5 and is silently clamped to 50. Requires the rosbags library (pip install topicforge[bags]) and the ROS 2 side of the runtime: on a DDS-only setup it raises an error. The mock backend returns fixture samples on canned bag paths. Bags that embed no message definitions (rosbag2 .db3 from Humble) are decoded with the type definitions of the bag's recorded distro, or Humble when it records none; note says which. Arrays over 4096 elements are cut and note lists the fields. Read-only by architecture: nothing writes to the bag file. Raises an MCP error when the bag path does not exist, the topic is not present in the bag, or rosbags is not installed. |
| list_endpointsA | List every DDS endpoint (writer and reader) announced on the bus, one EndpointInfo per endpoint with role, topic, type_name, type_id, the owning participant_guid joined with its participant_name, and a structured qos (reliability, durability, history, deadline, liveliness kind and lease, ownership kind and strength, partitions, latency budget, destination order, data representation). Use it instead of parsing peek_dds_samples output and joining GUID prefixes. Spotting orphans: by_topic rolls the endpoints up per topic with writer_count, reader_count and orphan ("no_reader" = a writer nobody subscribes to, "no_writer" = a reader nobody publishes to), plus the union of partitions. Reading qos: a duration of None (deadline_ns, liveliness_lease_ns, latency_budget_ns) means infinite or not set; a policy field of None means the endpoint did not announce it. Ownership: among EXCLUSIVE writers the live one with the highest ownership_strength delivers to a reader; which writer currently owns an instance is reader-side runtime state TopicForge cannot observe. Departed endpoints: when a participant leaves, its endpoints are remembered (last 200, 1 h) and shown in by_topic as departed_writers / departed_readers (participant name and gone_ns), so a topic that lost its only writer is explained in one call; they are listed in endpoints only with include_departed. Topic filter: rt/scan and scan match each other (exact name first; note says which form matched), and a filter that matches nothing returns a note with the closest known topics. announced_ns is the discovery announcement's source timestamp on the announcing side's clock, which can differ from this host's clock. This lists discovery facts, not data flow: it shows which endpoints exist and how they are configured, not whether samples move. activity is always None (see activity_note): TopicForge holds no reader on user topics and cannot tell a silent or hung writer from a healthy one. Pair it with detect_qos_mismatches to see which pairs cannot match. TopicForge's own observer participant is excluded unless include_observer is true. Output is capped at 500 endpoints (truncated, total_discovered); by_topic still covers all matches. Read-only. Raises an MCP error when no DDS module is active. Mock mode returns a fixture matching the other mock DDS tools. |