podkop-mcp
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_TTLseconds (default 30).
Why
Managing Podkop/Forkop by hand is error-prone:
- Forkop 1.x stores domains in one space-separated string (
section.domain). Oneuci add_listturns it into an exact-matchdomainlist and subdomains silently stop being routed. - Forkop 2.x uses a multi-line
section.destinationstext 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/configalias. 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 getJSON anducifor writes. Any option can be read or changed; the package is restricted toforkop/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=truecreates a router-side backup, commits UCI, reloads and verifies. On failure the backup is restored automatically.PODKOP_MCP_READ_ONLY=1blocks all writes. - Secrets redacted by default: subscription URLs,
proxy_string, keys, tokens. Raw export requiresreveal_secrets=true. - Bilingual docs (English / Russian) and CI.
Support matrix
| Capability | Forkop 1.x | Forkop 2.x | Podkop |
|---|---|---|---|
| UCI package | forkop |
forkop |
podkop |
| Domain option | domain (string) |
destinations (multiline) |
user_domains (list) |
| Community lists | yes | yes | yes |
| Rule sets | no | yes | yes |
| Clash API | action CLI | HTTP CLI | action CLI |
| Proxy runtime select | — | yes | — |
| Read-only monitoring | yes | yes | yes |
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:
pip install podkop-mcp
# or run without installing
uvx podkop-mcp
Install from source:
git clone https://github.com/mikel38487/podkop-mcp.git
cd podkop-mcp
python -m venv .venv
. .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .
Configure your MCP client (Claude Desktop, Cursor, DSH, etc.):
{
"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:
"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_doctorand show the backend version.” - “Add
example.comandcdn.example.comtomain; 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
youtubesection and reload.”
Documentation
- Tool reference · Справочник инструментов
- Architecture · Архитектура
- Compatibility · Совместимость
- Troubleshooting · Диагностика
- Testing in OpenWrt Docker · Тестирование в Docker
- Security · Безопасность
- Security policy, contributing
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
| Group | Tools |
|---|---|
| System | podkop_doctor, podkop_status, podkop_system_info, podkop_health, podkop_logs, podkop_cli, podkop_core_features, podkop_import_check, podkop_installer_status, podkop_installer_log |
| Config | podkop_config_get, podkop_config_export, podkop_option_get/set/delete, podkop_backup_create/list/restore |
| Sections | podkop_sections_list, podkop_section_get/create/update/delete/enable, podkop_tunnel_create, podkop_child_create |
| Domains | podkop_domains_list/add/remove/replace/find/suggest, podkop_domain_check |
| Lists | podkop_community_lists_get/update |
| Connections | podkop_connections_config, podkop_subscription_add/delete/update, podkop_proxies_list, podkop_proxy_select/test |
| Monitoring | podkop_monitor_snapshot, podkop_connections_live, podkop_traffic |
| Service | podkop_service_action, podkop_lists_update |
Full reference: docs/tools.md.
Configuration reference
| Variable | Default | Meaning |
|---|---|---|
PODKOP_MCP_SSH_HOST |
— | Router address |
PODKOP_MCP_SSH_PORT |
22 |
SSH port |
PODKOP_MCP_SSH_USER |
root |
SSH 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_hosts |
Host key file |
PODKOP_MCP_HOST_KEY_POLICY |
reject |
reject, accept-new, ignore |
PODKOP_MCP_CONNECT_TIMEOUT |
8 |
SSH connect timeout, seconds |
PODKOP_MCP_COMMAND_TIMEOUT |
45 |
Command timeout, seconds |
PODKOP_MCP_BACKEND |
auto |
auto, forkop or podkop; the requested CLI must be installed |
PODKOP_MCP_READ_ONLY |
0 |
Block all writes |
PODKOP_MCP_BACKUP_DIR |
/etc/podkop-mcp/backups |
Router-side backups |
PODKOP_MCP_BACKUP_KEEP |
30 |
Number of backups to keep |
PODKOP_MCP_MAX_OUTPUT_BYTES |
200000 |
Cap on captured command output, bytes |
PODKOP_MCP_LOG_LEVEL |
INFO |
Server log level on stderr (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
PODKOP_MCP_PROFILE_TTL |
30 |
Seconds 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_NAME |
router |
Name 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:
{
"targets": {
"home": {"host": "192.0.2.10"},
"office": {"host": "192.0.2.11", "port": 2222}
},
"default_target": "home"
}
"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
python -m pytest -q # unit + fake router + live/docker skipped
python scripts/smoke_mcp.py # MCP stdio handshake
PODKOP_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).
.\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_exportif 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.
Metadata
Release files for podkop-mcp 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| podkop_mcp-0.2.0.tar.gz | 167.7 kB | With provenance | Details for podkop_mcp-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| podkop_mcp-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for podkop_mcp-0.2.0-py3-none-any.whl |
Total release size: 247.7 kB
Release files / podkop_mcp-0.2.0.tar.gz
| Download URL | podkop_mcp-0.2.0.tar.gz |
|---|---|
| Size | 167.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2df4dec2a80b8772a4a4cbd891f01d8f0dbed3ec38d827f06b2c4e7951fdcc83
|
|
BLAKE2b-256 checksum How to use checksums |
a4b231dca8ae61f5f397602f53a65d5d9ccfbebc89e6d9be36d69b0d722be5a3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logRelease files / podkop_mcp-0.2.0-py3-none-any.whl
| Download URL | podkop_mcp-0.2.0-py3-none-any.whl |
|---|---|
| Size | 80.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3d121e1580cde9f2ee44f6ae2691a5ea29255a8b7449dcb02ca53572de9db9e6
|
|
BLAKE2b-256 checksum How to use checksums |
eba8b50c4e3fcebb0c63201f3be47defdcbdb5ab037560343646f9ef66c9e73b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logSecurity for podkop-mcp 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for podkop_mcp-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.