opencode-skills-doctor 0.3.4 → 0.3.6

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 CHANGED
@@ -155,6 +155,32 @@ It registers the same `skills_doctor` tool (1.x has no `/skills-doctor` command
155
155
 
156
156
  > The package root entry is the V2 plugin and requires OpenCode 2.x. The CLI works on both lines.
157
157
 
158
+ ## MCP server (Claude Code, Codex, Kimi Code, any MCP client)
159
+
160
+ `skills-doctor mcp` speaks the Model Context Protocol over stdio and exposes one tool: `skills_doctor` with arguments `directory?`, `budget?`, `lang?` and `global?`. It does not need OpenCode installed.
161
+
162
+ Claude Code (`.mcp.json` in the project, or your MCP config):
163
+
164
+ ```json
165
+ {
166
+ "mcpServers": {
167
+ "skills-doctor": { "command": "npx", "args": ["-y", "opencode-skills-doctor", "mcp"] }
168
+ }
169
+ }
170
+ ```
171
+
172
+ Codex (`~/.codex/config.toml`):
173
+
174
+ ```toml
175
+ [mcp_servers.skills-doctor]
176
+ command = "npx"
177
+ args = ["-y", "opencode-skills-doctor", "mcp"]
178
+ ```
179
+
180
+ Any other MCP client: run `npx -y opencode-skills-doctor mcp` and allow the `skills_doctor` tool.
181
+
182
+ It scans `.opencode/skills`, `.claude/skills` and `.agents/skills` (project and global), so it can audit skills used by other agents too.
183
+
158
184
  ## Rules
159
185
 
160
186
  | Rule | Level | Meaning |
package/README.zh-CN.md CHANGED
@@ -155,6 +155,32 @@ export { SkillsDoctorPlugin } from "opencode-skills-doctor/v1"
155
155
 
156
156
  > 包根入口是 V2 插件,需要 OpenCode 2.x;CLI 两条线都能用。
157
157
 
158
+ ## MCP server(Claude Code、Codex、Kimi Code、任何 MCP 客户端)
159
+
160
+ `skills-doctor mcp` 用 stdio 讲 Model Context Protocol,只暴露一个工具:`skills_doctor`,参数 `directory?`、`budget?`、`lang?`、`global?`。不需要装 OpenCode。
161
+
162
+ Claude Code(项目里的 `.mcp.json`,或你的 MCP 配置):
163
+
164
+ ```json
165
+ {
166
+ "mcpServers": {
167
+ "skills-doctor": { "command": "npx", "args": ["-y", "opencode-skills-doctor", "mcp"] }
168
+ }
169
+ }
170
+ ```
171
+
172
+ Codex(`~/.codex/config.toml`):
173
+
174
+ ```toml
175
+ [mcp_servers.skills-doctor]
176
+ command = "npx"
177
+ args = ["-y", "opencode-skills-doctor", "mcp"]
178
+ ```
179
+
180
+ 其他 MCP 客户端:启动 `npx -y opencode-skills-doctor mcp` 并允许 `skills_doctor` 工具即可。
181
+
182
+ 它扫描 `.opencode/skills`、`.claude/skills`、`.agents/skills`(项目和全局),所以其他 agent 用的 skill 也能一起体检。
183
+
158
184
  ## 检测规则
159
185
 
160
186
  | 规则 | 级别 | 含义 |
package/package.json CHANGED
@@ -1,53 +1,55 @@
1
- {
2
- "name": "opencode-skills-doctor",
3
- "version": "0.3.4",
4
- "description": "Audit, fix and update OpenCode skills: measure per-step context cost, catch shadowed duplicates, archive safely and sync from catalogs. Works on OpenCode 1.x and 2.x.",
5
- "type": "module",
6
- "exports": {
7
- ".": "./src/plugin.mjs",
8
- "./plugin": "./src/plugin.mjs",
9
- "./server": "./src/plugin-v1.mjs",
10
- "./v1": "./src/plugin-v1.mjs",
11
- "./audit": "./src/audit.mjs"
12
- },
13
- "bin": {
14
- "skills-doctor": "./bin/skills-doctor.mjs"
15
- },
16
- "peerDependencies": {
17
- "@opencode/plugin": ">=2.0.0",
18
- "@opencode-ai/plugin": ">=1.0.0"
19
- },
20
- "peerDependenciesMeta": {
21
- "@opencode/plugin": {
22
- "optional": true
23
- },
24
- "@opencode-ai/plugin": {
25
- "optional": true
26
- }
27
- },
28
- "engines": {
29
- "node": ">=20"
30
- },
31
- "scripts": {
32
- "test": "node test/doctor.test.mjs"
33
- },
34
- "files": [
35
- "bin",
36
- "src",
37
- "README.md",
38
- "README.zh-CN.md"
39
- ],
40
- "keywords": [
41
- "opencode",
42
- "skills",
43
- "lint",
44
- "fix",
45
- "cli",
46
- "plugin"
47
- ],
48
- "repository": {
49
- "type": "git",
50
- "url": "https://github.com/wangyuchen5110/opencodeplugginprogram.git"
51
- },
52
- "license": "MIT"
53
- }
1
+ {
2
+ "name": "opencode-skills-doctor",
3
+ "version": "0.3.6",
4
+ "description": "Audit, fix and update agent skills: per-step context cost, shadowed duplicates, safe archive, catalog sync. CLI, OpenCode plugin and MCP server (Claude Code / Codex / any MCP client).",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/plugin.mjs",
8
+ "./plugin": "./src/plugin.mjs",
9
+ "./server": "./src/plugin-v1.mjs",
10
+ "./v1": "./src/plugin-v1.mjs",
11
+ "./audit": "./src/audit.mjs"
12
+ },
13
+ "bin": {
14
+ "skills-doctor": "./bin/skills-doctor.mjs"
15
+ },
16
+ "peerDependencies": {
17
+ "@opencode/plugin": ">=2.0.0",
18
+ "@opencode-ai/plugin": ">=1.0.0"
19
+ },
20
+ "peerDependenciesMeta": {
21
+ "@opencode/plugin": {
22
+ "optional": true
23
+ },
24
+ "@opencode-ai/plugin": {
25
+ "optional": true
26
+ }
27
+ },
28
+ "engines": {
29
+ "node": ">=20"
30
+ },
31
+ "scripts": {
32
+ "test": "node test/doctor.test.mjs"
33
+ },
34
+ "files": [
35
+ "bin",
36
+ "src",
37
+ "README.md",
38
+ "README.zh-CN.md"
39
+ ],
40
+ "keywords": [
41
+ "opencode",
42
+ "skills",
43
+ "agent-skills",
44
+ "mcp",
45
+ "lint",
46
+ "fix",
47
+ "cli",
48
+ "plugin"
49
+ ],
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://github.com/wangyuchen5110/opencodeplugginprogram.git"
53
+ },
54
+ "license": "MIT"
55
+ }
package/src/cli.mjs CHANGED
@@ -7,10 +7,13 @@ import { inspectManaged } from "./managed.mjs"
7
7
  import { planFixes, applyFixes, hideSkills } from "./fix.mjs"
