podkop-mcp

io.github.mikel38487由 GitHub 账号 mikel38487 发布v0.2.2更新于 Nov 1, 2026

MCP server for managing Podkop and Forkop on OpenWrt over SSH

概览

AI 生成的概览

通过 SSH 管理 OpenWrt 路由器上的 Podkop 与 Forkop 代理分流,让助手查看、修改并备份 UCI 配置,支持 dry-run 与密钥脱敏。

可以做什么
  • 检查路由器与后端状态:诊断、服务状态、系统信息、健康检查、日志、安装器状态及只读 CLI 命令
  • 读写 UCI 配置:读取、导出、单项读取/设置/删除,以及备份创建、列出与恢复
  • 管理分流规则段:列出、查看、创建、更新、启用/停用、删除,并可创建隧道与通用子项
  • 管理域名与社区列表:列出、添加、删除、替换、查找覆盖情况、推荐未覆盖主机、检查 DNS/FakeIP、更新列表
  • 处理连接与监控:订阅增删改、代理组与延迟、实时连接、流量速率
适用场景
适用于在 OpenWrt 路由器上运行 Podkop 或 Forkop,并希望由助手修改分流域名、规则段或订阅,而不必手写 UCI 命令;也适合排查流量是否走代理。
运行要求
本机需 Python 3.10+,可用 pip 或 uvx 运行;需要已安装 Forkop 或 Podkop 的 OpenWrt 路由器,以及 SSH 密钥或密码访问。必填 PODKOP_MCP_SSH_HOST(或 SSH 配置别名);可选 PODKOP_MCP_SSH_USER、PODKOP_MCP_SSH_KEY_PATH、PODKOP_MCP_KNOWN_HOSTS、PODKOP_MCP_HOST_KEY_POLICY、PODKOP_MCP_READ_ONLY、PODKOP_MCP_CONFIG(多路由器)。
安装前请注意
它持有路由器的 SSH 凭据,包括私钥路径、内联密钥、口令或密码变量,应仅通过环境变量传入。写入类工具会修改路由器配置并重载服务,默认 dry-run,apply=true 时先备份、失败自动回滚;PODKOP_MCP_READ_ONLY=1 可禁止写入。reveal_secrets=true 会暴露订阅链接、代理字符串和密钥。主机密钥策略默认 reject,accept-new 无法防御首次连接的中间人攻击。项目为 alpha 且非官方。

安装

在 SourceWeft 中

  1. 打开 控制台中的 podkop-mcp,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

podkop-mcp

Languages: English · Русский

[CI] [PyPI] [Python versions] [License: MIT]

MCP server for managing Podkop and Forkop on OpenWrt over SSH.

Stop editing UCI by hand, remembering the uci add_list trap and passing base64-wrapped scripts to PowerShell. Ask your MCP client to add domains, list sections, inspect live connections, reload the service and roll back a bad change — with dry-run, backups and redaction built in.

Status: alpha. Live read-only tested against Forkop 1.0.5 and Forkop 2.0.0 (OpenWrt 25.12.6). Write/apply paths were verified in disposable OpenWrt Docker containers with real Forkop 2.0.0 and Podkop 0.7.24. Backend schema is revalidated every PODKOP_MCP_PROFILE_TTL seconds (default 30).

Why

Managing Podkop/Forkop by hand is error-prone:

  • Forkop 1.x stores domains in one space-separated string (section.domain). One uci add_list turns it into an exact-match domain list and subdomains silently stop being routed.
  • Forkop 2.x uses a multi-line section.destinations text with # comments.
  • Podkop uses a real UCI list (section.user_domains).
  • Forkop reloads asynchronously; a failed reload must be detected and rolled back.
  • Subscription URLs and proxy strings are secrets and end up in logs.

Unlike MCP servers that run on the router itself, podkop-mcp runs on the administrator's machine, talks to OpenWrt over SSH and understands Podkop/Forkop semantics: dry-run, backups, rollback and secret redaction. All of that is hidden behind typed MCP tools.

