@max-null/dsh-skill-mcp-center 0.5.0 → 0.5.1

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/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 result = await ctx.connection.rpc.call("/skill-mcp", endpoint, payload);
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` — a private loopback RPC channel exposing the engine to the
3
- * browser half (same seam the plugin center uses; the Typert Remote path is
4
- * closed to third parties).
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` — a private loopback RPC channel exposing the engine to the
3
- * browser half (same seam the plugin center uses; the Typert Remote path is
4
- * closed to third parties).
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
- const CHANNEL = '/skill-mcp';
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', 'connection'];
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.rpc.handle(CHANNEL, async (endpoint, payload) => {
16
- try {
17
- const p = (payload ?? {});
18
- switch (endpoint) {
19
- case 'listSkills': {
20
- const cwd = p.cwd;
21
- return { ok: true, value: await ctx.skillMcp.listSkills(typeof cwd === 'string' ? cwd : undefined) };
22
- }
23
- case 'toggleSkill': {
24
- const path = p.path;
25
- if (typeof path !== 'string' || path === '')
26
- return internal('toggleSkill: path is required');
27
- return { ok: true, value: await ctx.skillMcp.toggleSkill(path) };
28
- }
29
- case 'readSkill': {
30
- const path = p.path;
31
- if (typeof path !== 'string' || path === '')
32
- return internal('readSkill: path is required');
33
- const cwd = p.cwd;
34
- return { ok: true, value: await ctx.skillMcp.readSkill(path, typeof cwd === 'string' ? cwd : undefined) };
35
- }
36
- case 'listMcpServers':
37
- return { ok: true, value: await ctx.skillMcp.listMcpServers() };
38
- case 'createMcpServer':
39
- return { ok: true, value: await ctx.skillMcp.createMcpServer(p.config) };
40
- case 'updateMcpServer':
41
- return { ok: true, value: await ctx.skillMcp.updateMcpServer(String(p.id), p.config) };
42
- case 'removeMcpServer':
43
- return { ok: true, value: await ctx.skillMcp.removeMcpServer(String(p.id)) };
44
- case 'setMcpServerEnabled':
45
- return { ok: true, value: await ctx.skillMcp.setMcpServerEnabled(String(p.id), p.enabled === true) };
46
- case 'mcpStatus':
47
- return { ok: true, value: await ctx.skillMcp.mcpStatus() };
48
- default:
49
- return internal(`unknown endpoint "${endpoint}"`);
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
- catch (error) {
53
- return internal(error instanceof Error ? error.message : String(error));
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
- }, { authority: 'loopback' });
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.0",
3
+ "version": "0.5.1",
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", "docs/shots"],
24
+ "cordis.patch.yml",
25
+ "docs/shots",
26
+ "release-notes.md"
27
+ ],
25
28
  "dsh": {
26
29
  "bundle": {
27
30
  "patch": "./cordis.patch.yml"
@@ -0,0 +1,35 @@
1
+ # Release Notes — @max-null/dsh-skill-mcp-center
2
+
3
+ ## 0.5.1 (2026-09-17)
4
+
5
+ ### 修复
6
+
7
+ - 修正 web 环境下设置面板的「加载失败:transport failure for /skill-mcp/listSkills: HTTP 405」。
8
+ 根因在宿主侧:`ctx.connection.rpc.handle()` 注册逻辑通道时会访问 `client-connection`
9
+ 未声明的 `webServer` 注入,cordis 属性代理抛错后注册静默失败(DSH 源码形态特有;
10
+ 打包内核形态不受影响)。上游报告:deepseek-ai/deepseek-harness discussions #6880。
11
+
12
+ ### 变更
13
+
14
+ - 传输层改为双路径自适应:
15
+ - 宿主提供 `connection.fetch.register` → 注册 `POST /api/skill-mcp` 精确路由
16
+ (只写 Connection 自己的路由表,不触碰 `owner.webServer`);
17
+ - 宿主只提供逻辑通道注册表(老打包内核)→ 自动回退 `/skill-mcp`;
18
+ - 浏览器侧先试新路由,**收到 404 才回退**(新路由未注册时 `/api/*` 是 404,根路径才是 405)。
19
+ - `connection` 服务改用 `ctx.inject(['connection'], cb)` 获取:它在插件 apply 时尚未就绪,
20
+ 且 `ctx.plugin(P)` 不等待依赖。
21
+
22
+ ### 兼容性
23
+
24
+ - 无 API 变更:端点集合、请求体、响应信封(RpcResult)与 peer 依赖均未变。
25
+ - 面板端与宿主端需同版本升级(浏览器侧的传输策略随之切换)。
26
+
27
+ ### 测试
28
+
29
+ - 新增 `tests/rpc-transport.test.ts`(8 条):传输层选择、服务晚到时不抢先注册、
30
+ 路由派发、JSON 解析失败与引擎异常收敛为 `internal` 信封。
31
+ - 全量 18 条通过;`npm run typecheck` 无错。
32
+
33
+ ### 验证
34
+
35
+ - web(源码形态)实测:`POST /api/skill-mcp` → 200,面板恢复,控制台 0 errors。