EuiccBridge

EuiccBridge

Enables management of eSIM profiles, SMS, and cellular modem diagnostics on Linux through a CLI and MCP server with safety defaults.

Category
访问服务器

README

EuiccBridge

A CLI (and MCP server) for managing eSIM profiles, SIM SMS, and cellular modem diagnostics on Linux — local-first, scriptable, and safe to point an AI agent at.

  • eSIM lifecycle: list, download, enable, disable, delete, switch Profiles
  • SMS: send/receive over AT/PDU directly, no ModemManager dependency
  • Diagnostics: registration, APN/PDP, interface, and connectivity checks, plus a doctor command for environment pre-flight checks
  • Safety by default: confirmations, redaction, allowlists, audit logging
  • One core library, three fronts: CLI today, MCP server for AI tooling, Web API planned — all calling the same code, not shelling out to each other

Built on top of proven lower-level tools (lpac, libqmi/qmicli, standard Linux networking and AT commands) rather than reimplementing GSMA RSP, QMI, or AT protocol stacks from scratch.

Goals

  • Keep eSIM activation codes, Profile operations, SMS, and SIM identifiers under local, auditable control.
  • Use a Linux VM or small Linux host as the hardware controller.
  • Expose one shared core library through CLI first, then MCP, then optional Web API/WebUI.
  • Make every dangerous operation explicit, confirmed, logged safely, and reversible where possible.

Non-Goals

  • Do not implement GSMA RSP, APDU, QMI, MBIM, IMS, ePDG, IPsec, or SIP stacks.
  • Do not promise VoWiFi calling or SMS over WiFi Calling.
  • Do not build a multi-tenant/commercial traffic pool, IP-rotation-for-hire service, or Bot platform. (A single-SIM, self-use SOCKS5/HTTP proxy bound to whichever cellular interface is currently active is a different, smaller thing — see Roadmap. The line being drawn is multi-tenant/pool vs. self-use, not "proxy vs. no proxy".)

Architecture

