cd /news/large-language-models/device-context-protocol-bridge-llm-a… · home topics large-language-models article
[ARTICLE · art-57078] src=github.com ↗ pub= topic=large-language-models verified=true sentiment=↑ positive

Device Context Protocol – Bridge LLM Agents to Physical Devices

The Device Context Protocol (DCP) v0.3, hardware-validated on ESP32-WROOM-32 and ESP32-S3 boards, enables LLM agents to safely control physical devices using sub-50-byte frames and under 1 KB of RAM. A reference Bridge translates DCP to the Model Context Protocol (MCP), allowing zero-config use with MCP hosts like Claude Desktop. The protocol's intent-level, transport-agnostic design enforces safety before any byte reaches the device, catching all six categories of hallucinated or adversarial calls at the wire layer.

read9 min views58 publishedJul 13, 2026
Device Context Protocol – Bridge LLM Agents to Physical Devices
Image: Michielbdejong (auto-discovered)

Status: Draft v0.3 — May 2026 · Hardware-validated on ESP32-WROOM-32

A protocol that lets LLM agents safely control physical devices, down to dollar-class microcontrollers.

Intent-level, transport-agnostic, capability-scoped. Compact wire format (sub-50-byte frames). Self-contained firmware: under 1 KB of RAM, ~28 KB of flash.

Complementary to

[MCP]— a reference Bridge translates DCP ↔ MCP so any MCP host (Claude Desktop, Claude Code, IDE assistants) works zero-config.

Why DCP?Design principlesArchitectureQuickstartAdd a feature in 5 stepsRecipes — five ready-to-flash device skeletonsWire format· fullSPEC.mdManifestRoadmapDesign rationale:docs/RATIONALE.md— why not MCP-on-MCU, why not WoT, why not Matter.

MCP is excellent for SaaS tools, but assumes JSON-RPC over WebSocket and runtime tool discovery. On an MCU with 32 KB of RAM, that's a non-starter.

DCP keeps MCP's mental model (manifest + tool calls) but:

  • compiles to a compact CBOR wire format
  • uses a static intent table (no runtime negotiation)
  • moves safety enforcement to a Bridge process

A reference Bridge translates DCP ↔ MCP, so any MCP-compatible LLM works out of the box. DCP is the last mile to physical hardware.

Why this matters in one chart: the protocol's schema decides how many hallucinated or adversarial calls are stopped before any byte reaches a device. DCP catches all six categories at the wire layer; the others catch what their existing schema happens to cover.

Intent, not register.set_brightness(50%)

, notwrite_pwm(pin=5, duty=128)

.Units in the protocol. Every number declares a unit. No ambiguity.Static intent table. Manifest known at compile time; runtime is pure binary.Safety lives in the Bridge. Devices trust the Bridge; LLMs never see raw GPIO.Idempotent by default. Non-idempotent intents must declare themselves.Transport-agnostic. UART, BLE, MQTT, USB-CDC, WebSocket — one frame.

The Bridge is the sole trust boundary. On every call it issues and verifies capability tokens, enforces range/type/unit checks from the manifest, and supports dry-run as a wire-format primitive. Devices remain simple enough to fit on commodity microcontrollers; everything the LLM is allowed to do is enforced before any byte traverses the device boundary.

As of v0.3 the reference firmware is measured-validated on two physical boards — an ESP32-WROOM-32 dev board over CH340 USB-Serial, and an ESP32-S3 (LILYGO T-Panel S3) over the S3's native USB-Serial/JTAG — both at 115 200 baud:

  • 13/13 round-trip tests pass on each board ( tools/test_uart_roundtrip.py

) - 88/88 Python unit & conformance tests pass

  • Full lamp firmware: 295 KB flash, 22.7 KB globals on WROOM-32 — most of which is the Arduino-ESP32 runtime + FreeRTOS, not DCP The DCP layer itself measures 27.6 KB of flash and 0.6 KB of RAM over a baseline empty sketch — reproduce withdocs/paper/figures/measure_footprint.py

. The flash figure is over the original<16 KB

design target (set before on-device HMAC was added); the RAM figure is well under it.- The S3 run also exercises DCP over a native-USB CDC link rather than a USB-UART bridge chip — same firmware, no transport-specific code

Static RAM is the scarce resource on an MCU. The DCP layer's measured 0.6 KB of RAM sits two orders of magnitude under IoT-MCP's reported 74 KB peak memory. DCP's flash cost (27.6 KB, measured) is not plotted — IoT-MCP does not report a comparable flash figure.

See docs/RATIONALE.md §7 for what the hardware validation does and does not prove.

