dsh-mcp-panel 0.3.0 → 0.4.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 (71) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.es.md +48 -109
  3. package/README.hi.md +48 -109
  4. package/README.md +90 -79
  5. package/README.pt.md +48 -109
  6. package/README.zh.md +96 -86
  7. package/cordis.patch.yml +17 -4
  8. package/lib/client.js +1350 -48
  9. package/lib/client.js.map +1 -1
  10. package/lib/index.js +1191 -46
  11. package/lib/schemas-BhI7GrM5.js +4091 -0
  12. package/lib/typert.host.js +193 -4099
  13. package/lib/types/aggregate.d.ts +18 -1
  14. package/lib/types/aggregate.d.ts.map +1 -1
  15. package/lib/types/client/McpPanelTab.d.ts +12 -6
  16. package/lib/types/client/McpPanelTab.d.ts.map +1 -1
  17. package/lib/types/client/ServerEditor.d.ts +25 -0
  18. package/lib/types/client/ServerEditor.d.ts.map +1 -0
  19. package/lib/types/client/TrialConsole.d.ts +20 -0
  20. package/lib/types/client/TrialConsole.d.ts.map +1 -0
  21. package/lib/types/client/index.d.ts +7 -5
  22. package/lib/types/client/index.d.ts.map +1 -1
  23. package/lib/types/client/locales.d.ts +157 -1
  24. package/lib/types/client/locales.d.ts.map +1 -1
  25. package/lib/types/client/present.d.ts +6 -0
  26. package/lib/types/client/present.d.ts.map +1 -1
  27. package/lib/types/client/remote.d.ts +175 -2
  28. package/lib/types/client/remote.d.ts.map +1 -1
  29. package/lib/types/command.d.ts +32 -3
  30. package/lib/types/command.d.ts.map +1 -1
  31. package/lib/types/config.d.ts +41 -1
  32. package/lib/types/config.d.ts.map +1 -1
  33. package/lib/types/diagnostics.d.ts +55 -0
  34. package/lib/types/diagnostics.d.ts.map +1 -0
  35. package/lib/types/index.d.ts +32 -15
  36. package/lib/types/index.d.ts.map +1 -1
  37. package/lib/types/patch.d.ts +176 -0
  38. package/lib/types/patch.d.ts.map +1 -0
  39. package/lib/types/service.d.ts +73 -8
  40. package/lib/types/service.d.ts.map +1 -1
  41. package/lib/types/trial.d.ts +72 -0
  42. package/lib/types/trial.d.ts.map +1 -0
  43. package/lib/types/typert.host.d.ts +164 -0
  44. package/lib/types/typert.host.d.ts.map +1 -1
  45. package/lib/types/upstream.d.ts +44 -15
  46. package/lib/types/upstream.d.ts.map +1 -1
  47. package/lib/types/wire.d.ts +507 -10
  48. package/lib/types/wire.d.ts.map +1 -1
  49. package/lib/types/write.d.ts +30 -0
  50. package/lib/types/write.d.ts.map +1 -0
  51. package/package.json +44 -2
  52. package/src/aggregate.ts +79 -2
  53. package/src/client/McpPanelTab.tsx +138 -17
  54. package/src/client/ServerEditor.tsx +290 -0
  55. package/src/client/TrialConsole.tsx +145 -0
  56. package/src/client/index.ts +45 -18
  57. package/src/client/locales.ts +169 -5
  58. package/src/client/present.ts +9 -0
  59. package/src/client/remote.ts +17 -2
  60. package/src/client/styles.ts +201 -0
  61. package/src/command.ts +139 -18
  62. package/src/config.ts +88 -2
  63. package/src/diagnostics.ts +126 -0
  64. package/src/index.ts +49 -16
  65. package/src/patch.ts +493 -0
  66. package/src/probe.ts +1 -1
  67. package/src/service.ts +254 -9
  68. package/src/trial.ts +192 -0
  69. package/src/upstream.ts +45 -15
  70. package/src/wire.ts +292 -10
  71. package/src/write.ts +97 -0
package/lib/client.js CHANGED
@@ -113,7 +113,10 @@ window.__ModuleLoader__.load({
113
113
  empty: snapshot.servers.length === 0,
114
114
  observed: snapshot.observed,
115
115
  patchFile: snapshot.patchFile,
116
- refreshIntervalMs: snapshot.refreshIntervalMs
116
+ refreshIntervalMs: snapshot.refreshIntervalMs,
117
+ capabilities: snapshot.capabilities,
118
+ trial: snapshot.trial,
119
+ writeEnabled: snapshot.writeEnabled
117
120
  };
118
121
  }
