dsh-desktop-kit 2.7.6 → 2.7.7
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/CHANGELOG.md +7 -0
- package/README.md +2 -2
- package/client.js +138 -2
- package/docs/TEST-2.7.7.md +25 -0
- package/package.json +1 -1
- package/plugins/dsh-mcp-manager/README.md +13 -3
- package/plugins/dsh-mcp-manager/client.js +137 -1
- package/plugins/dsh-mcp-manager/index.js +27 -4
- package/plugins/dsh-mcp-manager/lib/api.js +33 -2
- package/plugins/dsh-mcp-manager/lib/client.js +18 -0
- package/plugins/dsh-mcp-manager/lib/manager.js +14 -2
- package/plugins/dsh-mcp-manager/screenshots/prompts-1440.png +0 -0
- package/plugins/dsh-mcp-manager/screenshots/prompts-390.png +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.7.7 (2026-11-12)
|
|
4
|
+
|
|
5
|
+
- **MCP prompts.** Settings → MCP → **Prompts**: список промптов сервера, аргументы, запуск, результат и копирование. Новые RPC `prompts`/`prompt`; клиент поддерживает `prompts/list` и `prompts/get` с пагинацией.
|
|
6
|
+
- **`/mcp prompts` и `/mcp prompt`.** Промпты доступны и из командной строки, включая JSON-аргументы.
|
|
7
|
+
- **UI.** Модалка prompts проверена на 390/768/1440: overflow 0, console 0, markup 0.
|
|
8
|
+
- **README.** Скриншоты prompts 1440/390 и описание.
|
|
9
|
+
|
|
3
10
|
## 2.7.6 (2026-11-11)
|
|
4
11
|
|
|
5
12
|
- **README images через unpkg.** Npm-страница не разрешает относительные пути, а GitHub raw и jsDelivr в этой сети не открываются. Все 15 изображений переведены на `https://unpkg.com/dsh-desktop-kit@2.7.4/...`; загрузка проверена в headless Chrome.
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## MCP: подключения без JSON
|
|
10
10
|
|
|
11
|
-
Настройки → **MCP**: пресеты GitHub, PostgreSQL и Filesystem, живые статусы, health, список tools и безопасное хранение токенов в `~/.dsh/mcp.secrets.json`.
|
|
11
|
+
Настройки → **MCP**: пресеты GitHub, PostgreSQL и Filesystem, живые статусы, health, список tools и безопасное хранение токенов в `~/.dsh/mcp.secrets.json`. Промпты сервера открываются кнопкой **Prompts**: аргументы, запуск и копирование результата.
|
|
12
12
|
|
|
13
13
|

|
|
14
14
|
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|