8
8
  import { auditSkills } from "./audit.mjs"
9
9
  import { loadConfig } from "./config.mjs"
10
+ import { startMcpServer } from "./mcp.mjs"
10
11
  import { padDisplay } from "./parse.mjs"
11
12
  import { detectLang, normalizeLang, translator } from "./i18n.mjs"
12
13
 
13
- const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"))
14
+ const pkg = JSON.parse(
15
+ fs.readFileSync(new URL("../package.json", import.meta.url), "utf8").replace(/^\uFEFF/, ""),
16
+ )
14
17
 
15
18
  const color = process.stdout.isTTY && !process.env.NO_COLOR
16
19
  const paint = (code, text) => (color ? `\x1b[${code}m${text}\x1b[0m` : text)
@@ -57,7 +60,7 @@ function parseArgs(argv) {
57
60
  }
58
61
 
59
62
  const args = [...argv]
60
- const commands = new Set(["check", "scan", "update", "fix", "help"])
63
+ const commands = new Set(["check", "scan", "update", "fix", "mcp", "help"])
61
64
  if (args[0] && commands.has(args[0])) {
62
65
  options.command = args.shift()
63
66
  }
@@ -425,6 +428,11 @@ export async function main(argv) {
425
428
  return
426
429
  }
427
430
 
431
+ if (options.command === "mcp") {
432
+ startMcpServer({ version: pkg.version })
433
+ return
434
+ }
435
+
428
436
  if (options.command === "check") {
429
437
  const audit = auditSkills({
430
438
  cwd,
package/src/i18n.mjs CHANGED
@@ -10,6 +10,7 @@ Usage:
10
10
  skills-doctor scan [options] list every skill with its source and precedence
11
11
  skills-doctor fix [options] archive shadowed definitions (safe structural fixes)
12
12
  skills-doctor update [options] compare and download skills from HTTP catalogs
13
+ skills-doctor mcp run as an MCP server (Claude Code, Codex, any MCP client)
13
14
 
14
15
  Options:
15
16
  --root <dir> working directory (default: current directory)
@@ -151,6 +152,7 @@ Exit codes: 0 ok; 1 errors (and warnings with --strict); 2 bad arguments or runt
151
152
  skills-doctor scan [选项] 列出所有 skill 及其来源/优先级
152
153
  skills-doctor fix [选项] 归档被遮蔽的定义(安全的结构性修复)
153
154
  skills-doctor update [选项] 对比并拉取 HTTP catalog 里的 skill
155
+ skills-doctor mcp 以 MCP server 方式运行(Claude Code、Codex 等任何 MCP 客户端)
154
156
 
155
157
  选项:
156
158
  --root <dir> 以该目录为工作目录(默认当前目录)
package/src/mcp.mjs ADDED
@@ -0,0 +1,150 @@
1
+ import { auditSkills, formatMarkdown } from "./audit.mjs"
2
+ import { normalizeLang, detectLang } from "./i18n.mjs"
3
+
4
+ export const PROTOCOL_VERSION = "2025-06-18"
5
+ export const SERVER_NAME = "skills-doctor"
6
+ export const TOOL_NAME = "skills_doctor"
7
+
8
+ export function toolDefinition() {
9
+ return {
10
+ name: TOOL_NAME,
11
+ description:
12
+ "Audit local agent skills (OpenCode / Claude Code / .agents): duplicate and shadowed definitions, per-step description token budget, oversized bodies and fix hints. Returns a markdown report.",
13
+ inputSchema: {
14
+ type: "object",
15
+ properties: {
16
+ directory: {
17
+ type: "string",
18
+ description: "Directory to audit (default: the server process working directory)",
19
+ },
20
+ budget: {
21
+ type: "number",
22
+ description: "Description token budget per model step (default 1200)",
23
+ },
24
+ lang: {
25
+ type: "string",
26
+ enum: ["en", "zh"],
27
+ description: "Report language (default: auto from system locale)",
28
+ },
29
+ global: {
30
+ type: "boolean",
31
+ description: "Include global skill directories such as ~/.config/opencode and ~/.claude (default true)",
32
+ },
33
+ },
34
+ additionalProperties: false,
35
+ },
36
+ }
37
+ }
38
+
39
+ function ok(id, result) {
40
+ return { jsonrpc: "2.0", id, result }
41
+ }
42
+
43
+ function fail(id, code, message) {
44
+ return { jsonrpc: "2.0", id, error: { code, message } }
45
+ }
46
+
47
+ /** 处理一条 JSON-RPC 消息;通知返回 null。 */
48
+ export async function handleMessage(message, { version = "0.0.0" } = {}) {
49
+ if (!message || typeof message !== "object" || typeof message.method !== "string") return null
50
+ const { id, method, params } = message
51
+ const isNotification = id === undefined || id === null
52
+
53
+ try {
54
+ switch (method) {
55
+ case "initialize":
56
+ return ok(id, {
57
+ protocolVersion: PROTOCOL_VERSION,
58
+ capabilities: { tools: { listChanged: false } },
59
+ serverInfo: { name: SERVER_NAME, version },
60
+ })
61
+
62
+ case "notifications/initialized":
63
+ case "notifications/cancelled":
64
+ return null
65
+
66
+ case "ping":
67
+ return ok(id, {})
68
+
69
+ case "tools/list":
70
+ return ok(id, { tools: [toolDefinition()] })
71
+
72
+ case "resources/list":
73
+ return ok(id, { resources: [] })
74
+
75
+ case "prompts/list":
76
+ return ok(id, { prompts: [] })
77
+
78
+ case "tools/call": {
79
+ const name = params?.name
80
+ if (name !== TOOL_NAME) return fail(id, -32602, `unknown tool: ${name}`)
81
+
82
+ const args = params?.arguments && typeof params.arguments === "object" ? params.arguments : {}
83
+ const directory =
84
+ typeof args.directory === "string" && args.directory.trim() ? args.directory.trim() : process.cwd()
85
+ const budget = typeof args.budget === "number" && args.budget > 0 ? args.budget : null
86
+ const lang = normalizeLang(args.lang) ?? detectLang()
87
+
88
+ const audit = auditSkills({ cwd: directory, budget, lang, includeGlobal: args.global !== false })
89
+ return ok(id, { content: [{ type: "text", text: formatMarkdown(audit) }] })
90
+ }
91
+
92
+ default:
93
+ if (isNotification) return null
94
+ return fail(id, -32601, `method not found: ${method}`)
95
+ }
96
+ } catch (err) {
97
+ if (isNotification) return null
98
+ return fail(id, -32603, String(err?.message ?? err))
99
+ }
100
+ }
101
+
102
+ /** stdio transport:newline-delimited JSON-RPC(MCP 标准)。 */
103
+ export function startMcpServer({ version, input = process.stdin, output = process.stdout, onExit = () => process.exit(0) } = {}) {
104
+ let buffer = ""
105
+ let queue = Promise.resolve()
106
+
107
+ const write = (payload) => {
108
+ output.write(JSON.stringify(payload) + "\n")
109
+ }
110
+
111
+ const handleLine = (line) => {
112
+ let message
113
+ try {
114
+ message = JSON.parse(line)
115
+ } catch {
116
+ return
117
+ }
118
+ queue = queue
119
+ .then(() => handleMessage(message, { version }))
120
+ .then((response) => {
121
+ if (response) write(response)
122
+ })
123
+ .catch((err) => {
124
+ write(fail(message?.id ?? null, -32603, String(err?.message ?? err)))
125
+ })
126
+ }
127
+
128
+ input.setEncoding?.("utf8")
129
+ input.on("data", (chunk) => {
130
+ buffer += chunk
131
+ let index
132
+ while ((index = buffer.indexOf("\n")) >= 0) {
133
+ const line = buffer.slice(0, index).trim()
134
+ buffer = buffer.slice(index + 1)
135
+ if (line) handleLine(line)
136
+ }
137
+ })
138
+
139
+ let finished = false
140
+ const finish = () => {
141
+ if (finished) return
142
+ finished = true
143
+ // 等队列里的异步响应写完再退出,避免管道结束时丢包
144
+ queue.finally(onExit)
145
+ }
146
+ input.on("end", finish)
147
+ input.on("close", finish)
148
+
149
+ return { write, close: finish }
150
+ }