Features

  • SSH transport with a key from MCP parameters: key file, inline base64/PEM, or ~/.ssh/config alias. Host key verification, keepalive, timeouts.
  • Backend detection: Forkop 1.x, Forkop 2.x, Podkop; capabilities derived from the installed CLI.
  • Full UCI access via ubus call uci get JSON and uci for writes. Any option can be read or changed; the package is restricted to forkop/podkop.
  • Domain operations that preserve the backend format: add/remove/replace, search coverage, suggest uncovered hosts from live connections, check FakeIP.
  • Sections: list, inspect, create, update, enable/disable, delete with cascade of child subscriptions/URLTest items.
  • Subscriptions and proxy groups: list, add, delete, update, test latency, select a proxy (Forkop 2.x).
  • Monitoring: service/system status, health checks, logs, live connections, traffic rates, proxy delays.
  • Safety: mutating tools are dry-run by default; apply=true creates a router-side backup, commits UCI, reloads and verifies. On failure the backup is restored automatically. PODKOP_MCP_READ_ONLY=1 blocks all writes.
  • Secrets redacted by default: subscription URLs, proxy_string, keys, tokens. Raw export requires reveal_secrets=true.
  • Bilingual docs (English / Russian) and CI.

Support matrix

CapabilityForkop 1.xForkop 2.xPodkop
UCI packageforkopforkoppodkop
Domain optiondomain (string)destinations (multiline)user_domains (list)
Community listsyesyesyes
Rule setsnoyesyes
Clash APIaction CLIHTTP CLIaction CLI
Proxy runtime select—yes—
Read-only monitoringyesyesyes

Install

Requirements:

  • Python 3.10+ (CI tests 3.10 through 3.14)
  • OpenWrt router with Forkop or Podkop installed
  • SSH key access to the router

Install from PyPI:

bash
pip install podkop-mcp# or run without installinguvx podkop-mcp

Install from source:

bash
git clone https://github.com/mikel38487/podkop-mcp.gitcd podkop-mcppython -m venv .venv. .venv/bin/activate          # Windows: .venv\Scripts\activatepip install -e .

Configure your MCP client (Claude Desktop, Cursor, DSH, etc.):

json
{  "mcpServers": {    "podkop": {      "command": "podkop-mcp",      "env": {        "PODKOP_MCP_SSH_HOST": "192.168.1.1",        "PODKOP_MCP_SSH_USER": "root",        "PODKOP_MCP_SSH_KEY_PATH": "C:/Users/<user>/.ssh/router_key",        "PODKOP_MCP_KNOWN_HOSTS": "C:/Users/<user>/.ssh/known_hosts",        "PODKOP_MCP_HOST_KEY_POLICY": "reject"      }    }  }}

Or use an existing ~/.ssh/config alias:

json
"env": {  "PODKOP_MCP_SSH_CONFIG_HOST": "router-home",  "PODKOP_MCP_SSH_KEY_PATH": "C:/Users/<user>/.ssh/router_key",  "PODKOP_MCP_HOST_KEY_POLICY": "reject"}

Host key policy defaults to reject. Add the router key to known_hosts first (for example with a normal ssh login). accept-new is a one-time TOFU convenience for a new router; it does not protect against a MITM on the first connection.

Ask the model:

  • “Run podkop_doctor and show the backend version.”
  • “Add example.com and cdn.example.com to main; show me the diff first.”
  • “List sections with domain counts.”
  • “Which hosts in live connections are not covered by any section?”
  • “Show traffic for the last 2 seconds and the top clients.”
  • “Disable the youtube section and reload.”

Documentation

How write operations work

dry-run (default)  → read config, compute normalized diff, show uci commands
apply=true  → cp /etc/config/<pkg> /etc/podkop-mcp/backups/<ts>-<reason>.uci  → uci set/add_list/delete (package restricted to forkop/podkop)  → uci commit  → /etc/init.d/<pkg> reload  → poll get_status up to ~35 s  → on failure: restore backup, commit, reload  → return diff, backup id, health summary

No command is executed through a shell string built from user input; values are shell-quoted with shlex and the script is sent to sh via stdin. Paths are validated; no base64 applet is required on the router.

Tool overview

