# RFC-0005 — First End-to-End MCP Tool: `system.echo`

- **Status:** RATIFIED 2026-04-21.
- **Depends on:** RFC-0001 v1.3 (G1/G2/G3 landed by CTO 2026-04-21).
- **Author:** 🧠 Agentic Architect
- **Sprint:** 1 (mock-fleet demo gate)
- **Depends on:** RFC-0001 v1.2 (`mcp.json` / projection / error envelope), RFC-0002 v2.2 (WSS envelope; `cmd` / `cmd_ack` shapes), RFC-0003 v1.2 (scope vocabulary, `safety_class` enforcement matrix).
- **Audience:** Cloud Agents (LLMs); 🦀 edge-kubelet-engineer (device handler); ☁️ cloudflare-native-edge (gateway dispatch).

---

## §1. Motivation

PaaS E2E is green on dev with five mock devices live. Before any non-trivial tool (sysinfo snapshot, GPIO toggle, video probe) ships, the entire `cmd → device-execution → cmd_ack → MCP-response` loop MUST be certified end-to-end against the real WSS envelope, the real DO routing, and the real RFC-0001 error envelope. `system.echo` is the smallest tool that exercises every leg of that loop with **zero hardware dependency**, **zero mutation**, and (almost) **zero new vocabulary**. It is a transport-certification tool, not a product feature.

---

## §2. Tool Identifier & Projection

The tool is published by each mock device as a capability with:

- `kind`: **`system.echo`** *(new closed-enum value — vocabulary gate, see §10)*
- `kind_short`: **`sysecho`** *(new entry in RFC-0001 §3.2 registry)*
- `cap_id`: `echo`
- `verb`: `invoke` *(already in RFC-0001 verb enum — no new verb)*
- `safety_class`: `read_only` *(already in RFC-0001 §2 enum and RFC-0003 §5 matrix — no new value)*

Per RFC-0001 §3 projection rule (`{kind_short}.{node_id}.{cap_id}.{verb}`) the worst-case projected MCP tool name is:

```
sysecho.01hzx9k3m4p7q8r9s0t1v2w3xy.echo.invoke    (46 chars, ≤ 64) ✓
```

> **Naming note.** The shorthand `system.echo` used in the CTO directive refers to the *kind* (and to this RFC's title), NOT to the projected MCP tool name. The wire-level `tool` string in the `cmd` envelope MUST be the four-segment projected form above; nothing else satisfies RFC-0002 §5.7 `tool` regex `^[a-z0-9_]+(\.[a-z0-9_]+){3}$`.

---

## §3. Args Schema (Draft 2020-12)

`schema_ref`: `mcp://schemas/system.echo.invoke.input@1.0.0`

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "mcp://schemas/system.echo.invoke.input@1.0.0",
  "type": "object",
  "additionalProperties": false,
  "required": ["message"],
  "properties": {
    "message": {
      "type": "string",
      "maxLength": 1024,
      "pattern": "^[\\x20-\\x7E]*$",
      "description": "ASCII-printable echo payload. ≤ 1024 bytes (1 byte/char under ASCII clamp). No device-supplied strings transit this field."
    }
  }
}
```

ASCII clamp matches RFC-0001 v1.1 locked decision #7 (no UTF-8 surprises through the LLM tokenizer).

---

## §4. Result Schema (Draft 2020-12)

`schema_ref`: `mcp://schemas/system.echo.invoke.output@1.0.0`

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "mcp://schemas/system.echo.invoke.output@1.0.0",
  "type": "object",
  "additionalProperties": false,
  "required": ["message", "received_at_ms", "node_id"],
  "properties": {
    "message":        { "type": "string", "maxLength": 1024,
                        "pattern": "^[\\x20-\\x7E]*$",
                        "description": "Verbatim echo of input.message." },
    "received_at_ms": { "type": "integer", "minimum": 1700000000000,
                        "description": "Device-local wall clock at handler entry. Server does NOT trust this for ordering — it is observational only." },
    "node_id":        { "type": "string", "pattern": "^[0-9a-hjkmnp-tv-z]{26}$",
                        "description": "MUST equal the DO's bound node_id; gateway cross-checks before returning to caller." }
  }
}
```

This object is carried verbatim as `cmd_ack.payload.result` per RFC-0002 §5.8.

---

## §5. Safety Class & Required Scope

- `safety_class = read_only` — pre-existing in RFC-0001 §2 enum and in RFC-0003 §5 matrix. **Not coined here.**
- Required caller scope (agent-side, agent_runtime token): `tools:call:read_only` — pre-existing in RFC-0003 §4. **Not coined here.**
- Required device-side scope (device-runtime token): `device:connect` only — already required for any WS session (RFC-0003 §11). No additional device scope.
- Cross-tenant check per RFC-0003 §5 applies unchanged.

---

## §6. Wire Flow (numbered)

1. Cloud Agent (holding `agent_runtime` JWT with `tools:call:read_only`) calls MCP `tools/call` on the gateway with `name = "sysecho.<ULID26>.echo.invoke"`, `arguments = { "message": "ping" }`.
2. Gateway Worker resolves `name` → `(node_id, kind, cap_id, verb)` via the projected tool registry; verifies tenant match and `safety_class` per RFC-0003 §5; validates `arguments` against `mcp://schemas/system.echo.invoke.input@1.0.0`.
3. Gateway Worker forwards the call to `env.DEVICE_DO.idFromName(node_id)`.
4. `DeviceConnectionDO` mints a server-side `cmd` envelope (RFC-0002 §5.7) with a fresh ULID `msg_id`, `payload.tool = "sysecho.<ULID26>.echo.invoke"`, `payload.arguments = { "message": "ping" }`. No `commit_token` (read_only tool).
5. DO writes the `cmd` to the WS (and to the per-DO `cmd_queue` for the resume protocol per RFC-0002 §7.3).
6. Mock device's WS handler receives the `cmd`, validates the tool name against its announced manifest, validates `arguments` against the cached input schema, executes the handler: `received_at_ms = now_ms(); result = { message, received_at_ms, node_id }`.
7. Mock device sends back `cmd_ack` (RFC-0002 §5.8) with `in_reply_to = <server cmd msg_id>`, `payload = { "ok": true, "result": <§4 object> }`.
8. DO validates `cmd_ack.payload.result` against `mcp://schemas/system.echo.invoke.output@1.0.0`, cross-checks `result.node_id == bound node_id`, completes the in-flight promise correlated by `in_reply_to`.
9. DO returns the `result` to the gateway Worker; Worker wraps in MCP `tools/call` response and returns to the Cloud Agent.
10. On any timeout / failure at any step, gateway/DO emits the RFC-0001 §4 error envelope (see §8).