|
|
27
27
|
|
|
28
|
-
**v2.7.
|
|
28
|
+
**v2.7.7** · MIT · проверено на DSH `0.2.1-rc.5` и `0.2.2-rc.1` (Windows 11) · без внешних зависимостей и телеметрии
|
|
29
29
|
|
|
30
30
|
**План развития:** [docs/ROADMAP-3.0.md](docs/ROADMAP-3.0.md) — Memory Fabric, семантическое
|
|
31
31
|
слияние, локальный индекс памяти, шифрованные переносы, Memory Hub и npm trusted publishing.
|
package/client.js
CHANGED
|
@@ -6929,6 +6929,12 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
6929
6929
|
.mcp-tool{display:flex;gap:8px;align-items:baseline;font-size:12px;line-height:1.45;min-width:0}
|
|
6930
6930
|
.mcp-tool code{color:var(--mcp-accent);font:inherit;font-weight:650;white-space:nowrap}
|
|
6931
6931
|
.mcp-tool span{color:var(--mcp-muted);overflow:hidden;text-overflow:ellipsis}
|
|
6932
|
+
.mcp-prompt-list{display:flex;flex-direction:column;gap:8px}
|
|
6933
|
+
.mcp-prompt-card{text-align:left;border:1px solid var(--mcp-line);background:var(--mcp-panel);border-radius:12px;padding:11px 12px;cursor:pointer;font:inherit;color:inherit;min-height:44px}
|
|
6934
|
+
.mcp-prompt-card:hover{border-color:rgba(15,118,110,.4);background:var(--mcp-accent-soft)}
|
|
6935
|
+
.mcp-prompt-card b{display:block;font-size:13px}
|
|
6936
|
+
.mcp-prompt-card span{display:block;color:var(--mcp-muted);font-size:12px;line-height:1.4;margin-top:3px}
|
|
6937
|
+
.mcp-prompt-output{margin:0;padding:12px;border:1px solid var(--mcp-line);border-radius:10px;background:var(--mcp-soft);color:var(--mcp-text);font:12px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere;max-height:38vh;overflow:auto}
|
|
6932
6938
|
.mcp-empty{border:1px dashed var(--mcp-line);border-radius:18px;padding:26px;background:linear-gradient(160deg,var(--mcp-soft),transparent 75%);display:flex;flex-direction:column;gap:12px;align-items:flex-start}
|
|
6933
6939
|
.mcp-empty h3{margin:0;font-size:18px;letter-spacing:-.015em}
|
|
6934
6940
|
.mcp-empty p{margin:0;color:var(--mcp-muted);font-size:13px;line-height:1.5;max-width:60ch}
|
|
@@ -6983,7 +6989,7 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
6983
6989
|
);
|
|
6984
6990
|
}
|
|
6985
6991
|
|
|
6986
|
-
function ServerCard({ server, busy, onToggle, onTest, onTools, onRemove, tools }) {
|
|
6992
|
+
function ServerCard({ server, busy, onToggle, onTest, onTools, onPrompts, onRemove, tools }) {
|
|
6987
6993
|
const [open, setOpen] = useState(false);
|
|
6988
6994
|
const connected = server.connected;
|
|
6989
6995
|
const status = connected ? "on" : (server.error ? "err" : "off");
|
|
@@ -7006,6 +7012,7 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
7006
7012
|
h(Toggle, { checked: server.trusted, label: "Доверен", disabled: busy, onChange: (value) => onToggle(server.id, { trusted: value }) }),
|
|
7007
7013
|
h("div", { className: "mcp-actions" },
|
|
7008
7014
|
h("button", { className: "mcp-btn ghost", disabled: busy, onClick: () => onTest(server.id) }, "Проверить"),
|
|
7015
|
+
h("button", { className: "mcp-btn ghost", disabled: busy, onClick: () => onPrompts(server.id) }, "Prompts"),
|
|
7009
7016
|
h("button", { className: "mcp-btn ghost", disabled: busy, onClick: () => { setOpen(!open); if (!open) onTools(server.id); } }, open ? "Скрыть" : "Tools"),
|
|
7010
7017
|
h("button", { className: "mcp-btn danger ghost", disabled: busy, onClick: () => onRemove(server.id) }, "Удалить"),
|
|
7011
7018
|
),
|
|
@@ -7021,6 +7028,58 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
7021
7028
|
);
|
|
7022
7029
|
}
|
|
7023
7030
|
|
|
7031
|
+
function PromptModal({ state, onClose, onPick, onBack, onArg, onRun, onCopy }) {
|
|
7032
|
+
if (!state) return null;
|
|
7033
|
+
const prompt = state.prompt;
|
|
7034
|
+
const args = Array.isArray(prompt?.arguments) ? prompt.arguments : [];
|
|
7035
|
+
return h("div", { className: "mcp-overlay", onClick: onClose },
|
|
7036
|
+
h("div", {
|
|
7037
|
+
className: "mcp-modal",
|
|
7038
|
+
role: "dialog",
|
|
7039
|
+
"aria-modal": "true",
|
|
7040
|
+
"aria-labelledby": "mcp-prompts-title",
|
|
7041
|
+
onClick: (event) => event.stopPropagation(),
|
|
7042
|
+
},
|
|
7043
|
+
h("div", { className: "mcp-modal-head" },
|
|
7044
|
+
h("div", null,
|
|
7045
|
+
h("h2", { id: "mcp-prompts-title" }, prompt ? (prompt.title || prompt.name) : "Prompts"),
|
|
7046
|
+
h("p", null, prompt ? (prompt.description || "Промпт MCP-сервера") : `Промпты сервера «${state.serverId}».`),
|
|
7047
|
+
),
|
|
7048
|
+
h("button", { className: "mcp-btn ghost", type: "button", onClick: onClose, "aria-label": "Закрыть" }, "Закрыть"),
|
|
7049
|
+
),
|
|
7050
|
+
state.error ? h("div", { className: "mcp-error", role: "alert" }, state.error) : null,
|
|
7051
|
+
!prompt ? h("div", { className: "mcp-prompt-list" },
|
|
7052
|
+
state.prompts.length === 0 ? h("div", { className: "mcp-meta" }, "У сервера нет prompts.") : state.prompts.map((item) => h("button", {
|
|
7053
|
+
key: item.name,
|
|
7054
|
+
type: "button",
|
|
7055
|
+
className: "mcp-prompt-card",
|
|
7056
|
+
onClick: () => onPick(item),
|
|
7057
|
+
}, h("b", null, item.name), h("span", null, item.description || "Без описания"))),
|
|
7058
|
+
) : h("div", { className: "mcp-form" },
|
|
7059
|
+
args.map((arg, index) => h("div", { className: `mcp-field${String(arg.description || "").length > 40 ? " wide" : ""}`, key: arg.name },
|
|
7060
|
+
h("label", { htmlFor: `mcp-prompt-arg-${index}` }, arg.name, arg.required ? " *" : ""),
|
|
7061
|
+
h("input", {
|
|
7062
|
+
id: `mcp-prompt-arg-${index}`,
|
|
7063
|
+
value: state.args[arg.name] ?? "",
|
|
7064
|
+
placeholder: arg.description || "",
|
|
7065
|
+
onChange: (event) => onArg(arg.name, event.target.value),
|
|
7066
|
+
}),
|
|
7067
|
+
)),
|
|
7068
|
+
args.length === 0 ? h("p", { className: "mcp-note", style: { gridColumn: "1/-1" } }, "У промпта нет аргументов.") : null,
|
|
7069
|
+
state.result ? h("div", { className: "mcp-field wide" },
|
|
7070
|
+
h("label", null, "Результат"),
|
|
7071
|
+
h("pre", { className: "mcp-prompt-output" }, state.result),
|
|
7072
|
+
) : null,
|
|
7073
|
+
h("div", { className: "mcp-form-actions", style: { gridColumn: "1/-1" } },
|
|
7074
|
+
state.prompts.length > 1 ? h("button", { className: "mcp-btn ghost", type: "button", onClick: onBack }, "Назад") : null,
|
|
7075
|
+
state.result ? h("button", { className: "mcp-btn ghost", type: "button", onClick: onCopy }, "Копировать") : null,
|
|
7076
|
+
h("button", { className: "mcp-btn primary", type: "button", disabled: state.busy, onClick: onRun }, state.busy ? "Запрашиваем…" : "Получить"),
|
|
7077
|
+
),
|
|
7078
|
+
),
|
|
7079
|
+
),
|
|
7080
|
+
);
|
|
7081
|
+
}
|
|
7082
|
+
|
|
7024
7083
|
function ServerForm({ initial, busy, onCancel, onSave }) {
|
|
7025
7084
|
const [form, setForm] = useState(initial);
|
|
7026
7085
|
const set = (patch) => setForm((current) => ({ ...current, ...patch }));
|
|
@@ -7100,6 +7159,7 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
7100
7159
|
const [form, setForm] = useState(null);
|
|
7101
7160
|
const [tools, setTools] = useState({});
|
|
7102
7161
|
const [toast, setToast] = useState("");
|
|
7162
|
+
const [promptModal, setPromptModal] = useState(null);
|
|
7103
7163
|
|
|
7104
7164
|
const load = async () => {
|
|
7105
7165
|
try {
|
|
@@ -7127,6 +7187,72 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
7127
7187
|
}
|
|
7128
7188
|
};
|
|
7129
7189
|
|
|
7190
|
+
const openPrompts = async (id) => {
|
|
7191
|
+
setBusy(true);
|
|
7192
|
+
setError(null);
|
|
7193
|
+
try {
|
|
7194
|
+
const result = await call(window.__DSH_MCP_CTX__, "prompts", { id });
|
|
7195
|
+
setPromptModal({
|
|
7196
|
+
serverId: id,
|
|
7197
|
+
prompts: Array.isArray(result.prompts) ? result.prompts : [],
|
|
7198
|
+
prompt: null,
|
|
7199
|
+
args: {},
|
|
7200
|
+
result: "",
|
|
7201
|
+
busy: false,
|
|
7202
|
+
error: result.error || "",
|
|
7203
|
+
});
|
|
7204
|
+
} catch (cause) {
|
|
7205
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
7206
|
+
} finally {
|
|
7207
|
+
setBusy(false);
|
|
7208
|
+
}
|
|
7209
|
+
};
|
|
7210
|
+
|
|
7211
|
+
const pickPrompt = (prompt) => {
|
|
7212
|
+
setPromptModal((current) => current ? {
|
|
7213
|
+
...current,
|
|
7214
|
+
prompt,
|
|
7215
|
+
args: Object.fromEntries((prompt.arguments ?? []).map((arg) => [arg.name, ""])),
|
|
7216
|
+
result: "",
|
|
7217
|
+
error: "",
|
|
7218
|
+
} : current);
|
|
7219
|
+
};
|
|
7220
|
+
|
|
7221
|
+
const runPrompt = async () => {
|
|
7222
|
+
const current = promptModal;
|
|
7223
|
+
if (!current?.prompt) return;
|
|
7224
|
+
setPromptModal({ ...current, busy: true, error: "", result: "" });
|
|
7225
|
+
try {
|
|
7226
|
+
const result = await call(window.__DSH_MCP_CTX__, "prompt", {
|
|
7227
|
+
id: current.serverId,
|
|
7228
|
+
name: current.prompt.name,
|
|
7229
|
+
arguments: current.args,
|
|
7230
|
+
});
|
|
7231
|
+
setPromptModal((latest) => latest ? {
|
|
7232
|
+
...latest,
|
|
7233
|
+
busy: false,
|
|
7234
|
+
result: result.text || "",
|
|
7235
|
+
error: result.text ? "" : "Сервер вернул пустой промпт",
|
|
7236
|
+
} : latest);
|
|
7237
|
+
} catch (cause) {
|
|
7238
|
+
setPromptModal((latest) => latest ? {
|
|
7239
|
+
...latest,
|
|
7240
|
+
busy: false,
|
|
7241
|
+
error: cause instanceof Error ? cause.message : String(cause),
|
|
7242
|
+
} : latest);
|
|
7243
|
+
}
|
|
7244
|
+
};
|
|
7245
|
+
|
|
7246
|
+
const copyPrompt = async () => {
|
|
7247
|
+
try {
|
|
7248
|
+
await navigator.clipboard.writeText(promptModal?.result ?? "");
|
|
7249
|
+
setToast("Промпт скопирован");
|
|
7250
|
+
setTimeout(() => setToast(""), 2500);
|
|
7251
|
+
} catch {
|
|
7252
|
+
setError("Не удалось скопировать — выделите текст вручную");
|
|
7253
|
+
}
|
|
7254
|
+
};
|
|
7255
|
+
|
|
7130
7256
|
const presets = PRESETS;
|
|
7131
7257
|
const openPreset = (preset) => setForm({
|
|
7132
7258
|
edit: false,
|
|
@@ -7198,11 +7324,21 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
7198
7324
|
if (result.error) setError(result.error);
|
|
7199
7325
|
} catch (cause) { setError(cause instanceof Error ? cause.message : String(cause)); }
|
|
7200
7326
|
},
|
|
7327
|
+
onPrompts: openPrompts,
|
|
7201
7328
|
onRemove: (id) => {
|
|
7202
7329
|
if (window.confirm(`Удалить MCP-сервер «${id}»?`)) void run("remove", { id }, "Сервер удалён");
|
|
7203
7330
|
},
|
|
7204
7331
|
}))) : null,
|
|
7205
7332
|
servers.length > 0 ? h("p", { className: "mcp-note" }, `Секреты: ${data?.secretsFile || "~/.dsh/mcp.secrets.json"} · новые tools появятся в новом чате после подключения.`) : null,
|
|
7333
|
+
h(PromptModal, {
|
|
7334
|
+
state: promptModal,
|
|
7335
|
+
onClose: () => setPromptModal(null),
|
|
7336
|
+
onPick: pickPrompt,
|
|
7337
|
+
onBack: () => setPromptModal((current) => current ? { ...current, prompt: null, args: {}, result: "", error: "" } : current),
|
|
7338
|
+
onArg: (name, value) => setPromptModal((current) => current ? { ...current, args: { ...current.args, [name]: value } } : current),
|
|
7339
|
+
onRun: runPrompt,
|
|
7340
|
+
onCopy: copyPrompt,
|
|
7341
|
+
}),
|
|
7206
7342
|
form ? h(ServerForm, {
|
|
7207
7343
|
initial: form,
|
|
7208
7344
|
busy,
|
|
@@ -7402,7 +7538,7 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
7402
7538
|
if (__kitErrors.length <= 5) console.error("[dsh-desktop-kit] registration failed:", label, message);
|
|
7403
7539
|
};
|
|
7404
7540
|
try {
|
|
7405
|
-
window.__DSH_DESKTOP_KIT__ = { version: "2.7.
|
|
7541
|
+
window.__DSH_DESKTOP_KIT__ = { version: "2.7.7", parts: __partNames, errors: __kitErrors, ready: false };
|
|
7406
7542
|
} catch {}
|
|
7407
7543
|
const __inject = [];
|
|
7408
7544
|
for (const __part of __parts) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# DSH Desktop Kit 2.7.7 — тест
|
|
2
|
+
|
|
3
|
+
Дата: 2026-11-12.
|
|
4
|
+
|
|
5
|
+
## MCP prompts
|
|
6
|
+
|
|
7
|
+
- `McpClient.listPrompts()` — `prompts/list` с пагинацией, только при capability `prompts`.
|
|
8
|
+
- `McpClient.getPrompt()` — `prompts/get` с аргументами.
|
|
9
|
+
- `McpManager.prompts/prompt`.
|
|
10
|
+
- RPC Settings: `prompts`, `prompt`.
|
|
11
|
+
- Команды: `/mcp prompts <server>`, `/mcp prompt <server> <name> [json]`.
|
|
12
|
+
- UI: модалка со списком, полями аргументов, результатом и копированием.
|
|
13
|
+
|
|
14
|
+
## Проверки
|
|
15
|
+
|
|
16
|
+
- `node tools/mcp-manager-tests.mjs` — OK, включая prompts/list и prompts/get.
|
|
17
|
+
- `npm test` — exit 0.
|
|
18
|
+
- `npm run build:client` / `check:client` — OK.
|
|
19
|
+
- browser_qa audit 390/768/1440 — overflow 0, console 0, markup 0.
|
|
20
|
+
- Скриншоты: `plugins/dsh-mcp-manager/screenshots/prompts-1440.png`, `prompts-390.png`.
|
|
21
|
+
|
|
22
|
+
## Ограничения
|
|
23
|
+
|
|
24
|
+
- OAuth и sampling по-прежнему не реализованы.
|
|
25
|
+
- Аргументы промпта — строковые поля; вложенные объекты задаются через `/mcp prompt` JSON.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-desktop-kit",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.7",
|
|
4
4
|
"description": "DeepSeek Harness desktop kit 2.7: 16 plugins and 30 skills — self-applying skills, token lab, Skills Studio, project memory, GitHub and USB memory transfer, guardrails, repo map, checkpoints, plan mode, browser QA, GitHub review, Claude Code-like hooks/agents/commands, Russian UI, peak guard, chat move and profile transfer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dsh-mcp-manager
|
|
2
2
|
|
|
3
|
-
MCP-серверы для DSH без SDK-зависимости: stdio и streamable HTTP, tools, resources, trust-гейт и премиальный экран в Settings.
|
|
3
|
+
MCP-серверы для DSH без SDK-зависимости: stdio и streamable HTTP, tools, resources, prompts, trust-гейт и премиальный экран в Settings.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
@@ -12,6 +12,7 @@ MCP-серверы для DSH без SDK-зависимости: stdio и stream
|
|
|
12
12
|
- переключатели «Включён» и «Доверен»;
|
|
13
13
|
- «Проверить» — переподключение и health;
|
|
14
14
|
- «Tools» — список инструментов с описаниями;
|
|
15
|
+
- «Prompts» — промпты сервера: аргументы, запуск и копирование результата;
|
|
15
16
|
- «Добавить сервер» — пресеты GitHub / PostgreSQL / Filesystem или ручная форма;
|
|
16
17
|
- токен сохраняется в `~/.dsh/mcp.secrets.json` (0600), а в `mcp.json` остаётся только `${secret:…}`;
|
|
17
18
|
- новые tools появляются в **новом чате** после подключения.
|
|
@@ -20,6 +21,14 @@ MCP-серверы для DSH без SDK-зависимости: stdio и stream
|
|
|
20
21
|
|
|
21
22
|
<p><img src="screenshots/settings-390.png" alt="MCP Settings на 390px" width="390"> <img src="screenshots/modal-390.png" alt="Форма добавления MCP-сервера" width="390"></p>
|
|
22
23
|
|
|
24
|
+
### Prompts
|
|
25
|
+
|
|
26
|
+
Промпт — это готовый запрос с аргументами, который отдаёт сам MCP-сервер. Нажмите **Prompts**, заполните поля и получите текст. Результат можно скопировать одной кнопкой.
|
|
27
|
+
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
<img src="screenshots/prompts-390.png" alt="MCP Prompts на 390px" width="390">
|
|
31
|
+
|
|
23
32
|
## Конфиг
|
|
24
33
|
|
|
25
34
|
`~/.dsh/mcp.json` — глобальные серверы, стартуют автоматически при `trusted: true`.
|
|
@@ -61,6 +70,8 @@ MCP-серверы для DSH без SDK-зависимости: stdio и stream
|
|
|
61
70
|
/mcp init
|
|
62
71
|
/mcp list
|
|
63
72
|
/mcp tools github
|
|
73
|
+
/mcp prompts github
|
|
74
|
+
/mcp prompt github review {"target":"src/app.js"}
|
|
64
75
|
/mcp call github search_repositories {"query":"dsh-desktop-kit"}
|
|
65
76
|
/mcp resources postgres
|
|
66
77
|
/mcp read postgres postgres://schema
|
|
@@ -72,7 +83,6 @@ MCP-серверы для DSH без SDK-зависимости: stdio и stream
|
|
|
72
83
|
## Ограничения
|
|
73
84
|
|
|
74
85
|
- Нет OAuth: только env/headers. Токены кладите в переменные окружения.
|
|
75
|
-
- Нет
|
|
76
|
-
- Нет prompts и sampling; поддержаны tools/resources.
|
|
86
|
+
- Нет sampling: сервер не может запросить генерацию у модели.
|
|
77
87
|
- HTTP-транспорт поддерживает JSON и простой SSE-ответ; интерактивный SSE-стрим с серверными запросами не реализован.
|
|
78
88
|
- MCP-сервер — произвольный процесс. Доверяйте только своим конфигам.
|
|
@@ -64,6 +64,12 @@ window.__ModuleLoader__.load({
|
|
|
64
64
|
.mcp-tool{display:flex;gap:8px;align-items:baseline;font-size:12px;line-height:1.45;min-width:0}
|
|
65
65
|
.mcp-tool code{color:var(--mcp-accent);font:inherit;font-weight:650;white-space:nowrap}
|
|
66
66
|
.mcp-tool span{color:var(--mcp-muted);overflow:hidden;text-overflow:ellipsis}
|
|
67
|
+
.mcp-prompt-list{display:flex;flex-direction:column;gap:8px}
|
|
68
|
+
.mcp-prompt-card{text-align:left;border:1px solid var(--mcp-line);background:var(--mcp-panel);border-radius:12px;padding:11px 12px;cursor:pointer;font:inherit;color:inherit;min-height:44px}
|
|
69
|
+
.mcp-prompt-card:hover{border-color:rgba(15,118,110,.4);background:var(--mcp-accent-soft)}
|
|
70
|
+
.mcp-prompt-card b{display:block;font-size:13px}
|
|
71
|
+
.mcp-prompt-card span{display:block;color:var(--mcp-muted);font-size:12px;line-height:1.4;margin-top:3px}
|
|
72
|
+
.mcp-prompt-output{margin:0;padding:12px;border:1px solid var(--mcp-line);border-radius:10px;background:var(--mcp-soft);color:var(--mcp-text);font:12px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere;max-height:38vh;overflow:auto}
|
|
67
73
|
.mcp-empty{border:1px dashed var(--mcp-line);border-radius:18px;padding:26px;background:linear-gradient(160deg,var(--mcp-soft),transparent 75%);display:flex;flex-direction:column;gap:12px;align-items:flex-start}
|
|
68
74
|
.mcp-empty h3{margin:0;font-size:18px;letter-spacing:-.015em}
|
|
69
75
|
.mcp-empty p{margin:0;color:var(--mcp-muted);font-size:13px;line-height:1.5;max-width:60ch}
|
|
@@ -118,7 +124,7 @@ window.__ModuleLoader__.load({
|
|
|
118
124
|
);
|
|
119
125
|
}
|
|
120
126
|
|
|
121
|
-
function ServerCard({ server, busy, onToggle, onTest, onTools, onRemove, tools }) {
|
|
127
|
+
function ServerCard({ server, busy, onToggle, onTest, onTools, onPrompts, onRemove, tools }) {
|
|
122
128
|
const [open, setOpen] = useState(false);
|
|
123
129
|
const connected = server.connected;
|
|
124
130
|
const status = connected ? "on" : (server.error ? "err" : "off");
|
|
@@ -141,6 +147,7 @@ window.__ModuleLoader__.load({
|
|
|
141
147
|
h(Toggle, { checked: server.trusted, label: "Доверен", disabled: busy, onChange: (value) => onToggle(server.id, { trusted: value }) }),
|
|
142
148
|
h("div", { className: "mcp-actions" },
|
|
143
149
|
h("button", { className: "mcp-btn ghost", disabled: busy, onClick: () => onTest(server.id) }, "Проверить"),
|
|
150
|
+
h("button", { className: "mcp-btn ghost", disabled: busy, onClick: () => onPrompts(server.id) }, "Prompts"),
|
|
144
151
|
h("button", { className: "mcp-btn ghost", disabled: busy, onClick: () => { setOpen(!open); if (!open) onTools(server.id); } }, open ? "Скрыть" : "Tools"),
|
|
145
152
|
h("button", { className: "mcp-btn danger ghost", disabled: busy, onClick: () => onRemove(server.id) }, "Удалить"),
|
|
146
153
|
),
|
|
@@ -156,6 +163,58 @@ window.__ModuleLoader__.load({
|
|
|
156
163
|
);
|
|
157
164
|
}
|
|
158
165
|
|
|
166
|
+
function PromptModal({ state, onClose, onPick, onBack, onArg, onRun, onCopy }) {
|
|
167
|
+
if (!state) return null;
|
|
168
|
+
const prompt = state.prompt;
|
|
169
|
+
const args = Array.isArray(prompt?.arguments) ? prompt.arguments : [];
|
|
170
|
+
return h("div", { className: "mcp-overlay", onClick: onClose },
|
|
171
|
+
h("div", {
|
|
172
|
+
className: "mcp-modal",
|
|
173
|
+
role: "dialog",
|
|
174
|
+
"aria-modal": "true",
|
|
175
|
+
"aria-labelledby": "mcp-prompts-title",
|
|
176
|
+
onClick: (event) => event.stopPropagation(),
|
|
177
|
+
},
|
|
178
|
+
h("div", { className: "mcp-modal-head" },
|
|
179
|
+
h("div", null,
|
|
180
|
+
h("h2", { id: "mcp-prompts-title" }, prompt ? (prompt.title || prompt.name) : "Prompts"),
|
|
181
|
+
h("p", null, prompt ? (prompt.description || "Промпт MCP-сервера") : `Промпты сервера «${state.serverId}».`),
|
|
182
|
+
),
|
|
183
|
+
h("button", { className: "mcp-btn ghost", type: "button", onClick: onClose, "aria-label": "Закрыть" }, "Закрыть"),
|
|
184
|
+
),
|
|
185
|
+
state.error ? h("div", { className: "mcp-error", role: "alert" }, state.error) : null,
|
|
186
|
+
!prompt ? h("div", { className: "mcp-prompt-list" },
|
|
187
|
+
state.prompts.length === 0 ? h("div", { className: "mcp-meta" }, "У сервера нет prompts.") : state.prompts.map((item) => h("button", {
|
|
188
|
+
key: item.name,
|
|
189
|
+
type: "button",
|
|
190
|
+
className: "mcp-prompt-card",
|
|
191
|
+
onClick: () => onPick(item),
|
|
192
|
+
}, h("b", null, item.name), h("span", null, item.description || "Без описания"))),
|
|
193
|
+
) : h("div", { className: "mcp-form" },
|
|
194
|
+
args.map((arg, index) => h("div", { className: `mcp-field${String(arg.description || "").length > 40 ? " wide" : ""}`, key: arg.name },
|
|
195
|
+
h("label", { htmlFor: `mcp-prompt-arg-${index}` }, arg.name, arg.required ? " *" : ""),
|
|
196
|
+
h("input", {
|
|
197
|
+
id: `mcp-prompt-arg-${index}`,
|
|
198
|
+
value: state.args[arg.name] ?? "",
|
|
199
|
+
placeholder: arg.description || "",
|
|
200
|
+
onChange: (event) => onArg(arg.name, event.target.value),
|
|
201
|
+
}),
|
|
202
|
+
)),
|
|
203
|
+
args.length === 0 ? h("p", { className: "mcp-note", style: { gridColumn: "1/-1" } }, "У промпта нет аргументов.") : null,
|
|
204
|
+
state.result ? h("div", { className: "mcp-field wide" },
|
|
205
|
+
h("label", null, "Результат"),
|
|
206
|
+
h("pre", { className: "mcp-prompt-output" }, state.result),
|
|
207
|
+
) : null,
|
|
208
|
+
h("div", { className: "mcp-form-actions", style: { gridColumn: "1/-1" } },
|
|
209
|
+
state.prompts.length > 1 ? h("button", { className: "mcp-btn ghost", type: "button", onClick: onBack }, "Назад") : null,
|
|
210
|
+
state.result ? h("button", { className: "mcp-btn ghost", type: "button", onClick: onCopy }, "Копировать") : null,
|
|
211
|
+
h("button", { className: "mcp-btn primary", type: "button", disabled: state.busy, onClick: onRun }, state.busy ? "Запрашиваем…" : "Получить"),
|
|
212
|
+
),
|
|
213
|
+
),
|
|
214
|
+
),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
159
218
|
function ServerForm({ initial, busy, onCancel, onSave }) {
|
|
160
219
|
const [form, setForm] = useState(initial);
|
|
161
220
|
const set = (patch) => setForm((current) => ({ ...current, ...patch }));
|
|
@@ -235,6 +294,7 @@ window.__ModuleLoader__.load({
|
|
|
235
294
|
const [form, setForm] = useState(null);
|
|
236
295
|
const [tools, setTools] = useState({});
|
|
237
296
|
const [toast, setToast] = useState("");
|
|
297
|
+
const [promptModal, setPromptModal] = useState(null);
|
|
238
298
|
|
|
239
299
|
const load = async () => {
|
|
240
300
|
try {
|
|
@@ -262,6 +322,72 @@ window.__ModuleLoader__.load({
|
|
|
262
322
|
}
|
|
263
323
|
};
|
|
264
324
|
|
|
325
|
+
const openPrompts = async (id) => {
|
|
326
|
+
setBusy(true);
|
|
327
|
+
setError(null);
|
|
328
|
+
try {
|
|
329
|
+
const result = await call(window.__DSH_MCP_CTX__, "prompts", { id });
|
|
330
|
+
setPromptModal({
|
|
331
|
+
serverId: id,
|
|
332
|
+
prompts: Array.isArray(result.prompts) ? result.prompts : [],
|
|
333
|
+
prompt: null,
|
|
334
|
+
args: {},
|
|
335
|
+
result: "",
|
|
336
|
+
busy: false,
|
|
337
|
+
error: result.error || "",
|
|
338
|
+
});
|
|
339
|
+
} catch (cause) {
|
|
340
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
341
|
+
} finally {
|
|
342
|
+
setBusy(false);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const pickPrompt = (prompt) => {
|
|
347
|
+
setPromptModal((current) => current ? {
|
|
348
|
+
...current,
|
|
349
|
+
prompt,
|
|
350
|
+
args: Object.fromEntries((prompt.arguments ?? []).map((arg) => [arg.name, ""])),
|
|
351
|
+
result: "",
|
|
352
|
+
error: "",
|
|
353
|
+
} : current);
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const runPrompt = async () => {
|
|
357
|
+
const current = promptModal;
|
|
358
|
+
if (!current?.prompt) return;
|
|
359
|
+
setPromptModal({ ...current, busy: true, error: "", result: "" });
|
|
360
|
+
try {
|
|
361
|
+
const result = await call(window.__DSH_MCP_CTX__, "prompt", {
|
|
362
|
+
id: current.serverId,
|
|
363
|
+
name: current.prompt.name,
|
|
364
|
+
arguments: current.args,
|
|
365
|
+
});
|
|
366
|
+
setPromptModal((latest) => latest ? {
|
|
367
|
+
...latest,
|
|
368
|
+
busy: false,
|
|
369
|
+
result: result.text || "",
|
|
370
|
+
error: result.text ? "" : "Сервер вернул пустой промпт",
|
|
371
|
+
} : latest);
|
|
372
|
+
} catch (cause) {
|
|
373
|
+
setPromptModal((latest) => latest ? {
|
|
374
|
+
...latest,
|
|
375
|
+
busy: false,
|
|
376
|
+
error: cause instanceof Error ? cause.message : String(cause),
|
|
377
|
+
} : latest);
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const copyPrompt = async () => {
|
|
382
|
+
try {
|
|
383
|
+
await navigator.clipboard.writeText(promptModal?.result ?? "");
|
|
384
|
+
setToast("Промпт скопирован");
|
|
385
|
+
setTimeout(() => setToast(""), 2500);
|
|
386
|
+
} catch {
|
|
387
|
+
setError("Не удалось скопировать — выделите текст вручную");
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
265
391
|
const presets = PRESETS;
|
|
266
392
|
const openPreset = (preset) => setForm({
|
|
267
393
|
edit: false,
|
|
@@ -333,11 +459,21 @@ window.__ModuleLoader__.load({
|
|
|
333
459
|
if (result.error) setError(result.error);
|
|
334
460
|
} catch (cause) { setError(cause instanceof Error ? cause.message : String(cause)); }
|
|
335
461
|
},
|
|
462
|
+
onPrompts: openPrompts,
|
|
336
463
|
onRemove: (id) => {
|
|
337
464
|
if (window.confirm(`Удалить MCP-сервер «${id}»?`)) void run("remove", { id }, "Сервер удалён");
|
|
338
465
|
},
|
|
339
466
|
}))) : null,
|
|
340
467
|
servers.length > 0 ? h("p", { className: "mcp-note" }, `Секреты: ${data?.secretsFile || "~/.dsh/mcp.secrets.json"} · новые tools появятся в новом чате после подключения.`) : null,
|
|
468
|
+
h(PromptModal, {
|
|
469
|
+
state: promptModal,
|
|
470
|
+
onClose: () => setPromptModal(null),
|
|
471
|
+
onPick: pickPrompt,
|
|
472
|
+
onBack: () => setPromptModal((current) => current ? { ...current, prompt: null, args: {}, result: "", error: "" } : current),
|
|
473
|
+
onArg: (name, value) => setPromptModal((current) => current ? { ...current, args: { ...current.args, [name]: value } } : current),
|
|
474
|
+
onRun: runPrompt,
|
|
475
|
+
onCopy: copyPrompt,
|
|
476
|
+
}),
|
|
341
477
|
form ? h(ServerForm, {
|
|
342
478
|
initial: form,
|
|
343
479
|
busy,
|
|
@@ -47,8 +47,8 @@ function commandCwd(ctx) {
|
|
|
47
47
|
function registerCommand(ctx, manager) {
|
|
48
48
|
ctx.commands.register({
|
|
49
49
|
name: "mcp",
|
|
50
|
-
description: "MCP-серверы: список, инструменты,
|
|
51
|
-
input: { hint: "list | trust | untrust | tools [server] | call <server> <tool> [json] | resources <server> | read <server> <uri> | reload | init" },
|
|
50
|
+
description: "MCP-серверы: список, инструменты, prompts, resources, reload",
|
|
51
|
+
input: { hint: "list | trust | untrust | tools [server] | prompts [server] | prompt <server> <name> [json] | call <server> <tool> [json] | resources <server> | read <server> <uri> | reload | init" },
|
|
52
52
|
recordInput: false,
|
|
53
53
|
async handler({ rawInput }) {
|
|
54
54
|
const input = String(rawInput ?? "").trim();
|
|
@@ -87,10 +87,10 @@ function registerCommand(ctx, manager) {
|
|
|
87
87
|
return { kind: "success", text: `MCP reload: ${results.filter((r) => r.status === "fulfilled" && r.value.client).length}/${results.length} подключено.\n${formatStatus(manager)}` };
|
|
88
88
|
}
|
|
89
89
|
const server = config.servers.find((item) => item.id === rest[0]);
|
|
90
|
-
if (["tools", "call", "resources", "read"].includes(action) && !rest[0]) {
|
|
90
|
+
if (["tools", "call", "resources", "read", "prompts", "prompt"].includes(action) && !rest[0]) {
|
|
91
91
|
return { kind: "error", text: `/mcp ${action}: укажите сервер` };
|
|
92
92
|
}
|
|
93
|
-
if (["tools", "call", "resources", "read"].includes(action) && !server) {
|
|
93
|
+
if (["tools", "call", "resources", "read", "prompts", "prompt"].includes(action) && !server) {
|
|
94
94
|
return { kind: "error", text: `MCP-сервер не найден: ${rest[0]}` };
|
|
95
95
|
}
|
|
96
96
|
if (!server.trusted) return { kind: "error", text: `MCP ${server.id}: trusted=false; включите доверие в mcp.json` };
|
|
@@ -111,6 +111,29 @@ function registerCommand(ctx, manager) {
|
|
|
111
111
|
const text = await manager.call(server.id, toolName, args);
|
|
112
112
|
return { kind: "success", text: text || "(пустой результат)" };
|
|
113
113
|
}
|
|
114
|
+
if (action === "prompts") {
|
|
115
|
+
const prompts = await manager.prompts(server.id);
|
|
116
|
+
return {
|
|
117
|
+
kind: "success",
|
|
118
|
+
text: prompts.length
|
|
119
|
+
? prompts.map((prompt) => {
|
|
120
|
+
const args = Array.isArray(prompt.arguments) ? prompt.arguments.map((arg) => `${arg.name}${arg.required ? "*" : ""}`).join(", ") : "";
|
|
121
|
+
return `- ${prompt.name}${prompt.description ? ` — ${prompt.description}` : ""}${args ? ` [${args}]` : ""}`;
|
|
122
|
+
}).join("\n")
|
|
123
|
+
: "У сервера нет prompts.",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (action === "prompt") {
|
|
127
|
+
const name = rest[1];
|
|
128
|
+
if (!name) return { kind: "error", text: "/mcp prompt: укажите name" };
|
|
129
|
+
const args = rest[2] ? JSON.parse(rest.slice(2).join(" ")) : {};
|
|
130
|
+
const result = await manager.prompt(server.id, name, args);
|
|
131
|
+
const text = (Array.isArray(result?.messages) ? result.messages : [])
|
|
132
|
+
.map((message) => typeof message.content === "string" ? message.content : message.content?.text ?? "")
|
|
133
|
+
.filter(Boolean)
|
|
134
|
+
.join("\n");
|
|
135
|
+
return { kind: "success", text: text || "(пустой prompt)" };
|
|
136
|
+
}
|
|
114
137
|
if (action === "resources") {
|
|
115
138
|
const resources = await manager.resources(server.id);
|
|
116
139
|
return { kind: "success", text: resources.length ? resources.map((item) => `- ${item.uri}${item.name ? ` — ${item.name}` : ""}`).join("\n") : "Нет resources." };
|
|
@@ -32,8 +32,17 @@ async function readEnvelope(request) {
|
|
|
32
32
|
return { rpcId: body.rpcId, method: body.method, payload: body.payload ?? {} };
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function
|
|
36
|
-
|
|
35
|
+
function promptText(result) {
|
|
36
|
+
const messages = Array.isArray(result?.messages) ? result.messages : [];
|
|
37
|
+
return messages.map((message) => {
|
|
38
|
+
const content = message?.content;
|
|
39
|
+
if (typeof content === "string") return content;
|
|
40
|
+
if (Array.isArray(content)) return content.map((block) => block?.text ?? "").filter(Boolean).join("\n");
|
|
41
|
+
return content?.text ?? "";
|
|
42
|
+
}).filter(Boolean).join("\n\n");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function publicServer(server) { return {
|
|
37
46
|
id: server.id,
|
|
38
47
|
scope: server.scope,
|
|
39
48
|
transport: server.url ? "http" : "stdio",
|
|
@@ -239,6 +248,28 @@ export function registerApi(ctx, manager) {
|
|
|
239
248
|
error: record.error,
|
|
240
249
|
};
|
|
241
250
|
});
|
|
251
|
+
operation("prompts", async (payload) => {
|
|
252
|
+
const config = loadConfig({ cwd: process.cwd() });
|
|
253
|
+
const server = config.servers.find((item) => item.id === String(payload.id ?? ""));
|
|
254
|
+
if (!server) throw new Error("server not found");
|
|
255
|
+
const record = await manager.connect(server);
|
|
256
|
+
if (!record.client?.connected) return { prompts: [], error: record.error };
|
|
257
|
+
try {
|
|
258
|
+
const prompts = await manager.prompts(server.id);
|
|
259
|
+
record.prompts = prompts;
|
|
260
|
+
return { prompts, error: "" };
|
|
261
|
+
} catch (error) {
|
|
262
|
+
return { prompts: [], error: error instanceof Error ? error.message : String(error) };
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
operation("prompt", async (payload) => {
|
|
266
|
+
const config = loadConfig({ cwd: process.cwd() });
|
|
267
|
+
const server = config.servers.find((item) => item.id === String(payload.id ?? ""));
|
|
268
|
+
if (!server) throw new Error("server not found");
|
|
269
|
+
await manager.connect(server);
|
|
270
|
+
const result = await manager.prompt(server.id, String(payload.name ?? ""), payload.arguments ?? {});
|
|
271
|
+
return { text: promptText(result), description: result?.description ?? "" };
|
|
272
|
+
});
|
|
242
273
|
operation("call", async (payload) => {
|
|
243
274
|
const config = loadConfig({ cwd: process.cwd() });
|
|
244
275
|
const server = config.servers.find((item) => item.id === String(payload.id ?? ""));
|
|
@@ -165,6 +165,7 @@ export class McpClient {
|
|
|
165
165
|
this.capabilities = {};
|
|
166
166
|
this.tools = [];
|
|
167
167
|
this.resources = [];
|
|
168
|
+
this.prompts = [];
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
async connect() {
|
|
@@ -212,6 +213,23 @@ export class McpClient {
|
|
|
212
213
|
return this.transport.request("resources/read", { uri }, this.config.timeoutMs);
|
|
213
214
|
}
|
|
214
215
|
|
|
216
|
+
async listPrompts() {
|
|
217
|
+
if (!this.capabilities.prompts) return [];
|
|
218
|
+
const prompts = [];
|
|
219
|
+
let cursor;
|
|
220
|
+
do {
|
|
221
|
+
const result = await this.transport.request("prompts/list", cursor ? { cursor } : {}, this.config.timeoutMs);
|
|
222
|
+
if (Array.isArray(result?.prompts)) prompts.push(...result.prompts);
|
|
223
|
+
cursor = result?.nextCursor;
|
|
224
|
+
} while (cursor);
|
|
225
|
+
this.prompts = prompts;
|
|
226
|
+
return prompts;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async getPrompt(name, args = {}) {
|
|
230
|
+
return this.transport.request("prompts/get", { name, arguments: args }, this.config.timeoutMs);
|
|
231
|
+
}
|
|
232
|
+
|
|
215
233
|
async close() {
|
|
216
234
|
await this.transport.close?.();
|
|
217
235
|
this.connected = false;
|
|
@@ -53,7 +53,7 @@ export class McpManager {
|
|
|
53
53
|
throw new Error("MCP manager is closed");
|
|
54
54
|
}
|
|
55
55
|
const tools = await client.listTools();
|
|
56
|
-
const record = { server, client, tools, resources: [], error: "" };
|
|
56
|
+
const record = { server, client, tools, resources: [], prompts: [], error: "" };
|
|
57
57
|
this.clients.set(server.id, record);
|
|
58
58
|
for (const tool of tools) {
|
|
59
59
|
const name = this.toolName(server.id, tool.name);
|
|
@@ -62,7 +62,7 @@ export class McpManager {
|
|
|
62
62
|
return record;
|
|
63
63
|
} catch (error) {
|
|
64
64
|
await client.close().catch(() => {});
|
|
65
|
-
const record = { server, client: null, tools: [], resources: [], error: error instanceof Error ? error.message : String(error) };
|
|
65
|
+
const record = { server, client: null, tools: [], resources: [], prompts: [], error: error instanceof Error ? error.message : String(error) };
|
|
66
66
|
this.clients.set(server.id, record);
|
|
67
67
|
return record;
|
|
68
68
|
}
|
|
@@ -122,6 +122,18 @@ export class McpManager {
|
|
|
122
122
|
return record.client.readResource(uri);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
async prompts(serverId) {
|
|
126
|
+
const record = this.clients.get(serverId);
|
|
127
|
+
if (!record?.client?.connected) throw new Error(`MCP server not connected: ${serverId}`);
|
|
128
|
+
return record.client.listPrompts();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async prompt(serverId, name, args) {
|
|
132
|
+
const record = this.clients.get(serverId);
|
|
133
|
+
if (!record?.client?.connected) throw new Error(`MCP server not connected: ${serverId}`);
|
|
134
|
+
return record.client.getPrompt(name, args);
|
|
135
|
+
}
|
|
136
|
+
|
|
125
137
|
status() {
|
|
126
138
|
return [...this.clients.values()].map((record) => ({
|
|
127
139
|
id: record.server.id,
|
|
Binary file
|
|
Binary file
|