Skip to content

feat: OpenClaw foundation β€” MCP-first universal runtime#34

Merged
edgarriba merged 49 commits intomainfrom
feat/openclaw-foundation
Feb 27, 2026
Merged

feat: OpenClaw foundation β€” MCP-first universal runtime#34
edgarriba merged 49 commits intomainfrom
feat/openclaw-foundation

Conversation

@edgarriba
Copy link
Member

@edgarriba edgarriba commented Feb 16, 2026

Summary

Refactors bubbaloop into an MCP-first universal runtime where AI agents (OpenClaw, Claude, etc.) manage physical sensors exclusively through MCP tools.

  • MCP as core: 20 MCP tools across 3 RBAC tiers (Viewer/Operator/Admin), bearer token auth, stdio + HTTP transport, rate limiting, audit logging
  • PlatformOperations trait: Clean abstraction between MCP tools and daemon internals with MockPlatform for testing
  • Daemon simplified: Stripped to pure passive skill runtime β€” lifecycle, health, registry only. No agent rule engine, no autonomous decisions
  • Node SDK: bubbaloop-node-sdk crate reduces node boilerplate from ~300 to ~50 lines
  • Capability discovery: discover_capabilities MCP tool groups nodes by type (sensor, actuator, compute)
  • Full lifecycle via MCP: install_node β†’ start_node β†’ get_node_logs β†’ stop_node β†’ remove_node
  • 35 integration tests: End-to-end through real MCP transport with MockPlatform backend
  • 287 unit tests: Zero clippy warnings
  • Docs overhaul: Install flow, on-disk layout, SDK-first workflow, node marketplace, quickstart

Key architectural changes

Before After
MCP behind feature flag MCP is core (unconditional)
TUI in default build TUI behind tui feature flag
Zenoh queryable API Removed β€” MCP is the API
Agent rule engine in daemon Removed β€” daemon is passive
Nodes embedded in workspace Nodes are standalone repos

Test plan

  • pixi run check β€” cargo check passes
  • pixi run clippy β€” zero warnings
  • pixi run test β€” 287 unit tests pass
  • cargo test --features test-harness --test integration_mcp β€” 35 integration tests pass
  • bubbaloop node install rtsp-camera β€” marketplace install works
  • bubbaloop node start/stop/logs β€” full lifecycle works
  • MCP stdio: bubbaloop mcp --stdio responds to tool calls
  • Camera node verified streaming at 14.5fps on Jetson Orin

πŸ€– Generated with Claude Code

edgarriba and others added 30 commits February 15, 2026 23:31
…dge case tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add list_commands tool, rule management CRUD (add_rule/remove_rule/
update_rule), enriched tool descriptions, expanded ServerInfo
instructions, optional mcp section in node.yaml templates, and
Layer 5 + OpenClaw Integration in architecture docs.

17 MCP tools total, daemon-only (Enhanced Option B). Design rationale
in .omc/plans/openclaw-foundation.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Validate node_name on all 9 MCP handlers to prevent Zenoh key
expression injection. Fix silent condition parse failure that could
turn conditional rules into unconditional ones. Add rule name and
trigger pattern validation, MAX_RULES=100 limit, atomic file writes
via tempfile, and sanitized error messages.

New shared validation module with 6 tests. 301 tests passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the ros-z abstraction layer with direct Zenoh API usage across
the entire codebase. This eliminates an unnecessary dependency and
simplifies the messaging layer.

Rust core:
- Remove ros-z from workspace and crate dependencies
- Create local MessageTypeName trait (replaces ros-z MessageTypeInfo)
- Update get_descriptor_for_message to use new trait
- Clean ros-z references from cli/debug.rs

Dashboard:
- Remove ros-z format parsing from zenoh.ts, schema-registry.ts,
  subscription-manager.ts
- Simplify topic handling (no more %-encoding or 0/ prefix)
- Add 46 vanilla Zenoh test cases (536 total)

Templates & docs:
- Add liveliness token to Python node template
- Update all documentation to reference vanilla Zenoh topics
- Update topic format to bubbaloop/{scope}/{machine_id}/{node}/{resource}

34 files changed, 812 insertions, 5609 deletions (net -4797 lines)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add extern_path for Header in rust-node build.rs template so scaffolded
nodes use the self-contained proto pattern. Remove unused @foxglove/ws-protocol
dashboard dependency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comprehensive design document for transforming bubbaloop into an
MCP-first universal runtime. Validated by critic, architect, and
security reviewers (3 parallel Opus agents).