---

## §7. Timeouts

| Layer                                | Budget | Source                                                                 |
|--------------------------------------|-------:|------------------------------------------------------------------------|
| Gateway end-to-end (caller view)     |   5 s  | This RFC; gateway-enforced.                                            |
| Device-side handler execution        |   2 s  | `capability.constraints.deadline_ms_default = 2000` (RFC-0001 §2 default already covers this — no new field). |
| WS auth deadline                     |   5 s  | RFC-0002 §2.3 (orthogonal; only relevant on session bring-up).         |

DO MUST cancel the in-flight promise at the 5 s mark and return `E_DEADLINE_EXCEEDED`; a late-arriving `cmd_ack` after cancellation is dropped (and audit-logged).

---

## §8. Error Taxonomy

All errors reuse the **existing** RFC-0001 §4 closed enum. No new codes are introduced. Mapping from the CTO-mentioned aliases:

| CTO alias          | RFC-0001 §4 code        | Triggered by                                                          |
|--------------------|-------------------------|-----------------------------------------------------------------------|
| `E_TIMEOUT`        | `E_DEADLINE_EXCEEDED`   | Gateway 5 s budget elapsed without `cmd_ack`.                         |
| `E_DEVICE_OFFLINE` | `E_NODE_OFFLINE`        | DO has no live WS for the target `node_id`.                            |
| `E_INVALID_ARGS`   | `E_MANIFEST_INVALID`    | `arguments` fails JSON-Schema validation against §3 (gateway-side, before dispatch). *Reused, not coined; if a dedicated `E_INVALID_ARGS` is desired in the future, RFC-0001 §4 enum must be amended — out of scope here.* |
| `E_UNAUTHORIZED`   | `E_SAFETY_DENIED`       | Missing `tools:call:read_only`, cross-tenant target, or revoked `jti`. |

