topicforge 0.8.0__tar.gz → 0.9.0__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.8.0 → topicforge-0.9.0}/CHANGELOG.md +83 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/PKG-INFO +17 -17
- {topicforge-0.8.0 → topicforge-0.9.0}/README.md +16 -16
- {topicforge-0.8.0 → topicforge-0.9.0}/docs/CLIENTS.md +63 -22
- {topicforge-0.8.0 → topicforge-0.9.0}/docs/CONTRACT.md +138 -7
- {topicforge-0.8.0 → topicforge-0.9.0}/docs/DDS_QUICKSTART.md +18 -19
- {topicforge-0.8.0 → topicforge-0.9.0}/docs/EVAL.md +75 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/docs/TESTING.md +6 -6
- {topicforge-0.8.0 → topicforge-0.9.0}/docs/TOOLS.md +68 -33
- {topicforge-0.8.0 → topicforge-0.9.0}/docs/TROUBLESHOOTING.md +8 -13
- {topicforge-0.8.0 → topicforge-0.9.0}/docs/TUTORIEL.md +4 -4
- {topicforge-0.8.0 → topicforge-0.9.0}/docs/VALIDATION.md +1 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/docs/dds-interop-matrix.md +3 -3
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/README.md +1 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/plugin/README.md +1 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/plugin/pyproject.toml +2 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/pyproject.toml +7 -11
- {topicforge-0.8.0 → topicforge-0.9.0}/scripts/agent_eval/README.md +3 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/scripts/ground_truth/README.md +1 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/README.md +7 -7
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/__init__.py +1 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/__main__.py +41 -8
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/dds_helpers.py +2 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/dds_introspection.py +2 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/discovery_tracker.py +20 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/lifecycle.py +5 -5
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/log_filter.py +5 -3
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/param_diff.py +34 -6
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/qos_endpoints.py +4 -4
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/qos_normalize.py +4 -3
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/qos_scan.py +48 -8
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/tf_tree.py +31 -5
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/xtypes.py +1 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/composite.py +8 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/dds_cyclone/adapter.py +1 -7
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/adapter.py +59 -11
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/config/settings.py +10 -6
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/constants.py +6 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/models/__init__.py +5 -0
- topicforge-0.9.0/src/topicforge/models/diagnose.py +107 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/models/nodes.py +3 -3
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/models/schemas.py +52 -50
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/models/summaries.py +4 -5
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/models/tf.py +3 -1
- topicforge-0.9.0/src/topicforge/server/http.py +162 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/server/prompts.py +3 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/dds_views.py +15 -7
- topicforge-0.9.0/src/topicforge/services/diagnose.py +672 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/factory.py +2 -30
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/health.py +1 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/inspector.py +28 -5
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/tools/guard.py +3 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/tools/handlers.py +80 -33
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/_prompts.json +2 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/analyze_bag.json +3 -3
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/detect_qos_mismatches.json +42 -4
- topicforge-0.9.0/tests/contract/diagnose.json +262 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/get_node_info.json +1 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/get_topic_info.json +1 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/graph_diff.json +1 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/health_check.json +4 -4
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/inspect_tf.json +2 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/list_endpoints.json +5 -5
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/list_nodes.json +2 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/list_participants.json +5 -5
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/list_topics.json +1 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/participant_events.json +3 -3
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/peek_bag_samples.json +5 -5
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/peek_dds_samples.json +6 -6
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/sample_messages.json +6 -6
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/topic_metrics.json +2 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/entrypoint.sh +4 -3
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/fault_nodes.py +2 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/ground_truth.py +43 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/run_bench.py +14 -4
- topicforge-0.9.0/tests/integration/ros2/sros2_check.py +159 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/test_live_adapter.py +1 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_agent_eval_tools.py +44 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_config.py +13 -31
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_contract_shape.py +2 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_dds_cross_vendor.py +15 -39
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_dds_inactive_reason.py +0 -6
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_dds_introspection.py +1 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_dds_qos_normalization.py +1 -1
- topicforge-0.9.0/tests/test_diagnose.py +761 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_endpoints.py +52 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_factory.py +2 -16
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_friction_fixes.py +49 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_ground_truth_compare.py +248 -6
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_honest_outputs.py +1 -1
- topicforge-0.9.0/tests/test_http_transport.py +184 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_log_filter.py +26 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_node_tools.py +5 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_output_schema_validation.py +1 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_param_diff.py +3 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_qos_scan.py +21 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_tf_tools.py +77 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_tf_tree.py +21 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_tool_annotations.py +2 -2
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_tool_descriptions.py +3 -1
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_tools_integration.py +5 -3
- topicforge-0.8.0/src/topicforge/adapters/dds_fast/__init__.py +0 -11
- topicforge-0.8.0/src/topicforge/adapters/dds_fast/adapter.py +0 -487
- topicforge-0.8.0/src/topicforge/server/http.py +0 -75
- topicforge-0.8.0/tests/test_fast_adapter.py +0 -149
- topicforge-0.8.0/tests/test_http_transport.py +0 -112
- {topicforge-0.8.0 → topicforge-0.9.0}/.gitignore +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/LICENSE +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/docs/MIGRATION-0.7.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/00_hello_pub_sub/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/01_who_is_on_the_bus/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/02_why_cant_they_talk/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/03_a_node_crashed/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/04_late_joiner_misses_data/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/05_reliability_in_code/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/06_durability_late_joiner_in_code/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/07_deadline_in_code/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/08_crash_seen_from_inside/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/10_lidar_silent_after_driver_swap/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/11_who_talks_to_whom/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/12_safety_monitor_dropout/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/13_deadline_not_offered/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/14_restart_loop/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/plugin/LICENSE +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/cyclone_c/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/cyclone_cpp/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/cyclone_rust/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/dust_py/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/fast_publisher_cpp/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/fast_py/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/opensplice_publisher/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/rti_c/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/rti_cpp/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/base.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/bag_kind.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/bag_range.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/cdr_decoder.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/endpoints.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/metrics_buffer.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/nodes.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/qos_analyzer.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/qos_fixes.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/ros_discovery.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/ros_names.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/spawned_pids.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/stamps.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/topic_filter.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/urdf.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/dds_cyclone/ros_info.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/echo_parser.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/echo_stream.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/node_parsers.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/parsers.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/process_runner.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/process_tree.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/sample_extras.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/tf_capture.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/tf_parsers.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/adapter.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/fixtures.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/graph_fixtures.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/node_fixtures.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/tf_fixtures.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/budget.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/config/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/models/graph_diff.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/server/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/server/app.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/bag_service.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/bag_stats.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/graph_diff.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/graph_snapshot.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/params_file.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/sample_budget.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/summaries/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/summaries/_common.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/summaries/clock.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/summaries/laser_scan.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/summaries/motion.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/summaries/rate.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/summaries/sensor_data.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/telemetry/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/telemetry/client.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/tools/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/tools/annotations.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/tools/schema_compact.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/conftest.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_064_results.json +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_064_truth.json +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_071_morning_results.json +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_071_morning_truth.json +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_071_rerun_results.json +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_071_rerun_truth.json +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/image_noarr.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/image_trunc4.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/scan_edge.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/scan_noarr.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/scan_trunc128.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/scan_trunc3.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/string_latched.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/twist.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_info_humble.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_info_no_param_services.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_info_not_found.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_list_duplicates.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_list_empty.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_list_plain.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_list_warning_stdout.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/param_dump_humble.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/param_dump_secrets.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/service_list_types.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_clock.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_cmd_vel.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_parameter_events.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_scan.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_tf.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_tf_static.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros_discovery/humble_two_nodes.hex +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros_discovery/jazzy_two_nodes.hex +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/Dockerfile +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/bench_params.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/blocked_node.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/faults.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/graph_change.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/lifecycle_node.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/publisher.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/test_real_bus.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_analyze_bag_multi_format.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_bag_omnisim_humble.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_bag_service.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_bag_stats.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_bag_summaries.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_cdr_decoder.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_clock_basis.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_composite_adapter.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_composite_ghosts.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_contract_snapshots.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_contract_v2.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_cyclone_adapter.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_cyclone_ros_info.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_dds_helpers.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_dds_schemas.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_discovery_tracker.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_echo_parser.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_echo_stream.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_example_node_spec.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_field_names.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_graph_diff.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_health.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_history_and_hints.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_inspector.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_lifecycle_buffer.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_live_adapter_graph.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_live_adapter_parse.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_live_adapter_subprocess.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_live_sample_notes.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_metrics_buffer.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_mock_adapter.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_node_logic.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_node_parsers.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_peek_bag_samples.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_process_runner.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_process_tree.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_prompts.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_qos_analyzer.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_qos_endpoints.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_qos_fixes.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_rate_verdicts.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_restarts_by_node.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_ros_discovery.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_sample_options.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_sample_summary_flow.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_server_guard.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_server_info_version.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_server_lanes.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_stamps.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_summaries.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_summary_schema_compat.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_telemetry.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_topic_metrics.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_urdf.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_version_consistency.py +0 -0
- {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_xtypes.py +0 -0
|
@@ -5,6 +5,85 @@ All notable changes to TopicForge are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.9.0] - 2026-11-02
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `diagnose`, the seventeenth tool: one bounded sweep of the ROS 2 graph and the DDS bus that
|
|
15
|
+
answers "is anything wrong". It runs `environment`, `nodes`, `topics`, `dds`, `tf`, `logs` and
|
|
16
|
+
`responsiveness` (a parameter read per node, the `node` argument first) from the existing
|
|
17
|
+
services, within `budget_s` (10..40, default 30), and returns findings ranked `critical`,
|
|
18
|
+
`warning`, `info` with evidence and, for QoS, the `suggested_fixes` of `detect_qos_mismatches`.
|
|
19
|
+
A check that errors or runs out of budget is listed in `checks_skipped` and the verdict then
|
|
20
|
+
says the sweep is incomplete: it never reads as an all-clear. It holds the ROS lock for up to
|
|
21
|
+
`budget_s`. `tools/list` is 99,634 bytes (`diagnose` 4,267) against the 105,000 ceiling. The
|
|
22
|
+
bench gains `50_diagnose` and `51_diagnose_node` and a comparator section that requires the
|
|
23
|
+
exact set of seeded findings (`docs/CONTRACT.md` section 9.21).
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- `tools/list` is 95,391 bytes (-1,924): shorter lane notes, no duplicated "Read-only" phrase,
|
|
28
|
+
and every output field description at most 120 characters, with a test that keeps it so. The
|
|
29
|
+
`tools/list` test ceiling moves from 100,000 to 105,000 bytes to make room for `diagnose`
|
|
30
|
+
(`docs/CONTRACT.md` section 9.20).
|
|
31
|
+
|
|
32
|
+
### Removed
|
|
33
|
+
|
|
34
|
+
- The fastdds-based DDS adapter (`adapters/dds_fast/`, 487 lines). It never ran against a bus,
|
|
35
|
+
and its `fastdds` binding is not on PyPI, so no user could install it. Fast DDS participants
|
|
36
|
+
are still observed through the `cyclone` backend by standard RTPS discovery, as the CI bench
|
|
37
|
+
and the OmniSim kit show. `TOPICFORGE_DDS_BACKEND=fast` is now refused at startup with that
|
|
38
|
+
route, and `auto` only probes `cyclonedds`. `health_check.dds_backend` keeps `fast` in its
|
|
39
|
+
schema enum but never emits it. `demo-fast.yml` (a C++ Fast DDS publisher seen by Cyclone)
|
|
40
|
+
stays.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- `detect_qos_mismatches` on a live DDS backend now fills `reader_node` and `writer_node`. 0.8.1
|
|
45
|
+
built the scan's endpoints without the gid -> node map that `list_endpoints` uses, so both
|
|
46
|
+
fields stayed `null` on every pair (seen on Fast DDS in the OmniSim kit). Both tools now build
|
|
47
|
+
their endpoints through one snapshot method. The Docker bench checks the writer node on the
|
|
48
|
+
scan's pairs (`docs/CONTRACT.md` section 9.19).
|
|
49
|
+
|
|
50
|
+
## [0.8.1] - 2026-10-26
|
|
51
|
+
|
|
52
|
+
The 0.8.0 evaluation findings, a URDF read from the robot_state_publisher parameter, an optional
|
|
53
|
+
HTTP bearer token, a Docker image and a SROS2 bench cell. Additive only (`docs/CONTRACT.md`
|
|
54
|
+
section 9.18). `tools/list` is 97,315 bytes (+467).
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- HTTP transport: `TOPICFORGE_HTTP_TOKEN` (at least 32 printable characters, environment only)
|
|
59
|
+
makes every request carry `Authorization: Bearer <token>`, compared in constant time, else
|
|
60
|
+
`401`. `--host` may bind an address other than loopback only with a token; such a bind lifts
|
|
61
|
+
the DNS-rebinding allow-list. Plain HTTP: SECURITY.md says what the token does not cover.
|
|
62
|
+
- `inspect_tf` reads the URDF from the `robot_description` parameter of the node named
|
|
63
|
+
`robot_state_publisher` when no node publishes `/robot_description`; `urdf_note` names the node.
|
|
64
|
+
- `reader_node` and `writer_node` on `detect_qos_mismatches` reports, `not_matched` and
|
|
65
|
+
`matched` pairs.
|
|
66
|
+
- A root `Dockerfile`: a stdio image on `ros:jazzy-ros-core` with the `ros2` CLI and Cyclone DDS,
|
|
67
|
+
non-root. Live graphs need Linux `--network host`; docs/CLIENTS.md has the Docker Desktop
|
|
68
|
+
limits.
|
|
69
|
+
- Bench: a `sros2` job (`run_bench.py --sros2`) with one node under SROS2 Enforce. Run on jazzy
|
|
70
|
+
with Cyclone and Fast DDS, 4 of 4 checks in each.
|
|
71
|
+
- Eval harness: `restarts_at_first_call` in the `restart_loop` summary, counted at the agent's
|
|
72
|
+
first call rather than at `READY`.
|
|
73
|
+
|
|
74
|
+
### Changed
|
|
75
|
+
|
|
76
|
+
- `inspect_tf`: an edge with a steady rate silent for over ten of its periods (at least 1 s) is
|
|
77
|
+
stale even under `stale_after_s`. With the defaults, the evaluation's `tf_stale` read as healthy.
|
|
78
|
+
- `restarts_by_name` leaves out the name `/` that every ROS 2 node shares on Fast DDS; the note
|
|
79
|
+
points to `restarts_by_node`.
|
|
80
|
+
- `min_log_level` accepts `WARN`, `Error` and `warning`.
|
|
81
|
+
- `detect_qos_mismatches` without `topic` leaves `ros_discovery_info` out, and folds orphan
|
|
82
|
+
topics into one hint per side instead of one hint each.
|
|
83
|
+
- `param_diff_summary` names the `same` and `live_only` parameters.
|
|
84
|
+
- A filtered `/rosout` read that received nothing says so, instead of mixing the no-match advice
|
|
85
|
+
with the latched-topic advice.
|
|
86
|
+
|
|
8
87
|
## [0.8.0] - 2026-10-21
|
|
9
88
|
|
|
10
89
|
Graph diff, declared-against-real checks, DDS visibility and a Nav2 prompt. It also carries the
|
|
@@ -1864,7 +1943,10 @@ Initial MVP release of TopicForge: ROS Topic Inspector & Bag Analyzer MCP server
|
|
|
1864
1943
|
- The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
|
|
1865
1944
|
- `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
|
|
1866
1945
|
|
|
1867
|
-
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.
|
|
1946
|
+
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.9.0...HEAD
|
|
1947
|
+
[0.9.0]: https://github.com/yaniswav/TopicForge/compare/v0.8.1...v0.9.0
|
|
1948
|
+
[0.8.1]: https://github.com/yaniswav/TopicForge/compare/v0.8.0...v0.8.1
|
|
1949
|
+
[0.8.0]: https://github.com/yaniswav/TopicForge/compare/v0.7.2...v0.8.0
|
|
1868
1950
|
[0.7.2]: https://github.com/yaniswav/TopicForge/compare/v0.7.1...v0.7.2
|
|
1869
1951
|
[0.7.1]: https://github.com/yaniswav/TopicForge/compare/v0.7.0...v0.7.1
|
|
1870
1952
|
[0.7.0]: https://github.com/yaniswav/TopicForge/compare/v0.6.4...v0.7.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: topicforge
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
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 seventeen 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
|
|
|
@@ -101,9 +101,9 @@ The server speaks MCP over stdio and waits for a client, so wire it into one. Fo
|
|
|
101
101
|
}
|
|
102
102
|
```
|
|
103
103
|
|
|
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
|
|
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 (another `--host` needs a bearer token in `TOPICFORGE_HTTP_TOKEN`); 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=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjkuMCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%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.9.0%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
|
|
|
@@ -120,6 +120,7 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
|
|
|
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
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 |
|
|
122
122
|
| `graph_diff` | What changed since it last worked: snapshots the ROS 2 graph (or a bag) and diffs it with `previous`, a `snap-N` id or a bag: nodes, topics, types, writer QoS, counts, bag rates |
|
|
123
|
+
| `diagnose` | Is anything wrong: one bounded sweep (nodes, topics, QoS pairs, restarts, TF, `/rosout` errors, a parameter read per node) ranked `critical` / `warning` / `info` with evidence; a check that cannot run is listed, never silent. Holds the ROS lock up to `budget_s` |
|
|
123
124
|
| `sample_messages` | Peek recent messages on a ROS2 topic (count capped at 50) |
|
|
124
125
|
| `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
|
|
125
126
|
| `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
|
|
@@ -152,9 +153,9 @@ pip install "topicforge[dds]" # alias, kept so older commands
|
|
|
152
153
|
TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
|
|
153
154
|
```
|
|
154
155
|
|
|
155
|
-
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone
|
|
156
|
+
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone` and `auto` (`cyclone` if the binding imports, else `mock`). 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 nine `ros2` CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `inspect_tf`, `graph_diff`, `diagnose`, `sample_messages`, `analyze_bag`) to the CLI and the six DDS tools to the DDS backend; `peek_bag_samples` goes to the ROS half but runs no `ros2` process.
|
|
156
157
|
|
|
157
|
-
|
|
158
|
+
Fast DDS participants (ROS 2 nodes on `rmw_fastrtps_cpp`) need no backend of their own: the Cyclone backend observes them through standard RTPS discovery. Since 0.9.0 `TOPICFORGE_DDS_BACKEND=fast` is refused, like `rti`, `opensplice`, `coredx`, `intercom`, `opendds` and `dust`: startup stops with an error that lists the valid values (see [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md)). Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md).
|
|
158
159
|
|
|
159
160
|
## Configuration reference
|
|
160
161
|
|
|
@@ -164,11 +165,11 @@ A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Pyt
|
|
|
164
165
|
| `TOPICFORGE_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
|
165
166
|
| `TOPICFORGE_ROS2_BIN` | `ros2` | Name or path of the ROS2 CLI binary |
|
|
166
167
|
| `TOPICFORGE_TELEMETRY` | `off` | Opt-in anonymous telemetry; an unrecognized value aborts startup. See [Telemetry](#telemetry) |
|
|
167
|
-
| `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `
|
|
168
|
+
| `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `auto`; anything else is rejected |
|
|
168
169
|
| `TOPICFORGE_DDS_DOMAIN_ID` | `0` | DDS domain observed (0..232). Joined at startup; changing it needs a restart |
|
|
169
170
|
| `TOPICFORGE_MAX_SAMPLE_BYTES` | `1048576` | Size cap for one sampled message (1 KiB..64 MiB); a call returns at most 4 times that. Over-cap messages are dropped with a note |
|
|
170
171
|
|
|
171
|
-
Command-line options (not environment variables): `--transport stdio|streamable-http` (default `stdio`)
|
|
172
|
+
Command-line options (not environment variables): `--transport stdio|streamable-http` (default `stdio`), `--port N` (default 8765) and `--host ADDR` (default `127.0.0.1`), the last two only with `streamable-http`. The HTTP transport binds `127.0.0.1` with DNS-rebinding protection; `TOPICFORGE_HTTP_TOKEN` (at least 32 characters) makes every request carry `Authorization: Bearer <token>` and is required for any other `--host` (plain HTTP, so prefer an SSH tunnel); see [docs/CLIENTS.md](docs/CLIENTS.md#local-http-transport).
|
|
172
173
|
|
|
173
174
|
Samples with comments are in [`.env.example`](.env.example). Any invalid value stops the server with `topicforge: configuration error: ...` and exit code 2, so a typo cannot silently change behaviour.
|
|
174
175
|
|
|
@@ -176,19 +177,18 @@ Samples with comments are in [`.env.example`](.env.example). Any invalid value s
|
|
|
176
177
|
|
|
177
178
|
External validation against a simulated robot's ground truth (OmniSim): [docs/VALIDATION.md](docs/VALIDATION.md).
|
|
178
179
|
|
|
179
|
-
- DDS validation is partial. The Cyclone adapter has run against real buses with Cyclone, Fast DDS and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`, `.github/workflows/ros2-live.yml`).
|
|
180
|
+
- DDS validation is partial. The Cyclone adapter has run against real buses with Cyclone, Fast DDS and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`, `.github/workflows/ros2-live.yml`). No RTI, OpenDDS, CoreDX or OpenSplice participant has been observed by this project. The multi-vendor claim rests on the RTPS protocol guarantee, not on a recorded cross-vendor run.
|
|
180
181
|
- User-topic payloads are not decoded. `peek_dds_samples` on a user topic returns count 0 and a note that the topic is announced on the bus; no traffic is read. `topic_metrics` therefore has data only for the builtin discovery topics and says so in its `status`. It is a discovery-layer probe, not a publish-rate monitor.
|
|
181
182
|
- Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned. A crash and a clean leave cannot be told apart, and `lost_ns` is an upper bound of the death.
|
|
182
183
|
- Cyclone vendor ids: participants that do not follow the RTPS vendor-id convention in their GUID prefix (Dust DDS, and RTI by default) are reported with vendor `unknown`.
|
|
183
184
|
- Single domain: the server observes the domain it joined at startup; changing it needs a restart.
|
|
184
185
|
- DDS Security is not handled. A participant without credentials sees an empty secure bus. `detect_qos_mismatches` checks Partition, type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership (kind), DestinationOrder and DataRepresentation (History as a risk); Presentation, XTypes assignability and runtime behavior are not checked, and the result lists them in `policies_unchecked`. It returns a `MismatchScan` envelope: read `reports` for the mismatches.
|
|
185
|
-
- Fast DDS serves no `list_endpoints`.
|
|
186
186
|
- `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..40, default 10, for the whole call) runs out, and returns what arrived with a `note` (`N of M messages within T s`, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (`payload.header.stamp.sec`); `timestamp_ns` is `header.stamp` (the publisher's clock, sim time on a simulation), the time in the body of `/clock`, `/tf` and `/rosout` messages (`stamp_source` `payload`), or 0 for other headerless types, with `stamp_source` and `received_ns` (wall clock when the CLI printed it). Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_fields`. `nan` and `inf` come back as strings. `count` above 50 is capped with a note.
|
|
187
187
|
- `analyze_bag` (live) parses `ros2 bag info` text for the totals and counts; anomaly detection is mock-only. Per-topic times, rates (`(n - 1) / span`) and `latched` are added when the bag can be read locally (`.db3` with the standard library, `.mcap` with `rosbags`), else rates fall back to count / bag duration (`frequency_basis`). `peek_bag_samples` reads the file itself, through `rosbags`, returns the first `count` messages in recording order (not the last), and is served only by the ROS2 CLI adapter or the mock; its `timestamp_ns` is the message's `header.stamp` (`stamp_source` `header`), the body time of `Clock`, `TFMessage` and `Log` messages (`payload`) or, without either, the bag record time (`stamp_source` `recorded`), and `recorded_ns` is always the bag record time; bags that embed no message definitions (Humble `.db3`) are decoded with the Humble definitions, or the distro the bag records, and `note` says so. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output. `health_check` also reports `sim_clock_published` (live only): true when `/clock` has a publisher, a hint that header stamps may be simulation time.
|
|
188
188
|
- Calls are serialized per backend (one `ros2` call at a time, one DDS call at a time) and each call has a 45 s wall budget, lock wait included; a call that cannot get its lane fails at once with `busy`. `health_check` and `peek_bag_samples` take no lock.
|
|
189
189
|
- No streaming or push subscriptions: tools are strictly request/response.
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
Not done yet: an opt-in probe to tell a hung writer from a healthy one, wider real-bus validation (RTI, OpenDDS) and DDS Security. Open work is tracked in [issues](https://github.com/yaniswav/TopicForge/issues).
|
|
192
192
|
|
|
193
193
|
## Telemetry
|
|
194
194
|
|
|
@@ -204,10 +204,10 @@ When on, each tool call emits one event with exactly six fields:
|
|
|
204
204
|
|
|
205
205
|
| Field | Example | Notes |
|
|
206
206
|
| ------------ | --------------- | ----------------------------------------------------------- |
|
|
207
|
-
| `tool_name` | `"list_topics"` | One of the
|
|
207
|
+
| `tool_name` | `"list_topics"` | One of the seventeen tools, never argument values |
|
|
208
208
|
| `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
|
|
209
209
|
| `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
|
|
210
|
-
| `version` | `"0.
|
|
210
|
+
| `version` | `"0.9.0"` | TopicForge server version |
|
|
211
211
|
| `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
|
|
212
212
|
| `success` | `true` | Whether the handler returned or raised |
|
|
213
213
|
|
|
@@ -235,7 +235,7 @@ python -m ruff check src tests
|
|
|
235
235
|
python -m pytest
|
|
236
236
|
```
|
|
237
237
|
|
|
238
|
-
Tests run against the mock adapter, the live adapter's pure parsers and the binding-free DDS helpers; they never need a running ROS graph. Tests needing the `cyclonedds`
|
|
238
|
+
Tests run against the mock adapter, the live adapter's pure parsers and the binding-free DDS helpers; they never need a running ROS graph. Tests needing the `cyclonedds` binding skip themselves when it is absent, and the `integration` marker (real-bus tests) is deselected by default. The `Makefile` (`make check`) uses POSIX shell syntax; on plain PowerShell run the commands above. See [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
239
239
|
|
|
240
240
|
## Upgrading
|
|
241
241
|
|
|
@@ -248,13 +248,13 @@ src/topicforge/
|
|
|
248
248
|
server/ MCP bootstrap, build_app(settings), prompts and instructions
|
|
249
249
|
tools/ thin MCPServer handlers, no backend logic
|
|
250
250
|
services/ input validation, orchestration, adapter factory
|
|
251
|
-
adapters/ ros2_live, ros2_mock, dds_cyclone,
|
|
251
|
+
adapters/ ros2_live, ros2_mock, dds_cyclone, common/ (binding-free logic)
|
|
252
252
|
models/ frozen Pydantic schemas, the contract with MCP clients
|
|
253
253
|
config/ settings and mode resolution
|
|
254
254
|
telemetry/ opt-in, off by default
|
|
255
255
|
examples/ mock walkthroughs (*.md) and runnable live DDS examples (dds/)
|
|
256
|
-
scripts/ real-bus interop checks
|
|
257
|
-
docs/ guides
|
|
256
|
+
scripts/ real-bus interop checks, ground-truth comparator, agent evaluation
|
|
257
|
+
docs/ guides and reference
|
|
258
258
|
tests/ pytest suite, mock-only, no ROS2 or DDS required
|
|
259
259
|
```
|
|
260
260
|
|
|
@@ -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 seventeen 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
|
|
|
@@ -40,9 +40,9 @@ The server speaks MCP over stdio and waits for a client, so wire it into one. Fo
|
|
|
40
40
|
}
|
|
41
41
|
```
|
|
42
42
|
|
|
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
|
|
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 (another `--host` needs a bearer token in `TOPICFORGE_HTTP_TOKEN`); 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=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjkuMCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%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.9.0%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
|
|
|
@@ -59,6 +59,7 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
|
|
|
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
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 |
|
|
61
61
|
| `graph_diff` | What changed since it last worked: snapshots the ROS 2 graph (or a bag) and diffs it with `previous`, a `snap-N` id or a bag: nodes, topics, types, writer QoS, counts, bag rates |
|
|
62
|
+
| `diagnose` | Is anything wrong: one bounded sweep (nodes, topics, QoS pairs, restarts, TF, `/rosout` errors, a parameter read per node) ranked `critical` / `warning` / `info` with evidence; a check that cannot run is listed, never silent. Holds the ROS lock up to `budget_s` |
|
|
62
63
|
| `sample_messages` | Peek recent messages on a ROS2 topic (count capped at 50) |
|
|
63
64
|
| `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
|
|
64
65
|
| `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
|
|
@@ -91,9 +92,9 @@ pip install "topicforge[dds]" # alias, kept so older commands
|
|
|
91
92
|
TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
|
|
92
93
|
```
|
|
93
94
|
|
|
94
|
-
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone
|
|
95
|
+
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone` and `auto` (`cyclone` if the binding imports, else `mock`). 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 nine `ros2` CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `inspect_tf`, `graph_diff`, `diagnose`, `sample_messages`, `analyze_bag`) to the CLI and the six DDS tools to the DDS backend; `peek_bag_samples` goes to the ROS half but runs no `ros2` process.
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
Fast DDS participants (ROS 2 nodes on `rmw_fastrtps_cpp`) need no backend of their own: the Cyclone backend observes them through standard RTPS discovery. Since 0.9.0 `TOPICFORGE_DDS_BACKEND=fast` is refused, like `rti`, `opensplice`, `coredx`, `intercom`, `opendds` and `dust`: startup stops with an error that lists the valid values (see [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md)). Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md).
|
|
97
98
|
|
|
98
99
|
## Configuration reference
|
|
99
100
|
|
|
@@ -103,11 +104,11 @@ A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Pyt
|
|
|
103
104
|
| `TOPICFORGE_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
|
104
105
|
| `TOPICFORGE_ROS2_BIN` | `ros2` | Name or path of the ROS2 CLI binary |
|
|
105
106
|
| `TOPICFORGE_TELEMETRY` | `off` | Opt-in anonymous telemetry; an unrecognized value aborts startup. See [Telemetry](#telemetry) |
|
|
106
|
-
| `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `
|
|
107
|
+
| `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `auto`; anything else is rejected |
|
|
107
108
|
| `TOPICFORGE_DDS_DOMAIN_ID` | `0` | DDS domain observed (0..232). Joined at startup; changing it needs a restart |
|
|
108
109
|
| `TOPICFORGE_MAX_SAMPLE_BYTES` | `1048576` | Size cap for one sampled message (1 KiB..64 MiB); a call returns at most 4 times that. Over-cap messages are dropped with a note |
|
|
109
110
|
|
|
110
|
-
Command-line options (not environment variables): `--transport stdio|streamable-http` (default `stdio`)
|
|
111
|
+
Command-line options (not environment variables): `--transport stdio|streamable-http` (default `stdio`), `--port N` (default 8765) and `--host ADDR` (default `127.0.0.1`), the last two only with `streamable-http`. The HTTP transport binds `127.0.0.1` with DNS-rebinding protection; `TOPICFORGE_HTTP_TOKEN` (at least 32 characters) makes every request carry `Authorization: Bearer <token>` and is required for any other `--host` (plain HTTP, so prefer an SSH tunnel); see [docs/CLIENTS.md](docs/CLIENTS.md#local-http-transport).
|
|
111
112
|
|
|
112
113
|
Samples with comments are in [`.env.example`](.env.example). Any invalid value stops the server with `topicforge: configuration error: ...` and exit code 2, so a typo cannot silently change behaviour.
|
|
113
114
|
|
|
@@ -115,19 +116,18 @@ Samples with comments are in [`.env.example`](.env.example). Any invalid value s
|
|
|
115
116
|
|
|
116
117
|
External validation against a simulated robot's ground truth (OmniSim): [docs/VALIDATION.md](docs/VALIDATION.md).
|
|
117
118
|
|
|
118
|
-
- DDS validation is partial. The Cyclone adapter has run against real buses with Cyclone, Fast DDS and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`, `.github/workflows/ros2-live.yml`).
|
|
119
|
+
- DDS validation is partial. The Cyclone adapter has run against real buses with Cyclone, Fast DDS and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`, `.github/workflows/ros2-live.yml`). No RTI, OpenDDS, CoreDX or OpenSplice participant has been observed by this project. The multi-vendor claim rests on the RTPS protocol guarantee, not on a recorded cross-vendor run.
|
|
119
120
|
- User-topic payloads are not decoded. `peek_dds_samples` on a user topic returns count 0 and a note that the topic is announced on the bus; no traffic is read. `topic_metrics` therefore has data only for the builtin discovery topics and says so in its `status`. It is a discovery-layer probe, not a publish-rate monitor.
|
|
120
121
|
- Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned. A crash and a clean leave cannot be told apart, and `lost_ns` is an upper bound of the death.
|
|
121
122
|
- Cyclone vendor ids: participants that do not follow the RTPS vendor-id convention in their GUID prefix (Dust DDS, and RTI by default) are reported with vendor `unknown`.
|
|
122
123
|
- Single domain: the server observes the domain it joined at startup; changing it needs a restart.
|
|
123
124
|
- DDS Security is not handled. A participant without credentials sees an empty secure bus. `detect_qos_mismatches` checks Partition, type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership (kind), DestinationOrder and DataRepresentation (History as a risk); Presentation, XTypes assignability and runtime behavior are not checked, and the result lists them in `policies_unchecked`. It returns a `MismatchScan` envelope: read `reports` for the mismatches.
|
|
124
|
-
- Fast DDS serves no `list_endpoints`.
|
|
125
125
|
- `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..40, default 10, for the whole call) runs out, and returns what arrived with a `note` (`N of M messages within T s`, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (`payload.header.stamp.sec`); `timestamp_ns` is `header.stamp` (the publisher's clock, sim time on a simulation), the time in the body of `/clock`, `/tf` and `/rosout` messages (`stamp_source` `payload`), or 0 for other headerless types, with `stamp_source` and `received_ns` (wall clock when the CLI printed it). Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_fields`. `nan` and `inf` come back as strings. `count` above 50 is capped with a note.
|
|
126
126
|
- `analyze_bag` (live) parses `ros2 bag info` text for the totals and counts; anomaly detection is mock-only. Per-topic times, rates (`(n - 1) / span`) and `latched` are added when the bag can be read locally (`.db3` with the standard library, `.mcap` with `rosbags`), else rates fall back to count / bag duration (`frequency_basis`). `peek_bag_samples` reads the file itself, through `rosbags`, returns the first `count` messages in recording order (not the last), and is served only by the ROS2 CLI adapter or the mock; its `timestamp_ns` is the message's `header.stamp` (`stamp_source` `header`), the body time of `Clock`, `TFMessage` and `Log` messages (`payload`) or, without either, the bag record time (`stamp_source` `recorded`), and `recorded_ns` is always the bag record time; bags that embed no message definitions (Humble `.db3`) are decoded with the Humble definitions, or the distro the bag records, and `note` says so. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output. `health_check` also reports `sim_clock_published` (live only): true when `/clock` has a publisher, a hint that header stamps may be simulation time.
|
|
127
127
|
- Calls are serialized per backend (one `ros2` call at a time, one DDS call at a time) and each call has a 45 s wall budget, lock wait included; a call that cannot get its lane fails at once with `busy`. `health_check` and `peek_bag_samples` take no lock.
|
|
128
128
|
- No streaming or push subscriptions: tools are strictly request/response.
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
Not done yet: an opt-in probe to tell a hung writer from a healthy one, wider real-bus validation (RTI, OpenDDS) and DDS Security. Open work is tracked in [issues](https://github.com/yaniswav/TopicForge/issues).
|
|
131
131
|
|
|
132
132
|
## Telemetry
|
|
133
133
|
|
|
@@ -143,10 +143,10 @@ When on, each tool call emits one event with exactly six fields:
|
|
|
143
143
|
|
|
144
144
|
| Field | Example | Notes |
|
|
145
145
|
| ------------ | --------------- | ----------------------------------------------------------- |
|
|
146
|
-
| `tool_name` | `"list_topics"` | One of the
|
|
146
|
+
| `tool_name` | `"list_topics"` | One of the seventeen tools, never argument values |
|
|
147
147
|
| `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
|
|
148
148
|
| `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
|
|
149
|
-
| `version` | `"0.
|
|
149
|
+
| `version` | `"0.9.0"` | TopicForge server version |
|
|
150
150
|
| `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
|
|
151
151
|
| `success` | `true` | Whether the handler returned or raised |
|
|
152
152
|
|
|
@@ -174,7 +174,7 @@ python -m ruff check src tests
|
|
|
174
174
|
python -m pytest
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
Tests run against the mock adapter, the live adapter's pure parsers and the binding-free DDS helpers; they never need a running ROS graph. Tests needing the `cyclonedds`
|
|
177
|
+
Tests run against the mock adapter, the live adapter's pure parsers and the binding-free DDS helpers; they never need a running ROS graph. Tests needing the `cyclonedds` binding skip themselves when it is absent, and the `integration` marker (real-bus tests) is deselected by default. The `Makefile` (`make check`) uses POSIX shell syntax; on plain PowerShell run the commands above. See [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
178
178
|
|
|
179
179
|
## Upgrading
|
|
180
180
|
|
|
@@ -187,13 +187,13 @@ src/topicforge/
|
|
|
187
187
|
server/ MCP bootstrap, build_app(settings), prompts and instructions
|
|
188
188
|
tools/ thin MCPServer handlers, no backend logic
|
|
189
189
|
services/ input validation, orchestration, adapter factory
|
|
190
|
-
adapters/ ros2_live, ros2_mock, dds_cyclone,
|
|
190
|
+
adapters/ ros2_live, ros2_mock, dds_cyclone, common/ (binding-free logic)
|
|
191
191
|
models/ frozen Pydantic schemas, the contract with MCP clients
|
|
192
192
|
config/ settings and mode resolution
|
|
193
193
|
telemetry/ opt-in, off by default
|
|
194
194
|
examples/ mock walkthroughs (*.md) and runnable live DDS examples (dds/)
|
|
195
|
-
scripts/ real-bus interop checks
|
|
196
|
-
docs/ guides
|
|
195
|
+
scripts/ real-bus interop checks, ground-truth comparator, agent evaluation
|
|
196
|
+
docs/ guides and reference
|
|
197
197
|
tests/ pytest suite, mock-only, no ROS2 or DDS required
|
|
198
198
|
```
|
|
199
199
|
|
|
@@ -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.
|
|
9
|
+
uvx --from "topicforge==0.9.0" 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.
|
|
24
|
+
The version pin (`==0.9.0`) 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.
|
|
47
|
+
- **pip alternative.** `pip install "topicforge==0.9.0"`, 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.
|
|
75
|
+
"args": ["--from", "topicforge==0.9.0", "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.
|
|
98
|
+
claude mcp add topicforge --env TOPICFORGE_MODE=auto --env TOPICFORGE_DDS_BACKEND=cyclone --env TOPICFORGE_DDS_DOMAIN_ID=0 -- uvx --from "topicforge==0.9.0" 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=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjkuMCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%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=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjkuMCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%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.
|
|
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.9.0%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.
|
|
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.9.0%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.
|
|
141
|
+
"args": ["--from", "topicforge==0.9.0", "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.
|
|
170
|
+
to `"command": "cmd", "args": ["/c", "uvx", "--from", "topicforge==0.9.0",
|
|
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.
|
|
187
|
+
- "topicforge==0.9.0"
|
|
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.
|
|
204
|
+
"args": ["--from", "topicforge==0.9.0", "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.
|
|
230
|
+
args = ["--from", "topicforge==0.9.0", "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.
|
|
261
|
+
args: [--from, "topicforge==0.9.0", 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 seventeen tools.
|
|
273
273
|
|
|
274
274
|
## Amazon Q Developer / Kiro
|
|
275
275
|
|
|
@@ -297,11 +297,52 @@ The endpoint is `http://127.0.0.1:8765/mcp`. Two cases it is meant for:
|
|
|
297
297
|
- A robot: run it on the robot, then `ssh -L 8765:127.0.0.1:8765 robot` and use the same URL
|
|
298
298
|
on your laptop.
|
|
299
299
|
|
|
300
|
-
Threat model: the server has no authentication, so anything that can reach the
|
|
301
|
-
your robot graph. It therefore binds `127.0.0.1`
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
300
|
+
Threat model: without a token the server has no authentication, so anything that can reach the
|
|
301
|
+
port can read your robot graph. It therefore binds `127.0.0.1` by default and rejects any
|
|
302
|
+
request whose `Host` or `Origin` is not a loopback name with the served port, which stops a web
|
|
303
|
+
page in your browser from reaching it through DNS rebinding. Do not forward the port to a
|
|
304
|
+
network interface; use an SSH tunnel instead.
|
|
305
|
+
|
|
306
|
+
Bearer token (0.8.1). Set `TOPICFORGE_HTTP_TOKEN` to a secret of at least 32 printable
|
|
307
|
+
characters (for example `openssl rand -hex 32`) and every request must then carry
|
|
308
|
+
`Authorization: Bearer <token>`; a request without it gets `401`. The token is read from the
|
|
309
|
+
environment only, never from a flag. Only with a token may `--host` name another address:
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
TOPICFORGE_HTTP_TOKEN=... topicforge --transport streamable-http --host 0.0.0.0 --port 8765
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
This is plain HTTP: the token and every answer cross the network in clear, so keep it to a
|
|
316
|
+
network you trust, or put a TLS reverse proxy in front. The SSH tunnel above remains the safer
|
|
317
|
+
way to reach a robot. Clients that support HTTP headers in their MCP config (Claude Code's
|
|
318
|
+
`--header`, VS Code's `headers`, Cursor's `headers`) pass the token there.
|
|
319
|
+
|
|
320
|
+
## Docker
|
|
321
|
+
|
|
322
|
+
The repository's `Dockerfile` builds a stdio image on ROS 2 Jazzy (`ros:jazzy-ros-core`, the
|
|
323
|
+
`ros2` CLI and Cyclone DDS included, a non-root user):
|
|
324
|
+
|
|
325
|
+
```
|
|
326
|
+
docker build -t topicforge .
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Point a client at `docker` instead of `uvx`:
|
|
330
|
+
|
|
331
|
+
```json
|
|
332
|
+
{ "command": "docker", "args": ["run", "-i", "--rm", "topicforge"] }
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
What the container can see depends on its network:
|
|
336
|
+
|
|
337
|
+
- Linux with `--network host` (and the robot's `ROS_DOMAIN_ID`, e.g. `-e ROS_DOMAIN_ID=0`):
|
|
338
|
+
the live graph, as a local install would.
|
|
339
|
+
- Docker Desktop on Windows or macOS: the container does not see the host's DDS traffic. The
|
|
340
|
+
`ros2` CLI is in the image, so `auto` still picks live mode and finds an empty graph (only
|
|
341
|
+
`/rosout` and `/parameter_events`). Use it for bags, mounted with
|
|
342
|
+
`-v /path/to/bags:/bags:ro` (then ask about `/bags/run.mcap`), or for the demo robot with
|
|
343
|
+
`-e TOPICFORGE_MODE=mock`.
|
|
344
|
+
|
|
345
|
+
The image is about 1 GB (ROS 2 Jazzy core plus Cyclone DDS).
|
|
305
346
|
|
|
306
347
|
## Prompts and instructions
|
|
307
348
|
|
|
@@ -326,7 +367,7 @@ when the question matches.
|
|
|
326
367
|
|
|
327
368
|
## Troubleshooting
|
|
328
369
|
|
|
329
|
-
- Run the command by hand first: `uvx --from "topicforge==0.
|
|
370
|
+
- Run the command by hand first: `uvx --from "topicforge==0.9.0" topicforge --version`.
|
|
330
371
|
- Server starts but sees no DDS participants: check the domain id, and that the
|
|
331
372
|
client machine is on the same network as the robot (DDS discovery uses
|
|
332
373
|
multicast UDP). Ask the assistant to call `health_check`.
|