Key decisions:
- MCP as primary control plane (~22 tools), Zenoh as data plane
- Security foundation (Phase 0): auth, RBAC, rate limiting, audit
- Rule engine stays in daemon (real-time reactive system)
- 4-phase migration with dashboard migration before Zenoh API removal
- Ecosystem plan: Node SDK crate, community registry, contribution tiers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
29 bite-sized tasks across 4 phases:
- Phase 0: Security foundation (9 tasks β€” auth, RBAC, validation)
- Phase 1: MCP enhancement (9 tasks β€” PlatformOperations trait, 22 tools, stdio)
- Phase 2: Dashboard migration + test harness (4 tasks β€” integration tests)
- Phase 3: Cleanup + ecosystem (7 tasks β€” remove Zenoh API, TUI feature flag, SDK design)

~160 tests planned. TDD approach throughout. Exact file paths and code.
References design doc: 2026-02-24-mcp-first-refactor-design.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Key corrections based on rmcp, MCP spec, and axum research:
- rmcp stdio requires 'transport-io' feature flag
- MCP spec: OAuth 2.1 OPTIONAL, stdio has NO auth (process boundary)
- RBAC via ServerHandler::call_tool() override (not middleware)
- tower-governor instead of broken tower::limit::RateLimitLayer
- rmcp 0.16.0 upgrade consideration for task lifecycle

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Security fix: prevents remote access to dashboard server.
Per CLAUDE.md convention: bind localhost only, never 0.0.0.0.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Security fix: prevents PATH hijacking when reading node logs.
Adds JOURNALCTL_PATH constant pointing to /usr/bin/journalctl.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CRITICAL security fix: Action::Publish topic and Action::Command node
were completely unvalidated. Adds validate_publish_topic() and applies
validate_node_name() to command actions. Prevents arbitrary Zenoh
topic injection via rule engine.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… machine

Security fixes:
- query_zenoh now requires key_expr to start with 'bubbaloop/' and
  rejects wildcard-only queries, preventing unrestricted Zenoh network
  scanning via MCP.
- send_command and other node operations now use scoped key expressions
  (scope/machine_id) instead of wildcards, preventing cross-machine
  broadcast.
- Rule action execute() receives scope and machine_id for scoped keys.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Auto-generates token at ~/.bubbaloop/mcp-token (0600 perms) on first
daemon start. Uses constant-time comparison to prevent timing attacks.
Actual enforcement via call_tool() override in next task.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three permission levels: viewer (read-only), operator (day-to-day),
admin (system modification). Tool-to-tier mapping defined in
required_tier(). Unknown tools default to admin (least privilege).

Replaces #[tool_handler] macro with manual ServerHandler impl to
intercept call_tool() for RBAC checking before dispatch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Every MCP tool invocation is logged with tool name and key parameters
via log::info!("[MCP] tool=<name> ...") for security audit trail.

Rate limiting configured at 100 requests/minute burst via tower_governor
0.8, applied as an axum layer on the MCP HTTP server. A background
cleanup thread retains only recent rate limit entries every 60 seconds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- RBAC: default to Admin tier (single-user localhost) until Phase 1
  token-based tiers. Fixes query_zenoh being blocked for all callers.
- RBAC: return ErrorData on permission denied (not CallToolResult::success)
  so MCP clients can properly detect authorization failures.
- Rate limiter: replace std::thread::spawn with tokio task that respects
  shutdown signal, preventing thread leaks on daemon restart.
- Rate limiter: replace .unwrap() with .ok_or() for safe error propagation.
- Rate limiter: fix config to per_second(1) with burst_size(100) and
  correct the log message to reflect actual behavior.
- Format: normalize formatting across touched files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…form

Clean abstraction between MCP server and daemon internals. DaemonPlatform
wraps NodeManager+Zenoh for production use. MockPlatform enables contract
testing without external dependencies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MCP server no longer directly accesses Arc<NodeManager> or Arc<Session>.
All operations go through DaemonPlatform which implements
PlatformOperations, enabling future mock-based contract testing.