119
122
  /**
@@ -150,8 +153,585 @@ window.__ModuleLoader__.load({
150
153
  return servers.filter((row) => row.view.serverName.toLocaleLowerCase().includes(needle) || row.view.target.toLocaleLowerCase().includes(needle));
151
154
  }
152
155
  //#endregion
156
+ //#region src/client/ServerEditor.tsx
157
+ /** Server CRUD editor: form → patch fragment → copy or approval-gated write. */
158
+ let rowCounter = 0;
159
+ /** Render the CRUD editor. */
160
+ function ServerEditor({ t, view, entryId, writeEnabled, actions, onClose, onWritten }) {
161
+ const isEdit = view !== null;
162
+ const [serverName, setServerName] = (0, react.useState)(view?.serverName ?? "");
163
+ const [transport, setTransport] = (0, react.useState)(view?.transport === "streamable-http" ? "streamable-http" : "stdio");
164
+ const [command, setCommand] = (0, react.useState)(view?.command ?? "");
165
+ const [args, setArgs] = (0, react.useState)((view?.args ?? []).join("\n"));
166
+ const [cwd, setCwd] = (0, react.useState)(view?.cwd ?? "");
167
+ const [url, setUrl] = (0, react.useState)(view?.url ?? "");
168
+ const [timeout, setTimeoutText] = (0, react.useState)(view?.toolCallTimeoutMs === null ? "" : String(view?.toolCallTimeoutMs));
169
+ const [failFast, setFailFast] = (0, react.useState)(view?.failOnStartupError === true);
170
+ const [reconnectEnabled, setReconnectEnabled] = (0, react.useState)(view === null || view.reconnectEnabled === null ? true : view.reconnectEnabled);
171
+ const [reconnectAttempts, setReconnectAttempts] = (0, react.useState)(view === null || view.reconnectMaxAttempts === null ? "" : String(view.reconnectMaxAttempts));
172
+ const [envRows, setEnvRows] = (0, react.useState)(() => initialRows(view?.envKeys ?? []));
173
+ const [headerRows, setHeaderRows] = (0, react.useState)(() => initialRows(view?.headerKeys ?? []));
174
+ const [preview, setPreview] = (0, react.useState)(null);
175
+ const [busy, setBusy] = (0, react.useState)(false);
176
+ const [armed, setArmed] = (0, react.useState)(false);
177
+ const [error, setError] = (0, react.useState)(null);
178
+ const [notice, setNotice] = (0, react.useState)(null);
179
+ const [written, setWritten] = (0, react.useState)(null);
180
+ const rowsToMap = (rows, existingKeys) => {
181
+ const entries = {};
182
+ const keep = [];
183
+ for (const row of rows) {
184
+ const key = row.key.trim();
185
+ if (key === "") continue;
186
+ if (existingKeys.includes(key) && row.value === "") keep.push(key);
187
+ else entries[key] = row.value;
188
+ }
189
+ return {
190
+ entries,
191
+ keep
192
+ };
193
+ };
194
+ const buildOp = () => {
195
+ const existingEnv = view?.envKeys ?? [];
196
+ const existingHeaders = view?.headerKeys ?? [];
197
+ const env = rowsToMap(envRows, existingEnv);
198
+ const headers = rowsToMap(headerRows, existingHeaders);
199
+ const config = {
200
+ serverName: serverName.trim(),
201
+ transport,
202
+ ...transport === "stdio" ? {
203
+ command: command.trim(),
204
+ ...args.trim() === "" ? {} : { args: args.split("\n").map((line) => line.trim()).filter((line) => line !== "") },
205
+ ...cwd.trim() === "" ? {} : { cwd: cwd.trim() },
206
+ env: env.entries,
207
+ ...isEdit ? { keepEnv: env.keep } : {}
208
+ } : {
209
+ url: url.trim(),
210
+ headers: headers.entries,
211
+ ...isEdit ? { keepHeaders: headers.keep } : {}
212
+ },
213
+ ...timeout.trim() === "" ? {} : { toolCallTimeoutMs: Number(timeout) },
214
+ ...failFast ? { failOnStartupError: true } : {},
215
+ ...reconnectEnabled === false ? { reconnectEnabled: false } : {},
216
+ ...reconnectAttempts.trim() === "" ? {} : { reconnectMaxAttempts: Number(reconnectAttempts) }
217
+ };
218
+ return isEdit ? {
219
+ kind: "edit",
220
+ entryId,
221
+ config
222
+ } : {
223
+ kind: "add",
224
+ config
225
+ };
226
+ };
227
+ const doPreview = () => {
228
+ setError(null);
229
+ setNotice(null);
230
+ setWritten(null);
231
+ setArmed(false);
232
+ setBusy(true);
233
+ Promise.resolve().then(() => actions.previewPatch(JSON.stringify(buildOp()))).then((result) => {
234
+ setPreview(result);
235
+ setBusy(false);
236
+ }, (failure) => {
237
+ setError(messageOf(failure));
238
+ setBusy(false);
239
+ });
240
+ };
241
+ const doWrite = () => {
242
+ if (preview === null) return;
243
+ setError(null);
244
+ setNotice(null);
245
+ setBusy(true);
246
+ Promise.resolve().then(() => actions.writePatch(JSON.stringify(buildOp()), true)).then((result) => {
247
+ setWritten(result);
248
+ setBusy(false);
249
+ setArmed(false);
250
+ setNotice(t("writeDone").replace("{file}", result.file).replace("{backup}", result.backupPath));
251
+ onWritten();
252
+ }, (failure) => {
253
+ setError(t("writeFailed").replace("{message}", messageOf(failure)));
254
+ setBusy(false);
255
+ });
256
+ };
257
+ const copy = () => {
258
+ if (preview === null) return;
259
+ navigator.clipboard?.writeText(preview.fragment).then(() => {
260
+ setNotice(t("patchCopied"));
261
+ }, (failure) => {
262
+ setError(t("copyFailed").replace("{message}", messageOf(failure)));
263
+ });
264
+ };
265
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
266
+ className: "dmcp-editor",
267
+ "data-mcp-editor": isEdit ? entryId : "new",
268
+ children: [
269
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
270
+ className: "dmcp-heading",
271
+ children: isEdit ? t("editorTitleEdit").replace("{name}", view?.serverName ?? "") : t("editorTitleAdd")
272
+ }),
273
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
274
+ className: "dmcp-form",
275
+ children: [
276
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
277
+ className: "dmcp-field",
278
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("fieldServerName") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
279
+ type: "text",
280
+ value: serverName,
281
+ onChange: (event) => {
282
+ setServerName(event.currentTarget.value);
283
+ }
284
+ })]
285
+ }),
286
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
287
+ className: "dmcp-field",
288
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("fieldTransport") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
289
+ value: transport,
290
+ onChange: (event) => {
291
+ setTransport(event.currentTarget.value === "streamable-http" ? "streamable-http" : "stdio");
292
+ },
293
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
294
+ value: "stdio",
295
+ children: "stdio"
296
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
297
+ value: "streamable-http",
298
+ children: "streamable-http"
299
+ })]
300
+ })]
301
+ }),
302
+ transport === "stdio" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
303
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
304
+ className: "dmcp-field",
305
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("fieldCommand") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
306
+ type: "text",
307
+ value: command,
308
+ onChange: (event) => {
309
+ setCommand(event.currentTarget.value);
310
+ }
311
+ })]
312
+ }),
313
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
314
+ className: "dmcp-field",
315
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("fieldArgs") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
316
+ rows: 3,
317
+ value: args,
318
+ onChange: (event) => {
319
+ setArgs(event.currentTarget.value);
320
+ }
321
+ })]
322
+ }),
323
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
324
+ className: "dmcp-field",
325
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("fieldCwd") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
326
+ type: "text",
327
+ value: cwd,
328
+ onChange: (event) => {
329
+ setCwd(event.currentTarget.value);
330
+ }
331
+ })]
332
+ }),
333
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MapEditor, {
334
+ label: t("fieldEnv"),
335
+ rows: envRows,
336
+ setRows: setEnvRows,
337
+ t
338
+ })
339
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
340
+ className: "dmcp-field",
341
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("fieldUrl") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
342
+ type: "text",
343
+ value: url,
344
+ onChange: (event) => {
345
+ setUrl(event.currentTarget.value);
346
+ }
347
+ })]
348
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MapEditor, {
349
+ label: t("fieldHeaders"),
350
+ rows: headerRows,
351
+ setRows: setHeaderRows,
352
+ t
353
+ })] }),
354
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
355
+ className: "dmcp-field",
356
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("fieldTimeout") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
357
+ type: "number",
358
+ min: 1,
359
+ value: timeout,
360
+ onChange: (event) => {
361
+ setTimeoutText(event.currentTarget.value);
362
+ }
363
+ })]
364
+ }),
365
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
366
+ className: "dmcp-check",
367
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
368
+ type: "checkbox",
369
+ checked: failFast,
370
+ onChange: (event) => {
371
+ setFailFast(event.currentTarget.checked);
372
+ }
373
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("fieldFailFast") })]
374
+ }),
375
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
376
+ className: "dmcp-check",
377
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
378
+ type: "checkbox",
379
+ checked: reconnectEnabled,
380
+ onChange: (event) => {
381
+ setReconnectEnabled(event.currentTarget.checked);
382
+ }
383
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("fieldReconnectEnabled") })]
384
+ }),
385
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
386
+ className: "dmcp-field",
387
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("fieldReconnectMaxAttempts") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
388
+ type: "number",
389
+ min: 1,
390
+ value: reconnectAttempts,
391
+ onChange: (event) => {
392
+ setReconnectAttempts(event.currentTarget.value);
393
+ }
394
+ })]
395
+ })
396
+ ]
397
+ }),
398
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
399
+ className: "dmcp-editor-actions",
400
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
401
+ type: "button",
402
+ className: "dmcp-action",
403
+ onClick: doPreview,
404
+ disabled: busy,
405
+ children: t("previewPatch")
406
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
407
+ type: "button",
408
+ className: "dmcp-action",
409
+ onClick: onClose,
410
+ disabled: busy,
411
+ children: t("cancel")
412
+ })]
413
+ }),
414
+ error !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
415
+ className: "dmcp-error-text",
416
+ role: "alert",
417
+ children: error
418
+ }) : null,
419
+ notice !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
420
+ className: "dmcp-notice",
421
+ children: notice
422
+ }) : null,
423
+ preview !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
424
+ className: "dmcp-patch",
425
+ children: [
426
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
427
+ className: "dmcp-patch-hint",
428
+ children: preview.file === null ? t("patchFileUnknown") : t("patchFor").replace("{file}", preview.file)
429
+ }),
430
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
431
+ className: "dmcp-fragment",
432
+ children: preview.fragment
433
+ }),
434
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
435
+ className: "dmcp-editor-actions",
436
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
437
+ type: "button",
438
+ className: "dmcp-action",
439
+ onClick: copy,
440
+ children: t("copyPatch")
441
+ }), writeEnabled && preview.file !== null ? armed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
442
+ type: "button",
443
+ className: "dmcp-action dmcp-confirm",
444
+ onClick: doWrite,
445
+ disabled: busy,
446
+ children: t("confirmWrite")
447
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
448
+ type: "button",
449
+ className: "dmcp-action",
450
+ onClick: () => {
451
+ setArmed(true);
452
+ },
453
+ disabled: busy,
454
+ children: t("writeToProfile")
455
+ }) : null]
456
+ }),
457
+ armed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
458
+ className: "dmcp-notice",
459
+ children: t("writeRequiresConfirm")
460
+ }) : null,
461
+ !writeEnabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
462
+ className: "dmcp-error-text",
463
+ children: t("writeDisabled")
464
+ }) : null,
465
+ written !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
466
+ className: "dmcp-notice",
467
+ children: t(prevWrittenLabel(written.approvalPath))
468
+ }) : null
469
+ ]
470
+ }) : null
471
+ ]
472
+ });
473
+ }
474
+ /** Localized approval-channel label for one write result. */
475
+ function prevWrittenLabel(path) {
476
+ return path === "harness-approval" ? "approvalHarness" : "approvalInteractive";
477
+ }
478
+ /** Build the initial row list from existing keys (values left blank = keep). */
479
+ function initialRows(keys) {
480
+ return keys.map((key) => ({
481
+ id: ++rowCounter,
482
+ key,
483
+ value: ""
484
+ }));
485
+ }
486
+ /** Key/value row editor with add/remove; blank values on existing keys mean "keep". */
487
+ function MapEditor({ label, rows, setRows, t }) {
488
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
489
+ className: "dmcp-map",
490
+ children: [
491
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("legend", { children: label }),
492
+ rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
493
+ className: "dmcp-status",
494
+ children: t("none")
495
+ }) : null,
496
+ rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
497
+ className: "dmcp-map-row",
498
+ children: [
499
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
500
+ type: "text",
501
+ className: "dmcp-map-key",
502
+ "aria-label": t("keyColumn"),
503
+ placeholder: t("keyColumn"),
504
+ value: row.key,
505
+ onChange: (event) => {
506
+ setRows((current) => current.map((entry) => entry.id === row.id ? {
507
+ ...entry,
508
+ key: event.currentTarget.value
509
+ } : entry));
510
+ }
511
+ }),
512
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
513
+ type: "text",
514
+ className: "dmcp-map-value",
515
+ "aria-label": t("valueColumn"),
516
+ placeholder: t("unchanged"),
517
+ value: row.value,
518
+ onChange: (event) => {
519
+ setRows((current) => current.map((entry) => entry.id === row.id ? {
520
+ ...entry,
521
+ value: event.currentTarget.value
522
+ } : entry));
523
+ }
524
+ }),
525
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
526
+ type: "button",
527
+ className: "dmcp-action",
528
+ "aria-label": t("removeRow"),
529
+ onClick: () => {
530
+ setRows((current) => current.filter((entry) => entry.id !== row.id));
531
+ },
532
+ children: t("removeRow")
533
+ })
534
+ ]
535
+ }, row.id)),
536
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
537
+ type: "button",
538
+ className: "dmcp-action",
539
+ onClick: () => {
540
+ setRows((current) => [...current, {
541
+ id: ++rowCounter,
542
+ key: "",
543
+ value: ""
544
+ }]);
545
+ },
546
+ children: t("addRow")
547
+ })
548
+ ]
549
+ });
550
+ }
551
+ /** Message text of an arbitrary thrown value. */
552
+ function messageOf(failure) {
553
+ return failure instanceof Error ? failure.message : String(failure);
554
+ }
555
+ //#endregion
556
+ //#region src/client/TrialConsole.tsx
557
+ /** Tool trial console: server → registered mcp__* tool → JSON args → official pipeline. */
558
+ /** Render the trial console. */
559
+ function TrialConsole({ t, servers, policy, callTool }) {
560
+ const configured = servers.filter((server) => server.entryId !== "");
561
+ const [serverName, setServerName] = (0, react.useState)("");
562
+ const [toolName, setToolName] = (0, react.useState)("");
563
+ const [argsText, setArgsText] = (0, react.useState)("{}");
564
+ const [running, setRunning] = (0, react.useState)(false);
565
+ const [result, setResult] = (0, react.useState)(null);
566
+ const [error, setError] = (0, react.useState)(null);
567
+ const server = configured.find((candidate) => candidate.serverName === serverName) ?? configured[0];
568
+ (0, react.useEffect)(() => {
569
+ if (configured.length > 0 && serverName === "") setServerName(configured[0]?.serverName ?? "");
570
+ }, [configured, serverName]);
571
+ const tools = server?.tools ?? [];
572
+ (0, react.useEffect)(() => {
573
+ if (tools.length > 0 && !tools.some((tool) => tool.name === toolName)) setToolName(tools[0]?.name ?? "");
574
+ if (tools.length === 0 && toolName !== "") setToolName("");
575
+ }, [tools, toolName]);
576
+ const run = () => {
577
+ const selectedServer = server;
578
+ if (selectedServer === void 0 || toolName === "") return;
579
+ try {
580
+ JSON.parse(argsText.trim() === "" ? "{}" : argsText);
581
+ } catch {
582
+ setError(t("trialArgsInvalid"));
583
+ return;
584
+ }
585
+ setError(null);
586
+ setResult(null);
587
+ setRunning(true);
588
+ Promise.resolve().then(() => callTool(JSON.stringify({
589
+ serverName: selectedServer.serverName,
590
+ toolName,
591
+ argsJson: argsText.trim() === "" ? "{}" : argsText
592
+ }))).then((outcome) => {
593
+ setResult(outcome);
594
+ setRunning(false);
595
+ }, (failure) => {
596
+ setError(failure instanceof Error ? failure.message : String(failure));
597
+ setRunning(false);
598
+ });
599
+ };
600
+ if (!policy.enabled) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
601
+ className: "dmcp-trial",
602
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
603
+ className: "dmcp-heading",
604
+ children: t("trial")
605
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
606
+ className: "dmcp-status",
607
+ children: t("trialDisabled")
608
+ })]
609
+ });
610
+ const pretty = result === null ? null : prettyJson(result.resultJson);
611
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
612
+ className: "dmcp-trial",
613
+ children: [
614
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
615
+ className: "dmcp-heading",
616
+ children: t("trial")
617
+ }),
618
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
619
+ className: "dmcp-status",
620
+ children: t("trialHint")
621
+ }),
622
+ configured.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
623
+ className: "dmcp-status",
624
+ children: t("empty")
625
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
626
+ className: "dmcp-trial-row",
627
+ children: [
628
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
629
+ className: "dmcp-field",
630
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("trialServer") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
631
+ value: server?.serverName ?? "",
632
+ onChange: (event) => {
633
+ setServerName(event.currentTarget.value);
634
+ setToolName("");
635
+ },
636
+ children: configured.map((candidate) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
637
+ value: candidate.serverName,
638
+ children: candidate.serverName
639
+ }, candidate.serverName))
640
+ })]
641
+ }),
642
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
643
+ className: "dmcp-field",
644
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("trialTool") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
645
+ value: toolName,
646
+ onChange: (event) => {
647
+ setToolName(event.currentTarget.value);
648
+ },
649
+ children: [tools.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
650
+ value: "",
651
+ children: t("trialNoTools")
652
+ }) : null, tools.map((tool) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
653
+ value: tool.name,
654
+ children: tool.name
655
+ }, tool.name))]
656
+ })]
657
+ }),
658
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
659
+ type: "button",
660
+ className: "dmcp-action",
661
+ onClick: run,
662
+ disabled: running || toolName === "",
663
+ children: running ? t("trialRunning") : t("trialRun")
664
+ })
665
+ ]
666
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
667
+ className: "dmcp-field",
668
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("trialArgs") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
669
+ rows: 4,
670
+ spellCheck: false,
671
+ value: argsText,
672
+ onChange: (event) => {
673
+ setArgsText(event.currentTarget.value);
674
+ }
675
+ })]
676
+ })] }),
677
+ error !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
678
+ className: "dmcp-error-text",
679
+ role: "alert",
680
+ children: error
681
+ }) : null,
682
+ result !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
683
+ className: "dmcp-trial-result",
684
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
685
+ className: "dmcp-trial-meta",
686
+ children: [
687
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BadgeTone, {
688
+ ok: !result.isError,
689
+ label: result.isError ? "error" : "ok"
690
+ }),
691
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("trialDuration").replace("{ms}", String(result.durationMs)) }),
692
+ result.truncated ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
693
+ className: "dmcp-warn-text",
694
+ children: t("trialTruncated")
695
+ }) : null,
696
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
697
+ className: "dmcp-trial-call",
698
+ children: result.callId
699
+ })
700
+ ]
701
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
702
+ className: "dmcp-fragment dmcp-trial-json",
703
+ children: pretty
704
+ })]
705
+ }) : null,
706
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
707
+ className: "dmcp-status",
708
+ children: t("trialApprovalNote")
709
+ })
710
+ ]
711
+ });
712
+ }
713
+ /** One tone-colored badge chip (label is visible text). */
714
+ function BadgeTone({ ok, label }) {
715
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
716
+ className: "dmcp-badge",
717
+ "data-tone": ok ? "ok" : "error",
718
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
719
+ className: "dmcp-dot",
720
+ "aria-hidden": "true"
721
+ }), label]
722
+ });
723
+ }
724
+ /** Pretty-print one result JSON; unparseable (truncated) text renders raw. */
725
+ function prettyJson(json) {
726
+ try {
727
+ return JSON.stringify(JSON.parse(json), null, 2);
728
+ } catch {
729
+ return json;
730
+ }
731
+ }
732
+ //#endregion
153
733
  //#region src/client/McpPanelTab.tsx
