dsh-plugin-capabilities 0.3.6 → 0.3.8
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/README.md +3 -1
- package/lib/client.js +63 -20
- package/lib/client.js.map +2 -2
- package/lib/index.js +79 -30
- package/lib/index.js.map +2 -2
- package/package.json +1 -1
- package/src/client/MarketTab.tsx +1 -1
- package/src/client/McpTab.tsx +59 -19
- package/src/client/index.ts +5 -4
- package/src/client/locales.ts +16 -4
- package/src/index.ts +2 -1
- package/src/mcp.test.ts +83 -2
- package/src/mcp.ts +96 -18
- package/src/profile.ts +6 -2
- package/src/routes.ts +36 -21
package/lib/index.js
CHANGED
|
@@ -764,9 +764,11 @@ function argvProfile(argv = process.argv) {
|
|
|
764
764
|
if (flag !== -1 && flag + 1 < argv.length && !argv[flag + 1].startsWith("-")) return argv[flag + 1];
|
|
765
765
|
return void 0;
|
|
766
766
|
}
|
|
767
|
+
function dshHomeDir(dshHome = process.env.DSH_HOME) {
|
|
768
|
+
return dshHome ?? join2(homedir2(), ".dsh");
|
|
769
|
+
}
|
|
767
770
|
function profileDir(profile, dshHome = process.env.DSH_HOME) {
|
|
768
|
-
|
|
769
|
-
return join2(home, "profiles", profile);
|
|
771
|
+
return join2(dshHomeDir(dshHome), "profiles", profile);
|
|
770
772
|
}
|
|
771
773
|
|
|
772
774
|
// src/routes.ts
|
|
@@ -1429,17 +1431,26 @@ import { join as join9 } from "node:path";
|
|
|
1429
1431
|
import { parseDocument, Document } from "yaml";
|
|
1430
1432
|
var MCP_PLUGIN = "@deepseek-ai/dsh-mcp-client";
|
|
1431
1433
|
var SERVER_NAME_RE = /^[A-Za-z0-9_-]{1,32}$/;
|
|
1432
|
-
function
|
|
1433
|
-
|
|
1434
|
+
function assertPatchParses(path, doc) {
|
|
1435
|
+
if (doc.errors.length === 0) return;
|
|
1436
|
+
const at = String(doc.errors[0].message).split("\n", 1)[0];
|
|
1437
|
+
const total = doc.errors.length > 1 ? `, +${doc.errors.length - 1} more` : "";
|
|
1438
|
+
throw new Error(
|
|
1439
|
+
`\u914D\u7F6E\u6587\u4EF6 ${path} \u5B58\u5728 YAML \u8BED\u6CD5\u9519\u8BEF\uFF08${at}${total}\uFF09\uFF0C\u672A\u5199\u5165\u4EFB\u4F55\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u590D\u8BE5\u6587\u4EF6\u540E\u91CD\u8BD5 / YAML syntax error in the patch file (${at}${total}); nothing was written \u2014 fix it and retry`
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
function loadPatch(dirPath) {
|
|
1443
|
+
const path = join9(dirPath, "cordis.patch.yml");
|
|
1434
1444
|
const text = existsSync6(path) ? readFileSync6(path, "utf8") : "[]";
|
|
1435
1445
|
const doc = parseDocument(text);
|
|
1446
|
+
assertPatchParses(path, doc);
|
|
1436
1447
|
const contents = doc.contents;
|
|
1437
1448
|
if (contents !== null && contents.flow === true && contents.items.length === 0) contents.flow = false;
|
|
1438
1449
|
return doc;
|
|
1439
1450
|
}
|
|
1440
|
-
function savePatch(
|
|
1441
|
-
mkdirSync5(
|
|
1442
|
-
writeFileSync4(join9(
|
|
1451
|
+
function savePatch(dirPath, doc) {
|
|
1452
|
+
mkdirSync5(dirPath, { recursive: true });
|
|
1453
|
+
writeFileSync4(join9(dirPath, "cordis.patch.yml"), String(doc), "utf8");
|
|
1443
1454
|
}
|
|
1444
1455
|
function toNode(value) {
|
|
1445
1456
|
return new Document(value).contents;
|
|
@@ -1523,10 +1534,36 @@ function takenIds(doc) {
|
|
|
1523
1534
|
}
|
|
1524
1535
|
return taken;
|
|
1525
1536
|
}
|
|
1526
|
-
function listMcp(
|
|
1527
|
-
const doc = loadPatch(
|
|
1537
|
+
function listMcp(dirPath) {
|
|
1538
|
+
const doc = loadPatch(dirPath);
|
|
1528
1539
|
return mcpRowItems(doc).map(({ node }) => rowToMcp(doc, node));
|
|
1529
1540
|
}
|
|
1541
|
+
function listMcpScoped(profileDirPath, dshHomePath) {
|
|
1542
|
+
let globalRows = [];
|
|
1543
|
+
let globalError;
|
|
1544
|
+
if (existsSync6(join9(dshHomePath, "cordis.patch.yml"))) {
|
|
1545
|
+
try {
|
|
1546
|
+
globalRows = listMcp(dshHomePath);
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
globalError = error instanceof Error ? error.message : String(error);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
const globalIds = new Set(globalRows.map((row) => row.id).filter((id) => id !== ""));
|
|
1552
|
+
return {
|
|
1553
|
+
servers: [
|
|
1554
|
+
...globalRows.map((row) => ({ ...row, scope: "global" })),
|
|
1555
|
+
...listMcp(profileDirPath).map((row) => ({
|
|
1556
|
+
...row,
|
|
1557
|
+
scope: "profile",
|
|
1558
|
+
...globalIds.has(row.id) ? { shadowed: true } : {}
|
|
1559
|
+
}))
|
|
1560
|
+
],
|
|
1561
|
+
...globalError !== void 0 ? { globalError } : {}
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
function mcpScopeDir(scope, profileDirPath, dshHomePath) {
|
|
1565
|
+
return scope === "global" ? dshHomePath : profileDirPath;
|
|
1566
|
+
}
|
|
1530
1567
|
function validateMcpInput(input) {
|
|
1531
1568
|
if (!SERVER_NAME_RE.test(input.serverName)) return "serverName must be 1-32 chars of A-Z a-z 0-9 _ -";
|
|
1532
1569
|
const id = input.id ?? "";
|
|
@@ -1538,9 +1575,9 @@ function validateMcpInput(input) {
|
|
|
1538
1575
|
}
|
|
1539
1576
|
return null;
|
|
1540
1577
|
}
|
|
1541
|
-
function upsertMcp(
|
|
1578
|
+
function upsertMcp(dirPath, input) {
|
|
1542
1579
|
const inputId = input.id ?? "";
|
|
1543
|
-
const doc = loadPatch(
|
|
1580
|
+
const doc = loadPatch(dirPath);
|
|
1544
1581
|
const list = managedInsert(doc);
|
|
1545
1582
|
const existing = inputId !== "" ? mcpRowItems(doc).find(({ node: node2 }) => String(node2.get("id") ?? "") === inputId) : void 0;
|
|
1546
1583
|
let id = inputId !== "" ? inputId : `mcp-${input.serverName}`;
|
|
@@ -1572,21 +1609,21 @@ function upsertMcp(profileDirPath, input) {
|
|
|
1572
1609
|
} else {
|
|
1573
1610
|
rowSeq(doc).items.splice(rowSeq(doc).items.indexOf(existing.node), 1, node);
|
|
1574
1611
|
}
|
|
1575
|
-
savePatch(
|
|
1612
|
+
savePatch(dirPath, doc);
|
|
1576
1613
|
return id;
|
|
1577
1614
|
}
|
|
1578
|
-
function setMcpDisabled(
|
|
1579
|
-
const doc = loadPatch(
|
|
1615
|
+
function setMcpDisabled(dirPath, id, disabled) {
|
|
1616
|
+
const doc = loadPatch(dirPath);
|
|
1580
1617
|
managedInsert(doc);
|
|
1581
1618
|
const hit = mcpRowItems(doc).find(({ node }) => String(node.get("id") ?? "") === id);
|
|
1582
1619
|
if (hit === void 0) return false;
|
|
1583
1620
|
if (disabled) hit.node.set("disabled", true);
|
|
1584
1621
|
else hit.node.delete("disabled");
|
|
1585
|
-
savePatch(
|
|
1622
|
+
savePatch(dirPath, doc);
|
|
1586
1623
|
return true;
|
|
1587
1624
|
}
|
|
1588
|
-
function removeMcp(
|
|
1589
|
-
const doc = loadPatch(
|
|
1625
|
+
function removeMcp(dirPath, id) {
|
|
1626
|
+
const doc = loadPatch(dirPath);
|
|
1590
1627
|
managedInsert(doc);
|
|
1591
1628
|
const hit = mcpRowItems(doc).find(({ node }) => String(node.get("id") ?? "") === id);
|
|
1592
1629
|
if (hit === void 0 || hit.list === void 0) return false;
|
|
@@ -1596,7 +1633,7 @@ function removeMcp(profileDirPath, id) {
|
|
|
1596
1633
|
if (owner !== void 0 && hit.list.items.length === 0 && owner.items.length === 1) {
|
|
1597
1634
|
seq.items.splice(seq.items.indexOf(owner), 1);
|
|
1598
1635
|
}
|
|
1599
|
-
savePatch(
|
|
1636
|
+
savePatch(dirPath, doc);
|
|
1600
1637
|
return true;
|
|
1601
1638
|
}
|
|
1602
1639
|
|
|
@@ -1624,6 +1661,9 @@ function toSkillRow(skill, dshHome) {
|
|
|
1624
1661
|
function toRootView(entry) {
|
|
1625
1662
|
return { ...entry, live: entry.roots.every((root) => rootExists(root)) };
|
|
1626
1663
|
}
|
|
1664
|
+
function readScope(value) {
|
|
1665
|
+
return value === "global" ? "global" : "profile";
|
|
1666
|
+
}
|
|
1627
1667
|
function mountCapabilitiesRoutes(host, config) {
|
|
1628
1668
|
const disposers = [
|
|
1629
1669
|
host.webServer.register({
|
|
@@ -1940,7 +1980,7 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
1940
1980
|
sendJson(response, 502, { error: "market index unavailable (offline?)" });
|
|
1941
1981
|
return;
|
|
1942
1982
|
}
|
|
1943
|
-
const existing = new Set(
|
|
1983
|
+
const existing = new Set(listMcpScoped(config.profileDirPath, config.dshHomePath).servers.map((row) => row.serverName));
|
|
1944
1984
|
sendJson(response, 200, {
|
|
1945
1985
|
source: index.source,
|
|
1946
1986
|
servers: (index.servers ?? []).map((server) => ({ ...server, installed: existing.has(server.id) }))
|
|
@@ -1999,7 +2039,7 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
1999
2039
|
sendJson(response, 404, { error: "server not found in the market index" });
|
|
2000
2040
|
return;
|
|
2001
2041
|
}
|
|
2002
|
-
if (
|
|
2042
|
+
if (listMcpScoped(config.profileDirPath, config.dshHomePath).servers.some((row) => row.serverName === server.id)) {
|
|
2003
2043
|
sendJson(response, 409, { error: "already installed" });
|
|
2004
2044
|
return;
|
|
2005
2045
|
}
|
|
@@ -2030,7 +2070,12 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
2030
2070
|
response.end();
|
|
2031
2071
|
return;
|
|
2032
2072
|
}
|
|
2033
|
-
|
|
2073
|
+
const listing = listMcpScoped(config.profileDirPath, config.dshHomePath);
|
|
2074
|
+
sendJson(response, 200, {
|
|
2075
|
+
servers: listing.servers,
|
|
2076
|
+
...listing.globalError !== void 0 ? { globalError: listing.globalError } : {},
|
|
2077
|
+
restartNeeded: true
|
|
2078
|
+
});
|
|
2034
2079
|
}
|
|
2035
2080
|
}),
|
|
2036
2081
|
host.webServer.register({
|
|
@@ -2053,8 +2098,9 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
2053
2098
|
sendJson(response, 400, { error: invalid });
|
|
2054
2099
|
return;
|
|
2055
2100
|
}
|
|
2056
|
-
const
|
|
2057
|
-
|
|
2101
|
+
const scope = readScope(input.scope);
|
|
2102
|
+
const id = upsertMcp(mcpScopeDir(scope, config.profileDirPath, config.dshHomePath), input);
|
|
2103
|
+
sendJson(response, 200, { ok: true, id, scope, restartNeeded: true });
|
|
2058
2104
|
} catch (error) {
|
|
2059
2105
|
sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
2060
2106
|
}
|
|
@@ -2079,7 +2125,8 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
2079
2125
|
sendJson(response, 400, { error: "id and disabled are required" });
|
|
2080
2126
|
return;
|
|
2081
2127
|
}
|
|
2082
|
-
const
|
|
2128
|
+
const dir = mcpScopeDir(readScope(body.scope), config.profileDirPath, config.dshHomePath);
|
|
2129
|
+
const ok = setMcpDisabled(dir, body.id, body.disabled);
|
|
2083
2130
|
sendJson(response, ok ? 200 : 404, ok ? { ok: true, restartNeeded: true } : { error: "server row not found" });
|
|
2084
2131
|
} catch (error) {
|
|
2085
2132
|
sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
@@ -2105,7 +2152,8 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
2105
2152
|
sendJson(response, 400, { error: "id is required" });
|
|
2106
2153
|
return;
|
|
2107
2154
|
}
|
|
2108
|
-
const
|
|
2155
|
+
const dir = mcpScopeDir(readScope(body.scope), config.profileDirPath, config.dshHomePath);
|
|
2156
|
+
const ok = removeMcp(dir, body.id);
|
|
2109
2157
|
sendJson(response, ok ? 200 : 404, ok ? { ok: true, restartNeeded: true } : { error: "server row not found" });
|
|
2110
2158
|
} catch (error) {
|
|
2111
2159
|
sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
@@ -2124,8 +2172,9 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
2124
2172
|
try {
|
|
2125
2173
|
sendJson(response, 200, {
|
|
2126
2174
|
servers: scanAllMcp(),
|
|
2127
|
-
//
|
|
2128
|
-
|
|
2175
|
+
// ServerNames present in either patch layer, so the browser can
|
|
2176
|
+
// grey out existing ones.
|
|
2177
|
+
existing: listMcpScoped(config.profileDirPath, config.dshHomePath).servers.map((row) => row.serverName)
|
|
2129
2178
|
});
|
|
2130
2179
|
} catch (error) {
|
|
2131
2180
|
sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
@@ -2153,9 +2202,8 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
2153
2202
|
const results = [];
|
|
2154
2203
|
for (const server of scanAllMcp()) {
|
|
2155
2204
|
if (!wanted.has(`${server.agent}/${server.name}`)) continue;
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
results.push({ name: server.name, ok: false, error: "already in profile" });
|
|
2205
|
+
if (listMcpScoped(config.profileDirPath, config.dshHomePath).servers.some((row) => row.serverName === server.name)) {
|
|
2206
|
+
results.push({ name: server.name, ok: false, error: "already configured" });
|
|
2159
2207
|
continue;
|
|
2160
2208
|
}
|
|
2161
2209
|
const input = {
|
|
@@ -2252,6 +2300,7 @@ function apply(ctx, config) {
|
|
|
2252
2300
|
ctx.effect(
|
|
2253
2301
|
() => mountCapabilitiesRoutes(hostCtx, {
|
|
2254
2302
|
profileDirPath: profileDir(profile),
|
|
2303
|
+
dshHomePath: dshHomeDir(),
|
|
2255
2304
|
remountProvider
|
|
2256
2305
|
}),
|
|
2257
2306
|
"dsh-plugin-capabilities: http routes"
|