Additional codes that may surface unchanged: `E_RATE_LIMITED` (per-capability `rate_limit_rps`), `E_INTERNAL` (gateway/DO bug). All emitted via the standard envelope; `message` is server-templated ASCII (no device-supplied strings interpolated — RFC-0001 v1.1 decision #7 + RFC-0002 §4.1 rationale).

---

## §9. Capability Manifest Entry (mock-device announce)

Each mock device MUST include exactly this capability object in its `announce.payload.capabilities[]` (RFC-0002 §5.4 → RFC-0001 §2):

```json
{
  "cap_id": "echo",
  "kind": "system.echo",
  "schema_ref": "mcp://schemas/system.echo.invoke.input@1.0.0",
  "verbs": ["invoke"],
  "safety_class": "read_only",
  "constraints": { "rate_limit_rps": 10, "max_concurrency": 4, "deadline_ms_default": 2000 }
}
```

The gateway projects this to MCP tool name `sysecho.<node_id>.echo.invoke` per RFC-0001 §3, attaches the §3 input schema, the §4 output schema, and the `x-safety-class: read_only` annotation.

A new RFC-0001 `allOf` clamp SHOULD be added for `kind = "system.echo"` mirroring the `system.metrics` clamp:

```json
{
  "if":   { "properties": { "kind": { "const": "system.echo" } } },
  "then": {
    "properties": {
      "safety_class": { "const": "read_only" },
      "verbs":        { "items": { "enum": ["invoke"] } }
    }
  }
}
```

This clamp ships in the same RFC-0001 amendment that opens the `kind` enum (see §10 gates).

---

## §10. New Vocabulary (CTO Gate Items)

This RFC introduces the **minimum** new vocabulary required to project a non-`system.metrics` tool. Each item is gated on CTO ratification because it touches a closed enum:

| # | New item                                                            | RFC touched          | Rationale                                                       |
|---|---------------------------------------------------------------------|----------------------|-----------------------------------------------------------------|
| 1 | Add `"system.echo"` to `Capability.kind` enum.                      | RFC-0001 §2          | Closed enum, currently `["system.metrics"]` only.               |
| 2 | Add row `system.echo → sysecho` to `kind_short` registry.           | RFC-0001 §3.2        | Required by projection rule.                                    |
| 3 | Add `system.echo` clamp (§9 above) to the `allOf` block.            | RFC-0001 §2 `$defs`  | Mirrors the `system.metrics` clamp.                             |
| 4 | Register schema refs `system.echo.invoke.input@1.0.0` and `system.echo.invoke.output@1.0.0` in the schema registry. | Worker `/schemas/` route | Additive; no enum mutation.                                |

**Not introduced:** no new `verb`, no new `safety_class`, no new scope, no new error code, no new envelope type, no new close code, no new token class.

---

## §11. Example Wire Trace (happy path)

```
T+0ms     agent  → MCP    tools/call name="sysecho.01hzx9k3m4p7q8r9s0t1v2w3xy.echo.invoke" args={"message":"ping"}
T+3ms     GW     → DO     dispatch(node=01hzx9k3m4p7q8r9s0t1v2w3xy, tool=..., args={"message":"ping"})
T+5ms     DO     → device WS frame: {"type":"cmd","msg_id":"01HZXC0000000000000000SRV0",
                                     "payload":{"tool":"sysecho.01hzx9k3m4p7q8r9s0t1v2w3xy.echo.invoke",
                                                "arguments":{"message":"ping"}}}
T+12ms    device handler  received_at_ms=1745236800012
T+14ms    device → DO     WS frame: {"type":"cmd_ack","msg_id":"01HZXC0000000000000000DEV0",
                                     "in_reply_to":"01HZXC0000000000000000SRV0",
                                     "payload":{"ok":true,
                                                "result":{"message":"ping",
                                                          "received_at_ms":1745236800012,
                                                          "node_id":"01hzx9k3m4p7q8r9s0t1v2w3xy"}}}
T+16ms    DO     → GW     resolve(in_reply_to=01HZXC0000000000000000SRV0) → result
T+18ms    GW     → agent  MCP tools/call response: {"message":"ping","received_at_ms":1745236800012,
                                                    "node_id":"01hzx9k3m4p7q8r9s0t1v2w3xy"}
```

Total roundtrip ≈ 18 ms; well under the 5 s gateway budget and 2 s device budget.

---

## §12. Out of Scope

- Streaming / multi-frame echo (would require a `stream` verb projection and broker fanout accounting — Sprint N+).
- Batch echo (multiple messages per call).
- Client-driven retry semantics (caller may retry; gateway does NOT auto-retry — `cmd` is at-most-once until the resume protocol completes per RFC-0002 §7.3).
- Mid-session token rotation (out of scope per RFC-0003 §11.3).
- Persistence of echoed messages anywhere (audit log records `tool`, `node_id`, `decision`, `code` only — never `arguments` or `result` content).

---

## §13. Handoff

- **🦀 edge-kubelet-engineer:** implement the `system.echo` capability handler in the mock-device path: announce the §9 capability object, decode incoming `cmd` envelopes whose `payload.tool` matches the projected name, return `cmd_ack` per §11. No real-hardware path is required; mock fleet only.
- **☁️ cloudflare-native-edge:** (a) extend the projected-tool registry to accept `kind = "system.echo"` once RFC-0001 amendment lands; (b) wire the §3/§4 schemas into `/schemas/`; (c) implement DO `cmd` dispatch + `cmd_ack` correlation + 5 s deadline with `E_DEADLINE_EXCEEDED` on expiry; (d) cross-check `result.node_id == bound_node_id` before returning to caller.
- **🛡️ devex-protocol-sec:** no action required (no new scope, no new token class, no new secret).

---

## §14. Ratification Status

This RFC is **NOT self-approvable under standing delegation** because §10 items (1)–(3) mutate a closed enum in RFC-0001 (the `kind` enum and its `kind_short` registry), and standing delegation explicitly excludes vocabulary gates. Once CTO ratifies the RFC-0001 amendment, all downstream work (handler, dispatch, schema registry entries) is non-fatal and proceeds without further gates.