154
- /** The read-only MCP management tab: server rows, badges, tools, probes. */
734
+ /** The MCP management console tab: server cards, CRUD editor, trial console, capabilities, probes. */
155
735
  /** Localized label for one server badge code. */
156
736
  function badgeLabel(badge, t) {
157
737
  switch (badge) {
@@ -182,8 +762,14 @@ window.__ModuleLoader__.load({
182
762
  const start = format(view.startedAt);
183
763
  return view.finishedAt === null ? start : `${start}–${format(view.finishedAt)}`;
184
764
  }
185
- /** Render the read-only MCP management tab. */
186
- function McpPanelTab({ status, probe, t }) {
765
+ /** Localized text for one diagnostic code (fallback: the wire's English text). */
766
+ function diagnosticText(code, text, t) {
767
+ const key = `diag_${code}`;
768
+ const candidate = t(key);
769
+ return candidate === key ? text : candidate;
770
+ }
771
+ /** Render the MCP management console tab. */
772
+ function McpPanelTab({ status, probe, previewPatch, writePatch, callTool, t }) {
187
773
  const listId = (0, react.useId)();
188
774
  const [request, setRequest] = (0, react.useState)(0);
189
775
  const [expanded, setExpanded] = (0, react.useState)({});
@@ -191,6 +777,9 @@ window.__ModuleLoader__.load({
191
777
  const [state, setState] = (0, react.useState)({ status: "loading" });
192
778
  const [toolQueries, setToolQueries] = (0, react.useState)({});
193
779
  const [serverQuery, setServerQuery] = (0, react.useState)("");
780
+ const [editor, setEditor] = (0, react.useState)(null);
781
+ const [removeArm, setRemoveArm] = (0, react.useState)(null);
782
+ const [removeError, setRemoveError] = (0, react.useState)(null);
194
783
  const reload = () => {
195
784
  setRequest((value) => value + 1);
196
785
  };
@@ -231,7 +820,7 @@ window.__ModuleLoader__.load({
231
820
  const visibleServers = model !== void 0 ? filterServers(model.servers, serverQuery) : [];
232
821
  const summary = model !== void 0 ? summarizePanel(visibleServers) : null;
233
822
  const summaryText = summary === null ? "" : t("summary").replace("{total}", String(summary.total)).replace("{connected}", String(summary.connected)).replace("{errored}", String(summary.errored));
234
- const probeRunning = model !== void 0 && model.probes.some((probe) => probe.view.status === "running");
823
+ const probeRunning = model !== void 0 && model.probes.some((probeRow) => probeRow.view.status === "running");
235
824
  (0, react.useEffect)(() => {
236
825
  if (!probeRunning) return void 0;
237
826
  const timer = setInterval(() => {
@@ -328,7 +917,7 @@ window.__ModuleLoader__.load({
328
917
  const open = expanded[row.view.serverName] === true;
329
918
  const detailId = `${listId}-${encodeURIComponent(row.view.serverName)}`;
330
919
  const toolQuery = toolQueries[row.view.serverName] ?? "";
331
- const rowProbeRunning = model.probes.some((probe) => probe.view.status === "running" && probe.view.serverName === row.view.serverName);
920
+ const rowProbeRunning = model.probes.some((probeRow) => probeRow.view.status === "running" && probeRow.view.serverName === row.view.serverName);
332
921
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
333
922
  className: "dmcp-card",
334
923
  "data-mcp-server": row.view.serverName,
@@ -395,9 +984,36 @@ window.__ModuleLoader__.load({
395
984
  row.view.delayMs,
396
985
  " ",
397
986
  t("ms")
398
- ] })] }) : null
987
+ ] })] }) : null,
988
+ row.view.exitCode !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("exitCode") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: row.view.exitCode })] }) : null,
989
+ row.view.stderrTail !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("stderrTail") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: row.view.stderrTail })] }) : null
399
990
  ]