GroupTools
Systempodkop_doctor, podkop_status, podkop_system_info, podkop_health, podkop_logs, podkop_cli, podkop_core_features, podkop_import_check, podkop_installer_status, podkop_installer_log
Configpodkop_config_get, podkop_config_export, podkop_option_get/set/delete, podkop_backup_create/list/restore
Sectionspodkop_sections_list, podkop_section_get/create/update/delete/enable, podkop_tunnel_create, podkop_child_create
Domainspodkop_domains_list/add/remove/replace/find/suggest, podkop_domain_check
Listspodkop_community_lists_get/update
Connectionspodkop_connections_config, podkop_subscription_add/delete/update, podkop_proxies_list, podkop_proxy_select/test
Monitoringpodkop_monitor_snapshot, podkop_connections_live, podkop_traffic
Servicepodkop_service_action, podkop_lists_update

Full reference: docs/tools.md.

Configuration reference

VariableDefaultMeaning
PODKOP_MCP_SSH_HOST—Router address
PODKOP_MCP_SSH_PORT22SSH port
PODKOP_MCP_SSH_USERrootSSH user
PODKOP_MCP_SSH_KEY_PATH—Private key path
PODKOP_MCP_SSH_KEY—Inline private key (PEM)
PODKOP_MCP_SSH_KEY_B64—Inline key, base64
PODKOP_MCP_SSH_KEY_PASSPHRASE—Key passphrase
PODKOP_MCP_SSH_PASSWORD—Password instead of a key (secret: pass it through the environment, never commit it)
PODKOP_MCP_SSH_CONFIG_HOST—Alias from ~/.ssh/config
PODKOP_MCP_KNOWN_HOSTS~/.ssh/known_hostsHost key file
PODKOP_MCP_HOST_KEY_POLICYrejectreject, accept-new, ignore
PODKOP_MCP_CONNECT_TIMEOUT8SSH connect timeout, seconds
PODKOP_MCP_COMMAND_TIMEOUT45Command timeout, seconds
PODKOP_MCP_BACKENDautoauto, forkop or podkop; the requested CLI must be installed
PODKOP_MCP_READ_ONLY0Block all writes
PODKOP_MCP_BACKUP_DIR/etc/podkop-mcp/backupsRouter-side backups
PODKOP_MCP_BACKUP_KEEP30Number of backups to keep
PODKOP_MCP_MAX_OUTPUT_BYTES200000Cap on captured command output, bytes
PODKOP_MCP_LOG_LEVELINFOServer log level on stderr (DEBUG, INFO, WARNING, ERROR, CRITICAL)
PODKOP_MCP_PROFILE_TTL30Seconds before the backend profile is revalidated against the router
PODKOP_MCP_TARGETS—JSON with multiple routers
PODKOP_MCP_CONFIG—Path to a JSON file with multiple routers; takes precedence over PODKOP_MCP_TARGETS and the single-router variables
PODKOP_MCP_TARGET_NAMErouterName of the single router defined by PODKOP_MCP_SSH_HOST / PODKOP_MCP_SSH_CONFIG_HOST; ignored when a JSON target list is used

To manage several routers, put them in a JSON file and point PODKOP_MCP_CONFIG at it:

json
{  "targets": {    "home": {"host": "192.0.2.10"},    "office": {"host": "192.0.2.11", "port": 2222}  },  "default_target": "home"}
json
"env": {  "PODKOP_MCP_CONFIG": "~/.config/podkop-mcp/targets.json"}

The default_target router (or the first one when it is omitted) is used when a tool call does not pass target; pass "target": "office" to use the other router. PODKOP_MCP_TARGET_NAME does not select a target from this file — it only names the single router defined by PODKOP_MCP_SSH_HOST / PODKOP_MCP_SSH_CONFIG_HOST.

Testing

bash
python -m pytest -q                 # unit + fake router + live/docker skippedpython scripts/smoke_mcp.py         # MCP stdio handshakePODKOP_MCP_LIVE=1 python -m pytest -q -m live   # read-only, real router

For the real SSH/UCI/apply path there is an OpenWrt Docker container: real openwrt/rootfs userland, real ubus/rpcd/dropbear/uci, with stubs for the Forkop CLI/init (no sing-box/nft).

