node804-panos-mcp 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- node804_panos_mcp-0.1.6.dist-info/METADATA +376 -0
- node804_panos_mcp-0.1.6.dist-info/RECORD +40 -0
- node804_panos_mcp-0.1.6.dist-info/WHEEL +4 -0
- node804_panos_mcp-0.1.6.dist-info/entry_points.txt +2 -0
- node804_panos_mcp-0.1.6.dist-info/licenses/LICENSE +21 -0
- panos_mcp/__init__.py +34 -0
- panos_mcp/__main__.py +20 -0
- panos_mcp/_redact.py +69 -0
- panos_mcp/client.py +155 -0
- panos_mcp/config/__init__.py +0 -0
- panos_mcp/config/firewalls.py +336 -0
- panos_mcp/py.typed +0 -0
- panos_mcp/responses/__init__.py +0 -0
- panos_mcp/responses/schemas.py +493 -0
- panos_mcp/responses/shapers.py +165 -0
- panos_mcp/server.py +119 -0
- panos_mcp/tools/__init__.py +0 -0
- panos_mcp/tools/_common.py +506 -0
- panos_mcp/tools/_guards.py +271 -0
- panos_mcp/tools/_schemas.py +96 -0
- panos_mcp/tools/_xml_writers.py +93 -0
- panos_mcp/tools/admin.py +98 -0
- panos_mcp/tools/admin_ops.py +316 -0
- panos_mcp/tools/aggregations.py +720 -0
- panos_mcp/tools/certificates.py +249 -0
- panos_mcp/tools/diagnostics.py +362 -0
- panos_mcp/tools/firewalls.py +43 -0
- panos_mcp/tools/licenses.py +73 -0
- panos_mcp/tools/logs.py +210 -0
- panos_mcp/tools/nat.py +173 -0
- panos_mcp/tools/network.py +365 -0
- panos_mcp/tools/objects.py +454 -0
- panos_mcp/tools/panorama.py +1323 -0
- panos_mcp/tools/security.py +679 -0
- panos_mcp/tools/status.py +52 -0
- panos_mcp/tools/system.py +181 -0
- panos_mcp/tools/threat.py +122 -0
- panos_mcp/tools/userid.py +114 -0
- panos_mcp/tools/utility.py +352 -0
- panos_mcp/tools/vpn.py +127 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: node804-panos-mcp
|
|
3
|
+
Version: 0.1.6
|
|
4
|
+
Summary: Token-efficient MCP server for Palo Alto Networks PAN-OS firewalls and Panorama
|
|
5
|
+
Project-URL: Homepage, https://github.com/Node804/node804-panos-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/Node804/node804-panos-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/Node804/node804-panos-mcp/issues
|
|
8
|
+
Author-email: Michael Pope <me@michaelpope.cv>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: firewall,mcp,model-context-protocol,network-security,palo-alto,pan-os,panorama,panos
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Information Technology
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: System :: Networking :: Firewalls
|
|
21
|
+
Classifier: Topic :: System :: Systems Administration
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.13
|
|
24
|
+
Requires-Dist: httpx<1.0,>=0.28.1
|
|
25
|
+
Requires-Dist: keyring<30,>=25.0
|
|
26
|
+
Requires-Dist: mcp[cli]<2.0,>=1.3.0
|
|
27
|
+
Requires-Dist: node804-mcp-toolkit<1.0,>=0.1.0
|
|
28
|
+
Requires-Dist: pan-os-python<2.0,>=1.12.0
|
|
29
|
+
Requires-Dist: pydantic<3.0,>=2.10.6
|
|
30
|
+
Requires-Dist: python-dotenv<2.0,>=1.0.0
|
|
31
|
+
Requires-Dist: setuptools>=68
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: mypy<2.0,>=1.10; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-asyncio<2.0,>=0.23; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-cov<9.0,>=5.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest<11.0,>=8.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
38
|
+
Requires-Dist: tiktoken<1.0,>=0.7; extra == 'dev'
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# PAN-OS MCP Server
|
|
42
|
+
|
|
43
|
+
[](https://github.com/Node804/node804-panos-mcp/actions/workflows/ci.yml)
|
|
44
|
+
[](https://pypi.org/project/node804-panos-mcp/)
|
|
45
|
+
[](https://pypi.org/project/node804-panos-mcp/)
|
|
46
|
+
[](LICENSE)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
A token-efficient [MCP](https://modelcontextprotocol.io/) server for **Palo Alto Networks PAN-OS firewalls and Panorama**, built around three principles:
|
|
50
|
+
|
|
51
|
+
1. **Lean responses by default.** Every list-returning tool exposes only the fields you actually need to answer the question. Verbose responses are opt-in. Typical 40-70% token reduction vs. dump-everything alternatives.
|
|
52
|
+
2. **Default-deny RBAC.** With no environment configuration, the server exposes only read tools. Writes, commits, and raw API access require explicit opt-in via `PANOS_MODE`.
|
|
53
|
+
3. **TLS verification on by default.** Self-signed certs require an explicit opt-out or a custom CA bundle — no silent man-in-the-middle exposure.
|
|
54
|
+
|
|
55
|
+
**137 tools** across system, diagnostics/troubleshooting, network, security, objects, NAT, user-ID, admin, VPN, Panorama, logs, threat, certificates, licenses, utility, and aggregation surfaces.
|
|
56
|
+
|
|
57
|
+
## Permission Modes
|
|
58
|
+
|
|
59
|
+
| Mode | Tools | Description |
|
|
60
|
+
|------|-------|-------------|
|
|
61
|
+
| `read` *(default)* | 89 | Read-only inspection, live troubleshooting (policy-match / NAT-match / FIB-lookup tests, session table, config diff, rule hit counts), and aggregation summaries. No writes. |
|
|
62
|
+
| `standard` | 99 | Read + object and tag CRUD (address objects, service objects, tags). No policy rules. |
|
|
63
|
+
| `full` | 130 | Standard + policy rule CRUD (security, NAT, PBF, QoS, decryption, static routes; Panorama pre/post rules). **All writes go to candidate config — no live impact without commit.** |
|
|
64
|
+
| `admin` | 137 | Full + `commit`, push, `revert_config`, and the raw `set_config` / `delete_config` / `run_op_command` escape hatches. **This is the live-impact tier.** |
|
|
65
|
+
|
|
66
|
+
Tools above the active mode are not registered with the MCP server — the LLM never sees them, so there is nothing to call. Default mode is `read`.
|
|
67
|
+
|
|
68
|
+
## Setup
|
|
69
|
+
|
|
70
|
+
### Prerequisites
|
|
71
|
+
|
|
72
|
+
- **[uv](https://docs.astral.sh/uv/)** (recommended) — manages Python and dependencies for you
|
|
73
|
+
- or **Python 3.13+** with pip
|
|
74
|
+
- A **PAN-OS firewall or Panorama** with the XML API enabled, plus an API key (see below)
|
|
75
|
+
|
|
76
|
+
### Getting a PAN-OS API key
|
|
77
|
+
|
|
78
|
+
PAN-OS issues API keys via the `/api/?type=keygen` endpoint. The key inherits the admin account's RBAC — there is no separate "API role," so a read-only admin's key cannot push config even if `PANOS_MODE=admin`.
|
|
79
|
+
|
|
80
|
+
> **Best practice:** create a dedicated service account (e.g. `apiread`) with a **Superuser (read-only)** dynamic role for `PANOS_MODE=read`. Issue a separate key from an account with write permissions for higher tiers.
|
|
81
|
+
|
|
82
|
+
**curl:**
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
curl -k -X POST "https://fw.example.com/api/?type=keygen" \
|
|
86
|
+
--data-urlencode "user=apiread" \
|
|
87
|
+
--data-urlencode "password=<password>"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**PowerShell:**
|
|
91
|
+
|
|
92
|
+
```powershell
|
|
93
|
+
$body = @{ user = 'apiread'; password = '<password>' }
|
|
94
|
+
(Invoke-RestMethod -Method Post -Uri 'https://fw.example.com/api/?type=keygen' -Body $body -SkipCertificateCheck).response.result.key
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Copy the returned `<key>` into `PANOS_API_KEY`. The same endpoint works against Panorama — point it at the Panorama hostname. Keys do not expire by default but are invalidated if the admin's password changes or the account is deleted.
|
|
98
|
+
|
|
99
|
+
### Environment Variables
|
|
100
|
+
|
|
101
|
+
| Variable | Required | Description |
|
|
102
|
+
|----------|----------|-------------|
|
|
103
|
+
| `PANOS_HOST` | Yes\* | Single-firewall hostname. \*Not required in multi-firewall mode (see [Multi-firewall configuration](#multi-firewall-configuration)). |
|
|
104
|
+
| `PANOS_API_KEY` | Yes\* | Single-firewall API key. Stored in the OS keychain in multi-firewall mode. |
|
|
105
|
+
| `PANOS_MODE` | No | RBAC mode: `read`, `standard`, `full`, or `admin`. Default: `read`. |
|
|
106
|
+
| `PANOS_TLS_VERIFY` | No | `true` / `false` / `1` / `0` / `yes` / `no`. Default: `true`. |
|
|
107
|
+
| `PANOS_TLS_CA` | No | Path to a custom PEM CA bundle. Default: system trust store. |
|
|
108
|
+
| `PANOS_FIREWALLS_CONFIG` | No | Override path for `firewalls.json`. Default: `~/.config/panos-mcp/firewalls.json`. |
|
|
109
|
+
| `PANOS_AUDIT_LOG` | No | Path to a JSON-lines audit log. Every tool call is logged with sanitized args, success/error state, and timing. Disabled when unset. |
|
|
110
|
+
| `PANOS_ENABLE_RAW_CONFIG` | No | Register `set_config` / `delete_config` (also requires `PANOS_MODE=admin`). Default: `false`. |
|
|
111
|
+
| `PANOS_ENABLE_RAW_OPS` | No | Register `run_op_command` (also requires `PANOS_MODE=admin`). Default: `false`. |
|
|
112
|
+
| `PANOS_RAW_XPATH_ALLOW` | No | Comma-separated XPath prefixes that `set_config` / `delete_config` are restricted to. Default: unset (all non-denied). |
|
|
113
|
+
|
|
114
|
+
The raw escape hatches are **off by default and gated twice**: they register only when their `PANOS_ENABLE_RAW_*` flag is truthy *and* `PANOS_MODE=admin`, and even then an internal denylist blocks destructive verbs and protected config subtrees. See [`tools/_guards.py`](src/panos_mcp/tools/_guards.py).
|
|
115
|
+
|
|
116
|
+
### Install
|
|
117
|
+
|
|
118
|
+
**Option A — uv (recommended).** No install step: `uvx` fetches the package from PyPI on first run and caches it. Pin the version so upgrades are deliberate:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
uvx node804-panos-mcp==0.1.6
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Option B — pip:**
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pip install node804-panos-mcp
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Option C — from source (development):**
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
git clone https://github.com/Node804/node804-panos-mcp.git
|
|
134
|
+
cd node804-panos-mcp
|
|
135
|
+
uv sync # or: pip install -e ".[dev]"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Each option provides the `node804-panos-mcp` command and the `panos_mcp` Python module (runnable as `python -m panos_mcp`). The package pulls in its dependency `node804-mcp-toolkit` — shared RBAC, audit-logging, TLS, and lean-response utilities — automatically.
|
|
139
|
+
|
|
140
|
+
> Until `node804-mcp-toolkit` is published to PyPI, install it from source first: `pip install git+https://github.com/Node804/node804-mcp-toolkit.git`
|
|
141
|
+
|
|
142
|
+
### Claude Desktop
|
|
143
|
+
|
|
144
|
+
1. Open your Claude Desktop config file:
|
|
145
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
146
|
+
- **Mac:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
147
|
+
|
|
148
|
+
2. Add the server under `mcpServers` (create the file if it doesn't exist):
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"mcpServers": {
|
|
153
|
+
"PAN-OS": {
|
|
154
|
+
"command": "uvx",
|
|
155
|
+
"args": ["node804-panos-mcp==0.1.6"],
|
|
156
|
+
"env": {
|
|
157
|
+
"PANOS_HOST": "fw.example.com",
|
|
158
|
+
"PANOS_API_KEY": "LUFRPT1xxx...",
|
|
159
|
+
"PANOS_MODE": "read"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
If you installed with pip instead of uv, use `"command": "python"` with `"args": ["-m", "panos_mcp"]`.
|
|
167
|
+
|
|
168
|
+
3. Restart Claude Desktop — the server starts automatically when Claude needs it. It starts in **read-only mode** by default; raise `PANOS_MODE` to enable writes.
|
|
169
|
+
|
|
170
|
+
### Claude Code (CLI)
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
claude mcp add node804-panos-mcp \
|
|
174
|
+
-e PANOS_HOST=fw.example.com \
|
|
175
|
+
-e PANOS_API_KEY=LUFRPT1xxx... \
|
|
176
|
+
-e PANOS_MODE=read \
|
|
177
|
+
-- uvx node804-panos-mcp==0.1.6
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Verify with `claude mcp list`.
|
|
181
|
+
|
|
182
|
+
### Verifying It Works
|
|
183
|
+
|
|
184
|
+
Once connected, ask your LLM:
|
|
185
|
+
|
|
186
|
+
- **"What's my access level?"** → calls `server_status`, showing the active mode, live tool counts, TLS posture, and configured firewalls
|
|
187
|
+
- **"What firewalls are configured?"** → calls `list_firewalls`, confirming the registry and which targets require an explicit `firewall` argument
|
|
188
|
+
|
|
189
|
+
If either fails, see [Troubleshooting](#troubleshooting).
|
|
190
|
+
|
|
191
|
+
## Token efficiency
|
|
192
|
+
|
|
193
|
+
Every list-returning tool accepts these parameters (from `node804-mcp-toolkit`):
|
|
194
|
+
|
|
195
|
+
| Parameter | Default | Effect |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| `verbose` | `false` | When `true`, returns every field the underlying API exposes. Default returns only the fields most relevant to the tool's purpose. |
|
|
198
|
+
| `fields` | `null` | Explicit projection. `["name", "action", "disabled"]` returns exactly those fields. Overrides both `verbose` and the default whitelist. |
|
|
199
|
+
| `pagination.limit` | `100` | Max items per response. Hard ceiling: 1000. |
|
|
200
|
+
| `pagination.offset` | `0` | Skip count. Use with `limit` to page through inventories. |
|
|
201
|
+
| `pattern` | `null` | Server-side substring filter on the item's name field. Far cheaper than fetching the inventory and filtering client-side. |
|
|
202
|
+
|
|
203
|
+
Responses are wrapped as `{total, count, items}` so the LLM knows when pagination truncated the result and whether to fetch more. Single-record tools (`get_firewall_info`, `get_ha_status`, etc.) accept `verbose` and `fields`.
|
|
204
|
+
|
|
205
|
+
### Measured savings
|
|
206
|
+
|
|
207
|
+
Token counts below were measured against synthetic fixtures sized to mid-range deployments (100 security rules, 500 address objects). Encoding: `cl100k_base` (GPT-4 / Claude approximation). Every scenario is a regression-guarded test in `tests/benchmark/` — the build fails if a future change widens a response shape past the budget.
|
|
208
|
+
|
|
209
|
+
| Scenario | Naive approach | Tokens | Better approach | Tokens | Saving |
|
|
210
|
+
| --- | --- | ---: | --- | ---: | ---: |
|
|
211
|
+
| Find an address object by IP | `get_address_objects` (dump 500) | 21,207 | `find_address_object("10.0.0.5")` | 88 | **241× smaller** |
|
|
212
|
+
| Inventory snapshot | `get_address_objects` (dump 500) | 21,207 | `count_objects_by_type` | 27 | **785× smaller** |
|
|
213
|
+
| Audit security policy | `get_security_rules` (verbose, 100 rules) | 19,627 | `summarize_security_policy` | 58 | **338× smaller** |
|
|
214
|
+
| Audit security policy | `get_security_rules` (lean, 100 rules) | 5,095 | `summarize_security_policy` | 58 | **88× smaller** |
|
|
215
|
+
| Find rules matching a pattern | `get_security_rules` (lean dump) | 5,095 | `find_rule_by_name("rule-001")` | 249 | **20× smaller** |
|
|
216
|
+
| Filter rules by pattern | `get_security_rules` (no filter) | 5,095 | `get_security_rules(pattern="rule-00")` | 523 | **10× smaller** |
|
|
217
|
+
| Lean default vs verbose | `get_security_rules(verbose=true)` | 19,627 | `get_security_rules` (default) | 5,095 | **74% reduction** |
|
|
218
|
+
|
|
219
|
+
Run the benchmark suite yourself:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
pytest -v -s -m benchmark
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The headline number: **a policy audit that would cost ~20K tokens via the naive "dump everything" approach costs ~60 tokens via `summarize_security_policy`.**
|
|
226
|
+
|
|
227
|
+
## Audit logging
|
|
228
|
+
|
|
229
|
+
Every tool invocation can be captured to a JSON-lines file:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
export PANOS_AUDIT_LOG=/var/log/panos-mcp/audit.jsonl
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Each line is one event:
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{"ts":"2026-05-07T14:32:11.483Z","tool":"add_security_rule","mode":"full","firewall":"hq-fw","args":{"name":"block-tor","action":"deny"},"success":true,"duration_ms":412}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Sensitive keys (`api_key`, `password`, `secret`, `token`, `auth`) are redacted at any depth before logging. Strings over 2 KB are elided. Write failures warn once and never block tool execution.
|
|
242
|
+
|
|
243
|
+
The audit log is the only after-the-fact record for admin-mode operations (`commit`, `set_config`, `run_op_command`). Treat it accordingly.
|
|
244
|
+
|
|
245
|
+
## TLS verification
|
|
246
|
+
|
|
247
|
+
Connections to PAN-OS are TLS-verified by default. For environments where the firewall presents a self-signed certificate or one signed by an internal CA:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
# Option A: trust a custom CA bundle
|
|
251
|
+
export PANOS_TLS_CA=/etc/pki/internal-ca.pem
|
|
252
|
+
|
|
253
|
+
# Option B: disable verification (emits a startup warning)
|
|
254
|
+
export PANOS_TLS_VERIFY=false
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The startup log reports the active posture:
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
PanOS TLS: verify=ON (system trust store)
|
|
261
|
+
PanOS TLS: verify=ON (custom CA via PANOS_TLS_CA)
|
|
262
|
+
PanOS TLS: verify=OFF (PANOS_TLS_VERIFY=false)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Multi-firewall configuration
|
|
266
|
+
|
|
267
|
+
For environments managing multiple firewalls or Panorama instances, define them in `~/.config/panos-mcp/firewalls.json` (override with `PANOS_FIREWALLS_CONFIG=/path`):
|
|
268
|
+
|
|
269
|
+
```json
|
|
270
|
+
{
|
|
271
|
+
"firewalls": [
|
|
272
|
+
{ "name": "hq", "host": "hq.example.com" },
|
|
273
|
+
{ "name": "branch", "host": "branch.example.com" },
|
|
274
|
+
{ "name": "panorama", "host": "panorama.example.com" }
|
|
275
|
+
]
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
API keys live in the OS keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service via `keyring`). On first run, plaintext `api_key` fields in older configs auto-migrate to the keychain and the JSON is rewritten without them.
|
|
280
|
+
|
|
281
|
+
**Linux headless servers** without a keychain backend fall back to plaintext storage. The file is written with mode `0o600` (owner read/write only). The startup log emits a warning.
|
|
282
|
+
|
|
283
|
+
In multi-firewall mode, every tool that talks to a firewall requires an explicit `firewall: <name>` argument — no default is picked. Call `list_firewalls` to see configured targets and whether `firewall` is required.
|
|
284
|
+
|
|
285
|
+
## Tools by category
|
|
286
|
+
|
|
287
|
+
Each cell shows how many tools that category contributes at that RBAC tier. A `—` means none. The `Total` column sums across tiers; the bottom row matches the tool counts reported by `server_status`.
|
|
288
|
+
|
|
289
|
+
| Category | Read | Standard | Full | Admin | Total |
|
|
290
|
+
| :---------------- | ---: | -------: | ---: | ----: | ----: |
|
|
291
|
+
| System | 4 | — | — | — | 4 |
|
|
292
|
+
| Diagnostics | 5 | — | — | — | 5 |
|
|
293
|
+
| Network | 8 | — | 2 | — | 10 |
|
|
294
|
+
| Security | 6 | — | 12 | — | 18 |
|
|
295
|
+
| Objects & tags | 6 | 10 | — | — | 16 |
|
|
296
|
+
| NAT | 1 | — | 4 | — | 5 |
|
|
297
|
+
| User-ID | 3 | — | — | — | 3 |
|
|
298
|
+
| Administrators | 3 | — | — | — | 3 |
|
|
299
|
+
| VPN | 3 | — | — | — | 3 |
|
|
300
|
+
| Panorama | 22 | — | 9 | — | 31 |
|
|
301
|
+
| Logs | 5 | — | — | — | 5 |
|
|
302
|
+
| Threat & content | 4 | — | — | — | 4 |
|
|
303
|
+
| Certificates | 3 | — | 4 | — | 7 |
|
|
304
|
+
| Licenses | 2 | — | — | — | 2 |
|
|
305
|
+
| Utility | 4 | — | — | 1 | 5 |
|
|
306
|
+
| Server diagnostic | 2 | — | — | — | 2 |
|
|
307
|
+
| Aggregations | 8 | — | — | — | 8 |
|
|
308
|
+
| Commit & raw API | — | — | — | 6 | 6 |
|
|
309
|
+
| **Total** | **89** | **+10** | **+31** | **+7** | **137** |
|
|
310
|
+
|
|
311
|
+
A few entries worth calling out by name:
|
|
312
|
+
|
|
313
|
+
- **Diagnostics** (read) — `test_security_policy_match`, `test_nat_policy_match`, `test_routing_fib_lookup`, `get_sessions`, `get_session_detail`. Live troubleshooting via read-only `<test>` and session ops.
|
|
314
|
+
- **Utility** — `get_config_xpath`, `run_show_command`, `get_pending_changes`, `get_config_diff` (read) for ad-hoc inspection and reviewing staged changes; `run_op_command` (admin) for the operational-command escape hatch.
|
|
315
|
+
- **Aggregations** (read) — includes `get_rule_hit_counts` and `unused_rules` for usage-based rule cleanup.
|
|
316
|
+
- **Commit & raw API** (admin) — `commit`, `panorama_commit`, `panorama_push_to_devices`, `revert_config`, `set_config`, `delete_config`.
|
|
317
|
+
- **Server diagnostic** — `server_status` and `list_firewalls`. Always available, even in `read` mode.
|
|
318
|
+
|
|
319
|
+
Call `server_status` from your MCP client at runtime to see the active mode, live tool counts, TLS posture, audit destination, and the configured firewall registry.
|
|
320
|
+
|
|
321
|
+
## Aggregation tools
|
|
322
|
+
|
|
323
|
+
Purpose-built summarization tools that replace "dump everything and grep" patterns with focused queries. Returning a structured answer is 10-20x cheaper in tokens than returning the full inventory and letting the LLM compute the same thing — the largest single source of token savings in the project.
|
|
324
|
+
|
|
325
|
+
| Tool | What it answers |
|
|
326
|
+
|---|---|
|
|
327
|
+
| `find_rule_by_name(pattern, rule_types=None)` | Search rule names across security, NAT, PBF, and decryption rulebases. Case-insensitive substring match; each result is tagged with its rule type. |
|
|
328
|
+
| `find_address_object(query)` | Find address objects by name substring **or** by IP membership. If the query parses as an IP/CIDR, returns objects whose ip-netmask, ip-range, or ip-wildcard value overlaps the query. Otherwise matches names. |
|
|
329
|
+
| `count_rules_by_action()` | `{"allow": 142, "deny": 38, "drop": 12}` plus enabled/disabled counts. Replaces a 10K-token rule dump with a ~200-token snapshot. |
|
|
330
|
+
| `summarize_security_policy()` | One-call policy audit: rule counts, action breakdown, any-any-any rules (worth reviewing), rules with logging disabled, rules without a profile group, and the set of zones referenced. |
|
|
331
|
+
| `count_objects_by_type()` | Inventory snapshot: address objects by type (ip-netmask vs fqdn vs range), address groups (static vs dynamic), service objects by protocol, plus tag and application-filter totals. |
|
|
332
|
+
| `get_rule_hit_counts(rule_base, vsys)` | Per-rule hit counts and last-hit timestamps (`show rule-hit-count`) — which rules actually carry traffic. |
|
|
333
|
+
| `unused_rules(vsys, include_disabled=False)` | Security rules with zero hits, joined with the rulebase for disabled status — a ready cleanup worklist. |
|
|
334
|
+
|
|
335
|
+
All are gated at `Mode.READ` — same tier as the underlying inventory reads.
|
|
336
|
+
|
|
337
|
+
## Troubleshooting
|
|
338
|
+
|
|
339
|
+
| Symptom | Cause | Fix |
|
|
340
|
+
|---------|-------|-----|
|
|
341
|
+
| `No module named panos_mcp` | Package not installed | Use the `uvx` config (installs automatically), or run `pip install node804-panos-mcp`. |
|
|
342
|
+
| Server starts but tools return an auth error | Invalid or expired API key | Regenerate the key (see [Getting a PAN-OS API key](#getting-a-pan-os-api-key)) and update your config. The key is invalidated if the admin's password changes. |
|
|
343
|
+
| TLS / certificate verification errors on connect | Firewall presents a self-signed or internal-CA cert | Set `PANOS_TLS_CA` to your CA bundle, or `PANOS_TLS_VERIFY=false` to disable verification (emits a startup warning). |
|
|
344
|
+
| Tools you expect are missing | Mode is too restrictive | Check `server_status` for the active mode, then raise `PANOS_MODE` (`read` → `standard` → `full` → `admin`). |
|
|
345
|
+
| Writes succeed but nothing changes on the firewall | Changes are staged in candidate config | Call `commit` (admin mode). Use `get_pending_changes` / `get_config_diff` to review what's staged first. |
|
|
346
|
+
| "firewall is required" errors | Multi-firewall mode with no target | Pass `firewall: <name>`; run `list_firewalls` to see configured targets. |
|
|
347
|
+
| `set_config` / `run_op_command` not available in admin mode | Raw escape hatches are off by default | Set `PANOS_ENABLE_RAW_CONFIG=true` / `PANOS_ENABLE_RAW_OPS=true` *and* `PANOS_MODE=admin`. |
|
|
348
|
+
| A vsys-scoped query returns the wrong vsys's data | Tools default to `vsys1` | This release targets `vsys1`; for other virtual systems use `get_config_xpath` with an explicit vsys path until a `vsys` parameter lands. |
|
|
349
|
+
|
|
350
|
+
## Development
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
git clone https://github.com/Node804/node804-panos-mcp.git
|
|
354
|
+
cd node804-panos-mcp
|
|
355
|
+
pip install -e ".[dev]" # or: uv sync --extra dev
|
|
356
|
+
|
|
357
|
+
pytest -v # full test suite
|
|
358
|
+
pytest -v -m "not integration and not benchmark" # CI-equivalent
|
|
359
|
+
ruff check . && ruff format --check . # lint + format
|
|
360
|
+
mypy src # type check
|
|
361
|
+
node804-panos-mcp # boot the server
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
`node804-mcp-toolkit` installs automatically as a dependency. Until it's on PyPI, install it from source first (see [Install](#install)).
|
|
365
|
+
|
|
366
|
+
## Acknowledgments
|
|
367
|
+
|
|
368
|
+
Built on [`pan-os-python`](https://github.com/PaloAltoNetworks/pan-os-python) — Palo Alto Networks' official Python SDK. The PAN-OS object model and API client behavior come from there; this project provides the MCP wrapper, response shaping, RBAC, and audit infrastructure on top.
|
|
369
|
+
|
|
370
|
+
## AI-Assisted Development
|
|
371
|
+
|
|
372
|
+
Portions of this project — including code, tests, and documentation — were developed with the assistance of generative AI (Anthropic's Claude, ChatGPT). All changes are human-reviewed before merge and exercised by the automated test suite, but as with any software, review the code and test against a non-production firewall or Panorama before trusting it with write access (`standard` mode or above) to a live device.
|
|
373
|
+
|
|
374
|
+
## License
|
|
375
|
+
|
|
376
|
+
MIT
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
panos_mcp/__init__.py,sha256=Dy1GFYR21DszWIuPi2rQYOayiZdTI3AlywhoVrHC0rY,1470
|
|
2
|
+
panos_mcp/__main__.py,sha256=ybMhrDWiX1teFzCTKd6BJVkHXmdA9mt132-Q-RIOTY0,598
|
|
3
|
+
panos_mcp/_redact.py,sha256=A5iPMDKKMVtzVxjH_95p2JfdEI6bgmucIqraflOP8nc,2756
|
|
4
|
+
panos_mcp/client.py,sha256=TWNO2ZXF2-SHTHvEnfMo-Jy3QAGMBWG8a7ZKc8FqjIM,4945
|
|
5
|
+
panos_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
panos_mcp/server.py,sha256=iUm3VfX15-kofD_iVAgAwBuTHjl2-KWK_Xm9n6RaUso,5193
|
|
7
|
+
panos_mcp/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
panos_mcp/config/firewalls.py,sha256=GDclvAyZK8KhL1vW7NBYHFLqeKTUY_jaiLqti4nLvfk,11635
|
|
9
|
+
panos_mcp/responses/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
panos_mcp/responses/schemas.py,sha256=2-SkM7JLlHxr0P4PVSZ12cXtj4BJzS_1W1e2JLPzSeM,12439
|
|
11
|
+
panos_mcp/responses/shapers.py,sha256=bMmy0_VA5SSYd6IF-vedlEnUOnmxOJPWw-6QzXc9Xdo,5715
|
|
12
|
+
panos_mcp/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
panos_mcp/tools/_common.py,sha256=plw9x9vn0ZwhBrU3qQ57xHPMWBGaRzk7fRpaUYaV8R0,20735
|
|
14
|
+
panos_mcp/tools/_guards.py,sha256=Do-OQLti8RkXLfdWBUNEt7jMzMMHpY-cqf6y-wI0Yv0,10835
|
|
15
|
+
panos_mcp/tools/_schemas.py,sha256=W7ZqOUMD9yW24tLM9Jlqa8k6Wv_myP0o50wOVIRWQIo,2568
|
|
16
|
+
panos_mcp/tools/_xml_writers.py,sha256=_RMPlxmBcCb7U_GEDIyOoBkQoIosGp7ZiQnvgzLuCMY,3112
|
|
17
|
+
panos_mcp/tools/admin.py,sha256=8Y1D_o-CJPG-Xr7bWWlVygrCJW-Cg6FgRZPU6t7SUMs,3546
|
|
18
|
+
panos_mcp/tools/admin_ops.py,sha256=mQFmjtcgKt503K7vfBnHmVYOAIye0EhzL9EtRsp1K8A,12288
|
|
19
|
+
panos_mcp/tools/aggregations.py,sha256=wnB0EnUYpzhIhO8NhOKlij3jcIeV9K2qqWsQNWdyEZA,27475
|
|
20
|
+
panos_mcp/tools/certificates.py,sha256=iVGnqpSQR2V03FgedFjpC5y4vzYSUNoVpUCuPndYOmc,8950
|
|
21
|
+
panos_mcp/tools/diagnostics.py,sha256=EOrEMGyqMyaSkHNzFD1wuwK6rc42-KosToQP52Oep7A,13937
|
|
22
|
+
panos_mcp/tools/firewalls.py,sha256=x3aLCvHE3M_GYOqNY4nGn-1gSXnDYzzy7VWuSrBndxE,1320
|
|
23
|
+
panos_mcp/tools/licenses.py,sha256=qS-yb8Rj0iBCgRk4DNv-ZdGUYORsK6jThm1fMV74_Rs,2636
|
|
24
|
+
panos_mcp/tools/logs.py,sha256=WYjCs7IdJGXwmJjUmiRhx5vub7xw3s_ZU4s22djkD6c,7162
|
|
25
|
+
panos_mcp/tools/nat.py,sha256=mTCdCIFs_poxwGuQ2Y9wL1REeuW9iUr6EwEirNfDJnM,6225
|
|
26
|
+
panos_mcp/tools/network.py,sha256=l_695dGD6VtH-UI3tqHtDqyjdNXyQzbmNbdYvl0OjCc,12976
|
|
27
|
+
panos_mcp/tools/objects.py,sha256=zOWsnhQDvopIiSOfG17tBmL9LPzGb4stGEG1qAx0Sng,15421
|
|
28
|
+
panos_mcp/tools/panorama.py,sha256=V00fCz0wm-ysN4ZyT-MJwDWjHSlzKH6dj0QtJ2TZ-Ug,49602
|
|
29
|
+
panos_mcp/tools/security.py,sha256=XpVD_ZCMpqdUWViadlqX5YRwPBPzh30BrJclI3Vw2FQ,25407
|
|
30
|
+
panos_mcp/tools/status.py,sha256=_5MhuvBvHi627xGDqk-W8mVTIL6_dsAUi89-wJIDVEM,1929
|
|
31
|
+
panos_mcp/tools/system.py,sha256=QtSvXWNfwa86cTFJiAKU0rC4hh26IrXPaG1mRUS1iLI,6448
|
|
32
|
+
panos_mcp/tools/threat.py,sha256=k5nGJvnlMnd-PTwmOpuP3xj8IaOgVPA_mlNiCKYqLRY,4294
|
|
33
|
+
panos_mcp/tools/userid.py,sha256=nPoh_v4j9MtCx0Vnr_8m78HBqPCujyciOICfKchzhdk,3851
|
|
34
|
+
panos_mcp/tools/utility.py,sha256=9BComRHhHX5fdAJkBEhP1YmLvMr9Fc2eFf0otba1A-A,13718
|
|
35
|
+
panos_mcp/tools/vpn.py,sha256=iThlcPDPY8luCdnsmQE4oUPH0-WSkvS_Uc7F-YyEsRI,4667
|
|
36
|
+
node804_panos_mcp-0.1.6.dist-info/METADATA,sha256=FUCf-RONGeRH1XnXBBoUxLmYAWe7rKxgtQANxV9-ChQ,21790
|
|
37
|
+
node804_panos_mcp-0.1.6.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
38
|
+
node804_panos_mcp-0.1.6.dist-info/entry_points.txt,sha256=m2k17RxebP-jR2B7ekwgvTnpbBFaj6eNNfnSaL-RcHQ,60
|
|
39
|
+
node804_panos_mcp-0.1.6.dist-info/licenses/LICENSE,sha256=MWZG7mvTKQAmF-bl5ZJ9VkzcpQy-ZPgzRoMe_rKekTc,1069
|
|
40
|
+
node804_panos_mcp-0.1.6.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michael Pope
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
panos_mcp/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Token-efficient MCP server for Palo Alto Networks PAN-OS firewalls and Panorama.
|
|
2
|
+
|
|
3
|
+
Designed around three principles:
|
|
4
|
+
1. Lean responses by default — typical 40-70% token reduction vs unfiltered output.
|
|
5
|
+
2. Default-deny RBAC — only read tools are exposed unless PANOS_MODE opts in.
|
|
6
|
+
3. TLS verification on by default — self-signed certs require explicit opt-in.
|
|
7
|
+
|
|
8
|
+
See the README for installation, configuration, and the full tool catalog.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# pan-os-python 1.12.x still imports `distutils`, which Python 3.12 removed.
|
|
12
|
+
# We alias setuptools._distutils into sys.modules BEFORE any pan-os-python
|
|
13
|
+
# import so the legacy import still works. This shim is intentionally near
|
|
14
|
+
# the top of the package init to run before any submodule import.
|
|
15
|
+
#
|
|
16
|
+
# Track upstream resolution at:
|
|
17
|
+
# https://github.com/PaloAltoNetworks/pan-os-python/issues
|
|
18
|
+
# When pan-os-python ships a 1.13+ that drops distutils, this shim can go.
|
|
19
|
+
import sys as _sys
|
|
20
|
+
|
|
21
|
+
if "distutils" not in _sys.modules:
|
|
22
|
+
try:
|
|
23
|
+
from setuptools import _distutils as _legacy_distutils
|
|
24
|
+
from setuptools._distutils import version as _legacy_version
|
|
25
|
+
|
|
26
|
+
_sys.modules["distutils"] = _legacy_distutils
|
|
27
|
+
_sys.modules["distutils.version"] = _legacy_version
|
|
28
|
+
except ImportError:
|
|
29
|
+
# setuptools missing or doesn't expose _distutils; pan-os-python will
|
|
30
|
+
# fail to import with a clear error and the operator can install
|
|
31
|
+
# setuptools manually.
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
__version__ = "0.1.6"
|
panos_mcp/__main__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Entry point for ``python -m panos_mcp``.
|
|
2
|
+
|
|
3
|
+
This file makes the package runnable as a module — useful when an MCP host
|
|
4
|
+
config prefers to specify a Python interpreter explicitly rather than rely
|
|
5
|
+
on the ``node804-panos-mcp`` console script being on the user's PATH.
|
|
6
|
+
|
|
7
|
+
Both invocations call the same ``main()`` function:
|
|
8
|
+
|
|
9
|
+
node804-panos-mcp # via [project.scripts] console script
|
|
10
|
+
python -m panos_mcp # via this file
|
|
11
|
+
|
|
12
|
+
Pick whichever is easier to configure in your MCP host.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from .server import main
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
main()
|
panos_mcp/_redact.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Secret-redaction helpers for error messages and log lines.
|
|
2
|
+
|
|
3
|
+
PAN-OS API calls authenticate with a long-lived API key passed as a URL
|
|
4
|
+
query parameter (``?key=<API_KEY>``). The underlying ``pan-python`` and
|
|
5
|
+
``requests`` libraries occasionally surface the request URL in exception
|
|
6
|
+
strings, response bodies, and debug logs — most notably on TLS handshake
|
|
7
|
+
failures and unparseable responses where the full ``self.uri`` is echoed
|
|
8
|
+
back. An MCP host that captures stderr into a log aggregator would then
|
|
9
|
+
hold the key indefinitely.
|
|
10
|
+
|
|
11
|
+
This module provides a single ``redact_secrets`` function that strips any
|
|
12
|
+
``key=<value>`` (and ``api_key=<value>``) query parameter from arbitrary
|
|
13
|
+
text. Every code path that turns an exception into a log line or a tool
|
|
14
|
+
response runs the result through here.
|
|
15
|
+
|
|
16
|
+
The redaction is intentionally conservative — it only replaces the value,
|
|
17
|
+
leaves surrounding context intact so log readers can still see where the
|
|
18
|
+
event happened, and is regex-only so there's no parsing surface to abuse.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import re
|
|
24
|
+
from typing import Final
|
|
25
|
+
|
|
26
|
+
# Match `key=` / `api_key=` / `api-key=` (case-insensitive) followed by a
|
|
27
|
+
# value that runs until the next URL/whitespace separator or quote. The
|
|
28
|
+
# leading separator (``?`` or ``&``) is preserved so the surrounding URL
|
|
29
|
+
# remains visually intact.
|
|
30
|
+
_KEY_PARAM_RE: Final = re.compile(
|
|
31
|
+
r"(?P<sep>[?&])(?P<name>(?:api[-_]?)?key)=(?P<val>[^&\s'\"<>]+)",
|
|
32
|
+
flags=re.IGNORECASE,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Catch bare assignments like ``api_key=AQ...`` that appear in repr() output
|
|
36
|
+
# without a URL prefix. Word boundary on the left, value runs to the next
|
|
37
|
+
# whitespace, comma, paren, or quote.
|
|
38
|
+
_BARE_KEY_RE: Final = re.compile(
|
|
39
|
+
r"\b(?P<name>api[-_]?key)\s*[=:]\s*['\"]?(?P<val>[A-Za-z0-9+/=_\-]{16,})['\"]?",
|
|
40
|
+
flags=re.IGNORECASE,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
REDACTED: Final = "***REDACTED***"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def redact_secrets(text: str) -> str:
|
|
47
|
+
"""Strip API-key values from a string while preserving surrounding text.
|
|
48
|
+
|
|
49
|
+
Safe to call on any string — if no secret patterns match, the input is
|
|
50
|
+
returned unchanged. Designed to be cheap enough to apply liberally on
|
|
51
|
+
every error path.
|
|
52
|
+
"""
|
|
53
|
+
if not text:
|
|
54
|
+
return text
|
|
55
|
+
text = _KEY_PARAM_RE.sub(lambda m: f"{m.group('sep')}{m.group('name')}={REDACTED}", text)
|
|
56
|
+
text = _BARE_KEY_RE.sub(lambda m: f"{m.group('name')}={REDACTED}", text)
|
|
57
|
+
return text
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def redact_exception(exc: BaseException) -> str:
|
|
61
|
+
"""Render an exception as a redacted string suitable for logs/responses.
|
|
62
|
+
|
|
63
|
+
Falls back to the class name when ``str(exc)`` is empty (matches the
|
|
64
|
+
behavior of ``wrap_panos_error`` for consistency).
|
|
65
|
+
"""
|
|
66
|
+
return redact_secrets(str(exc)) or exc.__class__.__name__
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
__all__ = ["REDACTED", "redact_exception", "redact_secrets"]
|