dsh-plugin-mcp-toggles 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +62 -46
  2. package/lib/client.js +90 -56
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,55 +1,41 @@
1
1
  # dsh-plugin-mcp-toggles
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/dsh-plugin-mcp-toggles.svg)](https://www.npmjs.com/package/dsh-plugin-mcp-toggles)
4
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-plugin-mcp-toggles.svg)](https://www.npmjs.com/package/dsh-plugin-mcp-toggles)
4
5
  [![license](https://img.shields.io/npm/l/dsh-plugin-mcp-toggles.svg)](LICENSE)
5
6
  [![node](https://img.shields.io/node/v/dsh-plugin-mcp-toggles.svg)](package.json)
6
7
 
7
8
  为**每个会话**单独开关已安装的 MCP 服务器。
8
9
 
9
- ## 它解决什么
10
-
11
- MCP 服务器在 DSH profile 里是进程级的:`@deepseek-ai/dsh-mcp-client` 每行连一个
12
- 服务器,把 `mcp__<serverName>__<tool>` 注册进**全局**工具层。于是每个会话都看到
13
- 同一批 MCP 工具 —— 想让 A 会话只用 REA、B 会话只用别的服务器,原生做不到。
14
-
15
- 装上本插件后:会话标题栏右侧出现一个 **MCP** 按钮,点开列出已安装的 MCP 服务器,
16
- 每行一个开关,**只影响当前会话**。新会话默认全部开启(装上插件不会悄悄改变任何
17
- 人现有的能力)。
10
+ 装好之后,会话标题栏右侧(新会话页则是输入卡片上方那一行)会出现一个 **MCP**
11
+ 按钮,点开列出已安装的 MCP 服务器,每行一个开关,**只影响当前会话**。
18
12
 
19
13
  ## 安装
20
14
 
21
- 这是一个标准的 DSH bundle 包:`package.json` 里用 `dsh.bundle.patch` 声明要插入的
22
- 插件行,用 `dsh.client` 声明 Web 端入口,没有依赖、没有构建步骤,装上即可用。
15
+ 用 DSH 自带的插件命令,装进你要用的 profile:
23
16
 
24
- 从 npm 装进当前 profile:
25
-
26
- ```
27
- plugin_manager action=install_bundle target=dsh-plugin-mcp-toggles
17
+ ```bash
18
+ dsh plugin --profile web add dsh-plugin-mcp-toggles
28
19
  ```
29
20
 
30
- `target` 接受任意安装 spec:
21
+ 把 `web` 换成你的 profile 名(`desktop`、`tui`、`headless` 等)。这条命令会
22
+ 把它加入该 profile 的依赖与 bundle 列表,然后重启 DSH 即可。
31
23
 
32
- ```
33
- plugin_manager action=install_bundle target=dsh-plugin-mcp-toggles@0.1.0
34
- plugin_manager action=install_bundle target=github:ZhaoHanshao/dsh-plugin-mcp-toggles
35
- ```
24
+ 指定版本或改用 GitHub 源:
36
25
 
37
- 装好后重启 DSH,打开任意会话即可看到按钮。
26
+ ```bash
27
+ dsh plugin --profile web add dsh-plugin-mcp-toggles@0.1.0
28
+ dsh plugin --profile web add github:ZhaoHanshao/dsh-plugin-mcp-toggles
29
+ ```
38
30
 
39
- 想先读代码或自己打包检查文件清单:
31
+ 卸载:
40
32
 
41
33
  ```bash
42
- npm install dsh-plugin-mcp-toggles
43
- npm pack dsh-plugin-mcp-toggles --dry-run # 只打印将要发布的文件
34
+ dsh plugin --profile web remove dsh-plugin-mcp-toggles
44
35
  ```
45
36
 
46
- 它需要宿主提供 `webServer` 服务与 `conversation.session.header.utilities` /
47
- `conversation.hero.options` 两个槽位,因此要求 DSH 版本不低于它开发时的
48
- `@deepseek-ai/dsh-client-ui-conversation`(运行时 **0.2.1-rc.3**)。这条约束写进了
49
- `peerDependencies`,但标记为可选 —— DSH 把这些包内置在自己的安装目录里,普通 npm
50
- 安装不该为它再拉一份副本。
51
-
52
- Node 版本要求 `^22.19.0 || >=24.0.0`,与 DSH 官方下限一致。
37
+ > 在 DSH 会话里,也可以让 agent 用 `plugin_manager action=install_bundle
38
+ > target=dsh-plugin-mcp-toggles` 完成同样的安装。
53
39
 
54
40
  ## 用法
55
41
 
@@ -59,29 +45,39 @@ Node 版本要求 `^22.19.0 || >=24.0.0`,与 DSH 官方下限一致。
59
45
 
60
46
  按钮上的徽标显示当前状态:`全部开启`、`n/m`、`全部关闭`;部分关闭时按钮转为警示色。
61
47
 
62
- ## 两个入口,同一个开关集合
48
+ 开关立即生效,无需重启会话或重开服务器。
63
49
 
64
- | 槽位 | 出现位置 |
65
- |---|---|
66
- | `conversation.session.header.utilities` | 已有会话的标题栏右侧 |
67
- | `conversation.hero.options` | 新会话落地页,紧跟 workspace 与模式控件之后 |
50
+ ## 它解决什么
68
51
 
69
- 两处**必须都有**:新会话页的会话页头带 `hideChrome`
70
- (`blank && composerPhase === 'blank'`,见 `ConversationSession.tsx`),此时整个
71
- header 都不渲染,只挂标题栏会让按钮在新会话页上完全消失。两个槽都提供同一个
72
- `sessionId`,所以在哪一处切换都是同一个会话的开关 —— 在新会话页关掉的服务器,
73
- 进入对话后依然关着。
52
+ MCP 服务器在 DSH profile 里是进程级的:`@deepseek-ai/dsh-mcp-client` 每行连一个
53
+ 服务器,把 `mcp__<serverName>__<tool>` 注册进**全局**工具层。于是每个会话都看到
54
+ 同一批 MCP 工具 —— 想让 A 会话只用 REA、B 会话只用别的服务器,原生做不到。
55
+
56
+ 本插件把这份「全局可见」变成「按会话可见」。**新会话默认全部开启**,所以装上它
57
+ 不会悄悄改变任何人现有的能力;只有你亲手关掉的服务器才会在该会话里消失。
74
58
 
75
59
  ## 它怎么工作
76
60
 
77
- `agent.ctx.tools.restrict({ deny })` 是**作用域内**生效的全局工具掩码
78
- (`packages/core/tools/src/index.ts`)。可见性、schema 投影与派发都走同一个解析器,
79
- 因此被关掉的工具模型既看不到也调不到,其他会话不受影响。
61
+ `agent.ctx.tools.restrict({ deny })` 是**作用域内**生效的全局工具掩码。可见性、
62
+ schema 投影与派发都走同一个解析器,因此被关掉的工具模型既看不到也调不到,其他
63
+ 会话完全不受影响。
80
64
 
81
65
  - 关闭某服务器 → 把它的 `mcp__<server>__*` 工具加进该会话的 deny 集。
82
66
  - 全部服务器都关闭时,共享的 `list_mcp_resources` 等资源工具一并隐藏
83
67
  (`sharedResourceTools: false` 可关掉这个联动)。
84
- - 开关立即生效,无需重启会话或重开服务器。
68
+ - 掩码按 deny 指纹幂等:工具集变化时只重算、不重复叠加。
69
+
70
+ ## 两个入口,同一个开关集合
71
+
72
+ | 槽位 | 出现位置 |
73
+ |---|---|
74
+ | `conversation.session.header.utilities` | 已有会话的标题栏右侧 |
75
+ | `conversation.hero.options` | 新会话落地页,紧跟 workspace 与模式控件之后 |
76
+
77
+ 两处**必须都有**:新会话页的会话页头带 `hideChrome`
78
+ (`blank && composerPhase === 'blank'`),此时整个 header 都不渲染,只挂标题栏会让
79
+ 按钮在新会话页上完全消失。两个槽都提供同一个 `sessionId`,所以在哪一处切换都是
80
+ 同一个会话的开关 —— 在新会话页关掉的服务器,进入对话后依然关着。
85
81
 
86
82
  ## 开关存在哪
87
83
 
@@ -96,16 +92,36 @@ header 都不渲染,只挂标题栏会让按钮在新会话页上完全消失
96
92
 
97
93
  ## 配置
98
94
 
99
- `cordis.patch.yml` 的行配置:
95
+ 在 profile 的 `cordis.patch.yml` 里覆盖 `mcp-toggles` 这一行的 `config`:
100
96
 
101
97
  | 字段 | 默认 | 含义 |
102
98
  |---|---|---|
103
99
  | `sharedResourceTools` | `true` | 关闭全部 MCP 时是否连共享资源工具一起隐藏。 |
104
100
  | `extraServers` | `[]` | 额外要认作 MCP 服务器的名字;用于服务器暂时没有注册任何工具时也能列出来。 |
105
101
 
102
+ ## 兼容性
103
+
104
+ - **DSH**:需要 `@deepseek-ai/dsh-client-ui-conversation` ≥ `0.2.1-rc.3`
105
+ (提供上述两个槽位与 `webServer` 服务)。这条写进了 `peerDependencies` 并标记为
106
+ 可选 —— DSH 把这些包内置在安装目录里,普通 npm 安装不该再拉一份副本。
107
+ - **Node**:`^22.19.0 || >=24.0.0`,与 DSH 官方下限一致。
108
+
106
109
  ## 已知限制
107
110
 
108
111
  - 只管理 **MCP** 工具(`mcp__*` 前缀与共享资源工具),不动其他工具。
109
112
  - 服务器列表来自 `configEditor.entries()` 里的 `@deepseek-ai/dsh-mcp-client` 行;
110
113
  `configEditor` 不可用时退回当前工具名前缀,此时**没有工具**的服务器不会出现在列表里。
111
114
  - 开关按会话 id 保存;会话被删除后对应记录会保留(无害,不会影响其他会话)。
115
+
116
+ ## 开发
117
+
118
+ 没有依赖、没有构建步骤,`lib/` 就是发布内容。改完直接检查:
119
+
120
+ ```bash
121
+ npm run prepublishOnly # node --check 两个入口
122
+ npm pack --dry-run # 查看将要发布的文件
123
+ ```
124
+
125
+ ## License
126
+
127
+ MIT
package/lib/client.js CHANGED
@@ -38,6 +38,7 @@ window.__ModuleLoader__.load({
38
38
  'picker.summary.all': '全部开启',
39
39
  'picker.summary.none': '全部关闭',
40
40
  'picker.summary.pending': '选择 MCP',
41
+ 'picker.summary.empty': '无',
41
42
  'picker.saving': '保存中…',
42
43
  'picker.unsaved': '未写入磁盘',
43
44
  }
@@ -61,6 +62,7 @@ window.__ModuleLoader__.load({
61
62
  'picker.summary.all': 'All on',
62
63
  'picker.summary.none': 'All off',
63
64
  'picker.summary.pending': 'MCP',
65
+ 'picker.summary.empty': 'None',
64
66
  'picker.saving': 'Saving…',
65
67
  'picker.unsaved': 'not written to disk',
66
68
  }
@@ -130,7 +132,7 @@ window.__ModuleLoader__.load({
130
132
  /** 小箭头。 */
131
133
  function Caret() {
132
134
  return React.createElement('svg', {
133
- width: 10, height: 10, viewBox: '0 0 24 24', fill: 'none', 'aria-hidden': true,
135
+ width: 12, height: 12, viewBox: '0 0 24 24', fill: 'none', 'aria-hidden': true,
134
136
  }, React.createElement('path', {
135
137
  d: 'm6 9 6 6 6-6',
136
138
  stroke: 'currentColor', strokeWidth: 2.2, strokeLinecap: 'round', strokeLinejoin: 'round',
@@ -139,90 +141,120 @@ window.__ModuleLoader__.load({
139
141
 
140
142
  // #endregion
141
143
 
142
- // 与同区域的系统按钮(open-in-app / agent-terminal / schedule-catalog)保持
143
- // 同一套尺寸:高 24px、字号 11px、0.5px 边框、--dsw-radius-sm。
144
+ // 样式基准取自 DSH 自身,数值与相邻控件对齐:
145
+ // · 标题栏正文 14px/20px、圆角 12px、hover 用 --dsw-alias-interactive-bg-hover
146
+ // (ui-conversation/ConversationRoot.module.css 的 .crumb)
147
+ // · hero 行控件 28px 高、24px 圆角、13px 字号、透明底
148
+ // (ui-conversation/PermissionSelect.module.css 的 .trigger)
149
+ // · 开关 20×10、thumb 6px、缓动 --ds-ease-in-out
150
+ // (ui-trajectory/TrajectoryToolbar.module.css 的 .controlTrack)
151
+ // · 浮层阴影用 --dsw-shadow-lv3,不写死 rgba
152
+ // 只用主题 token,不引入任何硬编码颜色。
144
153
  const STYLE = `
145
154
  .mt-wrap { position: relative; display: inline-flex; flex: none; align-self: center; }
146
155
  .mt-trigger {
147
- box-sizing: border-box; display: inline-flex; align-items: center; gap: 4px;
148
- height: 24px; padding: 3px 6px; max-width: 190px;
149
- border: .5px solid var(--dsw-alias-border-l4); border-radius: var(--dsw-radius-sm);
150
- background: 0 0; color: var(--dsw-alias-label-primary);
151
- font-family: var(--dsw-font-family); font-size: 11px; line-height: 16px;
156
+ box-sizing: border-box; display: inline-flex; align-items: center; gap: 6px;
157
+ height: 28px; padding: 0 8px; max-width: 220px;
158
+ border: none; border-radius: 24px; outline: none;
159
+ background: transparent; color: var(--dsw-alias-label-secondary);
160
+ font-family: var(--dsw-font-family); font-size: 13px; line-height: 20px; font-weight: 500;
152
161
  white-space: nowrap; cursor: pointer;
153
162
  }
154
- .mt-trigger:hover { background: var(--dsw-alias-interactive-bg-hover); }
155
- .mt-trigger:focus-visible { background: var(--dsw-alias-interactive-bg-hover); outline: none; }
156
- .mt-trigger[data-open="true"] { border-color: var(--dsw-alias-border-l2); background: var(--dsw-alias-interactive-bg-hover); }
157
- .mt-trigger[data-partial="true"] { color: var(--dsw-alias-state-warn-primary); }
158
- .mt-trigger[data-none="true"] { color: var(--dsw-alias-label-secondary); }
159
- /* 新会话落地页那一行比标题栏更矮更紧凑,和相邻的 workspace/preset 控件同高。 */
160
- .mt-trigger[data-compact="true"] { height: 22px; padding: 2px 6px; font-size: 10.5px; }
161
- .mt-link[data-disabled="true"] { opacity: .5; cursor: default; pointer-events: none; }
162
- .mt-icon { flex: none; display: inline-flex; align-items: center; opacity: .9; }
163
+ .mt-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
164
+ .mt-trigger:focus-visible { box-shadow: 0 0 0 2px var(--dsw-alias-border-l3); }
165
+ .mt-trigger[data-open="true"] { background: var(--dsw-alias-interactive-bg-hover); }
166
+ .mt-trigger[data-none="true"] { color: var(--dsw-alias-label-dimmed); }
167
+ /* 部分关闭:用状态色点出「本会话少了东西」,而不是整块文字变色。 */
168
+ .mt-trigger[data-partial="true"] .mt-name { color: var(--dsw-alias-label-primary); }
169
+ .mt-trigger[data-partial="true"] .mt-badge { color: var(--dsw-alias-state-warn-primary); }
170
+ /* hero 行比标题栏紧凑,与相邻的 workspace/模式控件同高。 */
171
+ .mt-trigger[data-compact="true"] { height: 28px; padding: 0 8px; font-size: 13px; }
172
+ .mt-icon { flex: none; display: inline-flex; align-items: center; }
163
173
  .mt-name { overflow: hidden; text-overflow: ellipsis; }
164
- .mt-caret { flex: none; display: inline-flex; align-items: center; color: var(--dsw-alias-label-secondary); transition: transform .12s ease; }
174
+ .mt-caret {
175
+ flex: none; display: inline-flex; align-items: center;
176
+ color: var(--dsw-alias-label-caption);
177
+ transition: transform 120ms var(--ds-ease-in-out);
178
+ }
165
179
  .mt-trigger[data-open="true"] .mt-caret { transform: rotate(180deg); }
180
+ .mt-badge {
181
+ flex: none; padding: 0 6px; border-radius: 6px;
182
+ font-size: 12px; line-height: 18px; font-weight: 500;
183
+ background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-secondary);
184
+ }
185
+ .mt-badge[data-tone="warn"] { color: var(--dsw-alias-state-warn-primary); }
186
+
187
+ /* 浮层:与 DSH 弹层同源 —— overlay 底、lv3 阴影、12px 圆角。 */
166
188
  .mt-menu {
167
- position: absolute; top: calc(100% + 5px); right: 0; z-index: 60;
168
- width: 330px; max-height: 60vh; overflow-y: auto; padding: 4px;
169
- border: .5px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-md);
170
- background: var(--dsw-alias-bg-overlay); box-shadow: 0 8px 24px rgba(0,0,0,.16);
189
+ position: absolute; top: calc(100% + 6px); right: 0; z-index: 60;
190
+ width: 340px; max-height: 60vh; overflow-y: auto; padding: 6px;
191
+ border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px;
192
+ background: var(--dsw-alias-bg-overlay); box-shadow: var(--dsw-shadow-lv3);
171
193
  }
172
194
  .mt-head {
173
195
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
174
- padding: 5px 8px 6px; font-size: 11px; line-height: 16px;
196
+ padding: 6px 8px 8px;
197
+ font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary);
198
+ }
199
+ .mt-headActions { display: inline-flex; align-items: center; gap: 10px; flex: none; }
200
+ .mt-link {
201
+ cursor: pointer; border-radius: 6px; padding: 0 2px;
175
202
  color: var(--dsw-alias-label-secondary);
203
+ transition: color 120ms var(--ds-ease-in-out);
176
204
  }
177
- .mt-headActions { display: inline-flex; align-items: center; gap: 8px; flex: none; }
178
- .mt-link { cursor: pointer; color: var(--dsw-alias-label-secondary); }
179
205
  .mt-link:hover { color: var(--dsw-alias-brand-primary); }
206
+ .mt-link:focus-visible { outline: 2px solid var(--dsw-alias-border-l3); outline-offset: 1px; }
207
+ .mt-link[data-disabled="true"] { opacity: .5; cursor: default; pointer-events: none; }
180
208
  .mt-item {
181
- display: flex; align-items: center; gap: 8px; padding: 6px 8px;
182
- border-radius: var(--dsw-radius-sm); cursor: pointer;
209
+ display: flex; align-items: center; gap: 10px; padding: 8px;
210
+ border-radius: 8px; cursor: default;
183
211
  }
184
212
  .mt-item:hover { background: var(--dsw-alias-interactive-bg-hover); }
185
- .mt-item[data-busy="true"] { opacity: .6; cursor: default; }
213
+ .mt-item[data-busy="true"] { opacity: .6; }
186
214
  .mt-body { flex: 1; min-width: 0; }
187
- .mt-title { display: flex; align-items: center; gap: 5px; font-size: 11px; line-height: 16px; color: var(--dsw-alias-label-primary); }
215
+ .mt-title {
216
+ display: flex; align-items: center; gap: 6px;
217
+ font-size: 13px; line-height: 20px; font-weight: 500;
218
+ color: var(--dsw-alias-label-primary);
219
+ }
188
220
  .mt-sub {
189
- margin-top: 1px; font-size: 10px; line-height: 14px;
221
+ margin-top: 2px; font-size: 12px; line-height: 18px;
190
222
  color: var(--dsw-alias-label-secondary);
191
223
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
192
224
  }
193
- .mt-badge {
194
- flex: none; padding: 0 4px; border-radius: var(--dsw-radius-xs);
195
- font-size: 9.5px; line-height: 14px;
196
- background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-secondary);
197
- }
198
- .mt-badge[data-tone="warn"] { color: var(--dsw-alias-state-warn-primary); }
199
225
  .mt-switch {
200
226
  flex: none; position: relative; box-sizing: border-box;
201
- width: 26px; height: 15px; padding: 0; border-radius: 999px;
202
- border: .5px solid var(--dsw-alias-border-l2);
203
- background: var(--dsw-alias-bg-layer-2); cursor: pointer; transition: background .12s ease;
227
+ width: 32px; height: 18px; padding: 0;
228
+ border: none; border-radius: 9px;
229
+ background: var(--dsw-alias-border-l2); cursor: pointer;
230
+ transition: background-color 120ms var(--ds-ease-in-out);
204
231
  }
205
232
  .mt-switch::after {
206
- content: ""; position: absolute; top: 1.5px; left: 2px;
207
- width: 10px; height: 10px; border-radius: 50%;
208
- background: var(--dsw-alias-label-secondary); transition: transform .12s ease, background .12s ease;
233
+ content: ""; position: absolute; top: 3px; left: 3px;
234
+ width: 12px; height: 12px; border-radius: 50%;
235
+ background: var(--dsw-alias-bg-layer-1);
236
+ transition: transform 120ms var(--ds-ease-in-out);
209
237
  }
210
- .mt-switch[aria-checked="true"] { background: var(--dsw-alias-brand-primary); border-color: var(--dsw-alias-brand-primary); }
211
- .mt-switch[aria-checked="true"]::after { transform: translateX(11px); background: var(--dsw-alias-bg-base); }
212
- .mt-switch:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }
238
+ .mt-switch[aria-checked="true"] { background: var(--dsw-alias-brand-primary); }
239
+ .mt-switch[aria-checked="true"]::after { transform: translateX(14px); }
240
+ .mt-switch:focus-visible { outline: 2px solid var(--dsw-alias-border-l3); outline-offset: 2px; }
213
241
  .mt-switch:disabled { opacity: .5; cursor: default; }
