@max-null/dsh-skill-mcp-center 0.5.0 → 0.5.2
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 +1 -1
- package/client.js +12 -1
- package/dist/rpc.d.ts +35 -3
- package/dist/rpc.js +109 -44
- package/package.json +13 -10
- package/release-notes.md +57 -0
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Skill 与 MCP 管理中心:在设置里管理 skills 与 MCP 服务器,右
|
|
|
10
10
|
|
|
11
11
|
## Features / 功能
|
|
12
12
|
|
|
13
|
-
- **Skill management / Skill 管理** — browse every skill by
|
|
13
|
+
- **Skill management / Skill 管理** — browse every skill grouped by source (Global / Workspace / Built-in DSH official, plus one group per plugin that ships skills), toggle model invocation via the `disable-model-invocation` frontmatter (disk-backed skills only). Covers skills shipped **inside plugin packages** (e.g. `@max-null/dsh-skills`), not just the user-level roots — the host-level skill filesystem is disabled in web-app, so a plugin's own `skills/` directory would otherwise be invisible while its skills are loaded and in effect.
|
|
14
14
|
- **MCP management / MCP 管理** — add / edit / remove `mcp-client` servers, enable/disable without deleting config, all **hot-applied** through `ctx.loader` (no restart).
|
|
15
15
|
- **Live status / 实时状态** — a sidebar "MCP" tab (via `dsh-better-sidebar`) showing per-server connection state; **click a server row to expand its tools' names and descriptions** (collapsed by default — a server can expose dozens of them). Names come from `tools.schemas()`; the redundant `mcp__<server>__` prefix is hidden while the full name stays on hover. Polled while visible and following the session.
|
|
16
16
|
- **Skin-compatible / 皮肤兼容** — every color uses `var(--dsw-*)` tokens.
|
package/client.js
CHANGED
|
@@ -924,7 +924,18 @@ var inject = ["slots", "connection", "locale"];
|
|
|
924
924
|
function apply(ctx) {
|
|
925
925
|
injectCss();
|
|
926
926
|
rpc = async (endpoint, payload = {}) => {
|
|
927
|
-
const
|
|
927
|
+
const response = await fetch("/api/skill-mcp", {
|
|
928
|
+
method: "POST",
|
|
929
|
+
headers: { "content-type": "application/json" },
|
|
930
|
+
body: JSON.stringify({ endpoint, payload })
|
|
931
|
+
});
|
|
932
|
+
if (response.status === 404) {
|
|
933
|
+
const legacy = await ctx.connection.rpc.call("/skill-mcp", endpoint, payload);
|
|
934
|
+
if (legacy.ok) return legacy.value;
|
|
935
|
+
throw new Error(legacy.error?.message ?? `skill-mcp-center: ${endpoint} failed`);
|
|
936
|
+
}
|
|
937
|
+
if (!response.ok) throw new Error(`skill-mcp-center: ${endpoint} failed (HTTP ${response.status})`);
|
|
938
|
+
const result = await response.json();
|
|
928
939
|
if (result.ok) return result.value;
|
|
929
940
|
throw new Error(result.error?.message ?? `skill-mcp-center: ${endpoint} failed`);
|
|
930
941
|
};
|
package/dist/rpc.d.ts
CHANGED
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `SkillMcpRpc` —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* `SkillMcpRpc` — the host half of the browser's private RPC entry point.
|
|
3
|
+
*
|
|
4
|
+
* Transport (2026-09-17). Two host shapes are supported because the two
|
|
5
|
+
* compositions this plugin runs under differ:
|
|
6
|
+
*
|
|
7
|
+
* - **Newer hosts (0.1.5 line, e.g. the web source build)** expose
|
|
8
|
+
* `connection.fetch.register(...)`. This route is used there because
|
|
9
|
+
* `connection.rpc.handle('/skill-mcp', …)` ends at `owner.webServer.register()`
|
|
10
|
+
* inside Connection (`client-connection/src/rpc-host.ts:179`) and the owning
|
|
11
|
+
* fiber declares no `webServer` injection, so it throws
|
|
12
|
+
* `cannot get property "webServer" without inject`; every channel registered
|
|
13
|
+
* that way fails and the browser sees `HTTP 405` from the static fallback.
|
|
14
|
+
* `fetch.register` only writes Connection's own route map, so one exact route
|
|
15
|
+
* under the shared `/api` channel serves the same endpoints.
|
|
16
|
+
*
|
|
17
|
+
* - **Older hosts (e.g. the packaged SSiD kernel)** expose only the logical
|
|
18
|
+
* channel registry, where `rpc.handle` works, so it stays as the fallback.
|
|
19
|
+
*
|
|
20
|
+
* `connection` is resolved with `ctx.inject` rather than a declared dependency:
|
|
21
|
+
* it activates after this plugin, and `ctx.plugin()` does not wait for
|
|
22
|
+
* dependencies.
|
|
5
23
|
*/
|
|
6
24
|
import { Service, type Context } from '@deepseek-ai/cordis';
|
|
25
|
+
/** Exact Fetch route serving this plugin's endpoints under the shared channel. */
|
|
26
|
+
export declare const ROUTE = "/api/skill-mcp";
|
|
27
|
+
/** Logical channel used on hosts without exact Fetch-route registration. */
|
|
28
|
+
export declare const LEGACY_CHANNEL = "/skill-mcp";
|
|
29
|
+
/** Body the browser half posts to {@link ROUTE}. */
|
|
30
|
+
export interface SkillMcpRequest {
|
|
31
|
+
/** Endpoint name, e.g. `listSkills`. */
|
|
32
|
+
readonly endpoint: string;
|
|
33
|
+
/** Endpoint arguments. */
|
|
34
|
+
readonly payload?: unknown;
|
|
35
|
+
}
|
|
7
36
|
export declare class SkillMcpRpc extends Service {
|
|
8
37
|
static inject: string[];
|
|
38
|
+
/**
|
|
39
|
+
* @param ctx - plugin context carrying the skill/MCP engine.
|
|
40
|
+
*/
|
|
9
41
|
constructor(ctx: Context);
|
|
10
42
|
}
|
|
11
43
|
export default SkillMcpRpc;
|
package/dist/rpc.js
CHANGED
|
@@ -1,58 +1,123 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `SkillMcpRpc` —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* `SkillMcpRpc` — the host half of the browser's private RPC entry point.
|
|
3
|
+
*
|
|
4
|
+
* Transport (2026-09-17). Two host shapes are supported because the two
|
|
5
|
+
* compositions this plugin runs under differ:
|
|
6
|
+
*
|
|
7
|
+
* - **Newer hosts (0.1.5 line, e.g. the web source build)** expose
|
|
8
|
+
* `connection.fetch.register(...)`. This route is used there because
|
|
9
|
+
* `connection.rpc.handle('/skill-mcp', …)` ends at `owner.webServer.register()`
|
|
10
|
+
* inside Connection (`client-connection/src/rpc-host.ts:179`) and the owning
|
|
11
|
+
* fiber declares no `webServer` injection, so it throws
|
|
12
|
+
* `cannot get property "webServer" without inject`; every channel registered
|
|
13
|
+
* that way fails and the browser sees `HTTP 405` from the static fallback.
|
|
14
|
+
* `fetch.register` only writes Connection's own route map, so one exact route
|
|
15
|
+
* under the shared `/api` channel serves the same endpoints.
|
|
16
|
+
*
|
|
17
|
+
* - **Older hosts (e.g. the packaged SSiD kernel)** expose only the logical
|
|
18
|
+
* channel registry, where `rpc.handle` works, so it stays as the fallback.
|
|
19
|
+
*
|
|
20
|
+
* `connection` is resolved with `ctx.inject` rather than a declared dependency:
|
|
21
|
+
* it activates after this plugin, and `ctx.plugin()` does not wait for
|
|
22
|
+
* dependencies.
|
|
5
23
|
*/
|
|
6
24
|
import { Service } from '@deepseek-ai/cordis';
|
|
7
|
-
|
|
25
|
+
/** Exact Fetch route serving this plugin's endpoints under the shared channel. */
|
|
26
|
+
export const ROUTE = '/api/skill-mcp';
|
|
27
|
+
/** Logical channel used on hosts without exact Fetch-route registration. */
|
|
28
|
+
export const LEGACY_CHANNEL = '/skill-mcp';
|
|
8
29
|
function internal(message) {
|
|
9
30
|
return { ok: false, error: { code: 'internal', message, details: {} } };
|
|
10
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Wrap one endpoint result as a JSON response.
|
|
34
|
+
* @param value - RPC result envelope.
|
|
35
|
+
* @returns response carrying the envelope.
|
|
36
|
+
*/
|
|
37
|
+
function jsonResponse(value) {
|
|
38
|
+
return new Response(JSON.stringify(value), { headers: { 'content-type': 'application/json' } });
|
|
39
|
+
}
|
|
11
40
|
export class SkillMcpRpc extends Service {
|
|
12
|
-
static inject = ['skillMcp'
|
|
41
|
+
static inject = ['skillMcp'];
|
|
42
|
+
/**
|
|
43
|
+
* @param ctx - plugin context carrying the skill/MCP engine.
|
|
44
|
+
*/
|
|
13
45
|
constructor(ctx) {
|
|
14
46
|
super(ctx, 'skillMcpRpc');
|
|
15
|
-
ctx.connection
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
ctx.inject(['connection'], (connectionCtx) => {
|
|
48
|
+
// Both reads are attempted: the published peer types predate the
|
|
49
|
+
// `connection` declaration on Context, so neither is typed here.
|
|
50
|
+
const surface = connectionCtx;
|
|
51
|
+
const connection = surface.connection ?? surface.get?.('connection');
|
|
52
|
+
if (connection === undefined)
|
|
53
|
+
return;
|
|
54
|
+
if (typeof connection.fetch?.register === 'function') {
|
|
55
|
+
connection.fetch.register({
|
|
56
|
+
path: ROUTE,
|
|
57
|
+
methods: ['POST'],
|
|
58
|
+
requestBody: 'buffered',
|
|
59
|
+
fetch: async (request) => {
|
|
60
|
+
let body;
|
|
61
|
+
try {
|
|
62
|
+
body = await request.json();
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return jsonResponse(internal('request body must be JSON'));
|
|
66
|
+
}
|
|
67
|
+
return jsonResponse(await dispatch(ctx, body));
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
connection.rpc.handle(LEGACY_CHANNEL, (endpoint, payload) => dispatch(ctx, { endpoint, payload }));
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Run one endpoint against the engine.
|
|
78
|
+
* @param ctx - plugin context carrying the skill/MCP engine.
|
|
79
|
+
* @param body - decoded request body.
|
|
80
|
+
* @returns the endpoint result, or an `internal` failure.
|
|
81
|
+
*/
|
|
82
|
+
async function dispatch(ctx, body) {
|
|
83
|
+
try {
|
|
84
|
+
const p = (body.payload ?? {});
|
|
85
|
+
switch (body.endpoint) {
|
|
86
|
+
case 'listSkills': {
|
|
87
|
+
const cwd = p.cwd;
|
|
88
|
+
return { ok: true, value: await ctx.skillMcp.listSkills(typeof cwd === 'string' ? cwd : undefined) };
|
|
51
89
|
}
|
|
52
|
-
|
|
53
|
-
|
|
90
|
+
case 'toggleSkill': {
|
|
91
|
+
const path = p.path;
|
|
92
|
+
if (typeof path !== 'string' || path === '')
|
|
93
|
+
return internal('toggleSkill: path is required');
|
|
94
|
+
return { ok: true, value: await ctx.skillMcp.toggleSkill(path) };
|
|
54
95
|
}
|
|
55
|
-
|
|
96
|
+
case 'readSkill': {
|
|
97
|
+
const path = p.path;
|
|
98
|
+
if (typeof path !== 'string' || path === '')
|
|
99
|
+
return internal('readSkill: path is required');
|
|
100
|
+
const cwd = p.cwd;
|
|
101
|
+
return { ok: true, value: await ctx.skillMcp.readSkill(path, typeof cwd === 'string' ? cwd : undefined) };
|
|
102
|
+
}
|
|
103
|
+
case 'listMcpServers':
|
|
104
|
+
return { ok: true, value: await ctx.skillMcp.listMcpServers() };
|
|
105
|
+
case 'createMcpServer':
|
|
106
|
+
return { ok: true, value: await ctx.skillMcp.createMcpServer(p.config) };
|
|
107
|
+
case 'updateMcpServer':
|
|
108
|
+
return { ok: true, value: await ctx.skillMcp.updateMcpServer(String(p.id), p.config) };
|
|
109
|
+
case 'removeMcpServer':
|
|
110
|
+
return { ok: true, value: await ctx.skillMcp.removeMcpServer(String(p.id)) };
|
|
111
|
+
case 'setMcpServerEnabled':
|
|
112
|
+
return { ok: true, value: await ctx.skillMcp.setMcpServerEnabled(String(p.id), p.enabled === true) };
|
|
113
|
+
case 'mcpStatus':
|
|
114
|
+
return { ok: true, value: await ctx.skillMcp.mcpStatus() };
|
|
115
|
+
default:
|
|
116
|
+
return internal(`unknown endpoint "${body.endpoint}"`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
return internal(error instanceof Error ? error.message : String(error));
|
|
56
121
|
}
|
|
57
122
|
}
|
|
58
123
|
export default SkillMcpRpc;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@max-null/dsh-skill-mcp-center",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Skill & MCP management center for DeepSeek Harness — manage skills and MCP servers in Settings, live MCP status in the sidebar",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"dist",
|
|
23
23
|
"client.js",
|
|
24
|
-
"cordis.patch.yml",
|
|
24
|
+
"cordis.patch.yml",
|
|
25
|
+
"docs/shots",
|
|
26
|
+
"release-notes.md"
|
|
27
|
+
],
|
|
25
28
|
"dsh": {
|
|
26
29
|
"bundle": {
|
|
27
30
|
"patch": "./cordis.patch.yml"
|
|
@@ -64,18 +67,18 @@
|
|
|
64
67
|
"peerDependencies": {
|
|
65
68
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
66
69
|
"@deepseek-ai/cordis-plugin-loader": "^1.0.2",
|
|
67
|
-
"@deepseek-ai/dsh-client-connection": "
|
|
68
|
-
"@deepseek-ai/dsh-host-apiproxy": "
|
|
69
|
-
"@deepseek-ai/dsh-skill": "
|
|
70
|
-
"@deepseek-ai/dsh-tools": "
|
|
70
|
+
"@deepseek-ai/dsh-client-connection": ">=0.1.1-rc.1 <0.3.0",
|
|
71
|
+
"@deepseek-ai/dsh-host-apiproxy": ">=0.1.1-rc.1 <0.3.0",
|
|
72
|
+
"@deepseek-ai/dsh-skill": ">=0.1.1-rc.1 <0.3.0",
|
|
73
|
+
"@deepseek-ai/dsh-tools": ">=0.1.1-rc.1 <0.3.0"
|
|
71
74
|
},
|
|
72
75
|
"devDependencies": {
|
|
73
76
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
74
77
|
"@deepseek-ai/cordis-plugin-loader": "^1.0.2",
|
|
75
|
-
"@deepseek-ai/dsh-client-connection": "
|
|
76
|
-
"@deepseek-ai/dsh-host-apiproxy": "
|
|
77
|
-
"@deepseek-ai/dsh-skill": "
|
|
78
|
-
"@deepseek-ai/dsh-tools": "
|
|
78
|
+
"@deepseek-ai/dsh-client-connection": ">=0.1.1-rc.1 <0.3.0",
|
|
79
|
+
"@deepseek-ai/dsh-host-apiproxy": ">=0.1.1-rc.1 <0.3.0",
|
|
80
|
+
"@deepseek-ai/dsh-skill": ">=0.1.1-rc.1 <0.3.0",
|
|
81
|
+
"@deepseek-ai/dsh-tools": ">=0.1.1-rc.1 <0.3.0",
|
|
79
82
|
"@types/js-yaml": "^4.0.9",
|
|
80
83
|
"@types/node": "^26.2.0",
|
|
81
84
|
"@types/react": "~18.3.1",
|
package/release-notes.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Release Notes — @max-null/dsh-skill-mcp-center
|
|
2
|
+
|
|
3
|
+
## 0.5.2 (2026-09-29)
|
|
4
|
+
|
|
5
|
+
### 变更
|
|
6
|
+
|
|
7
|
+
- 放宽 `@deepseek-ai/dsh-*` 的版本范围:`peerDependencies` 与 `devDependencies` 中的
|
|
8
|
+
`dsh-client-connection`、`dsh-host-apiproxy`、`dsh-skill`、`dsh-tools` 由 `^0.1.1-rc.1`
|
|
9
|
+
改为 `>=0.1.1-rc.1 <0.3.0` —— 保留下限、只放宽上界,旧内核(0.1.7-rc.2)仍然满足。
|
|
10
|
+
|
|
11
|
+
### 为什么
|
|
12
|
+
|
|
13
|
+
- 内核侧逐个检查插件 `peerDependencies` 里的每个 `@deepseek-ai/*` 声明,判据是标准 semver:
|
|
14
|
+
`semver.satisfies(runtimeVersion, requirement, { includePrerelease: true })`。
|
|
15
|
+
- `^0.1.1-rc.1` 展开为 `>=0.1.1-rc.1 <0.2.0-0`,对 `0.2.0-rc.1` 判 **false**;判定失败的插件
|
|
16
|
+
会被**静默跳过**——不进 fiber graph,也不出现在「did not activate」列表里,用户侧的表现
|
|
17
|
+
是「插件装了却毫无反应」,没有任何日志可查。
|
|
18
|
+
- `>=0.1.1-rc.1 <0.3.0` 在 semver 7.7.4 下对 `0.1.7-rc.2` 与 `0.2.0-rc.1` 均判 true(已实测)。
|
|
19
|
+
|
|
20
|
+
### 兼容性
|
|
21
|
+
|
|
22
|
+
- 无运行时行为变更:端点集合、请求体、响应信封与代码路径均未改,只是依赖声明放宽。
|
|
23
|
+
- 面板端与宿主端仍需同版本(传输策略见 0.5.1)。
|
|
24
|
+
|
|
25
|
+
## 0.5.1 (2026-09-17)
|
|
26
|
+
|
|
27
|
+
### 修复
|
|
28
|
+
|
|
29
|
+
- 修正 web 环境下设置面板的「加载失败:transport failure for /skill-mcp/listSkills: HTTP 405」。
|
|
30
|
+
根因在宿主侧:`ctx.connection.rpc.handle()` 注册逻辑通道时会访问 `client-connection`
|
|
31
|
+
未声明的 `webServer` 注入,cordis 属性代理抛错后注册静默失败(DSH 源码形态特有;
|
|
32
|
+
打包内核形态不受影响)。上游报告:deepseek-ai/deepseek-harness discussions #6880。
|
|
33
|
+
|
|
34
|
+
### 变更
|
|
35
|
+
|
|
36
|
+
- 传输层改为双路径自适应:
|
|
37
|
+
- 宿主提供 `connection.fetch.register` → 注册 `POST /api/skill-mcp` 精确路由
|
|
38
|
+
(只写 Connection 自己的路由表,不触碰 `owner.webServer`);
|
|
39
|
+
- 宿主只提供逻辑通道注册表(老打包内核)→ 自动回退 `/skill-mcp`;
|
|
40
|
+
- 浏览器侧先试新路由,**收到 404 才回退**(新路由未注册时 `/api/*` 是 404,根路径才是 405)。
|
|
41
|
+
- `connection` 服务改用 `ctx.inject(['connection'], cb)` 获取:它在插件 apply 时尚未就绪,
|
|
42
|
+
且 `ctx.plugin(P)` 不等待依赖。
|
|
43
|
+
|
|
44
|
+
### 兼容性
|
|
45
|
+
|
|
46
|
+
- 无 API 变更:端点集合、请求体、响应信封(RpcResult)与 peer 依赖均未变。
|
|
47
|
+
- 面板端与宿主端需同版本升级(浏览器侧的传输策略随之切换)。
|
|
48
|
+
|
|
49
|
+
### 测试
|
|
50
|
+
|
|
51
|
+
- 新增 `tests/rpc-transport.test.ts`(8 条):传输层选择、服务晚到时不抢先注册、
|
|
52
|
+
路由派发、JSON 解析失败与引擎异常收敛为 `internal` 信封。
|
|
53
|
+
- 全量 18 条通过;`npm run typecheck` 无错。
|
|
54
|
+
|
|
55
|
+
### 验证
|
|
56
|
+
|
|
57
|
+
- web(源码形态)实测:`POST /api/skill-mcp` → 200,面板恢复,控制台 0 errors。
|