dsh-plugin-capabilities 0.3.7 → 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 +2 -0
- package/lib/client.js +63 -20
- package/lib/client.js.map +2 -2
- package/lib/index.js +70 -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 +66 -1
- package/src/mcp.ts +78 -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
|
|
@@ -1437,8 +1439,8 @@ function assertPatchParses(path, doc) {
|
|
|
1437
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`
|
|
1438
1440
|
);
|
|
1439
1441
|
}
|
|
1440
|
-
function loadPatch(
|
|
1441
|
-
const path = join9(
|
|
1442
|
+
function loadPatch(dirPath) {
|
|
1443
|
+
const path = join9(dirPath, "cordis.patch.yml");
|
|
1442
1444
|
const text = existsSync6(path) ? readFileSync6(path, "utf8") : "[]";
|
|
1443
1445
|
const doc = parseDocument(text);
|
|
1444
1446
|
assertPatchParses(path, doc);
|
|
@@ -1446,9 +1448,9 @@ function loadPatch(profileDirPath) {
|
|
|
1446
1448
|
if (contents !== null && contents.flow === true && contents.items.length === 0) contents.flow = false;
|
|
1447
1449
|
return doc;
|
|
1448
1450
|
}
|
|
1449
|
-
function savePatch(
|
|
1450
|
-
mkdirSync5(
|
|
1451
|
-
writeFileSync4(join9(
|
|
1451
|
+
function savePatch(dirPath, doc) {
|
|
1452
|
+
mkdirSync5(dirPath, { recursive: true });
|
|
1453
|
+
writeFileSync4(join9(dirPath, "cordis.patch.yml"), String(doc), "utf8");
|
|
1452
1454
|
}
|
|
1453
1455
|
function toNode(value) {
|
|
1454
1456
|
return new Document(value).contents;
|
|
@@ -1532,10 +1534,36 @@ function takenIds(doc) {
|
|
|
1532
1534
|
}
|
|
1533
1535
|
return taken;
|
|
1534
1536
|
}
|
|
1535
|
-
function listMcp(
|
|
1536
|
-
const doc = loadPatch(
|
|
1537
|
+
function listMcp(dirPath) {
|
|
1538
|
+
const doc = loadPatch(dirPath);
|
|
1537
1539
|
return mcpRowItems(doc).map(({ node }) => rowToMcp(doc, node));
|
|
1538
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
|
+
}
|
|
1539
1567
|
function validateMcpInput(input) {
|
|
1540
1568
|
if (!SERVER_NAME_RE.test(input.serverName)) return "serverName must be 1-32 chars of A-Z a-z 0-9 _ -";
|
|
1541
1569
|
const id = input.id ?? "";
|
|
@@ -1547,9 +1575,9 @@ function validateMcpInput(input) {
|
|
|
1547
1575
|
}
|
|
1548
1576
|
return null;
|
|
1549
1577
|
}
|
|
1550
|
-
function upsertMcp(
|
|
1578
|
+
function upsertMcp(dirPath, input) {
|
|
1551
1579
|
const inputId = input.id ?? "";
|
|
1552
|
-
const doc = loadPatch(
|
|
1580
|
+
const doc = loadPatch(dirPath);
|
|
1553
1581
|
const list = managedInsert(doc);
|
|
1554
1582
|
const existing = inputId !== "" ? mcpRowItems(doc).find(({ node: node2 }) => String(node2.get("id") ?? "") === inputId) : void 0;
|
|
1555
1583
|
let id = inputId !== "" ? inputId : `mcp-${input.serverName}`;
|
|
@@ -1581,21 +1609,21 @@ function upsertMcp(profileDirPath, input) {
|
|
|
1581
1609
|
} else {
|
|
1582
1610
|
rowSeq(doc).items.splice(rowSeq(doc).items.indexOf(existing.node), 1, node);
|
|
1583
1611
|
}
|
|
1584
|
-
savePatch(
|
|
1612
|
+
savePatch(dirPath, doc);
|
|
1585
1613
|
return id;
|
|
1586
1614
|
}
|
|
1587
|
-
function setMcpDisabled(
|
|
1588
|
-
const doc = loadPatch(
|
|
1615
|
+
function setMcpDisabled(dirPath, id, disabled) {
|
|
1616
|
+
const doc = loadPatch(dirPath);
|
|
1589
1617
|
managedInsert(doc);
|
|
1590
1618
|
const hit = mcpRowItems(doc).find(({ node }) => String(node.get("id") ?? "") === id);
|
|
1591
1619
|
if (hit === void 0) return false;
|
|
1592
1620
|
if (disabled) hit.node.set("disabled", true);
|
|
1593
1621
|
else hit.node.delete("disabled");
|
|
1594
|
-
savePatch(
|
|
1622
|
+
savePatch(dirPath, doc);
|
|
1595
1623
|
return true;
|
|
1596
1624
|
}
|
|
1597
|
-
function removeMcp(
|
|
1598
|
-
const doc = loadPatch(
|
|
1625
|
+
function removeMcp(dirPath, id) {
|
|
1626
|
+
const doc = loadPatch(dirPath);
|
|
1599
1627
|
managedInsert(doc);
|
|
1600
1628
|
const hit = mcpRowItems(doc).find(({ node }) => String(node.get("id") ?? "") === id);
|
|
1601
1629
|
if (hit === void 0 || hit.list === void 0) return false;
|
|
@@ -1605,7 +1633,7 @@ function removeMcp(profileDirPath, id) {
|
|
|
1605
1633
|
if (owner !== void 0 && hit.list.items.length === 0 && owner.items.length === 1) {
|
|
1606
1634
|
seq.items.splice(seq.items.indexOf(owner), 1);
|
|
1607
1635
|
}
|
|
1608
|
-
savePatch(
|
|
1636
|
+
savePatch(dirPath, doc);
|
|
1609
1637
|
return true;
|
|
1610
1638
|
}
|
|
1611
1639
|
|
|
@@ -1633,6 +1661,9 @@ function toSkillRow(skill, dshHome) {
|
|
|
1633
1661
|
function toRootView(entry) {
|
|
1634
1662
|
return { ...entry, live: entry.roots.every((root) => rootExists(root)) };
|
|
1635
1663
|
}
|
|
1664
|
+
function readScope(value) {
|
|
1665
|
+
return value === "global" ? "global" : "profile";
|
|
1666
|
+
}
|
|
1636
1667
|
function mountCapabilitiesRoutes(host, config) {
|
|
1637
1668
|
const disposers = [
|
|
1638
1669
|
host.webServer.register({
|
|
@@ -1949,7 +1980,7 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
1949
1980
|
sendJson(response, 502, { error: "market index unavailable (offline?)" });
|
|
1950
1981
|
return;
|
|
1951
1982
|
}
|
|
1952
|
-
const existing = new Set(
|
|
1983
|
+
const existing = new Set(listMcpScoped(config.profileDirPath, config.dshHomePath).servers.map((row) => row.serverName));
|
|
1953
1984
|
sendJson(response, 200, {
|
|
1954
1985
|
source: index.source,
|
|
1955
1986
|
servers: (index.servers ?? []).map((server) => ({ ...server, installed: existing.has(server.id) }))
|
|
@@ -2008,7 +2039,7 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
2008
2039
|
sendJson(response, 404, { error: "server not found in the market index" });
|
|
2009
2040
|
return;
|
|
2010
2041
|
}
|
|
2011
|
-
if (
|
|
2042
|
+
if (listMcpScoped(config.profileDirPath, config.dshHomePath).servers.some((row) => row.serverName === server.id)) {
|
|
2012
2043
|
sendJson(response, 409, { error: "already installed" });
|
|
2013
2044
|
return;
|
|
2014
2045
|
}
|
|
@@ -2039,7 +2070,12 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
2039
2070
|
response.end();
|
|
2040
2071
|
return;
|
|
2041
2072
|
}
|
|
2042
|
-
|
|
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
|
+
});
|
|
2043
2079
|
}
|
|
2044
2080
|
}),
|
|
2045
2081
|
host.webServer.register({
|
|
@@ -2062,8 +2098,9 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
2062
2098
|
sendJson(response, 400, { error: invalid });
|
|
2063
2099
|
return;
|
|
2064
2100
|
}
|
|
2065
|
-
const
|
|
2066
|
-
|
|
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 });
|
|
2067
2104
|
} catch (error) {
|
|
2068
2105
|
sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
2069
2106
|
}
|
|
@@ -2088,7 +2125,8 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
2088
2125
|
sendJson(response, 400, { error: "id and disabled are required" });
|
|
2089
2126
|
return;
|
|
2090
2127
|
}
|
|
2091
|
-
const
|
|
2128
|
+
const dir = mcpScopeDir(readScope(body.scope), config.profileDirPath, config.dshHomePath);
|
|
2129
|
+
const ok = setMcpDisabled(dir, body.id, body.disabled);
|
|
2092
2130
|
sendJson(response, ok ? 200 : 404, ok ? { ok: true, restartNeeded: true } : { error: "server row not found" });
|
|
2093
2131
|
} catch (error) {
|
|
2094
2132
|
sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
@@ -2114,7 +2152,8 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
2114
2152
|
sendJson(response, 400, { error: "id is required" });
|
|
2115
2153
|
return;
|
|
2116
2154
|
}
|
|
2117
|
-
const
|
|
2155
|
+
const dir = mcpScopeDir(readScope(body.scope), config.profileDirPath, config.dshHomePath);
|
|
2156
|
+
const ok = removeMcp(dir, body.id);
|
|
2118
2157
|
sendJson(response, ok ? 200 : 404, ok ? { ok: true, restartNeeded: true } : { error: "server row not found" });
|
|
2119
2158
|
} catch (error) {
|
|
2120
2159
|
sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
@@ -2133,8 +2172,9 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
2133
2172
|
try {
|
|
2134
2173
|
sendJson(response, 200, {
|
|
2135
2174
|
servers: scanAllMcp(),
|
|
2136
|
-
//
|
|
2137
|
-
|
|
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)
|
|
2138
2178
|
});
|
|
2139
2179
|
} catch (error) {
|
|
2140
2180
|
sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
@@ -2162,9 +2202,8 @@ function mountCapabilitiesRoutes(host, config) {
|
|
|
2162
2202
|
const results = [];
|
|
2163
2203
|
for (const server of scanAllMcp()) {
|
|
2164
2204
|
if (!wanted.has(`${server.agent}/${server.name}`)) continue;
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
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" });
|
|
2168
2207
|
continue;
|
|
2169
2208
|
}
|
|
2170
2209
|
const input = {
|
|
@@ -2261,6 +2300,7 @@ function apply(ctx, config) {
|
|
|
2261
2300
|
ctx.effect(
|
|
2262
2301
|
() => mountCapabilitiesRoutes(hostCtx, {
|
|
2263
2302
|
profileDirPath: profileDir(profile),
|
|
2303
|
+
dshHomePath: dshHomeDir(),
|
|
2264
2304
|
remountProvider
|
|
2265
2305
|
}),
|
|
2266
2306
|
"dsh-plugin-capabilities: http routes"
|