skill-library-mcp 1.0.0 → 1.2.0
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/.claude-plugin/plugin.json +12 -0
- package/.mcp.json +8 -0
- package/README.md +18 -2
- package/dist/index.js +96 -32
- package/package.json +30 -1
- package/.claude/settings.local.json +0 -10
- package/.github/workflows/ci.yml +0 -85
- package/.github/workflows/release.yml +0 -57
- package/.release-please-manifest.json +0 -3
- package/CLAUDE.md +0 -60
- package/Makefile +0 -18
- package/docs/architecture.md +0 -61
- package/docs/development.md +0 -83
- package/release-please-config.json +0 -17
- package/scripts/import-skills.ts +0 -103
- package/src/dedup.ts +0 -132
- package/src/index.ts +0 -26
- package/src/loader.ts +0 -33
- package/src/search.ts +0 -83
- package/src/server.ts +0 -83
- package/src/skill-index.ts +0 -105
- package/src/types.ts +0 -28
- package/test/dedup.test.ts +0 -33
- package/test/fixtures/allowed-tools/SKILL.md +0 -9
- package/test/fixtures/basic-skill/SKILL.md +0 -11
- package/test/fixtures/empty-description/SKILL.md +0 -7
- package/test/fixtures/invalid-frontmatter/SKILL.md +0 -7
- package/test/fixtures/multiline-description/SKILL.md +0 -11
- package/test/fixtures/no-frontmatter/SKILL.md +0 -3
- package/test/fixtures/skill-with-hyphenated-resources/SKILL.md +0 -8
- package/test/fixtures/skill-with-hyphenated-resources/resources/implementation-playbook.md +0 -3
- package/test/fixtures/skill-with-hyphenated-resources/resources/quick-start-guide.md +0 -3
- package/test/fixtures/skill-with-resources/SKILL.md +0 -8
- package/test/fixtures/skill-with-resources/resources/examples.md +0 -3
- package/test/fixtures/skill-with-resources/resources/guide.md +0 -3
- package/test/integration.test.ts +0 -148
- package/test/loader.test.ts +0 -78
- package/test/search.test.ts +0 -135
- package/test/server.test.ts +0 -94
- package/test/skill-index.test.ts +0 -144
- package/tsconfig.json +0 -15
- package/tsup.config.ts +0 -12
- package/vitest.config.ts +0 -5
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "skill-library",
|
|
3
|
+
"description": "On-demand access to 700+ curated skills for AI coding assistants. Search, browse categories, and load skills without polluting your context window.",
|
|
4
|
+
"version": "1.1.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "modbender"
|
|
7
|
+
},
|
|
8
|
+
"homepage": "https://github.com/modbender/skill-library-mcp",
|
|
9
|
+
"repository": "https://github.com/modbender/skill-library-mcp",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": ["skills", "mcp", "skill-library", "claude-code", "ai"]
|
|
12
|
+
}
|
package/.mcp.json
ADDED
package/README.md
CHANGED
|
@@ -14,15 +14,31 @@ An MCP server that provides on-demand skill loading for AI coding assistants. In
|
|
|
14
14
|
- **On-demand loading** — skills are fetched only when needed, not crammed into every conversation
|
|
15
15
|
- **IDF-weighted search** — finds the right skill even from natural language queries like "help me debug a memory leak"
|
|
16
16
|
- **Works with any MCP-compatible tool** — Claude Code, Cursor, Windsurf, VS Code, Claude Desktop, and others
|
|
17
|
+
- **Claude Code plugin** — one-command install with `claude plugin install`
|
|
17
18
|
- **Zero config** — run with `npx`, no setup needed
|
|
18
19
|
|
|
19
20
|
## Quick Start
|
|
20
21
|
|
|
22
|
+
### Claude Code Plugin (Recommended)
|
|
23
|
+
|
|
24
|
+
The easiest way to use this with Claude Code — install as a plugin:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
claude plugin install skill-library-mcp
|
|
21
28
|
```
|
|
22
|
-
|
|
29
|
+
|
|
30
|
+
The MCP server starts automatically when Claude Code launches. No manual configuration needed.
|
|
31
|
+
|
|
32
|
+
To install for a specific scope:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
claude plugin install skill-library-mcp --scope user # Default, available everywhere
|
|
36
|
+
claude plugin install skill-library-mcp --scope project # Project-only, shared with collaborators
|
|
23
37
|
```
|
|
24
38
|
|
|
25
|
-
|
|
39
|
+
### MCP Server (Manual)
|
|
40
|
+
|
|
41
|
+
For other tools or manual Claude Code setup, add to your MCP configuration:
|
|
26
42
|
|
|
27
43
|
<details>
|
|
28
44
|
<summary><strong>Claude Code (CLI)</strong></summary>
|
package/dist/index.js
CHANGED
|
@@ -6,54 +6,98 @@ import { fileURLToPath } from "url";
|
|
|
6
6
|
import { dirname, join as join3 } from "path";
|
|
7
7
|
|
|
8
8
|
// src/skill-index.ts
|
|
9
|
-
import { readdir, readFile
|
|
9
|
+
import { readdir, readFile } from "fs/promises";
|
|
10
10
|
import { join } from "path";
|
|
11
11
|
import { parse as parseYaml } from "yaml";
|
|
12
|
+
|
|
13
|
+
// src/tokenize.ts
|
|
14
|
+
function tokenize(text) {
|
|
15
|
+
const words = text.toLowerCase().replace(/[^a-z0-9\-]/g, " ").split(/\s+/).filter(Boolean);
|
|
16
|
+
const tokens = [];
|
|
17
|
+
for (const word of words) {
|
|
18
|
+
tokens.push(word);
|
|
19
|
+
if (word.includes("-")) {
|
|
20
|
+
for (const part of word.split("-")) {
|
|
21
|
+
if (part) tokens.push(part);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return tokens;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/categories.ts
|
|
29
|
+
var CATEGORY_KEYWORDS = [
|
|
30
|
+
["Frontend", ["react", "angular", "vue", "svelte", "nextjs", "frontend", "css", "tailwind", "ui", "ux"]],
|
|
31
|
+
["Backend", ["backend", "nodejs", "express", "nestjs", "fastapi", "django", "rails", "api", "rest", "graphql"]],
|
|
32
|
+
["AI & LLM", ["ai", "llm", "agent", "prompt", "rag", "claude", "openai", "embedding", "ml"]],
|
|
33
|
+
["DevOps & Infra", ["docker", "kubernetes", "terraform", "aws", "gcp", "azure", "deployment", "infrastructure"]],
|
|
34
|
+
["Data & Databases", ["data", "database", "sql", "postgres", "mongodb", "redis", "analytics", "pipeline", "etl"]],
|
|
35
|
+
["Security", ["security", "penetration", "vulnerability", "audit", "owasp", "xss", "encryption"]],
|
|
36
|
+
["Testing", ["test", "tdd", "testing", "e2e", "vitest", "jest", "playwright"]],
|
|
37
|
+
["Mobile", ["mobile", "react-native", "flutter", "ios", "android", "expo"]],
|
|
38
|
+
["Automation", ["automation", "workflow", "n8n", "zapier", "scraping", "bot"]],
|
|
39
|
+
["Python", ["python", "django", "flask", "fastapi", "pandas"]],
|
|
40
|
+
["TypeScript & JS", ["typescript", "javascript", "deno", "bun"]],
|
|
41
|
+
["Architecture", ["architecture", "microservices", "system-design", "patterns", "monorepo"]]
|
|
42
|
+
];
|
|
43
|
+
function buildCategories(entries) {
|
|
44
|
+
const categories = /* @__PURE__ */ new Map();
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const text = `${entry.dirName} ${entry.frontmatter.description}`.toLowerCase();
|
|
47
|
+
let matched = false;
|
|
48
|
+
for (const [category, keywords] of CATEGORY_KEYWORDS) {
|
|
49
|
+
if (keywords.some((kw) => text.includes(kw))) {
|
|
50
|
+
const list = categories.get(category) ?? [];
|
|
51
|
+
list.push(entry.dirName);
|
|
52
|
+
categories.set(category, list);
|
|
53
|
+
matched = true;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!matched) {
|
|
58
|
+
const list = categories.get("Other") ?? [];
|
|
59
|
+
list.push(entry.dirName);
|
|
60
|
+
categories.set("Other", list);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return categories;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/skill-index.ts
|
|
12
67
|
function parseFrontmatter(content) {
|
|
13
68
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
14
69
|
if (!match) return null;
|
|
15
70
|
try {
|
|
16
71
|
const parsed = parseYaml(match[1]);
|
|
17
72
|
if (!parsed || typeof parsed.name !== "string") return null;
|
|
73
|
+
const metadata = parsed.metadata && typeof parsed.metadata === "object" && !Array.isArray(parsed.metadata) ? parsed.metadata : void 0;
|
|
74
|
+
const allowedTools = Array.isArray(parsed.allowedTools) && parsed.allowedTools.every((t) => typeof t === "string") ? parsed.allowedTools : void 0;
|
|
18
75
|
return {
|
|
19
76
|
name: parsed.name,
|
|
20
77
|
description: typeof parsed.description === "string" ? parsed.description.trim() : "",
|
|
21
|
-
metadata
|
|
22
|
-
allowedTools
|
|
78
|
+
metadata,
|
|
79
|
+
allowedTools
|
|
23
80
|
};
|
|
24
81
|
} catch {
|
|
25
82
|
return null;
|
|
26
83
|
}
|
|
27
84
|
}
|
|
28
|
-
function tokenize(text) {
|
|
29
|
-
const words = text.toLowerCase().replace(/[^a-z0-9\-]/g, " ").split(/\s+/).filter(Boolean);
|
|
30
|
-
const tokens = /* @__PURE__ */ new Set();
|
|
31
|
-
for (const word of words) {
|
|
32
|
-
tokens.add(word);
|
|
33
|
-
if (word.includes("-")) {
|
|
34
|
-
for (const part of word.split("-")) {
|
|
35
|
-
if (part) tokens.add(part);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return tokens;
|
|
40
|
-
}
|
|
41
85
|
async function buildIndex(skillsDir2) {
|
|
42
86
|
const entries = [];
|
|
43
87
|
let dirs;
|
|
44
88
|
try {
|
|
45
89
|
dirs = await readdir(skillsDir2);
|
|
46
90
|
} catch {
|
|
47
|
-
return { entries, idfScores: /* @__PURE__ */ new Map(), totalDocs: 0 };
|
|
91
|
+
return { entries, idfScores: /* @__PURE__ */ new Map(), totalDocs: 0, categories: /* @__PURE__ */ new Map() };
|
|
48
92
|
}
|
|
49
93
|
for (const dirName of dirs) {
|
|
50
94
|
const skillPath = join(skillsDir2, dirName, "SKILL.md");
|
|
95
|
+
let content;
|
|
51
96
|
try {
|
|
52
|
-
await
|
|
97
|
+
content = await readFile(skillPath, "utf-8");
|
|
53
98
|
} catch {
|
|
54
99
|
continue;
|
|
55
100
|
}
|
|
56
|
-
const content = await readFile(skillPath, "utf-8");
|
|
57
101
|
const frontmatter = parseFrontmatter(content);
|
|
58
102
|
if (!frontmatter) continue;
|
|
59
103
|
const resourceDir = join(skillsDir2, dirName, "resources");
|
|
@@ -65,10 +109,9 @@ async function buildIndex(skillsDir2) {
|
|
|
65
109
|
hasResources = resourceFiles.length > 0;
|
|
66
110
|
} catch {
|
|
67
111
|
}
|
|
68
|
-
const searchTokens = /* @__PURE__ */ new Set(
|
|
69
|
-
...tokenize(frontmatter.name),
|
|
70
|
-
|
|
71
|
-
]);
|
|
112
|
+
const searchTokens = /* @__PURE__ */ new Set(
|
|
113
|
+
[...tokenize(frontmatter.name), ...tokenize(frontmatter.description)]
|
|
114
|
+
);
|
|
72
115
|
entries.push({
|
|
73
116
|
dirName,
|
|
74
117
|
frontmatter,
|
|
@@ -88,7 +131,8 @@ async function buildIndex(skillsDir2) {
|
|
|
88
131
|
for (const [token, df] of docFrequency) {
|
|
89
132
|
idfScores.set(token, Math.log(totalDocs / df));
|
|
90
133
|
}
|
|
91
|
-
|
|
134
|
+
const categories = buildCategories(entries);
|
|
135
|
+
return { entries, idfScores, totalDocs, categories };
|
|
92
136
|
}
|
|
93
137
|
|
|
94
138
|
// src/server.ts
|
|
@@ -144,11 +188,8 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
144
188
|
"should",
|
|
145
189
|
"please"
|
|
146
190
|
]);
|
|
147
|
-
function tokenize2(text) {
|
|
148
|
-
return text.toLowerCase().replace(/[^a-z0-9\-]/g, " ").split(/\s+/).filter(Boolean);
|
|
149
|
-
}
|
|
150
191
|
function searchSkills(index, query, limit = 20) {
|
|
151
|
-
const rawTokens =
|
|
192
|
+
const rawTokens = tokenize(query);
|
|
152
193
|
if (rawTokens.length === 0) return [];
|
|
153
194
|
const filtered = rawTokens.filter((t) => !STOP_WORDS.has(t));
|
|
154
195
|
const meaningful = filtered.length > 0 ? filtered : rawTokens;
|
|
@@ -228,10 +269,12 @@ ${resourceContent}`;
|
|
|
228
269
|
|
|
229
270
|
// src/server.ts
|
|
230
271
|
function createServer(index, skillsDir2) {
|
|
231
|
-
const server = new McpServer(
|
|
232
|
-
name: "skill-library",
|
|
233
|
-
|
|
234
|
-
|
|
272
|
+
const server = new McpServer(
|
|
273
|
+
{ name: "skill-library", version: "1.0.0" },
|
|
274
|
+
{
|
|
275
|
+
instructions: `Skill library with ${index.totalDocs} skills across ${index.categories.size} categories. Use search_skill to find skills by keyword, or list_categories to browse available categories.`
|
|
276
|
+
}
|
|
277
|
+
);
|
|
235
278
|
const lookupMap = /* @__PURE__ */ new Map();
|
|
236
279
|
for (const entry of index.entries) {
|
|
237
280
|
lookupMap.set(entry.dirName.toLowerCase(), entry);
|
|
@@ -291,6 +334,27 @@ ${lines.join("\n")}`
|
|
|
291
334
|
};
|
|
292
335
|
}
|
|
293
336
|
);
|
|
337
|
+
server.tool(
|
|
338
|
+
"list_categories",
|
|
339
|
+
"List all skill categories with counts and examples. Use this to discover what skills are available before searching.",
|
|
340
|
+
{},
|
|
341
|
+
async () => {
|
|
342
|
+
const lines = [];
|
|
343
|
+
for (const [category, skills] of index.categories) {
|
|
344
|
+
const examples = skills.slice(0, 3).join(", ");
|
|
345
|
+
const more = skills.length > 3 ? `, +${skills.length - 3} more` : "";
|
|
346
|
+
lines.push(`- **${category}** (${skills.length}) \u2014 ${examples}${more}`);
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
content: [{
|
|
350
|
+
type: "text",
|
|
351
|
+
text: `${index.totalDocs} skills in ${index.categories.size} categories:
|
|
352
|
+
|
|
353
|
+
${lines.join("\n")}`
|
|
354
|
+
}]
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
);
|
|
294
358
|
return server;
|
|
295
359
|
}
|
|
296
360
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skill-library-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "MCP server providing on-demand skill loading for AI coding assistants",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/modbender/skill-library-mcp"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/modbender/skill-library-mcp#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/modbender/skill-library-mcp/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"skills",
|
|
19
|
+
"claude",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"ai",
|
|
22
|
+
"llm",
|
|
23
|
+
"coding-assistant",
|
|
24
|
+
"skill-library",
|
|
25
|
+
"mcp-server"
|
|
26
|
+
],
|
|
27
|
+
"files": [
|
|
28
|
+
"dist/",
|
|
29
|
+
"skills/",
|
|
30
|
+
".claude-plugin/",
|
|
31
|
+
".mcp.json",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
6
35
|
"bin": {
|
|
7
36
|
"skill-library-mcp": "dist/index.js"
|
|
8
37
|
},
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
permissions:
|
|
4
|
-
contents: read
|
|
5
|
-
|
|
6
|
-
on:
|
|
7
|
-
push:
|
|
8
|
-
branches: [main]
|
|
9
|
-
pull_request:
|
|
10
|
-
branches: [main]
|
|
11
|
-
|
|
12
|
-
concurrency:
|
|
13
|
-
group: ${{ github.workflow }}-${{ github.ref }}
|
|
14
|
-
cancel-in-progress: true
|
|
15
|
-
|
|
16
|
-
jobs:
|
|
17
|
-
test:
|
|
18
|
-
runs-on: ubuntu-latest
|
|
19
|
-
steps:
|
|
20
|
-
- name: Checkout code
|
|
21
|
-
uses: actions/checkout@v4
|
|
22
|
-
|
|
23
|
-
- name: Install pnpm
|
|
24
|
-
uses: pnpm/action-setup@v4
|
|
25
|
-
|
|
26
|
-
- name: Setup Node.js
|
|
27
|
-
uses: actions/setup-node@v4
|
|
28
|
-
with:
|
|
29
|
-
node-version: '22'
|
|
30
|
-
cache: 'pnpm'
|
|
31
|
-
|
|
32
|
-
- name: Install dependencies
|
|
33
|
-
run: pnpm install --frozen-lockfile
|
|
34
|
-
|
|
35
|
-
- name: Run tests
|
|
36
|
-
run: pnpm test
|
|
37
|
-
|
|
38
|
-
build:
|
|
39
|
-
runs-on: ubuntu-latest
|
|
40
|
-
needs: test
|
|
41
|
-
steps:
|
|
42
|
-
- name: Checkout code
|
|
43
|
-
uses: actions/checkout@v4
|
|
44
|
-
|
|
45
|
-
- name: Install pnpm
|
|
46
|
-
uses: pnpm/action-setup@v4
|
|
47
|
-
|
|
48
|
-
- name: Setup Node.js
|
|
49
|
-
uses: actions/setup-node@v4
|
|
50
|
-
with:
|
|
51
|
-
node-version: '22'
|
|
52
|
-
cache: 'pnpm'
|
|
53
|
-
|
|
54
|
-
- name: Install dependencies
|
|
55
|
-
run: pnpm install --frozen-lockfile
|
|
56
|
-
|
|
57
|
-
- name: Build
|
|
58
|
-
run: pnpm build
|
|
59
|
-
|
|
60
|
-
- name: Upload build artifacts
|
|
61
|
-
uses: actions/upload-artifact@v4
|
|
62
|
-
with:
|
|
63
|
-
name: dist
|
|
64
|
-
path: dist/
|
|
65
|
-
retention-days: 7
|
|
66
|
-
|
|
67
|
-
ci-summary:
|
|
68
|
-
runs-on: ubuntu-latest
|
|
69
|
-
needs: [test, build]
|
|
70
|
-
if: always()
|
|
71
|
-
steps:
|
|
72
|
-
- name: Check all jobs status
|
|
73
|
-
env:
|
|
74
|
-
TEST_RESULT: ${{ needs.test.result }}
|
|
75
|
-
BUILD_RESULT: ${{ needs.build.result }}
|
|
76
|
-
run: |
|
|
77
|
-
if [[ "$TEST_RESULT" != "success" ]]; then
|
|
78
|
-
echo "Test job failed"
|
|
79
|
-
exit 1
|
|
80
|
-
fi
|
|
81
|
-
if [[ "$BUILD_RESULT" != "success" ]]; then
|
|
82
|
-
echo "Build job failed"
|
|
83
|
-
exit 1
|
|
84
|
-
fi
|
|
85
|
-
echo "All CI jobs passed successfully"
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
name: Release
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
|
|
8
|
-
permissions:
|
|
9
|
-
contents: write
|
|
10
|
-
pull-requests: write
|
|
11
|
-
id-token: write
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
release-please:
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
outputs:
|
|
17
|
-
release_created: ${{ steps.release.outputs.release_created }}
|
|
18
|
-
tag_name: ${{ steps.release.outputs.tag_name }}
|
|
19
|
-
steps:
|
|
20
|
-
- uses: googleapis/release-please-action@v4
|
|
21
|
-
id: release
|
|
22
|
-
with:
|
|
23
|
-
release-type: node
|
|
24
|
-
|
|
25
|
-
publish:
|
|
26
|
-
runs-on: ubuntu-latest
|
|
27
|
-
needs: release-please
|
|
28
|
-
if: needs.release-please.outputs.release_created == 'true'
|
|
29
|
-
permissions:
|
|
30
|
-
contents: read
|
|
31
|
-
id-token: write
|
|
32
|
-
steps:
|
|
33
|
-
- uses: actions/checkout@v4
|
|
34
|
-
|
|
35
|
-
- name: Install pnpm
|
|
36
|
-
uses: pnpm/action-setup@v4
|
|
37
|
-
|
|
38
|
-
- name: Setup Node.js
|
|
39
|
-
uses: actions/setup-node@v4
|
|
40
|
-
with:
|
|
41
|
-
node-version: '22'
|
|
42
|
-
registry-url: 'https://registry.npmjs.org'
|
|
43
|
-
cache: 'pnpm'
|
|
44
|
-
|
|
45
|
-
- name: Install dependencies
|
|
46
|
-
run: pnpm install --frozen-lockfile
|
|
47
|
-
|
|
48
|
-
- name: Run tests
|
|
49
|
-
run: pnpm test
|
|
50
|
-
|
|
51
|
-
- name: Build
|
|
52
|
-
run: pnpm build
|
|
53
|
-
|
|
54
|
-
- name: Publish to npm
|
|
55
|
-
run: pnpm publish --access public --no-git-checks --provenance
|
|
56
|
-
env:
|
|
57
|
-
NPM_CONFIG_PROVENANCE: true
|
package/CLAUDE.md
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
-
|
|
5
|
-
## What This Is
|
|
6
|
-
|
|
7
|
-
MCP server that indexes and serves Claude Code skills on demand. It exposes two tools (`search_skill`, `load_skill`) over stdio transport using the Model Context Protocol SDK.
|
|
8
|
-
|
|
9
|
-
## Commands
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
pnpm install # Install dependencies
|
|
13
|
-
pnpm test # Run all tests (vitest)
|
|
14
|
-
pnpm test -- test/search.test.ts # Run a single test file
|
|
15
|
-
pnpm test -- -t "exact token match" # Run a single test by name
|
|
16
|
-
pnpm build # Build to dist/ (tsup, ESM-only, node22 target)
|
|
17
|
-
pnpm dev # Run server locally via tsx
|
|
18
|
-
make ci # Run test + build
|
|
19
|
-
make mcp-test # Build and send initialize request to verify MCP handshake
|
|
20
|
-
pnpm dedup # Check for duplicate skills
|
|
21
|
-
tsx scripts/import-skills.ts <source-dir> [--no-dry-run] # Import skills from external source
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Architecture
|
|
25
|
-
|
|
26
|
-
The data flow is: `skills/` → `buildIndex()` → `SearchIndex` → `createServer()` → MCP tools over stdio.
|
|
27
|
-
|
|
28
|
-
- `src/skill-index.ts` — Reads `skills/*/SKILL.md`, parses YAML frontmatter, builds tokenized search index with IDF scores as `SearchIndex`
|
|
29
|
-
- `src/search.ts` — IDF-weighted search with stop-word filtering, query deduplication, minimum substring length (≥2 chars), name bonus +2.0, description bonus +1.0, threshold ≥0.5. Normalizes by matched token count (not total query tokens) to prevent unmatched terms from diluting scores
|
|
30
|
-
- `src/loader.ts` — Loads full SKILL.md content; optionally appends `resources/*.md` files
|
|
31
|
-
- `src/server.ts` — Creates `McpServer` with two tools. Builds a case-insensitive lookup map keyed by both `dirName` and `frontmatter.name`. `load_skill` falls back to fuzzy search suggestions when exact lookup fails
|
|
32
|
-
- `src/index.ts` — Entry point: resolves `skills/` dir relative to `dist/`, builds index, connects stdio transport
|
|
33
|
-
- `src/types.ts` — `SkillFrontmatter`, `SkillEntry`, `SearchIndex`, `SearchResult` interfaces
|
|
34
|
-
- `src/dedup.ts` — Deduplication utility: finds exact (hash-based) and near (Jaccard similarity >0.8) duplicate skills. Runnable as CLI
|
|
35
|
-
- `scripts/import-skills.ts` — Imports skills from external directories with dry-run support and dedup checking
|
|
36
|
-
|
|
37
|
-
## Testing
|
|
38
|
-
|
|
39
|
-
Two test layers, both using vitest:
|
|
40
|
-
|
|
41
|
-
**Unit tests** use synthetic skills in `test/fixtures/` for deterministic results. Never use the real `skills/` directory in unit tests.
|
|
42
|
-
|
|
43
|
-
**Integration tests** (`test/integration.test.ts`) use the real `skills/` directory to validate index completeness and search relevance.
|
|
44
|
-
|
|
45
|
-
## Conventions
|
|
46
|
-
|
|
47
|
-
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `test:`
|
|
48
|
-
- ESM-only (`"type": "module"`) — all internal imports use `.js` extensions
|
|
49
|
-
- Node 22 target, pnpm package manager
|
|
50
|
-
- tsup bundles `src/index.ts` to `dist/index.js` with `#!/usr/bin/env node` banner
|
|
51
|
-
- `skills/` lives at project root, resolved at runtime as `join(__dirname, "..", "skills")` from `dist/`
|
|
52
|
-
|
|
53
|
-
## Releases
|
|
54
|
-
|
|
55
|
-
Releases are fully automated via [release-please](https://github.com/googleapis/release-please). On every push to `main`, release-please analyzes conventional commits and opens/updates a Release PR with version bumps and changelog. Merging that PR creates a git tag and GitHub Release, which triggers npm publish.
|
|
56
|
-
|
|
57
|
-
**Commit → version mapping:**
|
|
58
|
-
- `fix: ...` → patch (1.0.0 → 1.0.1)
|
|
59
|
-
- `feat: ...` → minor (1.0.0 → 1.1.0)
|
|
60
|
-
- `feat!: ...` or `BREAKING CHANGE:` footer → major (1.0.0 → 2.0.0)
|
package/Makefile
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
.PHONY: test build ci dev clean mcp-test
|
|
2
|
-
|
|
3
|
-
test: ## Run vitest
|
|
4
|
-
pnpm test
|
|
5
|
-
|
|
6
|
-
build: ## Build with tsup
|
|
7
|
-
pnpm build
|
|
8
|
-
|
|
9
|
-
ci: test build ## Run CI locally (test + build)
|
|
10
|
-
|
|
11
|
-
dev: ## Run MCP server locally via tsx
|
|
12
|
-
pnpm dev
|
|
13
|
-
|
|
14
|
-
clean: ## Remove dist/ node_modules/ coverage/
|
|
15
|
-
rm -rf dist/ node_modules/ coverage/
|
|
16
|
-
|
|
17
|
-
mcp-test: build ## Build and test MCP server with initialize request
|
|
18
|
-
@echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' | node dist/index.js 2>/dev/null
|
package/docs/architecture.md
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
# Architecture
|
|
2
|
-
|
|
3
|
-
## Data Flow
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
skills/ (shipped with package)
|
|
7
|
-
↓
|
|
8
|
-
buildIndex()
|
|
9
|
-
↓
|
|
10
|
-
SkillEntry[] (in-memory index)
|
|
11
|
-
↓
|
|
12
|
-
┌─────────────┴─────────────┐
|
|
13
|
-
↓ ↓
|
|
14
|
-
searchSkills() loadSkill()
|
|
15
|
-
↓ ↓
|
|
16
|
-
SearchResult[] string (content)
|
|
17
|
-
↓ ↓
|
|
18
|
-
MCP tool: MCP tool:
|
|
19
|
-
search_skill load_skill
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
1. **Index build** (`src/skill-index.ts`): Reads all `skills/*/SKILL.md` files, parses YAML frontmatter, tokenizes name+description, detects resources. Returns `SkillEntry[]`.
|
|
23
|
-
|
|
24
|
-
2. **Search** (`src/search.ts`): Token-based scoring against the in-memory index. No external dependencies.
|
|
25
|
-
|
|
26
|
-
3. **Load** (`src/loader.ts`): Reads SKILL.md content from disk. Optionally appends resource files alphabetically.
|
|
27
|
-
|
|
28
|
-
4. **Server** (`src/server.ts`): Wraps search and load into MCP tools with a lookup map for direct name/dirName access.
|
|
29
|
-
|
|
30
|
-
## Search Algorithm
|
|
31
|
-
|
|
32
|
-
Given a query string, the algorithm:
|
|
33
|
-
|
|
34
|
-
1. **Tokenizes** query: lowercase, remove non-alphanumeric (except hyphens), split on whitespace
|
|
35
|
-
2. **Scores** each skill entry:
|
|
36
|
-
- For each query token × search token pair:
|
|
37
|
-
- Exact match: **+1.0**
|
|
38
|
-
- Substring match (either contains the other): **+0.5**
|
|
39
|
-
- Bonus: query is substring of skill name: **+0.5**
|
|
40
|
-
- Bonus: query is substring of skill description: **+0.3**
|
|
41
|
-
3. **Normalizes** by dividing total score by query token count
|
|
42
|
-
4. **Filters** results below 0.2 threshold
|
|
43
|
-
5. **Sorts** descending by score, limits to top N (default 20)
|
|
44
|
-
|
|
45
|
-
## Design Decisions
|
|
46
|
-
|
|
47
|
-
- **In-memory index**: Skills are indexed once at startup. The index is small (frontmatter + tokens only, not full content). Search is O(n) over entries — fast enough for hundreds of skills.
|
|
48
|
-
|
|
49
|
-
- **Lazy content loading**: Full SKILL.md content is only read from disk when `load_skill` is called. This keeps memory usage low.
|
|
50
|
-
|
|
51
|
-
- **Token-based search over full-text**: Simpler, no external dependencies, good enough for skill names and descriptions. Avoids pulling in a search library.
|
|
52
|
-
|
|
53
|
-
- **YAML frontmatter**: Standard format used by many tools (Jekyll, Hugo, MDX). Makes skills compatible with existing tooling.
|
|
54
|
-
|
|
55
|
-
- **ESM-only**: Modern Node.js (22+) target. No CJS compatibility needed.
|
|
56
|
-
|
|
57
|
-
## Performance
|
|
58
|
-
|
|
59
|
-
- Index build: ~50ms for 700 skills (sequential file reads)
|
|
60
|
-
- Search: <1ms for typical queries against 700 entries
|
|
61
|
-
- Load: Single file read (~1ms), plus resource files if requested
|
package/docs/development.md
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
# Development Guide
|
|
2
|
-
|
|
3
|
-
## Setup
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
git clone <repo-url>
|
|
7
|
-
cd skill-library-mcp
|
|
8
|
-
pnpm install
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Requirements: Node.js 22+, pnpm 9.15+
|
|
12
|
-
|
|
13
|
-
## Testing
|
|
14
|
-
|
|
15
|
-
Tests use synthetic fixtures in `test/fixtures/`, not the real `skills/` directory. This means tests work in CI without any skill data.
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
pnpm test # Run all tests once
|
|
19
|
-
pnpm test:watch # Run in watch mode
|
|
20
|
-
pnpm test:coverage # Run with coverage report
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Test files:
|
|
24
|
-
- `test/skill-index.test.ts` — Index building, frontmatter parsing, resource detection
|
|
25
|
-
- `test/search.test.ts` — Search scoring, ranking, limits, edge cases
|
|
26
|
-
- `test/loader.test.ts` — Content loading, resource inclusion, alphabetical ordering
|
|
27
|
-
|
|
28
|
-
## Build
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
pnpm build # Produces dist/index.js via tsup
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Build config is in `tsup.config.ts`:
|
|
35
|
-
- Entry: `src/index.ts`
|
|
36
|
-
- Format: ESM
|
|
37
|
-
- Target: Node 22
|
|
38
|
-
- Adds `#!/usr/bin/env node` shebang for CLI usage
|
|
39
|
-
|
|
40
|
-
## Local MCP Testing
|
|
41
|
-
|
|
42
|
-
Test the MCP server handshake:
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
make mcp-test
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
This builds the project and sends a JSON-RPC `initialize` request via stdin. You should see a valid JSON-RPC response with server capabilities.
|
|
49
|
-
|
|
50
|
-
For interactive testing:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
pnpm dev
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Then paste JSON-RPC messages to stdin.
|
|
57
|
-
|
|
58
|
-
## Makefile Targets
|
|
59
|
-
|
|
60
|
-
| Target | Description |
|
|
61
|
-
|--------|-------------|
|
|
62
|
-
| `make test` | Run vitest |
|
|
63
|
-
| `make build` | Build with tsup |
|
|
64
|
-
| `make ci` | Run test + build |
|
|
65
|
-
| `make dev` | Run MCP server via tsx |
|
|
66
|
-
| `make clean` | Remove dist/, node_modules/, coverage/ |
|
|
67
|
-
| `make mcp-test` | Build and test MCP initialize handshake |
|
|
68
|
-
|
|
69
|
-
## Release Process
|
|
70
|
-
|
|
71
|
-
1. Bump version in `package.json`
|
|
72
|
-
2. Commit: `git commit -m "chore: release v1.x.x"`
|
|
73
|
-
3. Tag: `git tag v1.x.x`
|
|
74
|
-
4. Push: `git push && git push --tags`
|
|
75
|
-
5. GitHub Actions automatically: runs tests → builds → publishes to npm (OIDC) → creates GitHub Release with changelog
|
|
76
|
-
|
|
77
|
-
## Troubleshooting
|
|
78
|
-
|
|
79
|
-
**Tests fail with "module not found"**: Make sure you ran `pnpm install`. The project uses ESM with `.js` extensions in imports.
|
|
80
|
-
|
|
81
|
-
**`make mcp-test` shows no output**: The server writes logs to stderr. Make sure the build succeeded (`pnpm build`).
|
|
82
|
-
|
|
83
|
-
**pnpm version mismatch**: The project pins `pnpm@9.15.4` via `packageManager` in `package.json`. Use `corepack enable` to auto-install the correct version.
|