coding-tool-x 3.9.3-261008 → 3.9.5-261009

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-tool-x",
3
- "version": "3.9.3-261008",
3
+ "version": "3.9.5-261009",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "source": "models.dev",
3
3
  "sourceUrl": "https://models.dev/api.json",
4
- "lastUpdated": "2026-10-08",
4
+ "lastUpdated": "2026-10-09",
5
5
  "defaultModels": {
6
6
  "claude": [
7
7
  "claude-opus-4-7",
@@ -3450,7 +3450,7 @@
3450
3450
  "image"
3451
3451
  ],
3452
3452
  "limit": {
3453
- "context": 524288,
3453
+ "context": 1048576,
3454
3454
  "output": 262144
3455
3455
  },
3456
3456
  "name": "Mistral Large 4",
@@ -9,6 +9,7 @@ const { installPlugin, uninstallPlugin, updatePlugin } = require('./plugin-manag
9
9
  const { createDriver: createChannelsDriver } = require('./channels');
10
10
  const { createDriver: createProxyDriver } = require('./proxy');
11
11
  const { createDriver: createStatisticsDriver } = require('./statistics');
12
+ const { importMcpServer } = require('../../../shared/mcp-import');
12
13
 
13
14
  function requiredId(value, label) {
14
15
  const id = String(value || '').trim();
@@ -153,18 +154,34 @@ function dshMcpSpecToConfig(spec = {}, serverName) {
153
154
  return config;
154
155
  }
155
156
 
156
- function readDshMcpEntries(context = {}) {
157
+ function dshMcpServerName(server, fallbackId) {
158
+ const requested = String(server?.name || '').trim();
159
+ if (/^[A-Za-z0-9_-]{1,32}$/.test(requested)) return requested;
160
+ return String(fallbackId || '')
161
+ .replace(/[^A-Za-z0-9_-]/g, '_')
162
+ .slice(0, 32) || 'mcp';
163
+ }
164
+
165
+ function readDshMcpRecords(context = {}) {
157
166
  const profile = resolveProfileName(context);
158
- if (!profile) return {};
167
+ if (!profile) return [];
159
168
  const result = listProfileMcp(context, profile);
160
- const entries = {};
169
+ const entries = [];
161
170
  for (const server of result?.servers || []) {
162
171
  if (!server?.id || server.disabled) continue;
163
- entries[server.id] = dshMcpConfigToSpec(server.config || {});
172
+ entries.push({
173
+ id: server.id,
174
+ name: server.config?.serverName || server.id,
175
+ spec: dshMcpConfigToSpec(server.config || {})
176
+ });
164
177
  }
165
178
  return entries;
166
179
  }
167
180
 
181
+ function readDshMcpEntries(context = {}) {
182
+ return Object.fromEntries(readDshMcpRecords(context).map(({ id, spec }) => [id, spec]));
183
+ }
184
+
168
185
  function promptContentFromDshConfig(config = {}) {
169
186
  for (const key of ['content', 'prompt', 'text']) {
170
187
  if (typeof config[key] === 'string') return config[key];
@@ -359,47 +376,33 @@ function createDriver(context = {}) {
359
376
  driver.read = () => resultFor(mcpContext('read'), 'ok', { mcpServers: readDshMcpEntries(context) });
360
377
  driver.normalize = spec => resultFor(mcpContext('normalize'), 'ok', spec);
361
378
  driver.sync = async server => {
362
- const profileName = profileNameForRequest(context, server);
363
- const id = String(server?.id || '').trim();
364
- if (!id) throw new Error('MCP server id is required');
365
- const patch = requirePatchResult(
366
- upsertProfilePatchRow(context, profileName, {
367
- id,
368
- name: '@deepseek-ai/dsh-mcp-client',
369
- config: dshMcpSpecToConfig(server.server || {}, id)
370
- }),
371
- profileName
372
- );
373
- return resultFor(mcpContext('sync'), 'ok', patch);
374
- };
375
- driver.remove = async serverId => {
376
- const profileName = profileNameForRequest(context);
377
- const id = String(serverId || '').trim();
378
- if (!id) throw new Error('MCP server id is required');
379
- if (!profileName) return resultFor(mcpContext('remove'), 'ok', true);
380
- const patch = requirePatchResult(deleteProfilePatchRow(context, profileName, id), profileName);
381
- return resultFor(mcpContext('remove'), 'ok', patch);
382
- };
379
+ const profileName = profileNameForRequest(context, server);
380
+ const id = String(server?.id || '').trim();
381
+ if (!id) throw new Error('MCP server id is required');
382
+ const patch = requirePatchResult(
383
+ upsertProfilePatchRow(context, profileName, {
384
+ id,
385
+ name: '@deepseek-ai/dsh-mcp-client',
386
+ config: dshMcpSpecToConfig(server.server || {}, dshMcpServerName(server, id))
387
+ }),
388
+ profileName
389
+ );
390
+ return resultFor(mcpContext('sync'), 'ok', patch);
391
+ };
392
+ driver.remove = async serverId => {
393
+ const profileName = profileNameForRequest(context);
394
+ const id = String(serverId || '').trim();
395
+ if (!id) throw new Error('MCP server id is required');
396
+ if (!profileName) return resultFor(mcpContext('remove'), 'ok', true);
397
+ const patch = requirePatchResult(deleteProfilePatchRow(context, profileName, id), profileName);
398
+ return resultFor(mcpContext('remove'), 'ok', patch);
399
+ };
383
400
  driver.import = async servers => {
384
- const entries = readDshMcpEntries(context);
385
- let count = 0;
386
- for (const [id, spec] of Object.entries(entries)) {
387
- if (servers[id]) {
388
- servers[id].apps = { ...(servers[id].apps || {}), dsh: true };
389
- continue;
401
+ let count = 0;
402
+ for (const entry of readDshMcpRecords(context)) {
403
+ if (importMcpServer(servers, { ...entry, platform: 'dsh' })) count++;
390
404
  }
391
- const now = Date.now();
392
- servers[id] = {
393
- id,
394
- name: id,
395
- server: spec,
396
- apps: { dsh: true },
397
- createdAt: now,
398
- updatedAt: now
399
- };
400
- count++;
401
- }
402
- return resultFor(mcpContext('import'), 'ok', count);
405
+ return resultFor(mcpContext('import'), 'ok', count);
403
406
  };
404
407
  }
405
408
 
@@ -326,8 +326,8 @@ router.post('/import/:platform', async (req, res) => {
326
326
  success: true,
327
327
  imported: count,
328
328
  message: count > 0
329
- ? `成功从 ${resolved.key} 导入 ${count} 个 MCP 服务器`
330
- : `${resolved.key} 没有可导入的 MCP 服务器`
329
+ ? `已从 ${resolved.key} 导入或更新 ${count} 个 MCP 服务器`
330
+ : `${resolved.key} 的 MCP 配置没有新增或变更`
331
331
  });
332
332
  } catch (error) {
333
333
  console.error('[MCP API] Import failed:', sanitizeErrorMessage(error.message || error));
@@ -9,6 +9,7 @@ const path = require('path');
9
9
  const { spawn } = require('child_process');
10
10
  const { McpClient, buildMissingCommandMessage, createMissingCommandHint } = require('./mcp-client');
11
11
  const mcpFormat = require('../../shared/mcp-format');
12
+ const { importMcpServer } = require('../../shared/mcp-import');
12
13
  const { convertToOmpMcpFormat, convertFromOmpMcpFormat } = mcpFormat;
13
14
  const { PATHS } = require('../../config/paths');
14
15
  const { ControlManifestStore } = require('./control-manifest-store');
@@ -1072,25 +1073,7 @@ async function importGenericMcpServers(driver, platform, servers) {
1072
1073
 
1073
1074
  for (const [id, spec] of Object.entries(mcpServers)) {
1074
1075
  if (!spec || typeof spec !== 'object' || Array.isArray(spec)) continue;
1075
- if (servers[id]) {
1076
- servers[id].apps = normalizeServerApps(servers[id].apps, {});
1077
- if (!servers[id].apps[platform]) {
1078
- servers[id].apps[platform] = true;
1079
- count++;
1080
- }
1081
- continue;
1082
- }
1083
-
1084
- const now = Date.now();
1085
- servers[id] = {
1086
- id,
1087
- name: id,
1088
- server: normalizeServerSpec(spec),
1089
- apps: { [platform]: true },
1090
- createdAt: now,
1091
- updatedAt: now
1092
- };
1093
- count++;
1076
+ if (importMcpServer(servers, { id, platform, spec: normalizeServerSpec(spec) })) count++;
1094
1077
  }
1095
1078
 
1096
1079
  return count;
@@ -8,6 +8,7 @@ const tomlStringify = require('@iarna/toml').stringify;
8
8
  const { ok, failed } = require('../driver-result');
9
9
  const { extractServerSpec, convertToCodexFormat, convertFromCodexFormat, convertToOmpMcpFormat, convertFromOmpMcpFormat, convertToOpenCodeFormat, convertFromOpenCodeFormat } = require('../mcp-format');
10
10
  const { redactSecrets, validateMcpId } = require('../project-config');
11
+ const { importMcpServer } = require('../mcp-import');
11
12
 
12
13
  const SCHEMA_URL = 'https://raw.githubusercontent.com/can1357/oh-my-omp/main/packages/coding-agent/src/config/mcp-schema.json';
13
14
 
@@ -192,14 +193,7 @@ function createMcpDriver({ platform, ...context } = {}) {
192
193
  let count = 0;
193
194
  for (const [id, spec] of Object.entries(map)) {
194
195
  if (!spec || typeof spec !== 'object' || Array.isArray(spec)) continue;
195
- if (servers[id]) {
196
- servers[id].apps = { ...(servers[id].apps || {}), [platform]: true };
197
- count += 1;
198
- continue;
199
- }
200
- const now = Date.now();
201
- servers[id] = { id, name: id, server: internalSpec(spec), apps: { [platform]: true }, createdAt: now, updatedAt: now };
202
- count += 1;
196
+ if (importMcpServer(servers, { id, platform, spec: internalSpec(spec) })) count += 1;
203
197
  }
204
198
  return count;
205
199
  }
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+
3
+ const { isDeepStrictEqual } = require('util');
4
+ const { validateMcpId } = require('./project-config');
5
+
6
+ const REDACTED_VALUES = new Set(['[REDACTED]', '[redacted]', '[EXPRESSION]']);
7
+
8
+ function normalizeImportedSpec(spec) {
9
+ if (!spec || typeof spec !== 'object' || Array.isArray(spec)) return spec;
10
+ return { ...spec, type: spec.type || 'stdio' };
11
+ }
12
+
13
+ function restoreRedactedUrl(incoming, existing) {
14
+ if (typeof incoming !== 'string' || typeof existing !== 'string') return incoming;
15
+ try {
16
+ const nextUrl = new URL(incoming);
17
+ const previousUrl = new URL(existing);
18
+ const isRedactedPart = value => {
19
+ try {
20
+ return REDACTED_VALUES.has(decodeURIComponent(value));
21
+ } catch (_) {
22
+ return false;
23
+ }
24
+ };
25
+ const decodePart = value => {
26
+ try {
27
+ return decodeURIComponent(value);
28
+ } catch (_) {
29
+ return value;
30
+ }
31
+ };
32
+ if (isRedactedPart(nextUrl.username) && previousUrl.username) {
33
+ nextUrl.username = decodePart(previousUrl.username);
34
+ }
35
+ if (isRedactedPart(nextUrl.password) && previousUrl.password) {
36
+ nextUrl.password = decodePart(previousUrl.password);
37
+ }
38
+ for (const [key, value] of nextUrl.searchParams.entries()) {
39
+ if (REDACTED_VALUES.has(value) && previousUrl.searchParams.has(key)) {
40
+ nextUrl.searchParams.set(key, previousUrl.searchParams.get(key));
41
+ }
42
+ }
43
+ return nextUrl.toString();
44
+ } catch (_) {
45
+ return incoming;
46
+ }
47
+ }
48
+
49
+ function restoreRedactedValues(incoming, existing, key = '') {
50
+ if (REDACTED_VALUES.has(incoming)) return existing === undefined ? incoming : existing;
51
+ if (key === 'url') return restoreRedactedUrl(incoming, existing);
52
+ if (Array.isArray(incoming)) {
53
+ const previous = Array.isArray(existing) ? existing : [];
54
+ return incoming.map((value, index) => restoreRedactedValues(value, previous[index]));
55
+ }
56
+ if (!incoming || typeof incoming !== 'object') return incoming;
57
+ const previous = existing && typeof existing === 'object' && !Array.isArray(existing) ? existing : {};
58
+ return Object.fromEntries(Object.entries(incoming).map(([childKey, value]) => [
59
+ childKey,
60
+ restoreRedactedValues(value, previous[childKey], childKey)
61
+ ]));
62
+ }
63
+
64
+ function importMcpServer(servers, { id, platform, spec, name } = {}) {
65
+ let normalizedId;
66
+ try {
67
+ normalizedId = validateMcpId(id);
68
+ } catch (_) {
69
+ return false;
70
+ }
71
+ const importedSpec = normalizeImportedSpec(spec);
72
+ const existing = servers[normalizedId];
73
+
74
+ if (!existing) {
75
+ const now = Date.now();
76
+ servers[normalizedId] = {
77
+ id: normalizedId,
78
+ name: typeof name === 'string' && name.trim() ? name : normalizedId,
79
+ server: importedSpec,
80
+ apps: { [platform]: true },
81
+ createdAt: now,
82
+ updatedAt: now
83
+ };
84
+ return true;
85
+ }
86
+
87
+ // Treat imports as full snapshots, restoring only values hidden by the platform.
88
+ const nextSpec = restoreRedactedValues(importedSpec, existing.server);
89
+ const nextApps = {
90
+ ...(existing.apps && typeof existing.apps === 'object' && !Array.isArray(existing.apps)
91
+ ? existing.apps
92
+ : {}),
93
+ [platform]: true
94
+ };
95
+ const nextName = typeof name === 'string' && name.trim() ? name : existing.name || normalizedId;
96
+ const changed = !isDeepStrictEqual(existing.server, nextSpec)
97
+ || !isDeepStrictEqual(existing.apps || {}, nextApps)
98
+ || existing.name !== nextName;
99
+
100
+ existing.server = nextSpec;
101
+ existing.apps = nextApps;
102
+ existing.name = nextName;
103
+ if (changed) existing.updatedAt = Date.now();
104
+ return changed;
105
+ }
106
+
107
+ module.exports = { importMcpServer };