dsh-mcp 1.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.
@@ -0,0 +1,152 @@
1
+ /** Copy dictionaries for the MCP server management Settings section. */
2
+
3
+ /** Simplified Chinese dictionary and key source of truth. */
4
+ export const zh = {
5
+ nav: 'MCP',
6
+ loading: '正在读取 MCP 服务器…',
7
+ loadError: '暂时无法读取 MCP 服务器。',
8
+ retry: '重试',
9
+ empty: '还没有配置 MCP 服务器。',
10
+ addServer: '添加服务器',
11
+ serverName: '服务器名称(serverName)',
12
+ serverNameHint: '工具将以 mcp__<serverName>__<tool> 命名;1-32 位字母、数字、下划线或连字符',
13
+ transport: '传输方式',
14
+ transportStdio: 'stdio(本地子进程)',
15
+ transportHttp: 'streamable-http(远程服务)',
16
+ enabled: '启用',
17
+ disable: '禁用',
18
+ command: '命令',
19
+ commandPlaceholder: '例如 npx',
20
+ args: '参数',
21
+ argsPlaceholder: '每行一个参数',
22
+ cwd: '工作目录',
23
+ cwdPlaceholder: '留空继承 Host 工作目录',
24
+ url: '服务器 URL',
25
+ urlPlaceholder: '例如 http://localhost:3000/mcp',
26
+ headers: '请求头',
27
+ headersPlaceholder: '每行一个 "名称: 值"',
28
+ envVars: '环境变量',
29
+ envHint: '注入到 stdio 子进程;标记为 secret 的值写入凭据文档,不在此明文保存',
30
+ envName: '变量名',
31
+ envValue: '值',
32
+ envSecret: 'secret',
33
+ envSecretHint: '值只写,保存后不可见;留空表示保留原值',
34
+ addEnvVar: '添加环境变量',
35
+ removeEnvVar: '移除该变量',
36
+ toolCallTimeoutMs: '工具调用超时(毫秒)',
37
+ failOnStartupError: '启动连接失败时拒绝挂载',
38
+ statusMounting: '连接中',
39
+ statusLive: '已连接',
40
+ statusFailed: '挂载失败',
41
+ statusStopped: '未启用',
42
+ toolCount: '工具',
43
+ edit: '编辑',
44
+ test: '测试连接',
45
+ testRunning: '正在测试…',
46
+ testOk: '连接成功',
47
+ testFail: '连接失败',
48
+ testUnknown: '未知结果',
49
+ save: '保存',
50
+ saving: '保存中…',
51
+ cancel: '取消',
52
+ remove: '删除',
53
+ removeConfirm: '确定删除该服务器?',
54
+ removing: '删除中…',
55
+ failureTitle: '操作失败',
56
+ serverNameConflict: 'serverName 已被其他服务器使用',
57
+ notFound: '服务器不存在',
58
+ invalidSpec: '配置不合法',
59
+ mountFailed: '挂载失败',
60
+ backToList: '返回列表',
61
+ editTitle: '编辑服务器',
62
+ newTitle: '新建服务器',
63
+ toolsTitle: '工具控制',
64
+ toolsModeLabel: '注入模式',
65
+ toolsModeFull: '全量注入(每轮注入所有启用的 MCP 工具)',
66
+ toolsModeSearch: '按需检索(仅注入常用/检索到的工具,省 token)',
67
+ toolsHintSearch: '按需模式:模型需要某 MCP 工具时会调用 mcp_tool_search 检索并自动注入当前对话。',
68
+ toolsHintFull: '全量模式:下方开关控制每个 MCP 工具是否注入模型请求。',
69
+ toolsLoading: '正在读取工具列表…',
70
+ toolsEmpty: '(暂无 MCP 工具)',
71
+ refresh: '刷新',
72
+ refreshing: '刷新中…',
73
+ toolsExpand: '展开工具',
74
+ toolsCollapse: '收起工具',
75
+ } satisfies Record<string, string>
76
+
77
+ /** MCP settings locale key union. */
78
+ export type McpSettingsLocaleKey = keyof typeof zh
79
+
80
+ /** English dictionary checked against the Chinese key set. */
81
+ export const en = {
82
+ nav: 'MCP',
83
+ loading: 'Reading MCP servers…',
84
+ loadError: 'MCP servers are temporarily unavailable.',
85
+ retry: 'Retry',
86
+ empty: 'No MCP servers are configured yet.',
87
+ addServer: 'Add server',
88
+ serverName: 'Server name (serverName)',
89
+ serverNameHint: 'Tools appear as mcp__<serverName>__<tool>; 1-32 letters, digits, underscores, or hyphens',
90
+ transport: 'Transport',
91
+ transportStdio: 'stdio (local child process)',
92
+ transportHttp: 'streamable-http (remote service)',
93
+ enabled: 'Enabled',
94
+ disable: 'Disable',
95
+ command: 'Command',
96
+ commandPlaceholder: 'e.g. npx',
97
+ args: 'Arguments',
98
+ argsPlaceholder: 'One argument per line',
99
+ cwd: 'Working directory',
100
+ cwdPlaceholder: 'Leave empty to inherit the Host working directory',
101
+ url: 'Server URL',
102
+ urlPlaceholder: 'e.g. http://localhost:3000/mcp',
103
+ headers: 'Headers',
104
+ headersPlaceholder: 'One "Name: value" per line',
105
+ envVars: 'Environment variables',
106
+ envHint: 'Injected into the stdio child; secret values are stored in the credentials document, never shown here',
107
+ envName: 'Name',
108
+ envValue: 'Value',
109
+ envSecret: 'secret',
110
+ envSecretHint: 'Write-only; a blank value keeps the stored one',
111
+ addEnvVar: 'Add environment variable',
112
+ removeEnvVar: 'Remove this variable',
113
+ toolCallTimeoutMs: 'Tool-call timeout (ms)',
114
+ failOnStartupError: 'Reject the mount when the startup connection fails',
115
+ statusMounting: 'Connecting',
116
+ statusLive: 'Connected',
117
+ statusFailed: 'Mount failed',
118
+ statusStopped: 'Disabled',
119
+ toolCount: 'tools',
120
+ edit: 'Edit',
121
+ test: 'Test connection',
122
+ testRunning: 'Testing…',
123
+ testOk: 'Connection succeeded',
124
+ testFail: 'Connection failed',
125
+ testUnknown: 'Unknown result',
126
+ save: 'Save',
127
+ saving: 'Saving…',
128
+ cancel: 'Cancel',
129
+ remove: 'Delete',
130
+ removeConfirm: 'Delete this server?',
131
+ removing: 'Deleting…',
132
+ failureTitle: 'Operation failed',
133
+ serverNameConflict: 'serverName is already used by another server',
134
+ notFound: 'Server not found',
135
+ invalidSpec: 'Invalid configuration',
136
+ mountFailed: 'Mount failed',
137
+ backToList: 'Back to list',
138
+ editTitle: 'Edit server',
139
+ newTitle: 'New server',
140
+ toolsTitle: 'Tool control',
141
+ toolsModeLabel: 'Injection mode',
142
+ toolsModeFull: 'Full injection (every enabled MCP tool each request)',
143
+ toolsModeSearch: 'On-demand search (inject only resident/hot tools, saves tokens)',
144
+ toolsHintSearch: 'On-demand mode: the model calls mcp_tool_search to discover and hot-inject MCP tools.',
145
+ toolsHintFull: 'Full mode: the switches below control which MCP tools are injected each request.',
146
+ toolsLoading: 'Reading tool list…',
147
+ toolsEmpty: '(no MCP tools)',
148
+ refresh: 'Refresh',
149
+ refreshing: 'Refreshing…',
150
+ toolsExpand: 'Expand tools',
151
+ toolsCollapse: 'Collapse tools',
152
+ } satisfies Record<McpSettingsLocaleKey, string>
@@ -0,0 +1,249 @@
1
+ /**
2
+ * MCP settings page store: the server list projection, the editing draft, and
3
+ * the transient probe outcome. All Remote traffic happens in the apply-world
4
+ * inject callbacks; the store only mirrors their results so components stay
5
+ * pure presentational.
6
+ */
7
+ import { defineStore, type EngineStoreHandle } from '@deepseek-ai/dsh-client-runtime/client'
8
+ import type {
9
+ McpEnvVarInput, McpProbeView, McpServerId, McpServerInput, McpServerView, McpTransportKind,
10
+ } from './types.ts'
11
+
12
+ /** One env row in the editor. */
13
+ export interface McpEnvRowDraft {
14
+ /** Stable row key for React lists. */
15
+ readonly key: string
16
+ readonly name: string
17
+ readonly secret: boolean
18
+ /** Editor text; a blank secret value keeps the stored one on save. */
19
+ readonly value: string
20
+ /** Whether a stored value exists (secret rows render write-only). */
21
+ readonly configured: boolean
22
+ }
23
+
24
+ /** One server being edited. */
25
+ export interface McpDraft {
26
+ /** Existing server id; null creates a new server. */
27
+ readonly id: McpServerId | null
28
+ readonly serverName: string
29
+ readonly transport: McpTransportKind
30
+ readonly enabled: boolean
31
+ readonly command: string
32
+ /** Arguments as newline-separated editor text. */
33
+ readonly argsText: string
34
+ readonly cwd: string
35
+ readonly url: string
36
+ /** Headers as "Name: value" lines. */
37
+ readonly headersText: string
38
+ readonly env: readonly McpEnvRowDraft[]
39
+ /** Timeout as editor text; blank falls back to the default. */
40
+ readonly toolCallTimeoutMs: string
41
+ readonly failOnStartupError: boolean
42
+ }
43
+
44
+ /** The transient probe outcome of the current draft. */
45
+ export interface McpTestOutcome {
46
+ readonly probe: McpProbeView
47
+ readonly elapsedMs: number
48
+ }
49
+
50
+ /** Page-level state mirroring Remote results and the current editing session. */
51
+ export interface McpManagerUiState {
52
+ loadState: 'loading' | 'ready' | 'error'
53
+ servers: readonly McpServerView[]
54
+ /** Draft being edited; null renders the server list. */
55
+ draft: McpDraft | null
56
+ /** Verb of the current in-flight Remote operation. */
57
+ busy: 'save' | 'remove' | null
58
+ testRunning: boolean
59
+ test: McpTestOutcome | null
60
+ }
61
+
62
+ /** Declared mutation surface for the page. */
63
+ type McpManagerActions = {
64
+ setLoadState: (draft: McpManagerUiState, state: McpManagerUiState['loadState']) => void
65
+ setServers: (draft: McpManagerUiState, servers: readonly McpServerView[]) => void
66
+ beginCreate: (draft: McpManagerUiState) => void
67
+ beginEdit: (draft: McpManagerUiState, server: McpServerView) => void
68
+ cancelEdit: (draft: McpManagerUiState) => void
69
+ updateDraft: (draft: McpManagerUiState, patch: Partial<McpDraft>) => void
70
+ setBusy: (draft: McpManagerUiState, busy: 'save' | 'remove' | null) => void
71
+ setTestRunning: (draft: McpManagerUiState, running: boolean) => void
72
+ setTest: (draft: McpManagerUiState, test: McpTestOutcome | null) => void
73
+ }
74
+
75
+ /** Default tool-call timeout when the editor leaves it blank (ms). */
76
+ export const DEFAULT_TOOL_CALL_TIMEOUT_MS = 60_000
77
+
78
+ let envRowSeq = 0
79
+
80
+ /** A fresh env row key for React identity. */
81
+ function nextEnvRowKey(): string {
82
+ envRowSeq += 1
83
+ return `env-${envRowSeq}`
84
+ }
85
+
86
+ /** A fresh empty env row for the editor. */
87
+ export function createEnvRowDraft(): McpEnvRowDraft {
88
+ return { key: nextEnvRowKey(), name: '', secret: false, value: '', configured: false }
89
+ }
90
+
91
+ /** Build an empty create draft. */
92
+ export function emptyDraft(): McpDraft {
93
+ return {
94
+ id: null,
95
+ serverName: '',
96
+ transport: 'stdio',
97
+ enabled: true,
98
+ command: '',
99
+ argsText: '',
100
+ cwd: '',
101
+ url: '',
102
+ headersText: '',
103
+ env: [],
104
+ toolCallTimeoutMs: String(DEFAULT_TOOL_CALL_TIMEOUT_MS),
105
+ failOnStartupError: false,
106
+ }
107
+ }
108
+
109
+ /** Build an edit draft from a stored server view. */
110
+ export function draftFromServer(server: McpServerView): McpDraft {
111
+ return {
112
+ id: server.id,
113
+ serverName: server.serverName,
114
+ transport: server.transport,
115
+ enabled: server.enabled,
116
+ command: server.command,
117
+ argsText: server.args.join('\n'),
118
+ cwd: server.cwd,
119
+ url: server.url,
120
+ headersText: server.headers.map(header => `${header.name}: ${header.value}`).join('\n'),
121
+ env: server.env.map(entry => ({
122
+ key: nextEnvRowKey(),
123
+ name: entry.name,
124
+ secret: entry.secret,
125
+ value: '',
126
+ configured: entry.configured,
127
+ })),
128
+ toolCallTimeoutMs: String(server.toolCallTimeoutMs),
129
+ failOnStartupError: server.failOnStartupError,
130
+ }
131
+ }
132
+
133
+ /** Parse newline-separated lines into trimmed non-empty arguments. */
134
+ function parseLines(text: string): string[] {
135
+ return text.split('\n').map(line => line.trim()).filter(line => line.length > 0)
136
+ }
137
+
138
+ /** Parse "Name: value" header lines; malformed lines are dropped. */
139
+ function parseHeaders(text: string): Array<{ name: string; value: string }> {
140
+ const headers: Array<{ name: string; value: string }> = []
141
+ for (const line of parseLines(text)) {
142
+ const separator = line.indexOf(':')
143
+ if (separator <= 0) continue
144
+ const name = line.slice(0, separator).trim()
145
+ const value = line.slice(separator + 1).trim()
146
+ if (name.length > 0) headers.push({ name, value })
147
+ }
148
+ return headers
149
+ }
150
+
151
+ /** Parse the timeout text; blank or invalid falls back to the default. */
152
+ function parseTimeout(text: string): number {
153
+ const value = Number(text)
154
+ return Number.isInteger(value) && value > 0 ? value : DEFAULT_TOOL_CALL_TIMEOUT_MS
155
+ }
156
+
157
+ /** The Remote submission derived from the current draft. */
158
+ export interface McpSubmission {
159
+ readonly server: McpServerInput
160
+ readonly env: readonly McpEnvVarInput[]
161
+ }
162
+
163
+ /**
164
+ * Convert the editing draft into the Remote upsert/test payload. A blank
165
+ * secret value submits no value, which keeps the stored secret on upsert.
166
+ */
167
+ export function draftToSubmission(draft: McpDraft): McpSubmission {
168
+ return {
169
+ server: {
170
+ serverName: draft.serverName.trim(),
171
+ transport: draft.transport,
172
+ enabled: draft.enabled,
173
+ command: draft.command.trim(),
174
+ args: parseLines(draft.argsText),
175
+ cwd: draft.cwd.trim(),
176
+ url: draft.url.trim(),
177
+ headers: parseHeaders(draft.headersText),
178
+ toolCallTimeoutMs: parseTimeout(draft.toolCallTimeoutMs),
179
+ failOnStartupError: draft.failOnStartupError,
180
+ },
181
+ env: draft.env.map(row => ({
182
+ name: row.name.trim(),
183
+ secret: row.secret,
184
+ ...row.secret && row.value.trim().length === 0 ? {} : { value: row.value },
185
+ })),
186
+ }
187
+ }
188
+
189
+ /** Map a Remote failure code to a locale key for user-facing copy. */
190
+ export function failureLocaleKey(code: string): string {
191
+ switch (code) {
192
+ case 'MCP_SERVER_NAME_CONFLICT': return 'serverNameConflict'
193
+ case 'MCP_SERVER_NOT_FOUND': return 'notFound'
194
+ case 'MCP_INVALID_SPEC': return 'invalidSpec'
195
+ case 'MCP_MOUNT_FAILED': return 'mountFailed'
196
+ default: return 'failureTitle'
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Declares the MCP management page state and write surface.
202
+ * @returns the store handle.
203
+ */
204
+ export function createMcpManagerStore(): EngineStoreHandle<McpManagerUiState, McpManagerActions> {
205
+ return defineStore({
206
+ init: (): McpManagerUiState => ({
207
+ loadState: 'loading',
208
+ servers: [],
209
+ draft: null,
210
+ busy: null,
211
+ testRunning: false,
212
+ test: null,
213
+ }),
214
+ actions: {
215
+ setLoadState: (d, state) => {
216
+ d.loadState = state
217
+ },
218
+ setServers: (d, servers) => {
219
+ d.servers = servers
220
+ },
221
+ beginCreate: (d) => {
222
+ d.draft = emptyDraft()
223
+ d.test = null
224
+ },
225
+ beginEdit: (d, server) => {
226
+ d.draft = draftFromServer(server)
227
+ d.test = null
228
+ },
229
+ cancelEdit: (d) => {
230
+ d.draft = null
231
+ d.test = null
232
+ d.busy = null
233
+ },
234
+ updateDraft: (d, patch) => {
235
+ if (d.draft === null) return
236
+ d.draft = { ...d.draft, ...patch }
237
+ },
238
+ setBusy: (d, busy) => {
239
+ d.busy = busy
240
+ },
241
+ setTestRunning: (d, running) => {
242
+ d.testRunning = running
243
+ },
244
+ setTest: (d, test) => {
245
+ d.test = test
246
+ },
247
+ },
248
+ })
249
+ }