Background
This topic recently arose from a commentary between @Kentzo and me in the 7.20beta thread concerning an âLSPâ. While many folks may not know what an LSP is⌠it is an API that most modern text editors use (like VSCode) to do, among other things, syntax checking, and coloring syntax. See https://lang-server.org for more info.
While there are RouterOS code editor plugins, including @Kentzoâs, these all use regular expressions. This worksâŚbut using regex is not going to 100% accurate. And an LSP can do WAY more things with script/config⌠depending on how LSP is implemented. For example, LSP can do stuff like âGo to definitionâ to find where variables are declared, and more (see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#languageFeatures for possible operations)
Although Iâve thought about it⌠I donât have time to actually develop a RouterOS LSP - the initial âplumbingâ to create an LSP project framework is more work than I want to take on⌠But there are plenty of videos on YouTube on how to create an LSP from scratch & imagine LLMs be pretty good at JavaScript and LSP â since 10000000x more common than RouterOS syntax. Plus some RouterOS LSP likely be a useful step to anyone attempting to develop an MCP for AI agents.
So did want to expand on HOW ONE COULD develop a RouterOS LSP using the undocumented /console/inspect request= commands that are built to RouterOS (and thus available via CLI/SSH/REST/API), so that part is possible if one wanted to create a RouterOS LSP. The only downside of using /console/inspect is a future RouterOS LSP needs to use REST and connect to an online router to work, while most LSP work âofflineâ since LSP are typically embedded/included with a compiler/interpreter. Now the nice part of using /console/inspect as source of LSP code editor is that it be 100% accurate at highlighting errors specific to the version/packages on the âLSP connectedâ RouterOS device.
Specifically, /console/inspect takes a ârequestâ parameter. This is similar to LSP model where code editor makes requests, and the LSP responds with the metadata for the LSP request. RouterOS âinspectâ supports a few ârequestsâ that help in an LSP. Specifically:
/console/inspect request=<tab>
child completion error highlight self syntax
Of main interest to a future LSP are ârequest=highlightâ and ârequest=completionâ. While there are dozens of things an LSP can provide to a code editor about a language like RouterOS config/script, âhighlightâ and âcompletionâ map directly into the LSP APIs.
So request=highlight input=<script/config/line> corresponds with the âDocument Highlightâ LSP API. RouterOS provides the following data which can âfeedâ the LSP:
:put [:serialize to=json [/console/inspect request=highlight input="/ip/address/add interface=ether1 address=1.1.1.1/24" as-value ]]
{"highlight":["dir","dir","dir","dir","dir","dir","dir","dir","dir","dir","dir","dir","cmd","cmd","cmd","none","arg","arg","arg","arg","arg","arg","arg","arg","arg","syntax-meta","none","none","none","none","none","none","none","arg","arg","arg","arg","arg","arg","arg","syntax-meta","none","none","none","none","none","none","none","syntax-meta","none","none"],"type":"highlight"}
and via REST it already be JSON.
Each of the elements in the highlight array corresponds to each character in the input= so the above was for the command:
/ip/address/add interface=ether1 address=1.1.1.1/24
# ddddddddddddCCC-aaaaaaaaaM-------aaaaaaaM-------M--
The values returned from request=highlight mostly match what /terminal/style so it provides EXACTLY the same colors as RouterOS UIs. So some possible values for highlightâs array are:
/terminal/style <tab>
ambiguous comment error escaped none syntax-meta syntax-noterm syntax-old syntax-val varname varname-global varname-local style
And there is also one/most additional ones. Specifically âobj-inactiveâ and âsyntax-obsoleteâ that I know. Here are some examples of invalid commands provided:
:put [:serialize to=json [/console/inspect request=highlight input="/ip/address bad-param" as-value ]]
{"highlight":["dir","dir","dir","dir","dir","dir","dir","dir","dir","dir","dir","none","obj-inactive","obj-inactive","obj-inactive","obj-inactive","obj-inactive","obj-inactive","obj-inactive","obj-inactive","obj-inactive"],"type":"highlight"}
:put [:serialize to=json [/console/inspect request=highlight input="/ip/address add bad-param" as-value ]]
{"highlight":["dir","dir","dir","dir","dir","dir","dir","dir","dir","dir","dir","none","cmd","cmd","cmd","none","error","none","none","none","none","none","none","none","none"],"type":"highlight"}
:put [:serialize to=json [/console/inspect request=highlight input="/ip/address add address " as-value ]]
{"highlight":["dir","dir","dir","dir","dir","dir","dir","dir","dir","dir","dir","none","cmd","cmd","cmd","none","arg","arg","arg","arg","arg","arg","arg","syntax-obsolete"],"type":"highlight"}
:put [:serialize to=json [/console/inspect request=highlight input="/ip/address add address=" as-value ]]
{"highlight":["dir","dir","dir","dir","dir","dir","dir","dir","dir","dir","dir","none","cmd","cmd","cmd","none","arg","arg","arg","arg","arg","arg","arg","syntax-meta"],"type":"highlight"}
:put [:serialize to=json [/console/inspect request=highlight input="/ip/address add address2=" as-value ]]
{"highlight"":["dir","dir","dir","dir","dir","dir","dir","dir","dir","dir","dir","none","cmd","cmd","cmd","none","error","none","none","none","none","none","none","none","none"],"type":"highlight"}
And same request=highlight can be used to VALIDATE RouterOS config since CRITICALLY an element with âerrorâ or âobj-inactiveâ in highlightâs array means the script will NOT work and has, well, a syntax error (at the array index where âerrorâ/âobj-inactiveâ was found in the array matches the char position of the command/script/config provided in inspectâs input= attributed used to get the highlights).
ârequest=highlightâ supports multi-line input, so you can check an entire script with newlines/spaces/etc & look for the âerror statesâ in results from â/console/inspect request=highlight input=<script/config/line>â were found. If none were found, then the syntax is at least valid (although may not work).
The other /console/inspect thing thatâs useful is request=completion. Similarly, it takes an input= with the script/config to test. With the LSP API having a similar âText Document Completionâ capability, so if editor requests completion, RouterOS can make SAME suggestion as in CLI does - but in any LSP support editor. See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion. This one is more complex, and IDK exactly the meaning of all the field, although, âpreferenceâ/âshowâ likely used to determine if something merely valid/possible syntax, or if should be used in completion.
For example, request=completion returns stuff like:
/console/inspect request=completion input="/ip/address/add interface=ether1 address=1.1.1.1/24"
Columns: TYPE, COMPLETION, STYLE, OFFSET, PREFERENCE, SHOW, TEXT
TYPE COMPLETION STYLE OFFSET PREFERENCE SHOW TEXT
completion <value> none 49 -1 no literal value that consists only of digits, letters and characters -.,:<>/|+_*&^%#@!~
completion none 51 80 no whitespace
completion ; syntax-meta 51 40 no end of command
/console/inspect request=completion input="/ip/address/add interface=ether1 address=1.1.1.1/24c"
Columns: TYPE, COMPLETION, STYLE, OFFSET, PREFERENCE, SHOW, TEXT
TYPE COMPLETION STYLE OFFSET PREFERENCE SHOW TEXT
completion <value> none 49 -1 no literal value that consists only of digits, letters and characters -.,:<>/|+_*&^%#@!~
completion none 52 80 no whitespace
completion ; syntax-meta 52 40 no end of command
# and adding a <space> at end of input= does show the attributes that are possible
/console/inspect request=completion input="/ip/address/add interface=ether1 address=1.1.1.1/24 "
Columns: TYPE, COMPLETION, STYLE, OFFSET, PREFERENCE, SHOW, TEXT
TYPE COMPLETION STYLE OFFSET PREFERENCE SHOW TEXT
completion ! none 52 80 no whitespace
completion broadcast arg 52 95 yes Broadcast address
completion comment arg 52 96 yes Short description of the item
completion copy-from arg 52 96 yes Item number
completion disabled arg 52 96 yes Defines whether item is ignored or used
completion netmask arg 52 95 yes Network mask
completion network arg 52 96 yes Network prefix
completion ; syntax-meta 52 40 no end of command
While perhaps other /console/inspect things might be useful to a future LSP, the above would get someone pretty far in creating an LSP for RouterOS. And, my existing tikoci/restraml project on GitHub make HEAVY use of request=child (to determine the AST/âcommand treeâ) and request=syntax (which, sometimes, has help or the valid values of an attribute) to generate RouterOS RAML/OpenAPI schema. For example, request=syntax using similar command as above tells you valid values for LAST attribute in input= (so address=):
/console/inspect request=syntax input="/ip/address/add interface=ether1 address=1.1.1.1/24"
Columns: TYPE, SYMBOL, SYMBOL-TYPE, NESTED, NONORM, TEXT
TYPE SYMBOL SYMBOL-TYPE NESTED NONORM TEXT
syntax Netmask definition 0 no IpNetmask | Num
syntax IpNetmask definition 1 no A.B.C.D
syntax Num definition 1 no 0..32 (integer number)
but this get more complex to âmapâ to the LSP API IMO. Plus â/console/inspect request=syntaxâ data is NOT actually complete as to âvalid valuesâ (shown in text= value). And there is a path= option to request=syntax, that isnât so easy to map from LSP code⌠but it does have what F1 help would return:
/console/inspect request=syntax path=ip,address,add
Columns: TYPE, SYMBOL, SYMBOL-TYPE, NESTED, NONORM, TEXT
TYPE SYMBOL SYMBOL-TYPE NESTED NONORM TEXT
syntax collection 0 yes
syntax address explanation 1 no Local IP address
syntax broadcast explanation 1 no Broadcast address
syntax comment explanation 1 no Short description of the item
syntax copy-from explanation 1 no Item number
syntax disabled explanation 1 no Defines whether item is ignored or used
syntax interface explanation 1 no Interface name
syntax netmask explanation 1 no Network mask
syntax network explanation 1 no Network prefix
Anyway, thought Iâd share what I know since the topic came up & all of this is undocumented and potentially underutilized.