topicforge 0.1.0__tar.gz → 0.1.2__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.1.2}/.gitignore +15 -3
- topicforge-0.1.2/CHANGELOG.md +73 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/PKG-INFO +55 -8
- {topicforge-0.1.0 → topicforge-0.1.2}/README.md +54 -7
- topicforge-0.1.2/docs/TESTING.md +403 -0
- topicforge-0.1.2/docs/pro.md +80 -0
- topicforge-0.1.2/docs/product-plan.md +178 -0
- topicforge-0.1.2/docs/v0.1.2-action-plan.md +356 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/pyproject.toml +2 -2
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/__init__.py +1 -1
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/adapters/base.py +12 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/adapters/ros2_live/adapter.py +105 -8
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/adapters/ros2_mock/adapter.py +4 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/adapters/ros2_mock/fixtures.py +6 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/config/settings.py +22 -1
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/models/schemas.py +32 -12
- topicforge-0.1.2/src/topicforge/server/app.py +87 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/services/inspector.py +9 -3
- topicforge-0.1.2/src/topicforge/telemetry/__init__.py +27 -0
- topicforge-0.1.2/src/topicforge/telemetry/client.py +171 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/tools/handlers.py +43 -22
- {topicforge-0.1.0 → topicforge-0.1.2}/tests/conftest.py +1 -1
- topicforge-0.1.2/tests/fixtures/csv_echo_imu.txt +12 -0
- topicforge-0.1.2/tests/fixtures/csv_echo_pose_multi.txt +9 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/tests/test_health.py +10 -3
- {topicforge-0.1.0 → topicforge-0.1.2}/tests/test_inspector.py +8 -4
- topicforge-0.1.2/tests/test_live_adapter_parse.py +276 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/tests/test_live_adapter_subprocess.py +92 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/tests/test_mock_adapter.py +8 -0
- topicforge-0.1.2/tests/test_telemetry.py +288 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/tests/test_tools_integration.py +48 -1
- topicforge-0.1.0/CHANGELOG.md +0 -33
- topicforge-0.1.0/docs/product-plan.md +0 -141
- topicforge-0.1.0/src/topicforge/server/app.py +0 -43
- topicforge-0.1.0/tests/test_live_adapter_parse.py +0 -129
- {topicforge-0.1.0 → topicforge-0.1.2}/LICENSE +0 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/__main__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/adapters/__init__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/config/__init__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/models/__init__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/server/__init__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/services/__init__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/services/factory.py +0 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/services/health.py +0 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/src/topicforge/tools/__init__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/tests/__init__.py +0 -0
- {topicforge-0.1.0 → topicforge-0.1.2}/tests/test_config.py +0 -0
|
@@ -188,7 +188,19 @@ CLAUDE*.md
|
|
|
188
188
|
|
|
189
189
|
|
|
190
190
|
# -------------------------------------------------------------------------
|
|
191
|
-
# Project-local docs and drafts kept out of the package / out of clients
|
|
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.
|
|
192
196
|
# -------------------------------------------------------------------------
|
|
193
|
-
docs/projet-file
|
|
194
|
-
docs/
|
|
197
|
+
/docs/projet-file/**
|
|
198
|
+
!/docs/projet-file/README.md
|
|
199
|
+
!/docs/projet-file/*-spec.md
|
|
200
|
+
/docs/assets/screencast-raw/
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# -------------------------------------------------------------------------
|
|
204
|
+
# Pro tier — paid features kept out of the open-source repo
|
|
205
|
+
# -------------------------------------------------------------------------
|
|
206
|
+
pro/
|
|
@@ -0,0 +1,73 @@
|
|
|
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.1.2] - 2026-05-13
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- `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.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`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.
|
|
19
|
+
- **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.
|
|
20
|
+
- **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.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **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).
|
|
25
|
+
- **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.
|
|
26
|
+
|
|
27
|
+
### Internal
|
|
28
|
+
|
|
29
|
+
- 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`.
|
|
30
|
+
|
|
31
|
+
## [0.1.1] - 2026-05-13
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **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.
|
|
36
|
+
- `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.
|
|
37
|
+
- Pluggable `Transport` callable; v0.1.1 ships a structured-log transport. A future S3-backed HTTP endpoint will plug in without touching tool handlers.
|
|
38
|
+
- 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.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- `Settings` gained a `telemetry_enabled: bool` field.
|
|
43
|
+
- `build_app(...)` accepts optional `telemetry` and `telemetry_transport` parameters for test injection.
|
|
44
|
+
- `register_tools(...)` now takes a `TelemetryClient`.
|
|
45
|
+
- `.env.example` documents `TOPICFORGE_TELEMETRY`.
|
|
46
|
+
- README adds a `Telemetry` section and updates the Security model note to reflect opt-in telemetry availability.
|
|
47
|
+
|
|
48
|
+
## [0.1.0] - 2026-05-12
|
|
49
|
+
|
|
50
|
+
Initial MVP release of TopicForge — ROS Topic Inspector & Bag Analyzer MCP server.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- Five read-only MCP tools exposed over FastMCP: `health_check`, `list_topics`, `get_topic_info`, `sample_messages`, and `analyze_bag`.
|
|
55
|
+
- `RosAdapter` protocol in `adapters/base.py` defining the contract every backend implements.
|
|
56
|
+
- Mock adapter (`adapters/ros2_mock/`) with deterministic fixtures modeling a small differential mobile robot equipped with a LIDAR and an RGB camera.
|
|
57
|
+
- Live adapter (`adapters/ros2_live/`) built on subprocess wrappers around the `ros2` CLI, with pure module-level parsers tested independently of any ROS2 install.
|
|
58
|
+
- 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`.
|
|
59
|
+
- 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`.
|
|
60
|
+
- Pydantic v2 schemas in `models/` configured with `extra="forbid"` and `frozen=True`, returned as the structured payload of every tool.
|
|
61
|
+
- Pytest suite that runs entirely without a ROS2 environment, covering services, mock adapter, and live-adapter parsers.
|
|
62
|
+
- Build, lint, and tooling configuration: Python 3.11+, `mcp >= 1.0.0` (FastMCP), `pydantic >= 2.6`, pytest, ruff, hatchling.
|
|
63
|
+
- Licensed under the MIT License.
|
|
64
|
+
|
|
65
|
+
### Notes
|
|
66
|
+
|
|
67
|
+
- The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
|
|
68
|
+
- `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
|
|
69
|
+
|
|
70
|
+
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.1.2...HEAD
|
|
71
|
+
[0.1.2]: https://github.com/yaniswav/TopicForge/compare/v0.1.1...v0.1.2
|
|
72
|
+
[0.1.1]: https://github.com/yaniswav/TopicForge/compare/v0.1.0...v0.1.1
|
|
73
|
+
[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.
|
|
3
|
+
Version: 0.1.2
|
|
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
|
|
@@ -47,23 +47,25 @@ Description-Content-Type: text/markdown
|
|
|
47
47
|
|
|
48
48
|
# TopicForge
|
|
49
49
|
|
|
50
|
-
>
|
|
50
|
+
> **The safety-first read-only MCP for ROS2 robotics.** TopicForge lets AI agents inspect your ROS2 graph and bag files without ever publishing back to the bus — so your safety officer doesn't flinch when you wire Claude into your robot stack.
|
|
51
51
|
|
|
52
|
-
TopicForge is a production-minded MCP (Model Context Protocol) server that lets AI agents
|
|
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 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.
|
|
53
|
+
|
|
54
|
+
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
55
|
|
|
54
56
|
## Why it exists
|
|
55
57
|
|
|
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:
|
|
58
|
+
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
59
|
|
|
58
60
|
| Tool | Purpose |
|
|
59
61
|
| ----------------- | ------------------------------------------------------ |
|
|
60
62
|
| `health_check` | Environment & mode introspection |
|
|
61
63
|
| `list_topics` | Discover the ROS graph |
|
|
62
64
|
| `get_topic_info` | Structured info for a single topic |
|
|
63
|
-
| `sample_messages` | Peek recent messages on a topic
|
|
65
|
+
| `sample_messages` | Peek recent messages on a topic (publish-time timestamps for `Header`-stamped types) |
|
|
64
66
|
| `analyze_bag` | Summarize a `.mcap` / `.db3` / `.bag` recording |
|
|
65
67
|
|
|
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.
|
|
68
|
+
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
69
|
|
|
68
70
|
## 30-second demo without ROS2
|
|
69
71
|
|
|
@@ -229,9 +231,54 @@ make check # both, plus tests (CI bundle)
|
|
|
229
231
|
| `TOPICFORGE_MODE` | `auto` | `mock`, `live`, or `auto` |
|
|
230
232
|
| `TOPICFORGE_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
|
231
233
|
| `TOPICFORGE_ROS2_BIN` | `ros2` | Name (or path) of the ROS2 CLI binary |
|
|
234
|
+
| `TOPICFORGE_TELEMETRY` | `off` | Opt-in anonymous usage telemetry. See [Telemetry](#telemetry). |
|
|
232
235
|
|
|
233
236
|
See [`.env.example`](.env.example).
|
|
234
237
|
|
|
238
|
+
## Telemetry
|
|
239
|
+
|
|
240
|
+
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`).
|
|
241
|
+
|
|
242
|
+
### How to opt in
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
TOPICFORGE_TELEMETRY=on python -m topicforge
|
|
246
|
+
# Windows PowerShell: $env:TOPICFORGE_TELEMETRY="on"; python -m topicforge
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Accepted on-values: `on`, `1`, `true`, `yes`, `enabled` (case-insensitive). Anything else — including unset — keeps telemetry off.
|
|
250
|
+
|
|
251
|
+
### How to opt out
|
|
252
|
+
|
|
253
|
+
Unset the variable, set it to `off`, or just don't touch it. Opt-out is the default.
|
|
254
|
+
|
|
255
|
+
### Exactly what is sent
|
|
256
|
+
|
|
257
|
+
When telemetry is on, each MCP tool call emits a single event with **only** these six fields:
|
|
258
|
+
|
|
259
|
+
| Field | Example | Notes |
|
|
260
|
+
| ----------------- | ---------------- | ---------------------------------------------------------------------- |
|
|
261
|
+
| `tool_name` | `"list_topics"` | One of the five MVP tools — never argument values. |
|
|
262
|
+
| `latency_ms` | `12.34` | Wall-clock duration of the handler, rounded to 2 decimals. |
|
|
263
|
+
| `mode` | `"mock"` | Effective runtime mode: `mock` or `live`. |
|
|
264
|
+
| `version` | `"0.1.2"` | TopicForge server version. |
|
|
265
|
+
| `session_id` | `"a1b2c3…"` | Random UUID generated per process. Never persisted, never re-used. |
|
|
266
|
+
| `success` | `true` | Whether the handler returned (true) or raised (false). |
|
|
267
|
+
|
|
268
|
+
### What is **never** sent
|
|
269
|
+
|
|
270
|
+
- Topic names, message types, message payloads
|
|
271
|
+
- Bag file paths or bag contents
|
|
272
|
+
- Hostnames, usernames, IP addresses, ROS distro, environment variables
|
|
273
|
+
- Stack traces, error messages, or any free-form text
|
|
274
|
+
- Any persistent identifier — `session_id` is regenerated on every server start
|
|
275
|
+
|
|
276
|
+
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.
|
|
277
|
+
|
|
278
|
+
### Where the code lives
|
|
279
|
+
|
|
280
|
+
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.
|
|
281
|
+
|
|
235
282
|
## Security model
|
|
236
283
|
|
|
237
284
|
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 +286,13 @@ TopicForge is designed for **local trust**: it runs as a subprocess of your MCP
|
|
|
239
286
|
- `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
287
|
- `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
288
|
- 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
|
|
289
|
+
- 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
290
|
|
|
244
291
|
Before exposing TopicForge to *untrusted* MCP clients (hosted endpoints, shared environments), add path isolation and revisit the `TOPICFORGE_ROS2_BIN` policy.
|
|
245
292
|
|
|
246
293
|
## MVP limitations
|
|
247
294
|
|
|
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.
|
|
295
|
+
- `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
296
|
- `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
297
|
- `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
298
|
- No streaming / push subscriptions in the MVP. Tools are strictly request/response.
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
# TopicForge
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> **The safety-first read-only MCP for ROS2 robotics.** TopicForge lets AI agents inspect your ROS2 graph and bag files without ever publishing back to the bus — so your safety officer doesn't flinch when you wire Claude into your robot stack.
|
|
4
4
|
|
|
5
|
-
TopicForge is a production-minded MCP (Model Context Protocol) server that lets AI agents
|
|
5
|
+
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 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.
|
|
6
|
+
|
|
7
|
+
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.
|
|
6
8
|
|
|
7
9
|
## Why it exists
|
|
8
10
|
|
|
9
|
-
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:
|
|
11
|
+
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:
|
|
10
12
|
|
|
11
13
|
| Tool | Purpose |
|
|
12
14
|
| ----------------- | ------------------------------------------------------ |
|
|
13
15
|
| `health_check` | Environment & mode introspection |
|
|
14
16
|
| `list_topics` | Discover the ROS graph |
|
|
15
17
|
| `get_topic_info` | Structured info for a single topic |
|
|
16
|
-
| `sample_messages` | Peek recent messages on a topic
|
|
18
|
+
| `sample_messages` | Peek recent messages on a topic (publish-time timestamps for `Header`-stamped types) |
|
|
17
19
|
| `analyze_bag` | Summarize a `.mcap` / `.db3` / `.bag` recording |
|
|
18
20
|
|
|
19
|
-
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.
|
|
21
|
+
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`.
|
|
20
22
|
|
|
21
23
|
## 30-second demo without ROS2
|
|
22
24
|
|
|
@@ -182,9 +184,54 @@ make check # both, plus tests (CI bundle)
|
|
|
182
184
|
| `TOPICFORGE_MODE` | `auto` | `mock`, `live`, or `auto` |
|
|
183
185
|
| `TOPICFORGE_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
|
184
186
|
| `TOPICFORGE_ROS2_BIN` | `ros2` | Name (or path) of the ROS2 CLI binary |
|
|
187
|
+
| `TOPICFORGE_TELEMETRY` | `off` | Opt-in anonymous usage telemetry. See [Telemetry](#telemetry). |
|
|
185
188
|
|
|
186
189
|
See [`.env.example`](.env.example).
|
|
187
190
|
|
|
191
|
+
## Telemetry
|
|
192
|
+
|
|
193
|
+
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`).
|
|
194
|
+
|
|
195
|
+
### How to opt in
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
TOPICFORGE_TELEMETRY=on python -m topicforge
|
|
199
|
+
# Windows PowerShell: $env:TOPICFORGE_TELEMETRY="on"; python -m topicforge
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Accepted on-values: `on`, `1`, `true`, `yes`, `enabled` (case-insensitive). Anything else — including unset — keeps telemetry off.
|
|
203
|
+
|
|
204
|
+
### How to opt out
|
|
205
|
+
|
|
206
|
+
Unset the variable, set it to `off`, or just don't touch it. Opt-out is the default.
|
|
207
|
+
|
|
208
|
+
### Exactly what is sent
|
|
209
|
+
|
|
210
|
+
When telemetry is on, each MCP tool call emits a single event with **only** these six fields:
|
|
211
|
+
|
|
212
|
+
| Field | Example | Notes |
|
|
213
|
+
| ----------------- | ---------------- | ---------------------------------------------------------------------- |
|
|
214
|
+
| `tool_name` | `"list_topics"` | One of the five MVP tools — never argument values. |
|
|
215
|
+
| `latency_ms` | `12.34` | Wall-clock duration of the handler, rounded to 2 decimals. |
|
|
216
|
+
| `mode` | `"mock"` | Effective runtime mode: `mock` or `live`. |
|
|
217
|
+
| `version` | `"0.1.2"` | TopicForge server version. |
|
|
218
|
+
| `session_id` | `"a1b2c3…"` | Random UUID generated per process. Never persisted, never re-used. |
|
|
219
|
+
| `success` | `true` | Whether the handler returned (true) or raised (false). |
|
|
220
|
+
|
|
221
|
+
### What is **never** sent
|
|
222
|
+
|
|
223
|
+
- Topic names, message types, message payloads
|
|
224
|
+
- Bag file paths or bag contents
|
|
225
|
+
- Hostnames, usernames, IP addresses, ROS distro, environment variables
|
|
226
|
+
- Stack traces, error messages, or any free-form text
|
|
227
|
+
- Any persistent identifier — `session_id` is regenerated on every server start
|
|
228
|
+
|
|
229
|
+
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.
|
|
230
|
+
|
|
231
|
+
### Where the code lives
|
|
232
|
+
|
|
233
|
+
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.
|
|
234
|
+
|
|
188
235
|
## Security model
|
|
189
236
|
|
|
190
237
|
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.
|
|
@@ -192,13 +239,13 @@ TopicForge is designed for **local trust**: it runs as a subprocess of your MCP
|
|
|
192
239
|
- `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`.
|
|
193
240
|
- `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.
|
|
194
241
|
- 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.
|
|
195
|
-
- No outbound network calls
|
|
242
|
+
- 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.
|
|
196
243
|
|
|
197
244
|
Before exposing TopicForge to *untrusted* MCP clients (hosted endpoints, shared environments), add path isolation and revisit the `TOPICFORGE_ROS2_BIN` policy.
|
|
198
245
|
|
|
199
246
|
## MVP limitations
|
|
200
247
|
|
|
201
|
-
- `sample_messages` in live mode uses `ros2 topic echo --once` with a short timeout; topics with no current publisher will return an empty sample.
|
|
248
|
+
- `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.
|
|
202
249
|
- `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).
|
|
203
250
|
- `analyze_bag` in live mode shells out to `ros2 bag info` and parses its text output. Deep anomaly detection is mock-only for now.
|
|
204
251
|
- No streaming / push subscriptions in the MVP. Tools are strictly request/response.
|