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.
Files changed (293) hide show
  1. {topicforge-0.8.0 → topicforge-0.9.0}/CHANGELOG.md +83 -1
  2. {topicforge-0.8.0 → topicforge-0.9.0}/PKG-INFO +17 -17
  3. {topicforge-0.8.0 → topicforge-0.9.0}/README.md +16 -16
  4. {topicforge-0.8.0 → topicforge-0.9.0}/docs/CLIENTS.md +63 -22
  5. {topicforge-0.8.0 → topicforge-0.9.0}/docs/CONTRACT.md +138 -7
  6. {topicforge-0.8.0 → topicforge-0.9.0}/docs/DDS_QUICKSTART.md +18 -19
  7. {topicforge-0.8.0 → topicforge-0.9.0}/docs/EVAL.md +75 -0
  8. {topicforge-0.8.0 → topicforge-0.9.0}/docs/TESTING.md +6 -6
  9. {topicforge-0.8.0 → topicforge-0.9.0}/docs/TOOLS.md +68 -33
  10. {topicforge-0.8.0 → topicforge-0.9.0}/docs/TROUBLESHOOTING.md +8 -13
  11. {topicforge-0.8.0 → topicforge-0.9.0}/docs/TUTORIEL.md +4 -4
  12. {topicforge-0.8.0 → topicforge-0.9.0}/docs/VALIDATION.md +1 -1
  13. {topicforge-0.8.0 → topicforge-0.9.0}/docs/dds-interop-matrix.md +3 -3
  14. {topicforge-0.8.0 → topicforge-0.9.0}/examples/README.md +1 -1
  15. {topicforge-0.8.0 → topicforge-0.9.0}/plugin/README.md +1 -1
  16. {topicforge-0.8.0 → topicforge-0.9.0}/plugin/pyproject.toml +2 -2
  17. {topicforge-0.8.0 → topicforge-0.9.0}/pyproject.toml +7 -11
  18. {topicforge-0.8.0 → topicforge-0.9.0}/scripts/agent_eval/README.md +3 -2
  19. {topicforge-0.8.0 → topicforge-0.9.0}/scripts/ground_truth/README.md +1 -0
  20. {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/README.md +7 -7
  21. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/__init__.py +1 -1
  22. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/__main__.py +41 -8
  23. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/dds_helpers.py +2 -2
  24. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/dds_introspection.py +2 -1
  25. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/discovery_tracker.py +20 -2
  26. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/lifecycle.py +5 -5
  27. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/log_filter.py +5 -3
  28. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/param_diff.py +34 -6
  29. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/qos_endpoints.py +4 -4
  30. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/qos_normalize.py +4 -3
  31. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/qos_scan.py +48 -8
  32. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/tf_tree.py +31 -5
  33. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/xtypes.py +1 -1
  34. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/composite.py +8 -1
  35. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/dds_cyclone/adapter.py +1 -7
  36. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/adapter.py +59 -11
  37. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/config/settings.py +10 -6
  38. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/constants.py +6 -0
  39. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/models/__init__.py +5 -0
  40. topicforge-0.9.0/src/topicforge/models/diagnose.py +107 -0
  41. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/models/nodes.py +3 -3
  42. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/models/schemas.py +52 -50
  43. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/models/summaries.py +4 -5
  44. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/models/tf.py +3 -1
  45. topicforge-0.9.0/src/topicforge/server/http.py +162 -0
  46. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/server/prompts.py +3 -2
  47. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/dds_views.py +15 -7
  48. topicforge-0.9.0/src/topicforge/services/diagnose.py +672 -0
  49. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/factory.py +2 -30
  50. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/health.py +1 -1
  51. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/inspector.py +28 -5
  52. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/tools/guard.py +3 -2
  53. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/tools/handlers.py +80 -33
  54. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/_prompts.json +2 -2
  55. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/analyze_bag.json +3 -3
  56. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/detect_qos_mismatches.json +42 -4
  57. topicforge-0.9.0/tests/contract/diagnose.json +262 -0
  58. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/get_node_info.json +1 -1
  59. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/get_topic_info.json +1 -1
  60. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/graph_diff.json +1 -1
  61. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/health_check.json +4 -4
  62. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/inspect_tf.json +2 -2
  63. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/list_endpoints.json +5 -5
  64. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/list_nodes.json +2 -2
  65. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/list_participants.json +5 -5
  66. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/list_topics.json +1 -1
  67. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/participant_events.json +3 -3
  68. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/peek_bag_samples.json +5 -5
  69. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/peek_dds_samples.json +6 -6
  70. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/sample_messages.json +6 -6
  71. {topicforge-0.8.0 → topicforge-0.9.0}/tests/contract/topic_metrics.json +2 -2
  72. {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/entrypoint.sh +4 -3
  73. {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/fault_nodes.py +2 -2
  74. {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/ground_truth.py +43 -1
  75. {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/run_bench.py +14 -4
  76. topicforge-0.9.0/tests/integration/ros2/sros2_check.py +159 -0
  77. {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/test_live_adapter.py +1 -1
  78. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_agent_eval_tools.py +44 -0
  79. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_config.py +13 -31
  80. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_contract_shape.py +2 -1
  81. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_dds_cross_vendor.py +15 -39
  82. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_dds_inactive_reason.py +0 -6
  83. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_dds_introspection.py +1 -1
  84. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_dds_qos_normalization.py +1 -1
  85. topicforge-0.9.0/tests/test_diagnose.py +761 -0
  86. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_endpoints.py +52 -0
  87. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_factory.py +2 -16
  88. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_friction_fixes.py +49 -2
  89. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_ground_truth_compare.py +248 -6
  90. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_honest_outputs.py +1 -1
  91. topicforge-0.9.0/tests/test_http_transport.py +184 -0
  92. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_log_filter.py +26 -2
  93. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_node_tools.py +5 -2
  94. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_output_schema_validation.py +1 -0
  95. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_param_diff.py +3 -2
  96. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_qos_scan.py +21 -2
  97. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_tf_tools.py +77 -0
  98. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_tf_tree.py +21 -0
  99. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_tool_annotations.py +2 -2
  100. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_tool_descriptions.py +3 -1
  101. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_tools_integration.py +5 -3
  102. topicforge-0.8.0/src/topicforge/adapters/dds_fast/__init__.py +0 -11
  103. topicforge-0.8.0/src/topicforge/adapters/dds_fast/adapter.py +0 -487
  104. topicforge-0.8.0/src/topicforge/server/http.py +0 -75
  105. topicforge-0.8.0/tests/test_fast_adapter.py +0 -149
  106. topicforge-0.8.0/tests/test_http_transport.py +0 -112
  107. {topicforge-0.8.0 → topicforge-0.9.0}/.gitignore +0 -0
  108. {topicforge-0.8.0 → topicforge-0.9.0}/LICENSE +0 -0
  109. {topicforge-0.8.0 → topicforge-0.9.0}/docs/MIGRATION-0.7.md +0 -0
  110. {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/00_hello_pub_sub/README.md +0 -0
  111. {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/01_who_is_on_the_bus/README.md +0 -0
  112. {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/02_why_cant_they_talk/README.md +0 -0
  113. {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/03_a_node_crashed/README.md +0 -0
  114. {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/04_late_joiner_misses_data/README.md +0 -0
  115. {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/05_reliability_in_code/README.md +0 -0
  116. {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/06_durability_late_joiner_in_code/README.md +0 -0
  117. {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/07_deadline_in_code/README.md +0 -0
  118. {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/08_crash_seen_from_inside/README.md +0 -0
  119. {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/10_lidar_silent_after_driver_swap/README.md +0 -0
  120. {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/11_who_talks_to_whom/README.md +0 -0
  121. {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/12_safety_monitor_dropout/README.md +0 -0
  122. {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/13_deadline_not_offered/README.md +0 -0
  123. {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/14_restart_loop/README.md +0 -0
  124. {topicforge-0.8.0 → topicforge-0.9.0}/examples/dds/README.md +0 -0
  125. {topicforge-0.8.0 → topicforge-0.9.0}/plugin/LICENSE +0 -0
  126. {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/cyclone_c/README.md +0 -0
  127. {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/cyclone_cpp/README.md +0 -0
  128. {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/cyclone_rust/README.md +0 -0
  129. {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/dust_py/README.md +0 -0
  130. {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/fast_publisher_cpp/README.md +0 -0
  131. {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/fast_py/README.md +0 -0
  132. {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/opensplice_publisher/README.md +0 -0
  133. {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/rti_c/README.md +0 -0
  134. {topicforge-0.8.0 → topicforge-0.9.0}/scripts/integration/publishers/rti_cpp/README.md +0 -0
  135. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/__init__.py +0 -0
  136. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/base.py +0 -0
  137. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/__init__.py +0 -0
  138. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/bag_kind.py +0 -0
  139. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/bag_range.py +0 -0
  140. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/cdr_decoder.py +0 -0
  141. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/endpoints.py +0 -0
  142. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/metrics_buffer.py +0 -0
  143. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/nodes.py +0 -0
  144. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/qos_analyzer.py +0 -0
  145. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/qos_fixes.py +0 -0
  146. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/ros_discovery.py +0 -0
  147. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/ros_names.py +0 -0
  148. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/spawned_pids.py +0 -0
  149. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/stamps.py +0 -0
  150. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/topic_filter.py +0 -0
  151. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/common/urdf.py +0 -0
  152. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
  153. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/dds_cyclone/ros_info.py +0 -0
  154. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
  155. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/echo_parser.py +0 -0
  156. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/echo_stream.py +0 -0
  157. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/node_parsers.py +0 -0
  158. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/parsers.py +0 -0
  159. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/process_runner.py +0 -0
  160. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/process_tree.py +0 -0
  161. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/sample_extras.py +0 -0
  162. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/tf_capture.py +0 -0
  163. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_live/tf_parsers.py +0 -0
  164. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
  165. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/adapter.py +0 -0
  166. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/fixtures.py +0 -0
  167. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/graph_fixtures.py +0 -0
  168. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/node_fixtures.py +0 -0
  169. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/adapters/ros2_mock/tf_fixtures.py +0 -0
  170. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/budget.py +0 -0
  171. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/config/__init__.py +0 -0
  172. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/models/graph_diff.py +0 -0
  173. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/server/__init__.py +0 -0
  174. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/server/app.py +0 -0
  175. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/__init__.py +0 -0
  176. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/bag_service.py +0 -0
  177. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/bag_stats.py +0 -0
  178. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/graph_diff.py +0 -0
  179. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/graph_snapshot.py +0 -0
  180. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/params_file.py +0 -0
  181. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/sample_budget.py +0 -0
  182. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/summaries/__init__.py +0 -0
  183. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/summaries/_common.py +0 -0
  184. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/summaries/clock.py +0 -0
  185. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/summaries/laser_scan.py +0 -0
  186. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/summaries/motion.py +0 -0
  187. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/summaries/rate.py +0 -0
  188. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/services/summaries/sensor_data.py +0 -0
  189. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/telemetry/__init__.py +0 -0
  190. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/telemetry/client.py +0 -0
  191. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/tools/__init__.py +0 -0
  192. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/tools/annotations.py +0 -0
  193. {topicforge-0.8.0 → topicforge-0.9.0}/src/topicforge/tools/schema_compact.py +0 -0
  194. {topicforge-0.8.0 → topicforge-0.9.0}/tests/__init__.py +0 -0
  195. {topicforge-0.8.0 → topicforge-0.9.0}/tests/conftest.py +0 -0
  196. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_064_results.json +0 -0
  197. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_064_truth.json +0 -0
  198. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_071_morning_results.json +0 -0
  199. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_071_morning_truth.json +0 -0
  200. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_071_rerun_results.json +0 -0
  201. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ground_truth/omnisim_071_rerun_truth.json +0 -0
  202. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/image_noarr.yaml +0 -0
  203. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/image_trunc4.yaml +0 -0
  204. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/scan_edge.yaml +0 -0
  205. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/scan_noarr.yaml +0 -0
  206. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/scan_trunc128.yaml +0 -0
  207. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/scan_trunc3.yaml +0 -0
  208. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/string_latched.yaml +0 -0
  209. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_echo/twist.yaml +0 -0
  210. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_info_humble.txt +0 -0
  211. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_info_no_param_services.txt +0 -0
  212. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_info_not_found.txt +0 -0
  213. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_list_duplicates.txt +0 -0
  214. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_list_empty.txt +0 -0
  215. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_list_plain.txt +0 -0
  216. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/node_list_warning_stdout.txt +0 -0
  217. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/param_dump_humble.yaml +0 -0
  218. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/param_dump_secrets.yaml +0 -0
  219. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_node/service_list_types.txt +0 -0
  220. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_clock.txt +0 -0
  221. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_cmd_vel.txt +0 -0
  222. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_parameter_events.txt +0 -0
  223. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_scan.txt +0 -0
  224. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_tf.txt +0 -0
  225. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros2_topic_info_verbose_tf_static.txt +0 -0
  226. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros_discovery/humble_two_nodes.hex +0 -0
  227. {topicforge-0.8.0 → topicforge-0.9.0}/tests/fixtures/ros_discovery/jazzy_two_nodes.hex +0 -0
  228. {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/__init__.py +0 -0
  229. {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/Dockerfile +0 -0
  230. {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/__init__.py +0 -0
  231. {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/bench_params.yaml +0 -0
  232. {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/blocked_node.py +0 -0
  233. {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/faults.py +0 -0
  234. {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/graph_change.py +0 -0
  235. {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/lifecycle_node.py +0 -0
  236. {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/ros2/publisher.py +0 -0
  237. {topicforge-0.8.0 → topicforge-0.9.0}/tests/integration/test_real_bus.py +0 -0
  238. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_analyze_bag_multi_format.py +0 -0
  239. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_bag_omnisim_humble.py +0 -0
  240. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_bag_service.py +0 -0
  241. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_bag_stats.py +0 -0
  242. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_bag_summaries.py +0 -0
  243. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_cdr_decoder.py +0 -0
  244. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_clock_basis.py +0 -0
  245. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_composite_adapter.py +0 -0
  246. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_composite_ghosts.py +0 -0
  247. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_contract_snapshots.py +0 -0
  248. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_contract_v2.py +0 -0
  249. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_cyclone_adapter.py +0 -0
  250. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_cyclone_ros_info.py +0 -0
  251. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_dds_helpers.py +0 -0
  252. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_dds_schemas.py +0 -0
  253. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_discovery_tracker.py +0 -0
  254. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_echo_parser.py +0 -0
  255. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_echo_stream.py +0 -0
  256. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_example_node_spec.py +0 -0
  257. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_field_names.py +0 -0
  258. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_graph_diff.py +0 -0
  259. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_health.py +0 -0
  260. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_history_and_hints.py +0 -0
  261. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_inspector.py +0 -0
  262. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_lifecycle_buffer.py +0 -0
  263. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_live_adapter_graph.py +0 -0
  264. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_live_adapter_parse.py +0 -0
  265. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_live_adapter_subprocess.py +0 -0
  266. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_live_sample_notes.py +0 -0
  267. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_metrics_buffer.py +0 -0
  268. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_mock_adapter.py +0 -0
  269. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_node_logic.py +0 -0
  270. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_node_parsers.py +0 -0
  271. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_peek_bag_samples.py +0 -0
  272. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_process_runner.py +0 -0
  273. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_process_tree.py +0 -0
  274. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_prompts.py +0 -0
  275. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_qos_analyzer.py +0 -0
  276. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_qos_endpoints.py +0 -0
  277. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_qos_fixes.py +0 -0
  278. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_rate_verdicts.py +0 -0
  279. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_restarts_by_node.py +0 -0
  280. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_ros_discovery.py +0 -0
  281. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_sample_options.py +0 -0
  282. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_sample_summary_flow.py +0 -0
  283. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_server_guard.py +0 -0
  284. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_server_info_version.py +0 -0
  285. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_server_lanes.py +0 -0
  286. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_stamps.py +0 -0
  287. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_summaries.py +0 -0
  288. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_summary_schema_compat.py +0 -0
  289. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_telemetry.py +0 -0
  290. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_topic_metrics.py +0 -0
  291. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_urdf.py +0 -0
  292. {topicforge-0.8.0 → topicforge-0.9.0}/tests/test_version_consistency.py +0 -0
  293. {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.7.2...HEAD
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.8.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 sixteen 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.
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 only; see [`docs/CLIENTS.md`](docs/CLIENTS.md#local-http-transport).
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=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjguMCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%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.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).
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`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the eight `ros2` CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `inspect_tf`, `graph_diff`, `sample_messages`, `analyze_bag`) to the CLI and the six DDS tools to the DDS backend; `peek_bag_samples` reads bags without the CLI.
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
- A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Python binding is not on PyPI: build it from eProsima's sources and install it next to TopicForge. There is no `[dds-fast]` extra. Any other value (`rti`, `opensplice`, `coredx`, `intercom`, `opendds`, `dust`) is rejected at startup with an error that lists the valid ones; Cyclone already sees those vendors' participants through standard discovery. Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md); error messages are in [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).
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`, `fast`, `auto`; anything else is rejected |
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`) and `--port N` (default 8765, only with `streamable-http`). The HTTP transport binds `127.0.0.1` only, with DNS-rebinding protection and no authentication; see [docs/CLIENTS.md](docs/CLIENTS.md#local-http-transport).
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`). The Fast DDS adapter (TopicForge joining through the `fastdds` binding) has never run against a bus, and 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
+ - 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
- Next: an opt-in probe to tell a hung writer from a healthy one, wider real-bus validation (Fast DDS, RTI, OpenDDS), and DDS Security. Open work is tracked in [issues](https://github.com/yaniswav/TopicForge/issues).
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 sixteen tools, never argument values |
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.8.0"` | TopicForge server version |
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` or `fastdds` 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).
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, dds_fast, common/ (binding-free logic)
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, product plan
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 sixteen 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.
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 only; see [`docs/CLIENTS.md`](docs/CLIENTS.md#local-http-transport).
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=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjguMCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%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.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).
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`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the eight `ros2` CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `inspect_tf`, `graph_diff`, `sample_messages`, `analyze_bag`) to the CLI and the six DDS tools to the DDS backend; `peek_bag_samples` reads bags without the CLI.
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
- A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Python binding is not on PyPI: build it from eProsima's sources and install it next to TopicForge. There is no `[dds-fast]` extra. Any other value (`rti`, `opensplice`, `coredx`, `intercom`, `opendds`, `dust`) is rejected at startup with an error that lists the valid ones; Cyclone already sees those vendors' participants through standard discovery. Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md); error messages are in [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).
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`, `fast`, `auto`; anything else is rejected |
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`) and `--port N` (default 8765, only with `streamable-http`). The HTTP transport binds `127.0.0.1` only, with DNS-rebinding protection and no authentication; see [docs/CLIENTS.md](docs/CLIENTS.md#local-http-transport).
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`). The Fast DDS adapter (TopicForge joining through the `fastdds` binding) has never run against a bus, and 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
+ - 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
- Next: an opt-in probe to tell a hung writer from a healthy one, wider real-bus validation (Fast DDS, RTI, OpenDDS), and DDS Security. Open work is tracked in [issues](https://github.com/yaniswav/TopicForge/issues).
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 sixteen tools, never argument values |
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.8.0"` | TopicForge server version |
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` or `fastdds` 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).
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, dds_fast, common/ (binding-free logic)
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, product plan
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.8.0" topicforge
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.8.0`) is moved on every release together with the other
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.8.0"`, then use
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.8.0", "topicforge"],
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.8.0" topicforge
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=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjguMCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D)
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=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjguMCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D`
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.8.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)
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.8.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`
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.8.0", "topicforge"],
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.8.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.8.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.8.0", "topicforge"],
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.8.0", "topicforge"]
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.8.0", topicforge]
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 sixteen tools.
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 port can read
301
- your robot graph. It therefore binds `127.0.0.1` only (there is no flag to change that) and
302
- rejects any request whose `Host` or `Origin` is not a loopback name with the served port, which
303
- stops a web page in your browser from reaching it through DNS rebinding. Do not forward the
304
- port to a network interface; use an SSH tunnel instead.
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.0" topicforge --version`.
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`.