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.
- {topicforge-0.1.0 → topicforge-0.2.0}/.gitignore +31 -4
- topicforge-0.2.0/CHANGELOG.md +112 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/PKG-INFO +97 -13
- topicforge-0.2.0/README.md +325 -0
- topicforge-0.2.0/docs/DDS_QUICKSTART.md +111 -0
- topicforge-0.2.0/docs/MIGRATION_v0.1_to_v0.2.md +140 -0
- topicforge-0.2.0/docs/TESTING.md +403 -0
- topicforge-0.2.0/docs/pro.md +80 -0
- topicforge-0.2.0/docs/product-plan.md +194 -0
- topicforge-0.2.0/docs/v0.1.2-action-plan.md +356 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/pyproject.toml +14 -2
- {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/__init__.py +1 -1
- topicforge-0.2.0/src/topicforge/adapters/__init__.py +17 -0
- topicforge-0.2.0/src/topicforge/adapters/base.py +105 -0
- topicforge-0.2.0/src/topicforge/adapters/common/__init__.py +5 -0
- topicforge-0.2.0/src/topicforge/adapters/common/qos_analyzer.py +86 -0
- topicforge-0.2.0/src/topicforge/adapters/dds_cyclone/__init__.py +11 -0
- topicforge-0.2.0/src/topicforge/adapters/dds_cyclone/adapter.py +110 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/adapters/ros2_live/adapter.py +151 -13
- {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/adapters/ros2_mock/adapter.py +41 -2
- {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/adapters/ros2_mock/fixtures.py +117 -1
- topicforge-0.2.0/src/topicforge/config/settings.py +147 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/models/__init__.py +6 -0
- topicforge-0.2.0/src/topicforge/models/schemas.py +360 -0
- topicforge-0.2.0/src/topicforge/server/app.py +87 -0
- topicforge-0.2.0/src/topicforge/services/constants.py +21 -0
- topicforge-0.2.0/src/topicforge/services/factory.py +95 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/services/health.py +1 -1
- {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/services/inspector.py +62 -6
- topicforge-0.2.0/src/topicforge/telemetry/__init__.py +27 -0
- topicforge-0.2.0/src/topicforge/telemetry/client.py +171 -0
- topicforge-0.2.0/src/topicforge/tools/handlers.py +291 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/tests/conftest.py +1 -1
- topicforge-0.2.0/tests/fixtures/csv_echo_imu.txt +12 -0
- topicforge-0.2.0/tests/fixtures/csv_echo_pose_multi.txt +9 -0
- topicforge-0.2.0/tests/test_cyclone_adapter.py +56 -0
- topicforge-0.2.0/tests/test_dds_schemas.py +168 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/tests/test_health.py +10 -3
- {topicforge-0.1.0 → topicforge-0.2.0}/tests/test_inspector.py +8 -4
- topicforge-0.2.0/tests/test_live_adapter_parse.py +276 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/tests/test_live_adapter_subprocess.py +92 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/tests/test_mock_adapter.py +8 -0
- topicforge-0.2.0/tests/test_qos_analyzer.py +155 -0
- topicforge-0.2.0/tests/test_telemetry.py +288 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/tests/test_tools_integration.py +55 -1
- topicforge-0.1.0/CHANGELOG.md +0 -33
- topicforge-0.1.0/README.md +0 -245
- topicforge-0.1.0/docs/product-plan.md +0 -141
- topicforge-0.1.0/src/topicforge/adapters/__init__.py +0 -5
- topicforge-0.1.0/src/topicforge/adapters/base.py +0 -44
- topicforge-0.1.0/src/topicforge/config/settings.py +0 -68
- topicforge-0.1.0/src/topicforge/models/schemas.py +0 -158
- topicforge-0.1.0/src/topicforge/server/app.py +0 -43
- topicforge-0.1.0/src/topicforge/services/factory.py +0 -39
- topicforge-0.1.0/src/topicforge/tools/handlers.py +0 -146
- topicforge-0.1.0/tests/test_live_adapter_parse.py +0 -129
- {topicforge-0.1.0 → topicforge-0.2.0}/LICENSE +0 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/__main__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/config/__init__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/server/__init__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/services/__init__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/src/topicforge/tools/__init__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.2.0}/tests/__init__.py +0 -0
- {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
|
-
|
|
194
|
-
|
|
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.
|
|
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
|
-
|
|
54
|
+
[](https://pypi.org/project/topicforge/)
|
|
55
|
+
[](https://pypi.org/project/topicforge/)
|
|
56
|
+
[](https://github.com/yaniswav/TopicForge/blob/main/LICENSE)
|
|
57
|
+
[](https://github.com/yaniswav/TopicForge#security-model)
|
|
51
58
|
|
|
52
|
-
|
|
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
|
|
228
|
-
|
|
|
229
|
-
| `TOPICFORGE_MODE`
|
|
230
|
-
| `TOPICFORGE_LOG_LEVEL`
|
|
231
|
-
| `TOPICFORGE_ROS2_BIN`
|
|
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
|
|
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)
|