@duckmind/dm-windows-x64 0.63.4 → 0.63.9
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/dm.exe +0 -0
- package/extensions/.dm-extensions.json +236 -13
- package/extensions/dm-context/package.json +1 -1
- package/extensions/dm-context/skills/context-management/SKILL.md +173 -223
- package/extensions/dm-context/skills/context-management/references/development-and-troubleshooting.md +75 -0
- package/extensions/dm-context/skills/context-management/references/interleaved-async-work.md +143 -0
- package/extensions/dm-context/skills/context-management/references/planning-and-execution.md +81 -0
- package/extensions/dm-context/skills/context-management/references/repeated-items-and-batch-work.md +69 -0
- package/extensions/dm-context/skills/context-management/references/retry-branch-and-pivot.md +80 -0
- package/extensions/dm-context/skills/context-management/references/search-research-and-reading.md +103 -0
- package/extensions/dm-context/skills/context-management/references/task-switching-and-cleanup.md +73 -0
- package/extensions/dm-context/src/context.js +3 -2
- package/extensions/dm-context/src/index.js +168 -84
- package/extensions/dm-mcp-adapter/OAUTH.md +3 -0
- package/extensions/dm-mcp-adapter/THIRD_PARTY_NOTICES.md +4 -0
- package/extensions/dm-mcp-adapter/abort.js +35 -0
- package/extensions/dm-mcp-adapter/agent-dir.js +45 -0
- package/extensions/dm-mcp-adapter/agent-plugin-loader.js +349 -0
- package/extensions/dm-mcp-adapter/app-bridge.bundle.js +5843 -0
- package/extensions/dm-mcp-adapter/cli.js +354 -0
- package/extensions/dm-mcp-adapter/commands.js +619 -0
- package/extensions/dm-mcp-adapter/config.js +1060 -0
- package/extensions/dm-mcp-adapter/consent-manager.js +60 -0
- package/extensions/dm-mcp-adapter/direct-tools.js +544 -0
- package/extensions/dm-mcp-adapter/dist/agent-dir.js +45 -0
- package/extensions/dm-mcp-adapter/dist/agent-plugin-loader.js +349 -0
- package/extensions/dm-mcp-adapter/dist/config.js +1060 -0
- package/extensions/dm-mcp-adapter/dist/mcp-bearer-store.js +311 -0
- package/extensions/dm-mcp-adapter/dist/metadata-cache.js +285 -0
- package/extensions/dm-mcp-adapter/dist/package-mcp-loader.js +144 -0
- package/extensions/dm-mcp-adapter/dist/resource-tools.js +7 -0
- package/extensions/dm-mcp-adapter/dist/types.js +247 -0
- package/extensions/dm-mcp-adapter/dist/ui-app-bridge-helpers.js +36 -0
- package/extensions/dm-mcp-adapter/dist/ui-stream-types.js +58 -0
- package/extensions/dm-mcp-adapter/dist/ui-tool-visibility.js +26 -0
- package/extensions/dm-mcp-adapter/dist/utils.js +307 -0
- package/extensions/dm-mcp-adapter/elicitation-handler.js +306 -0
- package/extensions/dm-mcp-adapter/error-signal.js +16 -0
- package/extensions/dm-mcp-adapter/errors.js +225 -0
- package/extensions/dm-mcp-adapter/failure-backoff.js +19 -0
- package/extensions/dm-mcp-adapter/glimpse-ui.js +61 -0
- package/extensions/dm-mcp-adapter/host-html-template.js +464 -0
- package/extensions/dm-mcp-adapter/index.js +1079 -0
- package/extensions/dm-mcp-adapter/init.js +624 -0
- package/extensions/dm-mcp-adapter/json-schema-validator.js +53 -0
- package/extensions/dm-mcp-adapter/lifecycle.js +385 -0
- package/extensions/dm-mcp-adapter/logger.js +115 -0
- package/extensions/dm-mcp-adapter/mcp-auth-flow.js +824 -0
- package/extensions/dm-mcp-adapter/mcp-auth.js +732 -0
- package/extensions/dm-mcp-adapter/mcp-bearer-store.js +311 -0
- package/extensions/dm-mcp-adapter/mcp-callback-server.js +388 -0
- package/extensions/dm-mcp-adapter/mcp-code.js +291 -0
- package/extensions/dm-mcp-adapter/mcp-keyring-helper.cjs +88 -0
- package/extensions/dm-mcp-adapter/mcp-oauth-provider.js +490 -0
- package/extensions/dm-mcp-adapter/mcp-output-guard.js +386 -0
- package/extensions/dm-mcp-adapter/mcp-panel.js +951 -0
- package/extensions/dm-mcp-adapter/mcp-probe.js +151 -0
- package/extensions/dm-mcp-adapter/mcp-references.js +263 -0
- package/extensions/dm-mcp-adapter/mcp-script-worker.mjs +126 -0
- package/extensions/dm-mcp-adapter/mcp-setup-panel.js +578 -0
- package/extensions/dm-mcp-adapter/mcp-status.js +80 -0
- package/extensions/dm-mcp-adapter/mcp-trace.js +215 -0
- package/extensions/dm-mcp-adapter/metadata-cache.js +292 -0
- package/extensions/dm-mcp-adapter/namespace-tools.js +155 -0
- package/extensions/dm-mcp-adapter/npx-resolver.js +465 -0
- package/extensions/dm-mcp-adapter/oauth-handler.js +17 -0
- package/extensions/dm-mcp-adapter/oauth.js +17 -0
- package/extensions/dm-mcp-adapter/onboarding-state.js +62 -0
- package/extensions/dm-mcp-adapter/package-mcp-loader.js +144 -0
- package/extensions/dm-mcp-adapter/package.json +68 -0
- package/extensions/dm-mcp-adapter/panel-keys.js +26 -0
- package/extensions/dm-mcp-adapter/prompts.js +258 -0
- package/extensions/dm-mcp-adapter/proxy-modes.js +1336 -0
- package/extensions/dm-mcp-adapter/request-headers-command.js +293 -0
- package/extensions/dm-mcp-adapter/resource-tools.js +7 -0
- package/extensions/dm-mcp-adapter/runtime-owner.js +79 -0
- package/extensions/dm-mcp-adapter/sampling-handler.js +222 -0
- package/extensions/dm-mcp-adapter/sandbox-proxy-template.js +190 -0
- package/extensions/dm-mcp-adapter/search-ranking.js +198 -0
- package/extensions/dm-mcp-adapter/server-manager.js +1258 -0
- package/extensions/dm-mcp-adapter/session-recovery.js +82 -0
- package/extensions/dm-mcp-adapter/skills/mcp-scripting/SKILL.md +39 -0
- package/extensions/dm-mcp-adapter/state.js +0 -0
- package/extensions/dm-mcp-adapter/test-runner.mjs +18 -0
- package/extensions/dm-mcp-adapter/tool-approval.js +151 -0
- package/extensions/dm-mcp-adapter/tool-metadata.js +247 -0
- package/extensions/dm-mcp-adapter/tool-registrar.js +212 -0
- package/extensions/dm-mcp-adapter/tool-result-renderer.js +355 -0
- package/extensions/dm-mcp-adapter/ts-shape.js +157 -0
- package/extensions/dm-mcp-adapter/types.js +264 -0
- package/extensions/dm-mcp-adapter/ui-app-bridge-helpers.js +36 -0
- package/extensions/dm-mcp-adapter/ui-resource-handler.js +209 -0
- package/extensions/dm-mcp-adapter/ui-server.js +815 -0
- package/extensions/dm-mcp-adapter/ui-session.js +455 -0
- package/extensions/dm-mcp-adapter/ui-stream-types.js +58 -0
- package/extensions/dm-mcp-adapter/ui-tool-visibility.js +26 -0
- package/extensions/dm-mcp-adapter/unix-socket-transport.js +83 -0
- package/extensions/dm-mcp-adapter/utils.js +307 -0
- package/extensions/dm-skills-manager/THIRD_PARTY_NOTICES.md +27 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/components.js +265 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/constants.js +31 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/creation-fallback.js +20 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/creation.js +145 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/dialog.js +738 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/dm-ai-compat.js +15 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/format.js +125 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/glyphs.js +142 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/layout.js +93 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/paths.js +76 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/registry.js +95 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/settings.js +93 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/startup.js +32 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/toggle.js +54 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/types.js +14 -0
- package/extensions/dm-skills-manager/extensions/skills-manager/ui.js +121 -0
- package/extensions/dm-skills-manager/extensions/skills-manager.js +111 -0
- package/extensions/dm-skills-manager/package.json +121 -0
- package/package.json +13 -1
- package/extensions/dm-localllm-provider/detect.test.js +0 -1051
- package/extensions/dm-localllm-provider/index.test.js +0 -149
- package/extensions/dm-localllm-provider/keychain.test.js +0 -61
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
export const SANDBOX_PROXY_SANDBOX = "allow-scripts allow-forms allow-modals allow-popups allow-downloads allow-same-origin";
|
|
2
|
+
export const SANDBOX_INNER_SANDBOX = SANDBOX_PROXY_SANDBOX;
|
|
3
|
+
export const SANDBOX_PROXY_PATH = "/sandbox";
|
|
4
|
+
export function buildSandboxProxyHtml(input) {
|
|
5
|
+
const parentOrigin = safeInlineJSON(input.parentOrigin);
|
|
6
|
+
return `<!doctype html>
|
|
7
|
+
<html lang="en">
|
|
8
|
+
<head>
|
|
9
|
+
<meta charset="utf-8" />
|
|
10
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
11
|
+
<title>MCP App Sandbox</title>
|
|
12
|
+
<style>
|
|
13
|
+
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: transparent; }
|
|
14
|
+
iframe { display: block; width: 100%; height: 100%; border: 0; }
|
|
15
|
+
</style>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<iframe id="mcp-app" title="MCP App" sandbox="${SANDBOX_INNER_SANDBOX}" referrerpolicy="no-referrer"></iframe>
|
|
19
|
+
<script>
|
|
20
|
+
const EXPECTED_PARENT_ORIGIN = ${parentOrigin};
|
|
21
|
+
const SANDBOX_PROXY_READY_METHOD = "ui/notifications/sandbox-proxy-ready";
|
|
22
|
+
const SANDBOX_RESOURCE_READY_METHOD = "ui/notifications/sandbox-resource-ready";
|
|
23
|
+
const INNER_SANDBOX = ${safeInlineJSON(SANDBOX_INNER_SANDBOX)};
|
|
24
|
+
const MAX_PENDING_MESSAGES = 64;
|
|
25
|
+
const innerFrame = document.getElementById("mcp-app");
|
|
26
|
+
const pendingToInner = [];
|
|
27
|
+
let innerReady = false;
|
|
28
|
+
|
|
29
|
+
const isObject = (value) => value !== null && typeof value === "object";
|
|
30
|
+
const isMessageFromParent = (event) =>
|
|
31
|
+
event.source === window.parent && event.origin === EXPECTED_PARENT_ORIGIN;
|
|
32
|
+
const isMessageFromInner = (event) =>
|
|
33
|
+
event.source === innerFrame.contentWindow && event.origin === window.location.origin;
|
|
34
|
+
|
|
35
|
+
const postToParent = (data) => {
|
|
36
|
+
window.parent.postMessage(data, EXPECTED_PARENT_ORIGIN);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const postToInner = (data) => {
|
|
40
|
+
if (!innerReady || !innerFrame.contentWindow) {
|
|
41
|
+
if (pendingToInner.length < MAX_PENDING_MESSAGES) pendingToInner.push(data);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
innerFrame.contentWindow.postMessage(data, window.location.origin);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const flushPendingMessages = () => {
|
|
48
|
+
if (!innerFrame.contentWindow) return;
|
|
49
|
+
innerReady = true;
|
|
50
|
+
for (const data of pendingToInner.splice(0)) {
|
|
51
|
+
innerFrame.contentWindow.postMessage(data, window.location.origin);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const sanitizeDomains = (domains) => {
|
|
56
|
+
if (!Array.isArray(domains)) return [];
|
|
57
|
+
return [...new Set(domains.filter((domain) =>
|
|
58
|
+
typeof domain === "string" &&
|
|
59
|
+
domain.length > 0 &&
|
|
60
|
+
/^[\\x21-\\x7E]+$/.test(domain) &&
|
|
61
|
+
!/[;'"]/.test(domain),
|
|
62
|
+
))];
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const toDirective = (name, trustedSources, domains) =>
|
|
66
|
+
name + " " + [...new Set([...trustedSources, ...domains])].join(" ");
|
|
67
|
+
|
|
68
|
+
const buildResourceCsp = (csp) => {
|
|
69
|
+
const resourceDomains = sanitizeDomains(csp?.resourceDomains);
|
|
70
|
+
const connectDomains = sanitizeDomains(csp?.connectDomains);
|
|
71
|
+
const frameDomains = sanitizeDomains(csp?.frameDomains);
|
|
72
|
+
const baseUriDomains = sanitizeDomains(csp?.baseUriDomains);
|
|
73
|
+
return [
|
|
74
|
+
"default-src 'none'",
|
|
75
|
+
"sandbox " + INNER_SANDBOX,
|
|
76
|
+
toDirective("script-src", ["'self'", "'unsafe-inline'"], resourceDomains),
|
|
77
|
+
toDirective("style-src", ["'self'", "'unsafe-inline'"], resourceDomains),
|
|
78
|
+
toDirective("font-src", ["'self'"], resourceDomains),
|
|
79
|
+
toDirective("img-src", ["'self'", "data:"], resourceDomains),
|
|
80
|
+
toDirective("media-src", ["'self'", "data:"], resourceDomains),
|
|
81
|
+
connectDomains.length > 0 ? "connect-src " + connectDomains.join(" ") : "connect-src 'none'",
|
|
82
|
+
frameDomains.length > 0 ? "frame-src " + frameDomains.join(" ") : "frame-src 'none'",
|
|
83
|
+
"worker-src 'none'",
|
|
84
|
+
"object-src 'none'",
|
|
85
|
+
baseUriDomains.length > 0 ? "base-uri " + baseUriDomains.join(" ") : "base-uri 'self'",
|
|
86
|
+
].join("; ");
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const escapeAttribute = (value) => value
|
|
90
|
+
.replace(/&/g, "&")
|
|
91
|
+
.replace(/"/g, """)
|
|
92
|
+
.replace(/</g, "<")
|
|
93
|
+
.replace(/>/g, ">");
|
|
94
|
+
|
|
95
|
+
const injectCsp = (html, csp) => {
|
|
96
|
+
const meta = '<meta http-equiv="Content-Security-Policy" content="' + escapeAttribute(csp) + '">';
|
|
97
|
+
const head = html.match(/<head(?:\\s[^>]*)?>/i);
|
|
98
|
+
if (head && head.index !== undefined) {
|
|
99
|
+
const end = head.index + head[0].length;
|
|
100
|
+
return html.slice(0, end) + meta + html.slice(end);
|
|
101
|
+
}
|
|
102
|
+
return meta + html;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const safeSandbox = (requested) => {
|
|
106
|
+
const requestedTokens = typeof requested === "string" ? requested.split(/\\s+/) : [];
|
|
107
|
+
const allowedTokens = new Set(INNER_SANDBOX.split(" "));
|
|
108
|
+
const tokens = requestedTokens.filter((token) => allowedTokens.has(token));
|
|
109
|
+
// Provider HTML needs both script execution and a real proxy-origin
|
|
110
|
+
// storage context. Never accept popup escape or top-navigation tokens.
|
|
111
|
+
return [...new Set([
|
|
112
|
+
"allow-scripts",
|
|
113
|
+
"allow-same-origin",
|
|
114
|
+
...tokens,
|
|
115
|
+
])].filter((token) => allowedTokens.has(token)).join(" ");
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const buildAllowAttribute = (permissions) => {
|
|
119
|
+
if (!isObject(permissions) || Array.isArray(permissions)) return "";
|
|
120
|
+
const allowed = [];
|
|
121
|
+
if (permissions.camera) allowed.push("camera");
|
|
122
|
+
if (permissions.microphone) allowed.push("microphone");
|
|
123
|
+
if (permissions.geolocation) allowed.push("geolocation");
|
|
124
|
+
if (permissions.clipboardWrite) allowed.push("clipboard-write");
|
|
125
|
+
return allowed.join("; ");
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const loadResource = (params) => {
|
|
129
|
+
if (!isObject(params) || typeof params.html !== "string" || !innerFrame.contentDocument) return;
|
|
130
|
+
innerFrame.setAttribute("sandbox", safeSandbox(params.sandbox));
|
|
131
|
+
const allow = buildAllowAttribute(params.permissions);
|
|
132
|
+
if (allow) innerFrame.setAttribute("allow", allow);
|
|
133
|
+
else innerFrame.removeAttribute("allow");
|
|
134
|
+
innerReady = false;
|
|
135
|
+
const document = innerFrame.contentDocument;
|
|
136
|
+
document.open();
|
|
137
|
+
document.write(injectCsp(params.html, buildResourceCsp(params.csp)));
|
|
138
|
+
document.close();
|
|
139
|
+
flushPendingMessages();
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
window.addEventListener("message", (event) => {
|
|
143
|
+
if (isMessageFromParent(event)) {
|
|
144
|
+
const data = event.data;
|
|
145
|
+
if (!isObject(data)) return;
|
|
146
|
+
if (data.method === SANDBOX_RESOURCE_READY_METHOD) {
|
|
147
|
+
loadResource(data.params);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (data.method === SANDBOX_PROXY_READY_METHOD) return;
|
|
151
|
+
if (typeof data.method === "string" && data.method.startsWith("ui/notifications/sandbox-")) return;
|
|
152
|
+
postToInner(data);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!isMessageFromInner(event)) return;
|
|
157
|
+
const data = event.data;
|
|
158
|
+
if (!isObject(data)) return;
|
|
159
|
+
if (typeof data.method === "string" && data.method.startsWith("ui/notifications/sandbox-")) return;
|
|
160
|
+
postToParent(data);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
postToParent({
|
|
164
|
+
jsonrpc: "2.0",
|
|
165
|
+
method: SANDBOX_PROXY_READY_METHOD,
|
|
166
|
+
params: {},
|
|
167
|
+
});
|
|
168
|
+
</script>
|
|
169
|
+
</body>
|
|
170
|
+
</html>`;
|
|
171
|
+
}
|
|
172
|
+
export function buildSandboxProxyCsp() {
|
|
173
|
+
return [
|
|
174
|
+
"default-src 'none'",
|
|
175
|
+
"script-src 'unsafe-inline'",
|
|
176
|
+
"style-src 'unsafe-inline'",
|
|
177
|
+
"frame-src 'self'",
|
|
178
|
+
"connect-src 'none'",
|
|
179
|
+
"worker-src 'none'",
|
|
180
|
+
"object-src 'none'",
|
|
181
|
+
"base-uri 'none'",
|
|
182
|
+
`sandbox ${SANDBOX_PROXY_SANDBOX}`
|
|
183
|
+
].join("; ");
|
|
184
|
+
}
|
|
185
|
+
function safeInlineJSON(value) {
|
|
186
|
+
const json = JSON.stringify(value);
|
|
187
|
+
if (json === undefined)
|
|
188
|
+
return "undefined";
|
|
189
|
+
return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
190
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { getServerPrefix, getToolNameCandidates, isServerDisabled, matchesToolPattern, resolveToolPrefix } from "./types.js";
|
|
2
|
+
import { isServerInActiveFailureBackoff } from "./failure-backoff.js";
|
|
3
|
+
const MIN_STEM_LENGTH = 4;
|
|
4
|
+
const FIELD_WEIGHTS = {
|
|
5
|
+
name: 12,
|
|
6
|
+
originalName: 10,
|
|
7
|
+
server: 8,
|
|
8
|
+
description: 5,
|
|
9
|
+
keywords: 5
|
|
10
|
+
};
|
|
11
|
+
const searchCache = new WeakMap;
|
|
12
|
+
export function resolveSearchKeywords(definition, toolOriginalName, serverName, globalPrefix) {
|
|
13
|
+
const map = definition?.searchKeywords;
|
|
14
|
+
if (!map || typeof map !== "object" || Array.isArray(map))
|
|
15
|
+
return [];
|
|
16
|
+
const candidates = getToolNameCandidates(toolOriginalName, serverName, resolveToolPrefix(definition, globalPrefix));
|
|
17
|
+
const keywords = [];
|
|
18
|
+
const seen = new Set;
|
|
19
|
+
for (const [pattern, values] of Object.entries(map)) {
|
|
20
|
+
if (!Array.isArray(values))
|
|
21
|
+
continue;
|
|
22
|
+
if (!matchesToolPattern(candidates, [pattern]))
|
|
23
|
+
continue;
|
|
24
|
+
for (const value of values) {
|
|
25
|
+
if (typeof value !== "string")
|
|
26
|
+
continue;
|
|
27
|
+
const trimmed = value.trim();
|
|
28
|
+
if (!trimmed || seen.has(trimmed))
|
|
29
|
+
continue;
|
|
30
|
+
seen.add(trimmed);
|
|
31
|
+
keywords.push(trimmed);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return keywords;
|
|
35
|
+
}
|
|
36
|
+
export function normalizeSearchText(value) {
|
|
37
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_./:-]+/g, " ").toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
export function tokenize(value) {
|
|
40
|
+
return normalizeSearchText(value).split(/[^a-z0-9]+/).filter(Boolean);
|
|
41
|
+
}
|
|
42
|
+
function prepareToolSearch(tool, server, keywords) {
|
|
43
|
+
const fields = {
|
|
44
|
+
name: normalizeSearchText(tool.name),
|
|
45
|
+
originalName: normalizeSearchText(tool.originalName),
|
|
46
|
+
server: normalizeSearchText(server),
|
|
47
|
+
description: normalizeSearchText(tool.description)
|
|
48
|
+
};
|
|
49
|
+
const preparedFields = Object.entries(fields).map(([field, value]) => [field, value, tokenize(value)]);
|
|
50
|
+
const keywordPhrases = keywords?.map((keyword) => normalizeSearchText(keyword).trim()).filter(Boolean) ?? [];
|
|
51
|
+
return {
|
|
52
|
+
tool,
|
|
53
|
+
fields: preparedFields,
|
|
54
|
+
nameTokens: preparedFields[0][2],
|
|
55
|
+
keywordPhrases,
|
|
56
|
+
keywordTokens: keywordPhrases.flatMap(tokenize)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function scorePreparedToolMatch(prepared, normalizedQuery, queryTokens) {
|
|
60
|
+
let score = 0;
|
|
61
|
+
let phraseMatched = false;
|
|
62
|
+
let wholeFieldExact = false;
|
|
63
|
+
const matchedTokens = new Set;
|
|
64
|
+
for (const [field, value, fieldTokens] of prepared.fields) {
|
|
65
|
+
const weight = FIELD_WEIGHTS[field];
|
|
66
|
+
if (value === normalizedQuery) {
|
|
67
|
+
score += weight * 14;
|
|
68
|
+
phraseMatched = true;
|
|
69
|
+
wholeFieldExact = true;
|
|
70
|
+
} else if (value.startsWith(normalizedQuery)) {
|
|
71
|
+
score += weight * 9;
|
|
72
|
+
phraseMatched = true;
|
|
73
|
+
} else if (value.includes(normalizedQuery)) {
|
|
74
|
+
score += weight * 6;
|
|
75
|
+
phraseMatched = true;
|
|
76
|
+
}
|
|
77
|
+
for (const token of queryTokens) {
|
|
78
|
+
if (fieldTokens.includes(token)) {
|
|
79
|
+
score += weight * 4;
|
|
80
|
+
matchedTokens.add(token);
|
|
81
|
+
} else if (fieldTokens.some((fieldToken) => fieldToken.startsWith(token) || fieldToken.length >= MIN_STEM_LENGTH && token.startsWith(fieldToken))) {
|
|
82
|
+
score += weight * 2;
|
|
83
|
+
matchedTokens.add(token);
|
|
84
|
+
} else if (value.includes(token)) {
|
|
85
|
+
score += weight;
|
|
86
|
+
matchedTokens.add(token);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (prepared.keywordPhrases.length > 0) {
|
|
91
|
+
const weight = FIELD_WEIGHTS.keywords;
|
|
92
|
+
let phraseScore = 0;
|
|
93
|
+
for (const phrase of prepared.keywordPhrases) {
|
|
94
|
+
if (phrase === normalizedQuery) {
|
|
95
|
+
phraseScore = Math.max(phraseScore, weight * 14);
|
|
96
|
+
phraseMatched = true;
|
|
97
|
+
wholeFieldExact = true;
|
|
98
|
+
} else if (phrase.startsWith(normalizedQuery)) {
|
|
99
|
+
phraseScore = Math.max(phraseScore, weight * 9);
|
|
100
|
+
phraseMatched = true;
|
|
101
|
+
} else if (phrase.includes(normalizedQuery)) {
|
|
102
|
+
phraseScore = Math.max(phraseScore, weight * 6);
|
|
103
|
+
phraseMatched = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
score += phraseScore;
|
|
107
|
+
for (const token of queryTokens) {
|
|
108
|
+
if (prepared.keywordTokens.includes(token)) {
|
|
109
|
+
score += weight * 4;
|
|
110
|
+
matchedTokens.add(token);
|
|
111
|
+
} else if (prepared.keywordTokens.some((keywordToken) => keywordToken.startsWith(token) || keywordToken.length >= MIN_STEM_LENGTH && token.startsWith(keywordToken))) {
|
|
112
|
+
score += weight * 2;
|
|
113
|
+
matchedTokens.add(token);
|
|
114
|
+
} else if (prepared.keywordPhrases.some((phrase) => phrase.includes(token))) {
|
|
115
|
+
score += weight;
|
|
116
|
+
matchedTokens.add(token);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const coverage = matchedTokens.size / queryTokens.length;
|
|
121
|
+
if (!phraseMatched && (queryTokens.length <= 2 ? coverage !== 1 : coverage < 0.6))
|
|
122
|
+
return null;
|
|
123
|
+
score += coverage === 1 ? 25 : Math.round(coverage * 10);
|
|
124
|
+
const firstQueryToken = queryTokens[0];
|
|
125
|
+
if (firstQueryToken !== undefined && prepared.nameTokens.includes(firstQueryToken))
|
|
126
|
+
score += 8;
|
|
127
|
+
if (wholeFieldExact)
|
|
128
|
+
score += 20;
|
|
129
|
+
return score;
|
|
130
|
+
}
|
|
131
|
+
export function scoreToolMatch(tool, server, query, keywords) {
|
|
132
|
+
const normalizedQuery = normalizeSearchText(query).trim();
|
|
133
|
+
const queryTokens = tokenize(query);
|
|
134
|
+
if (queryTokens.length === 0)
|
|
135
|
+
return null;
|
|
136
|
+
return scorePreparedToolMatch(prepareToolSearch(tool, server, keywords), normalizedQuery, queryTokens);
|
|
137
|
+
}
|
|
138
|
+
function getPreparedTools(state, serverName, metadata, definition, globalPrefix, includeKeywords) {
|
|
139
|
+
let stateCache = searchCache.get(state);
|
|
140
|
+
if (!stateCache) {
|
|
141
|
+
stateCache = new Map;
|
|
142
|
+
searchCache.set(state, stateCache);
|
|
143
|
+
}
|
|
144
|
+
const toolPrefix = resolveToolPrefix(definition, globalPrefix);
|
|
145
|
+
let cached = stateCache.get(serverName);
|
|
146
|
+
if (cached?.metadata !== metadata || cached.searchKeywords !== definition?.searchKeywords || cached.toolPrefix !== toolPrefix) {
|
|
147
|
+
cached = { metadata, searchKeywords: definition?.searchKeywords, toolPrefix };
|
|
148
|
+
stateCache.set(serverName, cached);
|
|
149
|
+
}
|
|
150
|
+
const cacheKey = includeKeywords ? "withKeywords" : "withoutKeywords";
|
|
151
|
+
let prepared = cached[cacheKey];
|
|
152
|
+
if (!prepared) {
|
|
153
|
+
prepared = metadata.map((tool) => prepareToolSearch(tool, serverName, includeKeywords && definition?.searchKeywords !== undefined ? resolveSearchKeywords(definition, tool.originalName, serverName, globalPrefix) : undefined));
|
|
154
|
+
cached[cacheKey] = prepared;
|
|
155
|
+
}
|
|
156
|
+
return prepared;
|
|
157
|
+
}
|
|
158
|
+
export function rankToolMatches(state, query, server, includeKeywords = true) {
|
|
159
|
+
const matches = [];
|
|
160
|
+
const normalizedQuery = normalizeSearchText(query).trim();
|
|
161
|
+
const queryTokens = tokenize(query);
|
|
162
|
+
if (queryTokens.length === 0)
|
|
163
|
+
return matches;
|
|
164
|
+
const globalPrefix = state.config.settings?.toolPrefix ?? "server";
|
|
165
|
+
for (const [serverName, metadata] of state.toolMetadata.entries()) {
|
|
166
|
+
if (server && serverName !== server)
|
|
167
|
+
continue;
|
|
168
|
+
const definition = state.config.mcpServers[serverName];
|
|
169
|
+
if (isServerDisabled(definition))
|
|
170
|
+
continue;
|
|
171
|
+
if (isServerInActiveFailureBackoff(state, serverName))
|
|
172
|
+
continue;
|
|
173
|
+
for (const prepared of getPreparedTools(state, serverName, metadata, definition, globalPrefix, includeKeywords)) {
|
|
174
|
+
const score = scorePreparedToolMatch(prepared, normalizedQuery, queryTokens);
|
|
175
|
+
if (score !== null)
|
|
176
|
+
matches.push({ server: serverName, tool: prepared.tool, score });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return matches.sort((a, b) => b.score - a.score || a.tool.name.localeCompare(b.tool.name));
|
|
180
|
+
}
|
|
181
|
+
export function paginate(items, offset, limit) {
|
|
182
|
+
const safeOffset = Number.isFinite(offset) ? Math.max(0, Math.trunc(offset)) : 0;
|
|
183
|
+
const safeLimit = Number.isFinite(limit) ? Math.max(1, Math.trunc(limit)) : 1;
|
|
184
|
+
const total = items.length;
|
|
185
|
+
const page = items.slice(safeOffset, safeOffset + safeLimit);
|
|
186
|
+
const nextOffset = safeOffset + page.length;
|
|
187
|
+
return {
|
|
188
|
+
items: page,
|
|
189
|
+
total,
|
|
190
|
+
hasMore: nextOffset < total,
|
|
191
|
+
nextOffset: nextOffset < total ? nextOffset : null
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
export function rankSuggestions(state, name, limit) {
|
|
195
|
+
const stripped = Object.keys(state.config.mcpServers).flatMap((server) => ["server", "short", "mcp"].map((prefix) => getServerPrefix(server, prefix))).filter((candidate) => Boolean(candidate) && name.startsWith(`${candidate}_`)).sort((a, b) => b.length - a.length).map((candidate) => name.slice(candidate.length + 1));
|
|
196
|
+
const query = stripped[0] ?? name;
|
|
197
|
+
return rankToolMatches(state, query, undefined, false).slice(0, limit).map((match) => match.tool.name);
|
|
198
|
+
}
|