# Directory Structure ``` ├── .env-example ├── .gitignore ├── .python-version ├── LICENSE ├── meraki-mcp.py ├── pyproject.toml ├── README.md └── requirements.txt ``` # Files -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- ``` 1 | 3.13 2 | ``` -------------------------------------------------------------------------------- /.env-example: -------------------------------------------------------------------------------- ``` 1 | MERAKI_API_KEY="Meraki API Key here" 2 | MERAKI_ORG_ID="Meraki Org ID here" ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | .Python 7 | build/ 8 | develop-eggs/ 9 | dist/ 10 | downloads/ 11 | eggs/ 12 | .eggs/ 13 | lib/ 14 | lib64/ 15 | parts/ 16 | sdist/ 17 | var/ 18 | wheels/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | uv.lock 23 | 24 | # Virtual Environment 25 | venv/ 26 | ENV/ 27 | env/ 28 | .env 29 | .venv/ 30 | 31 | # IDE 32 | .idea/ 33 | .vscode/ 34 | *.swp 35 | *.swo 36 | 37 | # OS 38 | .DS_Store 39 | Thumbs.db 40 | 41 | # Documentation 42 | ADDITIONAL_TOOLS_ROADMAP.md ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Meraki Magic MCP 2 | 3 | Meraki Magic is a Python-based MCP (Model Context Protocol) server for Cisco's Meraki Dashboard. Meraki Magic provides tools for querying the Meraki Dashboard API to discover, monitor, and manage your Meraki environment. 4 | 5 | ## Features 6 | 7 | - **Comprehensive Network Management**: Full network discovery, monitoring, and management 8 | - **Advanced Device Management**: Device provisioning, monitoring, and live tools 9 | - **Wireless Management**: Complete wireless SSID and RF profile management 10 | - **Switch Management**: Port management, VLAN configuration, and QoS rules 11 | - **Appliance Management**: VPN, firewall, content filtering, and security management 12 | - **Camera Management**: Analytics, snapshots, and sense configuration 13 | - **Network Automation**: Action batches and bulk operations 14 | - **Live Device Tools**: Ping, cable testing, LED control, and wake-on-LAN 15 | - **Advanced Monitoring**: Events, alerts, and performance analytics 16 | 17 | ## Installation 18 | 19 | 1. Clone the repository: 20 | ```bash 21 | git clone https://github.com/mkutka/meraki-magic.git 22 | cd meraki-magic-mcp 23 | ``` 24 | 25 | 2. Create a virtual environment and activate it: 26 | ```bash 27 | python -m venv .venv 28 | source .venv/bin/activate # On Windows: .venv\Scripts\activate 29 | ``` 30 | 31 | 3. Install dependencies: 32 | ```bash 33 | pip install -r requirements.txt 34 | ``` 35 | 36 | ## Configuration 37 | 38 | 1. Copy the example environment file: 39 | ```bash 40 | cp .env-example .env 41 | ``` 42 | 43 | 2. Update the `.env` file with your Meraki API Key and Organization ID: 44 | ```env 45 | MERAKI_API_KEY="Meraki API Key here" 46 | MERAKI_ORG_ID="Meraki Org ID here" 47 | ``` 48 | 49 | ## Usage With Claude Desktop Client 50 | 51 | 1. Configure Claude Desktop to use this MCP server: 52 | 53 | - Open Claude Desktop 54 | - Go to Settings > Developer > Edit Config 55 | - Add the following configuration file `claude_desktop_config.json` 56 | 57 | ```json 58 | { 59 | "mcpServers": { 60 | "Meraki_Magic_MCP": { 61 | "command": "/Users/mkutka/meraki-magic-mcp/.venv/bin/fastmcp", 62 | "args": [ 63 | "run", 64 | "/Users/mkutka/meraki-magic-mcp/meraki-mcp.py" 65 | ] 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | - Replace the paths above to reflect your local environment. 72 | 73 | 2. Restart Claude Desktop 74 | 75 | 3. Interact with Claude Desktop 76 | 77 | ## Network Tools Guide 78 | 79 | This guide provides a comprehensive overview of all the network tools available in the Meraki Magic MCP, organized by category and functionality. 80 | 81 | ### Table of Contents 82 | 83 | 1. [Organization Management Tools](#organization-management-tools) 84 | 2. [Network Management Tools](#network-management-tools) 85 | 3. [Device Management Tools](#device-management-tools) 86 | 4. [Wireless Management Tools](#wireless-management-tools) 87 | 5. [Switch Management Tools](#switch-management-tools) 88 | 6. [Appliance Management Tools](#appliance-management-tools) 89 | 7. [Camera Management Tools](#camera-management-tools) 90 | 8. [Network Automation Tools](#network-automation-tools) 91 | 9. [Advanced Monitoring Tools](#advanced-monitoring-tools) 92 | 10. [Live Device Tools](#live-device-tools) 93 | 94 | --- 95 | 96 | ## Organization Management Tools 97 | 98 | ### Basic Organization Operations 99 | - **`get_organizations()`** - Get a list of organizations the user has access to 100 | - **`get_organization_details(org_id)`** - Get details for a specific organization 101 | - **`get_organization_status(org_id)`** - Get the status and health of an organization 102 | - **`get_organization_inventory(org_id)`** - Get the inventory for an organization 103 | - **`get_organization_license(org_id)`** - Get the license state for an organization 104 | - **`get_organization_conf_change(org_id)`** - Get the org change state for an organization 105 | 106 | ### Advanced Organization Management 107 | - **`get_organization_admins(org_id)`** - Get a list of organization admins 108 | - **`create_organization_admin(org_id, email, name, org_access, tags, networks)`** - Create a new organization admin 109 | - **`get_organization_api_requests(org_id, timespan)`** - Get organization API request history 110 | - **`get_organization_webhook_logs(org_id, timespan)`** - Get organization webhook logs 111 | 112 | ### Network Management 113 | - **`get_networks(org_id)`** - Get a list of networks from Meraki 114 | - **`create_network(name, tags, productTypes, org_id, copyFromNetworkId)`** - Create a new network 115 | - **`delete_network(network_id)`** - Delete a network in Meraki 116 | - **`get_network_details(network_id)`** - Get details for a specific network 117 | - **`update_network(network_id, update_data)`** - Update a network's properties 118 | 119 | --- 120 | 121 | ## Network Management Tools 122 | 123 | ### Network Monitoring 124 | - **`get_network_events(network_id, timespan, per_page)`** - Get network events history 125 | - **`get_network_event_types(network_id)`** - Get available network event types 126 | - **`get_network_alerts_history(network_id, timespan)`** - Get network alerts history 127 | - **`get_network_alerts_settings(network_id)`** - Get network alerts settings 128 | - **`update_network_alerts_settings(network_id, defaultDestinations, alerts)`** - Update network alerts settings 129 | 130 | ### Client Management 131 | - **`get_clients(network_id, timespan)`** - Get a list of clients from a network 132 | - **`get_client_details(network_id, client_id)`** - Get details for a specific client 133 | - **`get_client_usage(network_id, client_id)`** - Get the usage history for a client 134 | - **`get_client_policy(network_id, client_id)`** - Get the policy for a specific client 135 | - **`update_client_policy(network_id, client_id, device_policy, group_policy_id)`** - Update policy for a client 136 | 137 | ### Network Traffic & Analysis 138 | - **`get_network_traffic(network_id, timespan)`** - Get traffic analysis data for a network 139 | 140 | --- 141 | 142 | ## Device Management Tools 143 | 144 | ### Device Information 145 | - **`get_devices(org_id)`** - Get a list of devices from Meraki 146 | - **`get_network_devices(network_id)`** - Get a list of devices in a specific network 147 | - **`get_device_details(serial)`** - Get details for a specific device by serial number 148 | - **`get_device_status(serial)`** - Get the current status of a device 149 | - **`get_device_uplink(serial)`** - Get the uplink status of a device 150 | 151 | ### Device Operations 152 | - **`update_device(serial, device_settings)`** - Update a device in the Meraki organization 153 | - **`claim_devices(network_id, serials)`** - Claim one or more devices into a Meraki network 154 | - **`remove_device(serial)`** - Remove a device from its network 155 | - **`reboot_device(serial)`** - Reboot a device 156 | 157 | ### Device Monitoring 158 | - **`get_device_clients(serial, timespan)`** - Get clients connected to a specific device 159 | 160 | --- 161 | 162 | ## Live Device Tools 163 | 164 | ### Network Diagnostics 165 | - **`ping_device(serial, target_ip, count)`** - Ping a device from another device 166 | - **`get_device_ping_results(serial, ping_id)`** - Get results from a device ping test 167 | - **`cable_test_device(serial, ports)`** - Run cable test on device ports 168 | - **`get_device_cable_test_results(serial, cable_test_id)`** - Get results from a device cable test 169 | 170 | ### Device Control 171 | - **`blink_device_leds(serial, duration)`** - Blink device LEDs for identification 172 | - **`wake_on_lan_device(serial, mac)`** - Send wake-on-LAN packet to a device 173 | 174 | --- 175 | 176 | ## Wireless Management Tools 177 | 178 | ### Basic Wireless Operations 179 | - **`get_wireless_ssids(network_id)`** - Get wireless SSIDs for a network 180 | - **`update_wireless_ssid(network_id, ssid_number, ssid_settings)`** - Update a wireless SSID 181 | - **`get_wireless_settings(network_id)`** - Get wireless settings for a network 182 | 183 | ### Advanced Wireless Management 184 | - **`get_wireless_rf_profiles(network_id)`** - Get wireless RF profiles for a network 185 | - **`create_wireless_rf_profile(network_id, name, band_selection_type, **kwargs)`** - Create a wireless RF profile 186 | - **`get_wireless_channel_utilization(network_id, timespan)`** - Get wireless channel utilization history 187 | - **`get_wireless_signal_quality(network_id, timespan)`** - Get wireless signal quality history 188 | - **`get_wireless_connection_stats(network_id, timespan)`** - Get wireless connection statistics 189 | - **`get_wireless_client_connectivity_events(network_id, client_id, timespan)`** - Get wireless client connectivity events 190 | 191 | --- 192 | 193 | ## Switch Management Tools 194 | 195 | ### Basic Switch Operations 196 | - **`get_switch_ports(serial)`** - Get ports for a switch 197 | - **`update_switch_port(serial, port_id, name, tags, enabled, vlan)`** - Update a switch port 198 | - **`get_switch_vlans(network_id)`** - Get VLANs for a network 199 | - **`create_switch_vlan(network_id, vlan_id, name, subnet, appliance_ip)`** - Create a switch VLAN 200 | 201 | ### Advanced Switch Management 202 | - **`get_switch_port_statuses(serial)`** - Get switch port statuses 203 | - **`cycle_switch_ports(serial, ports)`** - Cycle (restart) switch ports 204 | - **`get_switch_access_control_lists(network_id)`** - Get switch access control lists 205 | - **`update_switch_access_control_lists(network_id, rules)`** - Update switch access control lists 206 | - **`get_switch_qos_rules(network_id)`** - Get switch QoS rules 207 | - **`create_switch_qos_rule(network_id, vlan, protocol, src_port, **kwargs)`** - Create a switch QoS rule 208 | 209 | --- 210 | 211 | ## Appliance Management Tools 212 | 213 | ### Basic Appliance Operations 214 | - **`get_security_center(network_id)`** - Get security information for a network 215 | - **`get_vpn_status(network_id)`** - Get VPN status for a network 216 | - **`get_firewall_rules(network_id)`** - Get firewall rules for a network 217 | - **`update_firewall_rules(network_id, rules)`** - Update firewall rules for a network 218 | 219 | ### Advanced Appliance Management 220 | - **`get_appliance_vpn_site_to_site(network_id)`** - Get appliance VPN site-to-site configuration 221 | - **`update_appliance_vpn_site_to_site(network_id, mode, hubs, subnets)`** - Update appliance VPN site-to-site configuration 222 | - **`get_appliance_content_filtering(network_id)`** - Get appliance content filtering settings 223 | - **`update_appliance_content_filtering(network_id, **kwargs)`** - Update appliance content filtering settings 224 | - **`get_appliance_security_events(network_id, timespan)`** - Get appliance security events 225 | - **`get_appliance_traffic_shaping(network_id)`** - Get appliance traffic shaping settings 226 | - **`update_appliance_traffic_shaping(network_id, global_bandwidth_limits)`** - Update appliance traffic shaping settings 227 | 228 | --- 229 | 230 | ## Camera Management Tools 231 | 232 | ### Basic Camera Operations 233 | - **`get_camera_video_settings(network_id, serial)`** - Get video settings for a camera 234 | - **`get_camera_quality_settings(network_id)`** - Get quality and retention settings for cameras 235 | 236 | ### Advanced Camera Management 237 | - **`get_camera_analytics_live(serial)`** - Get live camera analytics 238 | - **`get_camera_analytics_overview(serial, timespan)`** - Get camera analytics overview 239 | - **`get_camera_analytics_zones(serial)`** - Get camera analytics zones 240 | - **`generate_camera_snapshot(serial, timestamp)`** - Generate a camera snapshot 241 | - **`get_camera_sense(serial)`** - Get camera sense configuration 242 | - **`update_camera_sense(serial, sense_enabled, mqtt_broker_id, audio_detection)`** - Update camera sense configuration 243 | 244 | --- 245 | 246 | ## Network Automation Tools 247 | 248 | ### Action Batches 249 | - **`create_action_batch(org_id, actions, confirmed, synchronous)`** - Create an action batch for bulk operations 250 | - **`get_action_batch_status(org_id, batch_id)`** - Get action batch status 251 | - **`get_action_batches(org_id)`** - Get all action batches for an organization 252 | 253 | --- 254 | 255 | ## Advanced Monitoring Tools 256 | 257 | ### Network Events & Alerts 258 | - **`get_network_events(network_id, timespan, per_page)`** - Get network events history 259 | - **`get_network_event_types(network_id)`** - Get available network event types 260 | - **`get_network_alerts_history(network_id, timespan)`** - Get network alerts history 261 | - **`get_network_alerts_settings(network_id)`** - Get network alerts settings 262 | - **`update_network_alerts_settings(network_id, defaultDestinations, alerts)`** - Update network alerts settings 263 | 264 | ### Organization Monitoring 265 | - **`get_organization_api_requests(org_id, timespan)`** - Get organization API request history 266 | - **`get_organization_webhook_logs(org_id, timespan)`** - Get organization webhook logs 267 | 268 | --- 269 | 270 | ## Schema Definitions 271 | 272 | The MCP includes comprehensive Pydantic schemas for data validation: 273 | 274 | - `SsidUpdateSchema` - Wireless SSID configuration 275 | - `FirewallRule` - Firewall rule configuration 276 | - `DeviceUpdateSchema` - Device update parameters 277 | - `NetworkUpdateSchema` - Network update parameters 278 | - `AdminCreationSchema` - Admin creation parameters 279 | - `ActionBatchSchema` - Action batch configuration 280 | - `VpnSiteToSiteSchema` - VPN site-to-site configuration 281 | - `ContentFilteringSchema` - Content filtering settings 282 | - `TrafficShapingSchema` - Traffic shaping configuration 283 | - `CameraSenseSchema` - Camera sense settings 284 | - `SwitchQosRuleSchema` - Switch QoS rule configuration 285 | 286 | --- 287 | 288 | ## Best Practices 289 | 290 | 1. **Error Handling**: Always check API responses for errors 291 | 2. **Rate Limiting**: The Meraki API has rate limits; use appropriate delays 292 | 3. **Batch Operations**: Use action batches for bulk operations 293 | 4. **Validation**: Use the provided schemas for data validation 294 | 5. **Monitoring**: Regularly check network events and alerts 295 | 6. **Security**: Keep API keys secure and rotate them regularly 296 | 297 | --- 298 | 299 | ## Troubleshooting 300 | 301 | ### Common Issues 302 | 303 | 1. **Authentication Errors**: Verify your API key is correct and has appropriate permissions 304 | 2. **Rate Limiting**: If you encounter rate limiting, implement delays between requests 305 | 3. **Network Not Found**: Ensure the network ID is correct and accessible 306 | 4. **Device Not Found**: Verify the device serial number is correct and the device is online 307 | 308 | ### Debug Information 309 | 310 | Enable debug logging by setting the appropriate log level in your environment. 311 | 312 | --- 313 | 314 | ## Additional Resources 315 | 316 | - [Meraki API Documentation](https://developer.cisco.com/meraki/api-v1/) 317 | - [MCP Protocol Documentation](https://modelcontextprotocol.io/) 318 | - [FastMCP Documentation](https://github.com/jlowin/fastmcp) 319 | 320 | For more detailed information about additional tools and future enhancements, see the [Additional Tools Roadmap](ADDITIONAL_TOOLS_ROADMAP.md). 321 | 322 | --- 323 | 324 | ## ⚠️ Disclaimer 325 | 326 | **IMPORTANT: PRODUCTION USE DISCLAIMER** 327 | 328 | This software is provided "AS IS" without warranty of any kind, either express or implied. The authors and contributors make no representations or warranties regarding the suitability, reliability, availability, accuracy, or completeness of this software for any purpose. 329 | 330 | **USE AT YOUR OWN RISK**: This MCP server is designed for development, testing, and educational purposes. Running this software in production environments is done entirely at your own risk. The authors and contributors are not responsible for any damages, data loss, service interruptions, or other issues that may arise from the use of this software in production environments. 331 | 332 | **SECURITY CONSIDERATIONS**: This software requires access to your Meraki API credentials. Ensure that: 333 | - API keys are stored securely and not committed to version control 334 | - API keys have appropriate permissions and are rotated regularly 335 | - Network access is properly secured 336 | - Regular security audits are performed 337 | 338 | **NO WARRANTY**: The authors disclaim all warranties, including but not limited to warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the authors be liable for any claim, damages, or other liability arising from the use of this software. 339 | 340 | **SUPPORT**: This is an open-source project. For production use, consider implementing additional testing, monitoring, and support mechanisms appropriate for your environment. ``` -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- ```toml 1 | [project] 2 | name = "meraki-magic-mcp" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.13" 7 | dependencies = [ 8 | "mcp[cli]>=1.8.0", 9 | "meraki>=2.0.2", 10 | ] 11 | ``` -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- ``` 1 | aiohappyeyeballs==2.6.1 2 | aiohttp==3.11.18 3 | aiosignal==1.3.2 4 | annotated-types==0.7.0 5 | anyio==4.9.0 6 | attrs==25.3.0 7 | build==1.2.2.post1 8 | certifi==2025.4.26 9 | charset-normalizer==3.4.2 10 | click==8.1.8 11 | exceptiongroup==1.2.2 12 | fastmcp==2.2.10 13 | frozenlist==1.6.0 14 | h11==0.16.0 15 | httpcore==1.0.9 16 | httpx==0.28.1 17 | httpx-sse==0.4.0 18 | idna==3.10 19 | iniconfig==2.1.0 20 | Jinja2==3.1.6 21 | markdown-it-py==3.0.0 22 | MarkupSafe==3.0.2 23 | mcp==1.7.1 24 | mdurl==0.1.2 25 | meraki==2.0.2 26 | multidict==6.4.3 27 | openapi-pydantic==0.5.1 28 | packaging==25.0 29 | pluggy==1.5.0 30 | propcache==0.3.1 31 | pydantic==2.11.4 32 | pydantic-settings==2.9.1 33 | pydantic_core==2.33.2 34 | Pygments==2.19.1 35 | pyproject_hooks==1.2.0 36 | pytest==7.4.4 37 | python-dotenv==1.1.0 38 | python-multipart==0.0.20 39 | requests==2.32.3 40 | rich==14.0.0 41 | setuptools==70.3.0 42 | shellingham==1.5.4 43 | sniffio==1.3.1 44 | sse-starlette==2.3.4 45 | starlette==0.46.2 46 | typer==0.15.3 47 | typing-inspection==0.4.0 48 | typing_extensions==4.13.2 49 | urllib3==2.4.0 50 | uvicorn==0.34.2 51 | websockets==15.0.1 52 | wheel==0.45.1 53 | yarl==1.20.0 54 | ``` -------------------------------------------------------------------------------- /meraki-mcp.py: -------------------------------------------------------------------------------- ```python 1 | import os 2 | import json 3 | import meraki 4 | import asyncio 5 | import functools 6 | from typing import Dict, List, Optional, Any, TypedDict, Union, Callable 7 | from pydantic import BaseModel, Field 8 | from mcp.server.fastmcp import FastMCP 9 | from dotenv import load_dotenv 10 | 11 | # Load environment variables from .env file 12 | load_dotenv() 13 | 14 | # Create an MCP server 15 | mcp = FastMCP("Meraki Magic MCP") 16 | 17 | # Configuration 18 | MERAKI_API_KEY = os.getenv("MERAKI_API_KEY") 19 | MERAKI_ORG_ID = os.getenv("MERAKI_ORG_ID") 20 | 21 | # Initialize Meraki API client using Meraki SDK 22 | dashboard = meraki.DashboardAPI(api_key=MERAKI_API_KEY, suppress_logging=True) 23 | 24 | ################### 25 | # ASYNC UTILITIES 26 | ################### 27 | 28 | def to_async(func: Callable) -> Callable: 29 | """ 30 | Convert a synchronous function to an asynchronous function 31 | 32 | Args: 33 | func: The synchronous function to convert 34 | 35 | Returns: 36 | An asynchronous version of the function 37 | """ 38 | @functools.wraps(func) 39 | async def wrapper(*args, **kwargs): 40 | loop = asyncio.get_event_loop() 41 | return await loop.run_in_executor( 42 | None, 43 | lambda: func(*args, **kwargs) 44 | ) 45 | return wrapper 46 | 47 | # Create async versions of commonly used Meraki API methods 48 | async_get_organizations = to_async(dashboard.organizations.getOrganizations) 49 | async_get_organization = to_async(dashboard.organizations.getOrganization) 50 | async_get_organization_networks = to_async(dashboard.organizations.getOrganizationNetworks) 51 | async_get_organization_devices = to_async(dashboard.organizations.getOrganizationDevices) 52 | async_get_network = to_async(dashboard.networks.getNetwork) 53 | async_get_network_devices = to_async(dashboard.networks.getNetworkDevices) 54 | async_get_network_clients = to_async(dashboard.networks.getNetworkClients) 55 | async_get_device = to_async(dashboard.devices.getDevice) 56 | async_update_device = to_async(dashboard.devices.updateDevice) 57 | async_get_wireless_ssids = to_async(dashboard.wireless.getNetworkWirelessSsids) 58 | async_update_wireless_ssid = to_async(dashboard.wireless.updateNetworkWirelessSsid) 59 | 60 | ################### 61 | # SCHEMA DEFINITIONS 62 | ################### 63 | 64 | # Wireless SSID Schema 65 | class Dot11wSettings(BaseModel): 66 | enabled: bool = Field(False, description="Whether 802.11w is enabled or not") 67 | required: bool = Field(False, description="Whether 802.11w is required or not") 68 | 69 | class Dot11rSettings(BaseModel): 70 | enabled: bool = Field(False, description="Whether 802.11r is enabled or not") 71 | adaptive: bool = Field(False, description="Whether 802.11r is adaptive or not") 72 | 73 | class RadiusServer(BaseModel): 74 | host: str = Field(..., description="IP address of the RADIUS server") 75 | port: int = Field(..., description="Port of the RADIUS server") 76 | secret: str = Field(..., description="Secret for the RADIUS server") 77 | radsecEnabled: Optional[bool] = Field(None, description="Whether RADSEC is enabled or not") 78 | openRoamingCertificateId: Optional[int] = Field(None, description="OpenRoaming certificate ID") 79 | caCertificate: Optional[str] = Field(None, description="CA certificate for RADSEC") 80 | 81 | class SsidUpdateSchema(BaseModel): 82 | name: Optional[str] = Field(None, description="The name of the SSID") 83 | enabled: Optional[bool] = Field(None, description="Whether the SSID is enabled or not") 84 | authMode: Optional[str] = Field(None, description="The auth mode for the SSID (e.g., 'open', 'psk', '8021x-radius')") 85 | enterpriseAdminAccess: Optional[str] = Field(None, description="Enterprise admin access setting") 86 | encryptionMode: Optional[str] = Field(None, description="The encryption mode for the SSID") 87 | psk: Optional[str] = Field(None, description="The pre-shared key for the SSID when using PSK auth mode") 88 | wpaEncryptionMode: Optional[str] = Field(None, description="WPA encryption mode (e.g., 'WPA1 and WPA2', 'WPA2 only')") 89 | dot11w: Optional[Dot11wSettings] = Field(None, description="802.11w settings") 90 | dot11r: Optional[Dot11rSettings] = Field(None, description="802.11r settings") 91 | splashPage: Optional[str] = Field(None, description="The type of splash page for the SSID") 92 | radiusServers: Optional[List[RadiusServer]] = Field(None, description="List of RADIUS servers") 93 | visible: Optional[bool] = Field(None, description="Whether the SSID is visible or not") 94 | availableOnAllAps: Optional[bool] = Field(None, description="Whether the SSID is available on all APs") 95 | bandSelection: Optional[str] = Field(None, description="Band selection for SSID (e.g., '5 GHz band only', 'Dual band operation')") 96 | 97 | # Firewall Rule Schema 98 | class FirewallRule(BaseModel): 99 | comment: str = Field(..., description="Description of the firewall rule") 100 | policy: str = Field(..., description="'allow' or 'deny'") 101 | protocol: str = Field(..., description="The protocol (e.g., 'tcp', 'udp', 'any')") 102 | srcPort: Optional[str] = Field("Any", description="Source port (e.g., '80', '443-8080', 'Any')") 103 | srcCidr: str = Field("Any", description="Source CIDR (e.g., '192.168.1.0/24', 'Any')") 104 | destPort: Optional[str] = Field("Any", description="Destination port (e.g., '80', '443-8080', 'Any')") 105 | destCidr: str = Field("Any", description="Destination CIDR (e.g., '192.168.1.0/24', 'Any')") 106 | syslogEnabled: Optional[bool] = Field(False, description="Whether syslog is enabled for this rule") 107 | 108 | # Device Update Schema 109 | class DeviceUpdateSchema(BaseModel): 110 | name: Optional[str] = Field(None, description="The name of the device") 111 | tags: Optional[List[str]] = Field(None, description="List of tags for the device") 112 | lat: Optional[float] = Field(None, description="Latitude of the device") 113 | lng: Optional[float] = Field(None, description="Longitude of the device") 114 | address: Optional[str] = Field(None, description="Physical address of the device") 115 | notes: Optional[str] = Field(None, description="Notes for the device") 116 | moveMapMarker: Optional[bool] = Field(None, description="Whether to move the map marker or not") 117 | switchProfileId: Optional[str] = Field(None, description="Switch profile ID") 118 | floorPlanId: Optional[str] = Field(None, description="Floor plan ID") 119 | 120 | # Network Update Schema 121 | class NetworkUpdateSchema(BaseModel): 122 | name: Optional[str] = Field(None, description="The name of the network") 123 | timeZone: Optional[str] = Field(None, description="The timezone of the network") 124 | tags: Optional[List[str]] = Field(None, description="List of tags for the network") 125 | enrollmentString: Optional[str] = Field(None, description="Enrollment string for the network") 126 | notes: Optional[str] = Field(None, description="Notes for the network") 127 | 128 | # Admin Creation Schema 129 | class AdminCreationSchema(BaseModel): 130 | email: str = Field(..., description="Email address of the admin") 131 | name: str = Field(..., description="Name of the admin") 132 | orgAccess: str = Field(..., description="Access level for the organization") 133 | tags: Optional[List[str]] = Field(None, description="List of tags for the admin") 134 | networks: Optional[List[dict]] = Field(None, description="Network access for the admin") 135 | 136 | # Action Batch Schema 137 | class ActionBatchSchema(BaseModel): 138 | actions: List[dict] = Field(..., description="List of actions to perform") 139 | confirmed: bool = Field(True, description="Whether the batch is confirmed") 140 | synchronous: bool = Field(False, description="Whether the batch is synchronous") 141 | 142 | # VPN Configuration Schema 143 | class VpnSiteToSiteSchema(BaseModel): 144 | mode: str = Field(..., description="VPN mode (none, full, or hub-and-spoke)") 145 | hubs: Optional[List[dict]] = Field(None, description="List of hub configurations") 146 | subnets: Optional[List[dict]] = Field(None, description="List of subnet configurations") 147 | 148 | # Content Filtering Schema 149 | class ContentFilteringSchema(BaseModel): 150 | allowedUrls: Optional[List[str]] = Field(None, description="List of allowed URLs") 151 | blockedUrls: Optional[List[str]] = Field(None, description="List of blocked URLs") 152 | blockedUrlPatterns: Optional[List[str]] = Field(None, description="List of blocked URL patterns") 153 | youtubeRestrictedForTeenagers: Optional[bool] = Field(None, description="Restrict YouTube for teenagers") 154 | youtubeRestrictedForMature: Optional[bool] = Field(None, description="Restrict YouTube for mature content") 155 | 156 | # Traffic Shaping Schema 157 | class TrafficShapingSchema(BaseModel): 158 | globalBandwidthLimits: Optional[dict] = Field(None, description="Global bandwidth limits") 159 | rules: Optional[List[dict]] = Field(None, description="Traffic shaping rules") 160 | 161 | # Camera Sense Schema 162 | class CameraSenseSchema(BaseModel): 163 | senseEnabled: Optional[bool] = Field(None, description="Whether camera sense is enabled") 164 | mqttBrokerId: Optional[str] = Field(None, description="MQTT broker ID") 165 | audioDetection: Optional[dict] = Field(None, description="Audio detection settings") 166 | 167 | # Switch QoS Rule Schema 168 | class SwitchQosRuleSchema(BaseModel): 169 | vlan: int = Field(..., description="VLAN ID") 170 | protocol: str = Field(..., description="Protocol (tcp, udp, any)") 171 | srcPort: int = Field(..., description="Source port") 172 | srcPortRange: Optional[str] = Field(None, description="Source port range") 173 | dstPort: Optional[int] = Field(None, description="Destination port") 174 | dstPortRange: Optional[str] = Field(None, description="Destination port range") 175 | dscp: Optional[int] = Field(None, description="DSCP value") 176 | 177 | ####################### 178 | # ORGANIZATION TOOLS # 179 | ####################### 180 | 181 | # Get organizations 182 | @mcp.tool() 183 | async def get_organizations() -> str: 184 | """Get a list of organizations the user has access to""" 185 | organizations = await async_get_organizations() 186 | return json.dumps(organizations, indent=2) 187 | 188 | # Get organization details 189 | @mcp.tool() 190 | async def get_organization_details(org_id: str = None) -> str: 191 | """Get details for a specific organization, defaults to the configured organization""" 192 | organization_id = org_id or MERAKI_ORG_ID 193 | org_details = await async_get_organization(organization_id) 194 | return json.dumps(org_details, indent=2) 195 | 196 | # Get networks from Meraki 197 | @mcp.tool() 198 | async def get_networks(org_id: str = None) -> str: 199 | """Get a list of networks from Meraki""" 200 | organization_id = org_id or MERAKI_ORG_ID 201 | networks = await async_get_organization_networks(organization_id) 202 | return json.dumps(networks, indent=2) 203 | 204 | # Get devices from Meraki 205 | @mcp.tool() 206 | async def get_devices(org_id: str = None) -> str: 207 | """Get a list of devices from Meraki""" 208 | organization_id = org_id or MERAKI_ORG_ID 209 | devices = await async_get_organization_devices(organization_id) 210 | return json.dumps(devices, indent=2) 211 | 212 | # Create network in Meraki 213 | @mcp.tool() 214 | def create_network(name: str, tags: list[str], productTypes: list[str], org_id: str = None, copyFromNetworkId: str = None) -> str: 215 | """Create a new network in Meraki, optionally copying from another network.""" 216 | organization_id = org_id or MERAKI_ORG_ID 217 | kwargs = {} 218 | if copyFromNetworkId: 219 | kwargs['copyFromNetworkId'] = copyFromNetworkId 220 | network = dashboard.organizations.createOrganizationNetwork(organization_id, name, productTypes, tags=tags, **kwargs) 221 | return json.dumps(network, indent=2) 222 | 223 | # Delete network in Meraki 224 | @mcp.tool() 225 | def delete_network(network_id: str) -> str: 226 | """Delete a network in Meraki""" 227 | dashboard.networks.deleteNetwork(network_id) 228 | return f"Network {network_id} deleted" 229 | 230 | # Get organization status 231 | @mcp.tool() 232 | def get_organization_status(org_id: str = None) -> str: 233 | """Get the status and health of an organization""" 234 | organization_id = org_id or MERAKI_ORG_ID 235 | status = dashboard.organizations.getOrganizationStatus(organization_id) 236 | return json.dumps(status, indent=2) 237 | 238 | # Get organization inventory 239 | @mcp.tool() 240 | def get_organization_inventory(org_id: str = None) -> str: 241 | """Get the inventory for an organization""" 242 | organization_id = org_id or MERAKI_ORG_ID 243 | inventory = dashboard.organizations.getOrganizationInventoryDevices(organization_id) 244 | return json.dumps(inventory, indent=2) 245 | 246 | # Get organization license state 247 | @mcp.tool() 248 | def get_organization_license(org_id: str = None) -> str: 249 | """Get the license state for an organization""" 250 | organization_id = org_id or MERAKI_ORG_ID 251 | license_state = dashboard.organizations.getOrganizationLicensesOverview(organization_id) 252 | return json.dumps(license_state, indent=2) 253 | 254 | # Get organization configuration changes 255 | @mcp.tool() 256 | def get_organization_conf_change(org_id: str = None) -> str: 257 | """Get the org change state for an organization""" 258 | organization_id = org_id or MERAKI_ORG_ID 259 | org_config_changes = dashboard.organizations.getOrganizationConfigurationChanges(organization_id) 260 | return json.dumps(org_config_changes, indent=2) 261 | 262 | ####################### 263 | # NETWORK TOOLS # 264 | ####################### 265 | 266 | # Get network details 267 | @mcp.tool() 268 | def get_network_details(network_id: str) -> str: 269 | """Get details for a specific network""" 270 | network = dashboard.networks.getNetwork(network_id) 271 | return json.dumps(network, indent=2) 272 | 273 | # Get network devices 274 | @mcp.tool() 275 | def get_network_devices(network_id: str) -> str: 276 | """Get a list of devices in a specific network""" 277 | devices = dashboard.networks.getNetworkDevices(network_id) 278 | return json.dumps(devices, indent=2) 279 | 280 | # Update network 281 | @mcp.tool() 282 | def update_network(network_id: str, update_data: NetworkUpdateSchema) -> str: 283 | """ 284 | Update a network's properties using a schema-validated model 285 | 286 | Args: 287 | network_id: The ID of the network to update 288 | update_data: Network properties to update (name, timeZone, tags, enrollmentString, notes) 289 | """ 290 | # Convert the Pydantic model to a dictionary and filter out None values 291 | update_dict = {k: v for k, v in update_data.dict().items() if v is not None} 292 | 293 | result = dashboard.networks.updateNetwork(network_id, **update_dict) 294 | return json.dumps(result, indent=2) 295 | 296 | # Get clients from Meraki 297 | @mcp.tool() 298 | def get_clients(network_id: str, timespan: int = 86400) -> str: 299 | """ 300 | Get a list of clients from a specific Meraki network. 301 | 302 | Args: 303 | network_id (str): The ID of the Meraki network. 304 | timespan (int): The timespan in seconds to get clients (default: 24 hours) 305 | 306 | Returns: 307 | str: JSON-formatted list of clients. 308 | """ 309 | clients = dashboard.networks.getNetworkClients(network_id, timespan=timespan) 310 | return json.dumps(clients, indent=2) 311 | 312 | # Get client details 313 | @mcp.tool() 314 | def get_client_details(network_id: str, client_id: str) -> str: 315 | """Get details for a specific client in a network""" 316 | client = dashboard.networks.getNetworkClient(network_id, client_id) 317 | return json.dumps(client, indent=2) 318 | 319 | # Get client usage history 320 | @mcp.tool() 321 | def get_client_usage(network_id: str, client_id: str) -> str: 322 | """Get the usage history for a client""" 323 | usage = dashboard.networks.getNetworkClientUsageHistory(network_id, client_id) 324 | return json.dumps(usage, indent=2) 325 | 326 | # Get client policy from Meraki 327 | @mcp.tool() 328 | async def get_client_policy(network_id: str, client_id: str) -> str: 329 | """ 330 | Get the policy for a specific client in a specific Meraki network. 331 | 332 | Args: 333 | network_id (str): The ID of the Meraki network. 334 | client_id (str): The ID (MAC address or client ID) of the client. 335 | 336 | Returns: 337 | str: JSON-formatted client policy. 338 | """ 339 | loop = asyncio.get_event_loop() 340 | policy = await loop.run_in_executor( 341 | None, 342 | lambda: dashboard.networks.getNetworkClientPolicy(network_id, client_id) 343 | ) 344 | return json.dumps(policy, indent=2) 345 | 346 | # Update client policy 347 | @mcp.tool() 348 | def update_client_policy(network_id: str, client_id: str, device_policy: str, group_policy_id: str = None) -> str: 349 | """Update policy for a client""" 350 | kwargs = {'devicePolicy': device_policy} 351 | if group_policy_id: 352 | kwargs['groupPolicyId'] = group_policy_id 353 | 354 | result = dashboard.networks.updateNetworkClientPolicy(network_id, client_id, **kwargs) 355 | return json.dumps(result, indent=2) 356 | 357 | # Get network traffic analysis 358 | @mcp.tool() 359 | def get_network_traffic(network_id: str, timespan: int = 86400) -> str: 360 | """Get traffic analysis data for a network""" 361 | traffic = dashboard.networks.getNetworkTraffic(network_id, timespan=timespan) 362 | return json.dumps(traffic, indent=2) 363 | 364 | ####################### 365 | # DEVICE TOOLS # 366 | ####################### 367 | 368 | # Get device details 369 | @mcp.tool() 370 | async def get_device_details(serial: str) -> str: 371 | """Get details for a specific device by serial number""" 372 | device = await async_get_device(serial) 373 | return json.dumps(device, indent=2) 374 | 375 | # Update device 376 | @mcp.tool() 377 | async def update_device(serial: str, device_settings: DeviceUpdateSchema) -> str: 378 | """ 379 | Update a device in the Meraki organization using a schema-validated model 380 | 381 | Args: 382 | serial: The serial number of the device to update 383 | device_settings: Device properties to update (name, tags, lat, lng, address, notes, etc.) 384 | 385 | Returns: 386 | Confirmation of the update with the new settings 387 | """ 388 | # Convert the Pydantic model to a dictionary and filter out None values 389 | update_dict = {k: v for k, v in device_settings.dict().items() if v is not None} 390 | 391 | await async_update_device(serial, **update_dict) 392 | 393 | # Get the updated device details to return 394 | updated_device = await async_get_device(serial) 395 | 396 | return json.dumps({ 397 | "status": "success", 398 | "message": f"Device {serial} updated", 399 | "updated_settings": update_dict, 400 | "current_device": updated_device 401 | }, indent=2) 402 | 403 | # Claim devices into the Meraki organization 404 | @mcp.tool() 405 | def claim_devices(network_id: str, serials: list[str]) -> str: 406 | """Claim one or more devices into a Meraki network""" 407 | dashboard.networks.claimNetworkDevices(network_id, serials) 408 | return f"Devices {serials} claimed into network {network_id}" 409 | 410 | # Remove device from network 411 | @mcp.tool() 412 | def remove_device(serial: str) -> str: 413 | """Remove a device from its network""" 414 | dashboard.networks.removeNetworkDevices(serial) 415 | return f"Device {serial} removed from network" 416 | 417 | # Reboot device 418 | @mcp.tool() 419 | def reboot_device(serial: str) -> str: 420 | """Reboot a device""" 421 | result = dashboard.devices.rebootDevice(serial) 422 | return json.dumps(result, indent=2) 423 | 424 | # Get device clients 425 | @mcp.tool() 426 | def get_device_clients(serial: str, timespan: int = 86400) -> str: 427 | """Get clients connected to a specific device""" 428 | clients = dashboard.devices.getDeviceClients(serial, timespan=timespan) 429 | return json.dumps(clients, indent=2) 430 | 431 | # Get device status 432 | @mcp.tool() 433 | def get_device_status(serial: str) -> str: 434 | """Get the current status of a device""" 435 | status = dashboard.devices.getDeviceStatuses(serial) 436 | return json.dumps(status, indent=2) 437 | 438 | # Get device uplink status 439 | @mcp.tool() 440 | def get_device_uplink(serial: str) -> str: 441 | """Get the uplink status of a device""" 442 | uplink = dashboard.devices.getDeviceUplink(serial) 443 | return json.dumps(uplink, indent=2) 444 | 445 | ####################### 446 | # WIRELESS TOOLS # 447 | ####################### 448 | 449 | # Get wireless SSIDs 450 | @mcp.tool() 451 | async def get_wireless_ssids(network_id: str) -> str: 452 | """Get wireless SSIDs for a network""" 453 | ssids = await async_get_wireless_ssids(network_id) 454 | return json.dumps(ssids, indent=2) 455 | 456 | # Update wireless SSID 457 | @mcp.tool() 458 | async def update_wireless_ssid(network_id: str, ssid_number: str, ssid_settings: SsidUpdateSchema) -> str: 459 | """ 460 | Update a wireless SSID with comprehensive schema validation 461 | 462 | Args: 463 | network_id: The ID of the network containing the SSID 464 | ssid_number: The number of the SSID to update 465 | ssid_settings: Comprehensive SSID settings following the Meraki schema 466 | 467 | Returns: 468 | The updated SSID configuration 469 | """ 470 | # Convert the Pydantic model to a dictionary and filter out None values 471 | update_dict = {k: v for k, v in ssid_settings.dict().items() if v is not None} 472 | 473 | result = await async_update_wireless_ssid(network_id, ssid_number, **update_dict) 474 | return json.dumps(result, indent=2) 475 | 476 | # Get wireless settings 477 | @mcp.tool() 478 | def get_wireless_settings(network_id: str) -> str: 479 | """Get wireless settings for a network""" 480 | settings = dashboard.wireless.getNetworkWirelessSettings(network_id) 481 | return json.dumps(settings, indent=2) 482 | 483 | 484 | 485 | ####################### 486 | # SWITCH TOOLS # 487 | ####################### 488 | 489 | # Get switch ports 490 | @mcp.tool() 491 | def get_switch_ports(serial: str) -> str: 492 | """Get ports for a switch""" 493 | ports = dashboard.switch.getDeviceSwitchPorts(serial) 494 | return json.dumps(ports, indent=2) 495 | 496 | # Update switch port 497 | @mcp.tool() 498 | def update_switch_port(serial: str, port_id: str, name: str = None, tags: list[str] = None, enabled: bool = None, vlan: int = None) -> str: 499 | """Update a switch port""" 500 | kwargs = {} 501 | if name: 502 | kwargs['name'] = name 503 | if tags: 504 | kwargs['tags'] = tags 505 | if enabled is not None: 506 | kwargs['enabled'] = enabled 507 | if vlan: 508 | kwargs['vlan'] = vlan 509 | 510 | result = dashboard.switch.updateDeviceSwitchPort(serial, port_id, **kwargs) 511 | return json.dumps(result, indent=2) 512 | 513 | # Get switch VLAN settings 514 | @mcp.tool() 515 | def get_switch_vlans(network_id: str) -> str: 516 | """Get VLANs for a network""" 517 | vlans = dashboard.switch.getNetworkSwitchVlans(network_id) 518 | return json.dumps(vlans, indent=2) 519 | 520 | # Create switch VLAN 521 | @mcp.tool() 522 | def create_switch_vlan(network_id: str, vlan_id: int, name: str, subnet: str = None, appliance_ip: str = None) -> str: 523 | """Create a switch VLAN""" 524 | kwargs = {} 525 | if subnet: 526 | kwargs['subnet'] = subnet 527 | if appliance_ip: 528 | kwargs['applianceIp'] = appliance_ip 529 | 530 | result = dashboard.switch.createNetworkSwitchVlan(network_id, vlan_id, name, **kwargs) 531 | return json.dumps(result, indent=2) 532 | 533 | ####################### 534 | # APPLIANCE TOOLS # 535 | ####################### 536 | 537 | # Get security center 538 | @mcp.tool() 539 | def get_security_center(network_id: str) -> str: 540 | """Get security information for a network""" 541 | security = dashboard.appliance.getNetworkApplianceSecurityCenter(network_id) 542 | return json.dumps(security, indent=2) 543 | 544 | # Get VPN status 545 | @mcp.tool() 546 | def get_vpn_status(network_id: str) -> str: 547 | """Get VPN status for a network""" 548 | vpn_status = dashboard.appliance.getNetworkApplianceVpnSiteToSiteVpn(network_id) 549 | return json.dumps(vpn_status, indent=2) 550 | 551 | # Get firewall rules 552 | @mcp.tool() 553 | def get_firewall_rules(network_id: str) -> str: 554 | """Get firewall rules for a network""" 555 | rules = dashboard.appliance.getNetworkApplianceFirewallL3FirewallRules(network_id) 556 | return json.dumps(rules, indent=2) 557 | 558 | # Update firewall rules 559 | @mcp.tool() 560 | def update_firewall_rules(network_id: str, rules: List[FirewallRule]) -> str: 561 | """ 562 | Update firewall rules for a network using schema-validated models 563 | 564 | Args: 565 | network_id: The ID of the network 566 | rules: List of firewall rules following the Meraki schema 567 | 568 | Returns: 569 | The updated firewall rules configuration 570 | """ 571 | # Convert the list of Pydantic models to a list of dictionaries 572 | rules_dict = [rule.dict(exclude_none=True) for rule in rules] 573 | 574 | result = dashboard.appliance.updateNetworkApplianceFirewallL3FirewallRules(network_id, rules=rules_dict) 575 | return json.dumps(result, indent=2) 576 | 577 | ####################### 578 | # CAMERA TOOLS # 579 | ####################### 580 | 581 | # Get camera video settings 582 | @mcp.tool() 583 | def get_camera_video_settings(network_id: str, serial: str) -> str: 584 | """Get video settings for a camera""" 585 | settings = dashboard.camera.getDeviceCameraVideoSettings(serial) 586 | return json.dumps(settings, indent=2) 587 | 588 | # Get camera quality and retention settings 589 | @mcp.tool() 590 | def get_camera_quality_settings(network_id: str) -> str: 591 | """Get quality and retention settings for cameras in a network""" 592 | settings = dashboard.camera.getNetworkCameraQualityRetentionProfiles(network_id) 593 | return json.dumps(settings, indent=2) 594 | 595 | ####################### 596 | # ADVANCED ORGANIZATION TOOLS 597 | ####################### 598 | 599 | # Get organization admins 600 | @mcp.tool() 601 | def get_organization_admins(org_id: str = None) -> str: 602 | """Get a list of organization admins""" 603 | organization_id = org_id or MERAKI_ORG_ID 604 | admins = dashboard.organizations.getOrganizationAdmins(organization_id) 605 | return json.dumps(admins, indent=2) 606 | 607 | # Create organization admin 608 | @mcp.tool() 609 | def create_organization_admin(org_id: str, email: str, name: str, org_access: str, tags: list[str] = None, networks: list[dict] = None) -> str: 610 | """Create a new organization admin""" 611 | organization_id = org_id or MERAKI_ORG_ID 612 | kwargs = { 613 | 'email': email, 614 | 'name': name, 615 | 'orgAccess': org_access 616 | } 617 | if tags: 618 | kwargs['tags'] = tags 619 | if networks: 620 | kwargs['networks'] = networks 621 | 622 | result = dashboard.organizations.createOrganizationAdmin(organization_id, **kwargs) 623 | return json.dumps(result, indent=2) 624 | 625 | # Get organization API requests 626 | @mcp.tool() 627 | def get_organization_api_requests(org_id: str = None, timespan: int = 86400) -> str: 628 | """Get organization API request history""" 629 | organization_id = org_id or MERAKI_ORG_ID 630 | requests = dashboard.organizations.getOrganizationApiRequests(organization_id, timespan=timespan) 631 | return json.dumps(requests, indent=2) 632 | 633 | # Get organization webhook logs 634 | @mcp.tool() 635 | def get_organization_webhook_logs(org_id: str = None, timespan: int = 86400) -> str: 636 | """Get organization webhook logs""" 637 | organization_id = org_id or MERAKI_ORG_ID 638 | logs = dashboard.organizations.getOrganizationWebhooksLogs(organization_id, timespan=timespan) 639 | return json.dumps(logs, indent=2) 640 | 641 | ####################### 642 | # ENHANCED NETWORK MONITORING 643 | ####################### 644 | 645 | # Get network events 646 | @mcp.tool() 647 | def get_network_events(network_id: str, timespan: int = 86400, per_page: int = 100) -> str: 648 | """Get network events history""" 649 | events = dashboard.networks.getNetworkEvents(network_id, timespan=timespan, perPage=per_page) 650 | return json.dumps(events, indent=2) 651 | 652 | # Get network event types 653 | @mcp.tool() 654 | def get_network_event_types(network_id: str) -> str: 655 | """Get available network event types""" 656 | event_types = dashboard.networks.getNetworkEventsEventTypes(network_id) 657 | return json.dumps(event_types, indent=2) 658 | 659 | # Get network alerts history 660 | @mcp.tool() 661 | def get_network_alerts_history(network_id: str, timespan: int = 86400) -> str: 662 | """Get network alerts history""" 663 | alerts = dashboard.networks.getNetworkAlertsHistory(network_id, timespan=timespan) 664 | return json.dumps(alerts, indent=2) 665 | 666 | # Get network alerts settings 667 | @mcp.tool() 668 | def get_network_alerts_settings(network_id: str) -> str: 669 | """Get network alerts settings""" 670 | settings = dashboard.networks.getNetworkAlertsSettings(network_id) 671 | return json.dumps(settings, indent=2) 672 | 673 | # Update network alerts settings 674 | @mcp.tool() 675 | def update_network_alerts_settings(network_id: str, defaultDestinations: dict = None, alerts: list[dict] = None) -> str: 676 | """Update network alerts settings""" 677 | kwargs = {} 678 | if defaultDestinations: 679 | kwargs['defaultDestinations'] = defaultDestinations 680 | if alerts: 681 | kwargs['alerts'] = alerts 682 | 683 | result = dashboard.networks.updateNetworkAlertsSettings(network_id, **kwargs) 684 | return json.dumps(result, indent=2) 685 | 686 | ####################### 687 | # LIVE DEVICE TOOLS 688 | ####################### 689 | 690 | # Ping device 691 | @mcp.tool() 692 | def ping_device(serial: str, target_ip: str, count: int = 5) -> str: 693 | """Ping a device from another device""" 694 | result = dashboard.devices.createDeviceLiveToolsPing(serial, target_ip, count=count) 695 | return json.dumps(result, indent=2) 696 | 697 | # Get ping results 698 | @mcp.tool() 699 | def get_device_ping_results(serial: str, ping_id: str) -> str: 700 | """Get results from a device ping test""" 701 | result = dashboard.devices.getDeviceLiveToolsPing(serial, ping_id) 702 | return json.dumps(result, indent=2) 703 | 704 | # Cable test device 705 | @mcp.tool() 706 | def cable_test_device(serial: str, ports: list[str]) -> str: 707 | """Run cable test on device ports""" 708 | result = dashboard.devices.createDeviceLiveToolsCableTest(serial, ports) 709 | return json.dumps(result, indent=2) 710 | 711 | # Get cable test results 712 | @mcp.tool() 713 | def get_device_cable_test_results(serial: str, cable_test_id: str) -> str: 714 | """Get results from a device cable test""" 715 | result = dashboard.devices.getDeviceLiveToolsCableTest(serial, cable_test_id) 716 | return json.dumps(result, indent=2) 717 | 718 | # Blink device LEDs 719 | @mcp.tool() 720 | def blink_device_leds(serial: str, duration: int = 5) -> str: 721 | """Blink device LEDs for identification""" 722 | result = dashboard.devices.blinkDeviceLeds(serial, duration=duration) 723 | return json.dumps(result, indent=2) 724 | 725 | # Wake on LAN 726 | @mcp.tool() 727 | def wake_on_lan_device(serial: str, mac: str) -> str: 728 | """Send wake-on-LAN packet to a device""" 729 | result = dashboard.devices.createDeviceLiveToolsWakeOnLan(serial, mac) 730 | return json.dumps(result, indent=2) 731 | 732 | ####################### 733 | # ADVANCED WIRELESS TOOLS 734 | ####################### 735 | 736 | # Get wireless RF profiles 737 | @mcp.tool() 738 | def get_wireless_rf_profiles(network_id: str) -> str: 739 | """Get wireless RF profiles for a network""" 740 | profiles = dashboard.wireless.getNetworkWirelessRfProfiles(network_id) 741 | return json.dumps(profiles, indent=2) 742 | 743 | # Create wireless RF profile 744 | @mcp.tool() 745 | def create_wireless_rf_profile(network_id: str, name: str, band_selection_type: str, **kwargs) -> str: 746 | """Create a wireless RF profile""" 747 | result = dashboard.wireless.createNetworkWirelessRfProfile(network_id, name, bandSelectionType=band_selection_type, **kwargs) 748 | return json.dumps(result, indent=2) 749 | 750 | # Get wireless channel utilization 751 | @mcp.tool() 752 | def get_wireless_channel_utilization(network_id: str, timespan: int = 86400) -> str: 753 | """Get wireless channel utilization history""" 754 | utilization = dashboard.wireless.getNetworkWirelessChannelUtilizationHistory(network_id, timespan=timespan) 755 | return json.dumps(utilization, indent=2) 756 | 757 | # Get wireless signal quality 758 | @mcp.tool() 759 | def get_wireless_signal_quality(network_id: str, timespan: int = 86400) -> str: 760 | """Get wireless signal quality history""" 761 | quality = dashboard.wireless.getNetworkWirelessSignalQualityHistory(network_id, timespan=timespan) 762 | return json.dumps(quality, indent=2) 763 | 764 | # Get wireless connection stats 765 | @mcp.tool() 766 | def get_wireless_connection_stats(network_id: str, timespan: int = 86400) -> str: 767 | """Get wireless connection statistics""" 768 | stats = dashboard.wireless.getNetworkWirelessConnectionStats(network_id, timespan=timespan) 769 | return json.dumps(stats, indent=2) 770 | 771 | # Get wireless client connectivity events 772 | @mcp.tool() 773 | def get_wireless_client_connectivity_events(network_id: str, client_id: str, timespan: int = 86400) -> str: 774 | """Get wireless client connectivity events""" 775 | events = dashboard.wireless.getNetworkWirelessClientConnectivityEvents(network_id, client_id, timespan=timespan) 776 | return json.dumps(events, indent=2) 777 | 778 | ####################### 779 | # ADVANCED SWITCH TOOLS 780 | ####################### 781 | 782 | # Get switch port statuses 783 | @mcp.tool() 784 | def get_switch_port_statuses(serial: str) -> str: 785 | """Get switch port statuses""" 786 | statuses = dashboard.switch.getDeviceSwitchPortsStatuses(serial) 787 | return json.dumps(statuses, indent=2) 788 | 789 | # Cycle switch ports 790 | @mcp.tool() 791 | def cycle_switch_ports(serial: str, ports: list[str]) -> str: 792 | """Cycle (restart) switch ports""" 793 | result = dashboard.switch.cycleDeviceSwitchPorts(serial, ports) 794 | return json.dumps(result, indent=2) 795 | 796 | # Get switch access control lists 797 | @mcp.tool() 798 | def get_switch_access_control_lists(network_id: str) -> str: 799 | """Get switch access control lists""" 800 | acls = dashboard.switch.getNetworkSwitchAccessControlLists(network_id) 801 | return json.dumps(acls, indent=2) 802 | 803 | # Update switch access control lists 804 | @mcp.tool() 805 | def update_switch_access_control_lists(network_id: str, rules: list[dict]) -> str: 806 | """Update switch access control lists""" 807 | result = dashboard.switch.updateNetworkSwitchAccessControlLists(network_id, rules) 808 | return json.dumps(result, indent=2) 809 | 810 | # Get switch QoS rules 811 | @mcp.tool() 812 | def get_switch_qos_rules(network_id: str) -> str: 813 | """Get switch QoS rules""" 814 | rules = dashboard.switch.getNetworkSwitchQosRules(network_id) 815 | return json.dumps(rules, indent=2) 816 | 817 | # Create switch QoS rule 818 | @mcp.tool() 819 | def create_switch_qos_rule(network_id: str, vlan: int, protocol: str, src_port: int, src_port_range: str = None, dst_port: int = None, dst_port_range: str = None, dscp: int = None) -> str: 820 | """Create a switch QoS rule""" 821 | kwargs = { 822 | 'vlan': vlan, 823 | 'protocol': protocol, 824 | 'srcPort': src_port 825 | } 826 | if src_port_range: 827 | kwargs['srcPortRange'] = src_port_range 828 | if dst_port: 829 | kwargs['dstPort'] = dst_port 830 | if dst_port_range: 831 | kwargs['dstPortRange'] = dst_port_range 832 | if dscp: 833 | kwargs['dscp'] = dscp 834 | 835 | result = dashboard.switch.createNetworkSwitchQosRule(network_id, **kwargs) 836 | return json.dumps(result, indent=2) 837 | 838 | ####################### 839 | # ADVANCED APPLIANCE TOOLS 840 | ####################### 841 | 842 | # Get appliance VPN site-to-site status 843 | @mcp.tool() 844 | def get_appliance_vpn_site_to_site(network_id: str) -> str: 845 | """Get appliance VPN site-to-site configuration""" 846 | vpn = dashboard.appliance.getNetworkApplianceVpnSiteToSiteVpn(network_id) 847 | return json.dumps(vpn, indent=2) 848 | 849 | # Update appliance VPN site-to-site 850 | @mcp.tool() 851 | def update_appliance_vpn_site_to_site(network_id: str, mode: str, hubs: list[dict] = None, subnets: list[dict] = None) -> str: 852 | """Update appliance VPN site-to-site configuration""" 853 | kwargs = {'mode': mode} 854 | if hubs: 855 | kwargs['hubs'] = hubs 856 | if subnets: 857 | kwargs['subnets'] = subnets 858 | 859 | result = dashboard.appliance.updateNetworkApplianceVpnSiteToSiteVpn(network_id, **kwargs) 860 | return json.dumps(result, indent=2) 861 | 862 | # Get appliance content filtering 863 | @mcp.tool() 864 | def get_appliance_content_filtering(network_id: str) -> str: 865 | """Get appliance content filtering settings""" 866 | filtering = dashboard.appliance.getNetworkApplianceContentFiltering(network_id) 867 | return json.dumps(filtering, indent=2) 868 | 869 | # Update appliance content filtering 870 | @mcp.tool() 871 | def update_appliance_content_filtering(network_id: str, allowed_urls: list[str] = None, blocked_urls: list[str] = None, blocked_url_patterns: list[str] = None, youtube_restricted_for_teenagers: bool = None, youtube_restricted_for_mature: bool = None) -> str: 872 | """Update appliance content filtering settings""" 873 | kwargs = {} 874 | if allowed_urls: 875 | kwargs['allowedUrls'] = allowed_urls 876 | if blocked_urls: 877 | kwargs['blockedUrls'] = blocked_urls 878 | if blocked_url_patterns: 879 | kwargs['blockedUrlPatterns'] = blocked_url_patterns 880 | if youtube_restricted_for_teenagers is not None: 881 | kwargs['youtubeRestrictedForTeenagers'] = youtube_restricted_for_teenagers 882 | if youtube_restricted_for_mature is not None: 883 | kwargs['youtubeRestrictedForMature'] = youtube_restricted_for_mature 884 | 885 | result = dashboard.appliance.updateNetworkApplianceContentFiltering(network_id, **kwargs) 886 | return json.dumps(result, indent=2) 887 | 888 | # Get appliance security events 889 | @mcp.tool() 890 | def get_appliance_security_events(network_id: str, timespan: int = 86400) -> str: 891 | """Get appliance security events""" 892 | events = dashboard.appliance.getNetworkApplianceSecurityEvents(network_id, timespan=timespan) 893 | return json.dumps(events, indent=2) 894 | 895 | # Get appliance traffic shaping 896 | @mcp.tool() 897 | def get_appliance_traffic_shaping(network_id: str) -> str: 898 | """Get appliance traffic shaping settings""" 899 | shaping = dashboard.appliance.getNetworkApplianceTrafficShaping(network_id) 900 | return json.dumps(shaping, indent=2) 901 | 902 | # Update appliance traffic shaping 903 | @mcp.tool() 904 | def update_appliance_traffic_shaping(network_id: str, global_bandwidth_limits: dict = None) -> str: 905 | """Update appliance traffic shaping settings""" 906 | kwargs = {} 907 | if global_bandwidth_limits: 908 | kwargs['globalBandwidthLimits'] = global_bandwidth_limits 909 | 910 | result = dashboard.appliance.updateNetworkApplianceTrafficShaping(network_id, **kwargs) 911 | return json.dumps(result, indent=2) 912 | 913 | ####################### 914 | # CAMERA TOOLS 915 | ####################### 916 | 917 | # Get camera analytics live 918 | @mcp.tool() 919 | def get_camera_analytics_live(serial: str) -> str: 920 | """Get live camera analytics""" 921 | analytics = dashboard.camera.getDeviceCameraAnalyticsLive(serial) 922 | return json.dumps(analytics, indent=2) 923 | 924 | # Get camera analytics overview 925 | @mcp.tool() 926 | def get_camera_analytics_overview(serial: str, timespan: int = 86400) -> str: 927 | """Get camera analytics overview""" 928 | overview = dashboard.camera.getDeviceCameraAnalyticsOverview(serial, timespan=timespan) 929 | return json.dumps(overview, indent=2) 930 | 931 | # Get camera analytics zones 932 | @mcp.tool() 933 | def get_camera_analytics_zones(serial: str) -> str: 934 | """Get camera analytics zones""" 935 | zones = dashboard.camera.getDeviceCameraAnalyticsZones(serial) 936 | return json.dumps(zones, indent=2) 937 | 938 | # Generate camera snapshot 939 | @mcp.tool() 940 | def generate_camera_snapshot(serial: str, timestamp: str = None) -> str: 941 | """Generate a camera snapshot""" 942 | kwargs = {} 943 | if timestamp: 944 | kwargs['timestamp'] = timestamp 945 | 946 | result = dashboard.camera.generateDeviceCameraSnapshot(serial, **kwargs) 947 | return json.dumps(result, indent=2) 948 | 949 | # Get camera sense 950 | @mcp.tool() 951 | def get_camera_sense(serial: str) -> str: 952 | """Get camera sense configuration""" 953 | sense = dashboard.camera.getDeviceCameraSense(serial) 954 | return json.dumps(sense, indent=2) 955 | 956 | # Update camera sense 957 | @mcp.tool() 958 | def update_camera_sense(serial: str, sense_enabled: bool = None, mqtt_broker_id: str = None, audio_detection: dict = None) -> str: 959 | """Update camera sense configuration""" 960 | kwargs = {} 961 | if sense_enabled is not None: 962 | kwargs['senseEnabled'] = sense_enabled 963 | if mqtt_broker_id: 964 | kwargs['mqttBrokerId'] = mqtt_broker_id 965 | if audio_detection: 966 | kwargs['audioDetection'] = audio_detection 967 | 968 | result = dashboard.camera.updateDeviceCameraSense(serial, **kwargs) 969 | return json.dumps(result, indent=2) 970 | 971 | ####################### 972 | # NETWORK AUTOMATION TOOLS 973 | ####################### 974 | 975 | # Create action batch 976 | @mcp.tool() 977 | def create_action_batch(org_id: str, actions: list[dict], confirmed: bool = True, synchronous: bool = False) -> str: 978 | """Create an action batch for bulk operations""" 979 | organization_id = org_id or MERAKI_ORG_ID 980 | result = dashboard.organizations.createOrganizationActionBatch(organization_id, actions, confirmed=confirmed, synchronous=synchronous) 981 | return json.dumps(result, indent=2) 982 | 983 | # Get action batch status 984 | @mcp.tool() 985 | def get_action_batch_status(org_id: str, batch_id: str) -> str: 986 | """Get action batch status""" 987 | organization_id = org_id or MERAKI_ORG_ID 988 | status = dashboard.organizations.getOrganizationActionBatch(organization_id, batch_id) 989 | return json.dumps(status, indent=2) 990 | 991 | # Get action batches 992 | @mcp.tool() 993 | def get_action_batches(org_id: str = None) -> str: 994 | """Get all action batches for an organization""" 995 | organization_id = org_id or MERAKI_ORG_ID 996 | batches = dashboard.organizations.getOrganizationActionBatches(organization_id) 997 | return json.dumps(batches, indent=2) 998 | 999 | # Define resources 1000 | #Add a dynamic greeting resource 1001 | @mcp.resource("greeting: //{name}") 1002 | def greeting(name: str) -> str: 1003 | """Greet a user by name""" 1004 | return f"Hello {name}!" 1005 | 1006 | #execute and return the stdio output 1007 | if __name__ == "__main__": 1008 | mcp.run() 1009 | ```