powershell
.\scripts\openwrt-docker.ps1. .\.tmp\openwrt-env.ps1.\.venv\Scripts\python.exe -m pytest -q -m docker_ssh.\scripts\openwrt-docker.ps1 -Stop

This is a container, not a VM: it shares the host kernel. Full VM instructions and limitations are in docs/testing.md. Live tests never write to the production router; write operations are tested in the Docker container and through the fake transport.

Limitations

  • MCP transport is stdio; SSE/HTTP is out of scope.
  • Reordering sections is not exposed yet (UCI order is file order); use export/edit
    • podkop_config_export if needed.
  • Podkop subscription handling is best-effort; the active target is Forkop.
  • Forkop 1.x does not support active per-proxy latency tests; use URLTest history.
  • No automatic Forkop/Podkop upgrade.

License

MIT. Forkop and Podkop are separate projects under their own licenses.

Not affiliated

This is an independent, unofficial project. It is not affiliated with, endorsed by or maintained by the Podkop, Forkop or OpenWrt projects.

来源:README.md,提交 c42d8b7

工具

47

podkop_doctor

Check MCP configuration, SSH connectivity, backend and capabilities. Use this first when something does not work: it explains missing environment variables, SSH key problems and the detected Podkop/Forkop version without changing the router.

输入参数target

podkop_status

Return service, sing-box, DNS and version status of the router.

输入参数target

podkop_core_features

Return sing-box features compiled into the installed Forkop core (read-only).

输入参数target

podkop_import_check

Validate a Forkop config file on the router without applying it (read-only).

输入参数path必填target

podkop_installer_status

Return the status of the last Forkop installer run (read-only).

输入参数offsettarget

podkop_installer_log

Return the last Forkop installer log, redacted (read-only).

输入参数target

podkop_system_info

Return model, OpenWrt version, kernel, load, memory, disk and WAN info.

输入参数target

podkop_health

Run read-only health checks: sing-box, nftables, FakeIP and DNS. ``full=true`` additionally runs the global network check, which can take up to a minute because it tests real connectivity.

输入参数fulltargettimeout

podkop_logs

Return recent router logs, filtered and redacted. ``source`` selects Forkop/Podkop journal, sing-box journal or the whole system log. Secrets in URLs are masked before returning.

输入参数linesfiltersourcetarget

podkop_cli

Run a read-only Forkop/Podkop CLI command from the allowlist. Mutating commands (start, stop, reload, list_update, subscription_update, component actions) are rejected: use dedicated tools so that backups and rollback are applied. Output is redacted unless reveal_secrets=true.

输入参数argstargetcommand必填timeoutparse_jsonreveal_secrets

podkop_config_get

Return the full UCI configuration as structured JSON. Secrets (subscription URLs, proxy strings, keys) are masked unless reveal_secrets=true. The structure is identical for Forkop 1.x/2.x and Podkop; use podkop_sections_list for a compact view.

输入参数targetpackageinclude_rawreveal_secrets

podkop_config_export

Export the UCI config as text (backup-style). ``raw`` requires reveal_secrets=true because it contains proxy URLs and keys. By default the masked export from Forkop is returned.

输入参数modetargetreveal_secrets

podkop_option_get

Read a single UCI option or section by path, e.g. forkop.main.domain.

输入参数path必填targetreveal_secrets

podkop_option_set

Set a UCI option or list. Dry-run by default; apply=true writes, backs up and reloads. ``value_type=auto`` sets a list when ``value`` is a list, otherwise an option. The tool preserves the backend package and never touches other OpenWrt packages.

输入参数path必填applyvalue必填reasontargetvalue_type

podkop_option_delete

Delete a UCI option. Dry-run by default; apply=true writes with backup.

输入参数path必填applyreasontarget

podkop_backup_create

Create a config backup on the router (dry-run by default).

输入参数applyreasontarget

podkop_backup_list

List config backups stored on the router.

输入参数limittarget

podkop_backup_restore

Restore a config backup by id. Dry-run shows the action; apply=true restores and reloads.

输入参数applytargetbackup_id必填

podkop_sections_list