Replaced direct self.node_manager and self.session calls in all tool
handlers with self.platform.* calls. Removed execute_daemon_command,
zenoh_get_text, and now_ms helper methods (now in DaemonPlatform).
The only remaining direct session access is send_command which needs
a custom Zenoh query with payload via self.platform.session.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enables local AI agent integration via stdin/stdout JSON-RPC. Logs
redirected to ~/.bubbaloop/mcp-stdio.log to avoid corrupting MCP
protocol. No auth on stdio per MCP spec (process boundary provides
trust). Also supports HTTP mode via `bubbaloop mcp -p <port>`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests cover PlatformOperations trait, input validation, RBAC mapping,
and error handling against MockPlatform. No Zenoh or systemd needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New tools: get_stream_info, get_system_status, get_machine_info,
build_node, get_node_schema, get_events, test_rule.
All tools have audit logging and RBAC tier mappings.
Added get_trigger_log() and test_rule() methods to Agent.
Updated RBAC tests to cover all new tool tier assignments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reflects new tool categories: discovery, lifecycle, data, config,
automation, system. Documents dual-plane model (MCP control, Zenoh data)
in agent instructions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All Zenoh API queryables now log a deprecation warning on each call.
These will be removed in Phase 3 after dashboard migration is complete.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Make BubbaLoopMcpServer generic over P: PlatformOperations so tests
can plug in MockPlatform without requiring Zenoh/systemd. The server
defaults to DaemonPlatform for production code.

Key changes:
- Add send_zenoh_query() to PlatformOperations trait, refactoring
  send_command tool to use it instead of accessing session directly
- Make BubbaLoopMcpServer<P> generic with default type parameter
- Add test-harness feature that exposes MockPlatform and enables
  rmcp client for in-process duplex transport testing
- Create tests/integration_mcp.rs with TestHarness that spins up a
  real MCP client-server pair over tokio::io::duplex

The harness exercises the full MCP stack (serialization, RBAC, tool
routing) and is designed to scale to ~30 tests in the next task.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Resolve Cargo.toml, lib.rs, and dashboard merge conflicts
- Increase duplex buffer from 8KB to 64KB for headroom
- Replace .unwrap() with .expect() in test harness call_with_args

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cover nonexistent nodes (start/stop/restart/build), invalid key
expressions (missing prefix, wildcard-only), missing node config,
get_node_manifest, list_commands, and agent-unavailable paths
(add_rule, remove_rule, update_rule, test_rule). Total integration
tests: 35.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Outlines bubbaloop-node-sdk crate: Node trait, NodeConfig derive macro,
boilerplate handling (Zenoh, health, schema, config, shutdown).
Contributors write ~50 lines of business logic instead of ~300.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
zenoh_api.rs removed. All external access now goes through MCP tools.
Daemon retains Zenoh pub/sub service and agent rule engine.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CLI commands still work but log deprecation warnings pointing to
equivalent MCP tools. Core commands (daemon, mcp, doctor, status) unchanged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TUI code gated by --features tui. Not included in default build.
Reduces binary size and dependency count. Dashboard + MCP replace
TUI for normal use; TUI kept for SSH debugging scenarios.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
edgarriba and others added 14 commits February 26, 2026 11:41
MCP is fundamental to bubbaloop's architecture β€” it should always be
available, not opt-in. Removed all #[cfg(feature = "mcp")] gates.
rmcp, schemars, and tower_governor are now unconditional dependencies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…noh pub/sub, enrich manifests

- Delete agent rule engine (1,265 lines) β€” automation belongs in OpenClaw/AI agent layer
- Delete ZenohService (677 lines) β€” MCP replaces Zenoh state pub/sub
- Relocate create_session() to daemon/mod.rs
- Enrich NodeManifest with capabilities, publishes, subscribes, commands, requires, metadata
- Add Capability enum (Sensor, Actuator, Processor, Gateway)
- Add TopicSpec, CommandSpec, Requirements types for self-describing nodes
- Remove agent from daemon startup, MCP server, and all tool handlers
- Remove 6 agent MCP tools (create_automation, list_automations, etc.)
- Simplify daemon: registry + lifecycle + health + MCP only
- Update templates with rich manifest fields
- Update official nodes (openmeteo, rtsp-camera, system-telemetry, network-monitor)
- Create OpenClaw integration: SKILL.md, openclaw.json.example, README.md
- 2,471 lines removed, 249 lines added across 17 files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update daemon doc: "Bubbaloop Skill Runtime" with clear architecture description
- Remove dead duplicate daemon detection (queried deleted ZenohService)
- Update startup logs: "skill runtime started", node count, health monitor
- Update CLAUDE.md: daemon as passive skill runtime, no agent engine
- Update ARCHITECTURE.md: remove agent tools, add "passive runtime" concept
- Update README.md: replace agent section with external automation pattern
- Update ROADMAP.md: mark agent removal, add skill runtime milestone

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add get_cached_manifests() to NodeManager for static manifest collection
- Add get_manifests() to PlatformOperations trait with capability filtering
- Add discover_capabilities MCP tool: group nodes by capability type
- Update get_node_manifest to use cached manifests instead of Zenoh queries
- Add DiscoverCapabilitiesParams for optional capability filter
- Both new tools at Viewer RBAC tier (read-only)
- Update MockPlatform with test manifest data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The get_node_manifest MCP tool now reads from cached manifests via
get_manifests() instead of issuing Zenoh queries. Updated the integration
test assertion to match the new behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ship the Node SDK that reduces node authoring from ~300 lines of
boilerplate to ~50 lines of business logic. The SDK handles Zenoh
session creation (client mode enforced), health heartbeat (5s interval),
schema queryable registration, config loading, signal handling, and
graceful shutdown.

