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.
Files changed (43) hide show
  1. package/.claude-plugin/plugin.json +12 -0
  2. package/.mcp.json +8 -0
  3. package/README.md +18 -2
  4. package/dist/index.js +96 -32
  5. package/package.json +30 -1
  6. package/.claude/settings.local.json +0 -10
  7. package/.github/workflows/ci.yml +0 -85
  8. package/.github/workflows/release.yml +0 -57
  9. package/.release-please-manifest.json +0 -3
  10. package/CLAUDE.md +0 -60
  11. package/Makefile +0 -18
  12. package/docs/architecture.md +0 -61
  13. package/docs/development.md +0 -83
  14. package/release-please-config.json +0 -17
  15. package/scripts/import-skills.ts +0 -103
  16. package/src/dedup.ts +0 -132
  17. package/src/index.ts +0 -26
  18. package/src/loader.ts +0 -33
  19. package/src/search.ts +0 -83
  20. package/src/server.ts +0 -83
  21. package/src/skill-index.ts +0 -105
  22. package/src/types.ts +0 -28
  23. package/test/dedup.test.ts +0 -33
  24. package/test/fixtures/allowed-tools/SKILL.md +0 -9
  25. package/test/fixtures/basic-skill/SKILL.md +0 -11
  26. package/test/fixtures/empty-description/SKILL.md +0 -7
  27. package/test/fixtures/invalid-frontmatter/SKILL.md +0 -7
  28. package/test/fixtures/multiline-description/SKILL.md +0 -11
  29. package/test/fixtures/no-frontmatter/SKILL.md +0 -3
  30. package/test/fixtures/skill-with-hyphenated-resources/SKILL.md +0 -8
  31. package/test/fixtures/skill-with-hyphenated-resources/resources/implementation-playbook.md +0 -3
  32. package/test/fixtures/skill-with-hyphenated-resources/resources/quick-start-guide.md +0 -3
  33. package/test/fixtures/skill-with-resources/SKILL.md +0 -8
  34. package/test/fixtures/skill-with-resources/resources/examples.md +0 -3
  35. package/test/fixtures/skill-with-resources/resources/guide.md +0 -3
  36. package/test/integration.test.ts +0 -148
  37. package/test/loader.test.ts +0 -78
  38. package/test/search.test.ts +0 -135
  39. package/test/server.test.ts +0 -94
  40. package/test/skill-index.test.ts +0 -144
  41. package/tsconfig.json +0 -15
  42. package/tsup.config.ts +0 -12
  43. 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
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "skill-library": {
4
+ "command": "node",
5
+ "args": ["${CLAUDE_PLUGIN_ROOT}/dist/index.js"]
6
+ }
7
+ }
8
+ }
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
- npx -y skill-library-mcp
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
- Add it to your tool's MCP configuration:
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, access } from "fs/promises";
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: parsed.metadata,
22
- allowedTools: parsed.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 access(skillPath);
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
- ...tokenize(frontmatter.description)
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
- return { entries, idfScores, totalDocs };
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 = tokenize2(query);
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
- version: "1.0.0"
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.0.0",
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
  },
@@ -1,10 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "mcp__skill-library__search_skill",
5
- "Bash(for name in \"AI Code Review\" \"CI\" \"brand-guidelines\" \"docx\" \"pdf\")",
6
- "Bash(do:*)",
7
- "Bash(done)"
8
- ]
9
- }
10
- }
@@ -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
@@ -1,3 +0,0 @@
1
- {
2
- ".": "1.0.0"
3
- }
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
@@ -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
@@ -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.