jxp 4.2.0 → 5.0.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/.env.sample +16 -0
- package/README.md +2 -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 +2 -1
- package/dist/bin/server.js.map +1 -1
- package/dist/globals.d.ts +2 -2
- package/dist/globals.d.ts.map +1 -1
- package/dist/libs/docs-auth.d.ts.map +1 -1
- package/dist/libs/docs-auth.js +4 -2
- package/dist/libs/docs-auth.js.map +1 -1
- package/dist/libs/docs.js +74 -43
- package/dist/libs/docs.js.map +1 -1
- package/dist/libs/jxp.d.ts.map +1 -1
- package/dist/libs/jxp.js +22 -321
- package/dist/libs/jxp.js.map +1 -1
- package/dist/libs/load-config.d.ts +1 -1
- package/dist/libs/load-config.d.ts.map +1 -1
- package/dist/libs/load-config.js +2 -2
- package/dist/libs/load-config.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/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/request_log.d.ts +2 -0
- package/dist/libs/request_log.d.ts.map +1 -1
- package/dist/libs/request_log.js +7 -1
- package/dist/libs/request_log.js.map +1 -1
- package/dist/libs/schema.d.ts.map +1 -1
- package/dist/libs/schema.js +5 -8
- package/dist/libs/schema.js.map +1 -1
- package/dist/libs/schema_description.d.ts +7 -1
- package/dist/libs/schema_description.d.ts.map +1 -1
- package/dist/libs/schema_description.js +24 -25
- package/dist/libs/schema_description.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/types/jxp-config.d.ts +7 -0
- package/dist/types/jxp-config.d.ts.map +1 -1
- package/docs/changelog.md +61 -0
- package/docs/index.md +1 -0
- package/docs/mcp.md +134 -0
- package/mkdocs.yml +1 -0
- package/package.json +31 -26
- package/templates/assets/docs.css +107 -0
- package/templates/assets/mcp-console.js +343 -0
- package/templates/index.pug +37 -0
- package/templates/mcp.pug +112 -0
- package/templates/navbar.pug +2 -0
- package/templates/sidebar.pug +4 -0
|
@@ -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
|
+
})();
|
package/templates/index.pug
CHANGED
|
@@ -11,6 +11,12 @@ 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
|
|
14
20
|
a.btn.btn-outline-secondary(href="/docs/diagnostics") Index diagnostics
|
|
15
21
|
a.btn.btn-outline-secondary(href="/docs/md/api.md") REST reference
|
|
16
22
|
|
|
@@ -35,6 +41,37 @@ block content
|
|
|
35
41
|
| panels on API resource pages. Keys are stored in the browser only when “Remember” is checked.
|
|
36
42
|
.col-md-6
|
|
37
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
|
|
38
75
|
.card-header.fw-semibold API at a glance
|
|
39
76
|
.card-body
|
|
40
77
|
p.mb-2
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
extends layout
|
|
2
|
+
|
|
3
|
+
block content
|
|
4
|
+
h1.mb-1 MCP assistant
|
|
5
|
+
p.text-muted.mb-4
|
|
6
|
+
| Connect Cursor or LM Studio to this API, then try the playground below.
|
|
7
|
+
|
|
8
|
+
.card.mb-4.mcp-setup-card(
|
|
9
|
+
data-mcp-url=mcp_url
|
|
10
|
+
data-base-url=base_url
|
|
11
|
+
data-mcp-path=mcp_path
|
|
12
|
+
data-server-slug=mcp_server_slug
|
|
13
|
+
)
|
|
14
|
+
.card-header.fw-semibold
|
|
15
|
+
i.bi.bi-plug.me-1
|
|
16
|
+
| Add to your MCP config
|
|
17
|
+
.card-body
|
|
18
|
+
p.mb-2
|
|
19
|
+
| Merge into
|
|
20
|
+
code mcp.json
|
|
21
|
+
| (Cursor:
|
|
22
|
+
strong Cursor Settings → MCP
|
|
23
|
+
| ; LM Studio: server settings). Your API key is filled in from your session.
|
|
24
|
+
ul.nav.nav-tabs#mcpConfigTabs(role="tablist")
|
|
25
|
+
li.nav-item(role="presentation")
|
|
26
|
+
button.nav-link.active#mcp-http-tab(data-bs-toggle="tab" data-bs-target="#mcp-http-pane" type="button" role="tab" aria-controls="mcp-http-pane" aria-selected="true") Cursor / HTTP
|
|
27
|
+
li.nav-item(role="presentation")
|
|
28
|
+
button.nav-link#mcp-stdio-tab(data-bs-toggle="tab" data-bs-target="#mcp-stdio-pane" type="button" role="tab" aria-controls="mcp-stdio-pane" aria-selected="false") stdio (jxp-mcp)
|
|
29
|
+
.tab-content.border.border-top-0.rounded-bottom.p-3.bg-light#mcpConfigTabContent
|
|
30
|
+
.tab-pane.fade.show.active#mcp-http-pane(role="tabpanel" aria-labelledby="mcp-http-tab" tabindex="0")
|
|
31
|
+
p.small.text-muted.mb-2 Recommended for Cursor — receives server instructions on connect.
|
|
32
|
+
.position-relative
|
|
33
|
+
pre.mcp-json-block.mb-2#mcpJsonHttp(data-config="http")= mcp_http_json
|
|
34
|
+
button.btn.btn-sm.btn-outline-secondary.mcp-copy-btn(type="button" data-target="mcpJsonHttp")
|
|
35
|
+
i.bi.bi-clipboard.me-1
|
|
36
|
+
| Copy
|
|
37
|
+
.tab-pane.fade#mcp-stdio-pane(role="tabpanel" aria-labelledby="mcp-stdio-tab" tabindex="0")
|
|
38
|
+
p.small.text-muted.mb-2 Uses
|
|
39
|
+
code npx jxp-mcp
|
|
40
|
+
| to proxy stdio → HTTP (tools, resources, instructions).
|
|
41
|
+
.position-relative
|
|
42
|
+
pre.mcp-json-block.mb-2#mcpJsonStdio(data-config="stdio")= mcp_stdio_json
|
|
43
|
+
button.btn.btn-sm.btn-outline-secondary.mcp-copy-btn(type="button" data-target="mcpJsonStdio")
|
|
44
|
+
i.bi.bi-clipboard.me-1
|
|
45
|
+
| Copy
|
|
46
|
+
p.small.text-muted.mb-0.mt-3
|
|
47
|
+
| HTTP endpoint:
|
|
48
|
+
code= mcp_url
|
|
49
|
+
| ·
|
|
50
|
+
a(href="/docs/md/mcp.md") Full MCP docs
|
|
51
|
+
|
|
52
|
+
h2.h5.mb-3 Try it here
|
|
53
|
+
p.text-muted.mb-3.small
|
|
54
|
+
| Same read-only tools and permissions as
|
|
55
|
+
code GET /api
|
|
56
|
+
| . Commands below call
|
|
57
|
+
code POST /docs/mcp/call
|
|
58
|
+
| with your signed-in key.
|
|
59
|
+
|
|
60
|
+
.mcp-chat-wrap
|
|
61
|
+
.mcp-chat-thread#mcpChatThread(role="log" aria-live="polite" aria-label="MCP conversation")
|
|
62
|
+
.mcp-msg.mcp-msg-bot
|
|
63
|
+
.mcp-msg-label Assistant
|
|
64
|
+
.mcp-msg-body
|
|
65
|
+
p.mb-2 Ask about your data — or try:
|
|
66
|
+
ul.small.mb-0
|
|
67
|
+
li
|
|
68
|
+
code list models
|
|
69
|
+
li
|
|
70
|
+
code describe MODEL
|
|
71
|
+
li
|
|
72
|
+
code count MODEL
|
|
73
|
+
li
|
|
74
|
+
code find MODEL limit=5
|
|
75
|
+
li
|
|
76
|
+
code help
|
|
77
|
+
|
|
78
|
+
.mcp-chat-input-wrap
|
|
79
|
+
.d-flex.flex-wrap.gap-2.mb-2
|
|
80
|
+
each action in [{label: 'List models', cmd: 'list models'}, {label: 'Help', cmd: 'help'}]
|
|
81
|
+
button.btn.btn-sm.btn-outline-secondary.mcp-quick-btn(type="button" data-cmd=action.cmd)= action.label
|
|
82
|
+
form#mcpChatForm
|
|
83
|
+
.input-group
|
|
84
|
+
input#mcpChatInput.form-control(type="text" placeholder="Ask about your data…" autocomplete="off" aria-label="MCP message")
|
|
85
|
+
button.btn.btn-primary(type="submit")
|
|
86
|
+
i.bi.bi-send-fill.me-1
|
|
87
|
+
| Send
|
|
88
|
+
|
|
89
|
+
details.mt-4
|
|
90
|
+
summary.fw-semibold Advanced — call a tool directly
|
|
91
|
+
p.small.text-muted.mt-2 Pick a tool and pass JSON arguments (same as Cursor/LM Studio MCP clients).
|
|
92
|
+
.mcp-tool-panel.mt-2
|
|
93
|
+
.row.g-2.mb-2
|
|
94
|
+
.col-md-4
|
|
95
|
+
label.form-label.small.fw-semibold Tool
|
|
96
|
+
select#mcpToolSelect.form-select.form-select-sm
|
|
97
|
+
option(value="jxp_list_models") jxp_list_models
|
|
98
|
+
option(value="jxp_describe_model") jxp_describe_model
|
|
99
|
+
option(value="jxp_find") jxp_find
|
|
100
|
+
option(value="jxp_count") jxp_count
|
|
101
|
+
option(value="jxp_export_csv") jxp_export_csv
|
|
102
|
+
.col-md-8
|
|
103
|
+
label.form-label.small.fw-semibold Arguments (JSON)
|
|
104
|
+
textarea#mcpToolArgs.form-control.form-control-sm.font-monospace(rows="4") {}
|
|
105
|
+
button.btn.btn-sm.btn-outline-primary#mcpToolRun(type="button")
|
|
106
|
+
i.bi.bi-play-fill.me-1
|
|
107
|
+
| Run tool
|
|
108
|
+
|
|
109
|
+
block javascripts
|
|
110
|
+
script.
|
|
111
|
+
window.__MCP_SETUP__ = !{JSON.stringify({ mcpUrl: mcp_url || '', baseUrl: base_url || '', mcpPath: mcp_path || '/mcp', serverSlug: mcp_server_slug || 'jxp' })};
|
|
112
|
+
script(src="/docs/assets/mcp-console.js" defer)
|
package/templates/navbar.pug
CHANGED
|
@@ -7,6 +7,8 @@ header.docs-topbar.navbar.navbar-expand-lg.sticky-top
|
|
|
7
7
|
.ms-auto.d-flex.align-items-center.gap-2.flex-wrap
|
|
8
8
|
if docs_access === 'protected' && !docs_authenticated
|
|
9
9
|
a.btn.btn-sm.btn-outline-primary(href="/docs/login?next=/docs/api") Explore API
|
|
10
|
+
if mcp_enabled
|
|
11
|
+
a.btn.btn-sm.btn-outline-primary(href="/docs/login?next=/docs/mcp") Ask MCP
|
|
10
12
|
if docs_access !== 'disabled' && (docs_access !== 'protected' || docs_authenticated)
|
|
11
13
|
.docs-api-key-wrap
|
|
12
14
|
label.visually-hidden(for="docs-api-key") API Key
|
package/templates/sidebar.pug
CHANGED
|
@@ -7,6 +7,10 @@ nav.docs-sidebar#docsSidebarDesktop(aria-label="Documentation navigation")
|
|
|
7
7
|
a.docs-nav-link(href="/docs/md/" + file class=(active_guide === file ? 'active' : ''))= label
|
|
8
8
|
.docs-sidebar-section
|
|
9
9
|
.docs-sidebar-title Operations
|
|
10
|
+
if mcp_enabled
|
|
11
|
+
a.docs-nav-link(href="/docs/mcp" class=(active_section === 'mcp' ? 'active' : ''))
|
|
12
|
+
i.bi.bi-robot.me-1
|
|
13
|
+
| MCP assistant
|
|
10
14
|
a.docs-nav-link(href="/docs/diagnostics" class=(active_section === 'diagnostics' ? 'active' : ''))
|
|
11
15
|
i.bi.bi-speedometer2.me-1
|
|
12
16
|
| Index diagnostics
|