List routing sections with action, enabled flag, domain counts and lists. This is the fastest overview of what Forkop/Podkop is doing. Child UCI sections (subscriptions, URLTest groups, links) are counted; set include_children=true to return them too.

输入参数targetreveal_secretsinclude_children

podkop_section_get

Return all options of one section plus its child items.

输入参数targetsection必填reveal_secretsinclude_children

podkop_section_create

Create a new routing section. Dry-run by default. Minimal defaults are backend-specific: Forkop gets enabled=1 and the requested action; Podkop gets connection_type=proxy. For zapret2 on Forkop 1.x you must pass nfqws2_opt in options, otherwise the generated config will be rejected by the core.

输入参数name必填applyactiontargetoptions

podkop_tunnel_create

Create a Forkop 2.x tunnel child (WARP/WireGuard/OpenVPN/...). Dry-run by default. ``kind`` is one of warp, wireguard, amneziawg, tailscale, openvpn-client, openconnect, json. Extra backend options are passed through; secrets in preview are masked.

输入参数kind必填name必填applylabeltargetoptionssection必填

podkop_child_create

Create a generic Forkop 2.x child (urltest/fallback_group/link/interface/json/tunnel_peer). This is a low-level escape hatch for new 2.0 entities; domain tools and podkop_subscription_add remain the safe high-level paths. Dry-run by default.

输入参数name必填applylabeltargetoptionssection必填child_type必填

podkop_section_update

Update selected options of a section (partial update). Dry-run by default. Use podkop_domains_add/remove for domain lists: this tool writes raw values but does not normalize domain syntax.

输入参数applytargetoptions必填section必填

podkop_section_delete

Delete a section. Child items are deleted when cascade=true. Deleting the main section is refused unless allow_main=true; it is usually a configuration error. Dry-run by default.

输入参数applytargetcascadesection必填allow_main

podkop_section_enable

Enable or disable a section without deleting it.

输入参数applytargetenabled必填section必填

podkop_domains_list

List domains of one section, normalized plus the raw stored value. Works for all backends: Forkop 1.x keeps domain as one space-separated string, Forkop 2.x keeps one entry per line in destinations, Podkop uses the user_domains UCI list. Use the returned storage/option values to understand why entries are written the way they are.

输入参数targetsection

podkop_domains_add

Add domains to a section preserving the backend storage type. Forkop 1.x keeps section.domain as a single string: the tool never turns it into a UCI list (that would break domain_suffix matching). Forkop 2.x writes multiline destinations, Podkop uses the user_domains list. Duplicates are reported and not written again; invalid entries raise an error. Dry-run by default: the diff and UCI preview are returned and the router is untouched. With apply=true the config is written, the service is reloaded and, if verify=true, the first added domain is resolved via the router DNS to confirm it maps to a FakeIP address.

输入参数applytargetverifydomains必填section必填

podkop_domains_remove

Remove domains from a section, keeping the storage type intact. Domains that are not present are reported in not_found and do not make the call fail. Dry-run by default.

输入参数applytargetdomains必填section必填

podkop_domains_replace

Replace the whole domain set of a section. Every entry is validated first; on Forkop 2.x comments and blank lines of the existing destinations block are preserved. Use podkop_domains_add or podkop_domains_remove for incremental changes. Dry-run by default.

输入参数applytargetdomains必填section必填

podkop_domains_find

Find where a domain is covered by section entries. Checks every top-level section and returns exact matches with the entry kind (domain, zone, full, keyword, regex, ip). possible_lists shows community lists selected in sections: their content lives on the router as rule-sets, so the server cannot match a domain against them.

输入参数domain必填target

podkop_domains_suggest

Suggest hosts seen in live connections but not covered by any section. Groups hosts from the Clash API by traffic, skips IP addresses, localhost and already covered hosts. With only_direct=true (default) only hosts whose chain is direct are suggested, i.e. traffic that currently bypasses the proxy and may need a rule. Feed the result to podkop_domains_add (suggested_section is usually main).

输入参数limittargetonly_direct

podkop_domain_check