400
991
  }),
992
+ row.view.diagnostics.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
993
+ className: "dmcp-health",
994
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
995
+ className: "dmcp-health-title",
996
+ children: t("healthSuggestions")
997
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
998
+ className: "dmcp-health-list",
999
+ children: row.view.diagnostics.map((diagnostic) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
1000
+ diagnosticText(diagnostic.code, diagnostic.text, t),
1001
+ " ",
1002
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("code", {
1003
+ className: "dmcp-diag-code",
1004
+ children: [
1005
+ "(",
1006
+ diagnostic.code,
1007
+ ")"
1008
+ ]
1009
+ })
1010
+ ] }, diagnostic.code))
1011
+ })]
1012
+ }) : null,
1013
+ row.view.exitCode === null && row.view.stderrTail === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1014
+ className: "dmcp-status",
1015
+ children: t("healthPending")
1016
+ }) : null,
401
1017
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("code", {
402
1018
  className: "dmcp-target",
403
1019
  title: row.view.target,
@@ -407,6 +1023,70 @@ window.__ModuleLoader__.load({
407
1023
  row.view.target
408
1024
  ]
409
1025
  }),
1026
+ row.view.config !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1027
+ className: "dmcp-editor-actions",
1028
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1029
+ type: "button",
1030
+ className: "dmcp-action",
1031
+ onClick: () => {
1032
+ setEditor({
1033
+ entryId: row.view.entryId,
1034
+ view: row.view.config
1035
+ });
1036
+ },
1037
+ children: t("editServer")
1038
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1039
+ type: "button",
1040
+ className: "dmcp-action dmcp-danger",
1041
+ onClick: () => {
1042
+ setRemoveArm(row.view.serverName);
1043
+ setRemoveError(null);
1044
+ },
1045
+ children: t("removeServer")
1046
+ })]
1047
+ }) : null,
1048
+ removeArm === row.view.serverName && row.view.entryId !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1049
+ className: "dmcp-remove",
1050
+ children: [
1051
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1052
+ className: "dmcp-status",
1053
+ children: t("removeConfirm")
1054
+ }),
1055
+ removeError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1056
+ className: "dmcp-error-text",
1057
+ role: "alert",
1058
+ children: removeError
1059
+ }) : null,
1060
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1061
+ className: "dmcp-editor-actions",
1062
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1063
+ type: "button",
1064
+ className: "dmcp-action dmcp-danger",
1065
+ onClick: () => {
1066
+ setRemoveError(null);
1067
+ Promise.resolve().then(() => writePatch(JSON.stringify({
1068
+ kind: "disable",
1069
+ entryId: row.view.entryId,
1070
+ serverName: row.view.serverName
1071
+ }), true)).then(() => {
1072
+ setRemoveArm(null);
1073
+ reload();
1074
+ }, (error) => {
1075
+ setRemoveError(error instanceof Error ? error.message : String(error));
1076
+ });
1077
+ },
1078
+ children: t("confirmWrite")
1079
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1080
+ type: "button",
1081
+ className: "dmcp-action",
1082
+ onClick: () => {
1083
+ setRemoveArm(null);
1084
+ },
1085
+ children: t("cancel")
1086
+ })]
1087
+ })
1088
+ ]
1089
+ }) : null,
410
1090
  row.view.transport === "streamable-http" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
411
1091
  type: "button",
412
1092
  className: "dmcp-probe-now",
@@ -462,6 +1142,77 @@ window.__ModuleLoader__.load({
462
1142
  probeError
463
1143
  ]
464
1144
  }) : null,
1145
+ model.patchFile !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1146
+ className: "dmcp-patch-hint",
1147
+ children: [
1148
+ t("patchHint"),
1149
+ " ",
1150
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: model.patchFile })
1151
+ ]
1152
+ }) : null,
1153
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1154
+ className: "dmcp-toolbar",
1155
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1156
+ type: "button",
1157
+ className: "dmcp-action",
1158
+ onClick: () => {
1159
+ setEditor({
1160
+ entryId: "",
1161
+ view: null
1162
+ });
1163
+ },
1164
+ children: t("addServer")
1165
+ })
1166
+ }),
1167
+ editor !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ServerEditor, {
1168
+ t,
1169
+ view: editor.view,
1170
+ entryId: editor.entryId,
1171
+ writeEnabled: model.writeEnabled,
1172
+ actions: {
1173
+ previewPatch,
1174
+ writePatch
1175
+ },
1176
+ onClose: () => {
1177
+ setEditor(null);
1178
+ reload();
1179
+ },
1180
+ onWritten: () => {
1181
+ reload();
1182
+ }
1183
+ }) : null,
1184
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrialConsole, {
1185
+ t,
1186
+ servers: model.servers.map((row) => row.view),
1187
+ policy: state.status === "ready" ? state.snapshot.trial : {
1188
+ enabled: false,
1189
+ timeoutMs: 0,
1190
+ maxResultChars: 0
1191
+ },
1192
+ callTool
1193
+ }),
1194
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1195
+ className: "dmcp-capabilities",
1196
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1197
+ className: "dmcp-heading",
1198
+ children: t("capabilities")
1199
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
1200
+ className: "dmcp-capabilities-list",
1201
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
1202
+ tone: model.capabilities.resources.available ? "ok" : "muted",
1203
+ label: t("capResources")
1204
+ }), !model.capabilities.resources.available ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1205
+ className: "dmcp-tool-description",
1206
+ children: t("capPending")
1207
+ }) : null] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
1208
+ tone: model.capabilities.prompts.available ? "ok" : "muted",
1209
+ label: t("capPrompts")
1210
+ }), !model.capabilities.prompts.available ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1211
+ className: "dmcp-tool-description",
1212
+ children: t("capPending")
1213
+ }) : null] })]
1214
+ })]
1215
+ }),
465
1216
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
466
1217
  className: "dmcp-heading",
467
1218
  children: t("probes")
@@ -471,37 +1222,29 @@ window.__ModuleLoader__.load({
471
1222
  children: t("probeEmpty")
472
1223
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
473
1224
  className: "dmcp-probes",
474
- children: model.probes.map((probe) => {
475
- const badge = probeBadge(probe.view.status);
1225
+ children: model.probes.map((probeRow) => {
1226
+ const badge = probeBadge(probeRow.view.status);
476
1227
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
477
1228
  className: "dmcp-probe",
478
- "data-mcp-probe": probe.view.id,
1229
+ "data-mcp-probe": probeRow.view.id,
479
1230
  children: [
480
1231
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
481
1232
  tone: badge.tone,
482
1233
  label: probeLabel(badge.badge, t)
483
1234
  }),
484
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: probe.view.serverName }),
1235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: probeRow.view.serverName }),
485
1236
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
486
1237
  className: "dmcp-probe-time",
487
- children: formatProbeTime(probe.view)
1238
+ children: formatProbeTime(probeRow.view)
488
1239
  }),
489
1240
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
490
1241
  className: "dmcp-probe-detail",
491
- children: probe.view.detail ?? t("none")
1242
+ children: probeRow.view.detail ?? t("none")
492
1243
  })
493
1244
  ]
494
- }, probe.view.id);
1245
+ }, probeRow.view.id);
495
1246
  })
496
- }),
497
- model.patchFile !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
498
- className: "dmcp-patch-hint",
499
- children: [
500
- t("patchHint"),
501
- " ",
502
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: model.patchFile })
503
- ]
504
- }) : null
1247
+ })
505
1248
  ]
506
1249
  }) : null
507
1250
  ]