The reference firmware is portable by design (Arduino Stream

  • a software SHA-256, no SoC-specific code paths in DCP.{h,cpp}

). It cross-compiles for every current ESP32 variant and for ESP8266; two of those targets are also runtime-validated on real boards, the rest are build-validated pending hardware on the bench:

Target ISA Flash (lamp+blink) Globals Status
ESP32-WROOM-32 Xtensa LX6 (baseline) 294 KB 22.7 KB runtime ✓
ESP32-S3 (T-Panel) Xtensa LX7 322 KB 22.7 KB runtime ✓ (native USB)
ESP32-C3 RV32IMC 289 KB 13.4 KB builds ✓
ESP32-C6 RV32IMAC + HW-crypto 266 KB 14.0 KB builds ✓
ESP32-H2 RV32IMAC + 802.15.4 292 KB 14.0 KB builds ✓
ESP32-P4 RV32IMAFC dual-core 326 KB 22.0 KB builds ✓
ESP8266 NodeMCU Xtensa LX106 (legacy) 242 KB 28.9 KB builds ✓

All builds use Arduino-ESP32 core 3.3.8 / Arduino-ESP8266 core 3.x

  • the same firmware/esp32/

library. The sketch picks PWM API at compile time (ledcAttach

/ledcWrite

on ESP32,analogWrite

on ESP8266); the protocol layer itself has no#ifdef

. Reproduce with:

arduino-cli compile --clean --fqbn esp32:esp32:esp32c3 \
    --library firmware/esp32 firmware/esp32/examples/lamp
arduino-cli compile --clean --fqbn esp8266:esp8266:nodemcuv2 \
    --library firmware/esp32 firmware/esp32/examples/lamp
dcp: 0.3
device:
  id:     lamp-kitchen-01
  model:  smart_lamp_v1
  vendor: example.dev

intents:
  - name: set_brightness
    params:
      level: { type: float, unit: percent, range: [0, 100] }
      fade:  { type: duration, unit: ms, default: 0 }
    capability: lamp.write
    idempotent: true
    dry_run: true

  - name: read_brightness
    returns: { type: float, unit: percent }
    capability: lamp.read

events:
  - name: motion_detected
    payload:
      confidence: { type: float, unit: ratio, range: [0, 1] }
    capability: lamp.read

intent_id = crc16(name)

— manifests and firmware stay in sync without coordination.

A frame is a 6-byte fixed header + CBOR payload + an optional 16-byte truncated HMAC-SHA256. Header fields:

field meaning
ver
1 in v0.3
kind
0x01 call · 0x02 reply · 0x03 event · 0x04 error · 0x81 dry-run
seq
client-chosen, echoed in reply
intent_id
CRC-16/CCITT of intent name
cbor
CBOR map: params / return / event payload / error

Reply status codes: ok

, denied

, range

, busy

, unknown_intent

, capability_required

.

A typical set_brightness(50)

call is 19 bytes on the wire; the MCP JSON-RPC equivalent is approximately 180 bytes. The full normative spec lives at SPEC.md.

See docs/ADDING_FEATURES.md for the full 5-step loop with a worked blink(times, period)

example. The short version: edit the manifest, add a C++ handler + binding, recompile, flash, restart the MCP server — the LLM picks up the new tool automatically. The Bridge needs no code change.

pip install "pydcp[mcp,serial]"            # or [mcp,serial,mqtt,ble] for all transports
dcp inspect examples/lamp_manifest.yaml    # parsed manifest summary
dcp serve   examples/lamp_manifest.yaml --simulator
git clone https://github.com/device-context-protocol/dcp.git
cd dcp
pip install -e ".[mcp,serial,mqtt,ble,dev]"
pytest                                     # all 88 tests
python examples/lamp_demo.py               # in-process bridge ↔ fake lamp

The PyPI package is named pydcp

(the bare dcp

is squatted by an unrelated package). The import name is dcp

. The protocol name is DCP.

The reference Bridge ships an MCP server that exposes each DCP intent as an MCP tool. With --simulator

it spins up an in-process fake device, so you can demo with no hardware.

dcp serve examples/lamp_manifest.yaml --simulator               # no hardware
dcp serve examples/lamp_manifest.yaml --serial COM3             # real ESP32 over UART
dcp serve examples/lamp_manifest.yaml --mqtt broker.lan:1883 \  # MQTT
            --mqtt-prefix dcp/lamp-kitchen
dcp serve examples/lamp_manifest.yaml --ble AA:BB:CC:DD:EE:FF \ # BLE
            --ble-service 12345678-1234-5678-1234-567812345678