Check one domain: DNS resolution, FakeIP, section coverage, HTTPS probe. Resolves the domain through the router DNS (127.0.0.1) without touching the configuration. fakeip=true means the domain maps to 198.18.x.x or fc00::/18 (sing-box FakeIP ranges) and therefore goes through the proxy. probe_http runs a curl HEAD request on the router; curl failures are reported in http_error instead of raising.

输入参数domain必填targetprobe_http

podkop_community_lists_get

Return selected and available community lists of sections. With section: the selected lists of that section plus the built-in catalogue of known names. Without section: a map for every top-level section. Names outside the catalogue are reported in unknown (custom rule-sets are allowed and never removed automatically).

输入参数targetsection

podkop_community_lists_update

Add or remove community lists of one section as a UCI list. Names must match ^[a-z0-9_]+$; duplicates are dropped and the order of existing lists is preserved. The whole list is written at once, so no per-item add_list/del_list calls are issued. Names outside the built-in catalogue are reported in unknown but accepted. Dry-run by default; apply=true writes the config, creates a backup and reloads the service.

输入参数addapplyremovetargetsection必填

podkop_connections_config

List routing sections with subscriptions, urltest groups, links and tunnels. Subscription URLs and proxy strings are masked unless reveal_secrets=true. This is a read-only map of how connections are configured; use podkop_subscription_* to modify them.

输入参数targetsectionreveal_secrets

podkop_subscription_add

Add a subscription to a section. Dry-run by default; apply=true writes. Accepts http(s):// URLs or local absolute paths (max 4096 chars). Forkop creates a child section {section}_sub_N with sensible defaults; Podkop writes subscription_url inline into the section.

输入参数url必填applytargetoptionssection必填

podkop_subscription_delete

Delete a subscription by name, 1-based index, tag or raw URL. When ``section`` is given, only its children are considered. Podkop removes the subscription_url option instead. Dry-run by default.

输入参数applytargetsectionidentifier必填

podkop_subscription_update

Refresh subscription cache on the router (apply must be true). Uses async update with status polling when the backend supports it, otherwise runs the blocking subscription_update command. Bounded by ``timeout`` seconds.

输入参数waitapplytargetsectiontimeout

podkop_proxies_list

List Clash proxy groups and nodes with delays. With ``group``, returns that group's members (all) and its current selection (now). Read-only.

输入参数grouptarget

podkop_proxy_select

Select a proxy inside a Clash group (runtime change, not config). Dry-run shows what would be selected. apply=true performs the switch; on Forkop 1.x this raises BackendError because runtime selection is not supported there — that is expected behaviour.

输入参数applygroup必填proxy必填target

podkop_proxy_test

Run read-only proxy check and report selected-group delays. Calls check_proxy via CLI when available; falls back to reporting delays of currently selected proxies from Clash API history.

输入参数target

podkop_monitor_snapshot

Return a monitoring snapshot: service, system, proxies, connections, traffic. Collects everything available in one call; each part fails independently (errors are reported in check_errors instead of raising). Read-only and safe to call repeatedly.

输入参数toptargetinclude_connections

podkop_connections_live

List live connections with optional filters (host, source_ip, chain). ``host`` and ``chain`` are case-insensitive substrings; ``source_ip`` matches exactly or as a network prefix ("192.0.2." matches all clients in that subnet). Read-only.

输入参数hostchainlimittargetsource_ip

podkop_traffic

Measure traffic rate by sampling Clash connections twice. Returns download/upload bps, per-connection deltas and the top consumers. ``sample_seconds`` is clamped to 0.5..10 s. Read-only; the call blocks for the sampling interval.

输入参数toptargetsample_seconds

podkop_service_action

Start, stop, restart, reload or toggle autostart of Podkop/Forkop. Dry-run by default. ``stop`` is refused in read-only mode. Use podkop_apply-style tools for config changes; this tool only controls the service lifecycle.

输入参数waitapplyaction必填targettimeout

podkop_lists_update

Update built-in lists and/or subscriptions. This changes the router's downloaded rule sets and subscription cache, so apply=true is required. Long operations are bounded by ``timeout``.

输入参数kindwaitapplytargettimeout

版本历史

1
  1. v0.2.2最新Nov 1, 2026

评分

已满足适用检查项的 90%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分
  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具必需15 分
  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分