An MCP server for interacting with Juniper Junos network devices using LLMs.
A Model Context Protocol (MCP) server for Juniper Junos devices that enables LLM interactions with network equipment.
Warning: This server enables LLM access to your network infrastructure. Please review these security considerations carefully.
Corporate Policy Compliance: Only use this server if your company's policy allows sending data of Junos devices to LLM services.
Server Security: Always secure your Junos MCP server before deployment in production environments.
Authentication: Do not use password authentication for production deployments. We strongly recommend using SSH key-based authentication for enhanced security.
Deployment Strategy: Until your MCP server is properly secured, only deploy locally for testing purposes. Do not deploy remote servers in production without proper security measures.
Warning: The Junos MCP server supports configuration changes, but please ensure you only use this functionality when you want LLM-generated configurations to be loaded and committed on your Junos router.
Always review the configuration being generated by the LLM and only allow tool execution if it's the correct configuration for your use case.
Get the code.
git clone https://github.com/Juniper/junos-mcp-server.git
cd junos-mcp-server
pip install -r requirements.txt
If you're using uv, you can run the server directly:
uv run python jmcp.py -f devices.json -t stdio
$ python3.11 jmcp.py --help
Junos MCP Server
options:
-h, --help show this help message and exit
-f DEVICE_MAPPING, --device-mapping DEVICE_MAPPING
the name of the JSON file containing the device mapping
-H HOST, --host HOST Junos MCP Server host
-t TRANSPORT, --transport TRANSPORT
Junos MCP Server transport
-p PORT, --port PORT Junos MCP Server port
Junos MCP server supports both streamable-http and stdio transport. Do not use --host with stdio transport.
{
"mcpServers": {
"jmcp": {
"type": "stdio",
"command": "python3",
"args": ["jmcp.py", "-f", "devices.json", "-t", "stdio"]
}
}
}
{
"mcpServers": {
"jmcp": {
"type": "stdio",
"command": "uv",
"args": ["run", "python", "jmcp.py", "-f", "devices.json", "-t", "stdio"]
}
}
}
Note: Please provide absolute path for jmcp.py and devices.json file.
{
"mcpServers": {
"jmcp": {
"type": "stdio",
"command": "/usr/local/bin/docker",
"args": [
"run",
"--rm",
"-i",
"-v",
"devices.json:/app/config/devices.json",
"-v",
"vsrx_keypair.pem:/app/config/vsrx_keypair.pem",
"junos-mcp-server:latest"
]
}
}
}
$ docker build -t junos-mcp-server:latest .
By default, the Docker container runs with stdio transport:
$ docker run --rm -it -v /path/to/your/devices.json:/app/config/devices.json junos-mcp-server:latest
This uses the default command: python jmcp.py -f /app/config/devices.json -t stdio
You can override any arguments by specifying the full command:
For stdio transport:
$ docker run --rm -it -v /path/to/your/devices.json:/app/config/devices.json junos-mcp-server:latest python jmcp.py -f /app/config/devices.json -t stdio
For streamable-http transport:
$ docker run --rm -it -v /path/to/your/devices.json:/app/config/devices.json -p 30030:30030 junos-mcp-server:latest python jmcp.py -f /app/config/devices.json -t streamable-http -H 0.0.0.0
For streamable-http with custom port:
$ docker run --rm -it -v /path/to/your/devices.json:/app/config/devices.json -p 8080:8080 junos-mcp-server:latest python jmcp.py -f /app/config/devices.json -t streamable-http -p 8080 -H 0.0.0.0
Note:
-v /path/to/your/devices.json:/app/config/devices.json
-p host_port:container_port
-v /path/to/key.pem:/app/config/key.pem
)Build docker container for Junos MCP Server
$ docker build -t junos-mcp-server:latest .
Note: Mount your config file (devices.json) and mount any other files, in my case I am using pem file for ssh priv key authentication so I am also mounting vsrx_keypair.pem
Junos MCP server supports both password based auth as well as ssh key based auth.
{
"router-1": {
"ip": "ip-addr",
"port": 22,
"username": "user",
"auth": {
"type": "password",
"password": "pwd"
}
},
"router-2": {
"ip": "ip-addr",
"port": 22,
"username": "user",
"auth": {
"type": "ssh_key",
"private_key_path": "/path/to/private/key.pem"
}
},
"router-3": {
"ip": "ip-addr",
"port": 22,
"username": "user",
"auth": {
"type": "password",
"password": "pwd"
}
}
}
Note: Port value should be an integer (typically 22 for SSH).
$ python3.11 jmcp.py -f devices.json
[06/11/25 08:26:11] INFO Starting MCP server 'jmcp-server' with transport 'streamable-http' on http://127.0.0.1:30030/mcp
INFO: Started server process [33512]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:30030 (Press CTRL+C to quit)
{
"mcp": {
"servers": {
"my-junos-mcp-server": {
"url": "http://127.0.0.1:30030/mcp/"
}
}
}
}
Note: You can use VSCode's Cmd+Shift+P
to configure MCP server.
The Junos MCP server supports token-based authentication for secure client access when using streamable-http transport. This prevents unauthorized access to your network infrastructure.
The server includes a dedicated token management CLI tool: jmcp_token_manager.py
# Basic token generation
python jmcp_token_manager.py generate --id "vscode-dev"
# With description
python jmcp_token_manager.py generate --id "vscode-dev" --description "VSCode development environment"
# Example output:
Generated new token:
ID: vscode-dev
Token: jmcp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8
Description: VSCode development environment
Save this token securely - it won't be shown again!
python jmcp_token_manager.py list
# Example output:
ID Description Created
-------------------------------------------------------------------------------------
vscode-dev VSCode development environment 2025-01-28T10:30:00Z
prod-client Production client access 2025-01-28T09:15:00Z
python jmcp_token_manager.py show --id "vscode-dev"
# Example output:
Token ID: vscode-dev
Token: jmcp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8
Description: VSCode development environment
Created: 2025-01-28T10:30:00Z
python jmcp_token_manager.py revoke --id "vscode-dev"
# Example output:
Token 'vscode-dev' has been revoked
The server automatically detects and enables authentication based on the presence of tokens:
With tokens configured:
$ python jmcp.py -f devices.json -t streamable-http
INFO - Token-based authentication enabled
INFO - Clients must send 'Authorization: Bearer <token>' header
INFO - Use jmcp_token_manager.py to manage tokens
INFO - Streamable HTTP server started on http://127.0.0.1:30030
Without tokens configured:
$ python jmcp.py -f devices.json -t streamable-http
WARNING - No .tokens file found - server is open to all clients
INFO - Create tokens using: python jmcp_token_manager.py generate --id <token-id>
INFO - Streamable HTTP server started on http://127.0.0.1:30030
{
"mcp": {
"servers": {
"my-junos-mcp-server": {
"url": "http://127.0.0.1:30030/mcp/",
"headers": {
"Authorization": "Bearer jmcp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8"
}
}
}
}
}
# Test authentication with valid token
curl -X POST "http://127.0.0.1:30030/mcp/" \
-H "Authorization: Bearer jmcp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
# Test without token (should fail with 401)
curl -X POST "http://127.0.0.1:30030/mcp/" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
Note: MCP streamable-http requires the Accept: application/json, text/event-stream
header.
When using Docker, mount the .tokens
file to enable authentication:
# Generate token first (outside container)
python jmcp_token_manager.py generate --id "docker-client"
# Run container with token file mounted
docker run --rm -it \
-v /path/to/devices.json:/app/config/devices.json \
-v /path/to/.tokens:/app/.tokens \
-p 30030:30030 \
junos-mcp-server:latest \
python jmcp.py -f /app/config/devices.json -t streamable-http -H 0.0.0.0
Token Security:
Access Control:
Network Security:
The .tokens
file stores tokens in JSON format:
{
"vscode-dev": {
"token": "jmcp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8",
"description": "VSCode development environment",
"created": "2025-01-28T10:30:00Z"
},
"prod-client": {
"token": "jmcp_x9y8z7w6v5u4t3s2r1q0p9o8n7m6l5k4j3i2",
"description": "Production client access",
"created": "2025-01-28T09:15:00Z"
}
}
Important: Keep this file secure and don't commit it to version control.
JCNR is a cloud native router that runs on various cloud environments. One can use this MCP server with JCNR as well by following the steps given below. Please refer to JCNR documentation for more details on configuration.
set system services netconf ssh
set system services ssh port 3030
set system services ssh root-login allow
set system root-authentication encrypted-password "$6$3vvMI$RNemhmu9izWXzO46msh38frIg4VoeFNJWJZugxgnU.NQso3OQ00QWOIZmzNePD.MWjDODxBBEYut/W7kfADdV." (or)
set system root-authentication load-key-file <public key>
This section explains the architecture of the Junos MCP server and how to extend it with new tools.
The Junos MCP server uses the Model Context Protocol (MCP) to enable LLMs to interact with Juniper network devices. The server architecture consists of:
jmcp.py
): Handles MCP protocol communicationEach tool in the MCP server follows this flow:
LLM Request → MCP Server → Tool Registry → Handler Function → PyEZ → Junos Device
↓
LLM Response ← MCP Server ← Handler Response ← PyEZ Response ←
Adding a new tool is a simple 3-step process:
Create an async handler function in jmcp.py
(before the TOOL_HANDLERS
dictionary):
async def handle_my_new_tool(arguments: dict) -> list[types.ContentBlock]:
"""Handler for my_new_tool - describe what it does"""
# Extract arguments
router_name = arguments.get("router_name", "")
my_param = arguments.get("my_param", "default_value")
# Validate router exists
if router_name not in devices:
result = f"Router {router_name} not found in the device mapping."
else:
# Your tool logic here
log.debug(f"Executing my_new_tool on router {router_name}")
result = _run_junos_cli_command(router_name, f"show {my_param}")
return [types.TextContent(type="text", text=result)]
Add your handler to the TOOL_HANDLERS
dictionary (around line 330):
TOOL_HANDLERS = {
"execute_junos_command": handle_execute_junos_command,
"get_junos_config": handle_get_junos_config,
"junos_config_diff": handle_junos_config_diff,
"gather_device_facts": handle_gather_device_facts,
"get_router_list": handle_get_router_list,
"load_and_commit_config": handle_load_and_commit_config,
"my_new_tool": handle_my_new_tool, # Add your tool here
}
Add the tool definition to the list_tools()
method (around line 410):
types.Tool(
name="my_new_tool",
description="Brief description of what your tool does",
inputSchema={
"type": "object",
"properties": {
"router_name": {"type": "string", "description": "The name of the router"},
"my_param": {"type": "string", "description": "Description of parameter"}
},
"required": ["router_name"] # List required parameters
}
)
Here's a complete example of adding a tool to show BGP neighbors:
# Step 1: Handler function
async def handle_show_bgp_neighbors(arguments: dict) -> list[types.ContentBlock]:
"""Handler for show_bgp_neighbors tool"""
router_name = arguments.get("router_name", "")
neighbor_address = arguments.get("neighbor_address", "")
if router_name not in devices:
result = f"Router {router_name} not found in the device mapping."
else:
log.debug(f"Getting BGP neighbors from router {router_name}")
if neighbor_address:
cmd = f"show bgp neighbor {neighbor_address}"
else:
cmd = "show bgp summary"
result = _run_junos_cli_command(router_name, cmd)
return [types.TextContent(type="text", text=result)]
# Step 2: Add to TOOL_HANDLERS
TOOL_HANDLERS = {
# ... existing tools ...
"show_bgp_neighbors": handle_show_bgp_neighbors,
}
# Step 3: Add to list_tools()
types.Tool(
name="show_bgp_neighbors",
description="Show BGP neighbor information",
inputSchema={
"type": "object",
"properties": {
"router_name": {"type": "string", "description": "The name of the router"},
"neighbor_address": {"type": "string", "description": "Optional: specific neighbor IP"}
},
"required": ["router_name"]
}
)
log
logger for debugginglist[types.ContentBlock]
with text contentFor operations beyond CLI commands, use PyEZ directly:
from jnpr.junos import Device
from jnpr.junos.utils.config import Config
# Example: Using PyEZ tables
async def handle_get_interfaces(arguments: dict) -> list[types.ContentBlock]:
router_name = arguments.get("router_name", "")
if router_name not in devices:
result = f"Router {router_name} not found in the device mapping."
else:
device_info = devices[router_name]
try:
connect_params = prepare_connection_params(device_info, router_name)
with Device(**connect_params) as junos_device:
# Use PyEZ tables or other utilities
interfaces = junos_device.rpc.get_interface_information()
# Process interfaces...
result = "Interface information..."
except Exception as e:
result = f"Error: {e}"
return [types.TextContent(type="text", text=result)]
Example test:
# Test the handler directly
result = await handle_my_new_tool({
"router_name": "router-1",
"my_param": "test"
})
print(result[0].text)
Enable debug logging to see detailed execution:
logging.basicConfig(level=logging.DEBUG)
Use the stdio transport for easier debugging:
python jmcp.py -f devices.json -t stdio
Test individual commands manually:
result = _run_junos_cli_command("router-1", "show version")
print(result)
Open-source tool for collaborative editing, versioning, evaluating, and releasing prompts.
Execute developer-defined bash scripts in a Dockerized environment for coding agents.
A server for a structured, LLM-based coding workflow, from feature clarification and planning to phased development and progress tracking.
Provides sarcastic and cynical code reviews from the perspective of a grumpy senior developer.
Provides real-time access to Chainlink's decentralized on-chain price feeds.
Official MCP server for Sentry.
Convert Figma designs into React Native components.
Performs gene set enrichment analysis using the Enrichr API, supporting all available gene set libraries.
An AI-driven platform for frontend semantic cognition and automation.
Flag features, manage company data, and control feature access using Bucket.