topicforge 0.8.0__tar.gz → 0.8.1__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.8.1}/CHANGELOG.md +40 -1
- {topicforge-0.8.0 → topicforge-0.8.1}/PKG-INFO +5 -5
- {topicforge-0.8.0 → topicforge-0.8.1}/README.md +4 -4
- {topicforge-0.8.0 → topicforge-0.8.1}/docs/CLIENTS.md +62 -21
- {topicforge-0.8.0 → topicforge-0.8.1}/docs/CONTRACT.md +35 -5
- {topicforge-0.8.0 → topicforge-0.8.1}/docs/TESTING.md +2 -2
- {topicforge-0.8.0 → topicforge-0.8.1}/docs/TOOLS.md +5 -4
- {topicforge-0.8.0 → topicforge-0.8.1}/plugin/README.md +1 -1
- {topicforge-0.8.0 → topicforge-0.8.1}/plugin/pyproject.toml +2 -2
- {topicforge-0.8.0 → topicforge-0.8.1}/pyproject.toml +1 -1
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/__init__.py +1 -1
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/__main__.py +41 -8
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/log_filter.py +5 -3
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/param_diff.py +34 -6
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/qos_scan.py +48 -8
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/tf_tree.py +31 -5
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/composite.py +7 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/adapter.py +57 -8
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/models/nodes.py +1 -1
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/models/schemas.py +9 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/models/tf.py +3 -1
- topicforge-0.8.1/src/topicforge/server/http.py +162 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/dds_views.py +14 -6
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/inspector.py +23 -4
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/tools/handlers.py +19 -9
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/detect_qos_mismatches.json +38 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/get_node_info.json +1 -1
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/inspect_tf.json +1 -1
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/fault_nodes.py +2 -2
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/run_bench.py +14 -4
- topicforge-0.8.1/tests/integration/ros2/sros2_check.py +159 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/test_live_adapter.py +1 -1
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_agent_eval_tools.py +15 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_friction_fixes.py +22 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_ground_truth_compare.py +5 -3
- topicforge-0.8.1/tests/test_http_transport.py +184 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_log_filter.py +26 -2
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_node_tools.py +5 -2
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_param_diff.py +3 -2
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_qos_scan.py +21 -2
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_tf_tools.py +77 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_tf_tree.py +21 -0
- topicforge-0.8.0/src/topicforge/server/http.py +0 -75
- topicforge-0.8.0/tests/test_http_transport.py +0 -112
- {topicforge-0.8.0 → topicforge-0.8.1}/.gitignore +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/LICENSE +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/docs/DDS_QUICKSTART.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/docs/EVAL.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/docs/MIGRATION-0.7.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/docs/TROUBLESHOOTING.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/docs/TUTORIEL.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/docs/VALIDATION.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/docs/dds-interop-matrix.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/00_hello_pub_sub/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/01_who_is_on_the_bus/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/02_why_cant_they_talk/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/03_a_node_crashed/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/04_late_joiner_misses_data/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/05_reliability_in_code/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/06_durability_late_joiner_in_code/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/07_deadline_in_code/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/08_crash_seen_from_inside/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/10_lidar_silent_after_driver_swap/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/11_who_talks_to_whom/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/12_safety_monitor_dropout/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/13_deadline_not_offered/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/14_restart_loop/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/plugin/LICENSE +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/scripts/agent_eval/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/scripts/ground_truth/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/cyclone_c/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/cyclone_cpp/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/cyclone_rust/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/dust_py/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/fast_publisher_cpp/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/fast_py/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/opensplice_publisher/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/rti_c/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/rti_cpp/README.md +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/base.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/bag_kind.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/bag_range.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/cdr_decoder.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/dds_helpers.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/dds_introspection.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/discovery_tracker.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/endpoints.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/lifecycle.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/metrics_buffer.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/nodes.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/qos_analyzer.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/qos_endpoints.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/qos_fixes.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/qos_normalize.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/ros_discovery.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/ros_names.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/spawned_pids.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/stamps.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/topic_filter.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/urdf.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/xtypes.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/dds_cyclone/adapter.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/dds_cyclone/ros_info.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/dds_fast/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/dds_fast/adapter.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/echo_parser.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/echo_stream.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/node_parsers.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/parsers.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/process_runner.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/process_tree.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/sample_extras.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/tf_capture.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/tf_parsers.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/adapter.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/fixtures.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/graph_fixtures.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/node_fixtures.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/tf_fixtures.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/budget.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/config/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/config/settings.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/constants.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/models/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/models/graph_diff.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/models/summaries.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/server/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/server/app.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/server/prompts.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/bag_service.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/bag_stats.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/factory.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/graph_diff.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/graph_snapshot.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/health.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/params_file.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/sample_budget.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/summaries/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/summaries/_common.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/summaries/clock.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/summaries/laser_scan.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/summaries/motion.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/summaries/rate.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/summaries/sensor_data.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/telemetry/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/telemetry/client.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/tools/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/tools/annotations.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/tools/guard.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/tools/schema_compact.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/conftest.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/_prompts.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/analyze_bag.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/get_topic_info.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/graph_diff.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/health_check.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/list_endpoints.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/list_nodes.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/list_participants.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/list_topics.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/participant_events.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/peek_bag_samples.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/peek_dds_samples.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/sample_messages.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/topic_metrics.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_064_results.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_064_truth.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_071_morning_results.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_071_morning_truth.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_071_rerun_results.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_071_rerun_truth.json +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/image_noarr.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/image_trunc4.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/scan_edge.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/scan_noarr.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/scan_trunc128.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/scan_trunc3.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/string_latched.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/twist.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_info_humble.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_info_no_param_services.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_info_not_found.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_list_duplicates.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_list_empty.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_list_plain.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_list_warning_stdout.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/param_dump_humble.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/param_dump_secrets.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/service_list_types.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_clock.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_cmd_vel.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_parameter_events.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_scan.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_tf.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_tf_static.txt +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros_discovery/humble_two_nodes.hex +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros_discovery/jazzy_two_nodes.hex +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/Dockerfile +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/__init__.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/bench_params.yaml +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/blocked_node.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/entrypoint.sh +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/faults.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/graph_change.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/ground_truth.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/lifecycle_node.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/publisher.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/test_real_bus.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_analyze_bag_multi_format.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_bag_omnisim_humble.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_bag_service.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_bag_stats.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_bag_summaries.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_cdr_decoder.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_clock_basis.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_composite_adapter.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_composite_ghosts.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_config.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_contract_shape.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_contract_snapshots.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_contract_v2.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_cyclone_adapter.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_cyclone_ros_info.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_dds_cross_vendor.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_dds_helpers.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_dds_inactive_reason.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_dds_introspection.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_dds_qos_normalization.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_dds_schemas.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_discovery_tracker.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_echo_parser.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_echo_stream.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_endpoints.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_example_node_spec.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_factory.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_fast_adapter.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_field_names.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_graph_diff.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_health.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_history_and_hints.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_honest_outputs.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_inspector.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_lifecycle_buffer.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_live_adapter_graph.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_live_adapter_parse.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_live_adapter_subprocess.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_live_sample_notes.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_metrics_buffer.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_mock_adapter.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_node_logic.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_node_parsers.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_output_schema_validation.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_peek_bag_samples.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_process_runner.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_process_tree.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_prompts.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_qos_analyzer.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_qos_endpoints.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_qos_fixes.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_rate_verdicts.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_restarts_by_node.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_ros_discovery.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_sample_options.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_sample_summary_flow.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_server_guard.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_server_info_version.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_server_lanes.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_stamps.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_summaries.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_summary_schema_compat.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_telemetry.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_tool_annotations.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_tool_descriptions.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_tools_integration.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_topic_metrics.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_urdf.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_version_consistency.py +0 -0
- {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_xtypes.py +0 -0
|
@@ -5,6 +5,43 @@ 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
|
+
## [0.8.1] - 2026-10-26
|
|
9
|
+
|
|
10
|
+
The 0.8.0 evaluation findings, a URDF read from the robot_state_publisher parameter, an optional
|
|
11
|
+
HTTP bearer token, a Docker image and a SROS2 bench cell. Additive only (`docs/CONTRACT.md`
|
|
12
|
+
section 9.18). `tools/list` is 97,315 bytes (+467).
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- HTTP transport: `TOPICFORGE_HTTP_TOKEN` (at least 32 printable characters, environment only)
|
|
17
|
+
makes every request carry `Authorization: Bearer <token>`, compared in constant time, else
|
|
18
|
+
`401`. `--host` may bind an address other than loopback only with a token; such a bind lifts
|
|
19
|
+
the DNS-rebinding allow-list. Plain HTTP: SECURITY.md says what the token does not cover.
|
|
20
|
+
- `inspect_tf` reads the URDF from the `robot_description` parameter of the node named
|
|
21
|
+
`robot_state_publisher` when no node publishes `/robot_description`; `urdf_note` names the node.
|
|
22
|
+
- `reader_node` and `writer_node` on `detect_qos_mismatches` reports, `not_matched` and
|
|
23
|
+
`matched` pairs.
|
|
24
|
+
- A root `Dockerfile`: a stdio image on `ros:jazzy-ros-core` with the `ros2` CLI and Cyclone DDS,
|
|
25
|
+
non-root. Live graphs need Linux `--network host`; docs/CLIENTS.md has the Docker Desktop
|
|
26
|
+
limits.
|
|
27
|
+
- Bench: a `sros2` job (`run_bench.py --sros2`) with one node under SROS2 Enforce. Run on jazzy
|
|
28
|
+
with Cyclone and Fast DDS, 4 of 4 checks in each.
|
|
29
|
+
- Eval harness: `restarts_at_first_call` in the `restart_loop` summary, counted at the agent's
|
|
30
|
+
first call rather than at `READY`.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- `inspect_tf`: an edge with a steady rate silent for over ten of its periods (at least 1 s) is
|
|
35
|
+
stale even under `stale_after_s`. With the defaults, the evaluation's `tf_stale` read as healthy.
|
|
36
|
+
- `restarts_by_name` leaves out the name `/` that every ROS 2 node shares on Fast DDS; the note
|
|
37
|
+
points to `restarts_by_node`.
|
|
38
|
+
- `min_log_level` accepts `WARN`, `Error` and `warning`.
|
|
39
|
+
- `detect_qos_mismatches` without `topic` leaves `ros_discovery_info` out, and folds orphan
|
|
40
|
+
topics into one hint per side instead of one hint each.
|
|
41
|
+
- `param_diff_summary` names the `same` and `live_only` parameters.
|
|
42
|
+
- A filtered `/rosout` read that received nothing says so, instead of mixing the no-match advice
|
|
43
|
+
with the latched-topic advice.
|
|
44
|
+
|
|
8
45
|
## [0.8.0] - 2026-10-21
|
|
9
46
|
|
|
10
47
|
Graph diff, declared-against-real checks, DDS visibility and a Nav2 prompt. It also carries the
|
|
@@ -1864,7 +1901,9 @@ Initial MVP release of TopicForge: ROS Topic Inspector & Bag Analyzer MCP server
|
|
|
1864
1901
|
- The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
|
|
1865
1902
|
- `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
|
|
1866
1903
|
|
|
1867
|
-
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.
|
|
1904
|
+
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.8.1...HEAD
|
|
1905
|
+
[0.8.1]: https://github.com/yaniswav/TopicForge/compare/v0.8.0...v0.8.1
|
|
1906
|
+
[0.8.0]: https://github.com/yaniswav/TopicForge/compare/v0.7.2...v0.8.0
|
|
1868
1907
|
[0.7.2]: https://github.com/yaniswav/TopicForge/compare/v0.7.1...v0.7.2
|
|
1869
1908
|
[0.7.1]: https://github.com/yaniswav/TopicForge/compare/v0.7.0...v0.7.1
|
|
1870
1909
|
[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.8.
|
|
3
|
+
Version: 0.8.1
|
|
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
|
|
@@ -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=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjguMSIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%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.8.1%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
|
|
|
@@ -168,7 +168,7 @@ A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Pyt
|
|
|
168
168
|
| `TOPICFORGE_DDS_DOMAIN_ID` | `0` | DDS domain observed (0..232). Joined at startup; changing it needs a restart |
|
|
169
169
|
| `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
170
|
|
|
171
|
-
Command-line options (not environment variables): `--transport stdio|streamable-http` (default `stdio`)
|
|
171
|
+
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
172
|
|
|
173
173
|
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
174
|
|
|
@@ -207,7 +207,7 @@ When on, each tool call emits one event with exactly six fields:
|
|
|
207
207
|
| `tool_name` | `"list_topics"` | One of the sixteen 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.8.
|
|
210
|
+
| `version` | `"0.8.1"` | 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
|
|
|
@@ -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=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjguMSIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%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.8.1%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
|
|
|
@@ -107,7 +107,7 @@ A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Pyt
|
|
|
107
107
|
| `TOPICFORGE_DDS_DOMAIN_ID` | `0` | DDS domain observed (0..232). Joined at startup; changing it needs a restart |
|
|
108
108
|
| `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
109
|
|
|
110
|
-
Command-line options (not environment variables): `--transport stdio|streamable-http` (default `stdio`)
|
|
110
|
+
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
111
|
|
|
112
112
|
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
113
|
|
|
@@ -146,7 +146,7 @@ When on, each tool call emits one event with exactly six fields:
|
|
|
146
146
|
| `tool_name` | `"list_topics"` | One of the sixteen 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.8.
|
|
149
|
+
| `version` | `"0.8.1"` | 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
|
|
|
@@ -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.8.
|
|
9
|
+
uvx --from "topicforge==0.8.1" 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.8.
|
|
24
|
+
The version pin (`==0.8.1`) 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.8.
|
|
47
|
+
- **pip alternative.** `pip install "topicforge==0.8.1"`, 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.8.
|
|
75
|
+
"args": ["--from", "topicforge==0.8.1", "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.8.
|
|
98
|
+
claude mcp add topicforge --env TOPICFORGE_MODE=auto --env TOPICFORGE_DDS_BACKEND=cyclone --env TOPICFORGE_DDS_DOMAIN_ID=0 -- uvx --from "topicforge==0.8.1" 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=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjguMSIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%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=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjguMSIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%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.8.
|
|
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.8.1%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.8.
|
|
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.8.1%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.8.
|
|
141
|
+
"args": ["--from", "topicforge==0.8.1", "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.8.
|
|
170
|
+
to `"command": "cmd", "args": ["/c", "uvx", "--from", "topicforge==0.8.1",
|
|
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.8.
|
|
187
|
+
- "topicforge==0.8.1"
|
|
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.8.
|
|
204
|
+
"args": ["--from", "topicforge==0.8.1", "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.8.
|
|
230
|
+
args = ["--from", "topicforge==0.8.1", "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.8.
|
|
261
|
+
args: [--from, "topicforge==0.8.1", topicforge]
|
|
262
262
|
enabled: true
|
|
263
263
|
envs: { "TOPICFORGE_MODE": "auto", "TOPICFORGE_DDS_BACKEND": "cyclone", "TOPICFORGE_DDS_DOMAIN_ID": "0" }
|
|
264
264
|
timeout: 300
|
|
@@ -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.8.
|
|
370
|
+
- Run the command by hand first: `uvx --from "topicforge==0.8.1" 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`.
|
|
@@ -1099,7 +1099,7 @@ New `TfReport` fields:
|
|
|
1099
1099
|
|
|
1100
1100
|
| Field | Meaning |
|
|
1101
1101
|
| --- | --- |
|
|
1102
|
-
| `urdf_source` | `robot_description` when a URDF was read and compared, else `none` (default) |
|
|
1102
|
+
| `urdf_source` | `robot_description` when a URDF was read and compared (from the topic, or since 0.8.1 from the parameter of the same name), else `none` (default) |
|
|
1103
1103
|
| `urdf_link_count` | links in the URDF, `null` when none was read |
|
|
1104
1104
|
| `declared_frames_missing` | URDF links with no TF frame, in URDF order |
|
|
1105
1105
|
| `frames_not_in_urdf` | TF frames that are not a link, sorted. Informative: `map` and `odom` are legitimate |
|
|
@@ -1111,8 +1111,11 @@ missing link in `frames`; its `text` says that `robot_state_publisher` puts fixe
|
|
|
1111
1111
|
`/tf_static` and the others on `/tf`, and names the links under a non-fixed joint, which have a
|
|
1112
1112
|
frame only while `/joint_states` is published. With no TF frame captured at all nothing is
|
|
1113
1113
|
compared (no finding, a note), so an empty capture does not read as "every link is missing".
|
|
1114
|
-
`/robot_description`
|
|
1115
|
-
|
|
1114
|
+
In 0.8.0 `/robot_description` was a topic only. Since 0.8.1, when no node publishes the topic,
|
|
1115
|
+
`inspect_tf` reads the `robot_description` parameter of the first node named
|
|
1116
|
+
`robot_state_publisher` (any namespace) with one `ros2 param dump`, the same parameter read as
|
|
1117
|
+
`get_node_info`. `urdf_source` stays `robot_description` (a closed set does not grow between
|
|
1118
|
+
breaks, section 1.9) and `urdf_note` says the URDF came from that node's parameter.
|
|
1116
1119
|
`docs/TOOLS.md` says that a robot needs `robot_state_publisher` for this.
|
|
1117
1120
|
|
|
1118
1121
|
**`get_node_info(params_file=...)`.** `params_file` is a path on the machine the server runs on, to
|
|
@@ -1214,8 +1217,12 @@ its name; the Dust endpoint has none.
|
|
|
1214
1217
|
`list_endpoints(topic="/scan")` call in `calls.json`): the `/scan` writer's `node_name` and
|
|
1215
1218
|
`writer_nodes` are `/bench_robot`, the participant that hosts `/bench_robot` has an
|
|
1216
1219
|
`endpoint_count` of at least 12, `ros_security_enabled` is `false` and `ros_security_note` is
|
|
1217
|
-
`null`. Written for the four Docker cells; not run in the session that added it.
|
|
1218
|
-
|
|
1220
|
+
`null`. Written for the four Docker cells; not run in the session that added it. RTI is not
|
|
1221
|
+
covered. Since 0.8.1 the `sros2` job of `ros2-live.yml` (`run_bench.py --sros2`,
|
|
1222
|
+
`tests/integration/ros2/sros2_check.py`) covers a bus with SROS2 enabled: one node with the
|
|
1223
|
+
Enforce strategy, TopicForge without an identity sees its participant with `endpoint_count` 0 and
|
|
1224
|
+
the note above, and `health_check` reports the four fields with and without the variables. Run
|
|
1225
|
+
on 2026-10-23 on jazzy with Cyclone and Fast DDS: 4 of 4 checks passed in each cell.
|
|
1219
1226
|
|
|
1220
1227
|
### 9.17 The `diagnose-nav2` prompt and hidden topics (0.8.0)
|
|
1221
1228
|
|
|
@@ -1256,3 +1263,26 @@ latched status by hand, since an idle server publishes none) and must return one
|
|
|
1256
1263
|
must be absent from `list_topics`; `41_bond` reads `/bond` (`bond/msg/Status`, id `bench_robot`,
|
|
1257
1264
|
1 Hz) and every sample must carry the id. Comparator section `nav_primitives`. Written for the
|
|
1258
1265
|
four Docker cells; not run in the session that added it, and Nav2 itself was never run.
|
|
1266
|
+
|
|
1267
|
+
### 9.18 0.8.1: eval findings, URDF parameter, HTTP token
|
|
1268
|
+
|
|
1269
|
+
Additive. `tools/list` grows by 467 bytes, to 97,315 (measured like `test_tool_listing_stays_small`).
|
|
1270
|
+
|
|
1271
|
+
- `MismatchReport`, `NotMatchedPair` and `MatchedPair` gain `reader_node` and `writer_node`
|
|
1272
|
+
(string or `null`), the `node_name` of each endpoint (section 9.16).
|
|
1273
|
+
- `detect_qos_mismatches` without `topic` leaves `ros_discovery_info` out (every ROS 2
|
|
1274
|
+
participant reads and writes it with one QoS, about N x N matched pairs); `topic` can still
|
|
1275
|
+
name it. Orphan topics that no near name explains are folded into one hint per side.
|
|
1276
|
+
- `TfFrame.stale` is also true for an edge with a steady rate (5 transforms or more) silent for
|
|
1277
|
+
over ten of its periods, at least 1 s, even under `stale_after_s`. The finding text says which
|
|
1278
|
+
limit applied.
|
|
1279
|
+
- `restarts_by_name` leaves out the name `/` (the ROS 2 enclave every node shares on Fast DDS);
|
|
1280
|
+
those participants are counted in `restarts_note`, which points to `restarts_by_node`.
|
|
1281
|
+
- `min_log_level` accepts any case and `warning`; the schema keeps the lower-case enum.
|
|
1282
|
+
- `param_diff_summary` names the `same` and `live_only` parameters, up to ten each:
|
|
1283
|
+
`1 same (frame_id), 1 live_only (use_sim_time)`.
|
|
1284
|
+
- A filtered `/rosout` read that received nothing says so in one note.
|
|
1285
|
+
- `inspect_tf` reads the URDF from the robot_state_publisher parameter when no node publishes
|
|
1286
|
+
`/robot_description` (section 9.15).
|
|
1287
|
+
- HTTP transport: `TOPICFORGE_HTTP_TOKEN` and `--host` (SECURITY.md, docs/CLIENTS.md). No output
|
|
1288
|
+
change.
|
|
@@ -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.8.
|
|
18
|
+
python -m topicforge --version # -> topicforge 0.8.1
|
|
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.8.
|
|
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.8.1 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
|
|
|
@@ -125,7 +125,7 @@ ROS 2 graph only; on a DDS-only setup use `list_participants`. Describe one ROS
|
|
|
125
125
|
| `mode_effective` | `"mock" \| "live"` | yes | `live` or `mock` (fixtures). |
|
|
126
126
|
| `note` | `string \| null` | no | One sentence of context, or `None`. |
|
|
127
127
|
| `param_diff` | `array \| null` | no | File against node: differences only. |
|
|
128
|
-
| `param_diff_summary` | `string \| null` | no |
|
|
128
|
+
| `param_diff_summary` | `string \| null` | no | Left out of `param_diff`: same, live_only, with names. |
|
|
129
129
|
| `param_diff_note` | `string \| null` | no | - |
|
|
130
130
|
|
|
131
131
|
## `get_topic_info`
|
|
@@ -564,9 +564,10 @@ Return temporal metrics (frequency, sequence gaps, latency percentiles) for a DD
|
|
|
564
564
|
|
|
565
565
|
- `inspect_tf` compares the TF frames with the URDF on the `/robot_description`
|
|
566
566
|
topic, which `robot_state_publisher` publishes (the default since ROS 2 Galactic).
|
|
567
|
-
|
|
568
|
-
`
|
|
569
|
-
|
|
567
|
+
Without that topic it reads the `robot_description` parameter of the node named
|
|
568
|
+
`robot_state_publisher` (`urdf_note` then names that node and parameter);
|
|
569
|
+
otherwise `urdf_source` is `none` and `urdf_note` says why. A link on a non-fixed
|
|
570
|
+
joint has a TF frame only while `/joint_states` is published.
|
|
570
571
|
- `get_node_info(params_file=...)` reads a YAML file from the disk of the machine
|
|
571
572
|
running TopicForge (`.yaml` or `.yml`, at most 1 MiB, read-only). The path is never
|
|
572
573
|
sent to a node or to the `ros2` CLI, and nothing is written.
|
|
@@ -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.8.
|
|
7
|
+
- MCP server `topicforge`, started with `uvx` from the `topicforge` package on PyPI (version pinned to 0.8.1). It exposes sixteen 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`, `graph_diff`.
|
|
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
|
- Skill `diagnose-nav2`: the call order for a Nav2 stack that does not start, aborts goals or does not move the robot (lifecycle managers, bonds, goal status, TF, logs), and what TopicForge cannot see there.
|
|
@@ -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.8.
|
|
7
|
+
version = "0.8.1"
|
|
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.8.
|
|
12
|
+
"topicforge==0.8.1",
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
[tool.uv]
|
|
@@ -4,12 +4,20 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
6
|
import logging
|
|
7
|
+
import os
|
|
7
8
|
import sys
|
|
8
9
|
|
|
9
10
|
from topicforge import __version__
|
|
10
11
|
from topicforge.config import load_settings
|
|
11
12
|
from topicforge.server import build_app
|
|
12
|
-
from topicforge.server.http import
|
|
13
|
+
from topicforge.server.http import (
|
|
14
|
+
DEFAULT_HTTP_PORT,
|
|
15
|
+
LOOPBACK_HOST,
|
|
16
|
+
TOKEN_ENV,
|
|
17
|
+
is_loopback,
|
|
18
|
+
serve_http,
|
|
19
|
+
validate_token,
|
|
20
|
+
)
|
|
13
21
|
|
|
14
22
|
|
|
15
23
|
def _build_arg_parser() -> argparse.ArgumentParser:
|
|
@@ -19,13 +27,15 @@ def _build_arg_parser() -> argparse.ArgumentParser:
|
|
|
19
27
|
"ROS Topic Inspector & Bag Analyzer MCP server. "
|
|
20
28
|
"Runs on stdio by default so MCP clients (Claude Desktop, Claude "
|
|
21
29
|
"Code, etc.) can spawn it directly; --transport streamable-http "
|
|
22
|
-
"serves it on 127.0.0.1
|
|
30
|
+
"serves it on 127.0.0.1 (another --host needs a bearer token)."
|
|
23
31
|
),
|
|
24
32
|
epilog=(
|
|
25
33
|
"Configuration is read from environment variables: "
|
|
26
34
|
"TOPICFORGE_MODE (mock|live|auto, default auto), "
|
|
27
35
|
"TOPICFORGE_LOG_LEVEL (DEBUG|INFO|WARNING|ERROR, default INFO), "
|
|
28
|
-
"TOPICFORGE_ROS2_BIN (default 'ros2')
|
|
36
|
+
"TOPICFORGE_ROS2_BIN (default 'ros2'), "
|
|
37
|
+
f"{TOKEN_ENV} (bearer token for --transport streamable-http, at least 32 "
|
|
38
|
+
"characters; required for a --host other than loopback)."
|
|
29
39
|
),
|
|
30
40
|
)
|
|
31
41
|
parser.add_argument(
|
|
@@ -38,8 +48,16 @@ def _build_arg_parser() -> argparse.ArgumentParser:
|
|
|
38
48
|
choices=("stdio", "streamable-http"),
|
|
39
49
|
default="stdio",
|
|
40
50
|
help=(
|
|
41
|
-
"stdio (default) or streamable-http,
|
|
42
|
-
f"{LOOPBACK_HOST}
|
|
51
|
+
"stdio (default) or streamable-http, an HTTP endpoint bound to "
|
|
52
|
+
f"{LOOPBACK_HOST} by default (reach it through an SSH tunnel)."
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"--host",
|
|
57
|
+
default=None,
|
|
58
|
+
help=(
|
|
59
|
+
f"Bind address for --transport streamable-http (default {LOOPBACK_HOST}). "
|
|
60
|
+
f"Anything but loopback requires {TOKEN_ENV}; the traffic is plain HTTP."
|
|
43
61
|
),
|
|
44
62
|
)
|
|
45
63
|
parser.add_argument(
|
|
@@ -55,11 +73,26 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
55
73
|
"""Build the MCP app from environment settings and run it on stdio."""
|
|
56
74
|
parser = _build_arg_parser()
|
|
57
75
|
args = parser.parse_args(argv)
|
|
58
|
-
|
|
59
|
-
|
|
76
|
+
for flag, value in (("--port", args.port), ("--host", args.host)):
|
|
77
|
+
if value is not None and args.transport != "streamable-http":
|
|
78
|
+
parser.error(f"{flag} only applies to --transport streamable-http")
|
|
60
79
|
port = DEFAULT_HTTP_PORT if args.port is None else args.port
|
|
61
80
|
if not 1 <= port <= 65535:
|
|
62
81
|
parser.error("--port must be in 1..65535")
|
|
82
|
+
host = LOOPBACK_HOST if args.host is None else args.host.strip()
|
|
83
|
+
token = os.environ.get(TOKEN_ENV) or None
|
|
84
|
+
if args.transport == "streamable-http":
|
|
85
|
+
if token is not None:
|
|
86
|
+
try:
|
|
87
|
+
validate_token(token)
|
|
88
|
+
except ValueError as exc:
|
|
89
|
+
parser.error(str(exc))
|
|
90
|
+
if not host:
|
|
91
|
+
parser.error("--host must not be empty")
|
|
92
|
+
if token is None and not is_loopback(host):
|
|
93
|
+
parser.error(
|
|
94
|
+
f"--host {host} needs {TOKEN_ENV}: without a token only loopback is served"
|
|
95
|
+
)
|
|
63
96
|
|
|
64
97
|
try:
|
|
65
98
|
settings = load_settings()
|
|
@@ -78,7 +111,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
78
111
|
app = build_app(settings)
|
|
79
112
|
try:
|
|
80
113
|
if args.transport == "streamable-http":
|
|
81
|
-
serve_http(app, port)
|
|
114
|
+
serve_http(app, port, host, token)
|
|
82
115
|
else:
|
|
83
116
|
app.run()
|
|
84
117
|
except KeyboardInterrupt:
|
|
@@ -35,13 +35,15 @@ def is_log_type(message_type: str) -> bool:
|
|
|
35
35
|
def level_threshold(min_log_level: str | None) -> int | None:
|
|
36
36
|
"""Numeric threshold of a level name; `None` for no level filter.
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Case is ignored and `warning` means `warn`. Raises `AdapterError` for any other name
|
|
39
|
+
outside `debug`, `info`, `warn`, `error`, `fatal`.
|
|
39
40
|
"""
|
|
40
41
|
if min_log_level is None:
|
|
41
42
|
return None
|
|
42
43
|
try:
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
name = min_log_level.strip().lower()
|
|
45
|
+
return LOG_LEVELS["warn" if name == "warning" else name]
|
|
46
|
+
except (KeyError, TypeError, AttributeError):
|
|
45
47
|
raise AdapterError(
|
|
46
48
|
f"min_log_level must be one of {', '.join(LOG_LEVELS)}, got {min_log_level!r}"
|
|
47
49
|
) from None
|