@@ -522,7 +1265,7 @@ window.__ModuleLoader__.load({
522
1265
  }
523
1266
  //#endregion
524
1267
  //#region src/client/locales.ts
525
- /** Copy dictionaries for the MCP management Settings tab. */
1268
+ /** Copy dictionaries for the MCP management console tab. */
526
1269
  /** Simplified Chinese dictionary and key source of truth. */
527
1270
  const zh = {
528
1271
  tab: "MCP",
@@ -557,7 +1300,7 @@ window.__ModuleLoader__.load({
557
1300
  lastEvent: "最后事件",
558
1301
  filterTools: "过滤工具",
559
1302
  ms: "ms",
560
- derivedNote: "连接状态/重连计数来自上游 mcp/status 事件的提议(docs/upstream-proposal.md);当前未观察到上游数据,以下连接字段为\"未知\",其余为配置与工具注册表事实。",
1303
+ derivedNote: "连接状态/重连计数来自上游 mcp/status 事件;未观察到上游数据时连接字段显示为\"未知\",其余为配置与工具注册表事实。",
561
1304
  probes: "连通性探测",
562
1305
  probeEmpty: "暂无探测记录。用 mcp_probe 工具发起一次探测。",
563
1306
  probeRunning: "运行中",
@@ -570,7 +1313,85 @@ window.__ModuleLoader__.load({
570
1313
  probeReachable: "可达",
571
1314
  probeUnreachable: "不可达",
572
1315
  probeFailedAction: "探测失败",
573
- patchHint: "启停只能通过 patch 建议(/mcp <server> disable|enable)手工应用到"
1316
+ patchHint: "本控制台以\"追加操作\"方式写入 profile patch 层(永不改写既有内容),每次写入前自动备份。",
1317
+ health: "健康诊断",
1318
+ healthSuggestions: "自愈建议",
1319
+ healthNone: "未发现已知故障模式——可观测事实一切正常。",
1320
+ healthPending: "进程退出码 / stderr 尾部:待官方支持(官方 client 尚未暴露进程诊断)。",
1321
+ exitCode: "退出码",
1322
+ stderrTail: "stderr 尾部",
1323
+ diag_commandNotFound: "配置的命令不存在——安装对应包或修正可执行文件路径。",
1324
+ diag_commandSpawnFailed: "服务器进程启动失败或已退出——检查命令行、参数与服务器自身日志。",
1325
+ diag_connectionRefused: "服务器拒绝连接——先启动服务器,或检查 URL 中的主机/端口。",
1326
+ diag_connectionDropped: "连接中途断开——服务器可能崩溃或重启。",
1327
+ diag_timeout: "请求超时——服务器可能过载或 URL 错误;调用慢时可调整 toolCallTimeoutMs。",
1328
+ diag_dns: "主机名无法解析——检查 URL 中的域名与网络。",
1329
+ diag_auth401: "服务器拒绝了凭据(HTTP 401)——检查 Authorization 头或 token。",
1330
+ diag_auth403: "服务器拒绝访问(HTTP 403)——凭据有效但缺少权限。",
1331
+ diag_path404: "端点路径返回 404——检查 URL 路径(例如 /mcp)。",
1332
+ diag_rateLimit: "服务器限流——等待,或降低重连/请求频率。",
1333
+ diag_permission: "权限错误——检查文件权限、PATH 与命令是否可执行。",
1334
+ diag_reconnectExhausted: "重连预算已耗尽、客户端放弃——先修复根因,再重载该行(web 面板热重载 cordis.patch.yml)或重启。",
1335
+ diag_reconnectWaiting: "客户端正在退避等待重连——会自动重试;修复根因即可终止循环。",
1336
+ diag_entryFailed: "cordis 行挂载失败——查看 loader 诊断,修复该行后重载。",
1337
+ diagCodeFallback: "建议",
1338
+ addServer: "添加服务器",
1339
+ editServer: "编辑",
1340
+ removeServer: "删除(停用)",
1341
+ removeConfirm: "删除 = 追加 `set disabled: true` 操作(patch 词汇表没有 remove)。行保留在文件中,可随时重新启用。",
1342
+ editorTitleAdd: "添加 MCP 服务器",
1343
+ editorTitleEdit: "编辑 {name}",
1344
+ fieldServerName: "serverName(命名空间)",
1345
+ fieldTransport: "transport",
1346
+ fieldCommand: "command",
1347
+ fieldArgs: "args(每行一个参数)",
1348
+ fieldCwd: "cwd(可选)",
1349
+ fieldUrl: "url",
1350
+ fieldTimeout: "toolCallTimeoutMs(每次调用超时)",
1351
+ fieldFailFast: "failOnStartupError(启动失败即挂载失败)",
1352
+ fieldReconnectEnabled: "自动重连",
1353
+ fieldReconnectMaxAttempts: "reconnect.maxAttempts",
1354
+ fieldEnv: "env(环境变量)",
1355
+ fieldHeaders: "headers(请求头)",
1356
+ keyColumn: "键",
1357
+ valueColumn: "值",
1358
+ unchanged: "(未改动 = 保留原值)",
1359
+ addRow: "添加一行",
1360
+ removeRow: "删除",
1361
+ save: "保存",
1362
+ cancel: "取消",
1363
+ previewPatch: "生成 patch 片段",
1364
+ copyPatch: "复制片段",
1365
+ patchCopied: "已复制到剪贴板。",
1366
+ copyFailed: "复制失败(剪贴板不可用):{message}",
1367
+ patchFor: "将追加到 {file}:",
1368
+ patchFileUnknown: "无法确定 profile patch 文件路径(无 baseUrl)——只能复制片段。",
1369
+ writeToProfile: "写入 profile",
1370
+ confirmWrite: "确认写入",
1371
+ writeRequiresConfirm: "写入需要确认:请核对上面的片段,然后点击\"确认写入\"。",
1372
+ writeDone: "已写入 {file}(备份:{backup})。web 面板热重载 cordis.patch.yml;其他面板重启生效。",
1373
+ writeFailed: "写入失败:{message}",
1374
+ writeDisabled: "写入已被禁用(config.writeEnabled: false)——只能复制片段。",
1375
+ approvalHarness: "审批通道:ctx.approval",
1376
+ approvalInteractive: "审批通道:界面确认",
1377
+ trial: "工具试用台",
1378
+ trialHint: "通过官方工具管线调用(权限与审批生效);结果只显示在本页,不会进入模型上下文。",
1379
+ trialServer: "服务器",
1380
+ trialTool: "工具",
1381
+ trialArgs: "参数(JSON)",
1382
+ trialRun: "调用",
1383
+ trialRunning: "调用中…",
1384
+ trialResult: "结果(规范 JSON 与 render)",
1385
+ trialDuration: "耗时 {ms} ms",
1386
+ trialTruncated: "结果超过显示上限,已截断。",
1387
+ trialApprovalNote: "审批类调用需要当前会话处于开启轮次;否则 fail-closed 拒绝。",
1388
+ trialDisabled: "试用台已被禁用(config.trialEnabled: false)。",
1389
+ trialNoTools: "该服务器没有已注册工具——服务器可能未连接或同步失败。",
1390
+ trialArgsInvalid: "参数必须是合法 JSON。",
1391
+ capabilities: "能力一览",
1392
+ capResources: "Resources",
1393
+ capPrompts: "Prompts",
1394
+ capPending: "待官方支持——官方 client 尚未桥接该能力(Tools 是当前唯一桥接的 MCP 能力)。"
574
1395
  };
575
1396
  /** English dictionary checked against the Chinese key set. */
576
1397
  const en = {
@@ -606,7 +1427,7 @@ window.__ModuleLoader__.load({
606
1427
  lastEvent: "Last event",
607
1428
  filterTools: "Filter tools",
608
1429
  ms: "ms",
609
- derivedNote: "Connection status and reconnect counts come from the proposed upstream mcp/status seam (docs/upstream-proposal.md); no upstream data was observed, so connection fields read \"unknown\" and the rest are config and tool-registry facts.",
1430
+ derivedNote: "Connection status and reconnect counts come from the upstream mcp/status seam; without observed upstream data, connection fields read \"unknown\" and the rest are config and tool-registry facts.",
610
1431
  probes: "Connectivity probes",
611
1432
  probeEmpty: "No probes yet. Start one with the mcp_probe tool.",
612
1433
  probeRunning: "Running",
@@ -619,7 +1440,85 @@ window.__ModuleLoader__.load({
619
1440
  probeReachable: "Reachable",
620
1441
  probeUnreachable: "Unreachable",
621
1442
  probeFailedAction: "Probe failed",
622
- patchHint: "Enable/disable is a patch suggestion only (/mcp <server> disable|enable); apply it by hand to"
1443
+ patchHint: "This console writes the profile patch layer by APPENDING operations (existing content is never rewritten), and backs up the file before every write.",
1444
+ health: "Health diagnostics",
1445
+ healthSuggestions: "Self-heal suggestions",
1446
+ healthNone: "No known failure pattern detected from the observable facts.",
1447
+ healthPending: "Exit code / stderr tail: pending upstream support (the official client does not expose process diagnostics yet).",
1448
+ exitCode: "Exit code",
1449
+ stderrTail: "stderr tail",
1450
+ diag_commandNotFound: "The configured command was not found — install the package or fix the executable path.",
1451
+ diag_commandSpawnFailed: "The server process failed to start or exited — check the command line, its arguments, and the server's own logs.",
1452
+ diag_connectionRefused: "The server refused the connection — start the server or check the host/port in the URL.",
1453
+ diag_connectionDropped: "The connection dropped mid-session — the server may have crashed or restarted.",
1454
+ diag_timeout: "A request timed out — the server may be overloaded, or the URL may be wrong. Check toolCallTimeoutMs if calls are slow.",
1455
+ diag_dns: "The server hostname did not resolve — check the DNS name in the URL and the network.",
1456
+ diag_auth401: "The server rejected the credentials (HTTP 401) — check the Authorization header or token.",
1457
+ diag_auth403: "The server denied access (HTTP 403) — the credential is valid but lacks permission.",
1458
+ diag_path404: "The endpoint path returned 404 — check the URL path (e.g. /mcp).",
1459
+ diag_rateLimit: "The server rate-limited the client — wait, or reduce the reconnect/request rate.",
1460
+ diag_permission: "A permission error occurred — check file permissions, PATH access, and whether the command is executable.",
1461
+ diag_reconnectExhausted: "The reconnect budget is exhausted and the client has given up — fix the root cause, then reload the row (the web surface hot-reloads cordis.patch.yml edits) or restart.",
1462
+ diag_reconnectWaiting: "The client is waiting in reconnect backoff — it will retry automatically; fix the root cause to stop the cycle.",
1463
+ diag_entryFailed: "The cordis row failed to mount — read the loader diagnostics, then fix the row and reload.",
1464
+ diagCodeFallback: "Suggestion",
1465
+ addServer: "Add server",
1466
+ editServer: "Edit",
1467
+ removeServer: "Remove (disable)",
1468
+ removeConfirm: "Removal = appending a `set disabled: true` operation (the patch vocabulary has no remove). The row stays in the file and can be re-enabled anytime.",
1469
+ editorTitleAdd: "Add MCP server",
1470
+ editorTitleEdit: "Edit {name}",
1471
+ fieldServerName: "serverName (namespace)",
1472
+ fieldTransport: "transport",
1473
+ fieldCommand: "command",
1474
+ fieldArgs: "args (one argument per line)",
1475
+ fieldCwd: "cwd (optional)",
1476
+ fieldUrl: "url",
1477
+ fieldTimeout: "toolCallTimeoutMs (per-call timeout)",
1478
+ fieldFailFast: "failOnStartupError (fail the mount on startup errors)",
1479
+ fieldReconnectEnabled: "Auto reconnect",
1480
+ fieldReconnectMaxAttempts: "reconnect.maxAttempts",
1481
+ fieldEnv: "env (environment variables)",
1482
+ fieldHeaders: "headers (request headers)",
1483
+ keyColumn: "Key",
1484
+ valueColumn: "Value",
1485
+ unchanged: "(unchanged = keep the original value)",
1486
+ addRow: "Add a row",
1487
+ removeRow: "Remove",
1488
+ save: "Save",
1489
+ cancel: "Cancel",
1490
+ previewPatch: "Generate patch fragment",
1491
+ copyPatch: "Copy fragment",
1492
+ patchCopied: "Copied to the clipboard.",
1493
+ copyFailed: "Copy failed (clipboard unavailable): {message}",
1494
+ patchFor: "Will be appended to {file}:",
1495
+ patchFileUnknown: "The profile patch path is unknown (no baseUrl) — the fragment can only be copied.",
1496
+ writeToProfile: "Write to profile",
1497
+ confirmWrite: "Confirm write",
1498
+ writeRequiresConfirm: "Writing needs confirmation: review the fragment above, then press \"Confirm write\".",
1499
+ writeDone: "Written to {file} (backup: {backup}). The web surface hot-reloads cordis.patch.yml edits; other surfaces restart.",
1500
+ writeFailed: "Write failed: {message}",
1501
+ writeDisabled: "Writes are disabled (config.writeEnabled: false) — the fragment can only be copied.",
1502
+ approvalHarness: "Approval channel: ctx.approval",
1503
+ approvalInteractive: "Approval channel: interactive confirmation",
1504
+ trial: "Tool trial console",
1505
+ trialHint: "Calls run through the official tool pipeline (permission policy and approval stay in force); results are shown only here and never enter model context.",
1506
+ trialServer: "Server",
1507
+ trialTool: "Tool",
1508
+ trialArgs: "Arguments (JSON)",
1509
+ trialRun: "Call",
1510
+ trialRunning: "Calling…",
1511
+ trialResult: "Result (canonical JSON and render)",
1512
+ trialDuration: "{ms} ms",
1513
+ trialTruncated: "The result exceeded the display cap and was truncated.",
1514
+ trialApprovalNote: "Approval-style calls need the current session to be inside an open turn; otherwise they fail closed.",
1515
+ trialDisabled: "The trial console is disabled (config.trialEnabled: false).",
1516
+ trialNoTools: "This server has no registered tools — it may be disconnected or its sync failed.",
1517
+ trialArgsInvalid: "Arguments must be valid JSON.",
1518
+ capabilities: "Capabilities",
1519
+ capResources: "Resources",
1520
+ capPrompts: "Prompts",
1521
+ capPending: "Pending upstream support — the official client does not bridge this capability yet (tools are the only bridged MCP capability today)."
623
1522
  };
624
1523
  //#endregion
625
1524
  //#region node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
@@ -4714,12 +5613,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4714
5613
  //#endregion
4715
5614
  //#region src/wire.ts
4716
5615
  /**
4717
- * The panel's wire vocabulary: the snapshot types served over the `mcpPanel`
4718
- * Remote namespace, their zod v4 validation schema (the strict codec both
4719
- * Typert faces carry), and the single invocation descriptor shared by the
4720
- * host `./typert` manifest and the client Remote contribution. One canonical
4721
- * source for both faces keeps the host and client codecs from ever drifting
4722
- * apart.
5616
+ * The console's wire vocabulary: the snapshot types served over the
5617
+ * `mcpPanel` Remote namespace, their zod v4 validation schema (the strict
5618
+ * codec both Typert faces carry), and the invocation descriptors shared
5619
+ * verbatim by the host `./typert` manifest (`src/typert.host.ts`) and the
5620
+ * client Remote contribution (`src/client/remote.ts`). One canonical source
5621
+ * for both faces keeps the host and client codecs from ever drifting apart.
5622
+ *
5623
+ * Values that could be large or heterogeneous (trial results) cross the wire
5624
+ * as single bounded JSON strings; every scalar here has an explicit shape.
4723
5625
  *
4724
5626
  * @module dsh-mcp-panel/wire
4725
5627
  */
@@ -4773,7 +5675,31 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4773
5675
  _null()
4774
5676
  ]),
4775
5677
  probeCheckedAt: number().int().nullable(),
4776
- statusSource: union([literal("upstream-event"), literal("derived")])
5678
+ statusSource: union([literal("upstream-event"), literal("derived")]),
5679
+ config: object({
5680
+ serverName: string(),
5681
+ transport: union([
5682
+ literal("stdio"),
5683
+ literal("streamable-http"),
5684
+ literal("unknown")
5685
+ ]),
5686
+ command: string().nullable(),
5687
+ args: array(string()),
5688
+ cwd: string().nullable(),
5689
+ url: string().nullable(),
5690
+ envKeys: array(string()),
5691
+ headerKeys: array(string()),
5692
+ toolCallTimeoutMs: number().int().nullable(),
5693
+ failOnStartupError: boolean().nullable(),
5694
+ reconnectEnabled: boolean().nullable(),
5695
+ reconnectMaxAttempts: number().int().nullable()
5696
+ }).nullable(),
5697
+ diagnostics: array(object({
5698
+ code: string(),
5699
+ text: string()
5700
+ })),
5701
+ exitCode: number().int().nullable(),
5702
+ stderrTail: string().nullable()
4777
5703
  })),
4778
5704
  probes: array(object({
4779
5705
  id: string(),
@@ -4789,7 +5715,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4789
5715
  startedAt: number().int(),
4790
5716
  finishedAt: number().int().nullable(),
4791
5717
  detail: string().nullable()
4792
- }))
5718
+ })),
5719
+ capabilities: object({
5720
+ resources: object({ available: boolean() }),
5721
+ prompts: object({ available: boolean() })
5722
+ }),
5723
+ trial: object({
5724
+ enabled: boolean(),
5725
+ timeoutMs: number().int(),
5726
+ maxResultChars: number().int()
5727
+ }),
5728
+ writeEnabled: boolean()
4793
5729
  });