214
- .mt-sep { height: .5px; margin: 4px 2px; background: var(--dsw-alias-border-l1); }
215
242
  .mt-foot {
216
- display: flex; align-items: center; gap: 6px; padding: 5px 8px;
217
- font-size: 10.5px; line-height: 15px; color: var(--dsw-alias-label-secondary);
243
+ display: flex; align-items: center; gap: 6px; padding: 6px 8px;
244
+ font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary);
245
+ }
246
+ .mt-err {
247
+ padding: 4px 8px 6px; font-size: 12px; line-height: 18px;
248
+ color: var(--dsw-alias-state-error-primary);
218
249
  }
219
- .mt-err { padding: 3px 8px 6px; font-size: 10.5px; line-height: 15px; color: var(--dsw-alias-state-error-primary); }
220
250
  .mt-btn {
221
- padding: 3px 8px; border: .5px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-sm);
222
- background: 0 0; color: var(--dsw-alias-label-primary);
223
- font-family: var(--dsw-font-family); font-size: 11px; line-height: 16px; cursor: pointer;
251
+ padding: 4px 10px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px;
252
+ background: transparent; color: var(--dsw-alias-label-primary);
253
+ font-family: var(--dsw-font-family); font-size: 12px; line-height: 18px;
254
+ cursor: pointer; transition: background-color 120ms var(--ds-ease-in-out);
224
255
  }
