The MCP server is implemented and served by cmd/gateway at /mcp.
This DOC-06 section freezes only the narrow M2 MCP slice shipped by
ISSUE-GW-04:
ebus.v1.bus.summary.getebus.v1.bus.messages.listebus.v1.bus.periodicity.list
This DOC-06 freeze covers the M2 ebus.v1.bus.* tools only. Shared
watch-summary MCP behavior is frozen separately in DOC-09.
ebus.v1.bus.*stays separate from semantic MCP tools because it exposes bus-observability and transport-capability data rather than protocol-agnostic semantic state.- The
ebus.v1.bus.*tools are bounded summary/list surfaces. They do not construct or expose unbounded traffic history on demand. - All three tools accept the standard optional
consistencyargument. The two list tools also accept an optional positive integerlimit. - Omitting
limitreturns all currently retained records; it does not upgrade the surface into an unbounded dump. - These tools appear in
tools/listonly when the runtime wires a real bus observability provider into the MCP server.
- Top-level
statusremains part of the public contract for all three tools so passive capability, warmup, degraded, and timing-quality state stay explicit even when retained history still exists. - When passive support is unavailable or still warming up, the surface reports
that state explicitly through
status.capability,status.warmup, andstatus.timing_quality; it does not silently degrade those conditions into empty lists or fake zero-valued timing claims. - Busy-time and periodicity timing quality may be
estimatedorunavailableon the current transport stack. The MCP surface may not imply exact wire timestamps when the runtime does not have them.
All three tools return the standard Helianthus MCP envelope:
meta.contractmeta.consistencymeta.data_timestampmeta.data_hashdataerror
consistency.mode supports:
LIVE(default when omitted)SNAPSHOTwith requiredsnapshot_id
ebus.v1.bus.summary.get- arguments: optional
consistency
- arguments: optional
ebus.v1.bus.messages.list- arguments: optional positive integer
limit, optionalconsistency
- arguments: optional positive integer
ebus.v1.bus.periodicity.list- arguments: optional positive integer
limit, optionalconsistency
- arguments: optional positive integer
Returns the current bus-observability summary snapshot.
| Field | Type | Meaning |
|---|---|---|
status |
object | Current transport class, capability, warmup, timing-quality, and degraded state. |
messages |
object | Bounded recent-message summary: count, capacity. |
periodicity |
object | Bounded periodicity summary: count, capacity. |
counters |
object | Overflow counters: series_budget_overflow_total, periodicity_budget_overflow_total. |
status.capability fields:
| Field | Meaning |
|---|---|
active_supported |
The runtime can observe its own active traffic on this transport. |
passive_supported |
The runtime/transport topology supports the passive observe-first path. |
broadcast_supported |
The passive path is able to serve broadcast-derived observe-first behavior. |
passive_available |
true only when passive capability has reached available. |
passive_state |
One of unavailable, warming_up, available. |
passive_reason |
Present when the passive path is unavailable. Current runtime reasons are startup_timeout, reconnect_timeout, socket_loss, flap_dampened, unsupported_or_misconfigured, and capability_withdrawn. |
endpoint_state |
Passive tap endpoint lifecycle state from the runtime. |
tap_connected |
Current passive tap connection flag. |
status.warmup fields:
| Field | Meaning |
|---|---|
state |
Matches the bounded state set unavailable, warming_up, available. |
blocker |
Current dominant warmup blocker when the path is not fully ready. |
elapsed_seconds |
Current warmup-session elapsed time when the runtime has one. |
completed_transactions |
Number of passive completed transactions counted for the current session. |
required_transactions |
Threshold required for normal warmup completion. |
completion_mode |
Current/last completion mode when known. |
status.timing_quality fields:
| Field | Meaning |
|---|---|
active |
Current timing quality for active-path observations. |
passive |
Current timing quality for passive-path observations. |
busy |
Current timing quality for busy-time accounting. |
periodicity |
Current timing quality for periodicity accounting. |
Current timing-quality values evidenced on the merged GW-04 runtime are
estimated and unavailable.
status.degraded fields:
| Field | Meaning |
|---|---|
active |
true when degraded reasons are present. |
reasons |
Bounded degraded-reason list. Current MCP output includes passive unavailability reasons and dedup_degraded. |
Returns a bounded slice of recent bus-message records from the observe-first store.
| Field | Type | Meaning |
|---|---|---|
status |
object | Same status model as bus.summary.get. |
count |
integer | Number of retained recent-message records in the bounded store. |
capacity |
integer | Store capacity for recent-message retention. |
items |
array | The most recent retained records, optionally truncated by limit. |
items[] fields:
| Field | Type | Meaning |
|---|---|---|
scope |
string | Runtime traffic scope classification such as active or passive. |
family |
string | Family classifier such as B509, B524, or other. |
frame_type |
string | Frame-shape classifier from the observability store. |
outcome |
string | Runtime outcome classifier such as success or timeout. |
observed_at |
RFC3339 timestamp | Gateway observation timestamp. |
source_address |
integer | Source device address. |
target_address |
integer | Target device address. |
request_len |
integer | Request frame length in bytes. |
response_len |
integer | Response frame length in bytes. |
Ordering and bounds:
countandcapacitydescribe the whole bounded store, not just the returned slice.- If
limitis present, the gateway returns the newest retainedlimitrecords. - Returned records preserve retained order within that newest suffix.
Returns a bounded slice of periodicity summaries from the observe-first store.
| Field | Type | Meaning |
|---|---|---|
status |
object | Same status model as bus.summary.get. |
count |
integer | Number of retained periodicity tuples in the bounded store. |
capacity |
integer | Configured periodicity-tuple capacity. |
items |
array | The most recent retained periodicity tuples, optionally truncated by limit. |
items[] fields:
| Field | Type | Meaning |
|---|---|---|
source_bucket |
string | Normalized source bucket key used by the store. |
target_bucket |
string | Normalized target bucket key used by the store. |
primary |
integer | Primary opcode byte. |
secondary |
integer | Secondary opcode byte. |
family |
string | Family classifier such as B509 or B524. |
state |
string | Per-tuple periodicity state. The merged GW-04 runtime currently emits warming_up and available. |
last_seen |
RFC3339 timestamp | Most recent observation for this tuple. |
sample_count |
integer | Number of samples accepted into the tuple summary. |
last_interval |
string | Last observed interval, when available. |
mean_interval |
string | Mean interval, when available. |
min_interval |
string | Minimum interval, when available. |
max_interval |
string | Maximum interval, when available. |
Periodicity semantics:
- Per-tuple
statestays local to that tuple. Overall passive availability and timing-quality state still come from the top-levelstatus. - Retained periodicity entries may still be returned while overall passive
capability is unavailable. Consumers must not treat a non-empty
itemslist as proof that the passive path is currently healthy. - Interval fields are optional. The surface omits them when the runtime does not yet have a value to publish.
Shared watch-summary details are frozen in watch-summary.md.
This section captures the MCP-specific surface only.
Tool inventory:
ebus.v1.watch.summary.get- arguments: optional
consistency
- arguments: optional
MCP-specific invariants:
- Tool registration is provider-gated: the tool appears in
tools/listonly when the runtime wires a watch-summary provider (shadow cache lane). - Without provider wiring, tool calls fail as unknown tool.
consistency.modefollows the standard MCP rules (LIVEby default;SNAPSHOTwith requiredsnapshot_id).- Payload fields use snake_case and mirror the shared watch-summary schema.
- No GraphQL
busSummary,busMessages, orbusPeriodicityfreeze exists in this lane. - No Portal-specific watch-summary surface is frozen here.
- No unbounded traffic dump or timeline stream is part of this M2 bus contract.
- No exact wire-timestamp guarantee is frozen for current transports.
- No numeric busy-ratio MCP payload is frozen here; in M2 the MCP surface freezes summary/list semantics and explicit timing-quality state only.
- Runtime lane: Project-Helianthus/helianthus-ebusgateway#376
- Merged implementation PR: Project-Helianthus/helianthus-ebusgateway#377
- Merge commit:
3daf4beed9d6406f7af52869eea1c53ef14f2f62 - Gateway workspace proof artifact (outside this docs repo; from a
Project-Helianthus/helianthus-ebusgatewaycheckout):helianthus-ebusgateway/results-matrix-ha/20260312T175648Z-pr377-gw04-26ee758-passive-p01-p06-recovery/index.jsonwithP01..P06 = pass - Gateway workspace recovery probe reference (outside this docs repo; from the same
helianthus-ebusgatewaycheckout):helianthus-ebusgateway/results-matrix-ha/20260312T175250Z-pr377-gw04-26ee758-recovery/full88-probe-t01-after-adapter-reboot/index.jsonremainedblocked-infrawithinfra_reason=adapter_no_signal, and the PR merged under explicit owner override after the official addon/runtime restore was re-verified cleanly - Gateway repo code/test proof references (external to this docs repo, at merge commit
3daf4beed9d6406f7af52869eea1c53ef14f2f62):- MCP tool inventory and provider wiring: Project-Helianthus/helianthus-ebusgateway/cmd/gateway/mcp_bus_observability_integration_test.go
- MCP payload shape and limit/snapshot behavior: Project-Helianthus/helianthus-ebusgateway/mcp/server_test.go
This contract is wrong if later review proves any of the following:
ebus.v1.bus.*appears intools/listwithout a real bus observability provider being wired- either list tool fabricates unbounded history when
limitis omitted - passive unavailability or warmup becomes implicit instead of remaining visible
in top-level
status - busy-time or periodicity timing precision is implied without explicit
timing_quality
Summary response fragment:
{
"data": {
"status": {
"capability": {
"passive_state": "unavailable",
"passive_reason": "unsupported_or_misconfigured"
},
"timing_quality": {
"passive": "unavailable",
"busy": "unavailable",
"periodicity": "unavailable"
}
},
"messages": {
"count": 1,
"capacity": 1024
}
}
}Messages response fragment:
{
"data": {
"count": 3,
"capacity": 16,
"items": [
{
"scope": "passive",
"family": "B524",
"frame_type": "broadcast",
"outcome": "success"
}
]
}
}Periodicity response fragment:
{
"data": {
"count": 2,
"capacity": 8,
"items": [
{
"family": "B524",
"state": "available",
"last_interval": "30s",
"mean_interval": "29s"
}
]
}
}Note: This inventory reflects the current known tool surface. The gateway may expose additional tools discovered via the MCP tools/list endpoint.
- Core stable (
ebus.v1.*)ebus.v1.runtime.status.getebus.v1.adapter_info.getebus.v1.registry.devices.listebus.v1.registry.devices.getebus.v1.registry.planes.listebus.v1.registry.methods.listebus.v1.semantic.zones.getebus.v1.semantic.dhw.getebus.v1.semantic.energy_totals.getebus.v1.semantic.boiler_status.getebus.v1.semantic.system.getebus.v1.semantic.circuits.getebus.v1.semantic.radio_devices.getebus.v1.semantic.fm5_mode.getebus.v1.semantic.solar.getebus.v1.semantic.cylinders.getebus.v1.semantic.schedules.getebus.v1.semantic.schedules.set_zone_time_programebus.v1.semantic.schedules.set_dhw_time_programebus.v1.semantic.snapshot.getebus.v1.snapshot.captureebus.v1.snapshot.dropebus.v1.rpc.invoke
- Observe-first bus surface (registered only when the runtime wires bus
observability)
ebus.v1.bus.summary.getebus.v1.bus.messages.listebus.v1.bus.periodicity.list
- Observe-first watch-summary surface (registered only when the runtime wires
watch-summary provider)
ebus.v1.watch.summary.get
- Legacy aliases
ebus.devicesebus.invoke
ebus.v1.semantic.circuits.getexposes explicit per-circuit ownership asmanaging_device.managing_device.roleis always present and is one ofREGULATOR,FUNCTION_MODULE, orUNKNOWN.managing_device.device_idandmanaging_device.addressare populated only when the gateway has ownership evidence for the current topology.ebus.v1.semantic.system.getno longer exposesvr71_circuit_start_index; that threshold was a gateway heuristic and is not part of the canonical contract.
Methods available via ebus.v1.rpc.invoke on the system plane for
Vaillant/Saunier/AWB controllers.
Reads per-day weekly schedule timer programs via B524 opcode 0x03.
Intent: READ_ONLY
| Param | Type | Required | Description |
|---|---|---|---|
source |
byte | yes | Initiator address (gateway = 113) |
sel1 |
byte | yes | Timer selector 1 (controller-specific) |
sel2 |
byte | yes | Timer selector 2 (controller-specific) |
sel3 |
byte | yes | Timer selector 3 (controller-specific) |
weekday |
byte | yes | Weekday index: 0x00=Mon .. 0x06=Sun |
Wire format: [0x03, SEL1, SEL2, SEL3, WD]
Response fields: opcode, sel1, sel2, sel3, weekday, value (raw
timer bytes), slot_count (number of time slots). When the controller returns
no data, value is invalid.
Raw opcode passthrough for investigation. Sends caller-provided payload bytes verbatim on B524.
Intent: MUTATE (requires allow_dangerous: true and idempotency_key)
| Param | Type | Required | Description |
|---|---|---|---|
source |
byte | yes | Initiator address (gateway = 113) |
payload |
[]byte |
yes | Raw opcode bytes (1-16 bytes) |
Response fields: request_payload (echo of sent bytes), response_payload
(controller response), value (alias for response_payload). When no response
data, value is invalid.
Safety note: This method is intentionally readOnly: false because it can
send arbitrary opcodes including mutating ones (for example 0x04 timer
write). The gateway enforces allow_dangerous and idempotency gates.
scanis treated as a cross-device discovery layer and is not modeled as a heat-source class plane.- Heat-source planes (for class-specific modeling) are documented under architecture decisions and class design docs.
Helianthus uses MCP as the first integration surface for new capabilities. The development order is:
- MCP prototype and stabilization (
ebus.v1.*contract) - GraphQL parity after MCP determinism/contract gates are green
- Consumer rollout (HA and others) after GraphQL parity
The architecture model and gates are documented in: