Skip to main content
Glama

ROS-MCP

CI Container

ROS-MCP is a small MCP gateway for managing multiple MikroTik RouterOS devices over SSH. It exposes exactly two tools and keeps device credentials, SSH session reuse, retries, timeouts, and output limits behind the gateway.

Despite the project name, ROS means MikroTik RouterOS, not Robot Operating System.

MCP tools

device_list

Returns the configured, non-sensitive device inventory. It performs no network I/O and never returns passwords, private keys, environment-variable names, or host-key fingerprints.

command_execute

command_execute(device: string, command: string, dry_run: boolean = true)
  • device is always required. There is no implicit default-device fallback.

  • command is one RouterOS CLI line, limited to 8 KiB.

  • dry_run=true validates locally and does not open an SSH connection.

  • A result with status=unknown means the command may have executed and must not be retried automatically.

The gateway does not implement RouterOS Safe Mode and does not claim that a dry run validates RouterOS syntax or effects.

Related MCP server: MikroTik MCP Server

Configuration

Choose exactly one device-registry source:

  • ROS_DEVICES_JSON, containing the JSON object directly; or

  • ros-mcp --config PATH, where PATH is a UTF-8 JSON file.

The two sources use the same schema. Do not set ROS_DEVICES_JSON when using --config; the server rejects ambiguous configuration sources. Each device defines its SSH authentication method.

{
  "main": {
    "display_name": "Main Router",
    "description": "Internet edge",
    "host": "192.168.88.1",
    "port": 22,
    "username": "ai-mgmt",
    "enabled": true,
    "tags": ["production", "edge"],
    "connect_timeout_seconds": 15,
    "command_timeout_seconds": 25,
    "keepalive_seconds": 30,
    "idle_ttl_seconds": 300,
    "output_limit_bytes": 1048576,
    "auth": {
      "type": "password",
      "password": "REPLACE_WITH_ROUTEROS_SSH_PASSWORD"
    },
    "host_key": {
      "fingerprint_sha256": "SHA256:REPLACE_WITH_43_BASE64_CHARACTERS"
    }
  },
  "office": {
    "display_name": "Office Router",
    "host": "10.0.0.1",
    "port": 22,
    "username": "ai-mgmt",
    "auth": {
      "type": "private_key",
      "private_key_env": "ROS_OFFICE_PRIVATE_KEY",
      "passphrase_env": "ROS_OFFICE_KEY_PASSPHRASE"
    },
    "host_key": {
      "fingerprint_sha256": "SHA256:REPLACE_WITH_43_BASE64_CHARACTERS"
    }
  }
}

devices.example.json contains the same sanitized configuration as a starting point. Keep actual device inventories outside version control.

For password authentication, set exactly one of these fields:

  • password: the RouterOS SSH password directly in the configuration; no credential environment variable is required.

  • password_env: the name of an environment variable containing the password.

The legacy password_env form remains supported. Private-key authentication continues to use private_key_env and optional passphrase_env references:

ROS_MAIN_PASSWORD=<main RouterOS SSH password>
ROS_OFFICE_PRIVATE_KEY=<complete PEM/OpenSSH private-key text>
ROS_OFFICE_KEY_PASSPHRASE=<optional key passphrase>

An inline password is plaintext in the configuration file. Treat that file as a secret: do not commit it, and mount it read-only in containers. For a local process, restrict it to the deployment user (for example, chmod 600 devices.json). The default devices.json name is excluded from Git and Docker build contexts.

Device IDs must match ^[a-z][a-z0-9_-]{0,63}$. Configuration is validated and resolved once at startup, so restart the server after changing a configuration file. Missing secrets, unknown fields, duplicate JSON keys, invalid IDs, and malformed SHA-256 fingerprints prevent startup.

The host-key fingerprint is mandatory. Unknown keys are never accepted through TOFU or Paramiko's AutoAddPolicy.

Run locally

Python 3.11 or newer and uv are required for local development.

uv sync --all-groups
export ROS_DEVICES_JSON='{"main":{"host":"192.168.88.1","username":"ai-mgmt","auth":{"type":"password","password_env":"ROS_MAIN_PASSWORD"},"host_key":{"fingerprint_sha256":"SHA256:REPLACE_WITH_43_BASE64_CHARACTERS"}}}'
export ROS_MAIN_PASSWORD='replace-me'
uv run ros-mcp

To deploy from a file instead, put the JSON object above in a UTF-8 file and pass its path. The inline password in the file requires no credential environment variable.

chmod 600 /absolute/path/to/devices.json
uv run ros-mcp --config /absolute/path/to/devices.json

The process speaks MCP over stdio. Application code does not write ordinary messages to stdout because stdout belongs to the protocol.

Run with uvx

Use uvx to run a released version without cloning this repository. Pin the Git tag (or a full commit SHA) so deployments do not move with a branch:

uvx --from 'git+https://github.com/asharca/ros-mcp.git@v0.1.0' \
  ros-mcp --config /absolute/path/to/devices.json

The final ros-mcp selects this project's console command. This requires uv and Git. Do not use uvx ros-mcp: that PyPI name belongs to a different project. This repository is installed from its Git source instead.

Docker

Published images are available from GHCR:

docker pull ghcr.io/asharca/ros-mcp:latest

main publishes latest, main, and sha-<commit> tags. Git tags such as v1.2.3 additionally publish 1.2.3 and 1.2.

To build locally:

docker build -t ros-mcp:local .
docker run --rm -i \
  --read-only \
  --tmpfs /tmp:rw,size=16m \
  -e ROS_DEVICES_JSON \
  -e ROS_MAIN_PASSWORD \
  ros-mcp:local

The image runs as a non-root user, exposes no port, and starts the stdio MCP server directly. Its root filesystem is safe to run read-only.

Docker with a configuration file

Mount the configuration file read-only and pass the container path to --config after the image name. A file with an inline password needs no credential environment variable.

docker run --rm -i \
  --read-only \
  --tmpfs /tmp:rw,size=16m \
  --mount type=bind,src="/absolute/path/to/devices.json",dst=/config/devices.json,readonly \
  ghcr.io/asharca/ros-mcp:latest \
  --config /config/devices.json

Use an absolute source path that is visible to the Docker daemon. With Docker Desktop, share the source directory; with a remote daemon, use a file or secret mount managed on that daemon rather than a path from the MCP client's machine. The mounted file must be readable by the image's UID/GID 10001. Do not use -t or -d: the MCP transport needs attached stdin and stdout. Do not disable networking because the gateway needs outbound SSH access to RouterOS devices. Use a managed secret mount or set an owner/ACL that permits UID/GID 10001 to read the file without making it world-readable. If the file uses password_env, private_key_env, or passphrase_env, forward each referenced variable with Docker's -e option.

Deploy as an MCP server

ROS-MCP uses the stdio transport. Configure your MCP client to start one process per server instance and keep its stdin and stdout connected to the client. Client configuration formats differ; clients that use an mcpServers object can use either of the following shapes.

uvx

{
  "mcpServers": {
    "ros-mcp": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/asharca/ros-mcp.git@v0.1.0",
        "ros-mcp",
        "--config",
        "/absolute/path/to/devices.json"
      ]
    }
  }
}

Docker

{
  "mcpServers": {
    "ros-mcp": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "--read-only",
        "--tmpfs",
        "/tmp:rw,size=16m",
        "--mount",
        "type=bind,src=/absolute/path/to/devices.json,dst=/config/devices.json,readonly",
        "ghcr.io/asharca/ros-mcp:latest",
        "--config",
        "/config/devices.json"
      ]
    }
  }
}

For a file that uses environment-variable credential references instead, add the client's environment entries and matching Docker -e arguments. Verify host-key fingerprints through a trusted out-of-band channel, use a least-privileged RouterOS account, and keep manual approval enabled for command_execute in the MCP client.

ToolPlane

  1. Publish the image to a registry reachable by ToolPlane's Docker daemon.

  2. In a workspace, choose MCP > Add custom MCP > Docker.

  3. For environment configuration, enter the image reference and leave Start Command empty. Add ROS_DEVICES_JSON and all referenced secret variables on the deployment's Variables page, then restart it.

  4. For file configuration, use ToolPlane's file or secret-mount facility to place the file at (for example) /config/devices.json, then set Start Command to --config /config/devices.json. Add deployment variables only when the file uses environment-variable credential references, then restart it.

  5. Leave Disconnect from network disabled so the container can reach RouterOS SSH addresses.

ToolPlane currently has a 30-second call timeout. Configure command_timeout_seconds to about 25 seconds there, or raise ToolPlane's timeout above the gateway's command timeout.

Execution behavior

  • One reusable SSH session is maintained per device.

  • Commands for the same device are serialized in a bounded FIFO.

  • Different devices can execute concurrently.

  • A stale session may reconnect once before command dispatch.

  • A command is never replayed after dispatch.

  • stdout and stderr are drained concurrently and retained up to the configured per-stream byte limit.

  • All SSH clients are explicitly closed during MCP server shutdown.

Logging

ROS-MCP has no audit-log Module, log-query tool, command history, or persistent request/output storage. Expected operational failures are returned as structured tool results. The MCP runtime is configured to emit only error-level diagnostics to stderr.

Development

uv run pytest
uv run ruff check src tests
uv run ruff format --check src tests

Available Tools

2 tools
command_executeA

Execute a command on one device.

Commands may be non-idempotent. When the result reports an unknown outcome, the command may already have run and must not be retried automatically.

ParametersJSON Schema
NameRequiredDescriptionDefault
deviceYes
commandYes
dry_runNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
deviceYes
statusYes
stderrNo
stdoutNo
exit_codeNo
duration_msYes
execution_stateYes
stderr_truncatedNo
stdout_truncatedNo

TDQS

A3.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full disclosure burden and does so well for the risk profile: it warns that commands may be non-idempotent, that an unknown outcome may mean the command already ran, and that automatic retry is forbidden. It omits other relevant traits such as required permissions and the fact that dry_run defaults to true, which materially changes whether anything actually executes.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the action and followed by the critical failure-mode caveat. No filler and nothing buried.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-shape explanation isn't required, and the description correctly ties the "unknown outcome" language to the result. However, with 0% parameter coverage and no annotations, the omission of dry_run semantics and execution permissions leaves the definition materially incomplete for a mutating command tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% across three parameters, so the description must compensate, yet it explains none of them. Only "on one device" implies the device parameter is singular rather than a list; command and especially dry_run (defaulting to true, i.e., no execution unless changed) are left entirely unexplained.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: "Execute a command on one device." The scope (single device) is clear and the sibling device_list is obviously a different operation, so an agent won't confuse them, though the description never explicitly contrasts them.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by the non-idempotency warning, but there is no explicit when-to-use/when-not framing or named alternative beyond the unrelated device_list. The retry warning is useful context but is behavioral guidance rather than selection guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

device_listA

List configured RouterOS devices without connecting to them or exposing credentials.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
devicesYes

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations present, the description carries the full burden, and it does disclose two meaningful traits: no device connection occurs and no credentials are exposed. That is solid safety context for an inventory call, though it says nothing about whether results are cached or how connectivity-independent discovery works.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence whose second clause earns its place by stating the safety guarantee. No filler, no restatement of the tool name.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-format details are rightly omitted, and a zero-parameter list tool needs little else. The description is nearly complete; adding which sibling to use instead would close the gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters and the schema is empty with 100% coverage, so there is no parameter semantics to convey. Baseline of 4 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb (List) and resource (configured RouterOS devices), making the operation unambiguous. It also implicitly separates itself from the command_execute sibling by clarifying that this path does not connect to devices. It stops short of naming the sibling outright, which is the only thing keeping it from a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied: the 'without connecting to them or exposing credentials' clause suggests this is the safe inventory path versus actually running commands. There is no explicit when-to-use/when-not statement or named alternative, so an agent must infer that command_execute is the contrasting tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv0.1.0
    • First observedcommand_execute
    • First observeddevice_list

TDQS

A3.8/5.0

Scored across 2 tools

Disambiguation5/5

The two tools have completely distinct purposes: device_list is a read-only discovery operation and command_execute performs actions on a device. There is no realistic way to confuse them.

Naming Consistency5/5

Both names follow a clean, consistent noun_verb pattern (device_list, command_execute). The convention is uniform across the entire surface.

Tool Count3/5

Two tools is on the thin side for a RouterOS management server, even though a generic command gateway offsets some of that. It sits at the borderline the rubric flags as 1-2 tools feeling sparse.

Completeness4/5

The generic command_execute provides broad coverage of RouterOS operations, but there is no way to add, remove, or configure a device despite device_list referencing 'configured' devices, leaving a lifecycle gap.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to interact with MikroTik RouterOS devices through API and SSH connections, supporting network monitoring, configuration management, and diagnostics across multiple routers with automatic connection fallback.
    3
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables management of MikroTik routers running RouterOS 6 and 7 via SSH, Telnet, or API with automatic command adaptation. Provides over 46 MCP tools for device management, firewall, DHCP, VPN, configuration profiles, and more.
    3
    MIT
  • A
    license
    Not graded
    quality
    F
    maintenance
    Enables management of network devices via SSH using Netmiko, supporting command execution, configuration management, and concurrent operations across multiple vendors.
    5
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server for managing MikroTik RouterOS fleets, exposing 65+ tools for system administration, interfaces, firewall, DHCP/DNS, PPP, diagnostics, and SSH command execution with KeePass-backed credentials.
    10 npm
    2
    Apache 2.0