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.
Files changed (289) hide show
  1. {topicforge-0.8.0 → topicforge-0.8.1}/CHANGELOG.md +40 -1
  2. {topicforge-0.8.0 → topicforge-0.8.1}/PKG-INFO +5 -5
  3. {topicforge-0.8.0 → topicforge-0.8.1}/README.md +4 -4
  4. {topicforge-0.8.0 → topicforge-0.8.1}/docs/CLIENTS.md +62 -21
  5. {topicforge-0.8.0 → topicforge-0.8.1}/docs/CONTRACT.md +35 -5
  6. {topicforge-0.8.0 → topicforge-0.8.1}/docs/TESTING.md +2 -2
  7. {topicforge-0.8.0 → topicforge-0.8.1}/docs/TOOLS.md +5 -4
  8. {topicforge-0.8.0 → topicforge-0.8.1}/plugin/README.md +1 -1
  9. {topicforge-0.8.0 → topicforge-0.8.1}/plugin/pyproject.toml +2 -2
  10. {topicforge-0.8.0 → topicforge-0.8.1}/pyproject.toml +1 -1
  11. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/__init__.py +1 -1
  12. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/__main__.py +41 -8
  13. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/log_filter.py +5 -3
  14. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/param_diff.py +34 -6
  15. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/qos_scan.py +48 -8
  16. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/tf_tree.py +31 -5
  17. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/composite.py +7 -0
  18. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/adapter.py +57 -8
  19. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/models/nodes.py +1 -1
  20. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/models/schemas.py +9 -0
  21. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/models/tf.py +3 -1
  22. topicforge-0.8.1/src/topicforge/server/http.py +162 -0
  23. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/dds_views.py +14 -6
  24. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/inspector.py +23 -4
  25. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/tools/handlers.py +19 -9
  26. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/detect_qos_mismatches.json +38 -0
  27. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/get_node_info.json +1 -1
  28. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/inspect_tf.json +1 -1
  29. {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/fault_nodes.py +2 -2
  30. {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/run_bench.py +14 -4
  31. topicforge-0.8.1/tests/integration/ros2/sros2_check.py +159 -0
  32. {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/test_live_adapter.py +1 -1
  33. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_agent_eval_tools.py +15 -0
  34. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_friction_fixes.py +22 -0
  35. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_ground_truth_compare.py +5 -3
  36. topicforge-0.8.1/tests/test_http_transport.py +184 -0
  37. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_log_filter.py +26 -2
  38. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_node_tools.py +5 -2
  39. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_param_diff.py +3 -2
  40. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_qos_scan.py +21 -2
  41. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_tf_tools.py +77 -0
  42. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_tf_tree.py +21 -0
  43. topicforge-0.8.0/src/topicforge/server/http.py +0 -75
  44. topicforge-0.8.0/tests/test_http_transport.py +0 -112
  45. {topicforge-0.8.0 → topicforge-0.8.1}/.gitignore +0 -0
  46. {topicforge-0.8.0 → topicforge-0.8.1}/LICENSE +0 -0
  47. {topicforge-0.8.0 → topicforge-0.8.1}/docs/DDS_QUICKSTART.md +0 -0
  48. {topicforge-0.8.0 → topicforge-0.8.1}/docs/EVAL.md +0 -0
  49. {topicforge-0.8.0 → topicforge-0.8.1}/docs/MIGRATION-0.7.md +0 -0
  50. {topicforge-0.8.0 → topicforge-0.8.1}/docs/TROUBLESHOOTING.md +0 -0
  51. {topicforge-0.8.0 → topicforge-0.8.1}/docs/TUTORIEL.md +0 -0
  52. {topicforge-0.8.0 → topicforge-0.8.1}/docs/VALIDATION.md +0 -0
  53. {topicforge-0.8.0 → topicforge-0.8.1}/docs/dds-interop-matrix.md +0 -0
  54. {topicforge-0.8.0 → topicforge-0.8.1}/examples/README.md +0 -0
  55. {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/00_hello_pub_sub/README.md +0 -0
  56. {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/01_who_is_on_the_bus/README.md +0 -0
  57. {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/02_why_cant_they_talk/README.md +0 -0
  58. {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/03_a_node_crashed/README.md +0 -0
  59. {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/04_late_joiner_misses_data/README.md +0 -0
  60. {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/05_reliability_in_code/README.md +0 -0
  61. {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/06_durability_late_joiner_in_code/README.md +0 -0
  62. {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/07_deadline_in_code/README.md +0 -0
  63. {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/08_crash_seen_from_inside/README.md +0 -0
  64. {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/10_lidar_silent_after_driver_swap/README.md +0 -0
  65. {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/11_who_talks_to_whom/README.md +0 -0
  66. {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/12_safety_monitor_dropout/README.md +0 -0
  67. {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/13_deadline_not_offered/README.md +0 -0
  68. {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/14_restart_loop/README.md +0 -0
  69. {topicforge-0.8.0 → topicforge-0.8.1}/examples/dds/README.md +0 -0
  70. {topicforge-0.8.0 → topicforge-0.8.1}/plugin/LICENSE +0 -0
  71. {topicforge-0.8.0 → topicforge-0.8.1}/scripts/agent_eval/README.md +0 -0
  72. {topicforge-0.8.0 → topicforge-0.8.1}/scripts/ground_truth/README.md +0 -0
  73. {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/README.md +0 -0
  74. {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/cyclone_c/README.md +0 -0
  75. {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/cyclone_cpp/README.md +0 -0
  76. {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/cyclone_rust/README.md +0 -0
  77. {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/dust_py/README.md +0 -0
  78. {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/fast_publisher_cpp/README.md +0 -0
  79. {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/fast_py/README.md +0 -0
  80. {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/opensplice_publisher/README.md +0 -0
  81. {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/rti_c/README.md +0 -0
  82. {topicforge-0.8.0 → topicforge-0.8.1}/scripts/integration/publishers/rti_cpp/README.md +0 -0
  83. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/__init__.py +0 -0
  84. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/base.py +0 -0
  85. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/__init__.py +0 -0
  86. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/bag_kind.py +0 -0
  87. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/bag_range.py +0 -0
  88. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/cdr_decoder.py +0 -0
  89. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/dds_helpers.py +0 -0
  90. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/dds_introspection.py +0 -0
  91. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/discovery_tracker.py +0 -0
  92. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/endpoints.py +0 -0
  93. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/lifecycle.py +0 -0
  94. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/metrics_buffer.py +0 -0
  95. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/nodes.py +0 -0
  96. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/qos_analyzer.py +0 -0
  97. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/qos_endpoints.py +0 -0
  98. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/qos_fixes.py +0 -0
  99. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/qos_normalize.py +0 -0
  100. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/ros_discovery.py +0 -0
  101. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/ros_names.py +0 -0
  102. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/spawned_pids.py +0 -0
  103. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/stamps.py +0 -0
  104. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/topic_filter.py +0 -0
  105. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/urdf.py +0 -0
  106. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/common/xtypes.py +0 -0
  107. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
  108. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/dds_cyclone/adapter.py +0 -0
  109. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/dds_cyclone/ros_info.py +0 -0
  110. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/dds_fast/__init__.py +0 -0
  111. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/dds_fast/adapter.py +0 -0
  112. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
  113. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/echo_parser.py +0 -0
  114. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/echo_stream.py +0 -0
  115. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/node_parsers.py +0 -0
  116. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/parsers.py +0 -0
  117. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/process_runner.py +0 -0
  118. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/process_tree.py +0 -0
  119. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/sample_extras.py +0 -0
  120. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/tf_capture.py +0 -0
  121. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_live/tf_parsers.py +0 -0
  122. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
  123. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/adapter.py +0 -0
  124. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/fixtures.py +0 -0
  125. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/graph_fixtures.py +0 -0
  126. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/node_fixtures.py +0 -0
  127. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/adapters/ros2_mock/tf_fixtures.py +0 -0
  128. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/budget.py +0 -0
  129. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/config/__init__.py +0 -0
  130. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/config/settings.py +0 -0
  131. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/constants.py +0 -0
  132. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/models/__init__.py +0 -0
  133. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/models/graph_diff.py +0 -0
  134. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/models/summaries.py +0 -0
  135. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/server/__init__.py +0 -0
  136. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/server/app.py +0 -0
  137. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/server/prompts.py +0 -0
  138. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/__init__.py +0 -0
  139. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/bag_service.py +0 -0
  140. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/bag_stats.py +0 -0
  141. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/factory.py +0 -0
  142. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/graph_diff.py +0 -0
  143. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/graph_snapshot.py +0 -0
  144. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/health.py +0 -0
  145. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/params_file.py +0 -0
  146. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/sample_budget.py +0 -0
  147. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/summaries/__init__.py +0 -0
  148. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/summaries/_common.py +0 -0
  149. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/summaries/clock.py +0 -0
  150. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/summaries/laser_scan.py +0 -0
  151. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/summaries/motion.py +0 -0
  152. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/summaries/rate.py +0 -0
  153. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/services/summaries/sensor_data.py +0 -0
  154. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/telemetry/__init__.py +0 -0
  155. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/telemetry/client.py +0 -0
  156. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/tools/__init__.py +0 -0
  157. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/tools/annotations.py +0 -0
  158. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/tools/guard.py +0 -0
  159. {topicforge-0.8.0 → topicforge-0.8.1}/src/topicforge/tools/schema_compact.py +0 -0
  160. {topicforge-0.8.0 → topicforge-0.8.1}/tests/__init__.py +0 -0
  161. {topicforge-0.8.0 → topicforge-0.8.1}/tests/conftest.py +0 -0
  162. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/_prompts.json +0 -0
  163. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/analyze_bag.json +0 -0
  164. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/get_topic_info.json +0 -0
  165. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/graph_diff.json +0 -0
  166. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/health_check.json +0 -0
  167. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/list_endpoints.json +0 -0
  168. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/list_nodes.json +0 -0
  169. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/list_participants.json +0 -0
  170. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/list_topics.json +0 -0
  171. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/participant_events.json +0 -0
  172. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/peek_bag_samples.json +0 -0
  173. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/peek_dds_samples.json +0 -0
  174. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/sample_messages.json +0 -0
  175. {topicforge-0.8.0 → topicforge-0.8.1}/tests/contract/topic_metrics.json +0 -0
  176. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_064_results.json +0 -0
  177. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_064_truth.json +0 -0
  178. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_071_morning_results.json +0 -0
  179. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_071_morning_truth.json +0 -0
  180. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_071_rerun_results.json +0 -0
  181. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ground_truth/omnisim_071_rerun_truth.json +0 -0
  182. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/image_noarr.yaml +0 -0
  183. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/image_trunc4.yaml +0 -0
  184. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/scan_edge.yaml +0 -0
  185. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/scan_noarr.yaml +0 -0
  186. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/scan_trunc128.yaml +0 -0
  187. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/scan_trunc3.yaml +0 -0
  188. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/string_latched.yaml +0 -0
  189. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_echo/twist.yaml +0 -0
  190. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_info_humble.txt +0 -0
  191. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_info_no_param_services.txt +0 -0
  192. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_info_not_found.txt +0 -0
  193. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_list_duplicates.txt +0 -0
  194. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_list_empty.txt +0 -0
  195. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_list_plain.txt +0 -0
  196. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/node_list_warning_stdout.txt +0 -0
  197. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/param_dump_humble.yaml +0 -0
  198. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/param_dump_secrets.yaml +0 -0
  199. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_node/service_list_types.txt +0 -0
  200. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_clock.txt +0 -0
  201. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_cmd_vel.txt +0 -0
  202. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_parameter_events.txt +0 -0
  203. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_scan.txt +0 -0
  204. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_tf.txt +0 -0
  205. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros2_topic_info_verbose_tf_static.txt +0 -0
  206. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros_discovery/humble_two_nodes.hex +0 -0
  207. {topicforge-0.8.0 → topicforge-0.8.1}/tests/fixtures/ros_discovery/jazzy_two_nodes.hex +0 -0
  208. {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/__init__.py +0 -0
  209. {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/Dockerfile +0 -0
  210. {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/__init__.py +0 -0
  211. {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/bench_params.yaml +0 -0
  212. {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/blocked_node.py +0 -0
  213. {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/entrypoint.sh +0 -0
  214. {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/faults.py +0 -0
  215. {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/graph_change.py +0 -0
  216. {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/ground_truth.py +0 -0
  217. {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/lifecycle_node.py +0 -0
  218. {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/ros2/publisher.py +0 -0
  219. {topicforge-0.8.0 → topicforge-0.8.1}/tests/integration/test_real_bus.py +0 -0
  220. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_analyze_bag_multi_format.py +0 -0
  221. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_bag_omnisim_humble.py +0 -0
  222. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_bag_service.py +0 -0
  223. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_bag_stats.py +0 -0
  224. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_bag_summaries.py +0 -0
  225. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_cdr_decoder.py +0 -0
  226. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_clock_basis.py +0 -0
  227. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_composite_adapter.py +0 -0
  228. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_composite_ghosts.py +0 -0
  229. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_config.py +0 -0
  230. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_contract_shape.py +0 -0
  231. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_contract_snapshots.py +0 -0
  232. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_contract_v2.py +0 -0
  233. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_cyclone_adapter.py +0 -0
  234. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_cyclone_ros_info.py +0 -0
  235. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_dds_cross_vendor.py +0 -0
  236. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_dds_helpers.py +0 -0
  237. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_dds_inactive_reason.py +0 -0
  238. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_dds_introspection.py +0 -0
  239. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_dds_qos_normalization.py +0 -0
  240. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_dds_schemas.py +0 -0
  241. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_discovery_tracker.py +0 -0
  242. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_echo_parser.py +0 -0
  243. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_echo_stream.py +0 -0
  244. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_endpoints.py +0 -0
  245. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_example_node_spec.py +0 -0
  246. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_factory.py +0 -0
  247. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_fast_adapter.py +0 -0
  248. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_field_names.py +0 -0
  249. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_graph_diff.py +0 -0
  250. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_health.py +0 -0
  251. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_history_and_hints.py +0 -0
  252. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_honest_outputs.py +0 -0
  253. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_inspector.py +0 -0
  254. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_lifecycle_buffer.py +0 -0
  255. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_live_adapter_graph.py +0 -0
  256. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_live_adapter_parse.py +0 -0
  257. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_live_adapter_subprocess.py +0 -0
  258. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_live_sample_notes.py +0 -0
  259. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_metrics_buffer.py +0 -0
  260. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_mock_adapter.py +0 -0
  261. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_node_logic.py +0 -0
  262. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_node_parsers.py +0 -0
  263. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_output_schema_validation.py +0 -0
  264. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_peek_bag_samples.py +0 -0
  265. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_process_runner.py +0 -0
  266. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_process_tree.py +0 -0
  267. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_prompts.py +0 -0
  268. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_qos_analyzer.py +0 -0
  269. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_qos_endpoints.py +0 -0
  270. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_qos_fixes.py +0 -0
  271. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_rate_verdicts.py +0 -0
  272. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_restarts_by_node.py +0 -0
  273. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_ros_discovery.py +0 -0
  274. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_sample_options.py +0 -0
  275. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_sample_summary_flow.py +0 -0
  276. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_server_guard.py +0 -0
  277. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_server_info_version.py +0 -0
  278. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_server_lanes.py +0 -0
  279. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_stamps.py +0 -0
  280. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_summaries.py +0 -0
  281. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_summary_schema_compat.py +0 -0
  282. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_telemetry.py +0 -0
  283. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_tool_annotations.py +0 -0
  284. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_tool_descriptions.py +0 -0
  285. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_tools_integration.py +0 -0
  286. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_topic_metrics.py +0 -0
  287. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_urdf.py +0 -0
  288. {topicforge-0.8.0 → topicforge-0.8.1}/tests/test_version_consistency.py +0 -0
  289. {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.7.2...HEAD
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.0
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 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=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`) 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).
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.0"` | TopicForge server version |
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 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=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`) 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).
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.0"` | TopicForge server version |
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.0" topicforge
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.0`) is moved on every release together with the other
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.0"`, then use
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.0", "topicforge"],
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.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.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=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjguMCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D)
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=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjguMCIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D`
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.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.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.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.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.0", "topicforge"],
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.0",
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.0"
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.0", "topicforge"],
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.0", "topicforge"]
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.0", topicforge]
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 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.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` is a topic only: a URDF held solely as a `robot_description` parameter is not
1115
- read (`get_node_info` cuts long values) and gives `urdf_source` `none`; reading it is 0.8.1.
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. Not covered: a
1218
- bus with SROS2 enabled (a SROS2 cell is planned for 0.8.1) and RTI.
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.0
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.0 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.
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 | Counts left out: same, live_only. |
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
- A robot description held only as a node parameter is not read: `urdf_source` is
568
- `none` and `urdf_note` says why. A link on a non-fixed joint has a TF frame only
569
- while `/joint_states` is published.
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.0). 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`.
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.0"
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.0",
12
+ "topicforge==0.8.1",
13
13
  ]
14
14
 
15
15
  [tool.uv]
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "topicforge"
7
- version = "0.8.0"
7
+ version = "0.8.1"
8
8
  description = "ROS Topic Inspector & Bag Analyzer MCP server for AI agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,5 +1,5 @@
1
1
  """TopicForge: ROS Topic Inspector & Bag Analyzer MCP server."""
2
2
 
3
- __version__ = "0.8.0"
3
+ __version__ = "0.8.1"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -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 DEFAULT_HTTP_PORT, LOOPBACK_HOST, serve_http
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 only."
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, a local HTTP endpoint bound to "
42
- f"{LOOPBACK_HOST} only (no authentication; reach it through an SSH tunnel)."
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
- if args.port is not None and args.transport != "streamable-http":
59
- parser.error("--port only applies to --transport streamable-http")
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
- Raises `AdapterError` for a name outside `debug`, `info`, `warn`, `error`, `fatal`.
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
- return LOG_LEVELS[min_log_level]
44
- except (KeyError, TypeError):
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