Rust templates now generate SDK-based nodes instead of full-boilerplate
scaffolds. The old 527-line node.rs.template is replaced by a lean
98-line main.rs.template with the Node trait + run_node() pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- CLAUDE.md: add bubbaloop-node-sdk to structure and key files
- skillet-development.md: change "Future: Node SDK" to "Node SDK (Recommended)"
- create-your-first-node.md: show SDK-based scaffold and Node trait
- plugin-development.md: SDK as primary Quick Start path
- README.md: note SDK in Node Lifecycle section
- node-sdk-design.md: mark status as Shipped

All docs now present the SDK as the primary contributor workflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- skillet-development.md: change "Implementation pending" to "Shipped"
- plugin-development.md: replace phantom BubbleNode/ZContext/NodeError API
  with correct patterns and references to skillet-development.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add "Where Nodes Live" section to skillet-development.md explaining the
separate-repo model. Add "Where to Create Your Node" to the getting
started guide. Fix broken node-marketplace.md links. Add
crates/bubbaloop-nodes/ to .gitignore (stale build artifacts). Update
plugin-development.md integration section for standalone repos.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The marketplace guide exists at docs/guides/node-marketplace.md but
was accidentally orphaned when fixing the broken relative path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add install_node and remove_node MCP tools to PlatformOperations trait,
DaemonPlatform, MockPlatform, and MCP server. This enables AI agents
(OpenClaw) to manage the full node lifecycle: install β†’ build β†’ start β†’
stop β†’ remove.

Dead code cleanup:
- Remove unused API_PREFIX constant from TUI client
- Remove deprecated --strict daemon flag and parameter
- Remove stale cameras/inference/openmeteo pixi tasks (nodes moved to
  separate repos)
- Remove stale process-compose.yaml node processes
- Remove legacy TUI task references from pixi.toml
- Clean phantom RBAC entries for unimplemented tools (list_topics,
  doctor, set_node_config, read_sensor, create_node_instance,
  set_system_config)

287 unit tests + 28 integration tests pass, zero clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7 new tests exercising the full MCP transport path through MockPlatform:
- Valid source (local path + GitHub shorthand)
- Empty source rejection
- Shell metacharacter injection prevention (;, |, &)
- Existing node removal with post-removal verification
- Nonexistent node error handling
- Path traversal name validation (../etc/passwd)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- node-marketplace.md: add install flow diagram (7 steps), on-disk
  layout tree, remove stale inference node, add multi-instance section
- quickstart.md: replace stale npm TUI steps with working camera
  install flow (install β†’ configure β†’ instance β†’ verify)
- skillet-development.md: add "How bubbaloop node install Works"
  section with on-disk result and dev symlink pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@edgarriba edgarriba changed the title feat: OpenClaw foundation β€” vanilla Zenoh, MCP server, agent engine feat: OpenClaw foundation β€” MCP-first universal runtime Feb 26, 2026
edgarriba and others added 5 commits February 27, 2026 01:24
…nd simplification

- Fix auth token write-then-chmod race with atomic OpenOptions::mode(0o600)
- Eliminate TOCTOU in load_or_generate_token, refactor to accept path param
- Replace install_node denylist (;|&) with allowlist validation in validation module
- Propagate Zenoh config errors in daemon (was .ok(), now .expect())
- Add max 30 retries to daemon create_session (was infinite loop)
- Deduplicate MCP port parsing in daemon run()
- Document RBAC as deferred (phase-2), add startup warning
- Fix MockPlatform to record command variant in execute_command
- Remove tempfile from prod dependencies (kept in dev-dependencies)
- Log schema query reply failures in node SDK instead of silencing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert discovered string topics to {display, raw} format before merging
with static topics to match the component prop types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dashboard now uses useZenohSubscriptionContext for topic discovery,
so tests need the context mocked to avoid provider-not-found errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The component renders "Select a topic to start receiving data" but the
test expected "No topic selected".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@edgarriba edgarriba merged commit 1d0c851 into main Feb 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant