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

A new open protocol, Device Context Protocol (DCP), enables large language model agents to safely control physical devices including low-cost microcontrollers. DCP uses a compact wire format and a Bridge process for safety enforcement, with reference firmware validated on ESP32 hardware. The protocol is designed to complement the Model Context Protocol (MCP) by translating DCP to MCP for zero-configuration use with existing LLM hosts.

read9 min views1 publishedJul 13, 2026
Device Context Protocol – Bridge LLM Agents to Physical Devices
Image: source

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 ·