For multi-tenant or scoped access, mint short-lived HMAC tokens and pass them to the Bridge:

export DCP_SECRET=$(dcp token keygen)
dcp token mint --caps lamp.write,lamp.read --ttl 3600

Tokens are verified by the Bridge on every call. The device sees only already-authorized frames. Devices themselves do not verify signatures in v0.2 — that requires on-device HMAC, which is on the roadmap.

To wire it into Claude Desktop, add this to your claude_desktop_config.json

:

{
  "mcpServers": {
    "smart-lamp": {
      "command": "dcp",
      "args": [
        "serve",
        "C:/path/to/protocol/examples/lamp_manifest.yaml",
        "--simulator"
      ]
    }
  }
}

Then ask Claude "set the lamp to 60% brightness". The call flow:

Claude ─MCP─▶ dcp serve ─Bridge─▶ Loopback ─DCP wire─▶ GenericSimulator

For production use, replace GenericSimulator

with a real transport (UART / MQTT / BLE — coming next).

  • Multi-device atomic transactions
  • Firmware OTA
  • Mesh routing (use Thread / Zigbee underneath if you need it)
  • LLM-side authentication (delegated to the MCP host's session model)
  • Native CAN FD frames (ESP32-S3 TWAI is classic CAN; v0.4 ESP32-P4 port enables true CAN FD)

If you use DCP in academic work, please cite the arXiv preprint:

@misc{yang2026dcp,
  title        = {Device Context Protocol: A Compact, Safety-First Architecture
                  for LLM-Driven Control of Constrained Devices},
  author       = {Yang, Dongxu},
  year         = {2026},
  eprint       = {2605.26159},
  archivePrefix= {arXiv},
  primaryClass = {cs.NI},
  url          = {https://arxiv.org/abs/2605.26159},
}

A machine-readable CITATION.cff is also provided — GitHub renders a "Cite this repository" button in the sidebar.

MIT.

  • Wire format + manifest parser
  • Reference Python Bridge with loopback transport
  • Lamp example
  • MCP server wrapper + CLI ( dcp serve

) - Generic in-process device simulator

  • UART transport (COBS framing + CRC-16)
  • ESP32 reference firmware (Arduino-compatible C++)
  • Design rationale ( docs/RATIONALE.md) - CI (GitHub Actions, Linux + Windows, py 3.11–3.13)
  • MQTT transport
  • HMAC-SHA256 capability tokens (Bridge-side enforcement)
  • Manifest compiler: dcp codegen

(YAML → C header) - Compile-time DCP_ID(name)

macro in firmware - BLE GATT transport (bleak)

  • Release prep: CONTRIBUTING / CHANGELOG / CoC / SECURITY / issue templates
  • On-device HMAC verification (per-frame signatures, ESP32 firmware)
  • ESP32 BLE peripheral example (NimBLE-Arduino)
  • Conformance test suite (golden frames, language-neutral YAML)
  • Codegen --stubs

: emits handler signatures + binding table - Quickstart video script ( docs/QUICKSTART_VIDEO.md) - Real-hardware validation on two boards (ESP32-WROOM-32 over CH340, ESP32-S3 / T-Panel over native USB), 13/13 round-trips each

  • Cross-compile clean on ESP32 RISC-V family (C3, C6, H2, P4) and ESP8266
  • Public repo at device-context-protocol/dcp

(v0.3.0 released) - PyPI release ( pip install pydcp

, latest v0.3.1) - LLM-driven hallucination-rejection benchmark: 675 tool calls across 5 LLMs / 4 vendors, prompt-injection category instantiated from AgentDojo's attack templates. DCP catches 100% of capability- escalation and 78% of prompt-injection attempts vs 0–1% for MCP/ IoT-MCP. See tools/gen_llm_corpus.py

+tools/bench_hallucination_empirical.py

. - DCP vs IoT-MCP wire-latency A/B on identical ESP32-S3 hardware: 15.60 ms vs 15.59 ms median, within 5 µs. See firmware/esp32/examples/iotmcp_echo/

+tools/bench_latency_iotmcp.py

. - arXiv preprint published: arXiv:2605.26159(v0.3.1). Source bundle and rendered PDF also mirrored on the v0.3.1release page. - T-Panel S3 + CAN bus demo (firmware ready, awaiting hardware)

  • ESP32-P4 port for native CAN FD
  • Multi-MCU footprint matrix (nRF52840, Cortex-M0+, RP2040)
── more in #large-language-models 4 stories · sorted by recency
── more on @device context protocol 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/device-context-proto…] indexed:0 read:9min 2026-07-13 ·