jxp 4.1.1 → 5.0.0
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/.env.sample +30 -0
- package/README.md +2 -0
- package/dist/bin/index_audit.d.ts +3 -0
- package/dist/bin/index_audit.d.ts.map +1 -0
- package/dist/bin/index_audit.js +66 -0
- package/dist/bin/index_audit.js.map +1 -0
- package/dist/bin/mcp-stdio-bridge.d.ts +3 -0
- package/dist/bin/mcp-stdio-bridge.d.ts.map +1 -0
- package/dist/bin/mcp-stdio-bridge.js +87 -0
- package/dist/bin/mcp-stdio-bridge.js.map +1 -0
- package/dist/bin/server.d.ts.map +1 -1
- package/dist/bin/server.js +8 -2
- package/dist/bin/server.js.map +1 -1
- package/dist/libs/builtin_models.d.ts +21 -0
- package/dist/libs/builtin_models.d.ts.map +1 -0
- package/dist/libs/builtin_models.js +125 -0
- package/dist/libs/builtin_models.js.map +1 -0
- package/dist/libs/docs-auth.d.ts.map +1 -1
- package/dist/libs/docs-auth.js +6 -2
- package/dist/libs/docs-auth.js.map +1 -1
- package/dist/libs/docs.js +105 -44
- package/dist/libs/docs.js.map +1 -1
- package/dist/libs/groups.js +3 -4
- package/dist/libs/groups.js.map +1 -1
- package/dist/libs/index_diagnostics.d.ts +144 -0
- package/dist/libs/index_diagnostics.d.ts.map +1 -0
- package/dist/libs/index_diagnostics.js +671 -0
- package/dist/libs/index_diagnostics.js.map +1 -0
- package/dist/libs/jxp.d.ts.map +1 -1
- package/dist/libs/jxp.js +97 -331
- package/dist/libs/jxp.js.map +1 -1
- package/dist/libs/load-config.d.ts +2 -0
- package/dist/libs/load-config.d.ts.map +1 -1
- package/dist/libs/load-config.js +10 -0
- package/dist/libs/load-config.js.map +1 -1
- package/dist/libs/login.js +3 -8
- package/dist/libs/login.js.map +1 -1
- package/dist/libs/mcp/auth.d.ts +4 -0
- package/dist/libs/mcp/auth.d.ts.map +1 -0
- package/dist/libs/mcp/auth.js +49 -0
- package/dist/libs/mcp/auth.js.map +1 -0
- package/dist/libs/mcp/config.d.ts +21 -0
- package/dist/libs/mcp/config.d.ts.map +1 -0
- package/dist/libs/mcp/config.js +66 -0
- package/dist/libs/mcp/config.js.map +1 -0
- package/dist/libs/mcp/context.d.ts +5 -0
- package/dist/libs/mcp/context.d.ts.map +1 -0
- package/dist/libs/mcp/context.js +14 -0
- package/dist/libs/mcp/context.js.map +1 -0
- package/dist/libs/mcp/describe.d.ts +13 -0
- package/dist/libs/mcp/describe.d.ts.map +1 -0
- package/dist/libs/mcp/describe.js +36 -0
- package/dist/libs/mcp/describe.js.map +1 -0
- package/dist/libs/mcp/docs_auth.d.ts +5 -0
- package/dist/libs/mcp/docs_auth.d.ts.map +1 -0
- package/dist/libs/mcp/docs_auth.js +36 -0
- package/dist/libs/mcp/docs_auth.js.map +1 -0
- package/dist/libs/mcp/dynamic_import.d.ts +6 -0
- package/dist/libs/mcp/dynamic_import.d.ts.map +1 -0
- package/dist/libs/mcp/dynamic_import.js +12 -0
- package/dist/libs/mcp/dynamic_import.js.map +1 -0
- package/dist/libs/mcp/execute_tool.d.ts +10 -0
- package/dist/libs/mcp/execute_tool.d.ts.map +1 -0
- package/dist/libs/mcp/execute_tool.js +65 -0
- package/dist/libs/mcp/execute_tool.js.map +1 -0
- package/dist/libs/mcp/guides/default.md +67 -0
- package/dist/libs/mcp/guides.d.ts +14 -0
- package/dist/libs/mcp/guides.d.ts.map +1 -0
- package/dist/libs/mcp/guides.js +127 -0
- package/dist/libs/mcp/guides.js.map +1 -0
- package/dist/libs/mcp/model_visibility.d.ts +31 -0
- package/dist/libs/mcp/model_visibility.d.ts.map +1 -0
- package/dist/libs/mcp/model_visibility.js +73 -0
- package/dist/libs/mcp/model_visibility.js.map +1 -0
- package/dist/libs/mcp/mount.d.ts +10 -0
- package/dist/libs/mcp/mount.d.ts.map +1 -0
- package/dist/libs/mcp/mount.js +68 -0
- package/dist/libs/mcp/mount.js.map +1 -0
- package/dist/libs/mcp/read_service.d.ts +23 -0
- package/dist/libs/mcp/read_service.d.ts.map +1 -0
- package/dist/libs/mcp/read_service.js +141 -0
- package/dist/libs/mcp/read_service.js.map +1 -0
- package/dist/libs/mcp/server.d.ts +23 -0
- package/dist/libs/mcp/server.d.ts.map +1 -0
- package/dist/libs/mcp/server.js +111 -0
- package/dist/libs/mcp/server.js.map +1 -0
- package/dist/libs/parse_byte_size.d.ts +10 -0
- package/dist/libs/parse_byte_size.d.ts.map +1 -0
- package/dist/libs/parse_byte_size.js +80 -0
- package/dist/libs/parse_byte_size.js.map +1 -0
- package/dist/libs/query_limits.js +99 -13
- package/dist/libs/query_limits.js.map +1 -1
- package/dist/libs/read_handlers.d.ts +13 -0
- package/dist/libs/read_handlers.d.ts.map +1 -0
- package/dist/libs/read_handlers.js +381 -0
- package/dist/libs/read_handlers.js.map +1 -0
- package/dist/libs/schema.js +2 -2
- package/dist/libs/schema.js.map +1 -1
- package/dist/libs/schema_serialize.d.ts +3 -0
- package/dist/libs/schema_serialize.d.ts.map +1 -0
- package/dist/libs/schema_serialize.js +45 -0
- package/dist/libs/schema_serialize.js.map +1 -0
- package/dist/libs/security.js +7 -11
- package/dist/libs/security.js.map +1 -1
- package/dist/libs/setup.js +3 -4
- package/dist/libs/setup.js.map +1 -1
- package/dist/libs/startup.d.ts +4 -0
- package/dist/libs/startup.d.ts.map +1 -1
- package/dist/libs/startup.js +17 -0
- package/dist/libs/startup.js.map +1 -1
- package/dist/models/indexquerylog_model.d.ts +19 -0
- package/dist/models/indexquerylog_model.d.ts.map +1 -0
- package/dist/models/indexquerylog_model.js +32 -0
- package/dist/models/indexquerylog_model.js.map +1 -0
- package/dist/types/jxp-config.d.ts +29 -0
- package/dist/types/jxp-config.d.ts.map +1 -1
- package/dist/types/schema-fields.d.ts +4 -0
- package/dist/types/schema-fields.d.ts.map +1 -1
- package/docs/api.md +16 -2
- package/docs/changelog.md +101 -1
- package/docs/configuration.md +39 -0
- package/docs/index.md +2 -0
- package/docs/index_diagnostics.md +127 -0
- package/docs/mcp.md +134 -0
- package/docs/schemas.md +3 -1
- package/mkdocs.yml +2 -0
- package/package.json +26 -9
- package/templates/assets/diagnostics.js +352 -0
- package/templates/assets/docs.css +107 -0
- package/templates/assets/mcp-console.js +343 -0
- package/templates/diagnostics.pug +94 -0
- package/templates/index.pug +38 -0
- package/templates/mcp.pug +112 -0
- package/templates/navbar.pug +2 -0
- package/templates/sidebar.pug +12 -0
|
@@ -242,3 +242,110 @@ body.docs-body {
|
|
|
242
242
|
width: 100%;
|
|
243
243
|
max-width: 400px;
|
|
244
244
|
}
|
|
245
|
+
|
|
246
|
+
.mcp-chat-wrap {
|
|
247
|
+
border: 1px solid var(--docs-border);
|
|
248
|
+
border-radius: 0.5rem;
|
|
249
|
+
overflow: hidden;
|
|
250
|
+
background: #fafbfc;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.mcp-chat-thread {
|
|
254
|
+
max-height: 420px;
|
|
255
|
+
overflow-y: auto;
|
|
256
|
+
padding: 1rem;
|
|
257
|
+
display: flex;
|
|
258
|
+
flex-direction: column;
|
|
259
|
+
gap: 0.75rem;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.mcp-msg {
|
|
263
|
+
max-width: 92%;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.mcp-msg-user {
|
|
267
|
+
align-self: flex-end;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.mcp-msg-bot {
|
|
271
|
+
align-self: flex-start;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.mcp-msg-label {
|
|
275
|
+
font-size: 0.7rem;
|
|
276
|
+
font-weight: 600;
|
|
277
|
+
text-transform: uppercase;
|
|
278
|
+
letter-spacing: 0.04em;
|
|
279
|
+
color: #6c757d;
|
|
280
|
+
margin-bottom: 0.25rem;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.mcp-msg-body {
|
|
284
|
+
padding: 0.65rem 0.85rem;
|
|
285
|
+
border-radius: 0.5rem;
|
|
286
|
+
font-size: 0.9rem;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.mcp-msg-user .mcp-msg-body {
|
|
290
|
+
background: var(--docs-accent);
|
|
291
|
+
color: #fff;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.mcp-msg-user .mcp-msg-body code {
|
|
295
|
+
color: #fff;
|
|
296
|
+
background: rgba(255, 255, 255, 0.15);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.mcp-msg-bot .mcp-msg-body {
|
|
300
|
+
background: #fff;
|
|
301
|
+
border: 1px solid var(--docs-border);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.mcp-msg-error .mcp-msg-body {
|
|
305
|
+
border-color: #f5c2c7;
|
|
306
|
+
background: #f8d7da;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.mcp-result {
|
|
310
|
+
font-family: ui-monospace, monospace;
|
|
311
|
+
font-size: 0.78rem;
|
|
312
|
+
white-space: pre-wrap;
|
|
313
|
+
word-break: break-word;
|
|
314
|
+
max-height: 240px;
|
|
315
|
+
overflow: auto;
|
|
316
|
+
background: #212529;
|
|
317
|
+
color: #e9ecef;
|
|
318
|
+
padding: 0.75rem;
|
|
319
|
+
border-radius: 0.375rem;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.mcp-chat-input-wrap {
|
|
323
|
+
padding: 0.75rem 1rem;
|
|
324
|
+
border-top: 1px solid var(--docs-border);
|
|
325
|
+
background: #fff;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.mcp-tool-panel {
|
|
329
|
+
border: 1px solid var(--docs-border);
|
|
330
|
+
border-radius: 0.375rem;
|
|
331
|
+
padding: 1rem;
|
|
332
|
+
background: #fafbfc;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.mcp-setup-card .mcp-json-block {
|
|
336
|
+
font-family: ui-monospace, monospace;
|
|
337
|
+
font-size: 0.8rem;
|
|
338
|
+
background: #212529;
|
|
339
|
+
color: #e9ecef;
|
|
340
|
+
border-radius: 0.375rem;
|
|
341
|
+
padding: 1rem;
|
|
342
|
+
margin: 0;
|
|
343
|
+
overflow-x: auto;
|
|
344
|
+
white-space: pre;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.mcp-setup-card .mcp-copy-btn {
|
|
348
|
+
position: absolute;
|
|
349
|
+
top: 0.5rem;
|
|
350
|
+
right: 0.5rem;
|
|
351
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const STORAGE_KEY = "jxp_docs_api_key";
|
|
5
|
+
const STORAGE_REMEMBER = "jxp_docs_remember_key";
|
|
6
|
+
|
|
7
|
+
function getApiKey() {
|
|
8
|
+
const input = document.getElementById("docs-api-key");
|
|
9
|
+
return input ? input.value.trim() : "";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function loadStoredKey() {
|
|
13
|
+
if (localStorage.getItem(STORAGE_REMEMBER) !== "1") return;
|
|
14
|
+
const key = localStorage.getItem(STORAGE_KEY);
|
|
15
|
+
if (!key) return;
|
|
16
|
+
const input = document.getElementById("docs-api-key");
|
|
17
|
+
const remember = document.getElementById("docs-remember-key");
|
|
18
|
+
if (input) input.value = key;
|
|
19
|
+
if (remember) remember.checked = true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function saveKeyIfRemembered() {
|
|
23
|
+
const remember = document.getElementById("docs-remember-key");
|
|
24
|
+
const input = document.getElementById("docs-api-key");
|
|
25
|
+
if (!remember || !input) return;
|
|
26
|
+
if (remember.checked) {
|
|
27
|
+
localStorage.setItem(STORAGE_REMEMBER, "1");
|
|
28
|
+
localStorage.setItem(STORAGE_KEY, input.value.trim());
|
|
29
|
+
} else {
|
|
30
|
+
localStorage.removeItem(STORAGE_REMEMBER);
|
|
31
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
32
|
+
}
|
|
33
|
+
updateMcpConfigSnippets();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getMcpSetup() {
|
|
37
|
+
const card = document.querySelector(".mcp-setup-card");
|
|
38
|
+
const fromWindow = window.__MCP_SETUP__ || {};
|
|
39
|
+
const baseUrl =
|
|
40
|
+
fromWindow.baseUrl ||
|
|
41
|
+
card?.dataset.baseUrl ||
|
|
42
|
+
window.location.origin;
|
|
43
|
+
const mcpPath = fromWindow.mcpPath || card?.dataset.mcpPath || "/mcp";
|
|
44
|
+
const base = String(baseUrl).replace(/\/$/, "");
|
|
45
|
+
const path = mcpPath.startsWith("/") ? mcpPath : `/${mcpPath}`;
|
|
46
|
+
const mcpUrl = fromWindow.mcpUrl || card?.dataset.mcpUrl || `${base}${path}`;
|
|
47
|
+
return {
|
|
48
|
+
baseUrl: base,
|
|
49
|
+
mcpUrl,
|
|
50
|
+
serverSlug: fromWindow.serverSlug || card?.dataset.serverSlug || "jxp",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildMcpJsonConfig(kind, apiKey) {
|
|
55
|
+
const setup = getMcpSetup();
|
|
56
|
+
const key = apiKey || "YOUR_API_KEY";
|
|
57
|
+
const slug = setup.serverSlug || "jxp";
|
|
58
|
+
if (kind === "stdio") {
|
|
59
|
+
return JSON.stringify(
|
|
60
|
+
{
|
|
61
|
+
mcpServers: {
|
|
62
|
+
[slug]: {
|
|
63
|
+
command: "npx",
|
|
64
|
+
args: ["-y", "jxp-mcp"],
|
|
65
|
+
env: {
|
|
66
|
+
JXP_URL: setup.baseUrl,
|
|
67
|
+
JXP_API_KEY: key,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
null,
|
|
73
|
+
2
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return JSON.stringify(
|
|
77
|
+
{
|
|
78
|
+
mcpServers: {
|
|
79
|
+
[slug]: {
|
|
80
|
+
url: setup.mcpUrl,
|
|
81
|
+
headers: { "X-API-Key": key },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
null,
|
|
86
|
+
2
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function updateMcpConfigSnippets() {
|
|
91
|
+
const apiKey = getApiKey();
|
|
92
|
+
const httpEl = document.getElementById("mcpJsonHttp");
|
|
93
|
+
const stdioEl = document.getElementById("mcpJsonStdio");
|
|
94
|
+
if (httpEl) httpEl.textContent = buildMcpJsonConfig("http", apiKey);
|
|
95
|
+
if (stdioEl) stdioEl.textContent = buildMcpJsonConfig("stdio", apiKey);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function copyMcpConfig(targetId, btn) {
|
|
99
|
+
const el = document.getElementById(targetId);
|
|
100
|
+
if (!el) return;
|
|
101
|
+
try {
|
|
102
|
+
await navigator.clipboard.writeText(el.textContent || "");
|
|
103
|
+
const label = btn.innerHTML;
|
|
104
|
+
btn.innerHTML = '<i class="bi bi-check2 me-1"></i>Copied';
|
|
105
|
+
setTimeout(function () {
|
|
106
|
+
btn.innerHTML = label;
|
|
107
|
+
}, 1500);
|
|
108
|
+
} catch {
|
|
109
|
+
/* ignore */
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function escapeHtml(text) {
|
|
114
|
+
return String(text)
|
|
115
|
+
.replace(/&/g, "&")
|
|
116
|
+
.replace(/</g, "<")
|
|
117
|
+
.replace(/>/g, ">")
|
|
118
|
+
.replace(/"/g, """);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function formatBody(text) {
|
|
122
|
+
if (!text) return "";
|
|
123
|
+
try {
|
|
124
|
+
return JSON.stringify(JSON.parse(text), null, 2);
|
|
125
|
+
} catch {
|
|
126
|
+
return text;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function appendMessage(thread, role, bodyHtml, isError) {
|
|
131
|
+
const wrap = document.createElement("div");
|
|
132
|
+
wrap.className = "mcp-msg mcp-msg-" + role + (isError ? " mcp-msg-error" : "");
|
|
133
|
+
const label = document.createElement("div");
|
|
134
|
+
label.className = "mcp-msg-label";
|
|
135
|
+
label.textContent = role === "user" ? "You" : "Assistant";
|
|
136
|
+
const body = document.createElement("div");
|
|
137
|
+
body.className = "mcp-msg-body";
|
|
138
|
+
body.innerHTML = bodyHtml;
|
|
139
|
+
wrap.appendChild(label);
|
|
140
|
+
wrap.appendChild(body);
|
|
141
|
+
thread.appendChild(wrap);
|
|
142
|
+
thread.scrollTop = thread.scrollHeight;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function appendToolNote(thread, toolName, args) {
|
|
146
|
+
const argsText = Object.keys(args).length ? JSON.stringify(args) : "{}";
|
|
147
|
+
appendMessage(
|
|
148
|
+
thread,
|
|
149
|
+
"user",
|
|
150
|
+
`<p class="mb-0"><code>${escapeHtml(toolName)}</code> <span class="text-muted">${escapeHtml(argsText)}</span></p>`,
|
|
151
|
+
false
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function appendResult(thread, text, isError) {
|
|
156
|
+
const formatted = escapeHtml(formatBody(text));
|
|
157
|
+
appendMessage(thread, "bot", `<pre class="mcp-result mb-0">${formatted}</pre>`, isError);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function callMcpTool(name, args) {
|
|
161
|
+
const headers = { "Content-Type": "application/json", Accept: "application/json" };
|
|
162
|
+
const apiKey = getApiKey();
|
|
163
|
+
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
164
|
+
|
|
165
|
+
const res = await fetch("/docs/mcp/call", {
|
|
166
|
+
method: "POST",
|
|
167
|
+
credentials: "same-origin",
|
|
168
|
+
headers,
|
|
169
|
+
body: JSON.stringify({ name, arguments: args }),
|
|
170
|
+
});
|
|
171
|
+
const payload = await res.json().catch(() => ({ text: res.statusText, isError: true }));
|
|
172
|
+
if (!res.ok) {
|
|
173
|
+
const msg = payload.message || payload.text || res.statusText;
|
|
174
|
+
throw new Error(msg);
|
|
175
|
+
}
|
|
176
|
+
return payload;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function runTool(thread, name, args) {
|
|
180
|
+
appendToolNote(thread, name, args);
|
|
181
|
+
try {
|
|
182
|
+
const result = await callMcpTool(name, args);
|
|
183
|
+
appendResult(thread, result.text || "", !!result.isError);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
appendResult(thread, String(err.message || err), true);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function parseLimit(text) {
|
|
190
|
+
const match = text.match(/\blimit\s*=\s*(\d+)/i);
|
|
191
|
+
return match ? parseInt(match[1], 10) : 5;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function parseCommand(input) {
|
|
195
|
+
const text = input.trim();
|
|
196
|
+
if (!text) return null;
|
|
197
|
+
|
|
198
|
+
const lower = text.toLowerCase();
|
|
199
|
+
if (lower === "help" || lower === "?") {
|
|
200
|
+
return {
|
|
201
|
+
help: true,
|
|
202
|
+
html:
|
|
203
|
+
"<p>Commands:</p><ul class=\"mb-0\"><li><code>list models</code></li>" +
|
|
204
|
+
"<li><code>describe MODEL</code></li><li><code>count MODEL</code></li>" +
|
|
205
|
+
"<li><code>find MODEL</code> or <code>find MODEL limit=10</code></li></ul>",
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (/^list\s+models?$/i.test(text)) {
|
|
210
|
+
return { tool: "jxp_list_models", args: {} };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const describeMatch = text.match(/^describe\s+(\S+)/i);
|
|
214
|
+
if (describeMatch) {
|
|
215
|
+
return { tool: "jxp_describe_model", args: { model: describeMatch[1] } };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const countMatch = text.match(/^count\s+(\S+)/i);
|
|
219
|
+
if (countMatch) {
|
|
220
|
+
return { tool: "jxp_count", args: { model: countMatch[1] } };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const findMatch = text.match(/^find\s+(\S+)/i);
|
|
224
|
+
if (findMatch) {
|
|
225
|
+
return {
|
|
226
|
+
tool: "jxp_find",
|
|
227
|
+
args: { model: findMatch[1], limit: parseLimit(text) },
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
error:
|
|
233
|
+
"I didn't understand that. Try <code>list models</code>, <code>describe MODEL</code>, " +
|
|
234
|
+
"<code>count MODEL</code>, or <code>find MODEL limit=5</code>. Type <code>help</code> for more.",
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function handleUserInput(thread, input) {
|
|
239
|
+
const text = input.trim();
|
|
240
|
+
if (!text) return;
|
|
241
|
+
|
|
242
|
+
appendMessage(thread, "user", `<p class="mb-0">${escapeHtml(text)}</p>`, false);
|
|
243
|
+
|
|
244
|
+
const parsed = parseCommand(text);
|
|
245
|
+
if (!parsed) return;
|
|
246
|
+
|
|
247
|
+
if (parsed.help) {
|
|
248
|
+
appendMessage(thread, "bot", parsed.html, false);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (parsed.error) {
|
|
252
|
+
appendMessage(thread, "bot", `<p class="mb-0">${parsed.error}</p>`, true);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
await runTool(thread, parsed.tool, parsed.args);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function loadSessionApiKey() {
|
|
260
|
+
const access = document.documentElement.dataset.docsAccess;
|
|
261
|
+
if (access !== "protected") return;
|
|
262
|
+
try {
|
|
263
|
+
const res = await fetch("/docs/session", { credentials: "same-origin" });
|
|
264
|
+
if (!res.ok) return;
|
|
265
|
+
const data = await res.json();
|
|
266
|
+
if (!data.apikey) return;
|
|
267
|
+
const input = document.getElementById("docs-api-key");
|
|
268
|
+
if (input) input.value = data.apikey;
|
|
269
|
+
updateMcpConfigSnippets();
|
|
270
|
+
} catch {
|
|
271
|
+
/* ignore */
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
276
|
+
const access = document.documentElement.dataset.docsAccess;
|
|
277
|
+
const thread = document.getElementById("mcpChatThread");
|
|
278
|
+
const form = document.getElementById("mcpChatForm");
|
|
279
|
+
const chatInput = document.getElementById("mcpChatInput");
|
|
280
|
+
const toolSelect = document.getElementById("mcpToolSelect");
|
|
281
|
+
const toolArgs = document.getElementById("mcpToolArgs");
|
|
282
|
+
const toolRun = document.getElementById("mcpToolRun");
|
|
283
|
+
|
|
284
|
+
if (!thread) return;
|
|
285
|
+
|
|
286
|
+
loadSessionApiKey().then(function () {
|
|
287
|
+
if (access !== "protected") loadStoredKey();
|
|
288
|
+
updateMcpConfigSnippets();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const keyInput = document.getElementById("docs-api-key");
|
|
292
|
+
const remember = document.getElementById("docs-remember-key");
|
|
293
|
+
if (keyInput) {
|
|
294
|
+
keyInput.addEventListener("change", saveKeyIfRemembered);
|
|
295
|
+
keyInput.addEventListener("input", updateMcpConfigSnippets);
|
|
296
|
+
}
|
|
297
|
+
if (remember) remember.addEventListener("change", saveKeyIfRemembered);
|
|
298
|
+
|
|
299
|
+
document.querySelectorAll(".mcp-copy-btn").forEach(function (btn) {
|
|
300
|
+
btn.addEventListener("click", function () {
|
|
301
|
+
const target = btn.getAttribute("data-target");
|
|
302
|
+
if (target) copyMcpConfig(target, btn);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (form && chatInput) {
|
|
307
|
+
form.addEventListener("submit", function (e) {
|
|
308
|
+
e.preventDefault();
|
|
309
|
+
saveKeyIfRemembered();
|
|
310
|
+
const value = chatInput.value;
|
|
311
|
+
chatInput.value = "";
|
|
312
|
+
handleUserInput(thread, value);
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
document.querySelectorAll(".mcp-quick-btn").forEach(function (btn) {
|
|
317
|
+
btn.addEventListener("click", function () {
|
|
318
|
+
saveKeyIfRemembered();
|
|
319
|
+
const cmd = btn.getAttribute("data-cmd") || "";
|
|
320
|
+
if (chatInput) chatInput.value = cmd;
|
|
321
|
+
handleUserInput(thread, cmd);
|
|
322
|
+
if (chatInput) chatInput.value = "";
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
if (toolRun && toolSelect && toolArgs) {
|
|
327
|
+
toolRun.addEventListener("click", function () {
|
|
328
|
+
saveKeyIfRemembered();
|
|
329
|
+
let args = {};
|
|
330
|
+
const raw = toolArgs.value.trim();
|
|
331
|
+
if (raw) {
|
|
332
|
+
try {
|
|
333
|
+
args = JSON.parse(raw);
|
|
334
|
+
} catch (err) {
|
|
335
|
+
appendResult(thread, "Invalid JSON arguments: " + err.message, true);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
runTool(thread, toolSelect.value, args);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
})();
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
extends layout
|
|
2
|
+
|
|
3
|
+
block content
|
|
4
|
+
h1.h3.mb-1 Index diagnostics
|
|
5
|
+
p.text-muted.mb-4
|
|
6
|
+
| Compare schema indexes to MongoDB, review slow-query samples, and sync indexes.
|
|
7
|
+
| Requires an
|
|
8
|
+
strong admin
|
|
9
|
+
| API key (filled automatically after docs sign-in, or paste into the top bar).
|
|
10
|
+
|
|
11
|
+
ul#diag-tabs.nav.nav-tabs(role="tablist")
|
|
12
|
+
li.nav-item(role="presentation")
|
|
13
|
+
button.nav-link.active#tab-indexes-btn(type="button" data-bs-toggle="tab" data-bs-target="#tab-indexes" role="tab") Indexes
|
|
14
|
+
li.nav-item(role="presentation")
|
|
15
|
+
button.nav-link#tab-queries-btn(type="button" data-bs-toggle="tab" data-bs-target="#tab-queries" role="tab") Query log
|
|
16
|
+
|
|
17
|
+
.tab-content.mt-3
|
|
18
|
+
#tab-indexes.tab-pane.fade.show.active(role="tabpanel")
|
|
19
|
+
.d-flex.flex-wrap.gap-2.mb-3
|
|
20
|
+
button#diag-refresh-indexes.btn.btn-primary.btn-sm(type="button")
|
|
21
|
+
i.bi.bi-arrow-clockwise.me-1
|
|
22
|
+
| Refresh audit
|
|
23
|
+
.form-check.form-check-inline.mb-0.align-self-center
|
|
24
|
+
input#diag-unused.form-check-input(type="checkbox")
|
|
25
|
+
label.form-check-label(for="diag-unused") Include unused ($indexStats)
|
|
26
|
+
#diag-indexes-status.small.text-muted.mb-2
|
|
27
|
+
#diag-indexes-summary
|
|
28
|
+
.table-responsive
|
|
29
|
+
table#diag-indexes-table.table.table-sm.table-hover.d-none
|
|
30
|
+
thead
|
|
31
|
+
tr
|
|
32
|
+
th Model
|
|
33
|
+
th Collection
|
|
34
|
+
th Status
|
|
35
|
+
th Missing
|
|
36
|
+
th Extra
|
|
37
|
+
tbody
|
|
38
|
+
|
|
39
|
+
hr.mt-4
|
|
40
|
+
h2.h5 Sync indexes
|
|
41
|
+
p.small.text-muted
|
|
42
|
+
| Creates indexes defined in Mongoose schemas and drops DB indexes not in the schema.
|
|
43
|
+
| Type
|
|
44
|
+
code= sync_confirm
|
|
45
|
+
| to confirm.
|
|
46
|
+
.row.g-2.align-items-end
|
|
47
|
+
.col-md-6
|
|
48
|
+
input#diag-sync-confirm.form-control.form-control-sm(type="text" placeholder=sync_confirm autocomplete="off")
|
|
49
|
+
.col-auto
|
|
50
|
+
button#diag-sync-btn.btn.btn-outline-danger.btn-sm(type="button" disabled) Sync indexes
|
|
51
|
+
|
|
52
|
+
#tab-queries.tab-pane.fade(role="tabpanel")
|
|
53
|
+
.d-flex.flex-wrap.gap-2.mb-3
|
|
54
|
+
button#diag-refresh-queries.btn.btn-primary.btn-sm(type="button")
|
|
55
|
+
i.bi.bi-arrow-clockwise.me-1
|
|
56
|
+
| Refresh log
|
|
57
|
+
select#diag-query-severity.form-select.form-select-sm(style="width:auto")
|
|
58
|
+
option(value="") All severities
|
|
59
|
+
option(value="alert") alert
|
|
60
|
+
option(value="warn") warn
|
|
61
|
+
.diag-model-filter(style="min-width:10rem;max-width:14rem")
|
|
62
|
+
input#diag-query-model.form-control.form-control-sm(
|
|
63
|
+
type="text"
|
|
64
|
+
list="diag-query-model-list"
|
|
65
|
+
placeholder="All models"
|
|
66
|
+
autocomplete="off"
|
|
67
|
+
aria-label="Filter by model"
|
|
68
|
+
)
|
|
69
|
+
datalist#diag-query-model-list
|
|
70
|
+
each opt in model_filter_options
|
|
71
|
+
option(value=opt.value)= opt.label
|
|
72
|
+
#diag-queries-status.small.text-muted.mb-2
|
|
73
|
+
#diag-monitor-config.small.mb-2
|
|
74
|
+
#diag-query-monitor-help.d-none
|
|
75
|
+
.table-responsive
|
|
76
|
+
table#diag-queries-table.table.table-sm.table-hover.d-none
|
|
77
|
+
thead
|
|
78
|
+
tr
|
|
79
|
+
th When
|
|
80
|
+
th Model
|
|
81
|
+
th Op
|
|
82
|
+
th Severity
|
|
83
|
+
th Docs examined
|
|
84
|
+
th Returned
|
|
85
|
+
th Scan
|
|
86
|
+
th Reason
|
|
87
|
+
th Filter
|
|
88
|
+
tbody
|
|
89
|
+
|
|
90
|
+
block javascripts
|
|
91
|
+
script.
|
|
92
|
+
window.JXP_DIAG_SYNC_CONFIRM = !{JSON.stringify(sync_confirm)};
|
|
93
|
+
window.JXP_DIAG_MODEL_FILTER_OPTIONS = !{JSON.stringify(model_filter_options)};
|
|
94
|
+
script(src="/docs/assets/diagnostics.js" defer)
|
package/templates/index.pug
CHANGED
|
@@ -11,6 +11,13 @@ block content
|
|
|
11
11
|
if first_guide_url
|
|
12
12
|
a.btn.btn-primary(href=first_guide_url) Get started
|
|
13
13
|
a.btn.btn-outline-primary(href="/docs/api") Browse API
|
|
14
|
+
if mcp_enabled
|
|
15
|
+
a.btn.btn-outline-primary(href="/docs/mcp")
|
|
16
|
+
i.bi.bi-robot.me-1
|
|
17
|
+
| Ask MCP
|
|
18
|
+
else
|
|
19
|
+
a.btn.btn-outline-secondary(href="/docs/md/mcp.md") MCP setup
|
|
20
|
+
a.btn.btn-outline-secondary(href="/docs/diagnostics") Index diagnostics
|
|
14
21
|
a.btn.btn-outline-secondary(href="/docs/md/api.md") REST reference
|
|
15
22
|
|
|
16
23
|
.row.g-4.mb-4
|
|
@@ -34,6 +41,37 @@ block content
|
|
|
34
41
|
| panels on API resource pages. Keys are stored in the browser only when “Remember” is checked.
|
|
35
42
|
.col-md-6
|
|
36
43
|
.card.h-100
|
|
44
|
+
.card-header.fw-semibold
|
|
45
|
+
if mcp_enabled
|
|
46
|
+
| MCP for AI agents
|
|
47
|
+
else
|
|
48
|
+
| MCP (Model Context Protocol)
|
|
49
|
+
.card-body
|
|
50
|
+
if mcp_enabled
|
|
51
|
+
p.mb-2
|
|
52
|
+
| This server exposes read-only MCP tools at
|
|
53
|
+
code= mcp_path
|
|
54
|
+
| — use them from Cursor, LM Studio, or the built-in assistant.
|
|
55
|
+
pre.mb-3
|
|
56
|
+
code.
|
|
57
|
+
MCP_ENABLED=true
|
|
58
|
+
# Cursor: npx -y jxp-mcp
|
|
59
|
+
a.btn.btn-sm.btn-primary(href="/docs/mcp") Open MCP assistant
|
|
60
|
+
a.btn.btn-sm.btn-outline-secondary.ms-2(href="/docs/md/mcp.md") Setup guide
|
|
61
|
+
else
|
|
62
|
+
p.mb-2
|
|
63
|
+
| Enable
|
|
64
|
+
code MCP_ENABLED=true
|
|
65
|
+
| to expose five fixed read-only tools (
|
|
66
|
+
code jxp_list_models
|
|
67
|
+
| ,
|
|
68
|
+
code jxp_find
|
|
69
|
+
| , …) for AI clients and a built-in docs playground.
|
|
70
|
+
a.btn.btn-sm.btn-outline-primary(href="/docs/md/mcp.md") Read MCP docs
|
|
71
|
+
|
|
72
|
+
.row.g-4.mb-4
|
|
73
|
+
.col-12
|
|
74
|
+
.card
|
|
37
75
|
.card-header.fw-semibold API at a glance
|
|
38
76
|
.card-body
|
|
39
77
|
p.mb-2
|