topicforge 0.1.0__tar.gz → 0.2.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 (66) hide show
  1. {topicforge-0.1.0 → topicforge-0.2.0}/.gitignore +31 -4
  2. topicforge-0.2.0/CHANGELOG.md +112 -0
  3. {topicforge-0.1.0 → topicforge-0.2.0}/PKG-INFO +97 -13
  4. topicforge-0.2.0/README.md +325 -0
  5. topicforge-0.2.0/docs/DDS_QUICKSTART.md +111 -0
  6. topicforge-0.2.0/docs/MIGRATION_v0.1_to_v0.2.md +140 -0
  7. topicforge-0.2.0/docs/TESTING.md +403 -0
  8. topicforge-0.2.0/docs/pro.md +80 -0
  9. topicforge-0.2.0/docs/product-plan.md +194 -0
  10. topicforge-0.2.0/docs/v0.1.2-action-plan.md +356 -0
  11. {topicforge-0.1.0 → topicforge-0.2.0}/pyproject.toml +14 -2
  12. {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/__init__.py +1 -1
  13. topicforge-0.2.0/src/topicforge/adapters/__init__.py +17 -0
  14. topicforge-0.2.0/src/topicforge/adapters/base.py +105 -0
  15. topicforge-0.2.0/src/topicforge/adapters/common/__init__.py +5 -0
  16. topicforge-0.2.0/src/topicforge/adapters/common/qos_analyzer.py +86 -0
  17. topicforge-0.2.0/src/topicforge/adapters/dds_cyclone/__init__.py +11 -0
  18. topicforge-0.2.0/src/topicforge/adapters/dds_cyclone/adapter.py +110 -0
  19. {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/adapters/ros2_live/adapter.py +151 -13
  20. {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/adapters/ros2_mock/adapter.py +41 -2
  21. {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/adapters/ros2_mock/fixtures.py +117 -1
  22. topicforge-0.2.0/src/topicforge/config/settings.py +147 -0
  23. {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/models/__init__.py +6 -0
  24. topicforge-0.2.0/src/topicforge/models/schemas.py +360 -0
  25. topicforge-0.2.0/src/topicforge/server/app.py +87 -0
  26. topicforge-0.2.0/src/topicforge/services/constants.py +21 -0
  27. topicforge-0.2.0/src/topicforge/services/factory.py +95 -0
  28. {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/services/health.py +1 -1
  29. {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/services/inspector.py +62 -6
  30. topicforge-0.2.0/src/topicforge/telemetry/__init__.py +27 -0
  31. topicforge-0.2.0/src/topicforge/telemetry/client.py +171 -0
  32. topicforge-0.2.0/src/topicforge/tools/handlers.py +291 -0
  33. {topicforge-0.1.0 → topicforge-0.2.0}/tests/conftest.py +1 -1
  34. topicforge-0.2.0/tests/fixtures/csv_echo_imu.txt +12 -0
  35. topicforge-0.2.0/tests/fixtures/csv_echo_pose_multi.txt +9 -0
  36. topicforge-0.2.0/tests/test_cyclone_adapter.py +56 -0
  37. topicforge-0.2.0/tests/test_dds_schemas.py +168 -0
  38. {topicforge-0.1.0 → topicforge-0.2.0}/tests/test_health.py +10 -3
  39. {topicforge-0.1.0 → topicforge-0.2.0}/tests/test_inspector.py +8 -4
  40. topicforge-0.2.0/tests/test_live_adapter_parse.py +276 -0
  41. {topicforge-0.1.0 → topicforge-0.2.0}/tests/test_live_adapter_subprocess.py +92 -0
  42. {topicforge-0.1.0 → topicforge-0.2.0}/tests/test_mock_adapter.py +8 -0
  43. topicforge-0.2.0/tests/test_qos_analyzer.py +155 -0
  44. topicforge-0.2.0/tests/test_telemetry.py +288 -0
  45. {topicforge-0.1.0 → topicforge-0.2.0}/tests/test_tools_integration.py +55 -1
  46. topicforge-0.1.0/CHANGELOG.md +0 -33
  47. topicforge-0.1.0/README.md +0 -245
  48. topicforge-0.1.0/docs/product-plan.md +0 -141
  49. topicforge-0.1.0/src/topicforge/adapters/__init__.py +0 -5
  50. topicforge-0.1.0/src/topicforge/adapters/base.py +0 -44
  51. topicforge-0.1.0/src/topicforge/config/settings.py +0 -68
  52. topicforge-0.1.0/src/topicforge/models/schemas.py +0 -158
  53. topicforge-0.1.0/src/topicforge/server/app.py +0 -43
  54. topicforge-0.1.0/src/topicforge/services/factory.py +0 -39
  55. topicforge-0.1.0/src/topicforge/tools/handlers.py +0 -146
  56. topicforge-0.1.0/tests/test_live_adapter_parse.py +0 -129
  57. {topicforge-0.1.0 → topicforge-0.2.0}/LICENSE +0 -0
  58. {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/__main__.py +0 -0
  59. {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
  60. {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
  61. {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/config/__init__.py +0 -0
  62. {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/server/__init__.py +0 -0
  63. {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/services/__init__.py +0 -0
  64. {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/tools/__init__.py +0 -0
  65. {topicforge-0.1.0 → topicforge-0.2.0}/tests/__init__.py +0 -0
  66. {topicforge-0.1.0 → topicforge-0.2.0}/tests/test_config.py +0 -0
@@ -188,7 +188,34 @@ CLAUDE*.md
188
188
 
189
189
 
190
190
  # -------------------------------------------------------------------------
191
- # Project-local docs and drafts kept out of the package / out of clients
192
- # -------------------------------------------------------------------------
193
- docs/projet-file/
194
- docs/assets/screencast-raw/
191
+ # Project-local docs and drafts kept out of the package / out of clients.
192
+ # Allowlist exceptions: the folder README explains the convention, and
193
+ # `*-spec.md` files are committable specs produced by pack-growth streams
194
+ # (e.g. Stream C of a release plan). Everything else under projet-file/
195
+ # stays local: PDFs, personal market briefs, raw strategy notes.
196
+ # -------------------------------------------------------------------------
197
+ /docs/projet-file/**
198
+ !/docs/projet-file/README.md
199
+ !/docs/projet-file/*-spec.md
200
+ # Traction snapshots are versioned: weekly JSON + summary + folder README.
201
+ # This is the historical curve that informs decision gates G1/G2/G3
202
+ # (product-plan §12). Numeric history must survive across machines.
203
+ !/docs/projet-file/traction/
204
+ !/docs/projet-file/traction/**
205
+ # Launch-post drafts (Reddit, LinkedIn, X). Versioned so the maintainer
206
+ # can diff drafts across releases and keep marketing history auditable.
207
+ # Drafts, not production copy — never the public README.
208
+ !/docs/projet-file/launch-posts/
209
+ !/docs/projet-file/launch-posts/**
210
+ # Audit reports (security, architecture) + audit-followup triage docs.
211
+ # Versioned so audit trails survive across machines and the triage
212
+ # decisions are bisectable per release.
213
+ !/docs/projet-file/*-audit-*.md
214
+ !/docs/projet-file/audit-*.md
215
+ /docs/assets/screencast-raw/
216
+
217
+
218
+ # -------------------------------------------------------------------------
219
+ # Pro tier — paid features kept out of the open-source repo
220
+ # -------------------------------------------------------------------------
221
+ pro/
@@ -0,0 +1,112 @@
1
+ # Changelog
2
+
3
+ All notable changes to TopicForge are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.2.0] - 2026-05-14
11
+
12
+ ### Strategic
13
+
14
+ - **Mono-MCP pivot (2026-05-14).** The 3-to-5-MCP pack draft is collapsed into a 2-product strategy: TopicForge umbrella (this product — covers ROS2 today and grows a DDS observability module starting with v0.2.0), and **DatasetForge** (Vision Dataset Inspector, the standalone second product). DdsForge as a standalone repo is cancelled — its spec is reframed as the TopicForge DDS module spec at `docs/projet-file/mcp-02-spec.md`. Motif: solo-maintenance cost of two parallel repos was the binding constraint, and ROS2 / DDS are the same problem shape (typed pub/sub graph introspection) under the same `MiddlewareAdapter` superset.
15
+
16
+ ### Added
17
+
18
+ - **DDS module — 3 new MCP tools.** `list_participants(domain_id)`, `detect_qos_mismatches(topic)`, `peek_dds_samples(topic, count)`. All read-only ; surface DDS-layer introspection distinct from the ROS2 graph tools. `peek_dds_samples` is deliberately separate from `sample_messages` — different layer, different semantics, distinct tool description so an LLM picks the right one in a mixed setup.
19
+ - **`MiddlewareAdapter` protocol** in `adapters/base.py` — superset of the historical `RosAdapter`. Covers both ROS2 graph methods and the new DDS methods under one contract. `RosAdapter` retained as a backward-compat alias (`RosAdapter = MiddlewareAdapter`).
20
+ - **`CycloneDdsAdapter`** (`adapters/dds_cyclone/`) — lazy-imported only when `TOPICFORGE_DDS_BACKEND=cyclone` and the optional `cyclonedds` extras are installed (`pip install topicforge[dds]`). **v0.2.0 ships a protocol-compliant stub**: the lazy import, `is_available()`, and routing all work ; the 3 DDS methods raise `AdapterError` with a v0.2.x roadmap pointer. The real CycloneDDS discovery (builtin topics, QoS pair extraction, typed reader for samples) lands in a v0.2.x patch. The mock backend (`TOPICFORGE_DDS_BACKEND=mock`, the default) exposes a working DDS surface against deterministic fixtures in the meantime.
21
+ - **3 new Pydantic schemas**: `QosProfile` (Reliability / Durability / History / Deadline at MVP), `ParticipantInfo` (GUID, vendor, hostname, domain_id), `MismatchReport` (incompatible_policies + severity). All frozen, `extra="forbid"`.
22
+ - **Pure analyzer** `adapters/common/qos_analyzer.detect_mismatches` — module-level pure function, testable against synthesized QoS pairs without any DDS middleware installed.
23
+ - **Environment variables**:
24
+ - `TOPICFORGE_DDS_BACKEND` — `mock | cyclone | rti | auto`, default `mock`. The DDS module is opt-in ; existing ROS2-only setups behave unchanged.
25
+ - `TOPICFORGE_DDS_DOMAIN_ID` — DDS domain id observed (0..232), default `0`.
26
+ - **Mock fixtures enriched**: 2 deterministic DDS participants, two-topic scenario (`/dds/well_matched` and `/dds/qos_mismatch`) exercising `detect_qos_mismatches` end-to-end.
27
+ - **`pyproject.toml` extras**: `[dds]` pulls `cyclonedds>=0.10` ; `[all]` aliases `[dds]`. `pip install topicforge` keeps the core + mock only (zero install impact on ROS2-only users).
28
+
29
+ ### Changed
30
+
31
+ - **`TopicInfo` schema soft-breaking.** Three additive optional fields (`reader_count: int | None`, `writer_count: int | None`, `qos_profile: QosProfile | None`) — all default `None`. Producer side: code constructing `TopicInfo` directly is unaffected (defaults compile). **Strict MCP clients that validated v0.1.x responses against the `TopicInfo` schema with `additionalProperties: false` will reject v0.2.0 responses unless their schema is regenerated. Standard MCP clients that read tool descriptions dynamically are unaffected.**
32
+ - **`HealthReport` schema soft-breaking**, same shape. Three additive optional fields (`dds_backend`, `dds_domain_id`, `middleware_available`) with safe defaults (`"none"`, `None`, `False`).
33
+ - **`RosAdapter` renamed to `MiddlewareAdapter`** in `adapters/base.py`. The old name remains as an alias (`RosAdapter = MiddlewareAdapter`) ; existing imports `from topicforge.adapters import RosAdapter` still type-check. The `Ros2CliAdapter.name` value moves from `"live"` to `"ros2_cli"` — internal tag, separate from the MCP-wire `mode_effective` field which keeps its `Literal["mock", "live"]` contract.
34
+ - **`Settings`** gains `dds_backend` and `dds_domain_id` fields with safe defaults (`"mock"`, `0`). Existing `Settings(...)` constructors are unaffected.
35
+ - **`Ros2CliAdapter` DDS methods raise `AdapterError`** with a clear remediation path (`pip install topicforge[dds]` + `TOPICFORGE_DDS_BACKEND=cyclone`). This is the v0.2.0 MVP limitation D6 (single-adapter-at-a-time) ; a composite adapter that delegates per-tool is a v0.2.x roadmap item.
36
+
37
+ ### Internal
38
+
39
+ - `parse_topic_info` and `parse_bag_info` parsers : `mode_effective` kwarg typed as `EffectiveMode` (`Literal["mock", "live"]`) rather than the broader `AdapterName`, cleanly separating the wire-facing mode from the implementation tag.
40
+ - New pytest marker `requires_cyclonedds` for tests that need the SDK. Auto-skips otherwise via `pytest.importorskip`.
41
+
42
+ ### Notes
43
+
44
+ - **`cyclonedds` is optional.** Default installs (`pip install topicforge`) are unchanged from v0.1.2 in dependency footprint. Only `pip install topicforge[dds]` pulls the bindings (`cyclonedds>=0.10`).
45
+ - **No code change to the 5 ROS2 tools** — `health_check`, `list_topics`, `get_topic_info`, `sample_messages`, `analyze_bag` behave identically to v0.1.2. The wire contract (`mode_effective: Literal["mock", "live"]`) is unchanged.
46
+ - **v0.2.0 MVP limitation**: single adapter at a time. Users select ROS2 introspection (default) or DDS observability via `TOPICFORGE_DDS_BACKEND=cyclone`, not both simultaneously. The unselected half raises `AdapterError` with a remediation pointer. A composite adapter delegating per-tool category is a v0.2.x roadmap item.
47
+
48
+ ## [0.1.2] - 2026-05-13
49
+
50
+ ### Fixed
51
+
52
+ - `sample_messages` now returns real publish-time timestamps in live mode for `Header`-stamped messages. The live adapter previously shelled out to `ros2 topic echo --once`, which does not emit timestamps, so `MessageSample.timestamp_ns` was always `0`. The invocation is now `ros2 topic echo --csv --once`, whose flattened CSV exposes `header.stamp.sec` and `header.stamp.nanosec` as the first two columns for any `Header`-stamped message; the new `parse_csv_echo` parser reconstructs `timestamp_ns = sec * 1_000_000_000 + nanosec` and strips those two columns out of the payload. **Headerless message types** (e.g. `std_msgs/String`, `geometry_msgs/Twist`) still return `timestamp_ns=0` — they carry no embedded timestamp. Surfacing the rmw **receive** timestamp (rather than the publish-time `header.stamp`) for arbitrary message types remains a roadmap item tied to the future `rclpy`-backed adapter.
53
+
54
+ ### Added
55
+
56
+ - **`mode_effective` on every tool response (schema, soft-breaking additive).** `TopicInfo`, `SampleResult`, and `BagAnalysis` now carry a required `mode_effective: Literal["mock", "live"]` field. A new `effective_mode` property on the `RosAdapter` protocol is the single source of truth; `Ros2CliAdapter` returns `"live"`, `MockAdapter` returns `"mock"`, services thread it through at result construction time. **Producer side**: Python code constructing these models directly must now supply `mode_effective` — models are `frozen=True, extra="forbid"` with no default. **Client side (over MCP)**: additive — an MCP client consuming JSON sees one extra key per response and is unaffected unless it strictly validates against the v0.1.1 schema with a no-extra-keys assumption.
57
+ - **DdsForge spec** (`docs/projet-file/mcp-02-spec.md`). Strategic draft for MCP 02: safety-first read-only DDS observability across middleware vendors (CycloneDDS OSS, RTI Connext Pro tier). Five tools, `MiddlewareAdapter` protocol, mock + cyclone + rti + auto modes. Reviewer notes appended (2026-05-13): wrong cross-reference in §11 flagged.
58
+ - **DatasetForge spec** (`docs/projet-file/mcp-03-spec.md`). Vision Dataset Inspector spec, re-slotted to MCP 03 after competitive-landscape audit that surfaced zero non-ROS DDS-MCP projects and made DdsForge the stronger MCP 02 candidate. Reviewer notes appended (2026-05-13): contradictory §11 phrasing and two implicitly-resolved open questions flagged.
59
+
60
+ ### Changed
61
+
62
+ - **Safety-first read-only repositioning.** README and `docs/product-plan.md §1` now lead with "read-only by architecture, not by configuration" as the primary identity. Pack candidate list updated: MCP 02 is now DdsForge (non-ROS DDS observability, zero-competition niche); DatasetForge slides to MCP 03. Strategic context in `docs/product-plan.md §4` and §8 (DDS-complete horizon).
63
+ - **Internal API.** `Inspector.sample_messages` now returns a `SampleResult` envelope (previously a `list[MessageSample]`). The MCP-facing tool handler is reduced to a thin pass-through. No effect on the tool's wire-level response shape (handlers already wrapped the list into `SampleResult`), but flagged here for anyone importing `Inspector` directly outside this repo.
64
+
65
+ ### Internal
66
+
67
+ - Docstring fix in `parse_csv_echo`: the example output now shows post-strip payload keys as `col_0`, `col_1` (the parser re-indexes from `col_0` after dropping the two timestamp columns), matching the existing test in `tests/test_live_adapter_parse.py`.
68
+
69
+ ## [0.1.1] - 2026-05-13
70
+
71
+ ### Added
72
+
73
+ - **Opt-in anonymous usage telemetry** behind `TOPICFORGE_TELEMETRY=on` (default: off). When enabled, each MCP tool call emits a single event with six fields only: `tool_name`, `latency_ms`, `mode`, `version`, `session_id` (random UUID per process, never persisted), and `success`. No topic names, message bodies, bag paths, hostnames, or environment data ever leave the process. See the README "Telemetry" section for the full payload contract and opt-out instructions.
74
+ - `src/topicforge/telemetry/` module with `TelemetryClient`, `TelemetryEvent`, and an `instrument()` decorator that wraps tool handlers with timing + emit. When telemetry is off, `instrument()` is the identity function — zero overhead and zero possibility of a network call in the OFF code path.
75
+ - Pluggable `Transport` callable; v0.1.1 ships a structured-log transport. A future S3-backed HTTP endpoint will plug in without touching tool handlers.
76
+ - 29 telemetry tests covering: default-off behaviour, env var parsing (`on`/`1`/`true`/`yes`/`enabled` vs anything else), payload shape and key allowlist, payload privacy (user input never leaks), session id stability and per-process uniqueness, transport-exception isolation, decorator signature preservation, and end-to-end verification that the OFF code path never invokes the transport.
77
+
78
+ ### Changed
79
+
80
+ - `Settings` gained a `telemetry_enabled: bool` field.
81
+ - `build_app(...)` accepts optional `telemetry` and `telemetry_transport` parameters for test injection.
82
+ - `register_tools(...)` now takes a `TelemetryClient`.
83
+ - `.env.example` documents `TOPICFORGE_TELEMETRY`.
84
+ - README adds a `Telemetry` section and updates the Security model note to reflect opt-in telemetry availability.
85
+
86
+ ## [0.1.0] - 2026-05-12
87
+
88
+ Initial MVP release of TopicForge — ROS Topic Inspector & Bag Analyzer MCP server.
89
+
90
+ ### Added
91
+
92
+ - Five read-only MCP tools exposed over FastMCP: `health_check`, `list_topics`, `get_topic_info`, `sample_messages`, and `analyze_bag`.
93
+ - `RosAdapter` protocol in `adapters/base.py` defining the contract every backend implements.
94
+ - Mock adapter (`adapters/ros2_mock/`) with deterministic fixtures modeling a small differential mobile robot equipped with a LIDAR and an RGB camera.
95
+ - Live adapter (`adapters/ros2_live/`) built on subprocess wrappers around the `ros2` CLI, with pure module-level parsers tested independently of any ROS2 install.
96
+ - Three runtime modes selectable via `TOPICFORGE_MODE`: `mock`, `live`, and `auto`. The `auto` resolution lives in `Settings.effective_mode`; the live-to-mock fallback when the adapter cannot start lives in `services/factory.py`.
97
+ - Windows-first cross-platform support: executable resolution via `shutil.which` (handles `ros2.cmd` / `ros2.bat` shims), `subprocess.run` called with absolute paths and never `shell=True`, all filesystem paths via `pathlib.Path`.
98
+ - Pydantic v2 schemas in `models/` configured with `extra="forbid"` and `frozen=True`, returned as the structured payload of every tool.
99
+ - Pytest suite that runs entirely without a ROS2 environment, covering services, mock adapter, and live-adapter parsers.
100
+ - Build, lint, and tooling configuration: Python 3.11+, `mcp >= 1.0.0` (FastMCP), `pydantic >= 2.6`, pytest, ruff, hatchling.
101
+ - Licensed under the MIT License.
102
+
103
+ ### Notes
104
+
105
+ - The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
106
+ - `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
107
+
108
+ [Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.2.0...HEAD
109
+ [0.2.0]: https://github.com/yaniswav/TopicForge/compare/v0.1.2...v0.2.0
110
+ [0.1.2]: https://github.com/yaniswav/TopicForge/compare/v0.1.1...v0.1.2
111
+ [0.1.1]: https://github.com/yaniswav/TopicForge/compare/v0.1.0...v0.1.1
112
+ [0.1.0]: https://github.com/yaniswav/TopicForge/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: topicforge
3
- Version: 0.1.0
3
+ Version: 0.2.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
@@ -40,6 +40,10 @@ Classifier: Programming Language :: Python :: 3.12
40
40
  Requires-Python: >=3.11
41
41
  Requires-Dist: mcp>=1.0.0
42
42
  Requires-Dist: pydantic>=2.6
43
+ Provides-Extra: all
44
+ Requires-Dist: cyclonedds>=0.10; extra == 'all'
45
+ Provides-Extra: dds
46
+ Requires-Dist: cyclonedds>=0.10; extra == 'dds'
43
47
  Provides-Extra: dev
44
48
  Requires-Dist: pytest>=8.0; extra == 'dev'
45
49
  Requires-Dist: ruff>=0.4; extra == 'dev'
@@ -47,23 +51,30 @@ Description-Content-Type: text/markdown
47
51
 
48
52
  # TopicForge
49
53
 
50
- > Stop asking Claude to invent topic names. TopicForge is the MCP server that grounds AI agents in your real ROS2 stack - or a faithful mock when you have no robot at hand.
54
+ [![PyPI version](https://img.shields.io/pypi/v/topicforge.svg)](https://pypi.org/project/topicforge/)
55
+ [![Python versions](https://img.shields.io/pypi/pyversions/topicforge.svg)](https://pypi.org/project/topicforge/)
56
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/yaniswav/TopicForge/blob/main/LICENSE)
57
+ [![Read-only by architecture](https://img.shields.io/badge/safety-read--only_by_architecture-2563eb)](https://github.com/yaniswav/TopicForge#security-model)
51
58
 
52
- TopicForge is a production-minded MCP (Model Context Protocol) server that lets AI agents - such as Claude - inspect ROS2 topics and analyze ROS bag files through a clean, structured tool interface. It is designed for robotics developers, ML/CV engineers working with robot data, and teams that want their AI tooling to *understand* their robotics stack instead of guessing at it.
59
+ > **The safety-first read-only MCP for ROS2 robotics — now with a DDS observability module (v0.2.0).** TopicForge lets AI agents inspect your ROS2 graph, ROS bag files, and (since v0.2.0) the raw DDS layer beneath ROS, without ever publishing back to the bus. The MCP client can see the stack ; it cannot touch it.
60
+
61
+ TopicForge is a production-minded MCP (Model Context Protocol) server that lets AI agents — such as Claude — inspect ROS2 topics, analyze ROS bag files, and (since v0.2.0) observe the raw DDS layer through a clean, structured tool interface. It is read-only by **architecture**, not by configuration: there is no write path to misconfigure, no permission system to audit, no liability conversation to have. The MCP client can see the robot stack; it cannot touch it.
62
+
63
+ This stance matters because the ROS-MCP space is no longer empty — general-purpose ROS-MCP servers exist that let an LLM publish topics, call services, and command robots. That shape is fine for demos; it is untenable for production fleets, defense systems, automotive AUTOSAR Adaptive surfaces, or anything safety-certified. TopicForge is the read-only alternative for those audiences, plus the robotics developers, ML/CV engineers, and teams that want their AI tooling to *understand* their robotics stack without commanding it.
53
64
 
54
65
  ## Why it exists
55
66
 
56
- LLM agents are good at reasoning over text, but ROS2 introspection lives in a CLI + DDS world they cannot directly reach. Without grounding, an LLM will hallucinate topic names, message types, and bag contents. TopicForge bridges that gap with a small, well-typed set of MCP tools:
67
+ LLM agents are good at reasoning over text, but ROS2 introspection lives in a CLI + DDS world they cannot directly reach. Without grounding, an LLM will hallucinate topic names, message types, and bag contents. TopicForge bridges that gap with a small, well-typed set of MCP tools — all read-only, all returning frozen Pydantic schemas that a downstream agent can parse without ambiguity:
57
68
 
58
69
  | Tool | Purpose |
59
70
  | ----------------- | ------------------------------------------------------ |
60
71
  | `health_check` | Environment & mode introspection |
61
72
  | `list_topics` | Discover the ROS graph |
62
73
  | `get_topic_info` | Structured info for a single topic |
63
- | `sample_messages` | Peek recent messages on a topic |
74
+ | `sample_messages` | Peek recent messages on a topic (publish-time timestamps for `Header`-stamped types) |
64
75
  | `analyze_bag` | Summarize a `.mcap` / `.db3` / `.bag` recording |
65
76
 
66
- Outputs are structured, JSON-serializable, and stable across runtime modes - they look the same whether the server is talking to a real robot or to its built-in mock fixtures.
77
+ Outputs are structured, JSON-serializable, and stable across runtime modes - they look the same whether the server is talking to a real robot or to its built-in mock fixtures. Every response carries a `mode_effective` field (`"live"` or `"mock"`) so a downstream LLM can tell a real graph from the demo fixtures without re-reading `health_check`.
67
78
 
68
79
  ## 30-second demo without ROS2
69
80
 
@@ -177,6 +188,31 @@ TOPICFORGE_MODE=live python -m topicforge
177
188
 
178
189
  TopicForge invokes the `ros2` CLI under the hood, so it does **not** require `rclpy` to be importable. This keeps the live adapter portable across ROS2 distros.
179
190
 
191
+ ### DDS support (v0.2.0+)
192
+
193
+ Beyond ROS2 graph introspection, TopicForge can also observe a raw DDS bus directly — useful for non-ROS DDS stacks (defense, aerospace, automotive AUTOSAR Adaptive, industrial integration) and for diagnosing why a ROS2 subscriber isn't receiving when the graph says it should. Same safety-first contract : read-only by **architecture**, never publishes back to the bus.
194
+
195
+ Install the optional DDS extras and select a backend :
196
+
197
+ ```bash
198
+ pip install topicforge[dds]
199
+ TOPICFORGE_MODE=live TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
200
+ ```
201
+
202
+ Three new MCP tools (in addition to the five ROS2 tools above) :
203
+
204
+ | Tool | Purpose |
205
+ | ----------------------- | -------------------------------------------------------------------------------- |
206
+ | `list_participants` | DDS participants discovered on a domain, with vendor and hostname |
207
+ | `detect_qos_mismatches` | Reader/writer QoS incompatibilities preventing communication on a topic |
208
+ | `peek_dds_samples` | Recent samples on a raw DDS topic (distinct from `sample_messages` on ROS2 graph)|
209
+
210
+ **v0.2.0 limitation.** TopicForge selects one adapter at a time. With `TOPICFORGE_DDS_BACKEND=cyclone`, the 3 DDS tools work and the 5 ROS2 tools raise a clear `AdapterError` — and vice versa with the default `TOPICFORGE_DDS_BACKEND=mock`. The mock backend exposes all 8 tools against deterministic fixtures for local development and tests. Real CycloneDDS discovery (builtin topics, QoS pair extraction, typed reader for samples) ships as a v0.2.x patch ; the v0.2.0 `CycloneDdsAdapter` is a protocol-compliant stub that raises a clear roadmap message. A composite adapter delegating per-tool is a v0.2.x roadmap item.
211
+
212
+ `RTI Connext` and additional vendors will be available in the Pro tier (see `docs/pro.md`).
213
+
214
+ **Full 5-minute walkthrough** — including the canonical QoS-mismatch debugging scenario, the single-adapter limitation matrix, and troubleshooting — lives in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md).
215
+
180
216
  ### Configure with Claude Desktop
181
217
 
182
218
  Add to your `claude_desktop_config.json`:
@@ -224,14 +260,61 @@ make check # both, plus tests (CI bundle)
224
260
 
225
261
  ## Configuration reference
226
262
 
227
- | Variable | Default | Description |
228
- | ------------------------ | ------- | ----------------------------------------------------------------- |
229
- | `TOPICFORGE_MODE` | `auto` | `mock`, `live`, or `auto` |
230
- | `TOPICFORGE_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
231
- | `TOPICFORGE_ROS2_BIN` | `ros2` | Name (or path) of the ROS2 CLI binary |
263
+ | Variable | Default | Description |
264
+ | --------------------------- | ------- | ----------------------------------------------------------------------------- |
265
+ | `TOPICFORGE_MODE` | `auto` | `mock`, `live`, or `auto` |
266
+ | `TOPICFORGE_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
267
+ | `TOPICFORGE_ROS2_BIN` | `ros2` | Name (or path) of the ROS2 CLI binary |
268
+ | `TOPICFORGE_TELEMETRY` | `off` | Opt-in anonymous usage telemetry. See [Telemetry](#telemetry). |
269
+ | `TOPICFORGE_DDS_BACKEND` | `mock` | DDS module backend: `mock`, `cyclone`, `rti`, or `auto`. See [DDS support](#dds-support-v020). |
270
+ | `TOPICFORGE_DDS_DOMAIN_ID` | `0` | DDS domain id observed (0..232) when a DDS backend is active. |
232
271
 
233
272
  See [`.env.example`](.env.example).
234
273
 
274
+ ## Telemetry
275
+
276
+ TopicForge ships an **opt-in, anonymous, minimal** telemetry hook. It is **off by default** and the OFF code path performs **zero network calls** — pinned by a unit test (`tests/test_telemetry.py::test_build_app_off_makes_no_transport_calls`).
277
+
278
+ ### How to opt in
279
+
280
+ ```bash
281
+ TOPICFORGE_TELEMETRY=on python -m topicforge
282
+ # Windows PowerShell: $env:TOPICFORGE_TELEMETRY="on"; python -m topicforge
283
+ ```
284
+
285
+ Accepted on-values: `on`, `1`, `true`, `yes`, `enabled` (case-insensitive). Anything else — including unset — keeps telemetry off.
286
+
287
+ ### How to opt out
288
+
289
+ Unset the variable, set it to `off`, or just don't touch it. Opt-out is the default.
290
+
291
+ ### Exactly what is sent
292
+
293
+ When telemetry is on, each MCP tool call emits a single event with **only** these six fields:
294
+
295
+ | Field | Example | Notes |
296
+ | ----------------- | ---------------- | ---------------------------------------------------------------------- |
297
+ | `tool_name` | `"list_topics"` | One of the five MVP tools — never argument values. |
298
+ | `latency_ms` | `12.34` | Wall-clock duration of the handler, rounded to 2 decimals. |
299
+ | `mode` | `"mock"` | Effective runtime mode: `mock` or `live`. |
300
+ | `version` | `"0.1.2"` | TopicForge server version. |
301
+ | `session_id` | `"a1b2c3…"` | Random UUID generated per process. Never persisted, never re-used. |
302
+ | `success` | `true` | Whether the handler returned (true) or raised (false). |
303
+
304
+ ### What is **never** sent
305
+
306
+ - Topic names, message types, message payloads
307
+ - Bag file paths or bag contents
308
+ - Hostnames, usernames, IP addresses, ROS distro, environment variables
309
+ - Stack traces, error messages, or any free-form text
310
+ - Any persistent identifier — `session_id` is regenerated on every server start
311
+
312
+ The payload shape is fenced by `tests/test_telemetry.py::test_payload_contains_only_whitelisted_keys`. Adding a field there requires a matching change in this section.
313
+
314
+ ### Where the code lives
315
+
316
+ The complete telemetry implementation is in [`src/topicforge/telemetry/`](src/topicforge/telemetry/) — read it in under five minutes. The default transport is a structured log line (no HTTP endpoint yet); a future S3-backed endpoint will plug into the same `Transport` callable without touching tool handlers.
317
+
235
318
  ## Security model
236
319
 
237
320
  TopicForge is designed for **local trust**: it runs as a subprocess of your MCP client (Claude Desktop, Claude Code) on a machine you control, and inspects your own ROS2 graph or your own bag files. It is not hardened for adversarial inputs.
@@ -239,13 +322,13 @@ TopicForge is designed for **local trust**: it runs as a subprocess of your MCP
239
322
  - `TOPICFORGE_ROS2_BIN` accepts an arbitrary path - if you point it at a malicious binary, TopicForge will execute it. Treat the variable the way you treat `PATH`.
240
323
  - `analyze_bag` opens whatever path the MCP client passes (no workspace isolation, no symlink restriction). The threat model assumes the client is your trusted agent acting on your behalf.
241
324
  - All `ros2` CLI invocations use `subprocess.run` with an argument list - never `shell=True`. Topic names are validated against a strict allowlist (`^/[A-Za-z0-9_/]+$`) before being passed to the CLI.
242
- - No outbound network calls. No telemetry in v0.1.0 (opt-in usage metrics are on the Phase 1 roadmap).
325
+ - No outbound network calls by default. Since v0.1.1, opt-in anonymous usage telemetry is available behind `TOPICFORGE_TELEMETRY=on` — see [Telemetry](#telemetry) for the exact payload and opt-out instructions. When off (the default), the OFF code path is a verified no-op.
243
326
 
244
327
  Before exposing TopicForge to *untrusted* MCP clients (hosted endpoints, shared environments), add path isolation and revisit the `TOPICFORGE_ROS2_BIN` policy.
245
328
 
246
329
  ## MVP limitations
247
330
 
248
- - `sample_messages` in live mode uses `ros2 topic echo --once` with a short timeout; topics with no current publisher will return an empty sample.
331
+ - `sample_messages` in live mode uses `ros2 topic echo --csv --once` with a short timeout; topics with no current publisher will return an empty sample. `MessageSample.timestamp_ns` is the message's `header.stamp` (publish time) for `Header`-stamped messages and `0` for headerless types (`std_msgs/String`, `geometry_msgs/Twist`, …); surfacing the rmw receive timestamp for arbitrary types waits on the future `rclpy`-backed adapter.
249
332
  - `sample_messages` silently clamps `count` to 50 to keep tool output bounded; requests for more than 50 messages return at most 50 (the `SampleResult.count` field reflects what was actually returned).
250
333
  - `analyze_bag` in live mode shells out to `ros2 bag info` and parses its text output. Deep anomaly detection is mock-only for now.
251
334
  - No streaming / push subscriptions in the MVP. Tools are strictly request/response.
@@ -258,6 +341,7 @@ See [`docs/product-plan.md`](docs/product-plan.md) for the full product trajecto
258
341
  Near-term additions on the bench:
259
342
 
260
343
  - `rclpy`-backed live adapter for faster & richer sampling
344
+ - DDS observability module (Cyclone DDS first as an `extras` install — `pip install topicforge[dds]` —, RTI Connext later in the Pro tier). Generalizes the existing `RosAdapter` to a `MiddlewareAdapter` protocol. Same safety-first read-only contract.
261
345
  - URDF inspector / validator MCP tools
262
346
  - Bag anomaly detection (clock jumps, gaps, dropped frames, TF tree health)
263
347
  - Dataset export helpers (rosbag → COCO / HF Datasets)