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.
Files changed (135) hide show
  1. package/.env.sample +30 -0
  2. package/README.md +2 -0
  3. package/dist/bin/index_audit.d.ts +3 -0
  4. package/dist/bin/index_audit.d.ts.map +1 -0
  5. package/dist/bin/index_audit.js +66 -0
  6. package/dist/bin/index_audit.js.map +1 -0
  7. package/dist/bin/mcp-stdio-bridge.d.ts +3 -0
  8. package/dist/bin/mcp-stdio-bridge.d.ts.map +1 -0
  9. package/dist/bin/mcp-stdio-bridge.js +87 -0
  10. package/dist/bin/mcp-stdio-bridge.js.map +1 -0
  11. package/dist/bin/server.d.ts.map +1 -1
  12. package/dist/bin/server.js +8 -2
  13. package/dist/bin/server.js.map +1 -1
  14. package/dist/libs/builtin_models.d.ts +21 -0
  15. package/dist/libs/builtin_models.d.ts.map +1 -0
  16. package/dist/libs/builtin_models.js +125 -0
  17. package/dist/libs/builtin_models.js.map +1 -0
  18. package/dist/libs/docs-auth.d.ts.map +1 -1
  19. package/dist/libs/docs-auth.js +6 -2
  20. package/dist/libs/docs-auth.js.map +1 -1
  21. package/dist/libs/docs.js +105 -44
  22. package/dist/libs/docs.js.map +1 -1
  23. package/dist/libs/groups.js +3 -4
  24. package/dist/libs/groups.js.map +1 -1
  25. package/dist/libs/index_diagnostics.d.ts +144 -0
  26. package/dist/libs/index_diagnostics.d.ts.map +1 -0
  27. package/dist/libs/index_diagnostics.js +671 -0
  28. package/dist/libs/index_diagnostics.js.map +1 -0
  29. package/dist/libs/jxp.d.ts.map +1 -1
  30. package/dist/libs/jxp.js +97 -331
  31. package/dist/libs/jxp.js.map +1 -1
  32. package/dist/libs/load-config.d.ts +2 -0
  33. package/dist/libs/load-config.d.ts.map +1 -1
  34. package/dist/libs/load-config.js +10 -0
  35. package/dist/libs/load-config.js.map +1 -1
  36. package/dist/libs/login.js +3 -8
  37. package/dist/libs/login.js.map +1 -1
  38. package/dist/libs/mcp/auth.d.ts +4 -0
  39. package/dist/libs/mcp/auth.d.ts.map +1 -0
  40. package/dist/libs/mcp/auth.js +49 -0
  41. package/dist/libs/mcp/auth.js.map +1 -0
  42. package/dist/libs/mcp/config.d.ts +21 -0
  43. package/dist/libs/mcp/config.d.ts.map +1 -0
  44. package/dist/libs/mcp/config.js +66 -0
  45. package/dist/libs/mcp/config.js.map +1 -0
  46. package/dist/libs/mcp/context.d.ts +5 -0
  47. package/dist/libs/mcp/context.d.ts.map +1 -0
  48. package/dist/libs/mcp/context.js +14 -0
  49. package/dist/libs/mcp/context.js.map +1 -0
  50. package/dist/libs/mcp/describe.d.ts +13 -0
  51. package/dist/libs/mcp/describe.d.ts.map +1 -0
  52. package/dist/libs/mcp/describe.js +36 -0
  53. package/dist/libs/mcp/describe.js.map +1 -0
  54. package/dist/libs/mcp/docs_auth.d.ts +5 -0
  55. package/dist/libs/mcp/docs_auth.d.ts.map +1 -0
  56. package/dist/libs/mcp/docs_auth.js +36 -0
  57. package/dist/libs/mcp/docs_auth.js.map +1 -0
  58. package/dist/libs/mcp/dynamic_import.d.ts +6 -0
  59. package/dist/libs/mcp/dynamic_import.d.ts.map +1 -0
  60. package/dist/libs/mcp/dynamic_import.js +12 -0
  61. package/dist/libs/mcp/dynamic_import.js.map +1 -0
  62. package/dist/libs/mcp/execute_tool.d.ts +10 -0
  63. package/dist/libs/mcp/execute_tool.d.ts.map +1 -0
  64. package/dist/libs/mcp/execute_tool.js +65 -0
  65. package/dist/libs/mcp/execute_tool.js.map +1 -0
  66. package/dist/libs/mcp/guides/default.md +67 -0
  67. package/dist/libs/mcp/guides.d.ts +14 -0
  68. package/dist/libs/mcp/guides.d.ts.map +1 -0
  69. package/dist/libs/mcp/guides.js +127 -0
  70. package/dist/libs/mcp/guides.js.map +1 -0
  71. package/dist/libs/mcp/model_visibility.d.ts +31 -0
  72. package/dist/libs/mcp/model_visibility.d.ts.map +1 -0
  73. package/dist/libs/mcp/model_visibility.js +73 -0
  74. package/dist/libs/mcp/model_visibility.js.map +1 -0
  75. package/dist/libs/mcp/mount.d.ts +10 -0
  76. package/dist/libs/mcp/mount.d.ts.map +1 -0
  77. package/dist/libs/mcp/mount.js +68 -0
  78. package/dist/libs/mcp/mount.js.map +1 -0
  79. package/dist/libs/mcp/read_service.d.ts +23 -0
  80. package/dist/libs/mcp/read_service.d.ts.map +1 -0
  81. package/dist/libs/mcp/read_service.js +141 -0
  82. package/dist/libs/mcp/read_service.js.map +1 -0
  83. package/dist/libs/mcp/server.d.ts +23 -0
  84. package/dist/libs/mcp/server.d.ts.map +1 -0
  85. package/dist/libs/mcp/server.js +111 -0
  86. package/dist/libs/mcp/server.js.map +1 -0
  87. package/dist/libs/parse_byte_size.d.ts +10 -0
  88. package/dist/libs/parse_byte_size.d.ts.map +1 -0
  89. package/dist/libs/parse_byte_size.js +80 -0
  90. package/dist/libs/parse_byte_size.js.map +1 -0
  91. package/dist/libs/query_limits.js +99 -13
  92. package/dist/libs/query_limits.js.map +1 -1
  93. package/dist/libs/read_handlers.d.ts +13 -0
  94. package/dist/libs/read_handlers.d.ts.map +1 -0
  95. package/dist/libs/read_handlers.js +381 -0
  96. package/dist/libs/read_handlers.js.map +1 -0
  97. package/dist/libs/schema.js +2 -2
  98. package/dist/libs/schema.js.map +1 -1
  99. package/dist/libs/schema_serialize.d.ts +3 -0
  100. package/dist/libs/schema_serialize.d.ts.map +1 -0
  101. package/dist/libs/schema_serialize.js +45 -0
  102. package/dist/libs/schema_serialize.js.map +1 -0
  103. package/dist/libs/security.js +7 -11
  104. package/dist/libs/security.js.map +1 -1
  105. package/dist/libs/setup.js +3 -4
  106. package/dist/libs/setup.js.map +1 -1
  107. package/dist/libs/startup.d.ts +4 -0
  108. package/dist/libs/startup.d.ts.map +1 -1
  109. package/dist/libs/startup.js +17 -0
  110. package/dist/libs/startup.js.map +1 -1
  111. package/dist/models/indexquerylog_model.d.ts +19 -0
  112. package/dist/models/indexquerylog_model.d.ts.map +1 -0
  113. package/dist/models/indexquerylog_model.js +32 -0
  114. package/dist/models/indexquerylog_model.js.map +1 -0
  115. package/dist/types/jxp-config.d.ts +29 -0
  116. package/dist/types/jxp-config.d.ts.map +1 -1
  117. package/dist/types/schema-fields.d.ts +4 -0
  118. package/dist/types/schema-fields.d.ts.map +1 -1
  119. package/docs/api.md +16 -2
  120. package/docs/changelog.md +101 -1
  121. package/docs/configuration.md +39 -0
  122. package/docs/index.md +2 -0
  123. package/docs/index_diagnostics.md +127 -0
  124. package/docs/mcp.md +134 -0
  125. package/docs/schemas.md +3 -1
  126. package/mkdocs.yml +2 -0
  127. package/package.json +26 -9
  128. package/templates/assets/diagnostics.js +352 -0
  129. package/templates/assets/docs.css +107 -0
  130. package/templates/assets/mcp-console.js +343 -0
  131. package/templates/diagnostics.pug +94 -0
  132. package/templates/index.pug +38 -0
  133. package/templates/mcp.pug +112 -0
  134. package/templates/navbar.pug +2 -0
  135. 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, "&amp;")
116
+ .replace(/</g, "&lt;")
117
+ .replace(/>/g, "&gt;")
118
+ .replace(/"/g, "&quot;");
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)
@@ -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