@civilization/deepseek-harness-skill-mcp 0.1.7 → 0.1.9
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 +7 -3
- package/package.json +2 -1
- package/skills/extension-management/SKILL.md +78 -0
- package/src/index.js +27 -2
- package/src/web.js +66 -1
package/README.md
CHANGED
|
@@ -114,7 +114,11 @@ MCP 工具是提供给模型的工具 schema,不是用户 `/` 命令,因此
|
|
|
114
114
|
|
|
115
115
|
MCP 对端离线、命令启动失败或首次工具发现失败不会阻止 DSH 启动。stdio 与 Streamable HTTP 使用 Harness MCP client 的后台重连策略;旧版 SSE 当前依赖连接自身,在断线后可通过刷新配置重新加载。设置页在连接恢复并发现工具后更新状态。插件加载时也会把旧版本写入的严格启动配置迁移为非致命启动策略。
|
|
116
116
|
|
|
117
|
-
## Agent
|
|
117
|
+
## Agent 工具与 Skill
|
|
118
|
+
|
|
119
|
+
插件向 DSH 注册了 `extension-management` Skill 以及 7 个模型专属管理工具:
|
|
120
|
+
|
|
121
|
+
- **`extension-management` Skill**:引导模型掌握受管 Skill 路径与 stdio / streamable-http / sse 模式 MCP 服务器的标准配置结构与安全规约。
|
|
118
122
|
|
|
119
123
|
| 工具 | 作用 |
|
|
120
124
|
|---|---|
|
|
@@ -153,9 +157,9 @@ pnpm run web
|
|
|
153
157
|
|
|
154
158
|
## 验证
|
|
155
159
|
|
|
156
|
-
自动测试覆盖配置持久化、Skill 多路径、编辑和删除、单技能内容编辑与删除、用户分组、并发 revision、Skill 创建与调用权限、MCP
|
|
160
|
+
自动测试覆盖配置持久化、Skill 多路径、编辑和删除、单技能内容编辑与删除、用户分组、并发 revision、Skill 创建与调用权限、MCP 工具发现和卸载、轮询状态、中英文词典一致性,以及 DSH `webServer` 前缀路由、OPTIONS 预检和 RPC 信封协议桥接。测试不需要模型 API key。
|
|
157
161
|
|
|
158
|
-
浏览器端 `/` 候选缓存失效兼容逻辑会使用公开的 `connection/reset`
|
|
162
|
+
浏览器端 `/` 候选缓存失效兼容逻辑会使用公开的 `connection/reset` 客户端事件;服务端通过注入 `webServer` 与 `connection` 双重挂载 `/extensions` 路由并由 Cordis effect 托管生命周期,防止动态请求掉入前端静态资源的 405 fallback。自动测试验证 Host/RPC、HTTP 桥接和浏览器 bundle 构建,但没有在本仓库内启动完整 Harness Web 做端到端菜单点击验证。外部文件改动仍依赖 filesystem provider 先完成 watcher 失效,必要时可在变更后点击管理页“刷新”。
|
|
159
163
|
|
|
160
164
|
## 发布
|
|
161
165
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@civilization/deepseek-harness-skill-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Skill path and MCP server management for DeepSeek Harness Web",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"src",
|
|
26
26
|
"lib",
|
|
27
|
+
"skills",
|
|
27
28
|
"cordis.patch.yml",
|
|
28
29
|
"README.md",
|
|
29
30
|
"LICENSE"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: extension-management
|
|
3
|
+
description: Manage custom Skill directories and Model Context Protocol (MCP) server configurations (stdio, streamable-http, sse) in DeepSeek Harness.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Extension management (Skills & MCP)
|
|
7
|
+
|
|
8
|
+
The `dsh-skill-mcp` plugin manages custom Skill directories and external Model Context Protocol (MCP) servers in DeepSeek Harness (DSH).
|
|
9
|
+
|
|
10
|
+
When the user asks how to configure MCP servers or add custom Skill roots, describe the available tools and configuration schemas directly without probing the workspace.
|
|
11
|
+
|
|
12
|
+
## Core Management Tools
|
|
13
|
+
|
|
14
|
+
1. **`extensions_list`**:
|
|
15
|
+
- Lists all managed Skill sources and MCP configurations along with their enabled/disabled state and transport type.
|
|
16
|
+
- Note: An enabled state indicates authorization in configuration, not necessarily active connection health.
|
|
17
|
+
|
|
18
|
+
2. **`extensions_inspect`**:
|
|
19
|
+
- Inspects the actual Skill catalog currently snapshot for the caller and all visible MCP tools (`mcp__*`).
|
|
20
|
+
- Requires `cwd`. Zero discovered tools does not automatically mean connection failure (servers may still be discovering or starting).
|
|
21
|
+
|
|
22
|
+
3. **`extensions_add_skill`**:
|
|
23
|
+
- Adds an existing directory containing skill folders (each containing a `SKILL.md`).
|
|
24
|
+
- Parameters: `id` (unique internal identifier) and `directory` (absolute local path).
|
|
25
|
+
- Added sources are created disabled by default; call `extensions_set_enabled` to activate.
|
|
26
|
+
|
|
27
|
+
4. **`extensions_add_mcp`**:
|
|
28
|
+
- Adds a new MCP server configuration (created disabled by default).
|
|
29
|
+
- Parameters: `id` and `configuration` (JSON string).
|
|
30
|
+
- Supported transport configurations:
|
|
31
|
+
- **`stdio`**:
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"transport": "stdio",
|
|
35
|
+
"command": "node",
|
|
36
|
+
"args": ["D:/path/to/server.js"],
|
|
37
|
+
"cwd": "D:/path/to"
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
- **`streamable-http`**:
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"transport": "streamable-http",
|
|
44
|
+
"url": "http://127.0.0.1:9000/mcp"
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
- **`sse`** (legacy SSE transport):
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"transport": "sse",
|
|
51
|
+
"url": "http://127.0.0.1:9000/sse"
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
- **Security rules**:
|
|
55
|
+
- Never supply plain-text API secrets, passwords, or authentication tokens in parameters or URLs.
|
|
56
|
+
- URLs with user credentials or access tokens are strictly rejected.
|
|
57
|
+
|
|
58
|
+
5. **`extensions_set_enabled`**:
|
|
59
|
+
- Toggles an extension (`id` and `enabled: true|false`).
|
|
60
|
+
- Enabling a stdio server authorizes its executable to run when the DSH profile reloads.
|
|
61
|
+
|
|
62
|
+
6. **`extensions_update_skill`** & **`extensions_update_mcp`**:
|
|
63
|
+
- Modifies existing Skill directory paths or updates MCP connection configurations while preserving the existing ID and enabled status.
|
|
64
|
+
|
|
65
|
+
7. **`extensions_remove`**:
|
|
66
|
+
- Removes a managed configuration. Removing a Skill source removes its registration in DSH; it does NOT delete any files from the disk.
|
|
67
|
+
|
|
68
|
+
## Recommended Workflows
|
|
69
|
+
|
|
70
|
+
- **Adding a new MCP server**:
|
|
71
|
+
1. Call `extensions_list` to check existing IDs and prevent duplicate names.
|
|
72
|
+
2. Call `extensions_add_mcp` with a descriptive ID and valid JSON configuration.
|
|
73
|
+
3. Call `extensions_set_enabled` with `enabled: true`.
|
|
74
|
+
4. Inform the user that the configuration has been saved to the profile patch and tools will become available upon reload.
|
|
75
|
+
- **Mounting custom skills**:
|
|
76
|
+
1. Verify the target directory contains valid Skill folders with `SKILL.md`.
|
|
77
|
+
2. Call `extensions_add_skill` with an explicit ID.
|
|
78
|
+
3. Call `extensions_set_enabled` to activate.
|
package/src/index.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import Schema from '@deepseek-ai/schemastery'
|
|
3
3
|
import { homedir } from 'node:os'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
|
+
import { readFile } from 'node:fs/promises'
|
|
5
6
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
6
7
|
import { ExtensionStore } from './store.js'
|
|
7
|
-
import { createWebHandler } from './web.js'
|
|
8
|
+
import { createWebHandler, createWebHttpHandler } from './web.js'
|
|
8
9
|
|
|
9
10
|
export const name = 'extension-manager'
|
|
10
11
|
export const inject = ['tools', 'skills']
|
|
@@ -18,11 +19,35 @@ const output = {
|
|
|
18
19
|
|
|
19
20
|
/** Register management operations. MCP processes are started only by the profile loader. */
|
|
20
21
|
export async function apply(ctx, config) {
|
|
22
|
+
try {
|
|
23
|
+
const skillPath = new URL('../skills/extension-management/SKILL.md', import.meta.url)
|
|
24
|
+
const skillText = await readFile(skillPath, 'utf8')
|
|
25
|
+
const content = skillText.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '').trim()
|
|
26
|
+
ctx.effect(() => ctx.skills?.register({
|
|
27
|
+
name: 'extension-management',
|
|
28
|
+
description: 'Manage custom Skill directories and Model Context Protocol (MCP) server configurations.',
|
|
29
|
+
whenToUse: 'Use when configuring, inspecting, adding, or modifying external skills and MCP servers.',
|
|
30
|
+
source: 'bundled',
|
|
31
|
+
content,
|
|
32
|
+
}))
|
|
33
|
+
} catch {}
|
|
34
|
+
|
|
21
35
|
const home = process.env.DSH_HOME || join(homedir(), '.dsh')
|
|
22
36
|
const store = new ExtensionStore(config.patchPath || join(home, 'profiles', 'web', 'cordis.patch.yml'))
|
|
23
37
|
await store.ensureNonFatalMcpStartup()
|
|
38
|
+
|
|
39
|
+
const webHandler = createWebHandler(store, ctx)
|
|
40
|
+
const httpHandler = createWebHttpHandler(store, ctx)
|
|
41
|
+
|
|
24
42
|
ctx.inject(['connection'], web => {
|
|
25
|
-
web.connection.rpc.handle('/extensions',
|
|
43
|
+
web.connection.rpc.handle('/extensions', webHandler)
|
|
44
|
+
})
|
|
45
|
+
ctx.inject(['webServer'], hostCtx => {
|
|
46
|
+
hostCtx.effect(() => hostCtx.webServer.register({
|
|
47
|
+
kind: 'prefix',
|
|
48
|
+
path: '/extensions',
|
|
49
|
+
handler: httpHandler,
|
|
50
|
+
}), 'extension-manager: /extensions route')
|
|
26
51
|
})
|
|
27
52
|
const register = (name, description, parameters, execute) => ctx.tools.register(defineTool({ name, description, parameters, output, execute }))
|
|
28
53
|
|
package/src/web.js
CHANGED
|
@@ -61,7 +61,7 @@ export function createWebHandler(store, ctx) {
|
|
|
61
61
|
try {
|
|
62
62
|
if (!Object.hasOwn(schemas, endpoint)) throw new Error('Unknown operation.')
|
|
63
63
|
const args = schemas[endpoint](payload)
|
|
64
|
-
signal
|
|
64
|
+
signal?.throwIfAborted()
|
|
65
65
|
if (endpoint === 'add-skill') await store.addSkillPath(args.directory, signal, args.revision)
|
|
66
66
|
if (endpoint === 'add-mcp') {
|
|
67
67
|
const input = JSON.parse(args.configuration)
|
|
@@ -97,3 +97,68 @@ export function createWebHandler(store, ctx) {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
+
|
|
101
|
+
/** HTTP server route handler that satisfies DSH WebServer and RPC envelope protocols. */
|
|
102
|
+
export function createWebHttpHandler(store, ctx) {
|
|
103
|
+
const handler = createWebHandler(store, ctx)
|
|
104
|
+
return async (req, res) => {
|
|
105
|
+
if (req.method === 'OPTIONS') {
|
|
106
|
+
res.writeHead(204, {
|
|
107
|
+
'access-control-allow-origin': '*',
|
|
108
|
+
'access-control-allow-methods': 'POST, OPTIONS',
|
|
109
|
+
'access-control-allow-headers': 'content-type, *',
|
|
110
|
+
})
|
|
111
|
+
res.end()
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
if (req.method !== 'POST') {
|
|
115
|
+
res.writeHead(405, { 'content-type': 'application/json; charset=utf-8' })
|
|
116
|
+
res.end(JSON.stringify({ ok: false, error: { message: 'Method Not Allowed' } }))
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
const pathname = new URL(req.url ?? '/', 'http://dsh.internal').pathname
|
|
120
|
+
const endpoint = pathname.startsWith('/extensions/') ? pathname.slice('/extensions/'.length)
|
|
121
|
+
: pathname === '/extensions' ? 'list' : undefined
|
|
122
|
+
if (!endpoint) {
|
|
123
|
+
res.writeHead(404, { 'content-type': 'application/json; charset=utf-8' })
|
|
124
|
+
res.end(JSON.stringify({ ok: false, error: { message: 'Not Found' } }))
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let raw = ''
|
|
129
|
+
try {
|
|
130
|
+
for await (const chunk of req) raw += chunk.toString('utf8')
|
|
131
|
+
} catch {
|
|
132
|
+
res.writeHead(400, { 'content-type': 'application/json; charset=utf-8' })
|
|
133
|
+
res.end(JSON.stringify({ ok: false, error: { message: 'Failed to read request body.' } }))
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let body = {}
|
|
138
|
+
if (raw.trim()) {
|
|
139
|
+
try { body = JSON.parse(raw) }
|
|
140
|
+
catch {
|
|
141
|
+
res.writeHead(400, { 'content-type': 'application/json; charset=utf-8' })
|
|
142
|
+
res.end(JSON.stringify({ ok: false, error: { message: 'Invalid JSON body.' } }))
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const isRpcEnvelope = body && body.type === 'client-request' && typeof body.rpcId === 'string'
|
|
148
|
+
const actualEndpoint = isRpcEnvelope ? (body.method || endpoint) : endpoint
|
|
149
|
+
const payload = isRpcEnvelope ? (body.payload ?? {}) : body
|
|
150
|
+
|
|
151
|
+
const result = await handler(actualEndpoint, payload, req.signal)
|
|
152
|
+
|
|
153
|
+
const responseBody = isRpcEnvelope
|
|
154
|
+
? { type: 'server-response', rpcId: body.rpcId, result }
|
|
155
|
+
: result
|
|
156
|
+
|
|
157
|
+
const data = JSON.stringify(responseBody)
|
|
158
|
+
res.writeHead(200, {
|
|
159
|
+
'content-type': 'application/json; charset=utf-8',
|
|
160
|
+
'content-length': Buffer.byteLength(data),
|
|
161
|
+
})
|
|
162
|
+
res.end(data)
|
|
163
|
+
}
|
|
164
|
+
}
|