euiccbridge core library
  |-- CLI          first deliverable, machine-readable JSON
  |-- MCP server   AI/tooling entrypoint, same core library
  `-- Web API      later layer, same core library

The CLI is an entrypoint, not the business logic layer. Web API and MCP call the same Python core library directly rather than shelling out to the CLI.

Core modules:

  • core.device / core.device_paths: AT port / QMI device / cellular interface discovery, AT command execution, modem status parsing.
  • core.qmi: QMI device discovery and status via qmicli.
  • core.esim: lpac wrapper for eUICC/Profile operations (info, list, download, enable, disable, delete, switch).
  • core.gsm_pdu: self-contained GSM 03.40/03.38 SMS PDU codec (no ModemManager dependency — see "SMS backend" below).
  • core.sms / core.sms_store: SMS sync/list/send, backed by a local SQLite store that's the decoupling point for downstream consumers.
  • core.data: APN, registration, PDP, interface, and public-IP diagnostics.
  • core.doctor: read-only environment pre-flight checks.
  • core.config: config loading + SMS allowlist management.
  • core.security: confirmation, masking, allowlist, rate limits, audit.

SMS backend: AT/PDU-direct, not ModemManager

SMS is implemented from scratch against AT+CMGF/AT+CPMS/AT+CMGL/AT+CMGS in PDU mode, not via ModemManager/mmcli. This was a deliberate choice: a ModemManager instance and this project's own direct QMI/AT usage can end up contending for the same control channel, and comparable closed-source modem managers in this space document the same conflict and instruct disabling ModemManager rather than depending on it. Going AT/PDU-direct avoids adding a persistent background service and keeps this project's "auditable, local-first, no unnecessary daemons" posture consistent across eSIM, SMS, and diagnostics.

Relationship with EasyLPAC

EasyLPAC is a mature open-source lpac GUI frontend. EuiccBridge uses it as a reference and optional manual fallback, not as a runtime dependency.

Useful parts borrowed from EasyLPAC's design: lpac discovery behavior (same directory, system PATH, then /usr/bin/lpac), eSIM operation flow and user-facing error handling, eUICC manufacturer/certificate/SAS display ideas, and profile-notification handling (process after operations by default, with a setting to disable it).

EuiccBridge still calls lpac directly because it needs machine-readable CLI/MCP/Web API output, SMS, modem status, APN/PDP diagnosis, allowlists, audit, and redaction — those are service/controller concerns, not GUI concerns.

v1 Scope

  • Device discovery: AT port, QMI device, cellular interface, IMEI, ICCID, IMSI, subscriber number (when the SIM has one written to it — many data-only/IoT SIMs don't).
  • Radio state: operator, PLMN, registration state, signal, LTE band, APN, PDP, interface IP.
  • eSIM management: EID, Profile list, download, enable, disable, delete, switch (one-step disable-current + enable-target).
  • SMS: sync/list/send, allowlist, daily limit, safe audit, incremental (--since-id) reads for downstream consumers.
  • Data diagnosis: distinguish no registration, empty APN, inactive PDP, no interface IP, and public connectivity failure.
  • doctor: read-only pre-flight checks (AT/QMI reachability, lpac presence and backend health, device-node integrity, QMI raw-ip mode).
  • CLI and MCP: both use the same core library and default to redacted output.

v1 does not attempt: WiFi Calling SMS, VoWiFi outbound calls, IMS/ePDG/ IPsec/SIP implementation, or compatibility with any specific closed-source modem manager's API.

v1 CLI

The CLI supports JSON output so MCP, Web API, and scripts can consume the same result shape.

python3 -m euiccbridge doctor --json
python3 -m euiccbridge device list --json
python3 -m euiccbridge device status --json
python3 -m euiccbridge esim info --json
python3 -m euiccbridge esim list --json
python3 -m euiccbridge esim download --activation-code-stdin --confirm --json
python3 -m euiccbridge esim enable --iccid <iccid> --confirm --json
python3 -m euiccbridge esim disable --iccid <iccid> --confirm --json
python3 -m euiccbridge esim switch --iccid <iccid> --confirm --json
python3 -m euiccbridge esim delete --iccid <iccid> --confirm --force --json
python3 -m euiccbridge sms sync --json
python3 -m euiccbridge sms list --json
python3 -m euiccbridge sms send --to <number> --text <text> --confirm --json
python3 -m euiccbridge sms allowlist add --number <number> --json
python3 -m euiccbridge data diagnose --apn <apn> --json
python3 -m euiccbridge data test --interface <iface> --json

esim download prompts for the activation code on stdin (paste + Enter, no Ctrl+D needed) rather than taking it as an argument; add -v/--verbose to stream lpac's progress live instead of waiting for one final JSON blob.

Multiple devices

If more than one physical modem is attached, every command above needs a top-level --device <label> (before the subcommand) to say which one to target:

python3 -m euiccbridge device list --json
python3 -m euiccbridge --device 5-3 device status --json
python3 -m euiccbridge --device 5-3 esim list --json
python3 -m euiccbridge --device 5-3 sms send --to <number> --text <text> --confirm --json

device list shows each attached device's label (its USB serial number if the hardware reports one, otherwise its USB bus-port path e.g. 5-3 -- some rebranded/OEM modules don't report a serial). Devices are identified by correlating AT port + QMI device + network interface via Linux USB topology (sysfs), not by enumeration order, since /dev/ttyUSB* numbering isn't stable across reconnects.

Omitting --device works exactly as before when exactly one device is attached (nothing changes for single-device setups). With more than one attached and no --device given, every command fails explicitly with "N devices found (...) but none selected" rather than silently guessing -- auto-discovering AT port and QMI device independently, with no guarantee they picked the same physical modem, previously produced a device status response silently mixing fields from two different modems together (see docs/TROUBLESHOOTING.md). Set default_device in config to avoid passing --device on every call.

SMS messages are tagged with the device they were sent/received through, and the daily send limit (sms_daily_limit) is tracked per device, not globally -- two modems each get their own budget.

Safety Rules

  • eSIM activation codes are read from stdin/interactive input, never shell arguments, and are never written to logs, audit files, tracebacks, or JSON responses.
  • eSIM write operations refuse to run while a conflicting modem-manager service is active.
  • Profile deletion requires both --confirm and --force.
  • SMS sending requires explicit confirmation and an allowlisted recipient, plus a configurable daily send limit.
  • EID, ICCID, IMSI, IMEI, phone numbers, activation codes, and SMS bodies are redacted by default in every command's output; an explicit opt-out flag (audited when used) reveals it per-call — --reveal on esim/sms commands, --no-redact on device status.
  • MCP dangerous tools require confirm: true.
  • Services do not listen on public interfaces.

Compatibility

Validated against a Quectel EC25/EG25-class USB modem (AT + QMI) paired with an eUICC exposing a GSMA RSP2 Root CI1 certificate, on a Debian Linux controller. The design is not hardcoded to that one device.

Compatibility tiers:

  • Tier 1: Quectel EC25/EG25/EC20-like AT + QMI modems.
  • Tier 2: Other cellular modems exposing standard AT registration/SMS commands.
  • Tier 3: lpac-supported eUICC readers, eSIM management only.

Tiers 1 and 2 need only a SIM (physical or eSIM) to work — device status, signal, registration, data diagnosis, and SMS all operate at the AT/QMI layer and don't care what kind of SIM is inserted. Tier 3 (the esim command family) specifically needs an actual eUICC with a GSMA ISD-R applet; a plain (non-eUICC) SIM makes every esim command fail, and doctor's lpac_backend check reports this distinctly (warn, "no eUICC detected") rather than treating it as a broken environment.

Not supported: phone built-in eSIM interfaces, native macOS/Windows USB modem management, generic VoWiFi calling/SMS.

Roadmap

  • v1: local CLI + MCP, device status, eSIM management, SMS, data diagnosis, doctor, multi-device selection (--device, per-device SMS attribution and daily limits) — complete, every item unit-tested and real-hardware validated (including with two physical modems attached simultaneously) except eSIM download's success path, which is validated but depends on network conditions outside this project's control (see docs/TROUBLESHOOTING.md).
  • v1.1: scheduled keepalive SMS with allowlist, limits, retries, and reports.
  • v1.2: local Web API backed by the same core library.
  • v1.3: lightweight WebUI for status, eSIM, SMS, and diagnostics.
  • v1.x: self-use single-card SOCKS5/HTTP proxy bound to whichever cellular interface is currently active (explicitly not the multi-tenant/pool feature described in Non-Goals).
  • v2: human-friendly device labels/nicknames (beyond the raw serial/USB path), per-device queues, and cross-device aggregation views.
  • v3: VoWiFi/IMS status diagnostics and log collection only.
  • v4: advanced data routing and per-interface outbound testing.

Near-term, more granular candidates: +CMTI real-time SMS notification (deferred — needs a persistent AT-port listener, a different execution model than the rest of this project; a periodic sms sync + notification hook covers most of the same need at much less complexity), optional GSM 7-bit encode path for outgoing SMS (currently UCS2-only by design), install/ dependency guidance per distro, an uqmi backend for OpenWrt.

Status

All v1 Scope items are implemented, unit-tested, and validated against real hardware (eSIM info/list/download/enable/disable/delete/switch; SMS sync/ list/send including multi-part concatenation and non-ASCII content; device status; data diagnosis; doctor).

Two real bugs were found and fixed during hardware validation that are worth knowing about if you're bringing up your own hardware — see docs/TROUBLESHOOTING.md for the full symptom/cause/fix writeups:

  • USB re-enumeration can corrupt AT-port device nodes into plain regular files; doctor's device_node_types check catches this directly.
  • QMI WWAN interfaces in Ethernet/802.3 mode can fail to route reliably on some hardware; doctor's qmi_raw_ip check flags this, and scripts/80-euiccbridge-qmi-rawip.rules is a working fix you can adapt to your modem's VID:PID.

Current Runtime Notes

  • A conflicting modem-manager service (if one is installed) should remain stopped while running EuiccBridge — it will contend for the same QMI control channel and AT port.
  • lpac is not vendored in this repository; install it separately and point lpac_path at it.
  • sms_allowed_numbers can be managed without hand-editing the config file: euiccbridge sms allowlist add/remove/list --number <number> --json.
  • Scheduled SMS sync, single device: scripts/euiccbridge-sms-sync.service + .timer (copy both to /etc/systemd/system/, systemctl daemon-reload, systemctl enable --now euiccbridge-sms-sync.timer). Runs euiccbridge sms sync every 30s by default; adjust OnUnitActiveSec in the .timer file to match sms_poll_interval_seconds if you change that config value.
  • Scheduled SMS sync, multiple devices: the plain service above has no --device, so with more than one physical device attached it fails every run ("N devices found but none selected") instead of silently syncing the wrong one. Use the templated units instead -- scripts/euiccbridge-sms-sync@.service + @.timer -- one instance per device, named after its device list label:
    systemctl enable --now euiccbridge-sms-sync@5-3.timer
    systemctl enable --now euiccbridge-sms-sync@5-2.timer
    
    Since device labels can be a USB bus-port path (when the hardware reports no serial number), replugging a device into a different port changes its label -- re-check device list and re-enable the timer under the new label if that happens.
  • scripts/sms_notify.py is a separate, optional consumer script (deliberately not part of the euiccbridge package — it's policy/orchestration, reading the stable CLI JSON contract, not a capability) — see scripts/sms_notify.config.example.json for its config shape. It runs with --reveal, so point hook_command/webhook_url at a target you trust with real numbers/text.
  • The cellular data interface commonly needs QMI raw-ip mode to route real traffic reliably — see scripts/80-euiccbridge-qmi-rawip.rules and docs/TROUBLESHOOTING.md.

Example Config

Create euiccbridge.config.json in this directory, or /opt/euiccbridge/config.json.

Minimal single-modem config:

{
  "lpac_path": "/opt/euiccbridge/bin/lpac",
  "lpac_apdu": "qmi",
  "lpac_http": "curl",
  "auto_process_notifications": false
}

Pinned config for multi-device or debugging:

{
  "at_port": "/dev/ttyUSB2",
  "qmi_device": "/dev/cdc-wdm0",
  "interface": "wwp0s3u3i4",
  "lpac_path": "/opt/euiccbridge/bin/lpac",
  "lpac_apdu": "qmi",
  "lpac_http": "curl",
  "lpac_qmi_device": "/dev/cdc-wdm0",
  "lpac_at_device": "/dev/ttyUSB2",
  "sms_allowed_numbers": ["10086"],
  "sms_daily_limit": 5,
  "sms_storage": "ME",
  "sms_db_path": "/var/lib/euiccbridge/sms.db",
  "sms_poll_interval_seconds": 30,
  "auto_process_notifications": true,
  "lpac_commands": {
    "info": ["chip", "info"],
    "list": ["profile", "list"],
    "download": ["profile", "download", "-a", "{activation_code}"],
    "enable": ["profile", "enable", "{iccid}"],
    "disable": ["profile", "disable", "{iccid}"],
    "delete": ["profile", "delete", "{iccid}"],
    "notifications": ["notification", "process"]
  }
}

The lpac_commands section exists because lpac command flags can vary by build/distribution. Calibrate it on your target host with lpac --help and lpac profile --help before using write operations.

Troubleshooting

See docs/TROUBLESHOOTING.md for real-hardware failure modes and fixes (corrupted AT-port device nodes, QMI raw-ip mode, DNS injection affecting specific domains on some networks).

License

MIT — see LICENSE.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选