@xxxyz/dsh-mcp-manager 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +122 -0
- package/README.zh-CN.md +173 -0
- package/install.mjs +369 -0
- package/install.ps1 +43 -0
- package/install.sh +33 -0
- package/lib/client.js +303 -0
- package/lib/index.js +967 -0
- package/package.json +78 -0
- package/uninstall.mjs +161 -0
- package/uninstall.ps1 +33 -0
- package/uninstall.sh +28 -0
- package//345/256/211/350/243/205/346/226/271/345/274/217.md +131 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// dsh-mcp-manager — durable client half (web bundle, ModuleLoader CJS format).
|
|
2
|
+
// Registers the "MCP 管理" settings page. Talks to the host half through the
|
|
3
|
+
// exact-path HTTP route /dsh-mcp-manager/api (same origin) instead of the
|
|
4
|
+
// dynamic-only host.call channel.
|
|
5
|
+
window.__ModuleLoader__.load({
|
|
6
|
+
id: 'dsh-mcp-manager',
|
|
7
|
+
factory: (require) => {
|
|
8
|
+
var module = { exports: {} }
|
|
9
|
+
var exports = module.exports
|
|
10
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
11
|
+
const React = require('react')
|
|
12
|
+
|
|
13
|
+
const CSS =
|
|
14
|
+
'.mcpm-wrap{font-size:13px;line-height:1.6;display:flex;flex-direction:column;gap:14px;padding:4px 2px;color:inherit}' +
|
|
15
|
+
'.mcpm-wrap h2{margin:0;font-size:16px}' +
|
|
16
|
+
'.mcpm-sub{opacity:.75;font-size:12px}' +
|
|
17
|
+
'.mcpm-msg{padding:8px 10px;border-radius:6px;font-size:12px}' +
|
|
18
|
+
'.mcpm-msg.ok{background:rgba(46,160,67,.15);border:1px solid rgba(46,160,67,.4)}' +
|
|
19
|
+
'.mcpm-msg.err{background:rgba(248,81,73,.15);border:1px solid rgba(248,81,73,.4)}' +
|
|
20
|
+
'.mcpm-msg.info{background:rgba(88,166,255,.15);border:1px solid rgba(88,166,255,.5)}' +
|
|
21
|
+
'.mcpm-path{font-family:monospace;font-size:11px;opacity:.7;word-break:break-all}' +
|
|
22
|
+
'.mcpm-btn{font-size:12px;padding:3px 10px;border-radius:5px;border:1px solid rgba(128,128,128,.5);background:transparent;color:inherit;cursor:pointer;white-space:nowrap}' +
|
|
23
|
+
'.mcpm-btn:disabled{opacity:.5;cursor:default}' +
|
|
24
|
+
'.mcpm-btn.primary{border-color:rgba(88,166,255,.7)}' +
|
|
25
|
+
'.mcpm-btn.danger{border-color:rgba(248,81,73,.7)}' +
|
|
26
|
+
'.mcpm-backup{display:flex;flex-direction:column;gap:8px;border:1px dashed rgba(128,128,128,.45);border-radius:8px;padding:10px 12px}' +
|
|
27
|
+
'.mcpm-backup .row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}' +
|
|
28
|
+
'.mcpm-json{width:100%;box-sizing:border-box;font-family:monospace;font-size:11px;min-height:70px;background:transparent;color:inherit;border:1px solid rgba(128,128,128,.4);border-radius:5px;padding:6px;white-space:pre;overflow:auto}' +
|
|
29
|
+
'.mcpm-report{font-family:monospace;font-size:11px;opacity:.85;word-break:break-all}' +
|
|
30
|
+
'.mcpm-warn{font-size:11px;color:rgba(248,81,73,.95)}' +
|
|
31
|
+
'.mcpm-form{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 12px;border:1px solid rgba(128,128,128,.35);border-radius:8px;padding:12px}' +
|
|
32
|
+
'.mcpm-form h3{margin:0;grid-column:1/-1;font-size:13px}' +
|
|
33
|
+
'.mcpm-form label{display:flex;flex-direction:column;gap:3px;font-size:12px}' +
|
|
34
|
+
'.mcpm-form .full{grid-column:1/-1}' +
|
|
35
|
+
'.mcpm-form input,.mcpm-form select,.mcpm-form textarea{font-size:12px;padding:4px 6px;border-radius:5px;border:1px solid rgba(128,128,128,.5);background:transparent;color:inherit}' +
|
|
36
|
+
'.mcpm-form textarea{font-family:monospace;resize:vertical;min-height:44px}' +
|
|
37
|
+
'.mcpm-row{border:1px solid rgba(128,128,128,.35);border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:8px}' +
|
|
38
|
+
'.mcpm-row-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}' +
|
|
39
|
+
'.mcpm-name{font-weight:600}' +
|
|
40
|
+
'.mcpm-chip{font-size:11px;padding:1px 8px;border-radius:999px;border:1px solid rgba(128,128,128,.5);white-space:nowrap}' +
|
|
41
|
+
'.mcpm-chip.on{background:rgba(46,160,67,.18);border-color:rgba(46,160,67,.55)}' +
|
|
42
|
+
'.mcpm-chip.off{background:rgba(128,128,128,.15)}' +
|
|
43
|
+
'.mcpm-chip.live{background:rgba(88,166,255,.15);border-color:rgba(88,166,255,.5)}' +
|
|
44
|
+
'.mcpm-chip.bad{background:rgba(248,81,73,.18);border-color:rgba(248,81,73,.6)}' +
|
|
45
|
+
'.mcpm-chip.warn{background:rgba(219,154,4,.18);border-color:rgba(219,154,4,.6)}' +
|
|
46
|
+
'.mcpm-row-actions{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}' +
|
|
47
|
+
'.mcpm-detail{font-family:monospace;font-size:11px;opacity:.75;word-break:break-all}' +
|
|
48
|
+
'.mcpm-mask{position:fixed;inset:0;background:transparent;display:flex;align-items:center;justify-content:center;z-index:99999}' +
|
|
49
|
+
'.mcpm-dialog{background:color-mix(in srgb, currentColor 8%, transparent);backdrop-filter:blur(14px) saturate(1.2);border:1px solid rgba(128,128,128,.5);border-radius:10px;padding:16px 18px;min-width:300px;max-width:440px;box-shadow:0 10px 34px rgba(0,0,0,.35);color:inherit}' +
|
|
50
|
+
'.mcpm-dialog-title{font-size:14px;font-weight:600;margin-bottom:8px}' +
|
|
51
|
+
'.mcpm-dialog-body{font-size:12px;opacity:.88;line-height:1.7;margin-bottom:16px;word-break:break-all}' +
|
|
52
|
+
'.mcpm-dialog-actions{display:flex;justify-content:flex-end;gap:8px}'
|
|
53
|
+
|
|
54
|
+
function ensureCss() {
|
|
55
|
+
if (typeof document === 'undefined') return
|
|
56
|
+
const id = 'dsh-mcp-manager'
|
|
57
|
+
if (document.querySelector('style[data-plugin-css="' + id + '"]')) return
|
|
58
|
+
const tag = document.createElement('style')
|
|
59
|
+
tag.dataset.plugin = id
|
|
60
|
+
tag.dataset.pluginCss = id
|
|
61
|
+
tag.textContent = CSS
|
|
62
|
+
document.head.appendChild(tag)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function apiCall(op, args) {
|
|
66
|
+
return fetch('/dsh-mcp-manager/api', {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: { 'content-type': 'application/json' },
|
|
69
|
+
body: JSON.stringify({ op, args: args || {} }),
|
|
70
|
+
}).then((r) => r.json()).catch((e) => ({ ok: false, error: String((e && e.message) || e) }))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
name: 'dsh-mcp-manager-client',
|
|
75
|
+
inject: ['timer'],
|
|
76
|
+
apply(ctx) {
|
|
77
|
+
ensureCss()
|
|
78
|
+
const slots = ctx.get('slots')
|
|
79
|
+
if (slots === undefined) return
|
|
80
|
+
|
|
81
|
+
const LEVEL_LABEL = { project: '项目级', global: '全局', loader: '已加载(loader)' }
|
|
82
|
+
const emptyForm = () => ({ serverName: '', transport: 'streamable-http', url: '', command: '', args: '', headers: '', env: '', level: 'project' })
|
|
83
|
+
const kvToLines = (obj) => (obj ? Object.keys(obj).map((k) => k + '=' + obj[k]).join('\n') : '')
|
|
84
|
+
|
|
85
|
+
function MCPPage() {
|
|
86
|
+
const [state, setState] = React.useState({ loading: true, error: null, rows: [], paths: null, errors: [] })
|
|
87
|
+
const [formOpen, setFormOpen] = React.useState(false)
|
|
88
|
+
const [editing, setEditing] = React.useState(null)
|
|
89
|
+
const [busy, setBusy] = React.useState(null)
|
|
90
|
+
const [msg, setMsg] = React.useState(null)
|
|
91
|
+
const [form, setForm] = React.useState(emptyForm())
|
|
92
|
+
const [restartInfo, setRestartInfo] = React.useState(null)
|
|
93
|
+
const [tick, setTick] = React.useState(0)
|
|
94
|
+
const [backup, setBackup] = React.useState(null)
|
|
95
|
+
const [importText, setImportText] = React.useState('')
|
|
96
|
+
const [confirmRow, setConfirmRow] = React.useState(null)
|
|
97
|
+
|
|
98
|
+
const refresh = () => {
|
|
99
|
+
apiCall('mcpm-list', {}).then((res) => {
|
|
100
|
+
setState({ loading: false, error: res && res.ok ? null : ((res && res.error) || '加载失败'), rows: (res && res.rows) || [], paths: (res && res.paths) || null, errors: (res && res.errors) || [] })
|
|
101
|
+
}).catch((e) => setState({ loading: false, error: String((e && e.message) || e), rows: [], paths: null, errors: [] }))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
React.useEffect(() => { refresh() }, [])
|
|
105
|
+
React.useEffect(() => ctx.interval(() => setTick((t) => t + 1), 1000), [])
|
|
106
|
+
React.useEffect(() => ctx.interval(() => refresh(), 3000), [])
|
|
107
|
+
React.useEffect(() => {
|
|
108
|
+
if (!msg) return
|
|
109
|
+
const d = ctx.timeout(() => setMsg(null), 3000)
|
|
110
|
+
return d
|
|
111
|
+
}, [msg])
|
|
112
|
+
|
|
113
|
+
const run = (method, args, label, onOk) => {
|
|
114
|
+
setMsg(null)
|
|
115
|
+
setBusy(label)
|
|
116
|
+
apiCall(method, args).then((res) => {
|
|
117
|
+
if (res && res.ok) { setMsg({ kind: 'ok', text: '操作成功' }); refresh(); if (onOk) onOk() }
|
|
118
|
+
else setMsg({ kind: 'err', text: (res && res.error) || '操作失败' })
|
|
119
|
+
}).catch((e) => setMsg({ kind: 'err', text: String((e && e.message) || e) })).then(() => { setBusy(null); setRestartInfo(null) })
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const doExport = () => {
|
|
123
|
+
setMsg(null)
|
|
124
|
+
apiCall('mcpm-export', {}).then((res) => {
|
|
125
|
+
if (res && res.ok) { setBackup({ mode: 'export', json: res.json, savedTo: res.savedTo }); setMsg({ kind: 'ok', text: '已导出' }) }
|
|
126
|
+
else setMsg({ kind: 'err', text: (res && res.error) || '导出失败' })
|
|
127
|
+
}).catch((e) => setMsg({ kind: 'err', text: String((e && e.message) || e) }))
|
|
128
|
+
}
|
|
129
|
+
const doImport = () => {
|
|
130
|
+
setMsg(null)
|
|
131
|
+
apiCall('mcpm-import', { json: importText }).then((res) => {
|
|
132
|
+
if (res && res.ok) {
|
|
133
|
+
setBackup({ mode: 'import', added: res.added || [], skipped: res.skipped || [] })
|
|
134
|
+
setImportText('')
|
|
135
|
+
setMsg({ kind: 'ok', text: '导入完成:新增 ' + (res.added || []).length + ',跳过 ' + (res.skipped || []).length })
|
|
136
|
+
refresh()
|
|
137
|
+
} else setMsg({ kind: 'err', text: (res && res.error) || '导入失败' })
|
|
138
|
+
}).catch((e) => setMsg({ kind: 'err', text: String((e && e.message) || e) }))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const setF = (k) => (ev) => setForm(Object.assign({}, form, { [k]: ev.target.value }))
|
|
142
|
+
|
|
143
|
+
const openAdd = () => { setEditing(null); setForm(emptyForm()); setFormOpen(true) }
|
|
144
|
+
const openEdit = (row) => {
|
|
145
|
+
setEditing({ id: row.id, level: row.level })
|
|
146
|
+
setForm({
|
|
147
|
+
serverName: row.serverName || '',
|
|
148
|
+
transport: row.transport === 'stdio' ? 'stdio' : 'streamable-http',
|
|
149
|
+
url: row.url || '',
|
|
150
|
+
command: row.command || '',
|
|
151
|
+
args: (row.args || []).join('\n'),
|
|
152
|
+
headers: kvToLines(row.headers),
|
|
153
|
+
env: kvToLines(row.env),
|
|
154
|
+
level: row.level === 'global' ? 'global' : 'project',
|
|
155
|
+
})
|
|
156
|
+
setFormOpen(true)
|
|
157
|
+
}
|
|
158
|
+
const cancelForm = () => { setFormOpen(false); setEditing(null) }
|
|
159
|
+
|
|
160
|
+
const submitForm = () => {
|
|
161
|
+
const payload = Object.assign({}, form)
|
|
162
|
+
if (payload.transport === 'stdio') { payload.url = ''; payload.headers = '' }
|
|
163
|
+
else { payload.command = ''; payload.args = ''; payload.env = '' }
|
|
164
|
+
const onOk = () => { setFormOpen(false); setEditing(null) }
|
|
165
|
+
if (editing) run('mcpm-edit', Object.assign({ id: editing.id }, payload), 'form', onOk)
|
|
166
|
+
else run('mcpm-add', payload, 'form', onOk)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const rowsLocked = formOpen || busy !== null
|
|
170
|
+
const toggleRow = (row) => run('mcpm-set-enabled', { id: row.id, level: row.level, enabled: row.disabled }, row.id + ':toggle')
|
|
171
|
+
const restartRow = (row) => {
|
|
172
|
+
setTick(0)
|
|
173
|
+
setRestartInfo({ id: row.id, name: row.serverName, startedAt: Date.now() })
|
|
174
|
+
run('mcpm-restart', { id: row.id, level: row.level }, row.id + ':restart')
|
|
175
|
+
}
|
|
176
|
+
const removeRow = (row) => setConfirmRow(row)
|
|
177
|
+
const confirmDelete = () => {
|
|
178
|
+
const row = confirmRow
|
|
179
|
+
setConfirmRow(null)
|
|
180
|
+
if (row) run('mcpm-remove', { id: row.id, level: row.level }, row.id + ':remove')
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const rows = state.rows || []
|
|
184
|
+
const maskKv = (obj) => {
|
|
185
|
+
if (!obj) return null
|
|
186
|
+
return Object.keys(obj).map((k) => k + ': ' + String(obj[k]).replace(/^(.{4}).*$/, '$1****')).join(' ')
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const liveChip = (row) => {
|
|
190
|
+
if (!row.live) return null
|
|
191
|
+
const phase = row.live.phase
|
|
192
|
+
if (phase === 'failed') {
|
|
193
|
+
return React.createElement('span', { className: 'mcpm-chip bad' }, 'loader: 失败')
|
|
194
|
+
}
|
|
195
|
+
if (!row.live.enabled) {
|
|
196
|
+
return React.createElement('span', { className: 'mcpm-chip live' }, 'loader: off')
|
|
197
|
+
}
|
|
198
|
+
if (phase && phase !== 'active') {
|
|
199
|
+
return React.createElement('span', { className: 'mcpm-chip live' }, 'loader: on · ' + phase)
|
|
200
|
+
}
|
|
201
|
+
const tc = typeof row.toolCount === 'number' ? row.toolCount : null
|
|
202
|
+
const cls = tc === 0 ? 'mcpm-chip warn' : 'mcpm-chip live'
|
|
203
|
+
return React.createElement('span', { className: cls }, 'loader: on · active' + (tc === null ? '' : ' · ' + tc + ' 工具'))
|
|
204
|
+
}
|
|
205
|
+
const liveHint = (row) => {
|
|
206
|
+
if (!row.live) return null
|
|
207
|
+
if (row.live.phase === 'failed') {
|
|
208
|
+
return React.createElement('div', { className: 'mcpm-warn' }, '⚠ loader 启动失败:请检查 URL / 命令 / 凭证 / 网络,然后点“重启”重试')
|
|
209
|
+
}
|
|
210
|
+
if (row.live.phase === 'active' && typeof row.toolCount === 'number' && row.toolCount === 0) {
|
|
211
|
+
return React.createElement('div', { className: 'mcpm-warn' }, '⚠ 已连接但未注册任何工具:服务端可能未就绪或工具列表为空')
|
|
212
|
+
}
|
|
213
|
+
return null
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return React.createElement('div', { className: 'mcpm-wrap' },
|
|
217
|
+
React.createElement('h2', null, 'MCP 服务管理'),
|
|
218
|
+
React.createElement('div', { className: 'mcpm-sub' },
|
|
219
|
+
'管理 dsh-mcp-client 服务:写入 项目级(web profile) / 全局(home) 的 cordis.patch.yml,经 HMR 实时生效;重启 DSH 后由 Loader 自动加载。'),
|
|
220
|
+
restartInfo && React.createElement('div', { className: 'mcpm-msg info' },
|
|
221
|
+
'重启中… ' + restartInfo.name + '(已等待 ' + Math.floor((Date.now() - restartInfo.startedAt) / 1000) + ' 秒,完成后自动刷新)'),
|
|
222
|
+
msg && React.createElement('div', { className: 'mcpm-msg ' + msg.kind }, msg.text),
|
|
223
|
+
state.error && React.createElement('div', { className: 'mcpm-msg err' }, state.error),
|
|
224
|
+
state.errors && state.errors.length > 0 && React.createElement('div', { className: 'mcpm-msg err' }, '读取补丁告警: ' + state.errors.join('; ')),
|
|
225
|
+
state.paths && React.createElement('div', { className: 'mcpm-path' },
|
|
226
|
+
'项目级: ' + state.paths.project + '\n全局: ' + state.paths.global),
|
|
227
|
+
React.createElement('div', { className: 'mcpm-backup' },
|
|
228
|
+
React.createElement('div', { className: 'row' },
|
|
229
|
+
React.createElement('span', { className: 'mcpm-sub' }, '备份 / 恢复'),
|
|
230
|
+
React.createElement('button', { className: 'mcpm-btn', disabled: busy !== null, onClick: doExport }, '导出配置'),
|
|
231
|
+
React.createElement('button', { className: 'mcpm-btn', disabled: busy !== null, onClick: () => { setBackup({ mode: 'import', added: [], skipped: [] }); setImportText('') } }, '导入配置')),
|
|
232
|
+
backup && backup.mode === 'export' && React.createElement('div', null,
|
|
233
|
+
React.createElement('div', { className: 'mcpm-report' }, '已同时保存到: ' + (backup.savedTo || '(未落盘)')),
|
|
234
|
+
React.createElement('textarea', { className: 'mcpm-json', readOnly: true, value: backup.json })),
|
|
235
|
+
backup && backup.mode === 'import' && React.createElement('div', null,
|
|
236
|
+
React.createElement('textarea', { className: 'mcpm-json', placeholder: '粘贴导出的 JSON(数组或 { rows: [...] })', value: importText, onChange: (ev) => setImportText(ev.target.value) }),
|
|
237
|
+
React.createElement('div', { className: 'row' },
|
|
238
|
+
React.createElement('button', { className: 'mcpm-btn primary', disabled: busy !== null || !importText.trim(), onClick: doImport }, '执行导入'),
|
|
239
|
+
React.createElement('span', { className: 'mcpm-report' }, '新增: ' + (backup.added || []).join(', ') + ' 跳过: ' + (backup.skipped || []).map((s) => s.id + '(' + s.reason + ')').join(', ')))),
|
|
240
|
+
(!backup || backup.mode === 'export') && React.createElement('div', { className: 'mcpm-sub' }, '提示:导入会合并新增,已存在的条目自动跳过;先“导出配置”可获得模板。')),
|
|
241
|
+
React.createElement('div', null,
|
|
242
|
+
React.createElement('button', { className: 'mcpm-btn primary', onClick: () => (formOpen ? cancelForm() : openAdd()) },
|
|
243
|
+
formOpen ? '收起表单' : '+ 新增 MCP 服务')),
|
|
244
|
+
formOpen && React.createElement('div', { className: 'mcpm-form' },
|
|
245
|
+
React.createElement('h3', null, editing ? '编辑 MCP 服务:' + editing.id : '新增 MCP 服务'),
|
|
246
|
+
React.createElement('label', null, '服务名称 serverName(唯一,1-32 位)', React.createElement('input', { value: form.serverName, onChange: setF('serverName'), placeholder: 'e.g. github' })),
|
|
247
|
+
React.createElement('label', null, '传输方式', React.createElement('select', { value: form.transport, onChange: setF('transport') },
|
|
248
|
+
React.createElement('option', { value: 'streamable-http' }, 'streamable-http'),
|
|
249
|
+
React.createElement('option', { value: 'stdio' }, 'stdio'))),
|
|
250
|
+
React.createElement('label', { className: 'full' }, '级别', React.createElement('select', { value: form.level, onChange: setF('level') },
|
|
251
|
+
React.createElement('option', { value: 'project' }, '项目级(本 profile:profiles/web/cordis.patch.yml)'),
|
|
252
|
+
React.createElement('option', { value: 'global' }, '全局(所有 profile:~/.dsh/cordis.patch.yml)'))),
|
|
253
|
+
form.transport === 'streamable-http'
|
|
254
|
+
? React.createElement('label', { className: 'full' }, '服务 URL', React.createElement('input', { value: form.url, onChange: setF('url'), placeholder: 'https://host/mcp' }))
|
|
255
|
+
: React.createElement('label', { className: 'full' }, '启动命令', React.createElement('input', { value: form.command, onChange: setF('command'), placeholder: 'npx -y @modelcontextprotocol/server-github' })),
|
|
256
|
+
form.transport === 'stdio' && React.createElement('label', { className: 'full' }, '参数(空格/换行分隔)', React.createElement('textarea', { value: form.args, onChange: setF('args'), placeholder: '-y\n@modelcontextprotocol/server-github' })),
|
|
257
|
+
form.transport === 'stdio' && React.createElement('label', { className: 'full' }, '环境变量(每行 key=value)', React.createElement('textarea', { value: form.env, onChange: setF('env'), placeholder: 'GITHUB_TOKEN=xxx' })),
|
|
258
|
+
form.transport === 'streamable-http' && React.createElement('label', { className: 'full' }, '请求头(每行 key=value)', React.createElement('textarea', { value: form.headers, onChange: setF('headers'), placeholder: 'Authorization=Bearer xxx' })),
|
|
259
|
+
React.createElement('label', { className: 'full' }, ' ',
|
|
260
|
+
React.createElement('div', { style: { display: 'flex', gap: '8px' } },
|
|
261
|
+
React.createElement('button', { className: 'mcpm-btn primary', disabled: busy === 'form', onClick: submitForm }, editing ? '保存' : '添加'),
|
|
262
|
+
React.createElement('button', { className: 'mcpm-btn', onClick: cancelForm }, '取消')))),
|
|
263
|
+
state.loading
|
|
264
|
+
? React.createElement('div', { className: 'mcpm-sub' }, '加载中…')
|
|
265
|
+
: rows.length === 0
|
|
266
|
+
? React.createElement('div', { className: 'mcpm-sub' }, '暂无 MCP 服务。点击“+ 新增 MCP 服务”添加。')
|
|
267
|
+
: rows.map((row) => React.createElement('div', { key: row.id, className: 'mcpm-row' },
|
|
268
|
+
React.createElement('div', { className: 'mcpm-row-head' },
|
|
269
|
+
React.createElement('span', { className: 'mcpm-name' }, row.serverName),
|
|
270
|
+
React.createElement('span', { className: 'mcpm-chip ' + (row.disabled ? 'off' : 'on') }, row.disabled ? '已禁用' : '已启用'),
|
|
271
|
+
React.createElement('span', { className: 'mcpm-chip' }, LEVEL_LABEL[row.level] || row.level),
|
|
272
|
+
liveChip(row),
|
|
273
|
+
!row.managed && row.level !== 'loader' && React.createElement('span', { className: 'mcpm-chip off' }, '手动添加')),
|
|
274
|
+
liveHint(row),
|
|
275
|
+
React.createElement('div', { className: 'mcpm-detail' },
|
|
276
|
+
'id: ' + row.id + ' · ' + (row.transport || '?') + (row.url ? ' · ' + row.url : '') + (row.command ? ' · ' + row.command + ((row.args && row.args.length) ? ' ' + row.args.join(' ') : '') : '')),
|
|
277
|
+
(row.headers || row.env) && React.createElement('div', { className: 'mcpm-detail' },
|
|
278
|
+
(row.headers ? 'headers: ' + maskKv(row.headers) + ' ' : '') + (row.env ? 'env: ' + Object.keys(row.env).map((k) => k + ': ****').join(' ') : '')),
|
|
279
|
+
React.createElement('div', { className: 'mcpm-row-actions' },
|
|
280
|
+
row.level !== 'loader' && React.createElement('button', { className: 'mcpm-btn', disabled: rowsLocked, onClick: () => openEdit(row) }, '编辑'),
|
|
281
|
+
React.createElement('button', { className: 'mcpm-btn', disabled: rowsLocked, onClick: () => toggleRow(row) }, row.disabled ? '启用' : '禁用'),
|
|
282
|
+
React.createElement('button', { className: 'mcpm-btn', disabled: rowsLocked, onClick: () => restartRow(row) }, '重启'),
|
|
283
|
+
row.level !== 'loader' && React.createElement('button', { className: 'mcpm-btn danger', disabled: rowsLocked, onClick: () => removeRow(row) }, '删除')))),
|
|
284
|
+
confirmRow && React.createElement('div', { className: 'mcpm-mask', onClick: () => setConfirmRow(null) },
|
|
285
|
+
React.createElement('div', { className: 'mcpm-dialog', onClick: (ev) => ev.stopPropagation() },
|
|
286
|
+
React.createElement('div', { className: 'mcpm-dialog-title' }, '删除 MCP 服务'),
|
|
287
|
+
React.createElement('div', { className: 'mcpm-dialog-body' },
|
|
288
|
+
'确定要删除“' + confirmRow.serverName + '”吗?\n删除后该服务的配置将从补丁文件中移除,相关工具立即下线,此操作不可撤销。'),
|
|
289
|
+
React.createElement('div', { className: 'mcpm-dialog-actions' },
|
|
290
|
+
React.createElement('button', { className: 'mcpm-btn', onClick: () => setConfirmRow(null) }, '取消'),
|
|
291
|
+
React.createElement('button', { className: 'mcpm-btn danger', onClick: confirmDelete }, '删除'))))
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
slots.inject('settings.section', () => slots.register(
|
|
296
|
+
{ name: 'settings.section', id: 'mcp-manager', order: 16, label: 'MCP 管理' },
|
|
297
|
+
() => React.createElement(MCPPage)
|
|
298
|
+
))
|
|
299
|
+
},
|
|
300
|
+
}
|
|
301
|
+
return module.exports
|
|
302
|
+
},
|
|
303
|
+
})
|