Skip to main content
Glama
libertyrights

qcdm-mcp

qcdm-mcp

ALPHA — NOT TESTED ON REAL HARDWARE

This is an untested, best-effort implementation. The packet layouts were derived from public documentation and open-source MIT/GPL projects, but this server has not been run against a real Qualcomm modem. Some fields are marked inferred, unverified. Writing to a device (NV, memory, EFS) can crash the modem or brick it. Use at your own risk, on hardware you can afford to lose, and always back up NV/EFS first.

A single-file Python MCP (Model Context Protocol) server that speaks the Qualcomm DIAG / QCDM protocol ("Diag", "Diagnostic Monitor") over a serial diag port. It is the sibling project to sahara-mcp and mirrors its style.

Related MCP server: mcp-uart

Features

Exposes the following tools (all under the diag_ prefix):

Tool

Purpose

diag_list_ports

Enumerate serial ports (name, description, hwid)

diag_version

Modem version/build string via DIAG_VERSION_F + DIAG_EXT_BUILD_ID_F

diag_nv_read

Read a single NV item (hex payload)

diag_nv_write

Write a single NV item (WARNING: can corrupt/brick)

diag_memory_peek

Read modem memory via DIAG_PEEKB_F

diag_memory_poke

Write modem memory via DIAG_POKEB_F (WARNING; inferred layout)

diag_efs_list

List an EFS2 directory

diag_efs_pull

Copy a modem EFS file to the host

diag_efs_push

Push a host file into modem EFS + read-back verify (WARNING)

The server talks over a serial port at 115200 baud using HDLC-style framing.

How to expose a DIAG port

  • Rooted Android phone/dev/diag (requires root). To re-enumerate the phone with a usb diag port exposed (with adb still available):

    adb shell su -c 'setprop sys.usb.config diag,serial_cdev,rmnet,adb'

    It re-enumerates as a Qualcomm DIAG device (05c6:9091). On Linux, bind it with the option driver (sudo modprobe option; echo 05c6 9091 | sudo tee /sys/bus/usb-serial/drivers/option1/new_id). Only one of the serial nodes is the DIAG port; pass it explicitly or let the server probe.

  • USB modem / dongle — many Qualcomm modems expose a diag pseudo-serial port either directly or after sending the vendor command AT$QCDMG. This is commonly /dev/ttyUSB* (Linux) / COMx (Windows).

  • Windows COM ports — use Device Manager to find the diag port. If it is hidden, enable View → Show hidden devices. The server enumerates ports via serial.tools.list_ports and can auto-probe.

If port is left empty, the server probes every enumerated serial port with a DIAG_VERSION_F request and uses the first that answers (so you don't have to guess which ttyUSB*/COMx is the diag one).

Install

Requires Python 3.8+ and the pyproject.toml dependencies:

pip install pyserial
pip install "mcp"        # or: pip install -e .

Usage

Run the server (stdio transport, consumed by an MCP client):

python qcdm_mcp_server.py

Example tool invocations an agent might make:

diag_version(port="COM3")
diag_version(port="")                      # auto-detect
diag_nv_read(port="COM3", item_id=550)     # NV_UE_IMEI_I
diag_efs_list(port="COM3", path="/policyman")
diag_efs_pull(port="COM3", remote_path="/policyman/band_set_01.xml",
              local_path="band_set_01.xml")
diag_efs_push(port="COM3", local_path="band_set_01.xml",
              remote_path="/policyman/band_set_01.xml")
diag_memory_peek(port="COM3", address=0x40000000, length=16)

Protocol notes and corrections (verified against references)

  • On-wire framing — HDLC-like: payload + CRC-16, trailer 0x7e; 0x7e escaped as 0x7d 0x5e, 0x7d as 0x7d 0x5d. CRC is the reflected FCS-16 (poly 0x8408, init 0xFFFF, complemented) — verified against the CRC-16/X-25 check value.

  • Command codes (diagcmd.h lineage): DIAG_VERSION_F = 0x00, DIAG_EXT_BUILD_ID_F = 0x7c, DIAG_NV_READ_F = 0x26, DIAG_NV_WRITE_F = 0x27, DIAG_PEEKB/POKEB = 0x02/0x05, DIAG_SUBSYS_CMD_F = 0x4b, errors 0x13/0x14/0x15.

  • Corrections to common guesses: NV read/write are 0x26/0x27, not 0x27/0x28; DIAG_SUBSYS_CMD_F is 0x4b, not 0x54; EFS subcommands are the 0-based 0..15 ids (HELLO=0, OPENDIR=11, READDIR=12, STAT=15, …), not the 0x2011+ offset family that appears in some older documentation. See qcdm_mcp_server.py comments.

  • Length limits — legacy serial DIAG frames are capped around 4 KB (DIAG_MAX_PKT_LEN); larger payloads are split into small chunks (EFS transfers use 1 KB chunks, the negotiated window is 1 MiB). Newer transports add a multi_pkt wrapper for large frames; this server stays on the serial/HDLC path and does not use it.

  • Inferred/unverified: the exact split of the NV read payload between data and trailing status byte, the memory-poke response layout, and the variables inside the readdir response.

References and licensing

This project is licensed under the GPL-3.0 license. See LICENSE. Copyright (c) 2026 libertyrights. Our own code is GPL-3.0; the references above were used for protocol facts only and retain their own licenses.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables AI assistants to interact with physical serial port devices across platforms (Windows COM/Linux tty) with support for asynchronous communication, URC pattern recognition, and structured logging.
    1
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to communicate with UART/serial devices, offering tools for port management, data read/write, and protocol handling.
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    Enables AI agents to control serial port devices (modems, instruments, embedded boards) via MCP tools for listing ports, connecting, and sending/receiving commands.
    3
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables LLM agents to interact directly with Qualcomm EDL devices over USB, including device enumeration, Sahara protocol handshake, SoC identity queries, Firehose programmer upload, command-mode execution, and device reset.
    GPL 3.0