4794
5730
  /**
4795
5731
  * The `mcpPanel/status` invocation descriptor, shared verbatim by the host
@@ -4852,12 +5788,158 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4852
5788
  column: 1
4853
5789
  })
4854
5790
  });
5791
+ /** Strict wire schema for {@link PatchPreview}. */
5792
+ const PATCH_PREVIEW_SCHEMA = object({
5793
+ fragment: string(),
5794
+ file: string().nullable(),
5795
+ ops: number().int()
5796
+ });
5797
+ /** The `mcpPanel/previewPatch` invocation descriptor: render one CRUD op. */
5798
+ const MCP_PANEL_PREVIEW_DESCRIPTOR = Object.freeze({
5799
+ id: "dsh-mcp-panel#mcpPanel/previewPatch",
5800
+ service: "mcpPanel",
5801
+ namespace: "mcpPanel",
5802
+ method: "previewPatch",
5803
+ invocation: Object.freeze({ kind: "direct" }),
5804
+ parameters: Object.freeze([Object.freeze({
5805
+ name: "opJson",
5806
+ wire: "opJson",
5807
+ source: "json",
5808
+ codec: Object.freeze({
5809
+ mode: "strict",
5810
+ typeSymbol: "dsh-mcp-panel/types#PatchOpJson",
5811
+ schema: string()
5812
+ })
5813
+ })]),
5814
+ result: Object.freeze({
5815
+ mode: "strict",
5816
+ typeSymbol: "dsh-mcp-panel/types#PatchPreview",
5817
+ schema: PATCH_PREVIEW_SCHEMA
5818
+ }),
5819
+ sourceLocation: Object.freeze({
5820
+ file: "src/wire.ts",
5821
+ line: 1,
5822
+ column: 1
5823
+ })
5824
+ });
5825
+ /** Strict wire schema for {@link PatchWriteResult}. */
5826
+ const PATCH_WRITE_RESULT_SCHEMA = object({
5827
+ file: string(),
5828
+ backupPath: string(),
5829
+ approvalPath: union([literal("harness-approval"), literal("interactive-confirmation")]),
5830
+ bytes: number().int(),
5831
+ ops: number().int(),
5832
+ note: string()
5833
+ });
5834
+ /** The `mcpPanel/writePatch` invocation descriptor: approval-gated append. */
5835
+ const MCP_PANEL_WRITE_DESCRIPTOR = Object.freeze({
5836
+ id: "dsh-mcp-panel#mcpPanel/writePatch",
5837
+ service: "mcpPanel",
5838
+ namespace: "mcpPanel",
5839
+ method: "writePatch",
5840
+ invocation: Object.freeze({ kind: "direct" }),
5841
+ parameters: Object.freeze([
5842
+ Object.freeze({
5843
+ name: "opJson",
5844
+ wire: "opJson",
5845
+ source: "json",
5846
+ codec: Object.freeze({
5847
+ mode: "strict",
5848
+ typeSymbol: "dsh-mcp-panel/types#PatchOpJson",
5849
+ schema: string()
5850
+ })
5851
+ }),
5852
+ Object.freeze({
5853
+ name: "confirmed",
5854
+ wire: "confirmed",
5855
+ source: "json",
5856
+ codec: Object.freeze({
5857
+ mode: "strict",
5858
+ typeSymbol: "dsh-mcp-panel/types#PatchWriteConfirmed",
5859
+ schema: boolean()
5860
+ })
5861
+ }),
5862
+ Object.freeze({
5863
+ name: "sessionId",
5864
+ wire: "sessionId",
5865
+ source: "json",
5866
+ codec: Object.freeze({
5867
+ mode: "strict",
5868
+ typeSymbol: "dsh-mcp-panel/types#PatchWriteSessionId",
5869
+ schema: string()
5870
+ }),
5871
+ acceptsUndefined: true
5872
+ })
5873
+ ]),
5874
+ result: Object.freeze({
5875
+ mode: "strict",
5876
+ typeSymbol: "dsh-mcp-panel/types#PatchWriteResult",
5877
+ schema: PATCH_WRITE_RESULT_SCHEMA
5878
+ }),
5879
+ sourceLocation: Object.freeze({
5880
+ file: "src/wire.ts",
5881
+ line: 1,
5882
+ column: 1
5883
+ })
5884
+ });
5885
+ /** Strict wire schema for {@link McpTrialResultWire}. */
5886
+ const MCP_TRIAL_RESULT_SCHEMA = object({
5887
+ callId: string(),
5888
+ isError: boolean(),
5889
+ truncated: boolean(),
5890
+ durationMs: number().int(),
5891
+ resultJson: string()
5892
+ });
5893
+ /** The `mcpPanel/callTool` invocation descriptor: official-pipeline trial call. */
5894
+ const MCP_PANEL_CALLTOOL_DESCRIPTOR = Object.freeze({
5895
+ id: "dsh-mcp-panel#mcpPanel/callTool",
5896
+ service: "mcpPanel",
5897
+ namespace: "mcpPanel",
5898
+ method: "callTool",
5899
+ invocation: Object.freeze({ kind: "direct" }),
5900
+ parameters: Object.freeze([Object.freeze({
5901
+ name: "requestJson",
5902
+ wire: "requestJson",
5903
+ source: "json",
5904
+ codec: Object.freeze({
5905
+ mode: "strict",
5906
+ typeSymbol: "dsh-mcp-panel/types#TrialRequestJson",
5907
+ schema: string()
5908
+ })
5909
+ }), Object.freeze({
5910
+ name: "sessionId",
5911
+ wire: "sessionId",
5912
+ source: "json",
5913
+ codec: Object.freeze({
5914
+ mode: "strict",
5915
+ typeSymbol: "dsh-mcp-panel/types#TrialSessionId",
5916
+ schema: string()
5917
+ }),
5918
+ acceptsUndefined: true
5919
+ })]),
5920
+ result: Object.freeze({
5921
+ mode: "strict",
5922
+ typeSymbol: "dsh-mcp-panel/types#McpTrialResultWire",
5923
+ schema: MCP_TRIAL_RESULT_SCHEMA
5924
+ }),
5925
+ sourceLocation: Object.freeze({
5926
+ file: "src/wire.ts",
5927
+ line: 1,
5928
+ column: 1
5929
+ })
5930
+ });
4855
5931
  /**
4856
5932
  * The canonical invocation list both Typert faces register — the host
4857
5933
  * manifest and the client contribution share these exact descriptor objects,
4858
5934
  * so the two wire codecs can never drift apart.
4859
5935
  */
