coding-tool-x 3.9.4 → 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 +1 -1
- package/src/config/model-metadata.json +153 -10
- package/src/platforms/drivers/dsh/api-operations.js +46 -43
- package/src/server/api/mcp.js +2 -2
- package/src/server/services/mcp-service.js +2 -19
- package/src/shared/driver-factories/mcp.js +2 -8
- package/src/shared/mcp-import.js +107 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": "models.dev",
|
|
3
3
|
"sourceUrl": "https://models.dev/api.json",
|
|
4
|
-
"lastUpdated": "2026-09
|
|
4
|
+
"lastUpdated": "2026-10-09",
|
|
5
5
|
"defaultModels": {
|
|
6
6
|
"claude": [
|
|
7
7
|
"claude-opus-4-7",
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
"claude-opus-4-5",
|
|
19
19
|
"claude-haiku-4-5",
|
|
20
20
|
"claude-sonnet-4-5",
|
|
21
|
-
"claude-opus-5-5"
|
|
21
|
+
"claude-opus-5-5",
|
|
22
|
+
"claude-sonnet-5-5",
|
|
23
|
+
"claude-haiku-5-5"
|
|
22
24
|
],
|
|
23
25
|
"codex": [
|
|
24
26
|
"gpt-5.5",
|
|
@@ -58,7 +60,8 @@
|
|
|
58
60
|
"gpt-6-luna",
|
|
59
61
|
"gpt-6-sol",
|
|
60
62
|
"gpt-daybreak-blue-latest",
|
|
61
|
-
"gpt-daybreak-red-latest"
|
|
63
|
+
"gpt-daybreak-red-latest",
|
|
64
|
+
"gpt-6.1-sol"
|
|
62
65
|
],
|
|
63
66
|
"gemini": [
|
|
64
67
|
"gemini-3.1-pro-preview",
|
|
@@ -95,6 +98,7 @@
|
|
|
95
98
|
"claude-opus-4.7": "claude-opus-4-7",
|
|
96
99
|
"claude-sonnet-4-5": "claude-sonnet-4-5-20250929",
|
|
97
100
|
"claude-sonnet-4-6": "claude-sonnet-4-6",
|
|
101
|
+
"codestral-2508": "mistral/codestral-2508",
|
|
98
102
|
"codestral-latest": "mistral/codestral-latest",
|
|
99
103
|
"deepseek-flash": "deepseek/deepseek-flash",
|
|
100
104
|
"deepseek-v4-flash": "deepseek/deepseek-v4-flash",
|
|
@@ -142,6 +146,7 @@
|
|
|
142
146
|
"kimi-k3": "moonshotai/kimi-k3",
|
|
143
147
|
"mistral-large-2411": "mistral/mistral-large-2411",
|
|
144
148
|
"mistral-large-2512": "mistral/mistral-large-2512",
|
|
149
|
+
"mistral-large-4": "mistral/mistral-large-4",
|
|
145
150
|
"mistral-large-latest": "mistral/mistral-large-latest",
|
|
146
151
|
"mistral-medium-2505": "mistral/mistral-medium-2505",
|
|
147
152
|
"mistral-medium-2508": "mistral/mistral-medium-2508",
|
|
@@ -1506,6 +1511,33 @@
|
|
|
1506
1511
|
"omp"
|
|
1507
1512
|
]
|
|
1508
1513
|
},
|
|
1514
|
+
"claude-haiku-5-5": {
|
|
1515
|
+
"id": "claude-haiku-5-5",
|
|
1516
|
+
"input": [
|
|
1517
|
+
"text",
|
|
1518
|
+
"image"
|
|
1519
|
+
],
|
|
1520
|
+
"limit": {
|
|
1521
|
+
"context": 1000000,
|
|
1522
|
+
"output": 128000
|
|
1523
|
+
},
|
|
1524
|
+
"name": "Claude Haiku 5.5",
|
|
1525
|
+
"pricing": {
|
|
1526
|
+
"cacheCreation": 0.125,
|
|
1527
|
+
"cacheRead": 0.01,
|
|
1528
|
+
"input": 0.1,
|
|
1529
|
+
"output": 0.5
|
|
1530
|
+
},
|
|
1531
|
+
"provider": "anthropic",
|
|
1532
|
+
"reasoning": true,
|
|
1533
|
+
"sourceId": "anthropic/claude-haiku-5-5",
|
|
1534
|
+
"supportsTools": true,
|
|
1535
|
+
"toolTypes": [
|
|
1536
|
+
"claude",
|
|
1537
|
+
"opencode",
|
|
1538
|
+
"omp"
|
|
1539
|
+
]
|
|
1540
|
+
},
|
|
1509
1541
|
"claude-opus-4-5": {
|
|
1510
1542
|
"id": "claude-opus-4-5",
|
|
1511
1543
|
"input": [
|
|
@@ -1702,7 +1734,7 @@
|
|
|
1702
1734
|
"image"
|
|
1703
1735
|
],
|
|
1704
1736
|
"limit": {
|
|
1705
|
-
"context":
|
|
1737
|
+
"context": 200000,
|
|
1706
1738
|
"output": 64000
|
|
1707
1739
|
},
|
|
1708
1740
|
"name": "Claude Sonnet 4.5 (latest)",
|
|
@@ -1729,7 +1761,7 @@
|
|
|
1729
1761
|
"image"
|
|
1730
1762
|
],
|
|
1731
1763
|
"limit": {
|
|
1732
|
-
"context":
|
|
1764
|
+
"context": 200000,
|
|
1733
1765
|
"output": 64000
|
|
1734
1766
|
},
|
|
1735
1767
|
"name": "Claude Sonnet 4.5",
|
|
@@ -1803,6 +1835,33 @@
|
|
|
1803
1835
|
"omp"
|
|
1804
1836
|
]
|
|
1805
1837
|
},
|
|
1838
|
+
"claude-sonnet-5-5": {
|
|
1839
|
+
"id": "claude-sonnet-5-5",
|
|
1840
|
+
"input": [
|
|
1841
|
+
"text",
|
|
1842
|
+
"image"
|
|
1843
|
+
],
|
|
1844
|
+
"limit": {
|
|
1845
|
+
"context": 1000000,
|
|
1846
|
+
"output": 128000
|
|
1847
|
+
},
|
|
1848
|
+
"name": "Claude Sonnet 5.5",
|
|
1849
|
+
"pricing": {
|
|
1850
|
+
"cacheCreation": 2.5,
|
|
1851
|
+
"cacheRead": 0.1,
|
|
1852
|
+
"input": 2,
|
|
1853
|
+
"output": 10
|
|
1854
|
+
},
|
|
1855
|
+
"provider": "anthropic",
|
|
1856
|
+
"reasoning": true,
|
|
1857
|
+
"sourceId": "anthropic/claude-sonnet-5-5",
|
|
1858
|
+
"supportsTools": true,
|
|
1859
|
+
"toolTypes": [
|
|
1860
|
+
"claude",
|
|
1861
|
+
"opencode",
|
|
1862
|
+
"omp"
|
|
1863
|
+
]
|
|
1864
|
+
},
|
|
1806
1865
|
"deepseek/deepseek-flash": {
|
|
1807
1866
|
"id": "deepseek/deepseek-flash",
|
|
1808
1867
|
"input": [
|
|
@@ -1889,9 +1948,9 @@
|
|
|
1889
1948
|
},
|
|
1890
1949
|
"name": "DeepSeek V4 Pro",
|
|
1891
1950
|
"pricing": {
|
|
1892
|
-
"cacheRead": 0.
|
|
1893
|
-
"input": 0.
|
|
1894
|
-
"output":
|
|
1951
|
+
"cacheRead": 0.022,
|
|
1952
|
+
"input": 0.66,
|
|
1953
|
+
"output": 1.98
|
|
1895
1954
|
},
|
|
1896
1955
|
"provider": "deepseek",
|
|
1897
1956
|
"reasoning": true,
|
|
@@ -3181,6 +3240,33 @@
|
|
|
3181
3240
|
"omp"
|
|
3182
3241
|
]
|
|
3183
3242
|
},
|
|
3243
|
+
"gpt-6.1-sol": {
|
|
3244
|
+
"id": "gpt-6.1-sol",
|
|
3245
|
+
"input": [
|
|
3246
|
+
"text",
|
|
3247
|
+
"image"
|
|
3248
|
+
],
|
|
3249
|
+
"limit": {
|
|
3250
|
+
"context": 1050000,
|
|
3251
|
+
"output": 128000
|
|
3252
|
+
},
|
|
3253
|
+
"name": "GPT-6.1 Sol",
|
|
3254
|
+
"pricing": {
|
|
3255
|
+
"cacheCreation": 2.5,
|
|
3256
|
+
"cacheRead": 0.1,
|
|
3257
|
+
"input": 2,
|
|
3258
|
+
"output": 10
|
|
3259
|
+
},
|
|
3260
|
+
"provider": "openai",
|
|
3261
|
+
"reasoning": true,
|
|
3262
|
+
"sourceId": "openai/gpt-6.1-sol",
|
|
3263
|
+
"supportsTools": true,
|
|
3264
|
+
"toolTypes": [
|
|
3265
|
+
"codex",
|
|
3266
|
+
"opencode",
|
|
3267
|
+
"omp"
|
|
3268
|
+
]
|
|
3269
|
+
},
|
|
3184
3270
|
"gpt-daybreak-blue-latest": {
|
|
3185
3271
|
"id": "gpt-daybreak-blue-latest",
|
|
3186
3272
|
"input": [
|
|
@@ -3261,6 +3347,30 @@
|
|
|
3261
3347
|
"omp"
|
|
3262
3348
|
]
|
|
3263
3349
|
},
|
|
3350
|
+
"mistral/codestral-2508": {
|
|
3351
|
+
"id": "mistral/codestral-2508",
|
|
3352
|
+
"input": [
|
|
3353
|
+
"text"
|
|
3354
|
+
],
|
|
3355
|
+
"limit": {
|
|
3356
|
+
"context": 256000,
|
|
3357
|
+
"output": 8192
|
|
3358
|
+
},
|
|
3359
|
+
"name": "Codestral 25.08",
|
|
3360
|
+
"pricing": {
|
|
3361
|
+
"cacheRead": 0.03,
|
|
3362
|
+
"input": 0.3,
|
|
3363
|
+
"output": 0.9
|
|
3364
|
+
},
|
|
3365
|
+
"provider": "mistral",
|
|
3366
|
+
"reasoning": false,
|
|
3367
|
+
"sourceId": "mistral/codestral-2508",
|
|
3368
|
+
"supportsTools": true,
|
|
3369
|
+
"toolTypes": [
|
|
3370
|
+
"opencode",
|
|
3371
|
+
"omp"
|
|
3372
|
+
]
|
|
3373
|
+
},
|
|
3264
3374
|
"mistral/codestral-latest": {
|
|
3265
3375
|
"id": "mistral/codestral-latest",
|
|
3266
3376
|
"input": [
|
|
@@ -3272,6 +3382,7 @@
|
|
|
3272
3382
|
},
|
|
3273
3383
|
"name": "Codestral (latest)",
|
|
3274
3384
|
"pricing": {
|
|
3385
|
+
"cacheRead": 0.03,
|
|
3275
3386
|
"input": 0.3,
|
|
3276
3387
|
"output": 0.9
|
|
3277
3388
|
},
|
|
@@ -3319,6 +3430,7 @@
|
|
|
3319
3430
|
},
|
|
3320
3431
|
"name": "Mistral Large 3",
|
|
3321
3432
|
"pricing": {
|
|
3433
|
+
"cacheRead": 0.05,
|
|
3322
3434
|
"input": 0.5,
|
|
3323
3435
|
"output": 1.5
|
|
3324
3436
|
},
|
|
@@ -3331,6 +3443,31 @@
|
|
|
3331
3443
|
"omp"
|
|
3332
3444
|
]
|
|
3333
3445
|
},
|
|
3446
|
+
"mistral/mistral-large-4": {
|
|
3447
|
+
"id": "mistral/mistral-large-4",
|
|
3448
|
+
"input": [
|
|
3449
|
+
"text",
|
|
3450
|
+
"image"
|
|
3451
|
+
],
|
|
3452
|
+
"limit": {
|
|
3453
|
+
"context": 1048576,
|
|
3454
|
+
"output": 262144
|
|
3455
|
+
},
|
|
3456
|
+
"name": "Mistral Large 4",
|
|
3457
|
+
"pricing": {
|
|
3458
|
+
"cacheRead": 0.07,
|
|
3459
|
+
"input": 0.68,
|
|
3460
|
+
"output": 2.09
|
|
3461
|
+
},
|
|
3462
|
+
"provider": "mistral",
|
|
3463
|
+
"reasoning": true,
|
|
3464
|
+
"sourceId": "mistral/mistral-large-4",
|
|
3465
|
+
"supportsTools": true,
|
|
3466
|
+
"toolTypes": [
|
|
3467
|
+
"opencode",
|
|
3468
|
+
"omp"
|
|
3469
|
+
]
|
|
3470
|
+
},
|
|
3334
3471
|
"mistral/mistral-large-latest": {
|
|
3335
3472
|
"id": "mistral/mistral-large-latest",
|
|
3336
3473
|
"input": [
|
|
@@ -3343,6 +3480,7 @@
|
|
|
3343
3480
|
},
|
|
3344
3481
|
"name": "Mistral Large (latest)",
|
|
3345
3482
|
"pricing": {
|
|
3483
|
+
"cacheRead": 0.05,
|
|
3346
3484
|
"input": 0.5,
|
|
3347
3485
|
"output": 1.5
|
|
3348
3486
|
},
|
|
@@ -3415,6 +3553,7 @@
|
|
|
3415
3553
|
},
|
|
3416
3554
|
"name": "Mistral Medium 3.5",
|
|
3417
3555
|
"pricing": {
|
|
3556
|
+
"cacheRead": 0.15,
|
|
3418
3557
|
"input": 1.5,
|
|
3419
3558
|
"output": 7.5
|
|
3420
3559
|
},
|
|
@@ -3439,6 +3578,7 @@
|
|
|
3439
3578
|
},
|
|
3440
3579
|
"name": "Mistral Medium (latest)",
|
|
3441
3580
|
"pricing": {
|
|
3581
|
+
"cacheRead": 0.15,
|
|
3442
3582
|
"input": 1.5,
|
|
3443
3583
|
"output": 7.5
|
|
3444
3584
|
},
|
|
@@ -3505,11 +3645,12 @@
|
|
|
3505
3645
|
"image"
|
|
3506
3646
|
],
|
|
3507
3647
|
"limit": {
|
|
3508
|
-
"context":
|
|
3648
|
+
"context": 262144,
|
|
3509
3649
|
"output": 256000
|
|
3510
3650
|
},
|
|
3511
3651
|
"name": "Mistral Small 4",
|
|
3512
3652
|
"pricing": {
|
|
3653
|
+
"cacheRead": 0.015,
|
|
3513
3654
|
"input": 0.15,
|
|
3514
3655
|
"output": 0.6
|
|
3515
3656
|
},
|
|
@@ -3529,11 +3670,12 @@
|
|
|
3529
3670
|
"image"
|
|
3530
3671
|
],
|
|
3531
3672
|
"limit": {
|
|
3532
|
-
"context":
|
|
3673
|
+
"context": 262144,
|
|
3533
3674
|
"output": 256000
|
|
3534
3675
|
},
|
|
3535
3676
|
"name": "Mistral Small (latest)",
|
|
3536
3677
|
"pricing": {
|
|
3678
|
+
"cacheRead": 0.015,
|
|
3537
3679
|
"input": 0.15,
|
|
3538
3680
|
"output": 0.6
|
|
3539
3681
|
},
|
|
@@ -3778,6 +3920,7 @@
|
|
|
3778
3920
|
},
|
|
3779
3921
|
"name": "Kimi K3",
|
|
3780
3922
|
"pricing": {
|
|
3923
|
+
"cacheCreation": 3,
|
|
3781
3924
|
"cacheRead": 0.3,
|
|
3782
3925
|
"input": 3,
|
|
3783
3926
|
"output": 15
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
|
|
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
|
|
package/src/server/api/mcp.js
CHANGED
|
@@ -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
|
-
?
|
|
330
|
-
: `${resolved.key}
|
|
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
|
|
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
|
|
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 };
|