225
- .mt-btn:hover { background: var(--dsw-alias-interactive-bg-hover); }
256
+ .mt-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
257
+ .mt-btn:focus-visible { outline: 2px solid var(--dsw-alias-border-l3); outline-offset: 1px; }
226
258
  .mt-btn:disabled { opacity: .5; cursor: default; }
227
259
  `
228
260
 
@@ -301,12 +333,14 @@ window.__ModuleLoader__.load({
301
333
  const servers = data?.servers ?? []
302
334
  const enabledCount = data?.enabledCount ?? 0
303
335
 
336
+ // 徽标只放短状态:长句(无服务器/读取中)会把 220px 的触发器撑破,
337
+ // 完整说明留在弹层里。
304
338
  const label = !scoped
305
339
  ? t('picker.summary.pending')
306
340
  : data === undefined
307
- ? t('picker.loading')
341
+ ? '…'
308
342
  : servers.length === 0
309
- ? t('picker.empty')
343
+ ? t('picker.summary.empty')
310
344
  : enabledCount === servers.length
311
345
  ? t('picker.summary.all')
312
346
  : enabledCount === 0
@@ -378,7 +412,7 @@ window.__ModuleLoader__.load({
378
412
  'aria-expanded': open,
379
413
  onClick: () => { setOpen((value) => !value) },
380
414
  },
381
- React.createElement('span', { className: 'mt-icon' }, React.createElement(PlugGlyph, { size: 13 })),
415
+ React.createElement('span', { className: 'mt-icon' }, React.createElement(PlugGlyph, { size: 14 })),
382
416
  React.createElement('span', { className: 'mt-name' }, 'MCP'),
383
417
  React.createElement('span', {
384
418
  className: 'mt-badge',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-mcp-toggles",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Turn installed MCP servers on or off per conversation, from a picker in the session header.",
6
6
  "keywords": [