mikrotik-mcp
An MCP server that gives an agent three tools: discover configured MikroTik routers, run one RouterOS command on one of them over SSH, and search bundled offline RouterOS reference docs.
List profiles (
mikrotik_list_profiles) — search configured routers by name/host/description/tags; returns connection details, which config file each came from, read-only status, and config problems, but never secrets.Run commands (
mikrotik_exec) — execute a single RouterOS command (e.g./system resource print) on a named profile, returning stdout, stderr, exit code, duration and the host key fingerprint.Search docs (
mikrotik_search_docs) — offline BM25 search over bundled RouterOS reference material (console syntax, reading state, v6/v7 differences, write patterns), optionally filtered by RouterOS major version.Stateless execution — every call re-reads the config and opens/closes its own SSH connection; no session, working directory or shell state persists, so commands need absolute RouterOS paths.
Read-only by default — commands that look like writes are refused before connecting, per profile (
readOnly: falseto opt in); the docs stress this is a guardrail, not a security boundary.Flexible auth — per-profile key file, password or passphrase via named environment variable, or ssh-agent (opt-in).
Host key verification — known-hosts, pinned SHA256 fingerprint, or insecure-ignore for labs.
Config from many locations — env var paths,
.mikrotik-mcp.jsonup the directory tree, OS config dirs, and~/.mikrotik-mcp.json, merged with precedence and hot-reloaded on each call.Timeout and legacy crypto control — per-call timeout override and per-profile SSH algorithm overrides for older routers.
Allows running RouterOS commands on MikroTik routers over SSH, with profile-based configuration, read-only enforcement, host key verification, and support for key, password, or agent authentication.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mikrotik-mcpshow me the current interfaces and system uptime"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mikrotik-mcp
A Model Context Protocol server that runs RouterOS commands on MikroTik routers over SSH.
Node + TypeScript, no runtime toolchain of its own, and stateless: every tool call re-reads the config and opens and closes its own SSH connection. Nothing is cached, pooled or carried between calls.
It is also self-contained. A profile declares everything it needs, relative paths resolve against the config file, and nothing is inherited from the machine it happens to run on — no ssh-agent, no ~/.ssh/config, no ambient environment. Copy the config directory to a server or a container and it behaves identically.
It speaks JSON-RPC over stdio, so it drops into any MCP harness: Claude Code, Codex, opencode, Cursor, Zed, and anything else that can spawn a command.
Status: the config, policy and SSH layers are covered by tests, including real SSH handshakes against a local test server. It has not yet been run against physical MikroTik hardware — run
mikrotik-mcp --test <profile>against your own router first. Please report anything RouterOS does differently.
Use it
npx -y @denver6000/mikrotik-mcpThe published package is plain Node ESM with no runtime toolchain of its own — Node 18+ is enough.
Client | How to add it |
Claude Code |
|
Codex |
|
opencode |
|
Anything else |
|
If a profile uses password auth, the client also has to pass the variable holding it — see Getting a secret to the server.
Related MCP server: mikrotik-cli-mcp
Quick start
1. Prepare the router
Create a dedicated RouterOS user rather than reusing admin. A read-only group is the real safety mechanism — the server's own readOnly check is only a guardrail.
/user group add name=mcp-read policy=ssh,read,test
/user add name=mcp-readonly group=mcp-read
/ip service enable sshThen give it a key (preferred) or a password:
# key auth — upload your public key first, e.g. with scp
/user ssh-keys import public-key-file=id_mikrotik.pub user=mcp-readonly
# or password auth
/user set mcp-readonly password="..."2. Write a config file
Start from config.example.json. For a first router, put this at ~/.mikrotik-mcp.json:
{
"profiles": {
"core": {
"host": "10.0.0.1",
"username": "mcp-readonly+ct",
"auth": { "type": "key", "path": "./keys/mikrotik" },
"description": "Core router"
}
}
}./keys/mikrotik resolves against the config file's own directory, not the working directory — so a folder holding the config and its keys can be moved anywhere and still work.
Everything else defaults: port 22, known_hosts verification, read-only, 20 s timeout. The +ct suffix tells RouterOS to drop colour codes and paging.
auth has no default and must be declared. That is deliberate: guessing at an agent or a conventionally-named key would make the config depend on the machine it runs on, which is exactly what this server avoids.
3. Trust the host key
The first connection will be refused, on purpose, because the router is not in known_hosts yet. Either add it the normal way:
ssh-keyscan -H 10.0.0.1 >> ~/.ssh/known_hosts…or, if you prefer to pin the key in the config, run the command once and copy the fingerprint out of the error message into hostKey.fingerprintSha256. Verify the fingerprint against the router itself (/ip ssh print shows the host key, or read it on the console) before trusting it.
4. Check it
npx -y @denver6000/mikrotik-mcp --check-configConfig search path (highest precedence first):
C:\work\.mikrotik-mcp.json
loaded C:\Users\you\.mikrotik-mcp.json
Profiles (1):
core
ssh mcp-readonly+ct@10.0.0.1:22
auth agent
host key known-hosts
writes refused (readOnly)
from C:\Users\you\.mikrotik-mcp.jsonThis exits non-zero if a file is unparseable or no profiles were found, so it works in CI too. It reads the same files the server does, but connects to nothing.
5. Prove it against the router
--check-config never connects to anything. To test the whole path — TCP, host key, credentials, RouterOS — run:
npx -y @denver6000/mikrotik-mcp --test coreConnecting to 'core' (mcp-readonly+ct@10.0.0.1:22) as agent...
host key SHA256:9lK… (accepted by policy 'known-hosts')
command /system identity print
exit code 0
took 412ms
name: MikroTik-core
OK.It runs one harmless read (/system identity print), prints FAILED. with the reason if anything goes wrong, and exits non-zero. Do this before wiring up an agent — it tells you whether a problem is in the config, the network, or the router.
6. Point your agent at it
Ask it to run /system resource print on core. It should call mikrotik_list_profiles first, then mikrotik_exec.
Configuration
defaults applies to every profile in the same file; a profile overrides it field by field.
{
"$schema": "https://raw.githubusercontent.com/denver6000/mikrotik-mcp/main/schema/mikrotik-mcp.schema.json",
"defaults": {
"username": "mcp-readonly+ct",
"auth": { "type": "agent" },
"timeoutMs": 20000
},
"profiles": {
"core": {
"host": "10.0.0.1",
"description": "Core router, main uplink",
"tags": ["core", "site-a"]
},
"branch-a": {
"host": "192.168.50.1",
"port": 2222,
"tags": ["branch", "site-b"],
"hostKey": { "policy": "pinned", "fingerprintSha256": "SHA256:abc..." }
},
"lab": {
"host": "10.20.0.1",
"username": "admin+ct",
"auth": { "type": "password", "passwordEnv": "MIKROTIK_LAB_PASSWORD" },
"readOnly": false
}
}
}Adding $schema gives you completion and validation in any editor that speaks JSON Schema.
Profile fields
Field | Default | Meaning |
| — | Hostname or IP. Required. |
|
| SSH port. |
|
| RouterOS user. Append |
| — | Required (on the profile or in |
|
| |
Relative paths in | ||
|
| Refuse commands that are not recognised as read-only. |
|
| Connect + command timeout. |
| — | SSH algorithm overrides for older routers. See below. |
| — | Free-form, searchable by |
Where the config is read from
Every one of these is loaded, highest precedence first. When two files define the same profile name the earlier one wins; profiles with different names merge into one list.
# | Path |
1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
Which to use:
One personal file —
~/.mikrotik-mcp.json. Fine for most people; start here.Per-repo file —
.mikrotik-mcp.jsoncommitted alongside a network-automation repo, so anyone cloning it gets the right profiles. Safe to commit: it holds no secrets. Because parents are searched too, a file at the root of a monorepo covers every package under it.Both — the repo file wins for names it defines, and your personal file supplies the rest. This is how you override one router's settings locally without editing a shared file.
An explicit path —
$MIKROTIK_MCP_CONFIGfor CI, or to keep several environments in separate files:MIKROTIK_MCP_CONFIG=prod.json:staging.json.
Files are re-read on every tool call, so edits take effect without restarting the server or the agent. mikrotik_list_profiles reports which file each profile came from and flags any it could not parse.
Authentication
Every profile declares an auth block. There is no implicit fallback.
{ "type": "key", "path": "./keys/mikrotik" } // recommended
{ "type": "key", "path": "./keys/mikrotik", "passphraseEnv": "MTK_KEY_PASS" }
{ "type": "password", "passwordEnv": "MIKROTIK_LAB_PASSWORD" }
{ "type": "agent" } // opt-in; see the caveat below
{ "type": "agent", "socket": "/run/user/1000/ssh-agent" }Secrets never go in the config file. A profile names the environment variable holding a password or passphrase, and the value is read at connect time — so config files stay safe to commit and share.
Paths expand ~ and $VARS; anything still relative resolves against the config file's directory.
Key formats
ssh2 does not accept every key file OpenSSH can. Verified against ssh2 1.17:
Format | Works |
Anything | yes |
RSA PKCS#1 PEM ( | yes |
EC SEC1 PEM ( | yes |
PKCS#8 PEM ( | no |
In practice: generate keys with ssh-keygen -t ed25519 and you will never hit this. If you have a PKCS#8 key, --check-config names the problem and prints the conversion command rather than failing at connect time with "Unsupported key format".
Encrypted keys and the agent
Without an agent, an encrypted key's passphrase has to come from an environment variable — which runs into the subprocess problem below. So a self-contained setup in practice means a dedicated, unencrypted key file.
That is the standard automation answer, and it is safe here only because the blast radius is bounded elsewhere: the key belongs to a RouterOS user in a read-only group, it is used for nothing else, and it sits with chmod 600. On POSIX systems --check-config warns if the file is readable by group or others.
Agent auth remains supported for anyone who prefers it, but it is opt-in and not the default. It ties the profile to the environment the server was launched from: SSH_AUTH_SOCK is not always inherited by a subprocess spawned from a GUI application, and there is no agent at all in a typical container.
Getting a secret to the server
Your MCP client launches this server as a subprocess, so a variable exported in your interactive shell does not necessarily reach it. Either use agent or key auth — which need no variables, and are the reason agent is the default — or declare the variable in the client's own config. The field differs per client:
# Claude Code
claude mcp add mikrotik --env MIKROTIK_LAB_PASSWORD=... -- npx -y @denver6000/mikrotik-mcp# Codex — ~/.codex/config.toml
[mcp_servers.mikrotik]
command = "npx"
args = ["-y", "@denver6000/mikrotik-mcp"]
env = { MIKROTIK_LAB_PASSWORD = "..." }// opencode — note "environment", not "env"
{
"mcp": {
"mikrotik": {
"type": "local",
"command": ["npx", "-y", "@denver6000/mikrotik-mcp"],
"enabled": true,
"environment": { "MIKROTIK_LAB_PASSWORD": "..." }
}
}
}// generic mcpServers config
{
"mcpServers": {
"mikrotik": {
"command": "npx",
"args": ["-y", "@denver6000/mikrotik-mcp"],
"env": { "MIKROTIK_LAB_PASSWORD": "..." }
}
}
}Note the trade-off: this moves the password out of the router config and into the client config, which is usually not a file you want to commit either. Key or agent auth avoids the problem entirely.
Host key verification
On by default. A router whose key is unknown is refused, and the error prints the fingerprint so you can verify it and then trust it.
{ "policy": "pinned", "fingerprintSha256": "SHA256:abc..." } // self-contained; preferred
{ "policy": "known-hosts" } // default
{ "policy": "known-hosts", "knownHostsPath": "./known_hosts" }
{ "policy": "insecure-ignore" } // accept anything — lab use onlyUnder the default known-hosts policy, a known_hosts file beside the config is consulted first, then ~/.ssh/known_hosts as a fallback. Naming knownHostsPath explicitly uses that file only. For a config that must be portable, either ship a known_hosts next to it or pin the fingerprint — the personal-file fallback is a convenience for a workstation, not something to depend on. --check-config prints exactly which files will be consulted.
Setting fingerprintSha256 without a policy implies pinned. Hashed known_hosts entries, wildcards, [host]:port entries and negated patterns are all handled. A host that is known but presents a different key is a hard failure, never a prompt.
Older routers: legacy crypto
Modern SSH clients no longer enable the key exchange and host key algorithms that older RouterOS releases — or routers with strong crypto disabled — may be limited to. If the connection fails during handshake rather than at login, add what that router needs:
{
"host": "10.0.0.1",
"algorithms": {
"kex": { "append": ["diffie-hellman-group14-sha1"] },
"serverHostKey": { "append": ["ssh-rsa"] }
}
}append, prepend and remove are passed through to the SSH layer, as are plain arrays if you want to specify the whole list. Prefer upgrading RouterOS or enabling strong crypto on the router over weakening the client.
Read-only by default
Profiles are readOnly: true unless you say otherwise. Commands are classified before connecting: a command must contain a recognised read action (print, get, find, export, monitor, ping, …) and no recognised write action (set, add, remove, reboot, …). Every command in a ;- or newline-separated chain is checked, so a read cannot smuggle a write along with it. Anything the classifier does not understand is treated as a write and refused.
This is a guardrail against accidents, not a security boundary. A determined agent, or a command phrased in a way the classifier does not model, can get past it. For real enforcement, log in as a RouterOS user whose group grants read access only — the router is the only thing that can enforce that. Set
readOnly: falseon profiles where writes are intended.
Troubleshooting
Symptom | Cause |
| The config is not on the search path. Run |
A path shows | The file exists but is invalid JSON or breaks the schema; the reason is printed under Problems. Unknown keys are rejected, so check for typos like |
| Expected on first contact. Verify the printed fingerprint, then add it to |
| The router presented a different key than last time. Do not bypass this until you know why. |
| The client did not pass it to the subprocess — see Getting a secret to the server. |
| Intended. Set |
| Add an |
| PKCS#8 PEM. Convert it, or regenerate with |
| Either set |
| Wrong user, wrong key, or the RouterOS group lacks the |
Handshake fails before any login prompt | Algorithm mismatch with an older router — see Older routers: legacy crypto. |
A command fails but | RouterOS often reports errors in its output rather than through the exit status, so read the text, not just the status. |
Output is full of escape codes | Add |
RouterOS reference
Four documents ship with the package and back mikrotik_search_docs:
Document | Covers |
| Output control ( |
| Read commands by area — system, interfaces, addresses, routes, firewall, DHCP, wireless, logs, health, export |
| What changed: OSPF and BGP redesign, routing filter rule syntax, moved menus, the three wireless menus, containers |
| Write patterns, predicates over numbers, lockout risks, rule ordering, reversibility |
Content is written for this project rather than copied from MikroTik, and each claim was checked against the official documentation. Where something is widely used but could not be confirmed — print terse and print as-value — it is labelled as unverified rather than presented as fact. Verify those against your hardware.
The trap worth knowing
RouterOS item numbers are assigned per session and reassigned on the next print. Since every mikrotik_exec call is its own SSH session, a number from one call is meaningless in the next — and acting on a stale number does not error, it acts on whatever holds that number now. Always select by predicate:
/ip firewall filter remove [find where comment="mcp-temp"]This is stated in the mikrotik_exec tool description as well as the reference, because it is the most expensive thing an agent can get wrong here.
Command line
Command | Purpose |
| Start the MCP server on stdio (what your client runs). |
| Show every config path checked, what loaded, and what each profile resolves to. Connects to nothing. Exits non-zero on a bad or empty config. |
| Connect to that router and run one harmless read. Exits non-zero on failure. |
| As expected. |
Tools
mikrotik_list_profiles
Search the configured routers. Returns connection details, which config file each came from, whether it is read-only, and any config problems. Never returns secrets — only the name of the variable holding one.
Input | Type | Notes |
| string, optional | Case-insensitive substring over name, host, description and tags. |
mikrotik_search_docs
Search bundled RouterOS reference material — command syntax, idioms, and v6/v7 differences. Offline and read-only; it touches no router and needs no config.
Input | Type | Notes |
| string | Keywords, e.g. |
|
| Limit to sections that apply to that major version. |
| number, optional | Sections to return. Default 3. |
The reference lives in docs/ as plain markdown and is re-read on every call, so it can be edited or extended without rebuilding. Ranking is BM25 over sections, with no dependencies and no index to keep in sync.
mikrotik_exec
Run one RouterOS command on one router and return its output.
Input | Type | Notes |
| string | Profile name. |
| string | e.g. |
| number, optional | Overrides the profile's timeout. |
Returns stdout, stderr, the exit code and the host key fingerprint, as text and as structured content. Because each call is a fresh connection with no shell state, commands must use absolute RouterOS paths (/ip address print, not print after a cd).
Develop
No extra toolchain: Node runs the TypeScript sources directly via built-in type stripping, so npm install is the whole setup. Node 22.18+ is required for development; the published package supports Node 18+.
npm install
npm run dev # run the server on stdio, straight from src/
npm test # unit tests + a real SSH round-trip against a local ssh2 server
npm run typecheck
npm run schema # regenerate schema/mikrotik-mcp.schema.json from the zod schema
npm run build # emit dist/ with tsc
npm run inspect # open the MCP Inspector against src/Layout
src/
index.ts CLI entry: arg handling + stdio transport
server.ts createServer() — registers every tool
config/
schema.ts zod schema for the config file, and the resolved profile type
paths.ts where config files are looked for; ~ and $VAR expansion
load.ts read, validate, merge, search
report.ts the --check-config report
docs/
corpus.ts parse docs/*.md into searchable sections
search.ts dependency-free BM25 ranking
ssh/
exec.ts one connection, one command, then close
knownHosts.ts known_hosts parsing and host key verification
policy.ts read-only command classification
keyFile.ts offline key inspection: format, fingerprint, permissions
testConnection.ts the --test <profile> probe
tools/
listProfiles.ts mikrotik_list_profiles
exec.ts mikrotik_exec
searchDocs.ts mikrotik_search_docs
docs/ the RouterOS reference, shipped with the package
scripts/
generate-schema.ts zod schema -> JSON Schema
test/ node:test suites; helpers.ts builds a sandboxed config + envEvery entry point takes an injectable LookupEnvironment (env, cwd, home, platform) so tests never touch the real user's config or SSH agent.
Adding a tool
Add
src/tools/<name>.tsexporting aregister…Tool(server, e)function.Call it from
createServer()insrc/server.ts.Add a case to
test/server.test.ts.
Relative imports use explicit .ts extensions so Node can run the sources as-is; tsc rewrites them to .js on build. Keep stdout clean — it is the JSON-RPC channel. Log to stderr only.
Release
The npm package is @denver6000/mikrotik-mcp. It is scoped because the unscoped mikrotik-mcp is taken by an unrelated project; the installed command is still mikrotik-mcp.
version lives in both package.json and SERVER_VERSION in src/server.ts; bump both. A test fails if they disagree, and the release workflow fails if the git tag disagrees with either.
First publish
Trusted publishing cannot do a package's first release: a trusted publisher is configured on a package that already exists. So one release goes out from a workstation, and every release after that is automated.
It does not need an access token. npm warns against tokens with 2FA bypass for automation, and they are unnecessary here — modern npm authenticates in the browser, which works with a passkey, Windows Hello, Touch ID or a security key:
npm logout # clears any stored _authToken from ~/.npmrc
npm login # opens a browser; npm 9+ defaults to auth-type=web
npm whoami # must print denver6000 — the scope has to match
npm publishA stored _authToken in ~/.npmrc is the usual reason this fails with
E403 ... Two-factor authentication or granular access token with bypass 2fa enabled is required:
npm presents the token instead of starting an interactive login, and a token without 2FA bypass cannot publish. npm logout removes it, after which the browser flow takes over.
If your second factor is an authenticator app rather than a passkey, npm publish --otp=123456 also works.
Then switch CI on (once)
On npmjs.com, open the package → Settings → Trusted publisher, and add a GitHub Actions publisher:
Field | Value |
Repository |
|
Workflow filename |
|
After that, every later release is a tag push, with no npm token stored anywhere:
npm version patch # or minor / major — commits and tags
git push --follow-tags.github/workflows/release.yml then runs typecheck, tests and build, checks the tag against package.json, and publishes over OIDC. Provenance is attached automatically, so the package page links back to the exact commit and workflow run that built it.
Only dist/, docs/, schema/, README.md and LICENSE are published.
License
MIT
Available Tools
3 toolsmikrotik_execRun a RouterOS command over SSHADestructive
Run a single RouterOS command on a configured router over SSH and return its output. Stateless: each call opens a fresh connection and closes it before returning, so there is no session, working directory or shell state carried between calls — send absolute command paths such as '/system resource print'. Profiles are read-only by default, in which case commands that would change the router are refused before connecting. Use mikrotik_list_profiles to find profile names. Item numbers printed by RouterOS are NOT stable: they are reassigned per session and again on the next print, and every call here is a new session. Never pass a number seen in an earlier call to a later one — it will act on whatever holds that number now. Select by predicate instead, e.g. /ip firewall filter remove [find where comment="x"]. See mikrotik_search_docs for more.
| Name | Required | Description | Default |
|---|---|---|---|
| command | Yes | RouterOS command, e.g. '/system resource print' or '/ip address print detail'. | |
| profile | Yes | Profile name from mikrotik_list_profiles. | |
| timeoutMs | No | Override the profile's timeout for this call. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes | |
| stderr | Yes | |
| stdout | Yes | |
| exitCode | Yes | |
| durationMs | Yes | |
| hostKeyFingerprint | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare destructiveHint=true and readOnlyHint=false, but the description adds critical traits annotations cannot express: statelessness (fresh connection per call, no session/working directory/shell state), the read-only-profile refusal that happens before connecting, and the RouterOS item-number instability across sessions. This is genuine operational context beyond the safety hints.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and statelessness are front-loaded, followed by the read-only behavior and the item-number hazard, then sibling pointers. Every sentence carries information, though the item-number paragraph is slightly long for what is ultimately one warning.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be described. Given a 3-parameter, non-idempotent, potentially destructive open-world tool, the description covers everything an agent needs: connection model, refusal behavior, profile source, command addressing, and a serious correctness pitfall.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description still adds meaning by requiring absolute command paths with a concrete example ('/system resource print') and by tying the profile parameter to mikrotik_list_profiles. The item-number warning further informs how the command argument should be constructed, exceeding what the schema states.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Run a single RouterOS command on a configured router over SSH') plus the return ('return its output'). It also distinguishes itself from siblings by naming mikrotik_list_profiles and mikrotik_search_docs and explaining their roles, so an agent can separate this tool from its neighbors without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit routing: use mikrotik_list_profiles to obtain a profile, see mikrotik_search_docs for more, and select targets by predicate rather than by printed item number. It also states the operating condition (profiles read-only by default refuse mutating commands before connecting), which tells the agent when a call will be rejected.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
mikrotik_list_profilesList MikroTik router profilesARead-only
Search the configured MikroTik router profiles. Config files are re-read on every call. Returns connection details and whether each profile is read-only. Never returns secrets — passwords and passphrases live in environment variables, and only the variable name is shown. Call this first to discover the profile name that mikrotik_exec needs.
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | Case-insensitive substring matched against name, host, description and tags. Omit to list all. |
Output Schema
| Name | Required | Description |
|---|---|---|
| issues | Yes | |
| sources | Yes | Config files that were loaded, in precedence order. |
| profiles | Yes | |
| searched | Yes | Every path consulted, whether or not it exists. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint annotation, the description adds important behavioral detail: config files are re-read on every call, profiles can be read-only, and secrets are never returned—only environment variable names. This materially changes how an agent should interpret results and is exactly the kind of context annotations cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Every sentence earns its place: scope, freshness, return contents, security behavior, and usage sequence are each covered without repetition. The most actionable instruction is front-loaded at the end but clearly tied to the tool's role.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only list tool with one optional parameter and an output schema, the description is complete. It tells the agent what is returned, what is deliberately hidden, how to search, and how this tool connects to its sibling.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema already documents the optional query parameter completely with 100% coverage, including case-insensitive substring matching and omission behavior. The description does not add further parameter-level detail, but the schema handles the burden, so the baseline score applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Search') and resource ('configured MikroTik router profiles'), and immediately orients the agent by naming the sibling tool it feeds ('the profile name that mikrotik_exec needs'). This makes it easy to distinguish from mikrotik_exec without inspecting schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly tells the agent to 'Call this first' and explains the purpose: discover the profile name required by mikrotik_exec. This is clear when-to-use guidance with a named alternative, which is sufficient given the single sibling tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
mikrotik_search_docsSearch the RouterOS referenceARead-only
Search bundled RouterOS reference material for command syntax, idioms and version differences. Covers RouterOS v6 and v7. Use this before composing an unfamiliar command, and especially before any write — it documents traps such as item numbers being unstable between calls. Offline and read-only; it does not touch any router.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum sections to return. Default 3. | |
| query | Yes | What you need to do, in keywords — e.g. 'firewall filter print', 'bgp peer v7', 'remove rule safely'. | |
| version | No | Limit results to sections that apply to this RouterOS major version. Read it from /system resource print. If omitted, a version named in the query itself (e.g. 'bgp peer v7') is used. |
Output Schema
| Name | Required | Description |
|---|---|---|
| results | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is covered structurally. The description goes beyond that by stating it is offline, touches no router, and that the material documents traps such as item numbers being unstable between calls — genuinely useful behavioral context for planning a write.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each earning its place: scope first, usage guidance second, safety/behavioral constraint last. No filler and the most decision-relevant information (what it searches) is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-value documentation is unnecessary here. Between the description (scope, version coverage, offline/read-only nature, trap-documenting purpose) and the fully described schema, an agent has everything needed to call this correctly before issuing a write via mikrotik_exec.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents query, limit (max 10, default 3) and the v6/v7 version enum, including reading version from '/system resource print'. The description adds only the general v6/v7 scope, which the schema's version field already conveys, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Search bundled RouterOS reference material') and scopes the content precisely (command syntax, idioms, version differences). The closing 'it does not touch any router' cleanly separates it from the sibling mikrotik_exec, so an agent can route correctly without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly prescribes when to use it: 'before composing an unfamiliar command, and especially before any write.' That is clear actionable context, and the read-only clause implies the alternative is mikrotik_exec. It stops short of 5 because it never names a sibling or states when this tool should be skipped in favor of one.
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 tool update
- Added
mikrotik_search_docs
2 tool updates
v0.1.0- First observed
mikrotik_exec - First observed
mikrotik_list_profiles
TDQS
Scored across 3 tools
Each tool has a clearly distinct role: mikrotik_exec runs commands against the router, mikrotik_search_docs searches offline documentation, and mikrotik_list_profiles discovers configured profiles. There is no overlap in purpose and the descriptions reinforce the intended call order (list_profiles → search_docs → exec).
All names share the mikrotik_ prefix and are snake_case, with search_docs and list_profiles following a clean verb_noun pattern. mikrotik_exec deviates slightly by using a bare verb with no noun object, but the convention is otherwise predictable.
Three tools is on the lean side, but this is intentional and well-scoped: a single generic exec tool subsumes arbitrary RouterOS commands, so no per-command tools are needed. Anything less (e.g. dropping the docs or profile helper) would hurt usability, so the count is justified.
The lifecycle is covered end to end: discover profiles, look up syntax, then execute (read or write) with read-only safety enforced. Minor gaps exist, such as no batch/multi-command execution or profile management, but agents can work around these using the generic exec tool.
Maintenance
Related MCP Connectors
- gatewayOAuthai.sealgate
MCP gateway with runtime security policy, tool-call-level control, and audit of agent actions.
Secure remote MCP for supported accounting workflows in authorized KROS companies.
Connect any AI agent to 1,000+ apps and 27,000+ actions through one remote MCP server (OAuth).
Compliance frameworks (SOC 2, ISO 27001, CMMC, NIST, more) delivered to AI agents as MCP tools.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables 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.3MIT
- AlicenseNot gradedqualityDmaintenanceMCP server that sends CLI commands to MikroTik RouterOS via SSH. It allows executing any RouterOS CLI command and getting text output back.1-
- FlicenseNot gradedqualityCmaintenanceEnables agents to access network device management interfaces via SSH, keeping credentials on the server side. Supports both interactive terminal sessions and command execution on devices like Huawei VRP, MikroTik, and OpenWrt.1-
- AlicenseNot gradedqualityAmaintenanceMCP 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.14 npm2Apache 2.0