@usex/mikrotik-mcp 4.23.0 → 5.0.0
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.
- package/README.md +350 -284
- package/dist/cli.js +1870 -566
- package/dist/index.d.ts +57 -0
- package/dist/index.js +1 -1
- package/dist/shared/{library-2144zr9f.js → cli-2dj736ma.js} +33920 -22075
- package/dist/shared/{cli-p673815b.js → cli-vvy7yfyg.js} +1 -1
- package/dist/shared/{cli-tssmr7sb.js → library-13dhfdnq.js} +29455 -18578
- package/dist/shared/{library-8f4pbp0x.js → library-syt5fmtj.js} +1 -1
- package/dist/ui/aaa.html +1 -1
- package/dist/ui/connected-devices.html +1 -1
- package/dist/ui/dashboard.html +1 -1
- package/dist/ui/firewall-audit.html +1 -1
- package/dist/ui/firewall.html +1 -1
- package/dist/ui/interfaces.html +1 -1
- package/dist/ui/observability.html +70 -62
- package/dist/ui/records.html +1 -1
- package/package.json +12 -11
- package/policies/baseline.yaml +180 -0
- package/prompts/backup-and-document.md +28 -18
- package/prompts/build-tunnel-transactionally.md +74 -0
- package/prompts/fleet-rollout.md +68 -0
- package/prompts/setup-traffic-flow.md +68 -0
- package/schemas/README.md +1 -1
- package/schemas/config.schema.json +367 -4
- package/schemas/tool-catalog.json +3044 -1152
- package/schemas/tools/abort_rollout.json +17 -0
- package/schemas/tools/abort_transaction.json +17 -0
- package/schemas/tools/add_alert_rule.json +45 -0
- package/schemas/tools/add_ipv6_route.json +1 -1
- package/schemas/tools/add_route.json +3 -2
- package/schemas/tools/add_schedule.json +50 -0
- package/schemas/tools/add_script.json +1 -1
- package/schemas/tools/add_traffic_flow_target.json +36 -0
- package/schemas/tools/add_transaction_step.json +21 -0
- package/schemas/tools/analyze_flows.json +25 -0
- package/schemas/tools/begin_transaction.json +109 -0
- package/schemas/tools/block_attacker.json +22 -0
- package/schemas/tools/check_policy_snapshot.json +20 -0
- package/schemas/tools/commit_transaction.json +13 -0
- package/schemas/tools/configure_attack_response.json +12 -0
- package/schemas/tools/diff_explanations.json +24 -0
- package/schemas/tools/explain_device.json +22 -0
- package/schemas/tools/explain_policy_finding.json +17 -0
- package/schemas/tools/explain_rule_reachability.json +18 -0
- package/schemas/tools/explain_section.json +32 -0
- package/schemas/tools/export_policy_report.json +22 -0
- package/schemas/tools/flow_top_talkers.json +31 -0
- package/schemas/tools/get_alert_history.json +26 -0
- package/schemas/tools/get_attack_incident.json +13 -0
- package/schemas/tools/get_audit_timeline.json +24 -0
- package/schemas/tools/get_device_capabilities.json +7 -0
- package/schemas/tools/get_traffic_flow_settings.json +7 -0
- package/schemas/tools/list_alert_rules.json +7 -0
- package/schemas/tools/list_attack_incidents.json +27 -0
- package/schemas/tools/list_attack_responses.json +20 -0
- package/schemas/tools/list_policies.json +17 -0
- package/schemas/tools/list_schedules.json +12 -0
- package/schemas/tools/list_traffic_flow_targets.json +7 -0
- package/schemas/tools/mute_alert_rule.json +18 -0
- package/schemas/tools/plan_rollout.json +95 -0
- package/schemas/tools/refresh_device_capabilities.json +7 -0
- package/schemas/tools/remove_alert_rule.json +13 -0
- package/schemas/tools/remove_schedule.json +13 -0
- package/schemas/tools/remove_traffic_flow_target.json +13 -0
- package/schemas/tools/rollout_status.json +12 -0
- package/schemas/tools/run_policy_check.json +12 -0
- package/schemas/tools/run_schedule_now.json +13 -0
- package/schemas/tools/scan_for_attacks.json +21 -0
- package/schemas/tools/set_traffic_flow_settings.json +28 -0
- package/schemas/tools/simulate_change.json +72 -0
- package/schemas/tools/simulate_packet.json +53 -0
- package/schemas/tools/simulate_suite.json +71 -0
- package/schemas/tools/start_flow_collector.json +14 -0
- package/schemas/tools/start_rollout.json +98 -0
- package/schemas/tools/stop_flow_collector.json +7 -0
- package/schemas/tools/test_alert_channel.json +14 -0
- package/schemas/tools/unblock_attacker.json +20 -0
- package/schemas/tools/update_alert_rule.json +41 -0
- package/schemas/tools/update_route.json +3 -2
- package/schemas/tools/validate_policy_file.json +16 -0
- package/schemas/tools/verify_transaction.json +13 -0
package/dist/ui/records.html
CHANGED
|
@@ -99,7 +99,7 @@ Maps to Permission Policy \`microphone\` feature.`),geolocation:I({}).optional()
|
|
|
99
99
|
|
|
100
100
|
Maps to Permission Policy \`geolocation\` feature.`),clipboardWrite:I({}).optional().describe(`Request clipboard write access.
|
|
101
101
|
|
|
102
|
-
Maps to Permission Policy \`clipboard-write\` feature.`)});I({method:V(`ui/notifications/size-changed`),params:I({width:M().optional().describe(`New width in pixels.`),height:M().optional().describe(`New height in pixels.`)})});var xv=I({method:V(`ui/notifications/tool-input`),params:I({arguments:z(j(),P().describe(`Complete tool call arguments as key-value pairs.`)).optional().describe(`Complete tool call arguments as key-value pairs.`)})}),Sv=I({method:V(`ui/notifications/tool-input-partial`),params:I({arguments:z(j(),P().describe(`Partial tool call arguments (incomplete, may change).`)).optional().describe(`Partial tool call arguments (incomplete, may change).`)})}),Cv=I({method:V(`ui/notifications/tool-cancelled`),params:I({reason:j().optional().describe(`Optional reason for the cancellation (e.g., "user action", "timeout").`)})}),wv=I({fonts:j().optional()}),Tv=I({variables:hv.optional().describe(`CSS variables for theming the app.`),css:wv.optional().describe(`CSS blocks that apps can inject.`)}),Ev=I({method:V(`ui/resource-teardown`),params:I({})});z(j(),P());var Dv=I({text:I({}).optional().describe(`Host supports text content blocks.`),image:I({}).optional().describe(`Host supports image content blocks.`),audio:I({}).optional().describe(`Host supports audio content blocks.`),resource:I({}).optional().describe(`Host supports resource content blocks.`),resourceLink:I({}).optional().describe(`Host supports resource link content blocks.`),structuredContent:I({}).optional().describe(`Host supports structured content.`)});I({method:V(`ui/notifications/request-teardown`),params:I({}).optional()});var Ov=I({experimental:
|
|
102
|
+
Maps to Permission Policy \`clipboard-write\` feature.`)});I({method:V(`ui/notifications/size-changed`),params:I({width:M().optional().describe(`New width in pixels.`),height:M().optional().describe(`New height in pixels.`)})});var xv=I({method:V(`ui/notifications/tool-input`),params:I({arguments:z(j(),P().describe(`Complete tool call arguments as key-value pairs.`)).optional().describe(`Complete tool call arguments as key-value pairs.`)})}),Sv=I({method:V(`ui/notifications/tool-input-partial`),params:I({arguments:z(j(),P().describe(`Partial tool call arguments (incomplete, may change).`)).optional().describe(`Partial tool call arguments (incomplete, may change).`)})}),Cv=I({method:V(`ui/notifications/tool-cancelled`),params:I({reason:j().optional().describe(`Optional reason for the cancellation (e.g., "user action", "timeout").`)})}),wv=I({fonts:j().optional()}),Tv=I({variables:hv.optional().describe(`CSS variables for theming the app.`),css:wv.optional().describe(`CSS blocks that apps can inject.`)}),Ev=I({method:V(`ui/resource-teardown`),params:I({})});z(j(),P());var Dv=I({text:I({}).optional().describe(`Host supports text content blocks.`),image:I({}).optional().describe(`Host supports image content blocks.`),audio:I({}).optional().describe(`Host supports audio content blocks.`),resource:I({}).optional().describe(`Host supports resource content blocks.`),resourceLink:I({}).optional().describe(`Host supports resource link content blocks.`),structuredContent:I({}).optional().describe(`Host supports structured content.`)});I({method:V(`ui/notifications/request-teardown`),params:I({}).optional()});var Ov=I({experimental:z(j(),z(j(),qf()).describe(`Experimental features keyed by identifier.`)).optional().describe(`Experimental features keyed by identifier.`),openLinks:I({}).optional().describe(`Host supports opening external URLs.`),downloadFile:I({}).optional().describe(`Host supports file downloads via ui/download-file.`),serverTools:I({listChanged:N().optional().describe(`Host supports tools/list_changed notifications.`)}).optional().describe(`Host can proxy tool calls to the MCP server.`),serverResources:I({listChanged:N().optional().describe(`Host supports resources/list_changed notifications.`)}).optional().describe(`Host can proxy resource reads to the MCP server.`),logging:I({}).optional().describe(`Host accepts log messages.`),sandbox:I({permissions:bv.optional().describe(`Permissions granted by the host (camera, microphone, geolocation).`),csp:yv.optional().describe(`CSP domains approved by the host.`)}).optional().describe(`Sandbox configuration applied by the host.`),updateModelContext:Dv.optional().describe(`Host accepts context updates (ui/update-model-context) to be included in the model's context for future turns.`),message:Dv.optional().describe(`Host supports receiving content messages (ui/message) from the view.`),sampling:I({tools:I({}).optional().describe("Host supports tool use via `tools` and `toolChoice` parameters.")}).optional().describe("Host supports LLM sampling (sampling/createMessage) from the view.\nMirrors the MCP `ClientCapabilities.sampling` shape so hosts can pass it through.")}),kv=I({experimental:z(j(),z(j(),qf()).describe(`Experimental features keyed by identifier.`)).optional().describe(`Experimental features keyed by identifier.`),tools:I({listChanged:N().optional().describe(`App supports tools/list_changed notifications.`)}).optional().describe(`App exposes MCP-style tools that the host can call.`),availableDisplayModes:F(mv).optional().describe(`Display modes the app supports.`)});I({method:V(`ui/notifications/initialized`),params:I({}).optional()}),I({csp:yv.optional().describe(`Content Security Policy configuration for UI resources.`),permissions:bv.optional().describe(`Sandbox permissions requested by the UI resource.`),domain:j().optional().describe(`Dedicated origin for view sandbox.
|
|
103
103
|
|
|
104
104
|
Useful when views need stable, dedicated origins for OAuth callbacks, CORS policies, or API key allowlists.
|
|
105
105
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usex/mikrotik-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "MCP server for MikroTik RouterOS — 780+ tools over SSH for firewall, NAT, routing, DHCP, DNS, WireGuard, wireless, QoS and more.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"workspaces": [],
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|
|
33
|
+
"policies",
|
|
33
34
|
"prompts",
|
|
34
35
|
"schemas"
|
|
35
36
|
],
|
|
@@ -83,10 +84,10 @@
|
|
|
83
84
|
"prepare": "vp config"
|
|
84
85
|
},
|
|
85
86
|
"dependencies": {
|
|
86
|
-
"@modelcontextprotocol/ext-apps": "^1.7.
|
|
87
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
87
|
+
"@modelcontextprotocol/ext-apps": "^1.7.5",
|
|
88
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
88
89
|
"@tikoci/centrs": "^0.1.0",
|
|
89
|
-
"figlet": "^1.11.
|
|
90
|
+
"figlet": "^1.11.4",
|
|
90
91
|
"gradient-string": "^3.0.0",
|
|
91
92
|
"ipaddr.js": "^2.4.0",
|
|
92
93
|
"ssh2": "^1.17.0",
|
|
@@ -98,7 +99,7 @@
|
|
|
98
99
|
"@tailwindcss/vite": "^4.3.3",
|
|
99
100
|
"@types/bun": "latest",
|
|
100
101
|
"@types/figlet": "^1.7.0",
|
|
101
|
-
"@types/node": "^26.1.
|
|
102
|
+
"@types/node": "^26.1.2",
|
|
102
103
|
"@types/react": "^19.2.17",
|
|
103
104
|
"@types/react-dom": "^19.2.3",
|
|
104
105
|
"@types/ssh2": "^1.15.5",
|
|
@@ -108,12 +109,12 @@
|
|
|
108
109
|
"clsx": "^2.1.1",
|
|
109
110
|
"gsap": "^3.15.0",
|
|
110
111
|
"license-checker": "^25.0.1",
|
|
111
|
-
"lucide-react": "^1.
|
|
112
|
-
"radix-ui": "^1.6.
|
|
113
|
-
"react": "^19.2.
|
|
114
|
-
"react-dom": "^19.2.
|
|
115
|
-
"recharts": "^3.
|
|
116
|
-
"release-it": "^
|
|
112
|
+
"lucide-react": "^1.28.0",
|
|
113
|
+
"radix-ui": "^1.6.7",
|
|
114
|
+
"react": "^19.2.8",
|
|
115
|
+
"react-dom": "^19.2.8",
|
|
116
|
+
"recharts": "^3.10.1",
|
|
117
|
+
"release-it": "^21.0.0",
|
|
117
118
|
"sonner": "^2.0.7",
|
|
118
119
|
"tailwind-merge": "^3.6.0",
|
|
119
120
|
"tailwindcss": "^4.3.3",
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
name: MikroTik baseline hardening
|
|
3
|
+
# The bundled starter pack. Every rule here is drawn from a check the Security
|
|
4
|
+
# Hardening and Compliance Auditor modules already make, restated in the policy
|
|
5
|
+
# language so the file doubles as a worked example of the schema.
|
|
6
|
+
#
|
|
7
|
+
# Copy it, edit it, and point `policy.paths` at your own copy — these are a
|
|
8
|
+
# starting position, not a standard.
|
|
9
|
+
policies:
|
|
10
|
+
# ── Management plane ─────────────────────────────────────────────────────
|
|
11
|
+
- id: telnet-disabled
|
|
12
|
+
severity: critical
|
|
13
|
+
description: Telnet must be disabled — it carries credentials in clear text.
|
|
14
|
+
remediation: /ip service set telnet disabled=yes
|
|
15
|
+
match: { section: /ip/service, where: { name: telnet } }
|
|
16
|
+
assert: { field: disabled, equals: "yes" }
|
|
17
|
+
tags: [management, cis]
|
|
18
|
+
|
|
19
|
+
- id: ftp-disabled
|
|
20
|
+
severity: high
|
|
21
|
+
description: The FTP service must be disabled unless a device genuinely serves files.
|
|
22
|
+
remediation: /ip service set ftp disabled=yes
|
|
23
|
+
match: { section: /ip/service, where: { name: ftp } }
|
|
24
|
+
assert: { field: disabled, equals: "yes" }
|
|
25
|
+
tags: [management]
|
|
26
|
+
|
|
27
|
+
- id: ssh-strong-crypto
|
|
28
|
+
severity: high
|
|
29
|
+
description: SSH must use strong crypto (disables the legacy ciphers and 1024-bit DH).
|
|
30
|
+
remediation: /ip ssh set strong-crypto=yes
|
|
31
|
+
match: { section: /ip/ssh, settings: true }
|
|
32
|
+
assert: { field: strong-crypto, equals: "yes" }
|
|
33
|
+
tags: [management, crypto]
|
|
34
|
+
|
|
35
|
+
- id: ssh-forwarding-off
|
|
36
|
+
severity: medium
|
|
37
|
+
description: SSH TCP forwarding turns a router into a tunnel endpoint; leave it off unless a jump host needs it.
|
|
38
|
+
remediation: /ip ssh set forwarding-enabled=no
|
|
39
|
+
match: { section: /ip/ssh, settings: true }
|
|
40
|
+
assert:
|
|
41
|
+
none_of:
|
|
42
|
+
- { field: forwarding-enabled, equals: both }
|
|
43
|
+
tags: [management]
|
|
44
|
+
|
|
45
|
+
- id: no-default-admin
|
|
46
|
+
severity: critical
|
|
47
|
+
description: The stock `admin` account must be renamed or removed.
|
|
48
|
+
remediation: Create a named account with full policy, then /user remove admin
|
|
49
|
+
match: { section: /user, where: { name: admin } }
|
|
50
|
+
# Reaching this rule at all means an account literally named `admin` exists.
|
|
51
|
+
assert: { count: { exactly: 0 } }
|
|
52
|
+
on_empty: pass
|
|
53
|
+
tags: [accounts]
|
|
54
|
+
|
|
55
|
+
- id: users-have-comments
|
|
56
|
+
severity: low
|
|
57
|
+
description: Every account should say who it belongs to.
|
|
58
|
+
remediation: /user set <name> comment="owner"
|
|
59
|
+
match: { section: /user }
|
|
60
|
+
assert: { field: comment, present: true }
|
|
61
|
+
tags: [accounts, hygiene]
|
|
62
|
+
|
|
63
|
+
# ── Discovery & neighbour exposure ───────────────────────────────────────
|
|
64
|
+
- id: neighbor-discovery-scoped
|
|
65
|
+
severity: medium
|
|
66
|
+
description: Neighbour discovery must be limited to an interface list, not left on all interfaces.
|
|
67
|
+
remediation: /ip neighbor discovery-settings set discover-interface-list=LAN
|
|
68
|
+
match: { section: /ip/neighbor/discovery-settings, settings: true }
|
|
69
|
+
assert: { field: discover-interface-list, not_in: [all, none] }
|
|
70
|
+
tags: [discovery]
|
|
71
|
+
|
|
72
|
+
- id: mac-server-scoped
|
|
73
|
+
severity: medium
|
|
74
|
+
description: MAC-Telnet must be restricted to a management interface list.
|
|
75
|
+
remediation: /tool mac-server set allowed-interface-list=LAN
|
|
76
|
+
match: { section: /tool/mac-server, settings: true }
|
|
77
|
+
assert:
|
|
78
|
+
none_of:
|
|
79
|
+
- { field: allowed-interface-list, equals: all }
|
|
80
|
+
tags: [discovery, management]
|
|
81
|
+
|
|
82
|
+
- id: bandwidth-server-off
|
|
83
|
+
severity: low
|
|
84
|
+
description: The bandwidth-test server should be disabled when not actively testing.
|
|
85
|
+
remediation: /tool bandwidth-server set enabled=no
|
|
86
|
+
match: { section: /tool/bandwidth-server, settings: true }
|
|
87
|
+
assert: { field: enabled, equals: "no" }
|
|
88
|
+
tags: [services]
|
|
89
|
+
|
|
90
|
+
# ── Firewall ─────────────────────────────────────────────────────────────
|
|
91
|
+
- id: input-chain-has-drop
|
|
92
|
+
severity: critical
|
|
93
|
+
description: The input chain must end in an explicit drop — an implicit accept is not a firewall.
|
|
94
|
+
remediation: /ip firewall filter add chain=input action=drop comment="default deny"
|
|
95
|
+
match: { section: /ip/firewall/filter, where: { chain: input, action: drop } }
|
|
96
|
+
assert: { count: { min: 1 } }
|
|
97
|
+
tags: [firewall, default-deny]
|
|
98
|
+
|
|
99
|
+
- id: no-bare-input-accept
|
|
100
|
+
severity: critical
|
|
101
|
+
description: An input-chain accept must be scoped by address-list, interface list or connection state.
|
|
102
|
+
remediation: Add src-address-list=, in-interface-list= or connection-state= to the rule.
|
|
103
|
+
match: { section: /ip/firewall/filter, where: { chain: input, action: accept } }
|
|
104
|
+
assert:
|
|
105
|
+
any_of:
|
|
106
|
+
- { field: src-address-list, present: true }
|
|
107
|
+
- { field: in-interface-list, present: true }
|
|
108
|
+
- { field: connection-state, contains: established }
|
|
109
|
+
- { field: in-interface, present: true }
|
|
110
|
+
tags: [firewall]
|
|
111
|
+
|
|
112
|
+
- id: no-disabled-firewall-rules
|
|
113
|
+
severity: low
|
|
114
|
+
description: A disabled firewall rule is dead weight that hides intent — delete it or enable it.
|
|
115
|
+
remediation: Remove the rule, or enable it if it is still needed.
|
|
116
|
+
match: { section: /ip/firewall/filter }
|
|
117
|
+
# `none_of` rather than `not_equals`: a missing field fails every value
|
|
118
|
+
# predicate, and most rules simply omit `disabled=`. "must not be yes"
|
|
119
|
+
# (absent counts as fine) is `none_of`; "present and different" is
|
|
120
|
+
# `not_equals`. This is the single most common authoring mistake.
|
|
121
|
+
assert:
|
|
122
|
+
none_of:
|
|
123
|
+
- { field: disabled, equals: "yes" }
|
|
124
|
+
tags: [firewall, hygiene]
|
|
125
|
+
|
|
126
|
+
- id: ipv6-firewall-present
|
|
127
|
+
severity: high
|
|
128
|
+
description: A device with IPv6 addresses must also filter IPv6 — an IPv4-only firewall is half a firewall.
|
|
129
|
+
remediation: /ipv6 firewall filter add chain=input action=drop
|
|
130
|
+
match: { section: /ipv6/firewall/filter, where: { chain: input, action: drop } }
|
|
131
|
+
assert: { count: { min: 1 } }
|
|
132
|
+
on_empty: fail
|
|
133
|
+
tags: [firewall, ipv6]
|
|
134
|
+
|
|
135
|
+
# ── Kernel & routing ─────────────────────────────────────────────────────
|
|
136
|
+
- id: rp-filter-enabled
|
|
137
|
+
severity: high
|
|
138
|
+
description: Reverse-path filtering must be on, so spoofed source addresses are dropped.
|
|
139
|
+
remediation: /ip settings set rp-filter=strict
|
|
140
|
+
match: { section: /ip/settings, settings: true }
|
|
141
|
+
assert: { field: rp-filter, in: [strict, loose] }
|
|
142
|
+
tags: [kernel, anti-spoof]
|
|
143
|
+
|
|
144
|
+
- id: no-ip-forwarding-of-source-routes
|
|
145
|
+
severity: medium
|
|
146
|
+
description: Source-routed packets must not be accepted.
|
|
147
|
+
remediation: /ip settings set accept-source-route=no
|
|
148
|
+
match: { section: /ip/settings, settings: true }
|
|
149
|
+
assert:
|
|
150
|
+
none_of:
|
|
151
|
+
- { field: accept-source-route, equals: "yes" }
|
|
152
|
+
tags: [kernel]
|
|
153
|
+
|
|
154
|
+
# ── Time & logging ───────────────────────────────────────────────────────
|
|
155
|
+
- id: ntp-client-enabled
|
|
156
|
+
severity: medium
|
|
157
|
+
description: NTP must be enabled — every log timestamp and certificate check depends on it.
|
|
158
|
+
remediation: /system ntp client set enabled=yes servers=...
|
|
159
|
+
match: { section: /system/ntp/client, settings: true }
|
|
160
|
+
assert: { field: enabled, equals: "yes" }
|
|
161
|
+
tags: [time]
|
|
162
|
+
|
|
163
|
+
- id: remote-logging-configured
|
|
164
|
+
severity: medium
|
|
165
|
+
description: At least one logging action must ship logs off the device; local logs die with the router.
|
|
166
|
+
remediation: /system logging action add name=remote target=remote remote=<syslog-host>
|
|
167
|
+
match: { section: /system/logging/action, where: { target: remote } }
|
|
168
|
+
assert: { count: { min: 1 } }
|
|
169
|
+
on_empty: fail
|
|
170
|
+
tags: [logging]
|
|
171
|
+
|
|
172
|
+
- id: dns-not-open-resolver
|
|
173
|
+
severity: high
|
|
174
|
+
description: Remote DNS requests must be off, or the router becomes an open resolver for amplification attacks.
|
|
175
|
+
remediation: /ip dns set allow-remote-requests=no (or firewall port 53 on the WAN)
|
|
176
|
+
match: { section: /ip/dns, settings: true }
|
|
177
|
+
assert:
|
|
178
|
+
none_of:
|
|
179
|
+
- { field: allow-remote-requests, equals: "yes" }
|
|
180
|
+
tags: [dns, amplification]
|
|
@@ -14,24 +14,34 @@ change is creating a backup/export.
|
|
|
14
14
|
written on the router's flash. Give it a descriptive `label` (e.g. `pre-audit`).
|
|
15
15
|
2. **Device restore point** — `create_backup` (binary, for full restore) and
|
|
16
16
|
`create_export` (text `.rsc`, for review/diff). List them with `list_backups`.
|
|
17
|
-
3. **
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
`list_ip_services`.
|
|
24
|
-
- Routing: `list_routes`, `get_routing_table`.
|
|
25
|
-
- Security: `list_filter_rules`, `list_nat_rules`, `list_address_lists`,
|
|
26
|
-
`list_users`, `list_certificates`.
|
|
27
|
-
- VPN/QoS: `list_wireguard_interfaces` + `list_wireguard_peers`,
|
|
28
|
-
`list_simple_queues`, `list_queue_trees`.
|
|
29
|
-
- Containers: `list_containers` (if the container feature is enabled).
|
|
30
|
-
- Automation: `list_schedulers`, `list_scripts`.
|
|
17
|
+
3. **The write-up** — call `explain_device`. It analyses the configuration on the
|
|
18
|
+
MCP host and returns a finished architecture document: inferred role with the
|
|
19
|
+
signals behind it, topology with a Mermaid diagram, addressing and DHCP
|
|
20
|
+
scopes, the internet path, what each firewall chain does, what is exposed to
|
|
21
|
+
the internet, tunnels, management services, and an explicit list of anything
|
|
22
|
+
it did not recognise.
|
|
31
23
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
Do **not** pull `/export` into the conversation to write this by hand. The
|
|
25
|
+
tool's output is a fraction of the size, already analysed, and identical
|
|
26
|
+
across runs — which is what lets `diff_explanations` compare two dates later.
|
|
27
|
+
Use `explain_section` when the user asks about one area rather than the whole
|
|
28
|
+
router.
|
|
29
|
+
|
|
30
|
+
4. **Fill the gaps `explain_device` deliberately leaves.** A configuration export
|
|
31
|
+
describes what is _defined_, never what is _running_, so add the live facts it
|
|
32
|
+
cannot know:
|
|
33
|
+
- `get_system_resources`, `get_routerboard`, `get_installed_packages` — uptime,
|
|
34
|
+
hardware, what is actually installed.
|
|
35
|
+
- `list_certificates` — flag anything expiring within 30 days.
|
|
36
|
+
- `get_wireguard_status`, `list_ipsec_active_peers` — which tunnels are up
|
|
37
|
+
right now, as opposed to merely configured.
|
|
38
|
+
- `list_schedulers`, `list_scripts` — scheduled automation.
|
|
39
|
+
5. **Things worth reviewing** — run `firewall_audit` and
|
|
40
|
+
`run_security_hardening_audit` and summarise their findings rather than
|
|
41
|
+
re-deriving them by eye. Read the narrative's "what this document does not
|
|
42
|
+
cover" section aloud to the user: that is the part nobody understood, and it
|
|
43
|
+
is where the surprises live.
|
|
44
|
+
|
|
45
|
+
Present the narrative first, then the live facts, then the review section.
|
|
36
46
|
Reference the snapshot id and backup/export filenames you created so the user
|
|
37
47
|
knows their restore points.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build-tunnel-transactionally
|
|
3
|
+
title: Build a site-to-site tunnel as one cross-device transaction
|
|
4
|
+
description: Configure both ends of a tunnel under coordinated Safe Mode — verified while still uncommitted, then committed everywhere or rolled back everywhere. Never a half-built tunnel.
|
|
5
|
+
arguments:
|
|
6
|
+
- name: site_a
|
|
7
|
+
description: Configured device name of the first router (one end of the tunnel).
|
|
8
|
+
required: true
|
|
9
|
+
- name: site_b
|
|
10
|
+
description: Configured device name of the second router (the other end).
|
|
11
|
+
required: true
|
|
12
|
+
- name: tunnel
|
|
13
|
+
description: What to build (e.g. "WireGuard 10.99.0.0/30", "GRE over the existing WAN", "IPsec site-to-site"). If omitted, WireGuard is a good default.
|
|
14
|
+
required: false
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
Build a tunnel between **{{site_a}}** and **{{site_b}}** as a single
|
|
18
|
+
cross-device transaction, so it either works on both ends or exists on neither.
|
|
19
|
+
|
|
20
|
+
Tunnel to build: {{tunnel}}
|
|
21
|
+
|
|
22
|
+
The failure this avoids: configured end-by-end, a tunnel whose second half fails
|
|
23
|
+
leaves the first half live — and if the failing change was a firewall rule, a
|
|
24
|
+
router you can no longer reach. Under a transaction each device stages its
|
|
25
|
+
changes in its own Safe Mode session; nothing persists until every end has been
|
|
26
|
+
verified.
|
|
27
|
+
|
|
28
|
+
Follow these steps:
|
|
29
|
+
|
|
30
|
+
1. **Look before you write.** Read both ends: `list_interfaces`,
|
|
31
|
+
`list_ip_addresses`, and for WireGuard `list_wireguard_interfaces` /
|
|
32
|
+
`list_wireguard_peers`. Pick addresses and a port that do not collide with
|
|
33
|
+
anything already configured, and note which device you reach the other
|
|
34
|
+
THROUGH (that one must commit last).
|
|
35
|
+
|
|
36
|
+
2. **Open the transaction.** Call `begin_transaction` with
|
|
37
|
+
`devices=["{{site_a}}", "{{site_b}}"]`, a `label`, and — this is the part that
|
|
38
|
+
makes it safe — `assertions` that prove the tunnel actually works:
|
|
39
|
+
- `{"kind": "ping", "from": "{{site_a}}", "to": "<{{site_b}} tunnel address>"}`
|
|
40
|
+
- `{"kind": "wireguard-peer-handshake", "device": "{{site_a}}", "peer": "<{{site_b}} public key>"}`
|
|
41
|
+
(WireGuard only)
|
|
42
|
+
- `{"kind": "reachable", "device": "{{site_b}}"}` — cheap insurance that the
|
|
43
|
+
far end is still answering after its own changes.
|
|
44
|
+
Set `jump_host` if one router is reached through the other; the tool warns
|
|
45
|
+
when it is not committed last.
|
|
46
|
+
|
|
47
|
+
3. **Queue every command.** One `add_transaction_step` per RouterOS command,
|
|
48
|
+
naming the participant it runs on. Nothing executes yet. Cover both ends
|
|
49
|
+
completely: interface, addresses, peer/keys, the firewall rule that permits
|
|
50
|
+
the tunnel port, and the route(s) over it.
|
|
51
|
+
|
|
52
|
+
4. **Prepare and verify.** Call `verify_transaction`. This snapshots each device,
|
|
53
|
+
applies its steps inside Safe Mode, and runs the assertions against the
|
|
54
|
+
still-uncommitted fleet. If anything fails, everything is rolled back
|
|
55
|
+
automatically and the result is `ABORTED` — nothing changed anywhere. Report
|
|
56
|
+
which assertion failed and fix the plan before retrying.
|
|
57
|
+
|
|
58
|
+
5. **Commit.** Only after a clean verify, call `commit_transaction`. Read the
|
|
59
|
+
terminal state out loud:
|
|
60
|
+
- `COMMITTED` — both ends persisted; confirm with a final `ping` and
|
|
61
|
+
`get_wireguard_status`.
|
|
62
|
+
- `ABORTED` — nothing changed; safe to retry.
|
|
63
|
+
- `PARTIAL` — **tell the user immediately and stop**. Name each device's state
|
|
64
|
+
and the snapshot id in the report; the fix is a manual restore
|
|
65
|
+
(`diff_config_snapshots <id> live`, then `config_reconcile` or
|
|
66
|
+
`restore_backup`), not another blind attempt.
|
|
67
|
+
|
|
68
|
+
6. **If you need to back out** at any point before commit, call
|
|
69
|
+
`abort_transaction` — it closes every Safe Mode session and RouterOS reverts
|
|
70
|
+
the staged changes.
|
|
71
|
+
|
|
72
|
+
Be explicit about the limits when you report: this is a best-effort distributed
|
|
73
|
+
commit, not a database transaction. Commits are sequential, so there is a
|
|
74
|
+
seconds-long window where one end is committed and the other is not.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fleet-rollout
|
|
3
|
+
title: Roll one change out across the fleet, safely
|
|
4
|
+
description: Apply the same change to many routers as canary → wave → fleet, with a health gate and soak between waves and automatic revert of everything already changed on the first failure.
|
|
5
|
+
arguments:
|
|
6
|
+
- name: change
|
|
7
|
+
description: What to change on every device (e.g. "set the NTP servers to 10.0.0.1 and 10.0.0.2", "add the management address-list entry", "disable the telnet service").
|
|
8
|
+
required: true
|
|
9
|
+
- name: targets
|
|
10
|
+
description: Which devices — a list of names, "all", or a tag (e.g. "branch"). If omitted, ask before selecting anything.
|
|
11
|
+
required: false
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Roll this change out across the fleet: **{{change}}**
|
|
15
|
+
|
|
16
|
+
Targets: {{targets}}
|
|
17
|
+
|
|
18
|
+
This is the highest-blast-radius thing in the toolbox: one mistake, applied
|
|
19
|
+
automatically to every router. The structure that makes it safe is
|
|
20
|
+
canary → wave → fleet with a health gate between waves — use it, and show your
|
|
21
|
+
work before executing.
|
|
22
|
+
|
|
23
|
+
Follow these steps:
|
|
24
|
+
|
|
25
|
+
1. **Turn the request into exact commands.** Work out the RouterOS commands for
|
|
26
|
+
the change and check them against one device's current config first (a `get_*`
|
|
27
|
+
or `list_*` read). A rollout applies the same commands everywhere, so a
|
|
28
|
+
command that depends on a device-specific name (an interface, a list) is a
|
|
29
|
+
trap — say so rather than rolling it out.
|
|
30
|
+
|
|
31
|
+
2. **Preview with `plan_rollout`.** It contacts nothing. Show the human:
|
|
32
|
+
- which devices matched (and, for a selector, which were excluded and why),
|
|
33
|
+
- the wave split — the canary is the device that will find the problem,
|
|
34
|
+
- the exact commands, and the estimated duration.
|
|
35
|
+
Order matters for an explicit list: put the router you reach the others
|
|
36
|
+
THROUGH **last**, so a change that severs its path cannot strand the rest.
|
|
37
|
+
|
|
38
|
+
3. **Get agreement before executing.** Do not call `start_rollout` on your own
|
|
39
|
+
initiative for a fleet-wide change. Present the plan and ask.
|
|
40
|
+
|
|
41
|
+
4. **Execute with `start_rollout confirm=true`.** Defaults worth stating out
|
|
42
|
+
loud when you do:
|
|
43
|
+
- `soakSeconds=30` — the wait after each wave's health check. A change that
|
|
44
|
+
breaks connectivity ten seconds later must not already be on every router.
|
|
45
|
+
- `onFailure=halt-and-revert` — the first failed gate stops the rollout and
|
|
46
|
+
restores every device it already changed, newest first.
|
|
47
|
+
Raise the soak for anything touching routing, firewall or interfaces; those
|
|
48
|
+
break with a delay.
|
|
49
|
+
|
|
50
|
+
5. **Read the terminal state out loud.** Each one means something different:
|
|
51
|
+
- `COMPLETED` — every device applied the change.
|
|
52
|
+
- `HALTED and REVERTED` — a gate failed; the fleet is back where it started.
|
|
53
|
+
Report WHICH device and why, especially if the failure was on an untouched
|
|
54
|
+
device (that means the change broke something fleet-wide).
|
|
55
|
+
- `HALTED` (`halt-and-hold`) — changed devices are still changed, on purpose,
|
|
56
|
+
for inspection.
|
|
57
|
+
- `COMPLETED WITH FAILURES` — `continue` was set; list what failed.
|
|
58
|
+
- `NEEDS ATTENTION` — **stop and tell the user immediately.** A revert itself
|
|
59
|
+
failed; the report names the device and the snapshot id. The fix is a manual
|
|
60
|
+
restore (`diff_config_snapshots <id> live`, then `config_reconcile`), not
|
|
61
|
+
another rollout.
|
|
62
|
+
|
|
63
|
+
6. **Verify a sample afterwards.** Read the changed setting back from the canary
|
|
64
|
+
and from one device in the last wave. A rollout that reports success and a
|
|
65
|
+
device that actually has the setting are two different facts.
|
|
66
|
+
|
|
67
|
+
To stop a rollout in flight: `abort_rollout` — it reverts everything this
|
|
68
|
+
rollout changed, regardless of `onFailure`.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: setup-traffic-flow
|
|
3
|
+
title: Set up continuous traffic analytics (NetFlow/IPFIX)
|
|
4
|
+
description: Enable /ip traffic-flow, point the router at this host's collector, and start answering "who is using my bandwidth" continuously — cheap flow metadata, no packet payload.
|
|
5
|
+
arguments:
|
|
6
|
+
- name: collector_address
|
|
7
|
+
description: The IP address of THIS MCP host as the router sees it (e.g. 192.168.88.50). If omitted, work it out from the router's neighbour/ARP table or ask.
|
|
8
|
+
required: false
|
|
9
|
+
- name: interfaces
|
|
10
|
+
description: Which interfaces to sample (e.g. "ether1", "bridge"). Defaults to all.
|
|
11
|
+
required: false
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Set up continuous traffic-flow analytics for this MikroTik device.
|
|
15
|
+
|
|
16
|
+
Collector address: {{collector_address}}
|
|
17
|
+
Interfaces to sample: {{interfaces}}
|
|
18
|
+
|
|
19
|
+
**Explain this before you do it.** Traffic Flow makes the router export a
|
|
20
|
+
continuous stream of flow records — source and destination address, ports,
|
|
21
|
+
protocol, byte and packet counts — to a collector over UDP. It carries **no
|
|
22
|
+
packet payload**, which is exactly why it is cheap and privacy-preserving next
|
|
23
|
+
to the packet sniffer, but it is still a description of who on the network talks
|
|
24
|
+
to whom, sent continuously to a host. Say where it will be sent, and confirm the
|
|
25
|
+
address is one the user controls.
|
|
26
|
+
|
|
27
|
+
Follow these steps:
|
|
28
|
+
|
|
29
|
+
1. **Look before you write.** `get_traffic_flow_settings` and
|
|
30
|
+
`list_traffic_flow_targets` — flow export may already be on and pointed
|
|
31
|
+
somewhere. If a target already exists that is NOT this host, mention it
|
|
32
|
+
rather than silently adding a second.
|
|
33
|
+
|
|
34
|
+
2. **Work out the collector address.** It must be this MCP host's address _as
|
|
35
|
+
the router sees it_ — not `127.0.0.1`, which would make the router export to
|
|
36
|
+
itself. If it was not supplied, look at the router's `/ip address` and
|
|
37
|
+
neighbour tables to find the management subnet, and confirm the choice with
|
|
38
|
+
the user rather than guessing.
|
|
39
|
+
|
|
40
|
+
3. **Start the receiver first.** `start_flow_collector` (default UDP 2055).
|
|
41
|
+
Starting the exporter before the collector just throws the first minutes of
|
|
42
|
+
data away.
|
|
43
|
+
|
|
44
|
+
4. **Point the device at it.** `add_traffic_flow_target` with
|
|
45
|
+
`dst_address=<collector>`, `port=2055`, `version=9`. Use **9 or ipfix** —
|
|
46
|
+
v5 is IPv4-only and has no template mechanism, so IPv6 flows would be missing
|
|
47
|
+
entirely. A short `v9_template_timeout` (e.g. `30s`) makes the first decoded
|
|
48
|
+
flows appear sooner.
|
|
49
|
+
|
|
50
|
+
5. **Enable export.** `set_traffic_flow_settings` with `enabled=true` and the
|
|
51
|
+
requested `interfaces` (default all). Mention the two timeouts:
|
|
52
|
+
`active_flow_timeout` (default 30m) is how long a long-lived flow is held
|
|
53
|
+
before being reported — lower it if the user wants fresher numbers and can
|
|
54
|
+
accept more export traffic.
|
|
55
|
+
|
|
56
|
+
6. **Wait for the first template, then verify.** v9/IPFIX exporters send the
|
|
57
|
+
template periodically and data records are undecodable until it arrives, so
|
|
58
|
+
the first minute or two can legitimately be empty. Then run
|
|
59
|
+
`flow_top_talkers` — if it is still empty, its output names the reason
|
|
60
|
+
(collector not running, nothing received, templates pending, decode errors);
|
|
61
|
+
act on that rather than guessing.
|
|
62
|
+
|
|
63
|
+
7. **Report what they now have.** Top talkers for the last window, and where to
|
|
64
|
+
look next: `analyze_flows` for the full report, the dashboard's **Flows**
|
|
65
|
+
page for the live view.
|
|
66
|
+
|
|
67
|
+
To undo all of this later: `remove_traffic_flow_target`,
|
|
68
|
+
`set_traffic_flow_settings enabled=false`, `stop_flow_collector`.
|
package/schemas/README.md
CHANGED
|
@@ -7,7 +7,7 @@ edit by hand — regenerate instead.
|
|
|
7
7
|
| File | Contents |
|
|
8
8
|
| -------------------- | ------------------------------------------------------------------------------------------ |
|
|
9
9
|
| `config.schema.json` | The runtime configuration object (env vars / CLI flags). |
|
|
10
|
-
| `tool-catalog.json` | Every one of the
|
|
10
|
+
| `tool-catalog.json` | Every one of the 885 tools: `name`, `risk`, `title`, `description`, and input JSON Schema. |
|
|
11
11
|
| `tools/<name>.json` | The input JSON Schema for a single tool. |
|
|
12
12
|
|
|
13
13
|
`risk` is derived from the MCP tool annotations:
|