topicforge 0.7.1__tar.gz → 0.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {topicforge-0.7.1 → topicforge-0.7.2}/CHANGELOG.md +54 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/PKG-INFO +7 -6
- {topicforge-0.7.1 → topicforge-0.7.2}/README.md +6 -5
- {topicforge-0.7.1 → topicforge-0.7.2}/docs/CLIENTS.md +17 -17
- {topicforge-0.7.1 → topicforge-0.7.2}/docs/CONTRACT.md +55 -2
- {topicforge-0.7.1 → topicforge-0.7.2}/docs/DDS_QUICKSTART.md +1 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/docs/TESTING.md +5 -5
- {topicforge-0.7.1 → topicforge-0.7.2}/docs/TOOLS.md +45 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/docs/TROUBLESHOOTING.md +1 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/docs/TUTORIEL.md +1 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/README.md +1 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/plugin/README.md +1 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/plugin/pyproject.toml +2 -2
- {topicforge-0.7.1 → topicforge-0.7.2}/pyproject.toml +1 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/scripts/ground_truth/README.md +25 -3
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/__init__.py +1 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/base.py +3 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/dds_helpers.py +1 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/dds_introspection.py +12 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/discovery_tracker.py +39 -9
- topicforge-0.7.2/src/topicforge/adapters/common/ros_discovery.py +433 -0
- topicforge-0.7.2/src/topicforge/adapters/common/spawned_pids.py +35 -0
- topicforge-0.7.2/src/topicforge/adapters/common/tf_tree.py +403 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/composite.py +19 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/dds_cyclone/adapter.py +44 -3
- topicforge-0.7.2/src/topicforge/adapters/dds_cyclone/ros_info.py +69 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/dds_fast/adapter.py +4 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/ros2_live/adapter.py +19 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/ros2_live/echo_stream.py +2 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/ros2_live/process_runner.py +2 -0
- topicforge-0.7.2/src/topicforge/adapters/ros2_live/tf_capture.py +272 -0
- topicforge-0.7.2/src/topicforge/adapters/ros2_live/tf_parsers.py +45 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/ros2_mock/adapter.py +17 -2
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/ros2_mock/fixtures.py +40 -4
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/ros2_mock/node_fixtures.py +6 -0
- topicforge-0.7.2/src/topicforge/adapters/ros2_mock/tf_fixtures.py +58 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/constants.py +10 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/models/__init__.py +6 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/models/schemas.py +7 -0
- topicforge-0.7.2/src/topicforge/models/tf.py +107 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/server/prompts.py +3 -2
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/services/dds_views.py +11 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/services/inspector.py +65 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/tools/guard.py +2 -2
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/tools/handlers.py +54 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/contract/_prompts.json +2 -2
- topicforge-0.7.2/tests/contract/inspect_tf.json +344 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/contract/list_participants.json +12 -0
- topicforge-0.7.2/tests/fixtures/ground_truth/omnisim_071_morning_results.json +1 -0
- topicforge-0.7.2/tests/fixtures/ground_truth/omnisim_071_morning_truth.json +1 -0
- topicforge-0.7.2/tests/fixtures/ground_truth/omnisim_071_rerun_results.json +1 -0
- topicforge-0.7.2/tests/fixtures/ground_truth/omnisim_071_rerun_truth.json +1 -0
- topicforge-0.7.2/tests/fixtures/ros_discovery/humble_two_nodes.hex +1 -0
- topicforge-0.7.2/tests/fixtures/ros_discovery/jazzy_two_nodes.hex +1 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/integration/ros2/ground_truth.py +50 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/integration/ros2/publisher.py +34 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/integration/ros2/test_live_adapter.py +29 -0
- topicforge-0.7.2/tests/test_composite_ghosts.py +105 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_contract_shape.py +2 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_contract_v2.py +6 -1
- topicforge-0.7.2/tests/test_cyclone_ros_info.py +111 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_ground_truth_compare.py +266 -3
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_honest_outputs.py +1 -1
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_http_transport.py +1 -1
- topicforge-0.7.2/tests/test_restarts_by_node.py +65 -0
- topicforge-0.7.2/tests/test_ros_discovery.py +394 -0
- topicforge-0.7.2/tests/test_tf_tools.py +514 -0
- topicforge-0.7.2/tests/test_tf_tree.py +197 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_tool_annotations.py +2 -2
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_tool_descriptions.py +2 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_tools_integration.py +5 -2
- {topicforge-0.7.1 → topicforge-0.7.2}/.gitignore +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/LICENSE +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/docs/EVAL.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/docs/MIGRATION-0.7.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/docs/VALIDATION.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/docs/dds-interop-matrix.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/dds/00_hello_pub_sub/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/dds/01_who_is_on_the_bus/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/dds/02_why_cant_they_talk/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/dds/03_a_node_crashed/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/dds/04_late_joiner_misses_data/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/dds/05_reliability_in_code/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/dds/06_durability_late_joiner_in_code/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/dds/07_deadline_in_code/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/dds/08_crash_seen_from_inside/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/dds/10_lidar_silent_after_driver_swap/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/dds/11_who_talks_to_whom/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/dds/12_safety_monitor_dropout/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/dds/13_deadline_not_offered/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/dds/14_restart_loop/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/examples/dds/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/plugin/LICENSE +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/scripts/agent_eval/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/scripts/integration/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/scripts/integration/publishers/cyclone_c/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/scripts/integration/publishers/cyclone_cpp/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/scripts/integration/publishers/cyclone_rust/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/scripts/integration/publishers/dust_py/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/scripts/integration/publishers/fast_publisher_cpp/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/scripts/integration/publishers/fast_py/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/scripts/integration/publishers/opensplice_publisher/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/scripts/integration/publishers/rti_c/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/scripts/integration/publishers/rti_cpp/README.md +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/__main__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/__init__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/__init__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/bag_kind.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/cdr_decoder.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/endpoints.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/lifecycle.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/metrics_buffer.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/nodes.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/qos_analyzer.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/qos_endpoints.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/qos_fixes.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/qos_normalize.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/qos_scan.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/ros_names.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/stamps.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/topic_filter.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/common/xtypes.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/dds_fast/__init__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/ros2_live/echo_parser.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/ros2_live/node_parsers.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/ros2_live/parsers.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/ros2_live/process_tree.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/ros2_live/sample_extras.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/budget.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/config/__init__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/config/settings.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/models/nodes.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/models/summaries.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/server/__init__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/server/app.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/server/http.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/services/__init__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/services/bag_service.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/services/bag_stats.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/services/factory.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/services/health.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/services/sample_budget.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/services/summaries/__init__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/services/summaries/_common.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/services/summaries/laser_scan.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/services/summaries/motion.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/services/summaries/rate.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/services/summaries/sensor_data.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/telemetry/__init__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/telemetry/client.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/tools/__init__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/tools/annotations.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/src/topicforge/tools/schema_compact.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/__init__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/conftest.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/contract/analyze_bag.json +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/contract/detect_qos_mismatches.json +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/contract/get_node_info.json +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/contract/get_topic_info.json +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/contract/health_check.json +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/contract/list_endpoints.json +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/contract/list_nodes.json +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/contract/list_topics.json +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/contract/participant_events.json +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/contract/peek_bag_samples.json +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/contract/peek_dds_samples.json +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/contract/sample_messages.json +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/contract/topic_metrics.json +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ground_truth/omnisim_064_results.json +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ground_truth/omnisim_064_truth.json +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_echo/image_noarr.yaml +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_echo/image_trunc4.yaml +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_echo/scan_edge.yaml +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_echo/scan_noarr.yaml +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_echo/scan_trunc128.yaml +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_echo/scan_trunc3.yaml +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_echo/string_latched.yaml +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_echo/twist.yaml +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_node/node_info_humble.txt +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_node/node_info_no_param_services.txt +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_node/node_info_not_found.txt +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_node/node_list_duplicates.txt +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_node/node_list_empty.txt +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_node/node_list_plain.txt +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_node/node_list_warning_stdout.txt +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_node/param_dump_humble.yaml +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_node/param_dump_secrets.yaml +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_topic_info_verbose_clock.txt +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_topic_info_verbose_cmd_vel.txt +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_topic_info_verbose_parameter_events.txt +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_topic_info_verbose_scan.txt +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_topic_info_verbose_tf.txt +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/fixtures/ros2_topic_info_verbose_tf_static.txt +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/integration/__init__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/integration/ros2/Dockerfile +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/integration/ros2/__init__.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/integration/ros2/blocked_node.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/integration/ros2/entrypoint.sh +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/integration/ros2/run_bench.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/integration/test_real_bus.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_analyze_bag_multi_format.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_bag_omnisim_humble.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_bag_service.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_bag_stats.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_bag_summaries.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_cdr_decoder.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_composite_adapter.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_config.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_contract_snapshots.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_cyclone_adapter.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_dds_cross_vendor.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_dds_helpers.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_dds_inactive_reason.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_dds_introspection.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_dds_qos_normalization.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_dds_schemas.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_discovery_tracker.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_echo_parser.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_echo_stream.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_endpoints.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_example_node_spec.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_factory.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_fast_adapter.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_field_names.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_friction_fixes.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_health.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_history_and_hints.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_inspector.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_lifecycle_buffer.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_live_adapter_graph.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_live_adapter_parse.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_live_adapter_subprocess.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_live_sample_notes.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_metrics_buffer.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_mock_adapter.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_node_logic.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_node_parsers.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_node_tools.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_peek_bag_samples.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_process_runner.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_process_tree.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_prompts.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_qos_analyzer.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_qos_endpoints.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_qos_fixes.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_qos_scan.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_rate_verdicts.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_sample_options.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_sample_summary_flow.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_server_guard.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_server_info_version.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_server_lanes.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_stamps.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_summaries.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_telemetry.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_topic_metrics.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_version_consistency.py +0 -0
- {topicforge-0.7.1 → topicforge-0.7.2}/tests/test_xtypes.py +0 -0
|
@@ -7,6 +7,58 @@ and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.2] - 2026-10-18
|
|
11
|
+
|
|
12
|
+
TF tree inspection, ROS 2 node names on DDS participants, and a flag for the `ros2` CLI
|
|
13
|
+
processes TopicForge itself spawns. Additive only: no field or input was renamed, removed or
|
|
14
|
+
retyped (`docs/CONTRACT.md` section 9). Validated on the Docker ROS 2 bench (Humble and Jazzy,
|
|
15
|
+
rmw_fastrtps and rmw_cyclonedds).
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- `inspect_tf`, the 15th tool (additive, `docs/CONTRACT.md` section 9.8): rebuilds the TF tree
|
|
20
|
+
from `/tf_static` (latched, read once) and `/tf` (listened to for `window_s`, 1 to 20 s) and
|
|
21
|
+
returns `TfReport`: every frame with its parent, `kind` (static, dynamic, both), `age_s`,
|
|
22
|
+
`stale`, and two rates (`observed_frequency_hz` on wall time, `sim_frequency_hz` on header
|
|
23
|
+
stamps), the `roots`, and `findings` (`stale`, `two_parents`, `disconnected`,
|
|
24
|
+
`static_and_dynamic`, `cycle`, `no_path`). Ages are measured on the stamps against the newest
|
|
25
|
+
`/tf` stamp, so they hold on sim time; `real_time_factor` says when the robot runs on
|
|
26
|
+
`/clock`. `source_frame` and `target_frame` give the chain between two frames in `path`.
|
|
27
|
+
ROS lane, `ros2` CLI only (a DDS-only setup raises the usual error); the mock serves a fixed
|
|
28
|
+
tree with one stale frame. The mock `/tf` sample no longer carries `base_link -> laser`,
|
|
29
|
+
which moved to the new mock topic `/tf_static`.
|
|
30
|
+
- `list_participants` fills `node_names` (and `node_names_source` `ros_discovery_info`) on
|
|
31
|
+
the Cyclone backend: a fourth reader on `ros_discovery_info` plus a hand-written XCDR1
|
|
32
|
+
decoder (`adapters/common/ros_discovery.py`) that accepts the 24-byte (Humble) and
|
|
33
|
+
16-byte (Jazzy and later) gid. Checked on the Docker ROS 2 bench (Humble and Jazzy, rmw_fastrtps
|
|
34
|
+
and rmw_cyclonedds). Mock participants get names from the mock node fixtures.
|
|
35
|
+
- `ParticipantInfo.is_topicforge_cli`: the `ros2` CLI processes TopicForge itself spawns
|
|
36
|
+
(right after `get_node_info` they stay on the bus 0.3-2 s, so a 7-participant bus
|
|
37
|
+
listed 8) are flagged, never hidden, and the listing `note` explains them. Matched by
|
|
38
|
+
`_ros2cli_<pid>` node name, or (only when the participant's hostname is known and is this
|
|
39
|
+
machine's) Fast DDS GUID pid bits or Cyclone `__Pid`, against a 30 s ring of pids recorded
|
|
40
|
+
by the process runner; a participant announcing another host is never flagged. Limit: on
|
|
41
|
+
Windows `ros2.exe` spawns a child process, so the recorded pid rarely matches and the flag
|
|
42
|
+
mostly falls back to the time-correlation note (`may be its ghosts`).
|
|
43
|
+
- `ParticipantListing.restarts_by_node`: `restarts_by_name` keyed by ROS 2 node name.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- `MiddlewareAdapter` gains `capture_tf(window_s)` (needed by `inspect_tf`). Third-party
|
|
48
|
+
adapters implementing the protocol must add it; DDS-only ones can raise the DDS-only error
|
|
49
|
+
(`DDS_ONLY_ERROR_MSG`).
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- Ground-truth comparator (`scripts/ground_truth`, tooling only: no change to the MCP tools or
|
|
54
|
+
the output contract). Two real OmniSim runs on 0.7.1 showed false FAILs that were the host's
|
|
55
|
+
or the harness's doing, not TopicForge's: wall-clock frequency rows are now WARN at most when
|
|
56
|
+
the kit's real-time factor is under 0.5 or the wall/sim ratios of the kit and TopicForge
|
|
57
|
+
differ by more than 25 percent (simulated-time rows still gate); `/parameter_events` and
|
|
58
|
+
`/rosout` writer/reader counts are no longer compared; `ros2` CLI participants started by
|
|
59
|
+
TopicForge itself (`is_topicforge_cli` or a `_ros2cli_` node name) are ignored and reported.
|
|
60
|
+
`drive.py` now runs the DDS calls before `get_node_info` and settles 3 s before them and at the end.
|
|
61
|
+
|
|
10
62
|
## [0.7.1] - 2026-10-14
|
|
11
63
|
|
|
12
64
|
QoS fix suggestions and agent-friction fixes from the blind evaluation (`docs/EVAL.md`). Additive only: no field or
|
|
@@ -1661,7 +1713,8 @@ Initial MVP release of TopicForge: ROS Topic Inspector & Bag Analyzer MCP server
|
|
|
1661
1713
|
- The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
|
|
1662
1714
|
- `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
|
|
1663
1715
|
|
|
1664
|
-
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.7.
|
|
1716
|
+
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.7.2...HEAD
|
|
1717
|
+
[0.7.2]: https://github.com/yaniswav/TopicForge/compare/v0.7.1...v0.7.2
|
|
1665
1718
|
[0.7.1]: https://github.com/yaniswav/TopicForge/compare/v0.7.0...v0.7.1
|
|
1666
1719
|
[0.7.0]: https://github.com/yaniswav/TopicForge/compare/v0.6.4...v0.7.0
|
|
1667
1720
|
[0.6.4]: https://github.com/yaniswav/TopicForge/compare/v0.6.3...v0.6.4
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: topicforge
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: ROS Topic Inspector & Bag Analyzer MCP server for AI agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/yaniswav/TopicForge
|
|
6
6
|
Project-URL: Repository, https://github.com/yaniswav/TopicForge
|
|
@@ -73,7 +73,7 @@ A read-only MCP (Model Context Protocol) server that lets an AI agent inspect a
|
|
|
73
73
|
|
|
74
74
|
Every tool also declares itself read-only in the MCP protocol (`readOnlyHint`, `destructiveHint=false`, `idempotentHint`, with an honest `openWorldHint`), and a test fails if any tool lacks it. See [`SECURITY.md`](SECURITY.md#read-only-guarantee-architecture-declaration-proof) for the guarantee and its limits.
|
|
75
75
|
|
|
76
|
-
It gives the agent
|
|
76
|
+
It gives the agent fifteen typed tools that return frozen Pydantic schemas (output contract 2: one object per tool, one `note`, units in the field names; see [`docs/CONTRACT.md`](docs/CONTRACT.md)), identical whether the server talks to a real robot or to its built-in mock fixtures. Ask why `nav_planner` gets no scan, and the agent reads the bus, finds the BEST_EFFORT writer facing a RELIABLE reader and names the incompatible policy (see [`examples/02-debug-qos-mismatch.md`](examples/02-debug-qos-mismatch.md)). It is meant for ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
|
|
77
77
|
|
|
78
78
|
For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes. Cyclone DDS, Fast DDS and Dust DDS participants have been observed on live buses: the Fast DDS ones on every CI run of the ROS 2 test bench (Humble and Jazzy with `rmw_fastrtps_cpp`) and in the OmniSim validation. RTI Connext, OpenDDS and CoreDX announce themselves through the same standard discovery, but none of them has been observed yet. This covers discovery only: participants, readers, writers and their QoS. See [`docs/dds-interop-matrix.md`](docs/dds-interop-matrix.md).
|
|
79
79
|
|
|
@@ -103,7 +103,7 @@ The server speaks MCP over stdio and waits for a client, so wire it into one. Fo
|
|
|
103
103
|
|
|
104
104
|
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`. If the client cannot launch the server (WSL2 to Windows, a robot over an SSH tunnel), `topicforge --transport streamable-http --port 8765` serves `http://127.0.0.1:8765/mcp` on loopback only; see [`docs/CLIENTS.md`](docs/CLIENTS.md#local-http-transport).
|
|
105
105
|
|
|
106
|
-
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=
|
|
106
|
+
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjcuMiIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D) | [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.7.2%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D).
|
|
107
107
|
|
|
108
108
|
Setup for a real ROS2 environment (WSL2, Linux, Docker, native Windows) is in [`docs/TESTING.md`](docs/TESTING.md); recurring monitoring prompts and the privacy contract are in [`docs/TUTORIEL.md`](docs/TUTORIEL.md).
|
|
109
109
|
|
|
@@ -118,6 +118,7 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
|
|
|
118
118
|
| `get_topic_info` | Message type, publisher/subscriber counts and QoS for one topic |
|
|
119
119
|
| `list_nodes` | ROS 2 nodes on the graph: names, namespaces, duplicate names (one `ros2 node list` call) |
|
|
120
120
|
| `get_node_info` | One node's publishers, subscribers, services, actions, parameters (secrets masked) and `use_sim_time`; a node that does not answer is reported, not an error |
|
|
121
|
+
| `inspect_tf` | The TF tree from `/tf_static` and `/tf`: roots, stale and two-parent frames, disconnected trees, sim-time-safe ages and rates, optional path between two frames |
|
|
121
122
|
| `sample_messages` | Peek recent messages on a ROS2 topic (count capped at 50) |
|
|
122
123
|
| `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
|
|
123
124
|
| `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
|
|
@@ -150,7 +151,7 @@ pip install "topicforge[dds]" # alias, kept so older commands
|
|
|
150
151
|
TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
|
|
151
152
|
```
|
|
152
153
|
|
|
153
|
-
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the
|
|
154
|
+
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the seven `ros2` CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `inspect_tf`, `sample_messages`, `analyze_bag`) to the CLI and the six DDS tools to the DDS backend; `peek_bag_samples` reads bags without the CLI.
|
|
154
155
|
|
|
155
156
|
A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Python binding is not on PyPI: build it from eProsima's sources and install it next to TopicForge. There is no `[dds-fast]` extra. Any other value (`rti`, `opensplice`, `coredx`, `intercom`, `opendds`, `dust`) is rejected at startup with an error that lists the valid ones; Cyclone already sees those vendors' participants through standard discovery. Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md); error messages are in [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).
|
|
156
157
|
|
|
@@ -202,10 +203,10 @@ When on, each tool call emits one event with exactly six fields:
|
|
|
202
203
|
|
|
203
204
|
| Field | Example | Notes |
|
|
204
205
|
| ------------ | --------------- | ----------------------------------------------------------- |
|
|
205
|
-
| `tool_name` | `"list_topics"` | One of the
|
|
206
|
+
| `tool_name` | `"list_topics"` | One of the fifteen tools, never argument values |
|
|
206
207
|
| `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
|
|
207
208
|
| `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
|
|
208
|
-
| `version` | `"0.7.
|
|
209
|
+
| `version` | `"0.7.2"` | TopicForge server version |
|
|
209
210
|
| `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
|
|
210
211
|
| `success` | `true` | Whether the handler returned or raised |
|
|
211
212
|
|
|
@@ -12,7 +12,7 @@ A read-only MCP (Model Context Protocol) server that lets an AI agent inspect a
|
|
|
12
12
|
|
|
13
13
|
Every tool also declares itself read-only in the MCP protocol (`readOnlyHint`, `destructiveHint=false`, `idempotentHint`, with an honest `openWorldHint`), and a test fails if any tool lacks it. See [`SECURITY.md`](SECURITY.md#read-only-guarantee-architecture-declaration-proof) for the guarantee and its limits.
|
|
14
14
|
|
|
15
|
-
It gives the agent
|
|
15
|
+
It gives the agent fifteen typed tools that return frozen Pydantic schemas (output contract 2: one object per tool, one `note`, units in the field names; see [`docs/CONTRACT.md`](docs/CONTRACT.md)), identical whether the server talks to a real robot or to its built-in mock fixtures. Ask why `nav_planner` gets no scan, and the agent reads the bus, finds the BEST_EFFORT writer facing a RELIABLE reader and names the incompatible policy (see [`examples/02-debug-qos-mismatch.md`](examples/02-debug-qos-mismatch.md)). It is meant for ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
|
|
16
16
|
|
|
17
17
|
For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes. Cyclone DDS, Fast DDS and Dust DDS participants have been observed on live buses: the Fast DDS ones on every CI run of the ROS 2 test bench (Humble and Jazzy with `rmw_fastrtps_cpp`) and in the OmniSim validation. RTI Connext, OpenDDS and CoreDX announce themselves through the same standard discovery, but none of them has been observed yet. This covers discovery only: participants, readers, writers and their QoS. See [`docs/dds-interop-matrix.md`](docs/dds-interop-matrix.md).
|
|
18
18
|
|
|
@@ -42,7 +42,7 @@ The server speaks MCP over stdio and waits for a client, so wire it into one. Fo
|
|
|
42
42
|
|
|
43
43
|
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`. If the client cannot launch the server (WSL2 to Windows, a robot over an SSH tunnel), `topicforge --transport streamable-http --port 8765` serves `http://127.0.0.1:8765/mcp` on loopback only; see [`docs/CLIENTS.md`](docs/CLIENTS.md#local-http-transport).
|
|
44
44
|
|
|
45
|
-
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=
|
|
45
|
+
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjcuMiIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D) | [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.7.2%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D).
|
|
46
46
|
|
|
47
47
|
Setup for a real ROS2 environment (WSL2, Linux, Docker, native Windows) is in [`docs/TESTING.md`](docs/TESTING.md); recurring monitoring prompts and the privacy contract are in [`docs/TUTORIEL.md`](docs/TUTORIEL.md).
|
|
48
48
|
|
|
@@ -57,6 +57,7 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
|
|
|
57
57
|
| `get_topic_info` | Message type, publisher/subscriber counts and QoS for one topic |
|
|
58
58
|
| `list_nodes` | ROS 2 nodes on the graph: names, namespaces, duplicate names (one `ros2 node list` call) |
|
|
59
59
|
| `get_node_info` | One node's publishers, subscribers, services, actions, parameters (secrets masked) and `use_sim_time`; a node that does not answer is reported, not an error |
|
|
60
|
+
| `inspect_tf` | The TF tree from `/tf_static` and `/tf`: roots, stale and two-parent frames, disconnected trees, sim-time-safe ages and rates, optional path between two frames |
|
|
60
61
|
| `sample_messages` | Peek recent messages on a ROS2 topic (count capped at 50) |
|
|
61
62
|
| `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
|
|
62
63
|
| `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
|
|
@@ -89,7 +90,7 @@ pip install "topicforge[dds]" # alias, kept so older commands
|
|
|
89
90
|
TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
|
|
90
91
|
```
|
|
91
92
|
|
|
92
|
-
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the
|
|
93
|
+
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the seven `ros2` CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `inspect_tf`, `sample_messages`, `analyze_bag`) to the CLI and the six DDS tools to the DDS backend; `peek_bag_samples` reads bags without the CLI.
|
|
93
94
|
|
|
94
95
|
A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Python binding is not on PyPI: build it from eProsima's sources and install it next to TopicForge. There is no `[dds-fast]` extra. Any other value (`rti`, `opensplice`, `coredx`, `intercom`, `opendds`, `dust`) is rejected at startup with an error that lists the valid ones; Cyclone already sees those vendors' participants through standard discovery. Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md); error messages are in [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).
|
|
95
96
|
|
|
@@ -141,10 +142,10 @@ When on, each tool call emits one event with exactly six fields:
|
|
|
141
142
|
|
|
142
143
|
| Field | Example | Notes |
|
|
143
144
|
| ------------ | --------------- | ----------------------------------------------------------- |
|
|
144
|
-
| `tool_name` | `"list_topics"` | One of the
|
|
145
|
+
| `tool_name` | `"list_topics"` | One of the fifteen tools, never argument values |
|
|
145
146
|
| `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
|
|
146
147
|
| `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
|
|
147
|
-
| `version` | `"0.7.
|
|
148
|
+
| `version` | `"0.7.2"` | TopicForge server version |
|
|
148
149
|
| `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
|
|
149
150
|
| `success` | `true` | Whether the handler returned or raised |
|
|
150
151
|
|
|
@@ -6,7 +6,7 @@ TopicForge (ROS 2 / DDS) is a local, read-only MCP server that speaks stdio.
|
|
|
6
6
|
Every client below launches it as a child process with the same command:
|
|
7
7
|
|
|
8
8
|
```
|
|
9
|
-
uvx --from "topicforge==0.7.
|
|
9
|
+
uvx --from "topicforge==0.7.2" topicforge
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
and the same three environment variables:
|
|
@@ -21,7 +21,7 @@ The registry name is `io.github.yaniswav/topicforge`. Where a client asks for a
|
|
|
21
21
|
display name, use **TopicForge (ROS 2 / DDS)**: an unrelated SEO product is also
|
|
22
22
|
called TopicForge in the MCP registries.
|
|
23
23
|
|
|
24
|
-
The version pin (`==0.7.
|
|
24
|
+
The version pin (`==0.7.2`) is moved on every release together with the other
|
|
25
25
|
version strings; a test keeps them in sync.
|
|
26
26
|
|
|
27
27
|
## Before you start
|
|
@@ -44,7 +44,7 @@ version strings; a test keeps them in sync.
|
|
|
44
44
|
`"--from"` in the args. To build the binding yourself, install the Cyclone C library and
|
|
45
45
|
run `pip install "topicforge[dds]"`. The `[dds]` and `[dds-cyclone]` extras still work
|
|
46
46
|
as aliases, so older commands keep installing.
|
|
47
|
-
- **pip alternative.** `pip install "topicforge==0.7.
|
|
47
|
+
- **pip alternative.** `pip install "topicforge==0.7.2"`, then use
|
|
48
48
|
`"command": "topicforge"` (or `"command": "python", "args": ["-m", "topicforge"]`)
|
|
49
49
|
with no `args` for uvx. Use the absolute path of the binary if the client does
|
|
50
50
|
not see your venv.
|
|
@@ -72,7 +72,7 @@ say where the file lives and what differs.
|
|
|
72
72
|
"mcpServers": {
|
|
73
73
|
"topicforge": {
|
|
74
74
|
"command": "uvx",
|
|
75
|
-
"args": ["--from", "topicforge==0.7.
|
|
75
|
+
"args": ["--from", "topicforge==0.7.2", "topicforge"],
|
|
76
76
|
"env": {
|
|
77
77
|
"TOPICFORGE_MODE": "auto",
|
|
78
78
|
"TOPICFORGE_DDS_BACKEND": "cyclone",
|
|
@@ -95,7 +95,7 @@ claude plugin install topicforge@topicforge
|
|
|
95
95
|
Or just the server:
|
|
96
96
|
|
|
97
97
|
```
|
|
98
|
-
claude mcp add topicforge --env TOPICFORGE_MODE=auto --env TOPICFORGE_DDS_BACKEND=cyclone --env TOPICFORGE_DDS_DOMAIN_ID=0 -- uvx --from "topicforge==0.7.
|
|
98
|
+
claude mcp add topicforge --env TOPICFORGE_MODE=auto --env TOPICFORGE_DDS_BACKEND=cyclone --env TOPICFORGE_DDS_DOMAIN_ID=0 -- uvx --from "topicforge==0.7.2" topicforge
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
## Claude Desktop
|
|
@@ -112,9 +112,9 @@ the common JSON shape above and restart the app completely. On Windows, if
|
|
|
112
112
|
|
|
113
113
|
The one-click links in this page and in the README are https redirects, because GitHub and PyPI strip `cursor://` and `vscode:` links. Only the raw schemes are documented by Cursor and VS Code; the https forms (`cursor.com/install-mcp`, `vscode.dev/redirect/mcp/install`) were observed to redirect correctly on 2026-10-06 but are not in the vendors' docs (unverified, last checked 2026-10-06). The raw links are given as a fallback.
|
|
114
114
|
|
|
115
|
-
One click: [Add TopicForge to Cursor](https://cursor.com/install-mcp?name=topicforge&config=
|
|
115
|
+
One click: [Add TopicForge to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjcuMiIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D)
|
|
116
116
|
|
|
117
|
-
If that page does not open Cursor, use the raw deeplink: `cursor://anysphere.cursor-deeplink/mcp/install?name=topicforge&config=
|
|
117
|
+
If that page does not open Cursor, use the raw deeplink: `cursor://anysphere.cursor-deeplink/mcp/install?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjcuMiIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D`
|
|
118
118
|
|
|
119
119
|
Or paste the common JSON shape into `~/.cursor/mcp.json` (all projects) or
|
|
120
120
|
`.cursor/mcp.json` (one project).
|
|
@@ -125,9 +125,9 @@ The `plugin/` folder of this repository is also an [Agent Plugins](https://agent
|
|
|
125
125
|
|
|
126
126
|
## VS Code / GitHub Copilot
|
|
127
127
|
|
|
128
|
-
One click: [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.7.
|
|
128
|
+
One click: [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.7.2%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D)
|
|
129
129
|
|
|
130
|
-
Fallback, the raw URL handler: `vscode:mcp/install?%7B%22name%22%3A%22topicforge%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.7.
|
|
130
|
+
Fallback, the raw URL handler: `vscode:mcp/install?%7B%22name%22%3A%22topicforge%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.7.2%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D`
|
|
131
131
|
|
|
132
132
|
Or put this in `.vscode/mcp.json` (the key is `servers`, not `mcpServers`), or run
|
|
133
133
|
"MCP: Add Server" from the Command Palette:
|
|
@@ -138,7 +138,7 @@ Or put this in `.vscode/mcp.json` (the key is `servers`, not `mcpServers`), or r
|
|
|
138
138
|
"topicforge": {
|
|
139
139
|
"type": "stdio",
|
|
140
140
|
"command": "uvx",
|
|
141
|
-
"args": ["--from", "topicforge==0.7.
|
|
141
|
+
"args": ["--from", "topicforge==0.7.2", "topicforge"],
|
|
142
142
|
"env": {
|
|
143
143
|
"TOPICFORGE_MODE": "auto",
|
|
144
144
|
"TOPICFORGE_DDS_BACKEND": "cyclone",
|
|
@@ -167,7 +167,7 @@ Servers", and paste the common JSON shape (the file is
|
|
|
167
167
|
Project file `.roo/mcp.json` (takes precedence) or the global `mcp_settings.json`
|
|
168
168
|
opened from the MCP view. Paste the common JSON shape. Roo's docs say Windows
|
|
169
169
|
needs a `cmd /c` wrapper for `npx`; for `uvx`, try it directly first and fall back
|
|
170
|
-
to `"command": "cmd", "args": ["/c", "uvx", "--from", "topicforge==0.7.
|
|
170
|
+
to `"command": "cmd", "args": ["/c", "uvx", "--from", "topicforge==0.7.2",
|
|
171
171
|
"topicforge"]` if it fails to start.
|
|
172
172
|
|
|
173
173
|
## Continue
|
|
@@ -184,7 +184,7 @@ mcpServers:
|
|
|
184
184
|
command: uvx
|
|
185
185
|
args:
|
|
186
186
|
- "--from"
|
|
187
|
-
- "topicforge==0.7.
|
|
187
|
+
- "topicforge==0.7.2"
|
|
188
188
|
- "topicforge"
|
|
189
189
|
env:
|
|
190
190
|
TOPICFORGE_MODE: auto
|
|
@@ -201,7 +201,7 @@ In `settings.json`, under `context_servers`:
|
|
|
201
201
|
"context_servers": {
|
|
202
202
|
"topicforge": {
|
|
203
203
|
"command": "uvx",
|
|
204
|
-
"args": ["--from", "topicforge==0.7.
|
|
204
|
+
"args": ["--from", "topicforge==0.7.2", "topicforge"],
|
|
205
205
|
"env": {
|
|
206
206
|
"TOPICFORGE_MODE": "auto",
|
|
207
207
|
"TOPICFORGE_DDS_BACKEND": "cyclone",
|
|
@@ -227,7 +227,7 @@ run with the dds extra can exceed, so set it to 60 s:
|
|
|
227
227
|
```toml
|
|
228
228
|
[mcp_servers.topicforge]
|
|
229
229
|
command = "uvx"
|
|
230
|
-
args = ["--from", "topicforge==0.7.
|
|
230
|
+
args = ["--from", "topicforge==0.7.2", "topicforge"]
|
|
231
231
|
startup_timeout_sec = 60
|
|
232
232
|
|
|
233
233
|
[mcp_servers.topicforge.env]
|
|
@@ -258,7 +258,7 @@ extensions:
|
|
|
258
258
|
name: TopicForge (ROS 2 / DDS)
|
|
259
259
|
type: stdio
|
|
260
260
|
cmd: uvx
|
|
261
|
-
args: [--from, "topicforge==0.7.
|
|
261
|
+
args: [--from, "topicforge==0.7.2", topicforge]
|
|
262
262
|
enabled: true
|
|
263
263
|
envs: { "TOPICFORGE_MODE": "auto", "TOPICFORGE_DDS_BACKEND": "cyclone", "TOPICFORGE_DDS_DOMAIN_ID": "0" }
|
|
264
264
|
timeout: 300
|
|
@@ -269,7 +269,7 @@ extensions:
|
|
|
269
269
|
Program tab > Install > Edit `mcp.json` (same notation as Cursor), then paste the
|
|
270
270
|
common JSON shape. LM Studio's docs only show remote examples; local command
|
|
271
271
|
servers follow the Cursor notation but are not documented explicitly. Small
|
|
272
|
-
local models may struggle with
|
|
272
|
+
local models may struggle with fifteen tools.
|
|
273
273
|
|
|
274
274
|
## Amazon Q Developer / Kiro
|
|
275
275
|
|
|
@@ -325,7 +325,7 @@ when the question matches.
|
|
|
325
325
|
|
|
326
326
|
## Troubleshooting
|
|
327
327
|
|
|
328
|
-
- Run the command by hand first: `uvx --from "topicforge==0.7.
|
|
328
|
+
- Run the command by hand first: `uvx --from "topicforge==0.7.2" topicforge --version`.
|
|
329
329
|
- Server starts but sees no DDS participants: check the domain id, and that the
|
|
330
330
|
client machine is on the same network as the robot (DDS discovery uses
|
|
331
331
|
multicast UDP). Ask the assistant to call `health_check`.
|
|
@@ -171,7 +171,7 @@ The 45 s are wall time from the request to the response, lock wait included. The
|
|
|
171
171
|
enforces it (`topicforge.tools.guard`, with `topicforge.budget` passing the deadline down):
|
|
172
172
|
|
|
173
173
|
- Calls run in two lanes with one lock each: ROS (`list_topics`, `get_topic_info`,
|
|
174
|
-
`list_nodes`, `get_node_info`, `sample_messages`, `analyze_bag`) and DDS (`list_participants`, `detect_qos_mismatches`, `peek_dds_samples`,
|
|
174
|
+
`list_nodes`, `get_node_info`, `inspect_tf`, `sample_messages`, `analyze_bag`) and DDS (`list_participants`, `detect_qos_mismatches`, `peek_dds_samples`,
|
|
175
175
|
`participant_events`, `topic_metrics`, `list_endpoints`).
|
|
176
176
|
`health_check` never takes a lock and never runs the `ros2` CLI; `peek_bag_samples` reads
|
|
177
177
|
a file in pure Python and takes none either.
|
|
@@ -324,6 +324,17 @@ rather than absent). There is no `participant_name` value: since Foxy the partic
|
|
|
324
324
|
carries the name of the enclave, not of a node. The field exists because Fast DDS
|
|
325
325
|
participants all announce the name `/`.
|
|
326
326
|
|
|
327
|
+
Filled since 0.7.2 on the Cyclone backend (`node_names_source` `ros_discovery_info`) and
|
|
328
|
+
by the mock. A fourth reader on `ros_discovery_info` (type
|
|
329
|
+
`rmw_dds_common::msg::dds_::ParticipantEntitiesInfo_`, RELIABLE, TRANSIENT_LOCAL, KEEP_ALL)
|
|
330
|
+
feeds a cache keyed by the 12-byte GUID prefix; `adapters/common/ros_discovery.py` decodes
|
|
331
|
+
the XCDR1 payload by hand and accepts the gid width (24 or 16) that consumes the buffer
|
|
332
|
+
exactly. The decoder was checked on captures from real rmw_fastrtps nodes (Humble: 24,
|
|
333
|
+
Jazzy: 16, both in WSL), and the Docker ROS 2 bench names every node on Humble and Jazzy
|
|
334
|
+
with both rmw_fastrtps and rmw_cyclonedds; Connext writers are untested. A
|
|
335
|
+
participant that has left keeps the last non-empty list it announced. Fast DDS and RTI
|
|
336
|
+
backends leave it `none`.
|
|
337
|
+
|
|
327
338
|
Risk: `ros_discovery_info` is a user topic whose `Gid.data` is 24 bytes on Humble
|
|
328
339
|
and 16 bytes from Iron. Fallback: `ros2_cli`, through the GID prefixes printed by
|
|
329
340
|
`ros2 topic info -v /parameter_events` and `/rosout`.
|
|
@@ -658,7 +669,9 @@ shows only added properties.
|
|
|
658
669
|
### 9.1 Listing size
|
|
659
670
|
|
|
660
671
|
`tools/list` carries the description, input schema and output schema of every tool, and some
|
|
661
|
-
clients truncate it. In 0.7.0 it was about 178 KB compact (14 tools); 0.7.
|
|
672
|
+
clients truncate it. In 0.7.0 it was about 178 KB compact (14 tools); 0.7.2 serves about 119 KB
|
|
673
|
+
(measured 118,884 bytes against the 120,000 test ceiling), so the next tool needs the schema
|
|
674
|
+
compaction planned for 0.7.3 first.
|
|
662
675
|
How:
|
|
663
676
|
|
|
664
677
|
- Pydantic's generated `title` is dropped from every property and nested model, and the
|
|
@@ -671,6 +684,20 @@ How:
|
|
|
671
684
|
|
|
672
685
|
Rule for new fields: one sentence, no history, no restating the name.
|
|
673
686
|
|
|
687
|
+
### 9.1b `is_topicforge_cli` and `restarts_by_node` (0.7.2)
|
|
688
|
+
|
|
689
|
+
Both additive. `ParticipantInfo.is_topicforge_cli` (default `false`) marks the `ros2` CLI
|
|
690
|
+
process that a TopicForge call (`list_nodes`, `get_node_info`, `sample_messages`, ...)
|
|
691
|
+
spawned: it joins the domain as a participant and stays 0.3-2 s after exiting, so a bus
|
|
692
|
+
of 7 looked like 8. A participant is flagged, never removed, when one of these matches a
|
|
693
|
+
pid TopicForge started in the last 30 s: its `/_ros2cli_<pid>` node name; the pid in a
|
|
694
|
+
Fast DDS GUID prefix (bytes 4-5, low 16 bits); a Cyclone `__Pid` property. The listing
|
|
695
|
+
`note` says so, and counts participants that merely appeared within 5 s of a CLI call.
|
|
696
|
+
Only the composite (ROS CLI plus DDS) adapter flags; the pid check is unavailable where the
|
|
697
|
+
launcher is not the process that joins DDS (Windows `ros2.exe`), the note still applies.
|
|
698
|
+
`ParticipantListing.restarts_by_node` is `restarts_by_name` keyed by node full name, over
|
|
699
|
+
the participants that announced node names, the CLI ghosts left out.
|
|
700
|
+
|
|
674
701
|
### 9.2 `restarts_by_name`
|
|
675
702
|
|
|
676
703
|
A restarted ROS 2 node is a new DDS participant with a new GUID and the same name, so
|
|
@@ -774,3 +801,29 @@ value; compare against `health_check.now_ns` when needed.
|
|
|
774
801
|
a reader `deadline_ns` tighter than the writer's is incompatible.
|
|
775
802
|
- `HealthReport.ros2_distro` discloses `ROS_DISTRO` by design under the local-trust threat model
|
|
776
803
|
(README "Security model"); a hosted multi-tenant endpoint would scrub it.
|
|
804
|
+
|
|
805
|
+
### 9.8 `inspect_tf` (tool 15)
|
|
806
|
+
|
|
807
|
+
Tool 15, additive, ROS lane, open-world, `ros2` CLI only (a DDS-only setup raises the usual
|
|
808
|
+
error). It returns `TfReport`. Inputs: `window_s` (1..20, default 5), `stale_after_s` (default
|
|
809
|
+
2), `source_frame` and `target_frame` (both or neither).
|
|
810
|
+
|
|
811
|
+
- The tree comes from the transforms: `/tf_static` is read once (transient_local, one latched
|
|
812
|
+
message per publisher; it stops as soon as they are in, and waits at most the longer of
|
|
813
|
+
`window_s` and 6 s), concurrently with `/tf`, which is streamed uncut for `window_s`, at most
|
|
814
|
+
3000 messages. A topic that is absent or has no publisher is a `note`; when both streams fail for any
|
|
815
|
+
other reason the call raises an error instead of returning an empty tree.
|
|
816
|
+
- Time is the header stamp. `age_s` is the distance between a frame's newest stamp and the
|
|
817
|
+
newest `/tf` stamp (`reference_ns`), so it is correct on sim time. `stale` is `age_s` above
|
|
818
|
+
`stale_after_s`; static frames have no age. `observed_frequency_hz` uses the arrival time,
|
|
819
|
+
`sim_frequency_hz` the stamps, `real_time_factor` is their ratio (about 1 on a real robot).
|
|
820
|
+
- `findings.kind` is an open set (1.9): `stale`, `two_parents`, `disconnected` (more than one
|
|
821
|
+
root), `static_and_dynamic` (a frame on both topics), `cycle`, `no_path` (unknown or
|
|
822
|
+
unconnected frames). A sound tree has none.
|
|
823
|
+
- Ages are measured against the newest `/tf` stamp, so one publisher with a future stamp or a
|
|
824
|
+
zero stamp skews the staleness of every other frame: a frame can read as stale because
|
|
825
|
+
another one runs ahead. Check `stamp_span_s` and the stamps when many frames go stale at once.
|
|
826
|
+
- `echo` can drop messages at high rates: the structure and the findings hold, the exact
|
|
827
|
+
message counts and frequencies are approximate (`truncated` says when messages were lost).
|
|
828
|
+
- The mock `/tf_static` topic carries the fixed frames of the demo robot; mock `inspect_tf`
|
|
829
|
+
is a fixed 5 s capture and ignores `window_s`.
|
|
@@ -76,7 +76,7 @@ Order of checks per reader/writer pair: Partition first (wildcards `*` and `?` o
|
|
|
76
76
|
|
|
77
77
|
## 4. Composite adapter and backend selection
|
|
78
78
|
|
|
79
|
-
When `ros2` is on PATH and a DDS backend starts, TopicForge builds both a `Ros2CliAdapter` and the DDS adapter behind a `CompositeAdapter`: the
|
|
79
|
+
When `ros2` is on PATH and a DDS backend starts, TopicForge builds both a `Ros2CliAdapter` and the DDS adapter behind a `CompositeAdapter`: the seven `ros2` CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `inspect_tf`, `sample_messages`, `analyze_bag`) go to the CLI and the six DDS tools to the DDS backend; `peek_bag_samples` reads bags without the CLI. An explicit DDS backend (`cyclone`, `fast`, `auto`) is honoured in every mode except `mock`, including on a host without `ros2`.
|
|
80
80
|
|
|
81
81
|
| `TOPICFORGE_MODE` | `TOPICFORGE_DDS_BACKEND` | `ros2` on PATH | Active adapter | ROS2 graph and bag tools | DDS tools |
|
|
82
82
|
| --- | --- | --- | --- | --- | --- |
|
|
@@ -4,7 +4,7 @@ How to get a working ROS2 environment to point TopicForge at, and how to wire it
|
|
|
4
4
|
|
|
5
5
|
| You want to... | Time | Path |
|
|
6
6
|
| --- | --- | --- |
|
|
7
|
-
| Try the
|
|
7
|
+
| Try the fifteen tools without installing ROS2 | 5 min | [Mock mode](#mock-mode) |
|
|
8
8
|
| Live mode on Windows | 45 min | [WSL2 + Humble](#wsl2--ros2-humble-windows-recommended) |
|
|
9
9
|
| Live mode on Ubuntu | 20 min | [Linux native](#linux-native) |
|
|
10
10
|
| Throwaway environment | 15 min | [Docker](#docker) |
|
|
@@ -15,7 +15,7 @@ How to get a working ROS2 environment to point TopicForge at, and how to wire it
|
|
|
15
15
|
```bash
|
|
16
16
|
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\Activate.ps1
|
|
17
17
|
pip install topicforge
|
|
18
|
-
python -m topicforge --version # -> topicforge 0.7.
|
|
18
|
+
python -m topicforge --version # -> topicforge 0.7.2
|
|
19
19
|
TOPICFORGE_MODE=mock python -m topicforge # blocks on stdio; MCP clients spawn it
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -41,7 +41,7 @@ python3 -m venv ~/topicforge-venv && source ~/topicforge-venv/bin/activate
|
|
|
41
41
|
pip install topicforge # Ubuntu 22.04 ships Python 3.10, which is enough
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Then run live mode with three terminals. In the first, `ros2 run demo_nodes_cpp talker` publishes `/chatter` at about 1 Hz. In the second, `ros2 topic list` should show `/chatter`. In the third, source ROS2, activate the venv and run `TOPICFORGE_MODE=live python -m topicforge`. The startup line reads `topicforge 0.7.
|
|
44
|
+
Then run live mode with three terminals. In the first, `ros2 run demo_nodes_cpp talker` publishes `/chatter` at about 1 Hz. In the second, `ros2 topic list` should show `/chatter`. In the third, source ROS2, activate the venv and run `TOPICFORGE_MODE=live python -m topicforge`. The startup line reads `topicforge 0.7.2 ready (mode=live, requested_mode=live, adapter=ros2_cli, telemetry=off)`; `mode=mock` means `ros2` was not found and the server fell back to fixtures.
|
|
45
45
|
|
|
46
46
|
## Linux native
|
|
47
47
|
|
|
@@ -75,7 +75,7 @@ TopicForge resolves the `ros2` launcher with `shutil.which` (normally `ros2.exe`
|
|
|
75
75
|
|
|
76
76
|
## Test scenarios
|
|
77
77
|
|
|
78
|
-
Discover the graph. Ask "What topics are currently being published, and what message types do they carry?" TopicForge calls `list_topics`. Live mode shows `/chatter` plus `/rosout` and `/parameter_events`; mock mode shows `/cmd_vel`, `/odom`, `/scan`, `/tf` and `/camera/image_raw`.
|
|
78
|
+
Discover the graph. Ask "What topics are currently being published, and what message types do they carry?" TopicForge calls `list_topics`. Live mode shows `/chatter` plus `/rosout` and `/parameter_events`; mock mode shows `/cmd_vel`, `/odom`, `/scan`, `/tf`, `/tf_static` and `/camera/image_raw`.
|
|
79
79
|
|
|
80
80
|
Inspect and sample. Ask "Show me the latest message on /chatter." TopicForge calls `get_topic_info` then `sample_messages`. The payload has the message fields as nested named values. `timestamp_ns` is the `header.stamp` and the body time of `/clock`, `/tf` and `/rosout`, and `0` for other headerless types such as `std_msgs/String` (`stamp_source` says which).
|
|
81
81
|
|
|
@@ -102,4 +102,4 @@ TopicForge speaks MCP over stdio; any compliant client can spawn it with a comma
|
|
|
102
102
|
}
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
That is the Claude Desktop shape (`claude_desktop_config.json`); restart the app and the
|
|
105
|
+
That is the Claude Desktop shape (`claude_desktop_config.json`); restart the app and the fifteen tools appear under the hammer icon. For Claude Code run `claude mcp add topicforge -- topicforge`. Cursor, Continue and Cline accept the same stdio config; ready-to-paste configs for every major client are in [`CLIENTS.md`](CLIENTS.md). If the `topicforge` script is not on PATH, use `"command": "python", "args": ["-m", "topicforge"]`, or the absolute path of the binary inside your venv: desktop clients do not inherit your shell's PATH or venv activation.
|
|
@@ -5,7 +5,7 @@ served in mock mode. Do not edit by hand: `tests/test_contract_snapshots.py`
|
|
|
5
5
|
fails when this file is out of date. The rules behind the shapes are in
|
|
6
6
|
[CONTRACT.md](CONTRACT.md).
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
15 tools, all read-only.
|
|
9
9
|
|
|
10
10
|
| Tool | Title | World |
|
|
11
11
|
| --- | --- | --- |
|
|
@@ -14,6 +14,7 @@ fails when this file is out of date. The rules behind the shapes are in
|
|
|
14
14
|
| [`get_node_info`](#get_node_info) | Get node info | open |
|
|
15
15
|
| [`get_topic_info`](#get_topic_info) | Get topic info | open |
|
|
16
16
|
| [`health_check`](#health_check) | Health check | closed |
|
|
17
|
+
| [`inspect_tf`](#inspect_tf) | Inspect TF tree | open |
|
|
17
18
|
| [`list_endpoints`](#list_endpoints) | List DDS endpoints | open |
|
|
18
19
|
| [`list_nodes`](#list_nodes) | List ROS 2 nodes | open |
|
|
19
20
|
| [`list_participants`](#list_participants) | List DDS participants | open |
|
|
@@ -196,6 +197,48 @@ None.
|
|
|
196
197
|
| `ros_backend` | `"mock" \| "ros2_cli" \| "none"` | no | ROS 2 backend: `ros2_cli`, `mock` or `none`. |
|
|
197
198
|
| `note` | `str \| null` | no | One sentence of context, or `None`. |
|
|
198
199
|
|
|
200
|
+
## `inspect_tf`
|
|
201
|
+
|
|
202
|
+
**Inspect TF tree**
|
|
203
|
+
|
|
204
|
+
Annotations: `destructiveHint=false`, `idempotentHint=true`, `openWorldHint=true`, `readOnlyHint=true`
|
|
205
|
+
|
|
206
|
+
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. **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**: runs the `ros2` CLI behind the ROS lock (one call at a time); the whole call, lock wait included, ends within 45 s, and if another `ros2` call holds the lock it fails at once with a `busy` error (retry).
|
|
207
|
+
|
|
208
|
+
### Input parameters
|
|
209
|
+
|
|
210
|
+
| Name | Type | Required | Default | Constraints | Description |
|
|
211
|
+
| --- | --- | --- | --- | --- | --- |
|
|
212
|
+
| `window_s` | `float` | no | `5.0` | >= 1.0, <= 20.0 | Seconds to listen to `/tf`, 1..20, default 5. |
|
|
213
|
+
| `stale_after_s` | `float` | no | `2.0` | >= 0.1, <= 3600.0 | A frame is stale past this many seconds of stamp time, default 2. |
|
|
214
|
+
| `source_frame` | `str \| null` | no | `null` | - | Frame name, e.g. `base_link`. Give `source_frame` and `target_frame` together to get the chain between them in `path`. |
|
|
215
|
+
| `target_frame` | `str \| null` | no | `null` | - | See `source_frame`. |
|
|
216
|
+
|
|
217
|
+
### Output (top-level fields)
|
|
218
|
+
|
|
219
|
+
| Field | Type | Required | Description |
|
|
220
|
+
| --- | --- | --- | --- |
|
|
221
|
+
| `frames` | `list[TfFrame]` | yes | Sorted by name. |
|
|
222
|
+
| `roots` | `list[str]` | yes | Frames with no parent; one in a sound tree. |
|
|
223
|
+
| `frame_count` | `int` | yes | - |
|
|
224
|
+
| `static_count` | `int` | yes | Frames with a static transform. |
|
|
225
|
+
| `dynamic_count` | `int` | yes | Frames with a dynamic transform. |
|
|
226
|
+
| `stale_count` | `int` | yes | - |
|
|
227
|
+
| `transform_count` | `int` | yes | Distinct parent-child pairs. |
|
|
228
|
+
| `message_count` | `int` | yes | Transforms captured, repeats included. |
|
|
229
|
+
| `findings` | `list[TfFinding]` | yes | Empty when the tree is sound. |
|
|
230
|
+
| `hints` | `list[str]` | yes | - |
|
|
231
|
+
| `path` | `list[str] \| null` | no | Frames from `source_frame` to `target_frame`. |
|
|
232
|
+
| `path_note` | `str \| null` | no | Why `path` is `None`, or context. |
|
|
233
|
+
| `reference_ns` | `int \| null` | yes | Newest `/tf` stamp: the clock for `age_s`. |
|
|
234
|
+
| `window_s` | `float` | yes | - |
|
|
235
|
+
| `window_actual_s` | `float` | yes | - |
|
|
236
|
+
| `stamp_span_s` | `float \| null` | yes | - |
|
|
237
|
+
| `real_time_factor` | `float \| null` | yes | Stamp time over wall time: about 1 on a real robot, other on sim. |
|
|
238
|
+
| `truncated` | `bool` | yes | Messages were dropped or the cap was hit. |
|
|
239
|
+
| `mode_effective` | `"mock" \| "live"` | yes | `live` or `mock` (fixtures). |
|
|
240
|
+
| `note` | `str \| null` | no | One sentence of context, or `None`. |
|
|
241
|
+
|
|
199
242
|
## `list_endpoints`
|
|
200
243
|
|
|
201
244
|
**List DDS endpoints**
|
|
@@ -287,6 +330,7 @@ List DDS participants observed on the bus as a `ParticipantListing`: `guid`, `ve
|
|
|
287
330
|
| `note` | `str \| null` | no | One sentence of context, or `None`. |
|
|
288
331
|
| `restarts_by_name` | `dict[str, NameLifecycle]` | no | Names seen with several GUIDs or lost at least once (absent: ran once and stayed). A restart is a new GUID with the same name. |
|
|
289
332
|
| `restarts_note` | `str \| null` | no | Set when unnamed participants were left out of `restarts_by_name`. |
|
|
333
|
+
| `restarts_by_node` | `dict[str, NameLifecycle]` | no | `restarts_by_name` keyed by ROS 2 node full name. |
|
|
290
334
|
|
|
291
335
|
## `list_topics`
|
|
292
336
|
|
|
@@ -31,7 +31,7 @@ You called a ROS2 graph or bag tool (`list_topics`, `get_topic_info`, `sample_me
|
|
|
31
31
|
2. Re-run with `TOPICFORGE_MODE=live` and your existing `TOPICFORGE_DDS_BACKEND`.
|
|
32
32
|
3. Check `health_check`: `ros_backend` should be `"ros2_cli"` and `dds_backend` your vendor.
|
|
33
33
|
|
|
34
|
-
If you want a DDS-only deployment, the message is expected: you use the six DDS tools and `peek_bag_samples` (it reads bags without the CLI), and the
|
|
34
|
+
If you want a DDS-only deployment, the message is expected: you use the six DDS tools and `peek_bag_samples` (it reads bags without the CLI), and the seven ros2-CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `inspect_tf`, `sample_messages`, `analyze_bag`) raise it. For offline work use `TOPICFORGE_MODE=mock`. The opposite error, "DDS module is not active: ...", ends with the cause: the backend was not selected (`TOPICFORGE_DDS_BACKEND` is `mock`, the default, which means no DDS backend; set it to `cyclone` even when the binding is installed), the binding is not installed, or it is installed but the adapter failed to start (the server log has the error). `health_check` shows the same text as `dds_inactive_note`.
|
|
35
35
|
|
|
36
36
|
## "rosbags"
|
|
37
37
|
|
|
@@ -31,7 +31,7 @@ Add it to your MCP client. For Claude Desktop, edit `claude_desktop_config.json`
|
|
|
31
31
|
}
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
Restart Claude Desktop. All
|
|
34
|
+
Restart Claude Desktop. All fifteen tools appear under the hammer icon. Ask something like:
|
|
35
35
|
|
|
36
36
|
> What topics are being published right now, and what message types do they carry?
|
|
37
37
|
|
|
@@ -17,6 +17,6 @@ TOPICFORGE_MODE=mock python -m topicforge
|
|
|
17
17
|
# Windows PowerShell: $env:TOPICFORGE_MODE="mock"; python -m topicforge
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Point any MCP client at the server with `"env": { "TOPICFORGE_MODE": "mock" }`. The mock exposes all
|
|
20
|
+
Point any MCP client at the server with `"env": { "TOPICFORGE_MODE": "mock" }`. The mock exposes all 15 tools against deterministic fixtures, so every example is reproducible byte for byte.
|
|
21
21
|
|
|
22
22
|
To run against a real bus, use `TOPICFORGE_MODE=live` and, for the DDS tools, `TOPICFORGE_DDS_BACKEND=cyclone` (see [`docs/DDS_QUICKSTART.md`](../docs/DDS_QUICKSTART.md)). The mock shows the shape of every response, not the behaviour of the live DDS adapters, which differ in two ways that examples 02 and 04 call out: user-topic payloads are not decoded, and `topic_metrics` only has data for the builtin discovery topics.
|
|
@@ -4,7 +4,7 @@ TopicForge is a read-only MCP server for ROS 2 and DDS. This plugin bundles the
|
|
|
4
4
|
|
|
5
5
|
## What is included
|
|
6
6
|
|
|
7
|
-
- MCP server `topicforge`, started with `uvx` from the `topicforge` package on PyPI (version pinned to 0.7.
|
|
7
|
+
- MCP server `topicforge`, started with `uvx` from the `topicforge` package on PyPI (version pinned to 0.7.2). It exposes fifteen read-only tools: `health_check`, `list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `sample_messages`, `analyze_bag`, `peek_bag_samples`, `list_participants`, `list_endpoints`, `detect_qos_mismatches`, `participant_events`, `peek_dds_samples`, `topic_metrics`, `inspect_tf`.
|
|
8
8
|
- Skill `diagnose-dds-bus`: what to call, and in which order, when nodes do not talk, a topic gets no data, or a node crashed or restarts.
|
|
9
9
|
- Skill `inspect-ros2-robot`: listing topics, sampling messages and reading bags, including large arrays and simulation time.
|
|
10
10
|
- The server itself also offers both procedures as MCP prompts (`diagnose-dds-bus`, `inspect-ros2-robot`) and short server instructions, for clients that load MCP prompts instead of skills. The skills use output contract 2 (`health_check.contract_version` is 2, 0.7.0 and later).
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
# After a release reaches PyPI, regenerate the lock: `cd plugin && uv lock`.
|
|
5
5
|
[project]
|
|
6
6
|
name = "topicforge-claude-plugin"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.2"
|
|
8
8
|
description = "Locked launch manifest for the TopicForge Claude plugin."
|
|
9
9
|
# cyclonedds ships wheels for CPython 3.10 to 3.13 only (checked on PyPI 2026-10-07).
|
|
10
10
|
requires-python = ">=3.10,<3.14"
|
|
11
11
|
dependencies = [
|
|
12
|
-
"topicforge==0.7.
|
|
12
|
+
"topicforge==0.7.2",
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
[tool.uv]
|