podkop-mcp

io.github.mikel38487Published by GitHub account mikel38487v0.2.2Updated Nov 1, 2026

MCP server for managing Podkop and Forkop on OpenWrt over SSH

Overview

AI-generated overview

Manages Podkop and Forkop proxy routing on an OpenWrt router over SSH, letting an assistant inspect, edit and back up UCI config with dry-run and redaction.

What you can do
  • Check router and backend health: doctor, status, system info, health checks, logs, installer status and read-only CLI commands
  • Read and edit UCI config: get, export, option get/set/delete, backups create/list/restore
  • Manage routing sections: list, inspect, create, update, enable/disable, delete, plus tunnel and generic child creation
  • Manage domains and community lists: list, add, remove, replace, find coverage, suggest uncovered hosts, check DNS/FakeIP, update lists
  • Handle connections and monitoring: subscriptions add/delete/update, proxy groups and delays, live connections, traffic rates
When to use it
Useful when you run Podkop or Forkop on an OpenWrt router and want an assistant to change routing domains, sections or subscriptions without hand-editing UCI, or to diagnose why traffic is or is not going through the proxy.
Requirements
Python 3.10+ locally, run via pip or uvx; an OpenWrt router with Forkop or Podkop installed; SSH access with a key or password. Required: PODKOP_MCP_SSH_HOST (or an SSH config alias). Optional: 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 for multiple routers.
Before you install
It holds SSH credentials to a router, including the private key path, inline key, passphrase or password variables; keep them in the environment only. Write tools change router config and reload services, though they are dry-run by default and apply=true backs up and rolls back on failure; PODKOP_MCP_READ_ONLY=1 blocks writes. reveal_secrets=true exposes subscription URLs, proxy strings and keys. Host key policy defaults to reject; accept-new does not protect against a first-connection MITM…

Installation

In SourceWeft

  1. Open podkop-mcp in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

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.

Source: README.md at commit c42d8b7

Tools

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.

Parameterstarget

podkop_status

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

Parameterstarget

podkop_core_features

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

Parameterstarget

podkop_import_check

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

Parameterspathrequiredtarget

podkop_installer_status

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

Parametersoffsettarget

podkop_installer_log

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

Parameterstarget

podkop_system_info

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

Parameterstarget

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.

Parametersfulltargettimeout

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.

Parameterslinesfiltersourcetarget

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.

Parametersargstargetcommandrequiredtimeoutparse_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.

Parameterstargetpackageinclude_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.

Parametersmodetargetreveal_secrets

podkop_option_get

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

Parameterspathrequiredtargetreveal_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.

Parameterspathrequiredapplyvaluerequiredreasontargetvalue_type

podkop_option_delete

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

Parameterspathrequiredapplyreasontarget

podkop_backup_create

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

Parametersapplyreasontarget

podkop_backup_list

List config backups stored on the router.

Parameterslimittarget

podkop_backup_restore

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

Parametersapplytargetbackup_idrequired

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.

Parameterstargetreveal_secretsinclude_children

podkop_section_get

Return all options of one section plus its child items.

Parameterstargetsectionrequiredreveal_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.

Parametersnamerequiredapplyactiontargetoptions

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.

Parameterskindrequirednamerequiredapplylabeltargetoptionssectionrequired

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.

Parametersnamerequiredapplylabeltargetoptionssectionrequiredchild_typerequired

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.

Parametersapplytargetoptionsrequiredsectionrequired

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.

Parametersapplytargetcascadesectionrequiredallow_main

podkop_section_enable

Enable or disable a section without deleting it.

Parametersapplytargetenabledrequiredsectionrequired

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.

Parameterstargetsection

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.

Parametersapplytargetverifydomainsrequiredsectionrequired

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.

Parametersapplytargetdomainsrequiredsectionrequired

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.

Parametersapplytargetdomainsrequiredsectionrequired

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.

Parametersdomainrequiredtarget

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).

Parameterslimittargetonly_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.

Parametersdomainrequiredtargetprobe_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).

Parameterstargetsection

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.

Parametersaddapplyremovetargetsectionrequired

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.

Parameterstargetsectionreveal_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.

Parametersurlrequiredapplytargetoptionssectionrequired

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.

Parametersapplytargetsectionidentifierrequired

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.

Parameterswaitapplytargetsectiontimeout

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.

Parametersgrouptarget

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.

Parametersapplygrouprequiredproxyrequiredtarget

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.

Parameterstarget

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.

Parameterstoptargetinclude_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.

Parametershostchainlimittargetsource_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.

Parameterstoptargetsample_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.

Parameterswaitapplyactionrequiredtargettimeout

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``.

Parameterskindwaitapplytargettimeout

Version history

1
  1. v0.2.2LatestNov 1, 2026

Score

90% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts
  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts