feat(plugin): MCP 服务器管理页 + 按 server 独立启停与状态快照
design/09 §5.2.1 / §5.6.1:补上「看不见连没连上 / 暴露了哪些工具 / 为什么挂了」的洞。
- runtimeconfig:PluginConfig 新增 `enabled`(*bool,缺省=启用 ⇒ 既有 plugins.json
向后兼容);新增 RedactPluginSecrets(整把凭据 + 任意 ≥6 字符片段一并抹除)。
- mcp:新增 status.go 的只读状态读面(ToolSummaries:工具名 + 截断描述,取自能力
注册表本身,因此与模型可见的工具集合同源)。
- plugin 管理器:
· Install/Restore 尊重 enabled(停用=落盘不连接,重启后仍是停用,且一次都不连);
· SetEnabled 落盘 enabled(停用先落盘再下线;启用失败时 enabled 保持 false);
· 新增 Reconnect(对已停用者拒绝,不替用户启用);
· 新增 Statuses(三态:connected 缺席=未知,工具数/明细/时间只在知道时给出,
lastError 在记录那一刻即脱敏截断);
· 内置「MCP 服务器」页声明 + 7 个已声明动作(list/install/reconnect/enable/
disable/uninstall/transports),走既有通用动作端点(owner-admin + 审计);
· 护栏:热装插件的生命周期不改动内置提供者(mcp)的目录条目。
- gateway:已安装列表与 install 响应加 enabled,install 支持 config.enabled。
This commit is contained in:
1 parent
4ed5925296
commit
9654ce8861
14 files changed
+1969
-115
No files matched your search
@@ -287,15 +287,11 @@ func PanelIDValid(id string) bool {
|
||||
// markInstalled 记录安装时用过的面板目录键(memo,供卸载/停用复用),并把它报给
|
||||
// 面板中枢。未接线时只记 memo —— 计时与归因不依赖中枢在不在。
|
||||
func (m *Manager) markInstalled(cfg runtimeconfig.PluginConfig, changeReason string) {
|
||||
key := PanelPluginID(cfg)
|
||||
if key == "" {
|
||||
m.log.Warn("plugin has no panel catalog key", "name", cfg.Name)
|
||||
key := m.rememberPanelKey(cfg)
|
||||
if key == "" || isReservedPanelKey(key) {
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
m.panelKeys[cfg.Name] = key
|
||||
hub := m.panels
|
||||
m.mu.Unlock()
|
||||
hub := m.panelsHub()
|
||||
if hub == nil {
|
||||
return
|
||||
}
|
||||
@@ -308,6 +304,51 @@ func (m *Manager) markInstalled(cfg runtimeconfig.PluginConfig, changeReason str
|
||||
hub.MarkInstalled(key)
|
||||
}
|
||||
|
||||
// markDisabled 记录停用时用过的面板目录键,并让中枢隐藏该插件 `when:{enabled:true}` 的
|
||||
// 入口(声明保留,重新启用即原样回来)。
|
||||
//
|
||||
// 「停用」是**有意**没连,因此入口必须消失(§5.6.1「未启用的插件 ⇒ 入口不出现」);
|
||||
// 而「连不上」不该摘入口 —— 那时用户正需要点「重连」。
|
||||
func (m *Manager) markDisabled(cfg runtimeconfig.PluginConfig) {
|
||||
key := m.rememberPanelKey(cfg)
|
||||
if key == "" || isReservedPanelKey(key) {
|
||||
return
|
||||
}
|
||||
if hub := m.panelsHub(); hub != nil {
|
||||
hub.SetEnabled(key, false)
|
||||
}
|
||||
}
|
||||
|
||||
// rememberPanelKey 记下该插件的面板目录键(memo)并回它;键非法时记日志并回空串。
|
||||
//
|
||||
// memo 是**缓存而不是第二份真源**:键由 PanelPluginID 从 cfg 推出,这里只把结果留下,
|
||||
// 好让「活 server 已不在了」的卸载/停用仍能精确找到该清哪一条。
|
||||
func (m *Manager) rememberPanelKey(cfg runtimeconfig.PluginConfig) string {
|
||||
key := PanelPluginID(cfg)
|
||||
if key == "" {
|
||||
m.log.Warn("plugin has no panel catalog key", "name", cfg.Name)
|
||||
return ""
|
||||
}
|
||||
if isReservedPanelKey(key) {
|
||||
// 内置提供者的命名空间不归热装插件管:一台恰好叫 mcp 的 server 不许改动
|
||||
// 「MCP 服务器」页本身(见 isReservedPanelKey)。
|
||||
m.log.Info("plugin name collides with a built-in panel namespace; panel entry untouched",
|
||||
"name", cfg.Name, "panelId", key)
|
||||
return ""
|
||||
}
|
||||
m.mu.Lock()
|
||||
m.panelKeys[cfg.Name] = key
|
||||
m.mu.Unlock()
|
||||
return key
|
||||
}
|
||||
|
||||
// panelsHub 返回已接线中枢(未接线时 nil)。
|
||||
func (m *Manager) panelsHub() *Panels {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return m.panels
|
||||
}
|
||||
|
||||
// forgetPanels 在卸载后清掉该插件的面板。键优先取安装时记下的 memo;没有记录
|
||||
// (例如配置文件是手工放进 plugins.json 的)时回落到「名字恰好合法就用名字」。
|
||||
func (m *Manager) forgetPanels(name string) {
|
||||
@@ -325,6 +366,9 @@ func (m *Manager) forgetPanels(name string) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if isReservedPanelKey(key) {
|
||||
return
|
||||
}
|
||||
hub.MarkUninstalled(key)
|
||||
}
|
||||
|
||||
@@ -343,5 +387,8 @@ func (m *Manager) setPanelsEnabled(name string, on bool) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if isReservedPanelKey(key) {
|
||||
return
|
||||
}
|
||||
hub.SetEnabled(key, on)
|
||||
}
|
||||
Reference in new issue
Block a user