@tikoci/rosetta 0.6.9 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- package/package.json +1 -1
- package/src/browse.ts +135 -15
- package/src/classify.test.ts +205 -0
- package/src/classify.ts +216 -0
- package/src/db.ts +100 -0
- package/src/mcp-http.test.ts +8 -7
- package/src/mcp.ts +49 -185
- package/src/query.test.ts +129 -0
- package/src/query.ts +427 -4
package/src/classify.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* classify.ts — Pre-search input classifier for `routeros_search` / TUI.
|
|
3
|
+
*
|
|
4
|
+
* Cheap regex-based detectors that run before any DB query. Each detector
|
|
5
|
+
* fires independently and non-exclusively: "bgp 7.22 route reflection" fires
|
|
6
|
+
* topic + version + general FTS together. `searchAll()` uses the output to
|
|
7
|
+
* route side queries in parallel (changelogs by version/category, device
|
|
8
|
+
* lookups, property resolution) without re-parsing the input.
|
|
9
|
+
*
|
|
10
|
+
* Pure module — no DB, no I/O. DB-backed resolution (property exists?
|
|
11
|
+
* device matches a real product?) happens in `searchAll()`.
|
|
12
|
+
*
|
|
13
|
+
* See DESIGN.md "North Star Architecture — Unified routeros_search".
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { canonicalize } from "./canonicalize.ts";
|
|
17
|
+
import { KNOWN_TOPICS } from "./query.ts";
|
|
18
|
+
|
|
19
|
+
export type CommandFragment = {
|
|
20
|
+
/** `key=value` pairs extracted from the input (e.g. `chain=forward`). */
|
|
21
|
+
pairs: Array<{ key: string; value: string }>;
|
|
22
|
+
/** RouterOS verbs mentioned in the input (e.g. `add`, `set`, `print`). */
|
|
23
|
+
verbs: string[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type QueryClassification = {
|
|
27
|
+
/** Raw input (unchanged). */
|
|
28
|
+
input: string;
|
|
29
|
+
/** RouterOS version tag if a `7.X[.Y][betaN|rcN]` pattern was found. First match wins. */
|
|
30
|
+
version?: string;
|
|
31
|
+
/** Known-topic tokens — matched against `KNOWN_TOPICS`. Capped at 5 to keep side queries cheap. */
|
|
32
|
+
topics: string[];
|
|
33
|
+
/** Canonical command path (e.g. `/ip/firewall/filter`) if the input looks path-ish. */
|
|
34
|
+
command_path?: string;
|
|
35
|
+
/** `key=value` pairs / verbs parsed from fragment-style input (`add chain=forward`). */
|
|
36
|
+
command_fragment?: CommandFragment;
|
|
37
|
+
/** Device model candidate (e.g. `RB1100AHx4`, `hAP`, `CCR2216`). DB resolution happens in searchAll. */
|
|
38
|
+
device?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Property-name candidate — single lowercase token that *might* be a property name.
|
|
41
|
+
* Only set when input is a single short token not matched by any other detector.
|
|
42
|
+
* searchAll must verify existence in `properties.name` before acting on it.
|
|
43
|
+
*/
|
|
44
|
+
property?: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Version pattern: `7.22`, `7.22.1`, `7.23beta2`, `7.22rc1`. No leading \b so `v7.22` still matches. */
|
|
48
|
+
const VERSION_RE = /7\.\d+(?:\.\d+)?(?:beta\d+|rc\d+)?/;
|
|
49
|
+
|
|
50
|
+
/** key=value pattern — captures `chain=forward`, `action=accept`, `ssid="My Net"` (unquoted only here). */
|
|
51
|
+
const FRAGMENT_RE = /([a-z][a-z0-9-]{0,30})=([^\s]+)/gi;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Device model patterns. Order matters — specific prefixes before generic.
|
|
55
|
+
* Captures the full model token so searchAll can pass it to searchDevices.
|
|
56
|
+
*/
|
|
57
|
+
const DEVICE_PATTERNS: RegExp[] = [
|
|
58
|
+
/\bRB\d+[A-Za-z0-9+-]*\b/, // RB450G, RB1100AHx4, RB5009UG+S+IN
|
|
59
|
+
/\bCCR\d+[A-Za-z0-9+-]*\b/, // CCR2216-1G-12XS-2XQ
|
|
60
|
+
/\bCRS\d+[A-Za-z0-9+-]*\b/, // CRS354-48G-4S+2Q+RM
|
|
61
|
+
/\bCSS\d+[A-Za-z0-9+-]*\b/, // CSS610-8P-2S+IN
|
|
62
|
+
/\bSXT\d*[A-Za-z0-9+-]*\b/, // SXTsq, SXT5
|
|
63
|
+
/\bLHG[A-Za-z0-9+-]*\b/i, // LHG, LHGG-60ad
|
|
64
|
+
/\bLtAP[A-Za-z0-9+-]*\b/i, // LtAP, LtAP mini
|
|
65
|
+
/\bh(?:EX|AP)[A-Za-z0-9+-]*\b/, // hEX, hEX S, hAP, hAP ax²
|
|
66
|
+
/\b[cwm]AP[A-Za-z0-9+-]*\b/, // cAP, wAP, mAP, cAP ac
|
|
67
|
+
/\bnetPower[A-Za-z0-9+-]*\b/i, // netPower 15FR
|
|
68
|
+
/\bnetMetal[A-Za-z0-9+-]*\b/i, // netMetal 5
|
|
69
|
+
/\bGroove[A-Za-z0-9+-]*\b/i, // Groove A-52HPn
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const VERB_TOKENS = new Set([
|
|
73
|
+
"add", "set", "print", "remove", "edit", "enable", "disable",
|
|
74
|
+
"export", "import", "find", "get", "reset", "move",
|
|
75
|
+
"monitor", "scan", "run", "start", "stop",
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
/** Single-word tokens that look like a potential property name. */
|
|
79
|
+
const PROPERTY_CANDIDATE_RE = /^[a-z][a-z0-9-]{2,30}$/;
|
|
80
|
+
|
|
81
|
+
const MAX_TOPICS = 5;
|
|
82
|
+
|
|
83
|
+
/** Tokenize input into lowercase alphanumeric words, preserving dashes. */
|
|
84
|
+
function tokenize(input: string): string[] {
|
|
85
|
+
return input
|
|
86
|
+
.toLowerCase()
|
|
87
|
+
.replace(/[^\w\s-]/g, " ")
|
|
88
|
+
.split(/\s+/)
|
|
89
|
+
.filter((t) => t.length > 0);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Detect RouterOS version tag. First match only — "compare 7.21 and 7.22" keeps `7.21`. */
|
|
93
|
+
function detectVersion(input: string): string | undefined {
|
|
94
|
+
const m = VERSION_RE.exec(input);
|
|
95
|
+
return m ? m[0] : undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Detect command path. Uses canonicalize.primaryPath for robust handling of `/ip address`, `ip/address`, etc. */
|
|
99
|
+
function detectCommandPath(input: string): string | undefined {
|
|
100
|
+
// Heuristic: only run the canonicalizer if the input contains a forward slash
|
|
101
|
+
// OR starts with a known top-level command word. Otherwise every "routing"
|
|
102
|
+
// query would be classified as a command path.
|
|
103
|
+
const trimmed = input.trim();
|
|
104
|
+
const hasSlash = trimmed.includes("/");
|
|
105
|
+
const firstToken = trimmed.split(/\s+/)[0]?.toLowerCase();
|
|
106
|
+
const topLevels = new Set(["ip", "ipv6", "interface", "routing", "system", "bridge", "container", "port", "radius", "snmp", "tool", "user", "queue", "certificate", "file", "log", "disk", "caps-man", "mpls", "ppp", "special-login", "app"]);
|
|
107
|
+
if (!hasSlash && (!firstToken || !topLevels.has(firstToken))) return undefined;
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
// Prefer the parsed command's dir path if a verb was recognized; otherwise
|
|
111
|
+
// fall back to finalPath (pure navigation like `/ip/firewall/filter`).
|
|
112
|
+
const { commands, finalPath } = canonicalize(trimmed, "/");
|
|
113
|
+
const primary = commands.find((c) => !c.subshell) ?? commands[0];
|
|
114
|
+
const path = primary?.path ?? finalPath;
|
|
115
|
+
if (!path || path === "/") return undefined;
|
|
116
|
+
return path;
|
|
117
|
+
} catch {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Extract `key=value` pairs and RouterOS verbs from fragment-style input. */
|
|
123
|
+
function detectCommandFragment(input: string): CommandFragment | undefined {
|
|
124
|
+
const pairs: Array<{ key: string; value: string }> = [];
|
|
125
|
+
FRAGMENT_RE.lastIndex = 0;
|
|
126
|
+
for (let m = FRAGMENT_RE.exec(input); m !== null; m = FRAGMENT_RE.exec(input)) {
|
|
127
|
+
pairs.push({ key: m[1].toLowerCase(), value: m[2] });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const verbs: string[] = [];
|
|
131
|
+
for (const tok of tokenize(input)) {
|
|
132
|
+
if (VERB_TOKENS.has(tok) && !verbs.includes(tok)) verbs.push(tok);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (pairs.length === 0 && verbs.length === 0) return undefined;
|
|
136
|
+
return { pairs, verbs };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Detect device model mention. Returns first match; searchDevices handles disambiguation. */
|
|
140
|
+
function detectDevice(input: string): string | undefined {
|
|
141
|
+
for (const pattern of DEVICE_PATTERNS) {
|
|
142
|
+
const m = pattern.exec(input);
|
|
143
|
+
if (m) return m[0];
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Match tokens against KNOWN_TOPICS. Deduplicated, capped, in first-seen order. */
|
|
149
|
+
function detectTopics(input: string): string[] {
|
|
150
|
+
const seen = new Set<string>();
|
|
151
|
+
const out: string[] = [];
|
|
152
|
+
for (const tok of tokenize(input)) {
|
|
153
|
+
if (KNOWN_TOPICS.has(tok) && !seen.has(tok)) {
|
|
154
|
+
seen.add(tok);
|
|
155
|
+
out.push(tok);
|
|
156
|
+
if (out.length >= MAX_TOPICS) break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Flag a property-name candidate: single token that looks like an identifier
|
|
164
|
+
* and isn't already classified as anything else. searchAll will verify against
|
|
165
|
+
* `properties.name`.
|
|
166
|
+
*/
|
|
167
|
+
function detectProperty(
|
|
168
|
+
input: string,
|
|
169
|
+
opts: { hasPath: boolean; hasDevice: boolean; hasFragment: boolean; topics: string[] },
|
|
170
|
+
): string | undefined {
|
|
171
|
+
const trimmed = input.trim();
|
|
172
|
+
// Only single-token inputs are property candidates — multi-word inputs go to FTS search.
|
|
173
|
+
if (!PROPERTY_CANDIDATE_RE.test(trimmed)) return undefined;
|
|
174
|
+
if (opts.hasPath || opts.hasDevice || opts.hasFragment) return undefined;
|
|
175
|
+
// If the token is itself a known topic, prefer that classification — topic routing beats property lookup.
|
|
176
|
+
if (opts.topics.includes(trimmed.toLowerCase())) return undefined;
|
|
177
|
+
return trimmed.toLowerCase();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Classify a user query. Detectors run independently; return object lists every
|
|
182
|
+
* signal found so `searchAll()` can route parallel side queries.
|
|
183
|
+
*/
|
|
184
|
+
export function classifyQuery(input: string): QueryClassification {
|
|
185
|
+
const normalized = input ?? "";
|
|
186
|
+
const result: QueryClassification = {
|
|
187
|
+
input: normalized,
|
|
188
|
+
topics: [],
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
if (!normalized.trim()) return result;
|
|
192
|
+
|
|
193
|
+
const version = detectVersion(normalized);
|
|
194
|
+
if (version) result.version = version;
|
|
195
|
+
|
|
196
|
+
const commandPath = detectCommandPath(normalized);
|
|
197
|
+
if (commandPath) result.command_path = commandPath;
|
|
198
|
+
|
|
199
|
+
const fragment = detectCommandFragment(normalized);
|
|
200
|
+
if (fragment) result.command_fragment = fragment;
|
|
201
|
+
|
|
202
|
+
const device = detectDevice(normalized);
|
|
203
|
+
if (device) result.device = device;
|
|
204
|
+
|
|
205
|
+
result.topics = detectTopics(normalized);
|
|
206
|
+
|
|
207
|
+
const property = detectProperty(normalized, {
|
|
208
|
+
hasPath: !!commandPath,
|
|
209
|
+
hasDevice: !!device,
|
|
210
|
+
hasFragment: !!fragment,
|
|
211
|
+
topics: result.topics,
|
|
212
|
+
});
|
|
213
|
+
if (property) result.property = property;
|
|
214
|
+
|
|
215
|
+
return result;
|
|
216
|
+
}
|
package/src/db.ts
CHANGED
|
@@ -656,6 +656,105 @@ export function initDb() {
|
|
|
656
656
|
);`);
|
|
657
657
|
|
|
658
658
|
db.run(`CREATE INDEX IF NOT EXISTS idx_skill_refs_skill ON skill_references(skill_id);`);
|
|
659
|
+
|
|
660
|
+
// -- Glossary (domain jargon resolution for RouterOS terms) --
|
|
661
|
+
|
|
662
|
+
db.run(`CREATE TABLE IF NOT EXISTS glossary (
|
|
663
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
664
|
+
term TEXT NOT NULL UNIQUE,
|
|
665
|
+
definition TEXT NOT NULL,
|
|
666
|
+
aliases TEXT,
|
|
667
|
+
category TEXT,
|
|
668
|
+
search_hint TEXT
|
|
669
|
+
);`);
|
|
670
|
+
|
|
671
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_glossary_term ON glossary(term);`);
|
|
672
|
+
|
|
673
|
+
seedGlossary();
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Seed the glossary table with RouterOS domain jargon. Idempotent — uses
|
|
678
|
+
* INSERT OR IGNORE so existing rows are preserved across schema reinits.
|
|
679
|
+
*
|
|
680
|
+
* Fields:
|
|
681
|
+
* term — canonical lookup key (lowercase)
|
|
682
|
+
* definition — brief one-line explanation
|
|
683
|
+
* aliases — comma-separated alternate spellings/abbreviations
|
|
684
|
+
* category — grouping: 'product', 'protocol', 'subsystem', 'concept'
|
|
685
|
+
* search_hint — query expansion term(s) for FTS — what to actually search for
|
|
686
|
+
*/
|
|
687
|
+
function seedGlossary() {
|
|
688
|
+
const entries: Array<[string, string, string | null, string, string]> = [
|
|
689
|
+
// Products & platforms
|
|
690
|
+
["chr", "Cloud Hosted Router — RouterOS VM image for hypervisors", "cloud hosted router", "product", "CHR cloud hosted router"],
|
|
691
|
+
["routerboard", "MikroTik hardware product line running RouterOS", "rb,routerboard", "product", "RouterBOARD hardware"],
|
|
692
|
+
["swos", "SwOS — switch operating system for CRS1xx/2xx and CSS series", "switchos,switch os", "product", "SwOS switch operating system"],
|
|
693
|
+
["winbox", "WinBox — native Windows/macOS GUI management tool for RouterOS", null, "product", "WinBox GUI management"],
|
|
694
|
+
["webfig", "WebFig — web-based management interface for RouterOS", null, "product", "WebFig web interface"],
|
|
695
|
+
["the dude", "The Dude — MikroTik network monitor (GUI retired, server remains in RouterOS)", "dude", "product", "Dude network monitor"],
|
|
696
|
+
["netinstall", "Tool for reinstalling RouterOS on MikroTik hardware via network boot", null, "product", "netinstall network boot"],
|
|
697
|
+
|
|
698
|
+
// Wireless
|
|
699
|
+
["capsman", "Controlled Access Point Manager — centralized wireless management", "caps man,caps,csman", "subsystem", "CAPsMAN wireless controller"],
|
|
700
|
+
["wifiwave2", "WiFi Wave2 driver — successor to legacy wireless, for newer hardware", "wave2,wifi-qcom", "subsystem", "WiFi Wave2 wireless"],
|
|
701
|
+
["w60g", "Wireless 60GHz — 802.11ad wireless bridge interface", "60ghz,wireless wire", "subsystem", "60GHz wireless bridge"],
|
|
702
|
+
|
|
703
|
+
// Firewall & filtering
|
|
704
|
+
["mangle", "Firewall mangle table — packet marking and header modification", null, "subsystem", "firewall mangle packet marking"],
|
|
705
|
+
["raw", "Firewall raw table — pre-connection-tracking filtering", null, "subsystem", "firewall raw pre-conntrack"],
|
|
706
|
+
["conntrack", "Connection tracking — stateful firewall session table", "connection tracking", "subsystem", "conntrack connection tracking"],
|
|
707
|
+
["fasttrack", "FastTrack — kernel-level connection fast path bypassing firewall rules", "fast track,fast-track", "concept", "fasttrack fast path"],
|
|
708
|
+
|
|
709
|
+
// Routing
|
|
710
|
+
["ospf", "OSPF — Open Shortest Path First routing protocol", null, "protocol", "OSPF routing"],
|
|
711
|
+
["bgp", "BGP — Border Gateway Protocol for inter-AS routing", null, "protocol", "BGP routing"],
|
|
712
|
+
["rpki", "RPKI — Resource Public Key Infrastructure for BGP route origin validation", null, "protocol", "RPKI route origin validation"],
|
|
713
|
+
["mpls", "MPLS — Multi-Protocol Label Switching", "ldp,vpls,rsvp-te", "protocol", "MPLS label switching"],
|
|
714
|
+
["vrf", "VRF — Virtual Routing and Forwarding (multiple routing tables)", null, "concept", "VRF virtual routing"],
|
|
715
|
+
|
|
716
|
+
// VPN & tunnels
|
|
717
|
+
["ipsec", "IPsec — IP Security protocol suite for encrypted tunnels", "ike,ike1,ike2", "protocol", "IPsec IKE tunnel encryption"],
|
|
718
|
+
["wireguard", "WireGuard — modern VPN tunnel protocol", "wg", "protocol", "WireGuard VPN tunnel"],
|
|
719
|
+
["sstp", "SSTP — Secure Socket Tunneling Protocol (Microsoft VPN)", null, "protocol", "SSTP tunnel"],
|
|
720
|
+
["l2tp", "L2TP — Layer 2 Tunneling Protocol, often paired with IPsec", null, "protocol", "L2TP tunnel"],
|
|
721
|
+
["ovpn", "OpenVPN — SSL/TLS VPN tunnel implementation in RouterOS", "openvpn", "protocol", "OpenVPN tunnel"],
|
|
722
|
+
["vxlan", "VXLAN — Virtual Extensible LAN overlay network", null, "protocol", "VXLAN overlay"],
|
|
723
|
+
["gre", "GRE — Generic Routing Encapsulation tunnel", null, "protocol", "GRE tunnel"],
|
|
724
|
+
["eoip", "EoIP — Ethernet over IP tunnel (MikroTik proprietary)", null, "protocol", "EoIP Ethernet tunnel"],
|
|
725
|
+
|
|
726
|
+
// Bridging & switching
|
|
727
|
+
["l3hw", "L3 Hardware Offloading — hardware-accelerated IP routing on supported switches", "l3 hardware,hw offload", "concept", "L3 hardware offloading"],
|
|
728
|
+
["vlan", "VLAN — Virtual LAN (802.1Q tagging)", "802.1q", "concept", "VLAN tagging"],
|
|
729
|
+
["dot1x", "802.1X — port-based network access control", "802.1x,port authentication", "protocol", "dot1x port authentication"],
|
|
730
|
+
["mlag", "MLAG — Multi-Chassis Link Aggregation", null, "protocol", "MLAG multi-chassis"],
|
|
731
|
+
["macsec", "MACsec — 802.1AE Media Access Control Security", "802.1ae", "protocol", "MACsec layer2 encryption"],
|
|
732
|
+
|
|
733
|
+
// Services
|
|
734
|
+
["user-manager", "User Manager — RADIUS server and hotspot user management built into RouterOS", "userman", "subsystem", "User Manager RADIUS"],
|
|
735
|
+
["hotspot", "Hotspot — captive portal with authentication and walled garden", "captive portal", "subsystem", "Hotspot captive portal"],
|
|
736
|
+
["mqtt", "MQTT — IoT messaging protocol client in RouterOS", null, "protocol", "MQTT IoT messaging"],
|
|
737
|
+
["tr069", "TR-069 — CPE WAN Management Protocol for remote device management", "cwmp,acs", "protocol", "TR-069 remote management"],
|
|
738
|
+
["snmp", "SNMP — Simple Network Management Protocol for monitoring", null, "protocol", "SNMP monitoring"],
|
|
739
|
+
["romon", "RoMON — Router Management Overlay Network for out-of-band management", null, "subsystem", "RoMON management overlay"],
|
|
740
|
+
["zerotier", "ZeroTier — peer-to-peer VPN overlay (extra package)", null, "protocol", "ZeroTier peer VPN"],
|
|
741
|
+
|
|
742
|
+
// System concepts
|
|
743
|
+
["npk", "NPK — RouterOS package file format (.npk)", "package", "concept", "NPK package file"],
|
|
744
|
+
["supout", "Support output file — diagnostic bundle generated by /system/sup-output", "support output,sup-output", "concept", "supout support output diagnostics"],
|
|
745
|
+
["defconf", "Default configuration — factory-reset configuration script", "default config", "concept", "default configuration"],
|
|
746
|
+
["container", "OCI container support in RouterOS (extra package, 7.4+)", "docker", "subsystem", "container Docker OCI"],
|
|
747
|
+
["lora", "LoRa — Long Range IoT radio support (extra package)", "lorawan", "subsystem", "LoRa IoT radio"],
|
|
748
|
+
["poe", "PoE — Power over Ethernet (in/out)", "power over ethernet", "concept", "PoE power ethernet"],
|
|
749
|
+
["etherboot", "Etherboot — network boot mode for RouterOS reinstallation via netinstall", "net boot", "concept", "etherboot network boot netinstall"],
|
|
750
|
+
];
|
|
751
|
+
|
|
752
|
+
const stmt = db.prepare(
|
|
753
|
+
"INSERT OR IGNORE INTO glossary (term, definition, aliases, category, search_hint) VALUES (?, ?, ?, ?, ?)",
|
|
754
|
+
);
|
|
755
|
+
for (const [term, definition, aliases, category, search_hint] of entries) {
|
|
756
|
+
stmt.run(term, definition, aliases, category, search_hint);
|
|
757
|
+
}
|
|
659
758
|
}
|
|
660
759
|
|
|
661
760
|
/**
|
|
@@ -707,6 +806,7 @@ export function getDbStats() {
|
|
|
707
806
|
dude_images: count("SELECT COUNT(*) AS c FROM dude_images"),
|
|
708
807
|
skills: count("SELECT COUNT(*) AS c FROM skills"),
|
|
709
808
|
skill_references: count("SELECT COUNT(*) AS c FROM skill_references"),
|
|
809
|
+
glossary: count("SELECT COUNT(*) AS c FROM glossary"),
|
|
710
810
|
schema_nodes: count("SELECT COUNT(*) AS c FROM schema_nodes"),
|
|
711
811
|
schema_node_presence: count("SELECT COUNT(*) AS c FROM schema_node_presence"),
|
|
712
812
|
...(() => {
|
package/src/mcp-http.test.ts
CHANGED
|
@@ -317,7 +317,7 @@ describe("HTTP transport: session lifecycle", () => {
|
|
|
317
317
|
expect(serverInfo.name).toBe("rosetta");
|
|
318
318
|
});
|
|
319
319
|
|
|
320
|
-
test("tools/list returns all
|
|
320
|
+
test("tools/list returns all 13 tools after initialization", async () => {
|
|
321
321
|
const { sessionId } = await mcpInitialize(server.url);
|
|
322
322
|
|
|
323
323
|
// Send initialized notification first (required by protocol)
|
|
@@ -328,20 +328,21 @@ describe("HTTP transport: session lifecycle", () => {
|
|
|
328
328
|
|
|
329
329
|
const result = (messages[0] as Record<string, unknown>).result as Record<string, unknown>;
|
|
330
330
|
const tools = result.tools as Array<{ name: string }>;
|
|
331
|
-
expect(tools.length).toBe(
|
|
331
|
+
expect(tools.length).toBe(13);
|
|
332
332
|
|
|
333
333
|
const toolNames = tools.map((t) => t.name).sort();
|
|
334
334
|
expect(toolNames).toContain("routeros_search");
|
|
335
335
|
expect(toolNames).toContain("routeros_get_page");
|
|
336
336
|
expect(toolNames).toContain("routeros_lookup_property");
|
|
337
|
-
expect(toolNames).toContain("routeros_search_properties");
|
|
338
337
|
expect(toolNames).toContain("routeros_command_tree");
|
|
339
|
-
expect(toolNames).toContain("routeros_search_callouts");
|
|
340
338
|
expect(toolNames).toContain("routeros_search_changelogs");
|
|
341
339
|
expect(toolNames).toContain("routeros_command_version_check");
|
|
342
340
|
expect(toolNames).toContain("routeros_device_lookup");
|
|
343
341
|
expect(toolNames).toContain("routeros_stats");
|
|
344
342
|
expect(toolNames).toContain("routeros_current_versions");
|
|
343
|
+
// Dropped: routeros_search_callouts, routeros_search_videos (folded into routeros_search.related)
|
|
344
|
+
expect(toolNames).not.toContain("routeros_search_callouts");
|
|
345
|
+
expect(toolNames).not.toContain("routeros_search_videos");
|
|
345
346
|
});
|
|
346
347
|
|
|
347
348
|
test("resources/list returns dataset resources after initialization", async () => {
|
|
@@ -549,8 +550,8 @@ describe("HTTP transport: multi-session", () => {
|
|
|
549
550
|
const tools1 = ((msgs1[0] as Record<string, unknown>).result as Record<string, unknown>).tools as unknown[];
|
|
550
551
|
const tools2 = ((msgs2[0] as Record<string, unknown>).result as Record<string, unknown>).tools as unknown[];
|
|
551
552
|
|
|
552
|
-
expect(tools1.length).toBe(
|
|
553
|
-
expect(tools2.length).toBe(
|
|
553
|
+
expect(tools1.length).toBe(13);
|
|
554
|
+
expect(tools2.length).toBe(13);
|
|
554
555
|
});
|
|
555
556
|
|
|
556
557
|
test("deleting one session does not affect another", async () => {
|
|
@@ -572,7 +573,7 @@ describe("HTTP transport: multi-session", () => {
|
|
|
572
573
|
// Client2 still works
|
|
573
574
|
const msgs = await mcpRequest(server.url, client2.sessionId, "tools/list", 2);
|
|
574
575
|
const tools = ((msgs[0] as Record<string, unknown>).result as Record<string, unknown>).tools as unknown[];
|
|
575
|
-
expect(tools.length).toBe(
|
|
576
|
+
expect(tools.length).toBe(13);
|
|
576
577
|
|
|
577
578
|
// Client1 is gone
|
|
578
579
|
const resp = await fetch(server.url, {
|