Skip to main content
Glama
dbono711

Cisco NSO MCP Server

by dbono711
server.py18.2 kB
#!/usr/bin/env python3 """ Cisco NSO MCP Server This module implements a Model Context Protocol (MCP) server that provides network automation tools for interacting with Cisco NSO via RESTCONF. """ import argparse import os from typing import Any, Dict, Optional from cisco_nso_mcp_server.services.devices import ( get_device_config, get_device_ned_ids, get_device_platform, get_device_state, check_device_sync, sync_from_device, get_device_groups ) from cisco_nso_mcp_server.services.services import ( get_service_types, get_services ) from cisco_nso_mcp_server.services.environment import get_environment_summary from cisco_nso_mcp_server.utils import logger from cisco_nso_restconf.client import NSORestconfClient from cisco_nso_restconf.devices import Devices from cisco_nso_restconf.query import Query from fastmcp import FastMCP from starlette.responses import JSONResponse def register_resources(mcp: FastMCP, devices_helper: Devices, query_helper: Query) -> None: """ Register resources with the MCP server. This function registers all available resources with the MCP server, including the NSO environment summary resource that provides information about the network devices managed by NSO. Args: mcp: The FastMCP server instance to register resources with query_helper: The Query helper for interacting with NSO devices_helper: The Devices helper for interacting with NSO devices """ @mcp.resource( uri="https://resources.cisco-nso-mcp.io/environment", description="NSO environment summary", ) async def nso_environment() -> Dict[str, Any]: """ This resource provides a summary of the NSO environment, including the number of devices managed by NSO, the distribution of operating systems, the number of device groups, and the distribution of device models. Returns: A dictionary containing summary information about the NSO environment. """ try: # delegate to the service layer return await get_environment_summary(query_helper, devices_helper) except Exception as e: logger.error(f"Resource error: {str(e)}") return { "status": "error", "error_message": str(e) } def register_tools(mcp: FastMCP, client: NSORestconfClient, devices_helper: Devices) -> None: """ Register tools with the MCP server. This function registers all available tools with the MCP server, including tools for retrieving device platform information, device configuration, Network Element Driver (NED) IDs, and services from Cisco NSO. Args: mcp: The FastMCP server instance to register tools with client: The NSORestconfClient instance for interacting with NSO devices_helper: The Devices helper for interacting with NSO devices """ @mcp.tool( name="get_service_types", description="Retrieve the available service types in Cisco NSO.", tags={"services", "types"}, annotations={ "title": "Get Service Types", "readOnlyHint": True } ) async def get_service_types_tool(params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """ This tool retrieves the available service types in Cisco NSO. The response will include a list of available service types. Args: params (Optional[Dict[str, Any]], optional): Unused parameter. Defaults to None. Returns: A dictionary containing a list of available service types. """ try: # delegate to the service layer return await get_service_types(client) except Exception as e: return { "status": "error", "error_message": str(e) } @mcp.tool( name="get_services", description="Retrieve the available services in Cisco NSO. Requires a 'service_type' parameter.", tags={"services", "service"}, annotations={ "title": "Get Services", "readOnlyHint": True } ) async def get_services_tool(params: Dict[str, Any]) -> Dict[str, Any]: """ This tool retrieves the available services in Cisco NSO. The response will include a list of available services. Args: params (Optional[Dict[str, Any]], optional): Unused parameter. Defaults to None. Returns: A dictionary containing a list of available services. """ try: # validate required parameters if not params or "service_type" not in params: return { "status": "error", "error_message": "Missing required parameter: service_type" } # delegate to the service layer return await get_services(client, params["service_type"]) except Exception as e: return { "status": "error", "error_message": str(e) } @mcp.tool( name="get_device_platform", description="Retrieve platform information for a specific device in Cisco NSO. Requires a 'device_name' parameter.", tags={"devices", "platform"}, annotations={ "title": "Get Device Platform Information", "readOnlyHint": True } ) async def get_device_platform_tool(params: Dict[str, Any]) -> Dict[str, Any]: """ This tool takes a single parameter, 'device_name', which is the name of the device for which to retrieve platform information. The response will include the platform name, platform version, and model information for the specified device. Args: params (Dict[str, Any]): A dictionary containing the 'device_name' parameter. Returns: A dictionary containing the platform information for the specified device. """ try: # validate required parameters if not params or "device_name" not in params: return { "status": "error", "error_message": "Missing required parameter: device_name" } # delegate to the service layer return await get_device_platform(devices_helper, params["device_name"]) except Exception as e: return { "status": "error", "error_message": str(e) } @mcp.tool( name="get_device_config", description="Retrieve the full configuration for a specific device in Cisco NSO. Requires a 'device_name' parameter.", tags={"devices", "config"}, annotations={ "title": "Get Device Configuration", "readOnlyHint": True } ) async def get_device_config_tool(params: Dict[str, Any]) -> Dict[str, Any]: try: # validate required parameters if not params or "device_name" not in params: return { "status": "error", "error_message": "Missing required parameter: device_name" } # delegate to the service layer return await get_device_config(devices_helper, params["device_name"]) except Exception as e: return { "status": "error", "error_message": str(e) } @mcp.tool( name="get_device_state", description="Retrieve the state for a specific device in Cisco NSO. Requires a 'device_name' parameter.", tags={"devices", "state"}, annotations={ "title": "Get Device State", "readOnlyHint": True } ) async def get_device_state_tool(params: Dict[str, Any]) -> Dict[str, Any]: try: # validate required parameters if not params or "device_name" not in params: return { "status": "error", "error_message": "Missing required parameter: device_name" } # delegate to the service layer return await get_device_state(devices_helper, params["device_name"]) except Exception as e: return { "status": "error", "error_message": str(e) } @mcp.tool( name="get_device_groups", description="Retrieve the available device groups in Cisco NSO.", tags={"devices", "groups"}, annotations={ "title": "Get Device Groups", "readOnlyHint": True } ) async def get_device_groups_tool(params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """ This tool retrieves the available device groups in Cisco NSO. The response will include a list of available device groups. Args: params (Optional[Dict[str, Any]], optional): Unused parameter. Defaults to None. Returns: A dictionary containing a list of available device groups. """ try: # delegate to the service layer return await get_device_groups(devices_helper) except Exception as e: return { "status": "error", "error_message": str(e) } @mcp.tool( name="get_device_ned_ids", description="Retrieve the available Network Element Driver (NED) IDs in Cisco NSO.", tags={"devices", "neds"}, annotations={ "title": "Get Device NED ID's", "readOnlyHint": True } ) async def get_device_ned_ids_tool(params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """ This tool retrieves the available Network Element Driver (NED) IDs in Cisco NSO. The response will include a list of available NED IDs. Args: params (Optional[Dict[str, Any]], optional): Unused parameter. Defaults to None. Returns: A dictionary containing a list of available NED IDs. """ try: # delegate to the service layer return await get_device_ned_ids(devices_helper) except Exception as e: return { "status": "error", "error_message": str(e) } @mcp.tool( name="check_device_sync", description="Check the sync status for a specific device in Cisco NSO. Requires a 'device_name' parameter.", tags={"devices", "sync"}, annotations={ "title": "Check Device Sync Status", "readOnlyHint": True } ) async def check_device_sync_tool(params: Dict[str, Any]) -> Dict[str, Any]: try: # validate required parameters if not params or "device_name" not in params: return { "status": "error", "error_message": "Missing required parameter: device_name" } # delegate to the service layer return await check_device_sync(devices_helper, params["device_name"]) except Exception as e: return { "status": "error", "error_message": str(e) } @mcp.tool( name="sync_from_device", description="Sync from a specific device in Cisco NSO. Requires a 'device_name' parameter.", tags={"devices", "sync"}, annotations={ "title": "Sync Device", "readOnlyHint": False } ) async def sync_from_device_tool(params: Dict[str, Any]) -> Dict[str, Any]: try: # validate required parameters if not params or "device_name" not in params: return { "status": "error", "error_message": "Missing required parameter: device_name" } # delegate to the service layer return await sync_from_device(devices_helper, params["device_name"]) except Exception as e: return { "status": "error", "error_message": str(e) } def parse_args() -> argparse.Namespace: """ Parse command line arguments for the Cisco NSO MCP Server. This function parses the command line arguments and returns a Namespace object containing the parsed values. The following options are available: NSO Connection Options: --nso-scheme: NSO connection scheme (default: http) --nso-address: NSO server address (default: localhost) --nso-port: NSO server port (default: 8080) --nso-timeout: NSO connection timeout in seconds (default: 10) --nso-username: NSO username (default: admin) --nso-password: NSO password (default: admin) MCP Server Options: --transport: MCP transport type (default: stdio) Choices: stdio, http HTTP Transport Options (only used when --transport=http): --host: Host to bind to when using HTTP transport (default: 0.0.0.0) --port: Port to bind to when using HTTP transport (default: 8000) Returns: A Namespace object containing the parsed values. """ parser = argparse.ArgumentParser(description="Cisco NSO MCP Server") # NSO connection parameters nso_group = parser.add_argument_group('NSO Connection Options') nso_group.add_argument( "--nso-scheme", default=os.environ.get("NSO_SCHEME", "http"), help="NSO connection scheme (default: http)" ) nso_group.add_argument( "--nso-address", default=os.environ.get("NSO_ADDRESS", "localhost"), help="NSO server address (default: localhost)" ) nso_group.add_argument( "--nso-port", type=int, default=int(os.environ.get("NSO_PORT", "8080")), help="NSO server port (default: 8080)" ) nso_group.add_argument( "--nso-timeout", type=int, default=int(os.environ.get("NSO_TIMEOUT", "10")), help="NSO connection timeout in seconds (default: 10)" ) nso_group.add_argument( "--nso-username", default=os.environ.get("NSO_USERNAME", "admin"), help="NSO username (default: admin)" ) nso_group.add_argument( "--nso-password", default=os.environ.get("NSO_PASSWORD", "admin"), help="NSO password (default: admin)" ) nso_group.add_argument( "--nso-verify", default=os.environ.get("NSO_VERIFY", True), action=argparse.BooleanOptionalAction, help="Verify NSO HTTPS certificate (default: True). Use --no-nso-verify for self-signed certs (dev only).", ) nso_group.add_argument( "--nso-ca-bundle", default=os.environ.get("NSO_CA_BUNDLE"), help="Path to a CA bundle file to trust for NSO HTTPS.", ) # MCP server parameters mcp_group = parser.add_argument_group('MCP Server Options') mcp_group.add_argument( "--transport", default=os.environ.get("MCP_TRANSPORT", "stdio"), choices=["stdio", "http"], help="MCP transport type (default: stdio)" ) # HTTP-specific parameters http_group = parser.add_argument_group('HTTP Transport Options (only used when --transport=http)') http_group.add_argument( "--host", default=os.environ.get("MCP_HOST", "0.0.0.0"), help="Host to bind to when using HTTP transport (default: 0.0.0.0)" ) http_group.add_argument( "--port", type=int, default=int(os.environ.get("MCP_PORT", "8000")), help="Port to bind to when using HTTP transport (default: 8000)" ) args = parser.parse_args() return args def main(): """ Main function to run the server. """ # parse command line arguments args = parse_args() # initialize FastMCP server mcp = FastMCP(name="nso-mcp") # initialize NSO client with configurable parameters client = NSORestconfClient( scheme=args.nso_scheme, address=args.nso_address, port=args.nso_port, timeout=args.nso_timeout, username=args.nso_username, password=args.nso_password, disable_warning=(getattr(args, "nso_scheme", "http") == "https" and not getattr(args, "nso_verify", True)), ) # cisco-nso-restconf uses requests.Session internally. Configure TLS verification # at the session layer so --no-nso-verify actually disables cert checks. if getattr(args, "nso_scheme", "http") == "https": if getattr(args, "nso_ca_bundle", None): client.session.verify = args.nso_ca_bundle else: client.session.verify = getattr(args, "nso_verify", True) logger.info("NSORestconfClient initialized") # initialize NSO client helpers devices_helper = Devices(client) # devices helper query_helper = Query(client) # query helper # implement health check endpoint (http://<ip:<port>/health) @mcp.custom_route("/health", methods=["GET"]) async def health_check(request): return JSONResponse({"status": "healthy", "service": "mcp-server"}) # register resources and tools register_resources(mcp, devices_helper, query_helper) # register resources register_tools(mcp, client, devices_helper) # register tools # run the server with stdio transport if args.transport == "stdio": logger.info("🚀 Starting Model Context Protocol (MCP) NSO Server with stdio transport") mcp.run(transport="stdio") # run the server with HTTP transport elif args.transport == "http": logger.info(f"🚀 Starting Model Context Protocol (MCP) NSO Server with HTTP transport on {args.host}:{args.port}") mcp.run(transport="http", host=args.host, port=args.port) if __name__ == "__main__": main()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dbono711/cisco-nso-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server