4860
- const MCP_PANEL_INVOCATIONS = Object.freeze([MCP_PANEL_STATUS_DESCRIPTOR, MCP_PANEL_PROBE_DESCRIPTOR]);
5936
+ const MCP_PANEL_INVOCATIONS = Object.freeze([
5937
+ MCP_PANEL_STATUS_DESCRIPTOR,
5938
+ MCP_PANEL_PROBE_DESCRIPTOR,
5939
+ MCP_PANEL_PREVIEW_DESCRIPTOR,
5940
+ MCP_PANEL_WRITE_DESCRIPTOR,
5941
+ MCP_PANEL_CALLTOOL_DESCRIPTOR
5942
+ ]);
4861
5943
  //#endregion
4862
5944
  //#region src/client/remote.ts
4863
5945
  /** The client Remote contribution for the `mcpPanel` namespace. */
@@ -5132,6 +6214,207 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5132
6214
  border-radius: 50%;
5133
6215
  background: currentColor;
5134
6216
  }
6217
+ [data-dsh-mcp-panel] .dmcp-editor {
6218
+ display: flex;
6219
+ flex-direction: column;
6220
+ gap: 10px;
6221
+ border: 1px solid var(--dsw-alias-border-l3);
6222
+ border-radius: 6px;
6223
+ background: var(--dsw-alias-bg-layer-1);
6224
+ padding: 12px;
6225
+ }
6226
+ [data-dsh-mcp-panel] .dmcp-form {
6227
+ display: flex;
6228
+ flex-direction: column;
6229
+ gap: 8px;
6230
+ }
6231
+ [data-dsh-mcp-panel] .dmcp-field {
6232
+ display: flex;
6233
+ flex-direction: column;
6234
+ gap: 4px;
6235
+ font-size: 0.9em;
6236
+ color: var(--dsw-alias-label-secondary);
6237
+ }
6238
+ [data-dsh-mcp-panel] .dmcp-field input,
6239
+ [data-dsh-mcp-panel] .dmcp-field select,
6240
+ [data-dsh-mcp-panel] .dmcp-field textarea {
6241
+ font: inherit;
6242
+ color: var(--dsw-alias-label-primary);
6243
+ background: var(--dsw-alias-bg-layer-1);
6244
+ border: 1px solid var(--dsw-alias-border-l3);
6245
+ border-radius: 4px;
6246
+ padding: 4px 8px;
6247
+ }
6248
+ [data-dsh-mcp-panel] .dmcp-check {
6249
+ display: flex;
6250
+ align-items: center;
6251
+ gap: 8px;
6252
+ font-size: 0.9em;
6253
+ color: var(--dsw-alias-label-secondary);
6254
+ }
6255
+ [data-dsh-mcp-panel] .dmcp-map {
6256
+ border: 1px solid var(--dsw-alias-border-l2);
6257
+ border-radius: 4px;
6258
+ padding: 8px;
6259
+ display: flex;
6260
+ flex-direction: column;
6261
+ gap: 6px;
6262
+ margin: 0;
6263
+ }
6264
+ [data-dsh-mcp-panel] .dmcp-map legend {
6265
+ font-size: 0.9em;
6266
+ color: var(--dsw-alias-label-secondary);
6267
+ padding: 0 4px;
6268
+ }
6269
+ [data-dsh-mcp-panel] .dmcp-map-row {
6270
+ display: flex;
6271
+ gap: 6px;
6272
+ }
6273
+ [data-dsh-mcp-panel] .dmcp-map-row input {
6274
+ font: inherit;
6275
+ color: var(--dsw-alias-label-primary);
6276
+ background: var(--dsw-alias-bg-layer-1);
6277
+ border: 1px solid var(--dsw-alias-border-l3);
6278
+ border-radius: 4px;
6279
+ padding: 4px 8px;
6280
+ min-width: 0;
6281
+ }
6282
+ [data-dsh-mcp-panel] .dmcp-map-key {
6283
+ flex: 1 1 40%;
6284
+ }
6285
+ [data-dsh-mcp-panel] .dmcp-map-value {
6286
+ flex: 2 1 60%;
6287
+ }
6288
+ [data-dsh-mcp-panel] .dmcp-editor-actions {
6289
+ display: flex;
6290
+ gap: 8px;
6291
+ flex-wrap: wrap;
6292
+ }
6293
+ [data-dsh-mcp-panel] .dmcp-action:disabled {
6294
+ opacity: 0.6;
6295
+ cursor: default;
6296
+ }
6297
+ [data-dsh-mcp-panel] .dmcp-danger {
6298
+ color: var(--dsw-alias-state-error-primary);
6299
+ border-color: currentColor;
6300
+ }
6301
+ [data-dsh-mcp-panel] .dmcp-confirm {
6302
+ color: var(--dsw-alias-state-warn-primary);
6303
+ border-color: currentColor;
6304
+ }
6305
+ [data-dsh-mcp-panel] .dmcp-patch {
6306
+ display: flex;
6307
+ flex-direction: column;
6308
+ gap: 8px;
6309
+ }
6310
+ [data-dsh-mcp-panel] .dmcp-fragment {
6311
+ margin: 0;
6312
+ padding: 8px;
6313
+ border: 1px solid var(--dsw-alias-border-l2);
6314
+ border-radius: 4px;
6315
+ background: var(--dsw-alias-bg-layer-2);
6316
+ color: var(--dsw-alias-label-primary);
6317
+ overflow: auto;
6318
+ max-height: 240px;
6319
+ white-space: pre-wrap;
6320
+ overflow-wrap: anywhere;
6321
+ font-family: var(--dsw-alias-font-mono, monospace);
6322
+ font-size: 0.85em;
6323
+ }
6324
+ [data-dsh-mcp-panel] .dmcp-notice {
6325
+ margin: 0;
6326
+ color: var(--dsw-alias-label-secondary);
6327
+ overflow-wrap: anywhere;
6328
+ }
6329
+ [data-dsh-mcp-panel] .dmcp-warn-text {
6330
+ color: var(--dsw-alias-state-warn-primary);
6331
+ }
6332
+ [data-dsh-mcp-panel] .dmcp-health {
6333
+ display: flex;
6334
+ flex-direction: column;
6335
+ gap: 4px;
6336
+ }
6337
+ [data-dsh-mcp-panel] .dmcp-health-title {
6338
+ margin: 0;
6339
+ color: var(--dsw-alias-label-secondary);
6340
+ }
6341
+ [data-dsh-mcp-panel] .dmcp-health-list {
6342
+ list-style: disc;
6343
+ margin: 0;
6344
+ padding-left: 20px;
6345
+ display: flex;
6346
+ flex-direction: column;
6347
+ gap: 2px;
6348
+ color: var(--dsw-alias-label-primary);
6349
+ }
6350
+ [data-dsh-mcp-panel] .dmcp-diag-code {
6351
+ color: var(--dsw-alias-label-tertiary);
6352
+ }
6353
+ [data-dsh-mcp-panel] .dmcp-remove {
6354
+ display: flex;
6355
+ flex-direction: column;
6356
+ gap: 6px;
6357
+ border: 1px solid var(--dsw-alias-state-error-primary);
6358
+ border-radius: 4px;
6359
+ padding: 8px;
6360
+ }
6361
+ [data-dsh-mcp-panel] .dmcp-trial {
6362
+ display: flex;
6363
+ flex-direction: column;
6364
+ gap: 8px;
6365
+ border-top: 1px solid var(--dsw-alias-border-l2);
6366
+ padding-top: 12px;
6367
+ }
6368
+ [data-dsh-mcp-panel] .dmcp-trial-row {
6369
+ display: flex;
6370
+ gap: 8px;
6371
+ flex-wrap: wrap;
6372
+ align-items: flex-end;
6373
+ }
6374
+ [data-dsh-mcp-panel] .dmcp-trial-row .dmcp-field {
6375
+ flex: 1 1 180px;
6376
+ min-width: 0;
6377
+ }
6378
+ [data-dsh-mcp-panel] .dmcp-trial-result {
6379
+ display: flex;
6380
+ flex-direction: column;
6381
+ gap: 6px;
6382
+ }
6383
+ [data-dsh-mcp-panel] .dmcp-trial-meta {
6384
+ display: flex;
6385
+ align-items: center;
6386
+ gap: 10px;
6387
+ flex-wrap: wrap;
6388
+ margin: 0;
6389
+ color: var(--dsw-alias-label-secondary);
6390
+ }
6391
+ [data-dsh-mcp-panel] .dmcp-trial-call {
6392
+ color: var(--dsw-alias-label-tertiary);
6393
+ font-family: var(--dsw-alias-font-mono, monospace);
6394
+ font-size: 0.85em;
6395
+ }
6396
+ [data-dsh-mcp-panel] .dmcp-trial-json {
6397
+ max-height: 320px;
6398
+ }
6399
+ [data-dsh-mcp-panel] .dmcp-capabilities {
6400
+ display: flex;
6401
+ flex-direction: column;
6402
+ gap: 6px;
6403
+ }
6404
+ [data-dsh-mcp-panel] .dmcp-capabilities-list {
6405
+ list-style: none;
6406
+ margin: 0;
6407
+ padding: 0;
6408
+ display: flex;
6409
+ flex-direction: column;
6410
+ gap: 6px;
6411
+ }
6412
+ [data-dsh-mcp-panel] .dmcp-capabilities-list li {
6413
+ display: flex;
6414
+ align-items: baseline;
6415
+ gap: 8px;
6416
+ flex-wrap: wrap;
6417
+ }
5135
6418
  `;
5136
6419
  //#endregion
5137
6420
  //#region src/client/index.ts
@@ -5139,11 +6422,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5139
6422
  const NS = "settings.mcpPanel";
5140
6423
  /** Plugin name: matches the package name, the graph row id, and the bundle id. */
5141
6424
  const name = "dsh-mcp-panel";
5142
- /** Services the tab reads; `remote.mcpPanel` appears once this plugin mounts its contribution. */
6425
+ /** Services the console reads; `remote.mcpPanel` appears once this plugin mounts its contribution. */
5143
6426
  const inject = [
5144
6427
  "slots",
5145
6428
  "locale",
5146
- "remote"
6429
+ "remote",
6430
+ "sessions"
5147
6431
  ];
5148
6432
  /**
5149
6433
  * Browser plugin body: dictionaries, the scoped stylesheet, the Remote
@@ -5160,16 +6444,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5160
6444
  await ctx.remote.$mount(MCP_PANEL_REMOTE);
5161
6445
  ctx.inject(["remote.mcpPanel"], (scope) => {
5162
6446
  const t = scope.locale.bind(NS);
5163
- const status = async () => {
5164
- const result = await scope.remote.mcpPanel.status();
5165
- if (!result.ok) throw new Error(`mcpPanel.status failed: ${result.error.code}: ${result.error.message}`);
5166
- return result.value;
5167
- };
5168
- const probe = async (serverName) => {
5169
- const result = await scope.remote.mcpPanel.probe(serverName);
5170
- if (!result.ok) throw new Error(`mcpPanel.probe failed: ${result.error.code}: ${result.error.message}`);
6447
+ const unwrap = (result, method) => {
6448
+ if (!result.ok) throw new Error(`mcpPanel.${method} failed: ${result.error.code}: ${result.error.message}`);
5171
6449
  return result.value;
5172
6450
  };
6451
+ const status = async () => unwrap(await scope.remote.mcpPanel.status(), "status");
6452
+ const probe = async (serverName) => unwrap(await scope.remote.mcpPanel.probe(serverName), "probe");
6453
+ const previewPatch = async (opJson) => unwrap(await scope.remote.mcpPanel.previewPatch(opJson), "previewPatch");
6454
+ const writePatch = async (opJson, confirmed) => unwrap(await scope.remote.mcpPanel.writePatch(opJson, confirmed, currentSessionId(scope.get("sessions"))), "writePatch");
6455
+ const callTool = async (requestJson) => unwrap(await scope.remote.mcpPanel.callTool(requestJson, currentSessionId(scope.get("sessions"))), "callTool");
5173
6456
  scope.slots.inject("settings.plugins.tab", () => scope.slots.register({
5174
6457
  name: "settings.plugins.tab",
5175
6458
  id: "mcp",
@@ -5178,11 +6461,30 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5178
6461
  locale: NS,
5179
6462
  inject: () => ({
5180
6463
  status,
5181
- probe
6464
+ probe,
6465
+ previewPatch,
6466
+ writePatch,
6467
+ callTool
5182
6468
  })
5183
6469
  }, McpPanelTab));
5184
6470
  });
5185
6471
  }
6472
+ /**
6473
+ * Read the current session id from the sessions store face (structural:
6474
+ * the store shape differs across harness lines, so only the leaf is read).
6475
+ */
6476
+ function currentSessionId(sessions) {
6477
+ try {
6478
+ const list = sessions?.list;
6479
+ if (typeof list !== "object" || list === null) return void 0;
6480
+ const getSnapshot = list.getSnapshot;
6481
+ if (typeof getSnapshot !== "function") return void 0;
6482
+ const current = getSnapshot().current;
6483
+ return typeof current === "string" ? current : void 0;
6484
+ } catch {
6485
+ return;
6486
+ }
6487
+ }
5186
6488
  //#endregion
5187
6489
  exports.NS = NS;
5188
6490
  exports.apply = apply;