@olaservo/skill-jack-mcp 0.1.1 → 0.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/README.md CHANGED
@@ -35,17 +35,22 @@ npm run build
35
35
 
36
36
  ## Usage
37
37
 
38
- Configure a skills directory containing your Agent Skills:
38
+ Configure one or more skills directories containing your Agent Skills:
39
39
 
40
40
  ```bash
41
- # Pass skills directory as argument
41
+ # Single directory
42
42
  skill-jack-mcp /path/to/skills
43
43
 
44
- # Or use environment variable
44
+ # Multiple directories (separate args or comma-separated)
45
+ skill-jack-mcp /path/to/skills /path/to/more/skills
46
+ skill-jack-mcp /path/to/skills,/path/to/more/skills
47
+
48
+ # Using environment variable (comma-separated for multiple)
45
49
  SKILLS_DIR=/path/to/skills skill-jack-mcp
50
+ SKILLS_DIR=/path/to/skills,/path/to/more/skills skill-jack-mcp
46
51
  ```
47
52
 
48
- The server scans the directory and its `.claude/skills/` and `skills/` subdirectories for skills.
53
+ Each directory is scanned along with its `.claude/skills/` and `skills/` subdirectories for skills. Duplicate skill names are handled by keeping the first occurrence.
49
54
 
50
55
  **Windows note**: Use forward slashes in paths when using with MCP Inspector:
51
56
  ```bash
@@ -56,14 +61,14 @@ skill-jack-mcp "C:/Users/you/skills"
56
61
 
57
62
  The server implements the [Agent Skills](https://agentskills.io) progressive disclosure pattern:
58
63
 
59
- 1. **At startup**: Discovers skills from configured directory
64
+ 1. **At startup**: Discovers skills from configured directories
60
65
  2. **On connection**: Server instructions (with skill metadata) are sent in the initialize response
61
66
  3. **On tool call**: Agent calls `skill` tool to load full SKILL.md content
62
67
 
63
68
  ```
64
69
  ┌─────────────────────────────────────────────────────────┐
65
70
  │ Server starts │
66
- │ • Discovers skills from configured directory
71
+ │ • Discovers skills from configured directories
67
72
  │ • Generates instructions with skill metadata │
68
73
  │ ↓ │
69
74
  │ MCP Client connects │
@@ -203,7 +208,7 @@ These are loaded into the model's system prompt by [clients](https://modelcontex
203
208
 
204
209
  ## Skill Discovery
205
210
 
206
- Skills are discovered at startup from the configured directory. The server checks:
211
+ Skills are discovered at startup from the configured directories. For each directory, the server checks:
207
212
  - The directory itself for skill subdirectories
208
213
  - `.claude/skills/` subdirectory
209
214
  - `skills/` subdirectory
package/dist/index.d.ts CHANGED
@@ -6,7 +6,8 @@
6
6
  * Provides global skills with tools for progressive disclosure.
7
7
  *
8
8
  * Usage:
9
- * skill-jack-mcp /path/to/skills # Skills directory (required)
10
- * SKILLS_DIR=/path/to/skills skill-jack-mcp
9
+ * skill-jack-mcp /path/to/skills [/path2 ...] # One or more directories
10
+ * SKILLS_DIR=/path/to/skills skill-jack-mcp # Single directory via env
11
+ * SKILLS_DIR=/path1,/path2 skill-jack-mcp # Multiple (comma-separated)
11
12
  */
12
13
  export {};
package/dist/index.js CHANGED
@@ -6,8 +6,9 @@
6
6
  * Provides global skills with tools for progressive disclosure.
7
7
  *
8
8
  * Usage:
9
- * skill-jack-mcp /path/to/skills # Skills directory (required)
10
- * SKILLS_DIR=/path/to/skills skill-jack-mcp
9
+ * skill-jack-mcp /path/to/skills [/path2 ...] # One or more directories
10
+ * SKILLS_DIR=/path/to/skills skill-jack-mcp # Single directory via env
11
+ * SKILLS_DIR=/path1,/path2 skill-jack-mcp # Multiple (comma-separated)
11
12
  */
12
13
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
14
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -22,20 +23,40 @@ import { createSubscriptionManager, registerSubscriptionHandlers, } from "./subs
22
23
  */
23
24
  const SKILL_SUBDIRS = [".claude/skills", "skills"];
24
25
  /**
25
- * Get the skills directory from command line args or environment.
26
+ * Separator for multiple paths in SKILLS_DIR environment variable.
27
+ * Comma works cross-platform (not valid in file paths on any OS).
26
28
  */
27
- function getSkillsDir() {
28
- // Check command line argument first
29
+ const PATH_LIST_SEPARATOR = ",";
30
+ /**
31
+ * Get the skills directories from command line args and/or environment.
32
+ * Returns deduplicated, resolved paths.
33
+ */
34
+ function getSkillsDirs() {
35
+ const dirs = [];
36
+ // Collect all non-flag command-line arguments (comma-separated supported)
29
37
  const args = process.argv.slice(2);
30
- if (args.length > 0 && args[0] && !args[0].startsWith("-")) {
31
- return path.resolve(args[0]);
38
+ for (const arg of args) {
39
+ if (!arg.startsWith("-")) {
40
+ const paths = arg
41
+ .split(PATH_LIST_SEPARATOR)
42
+ .map((p) => p.trim())
43
+ .filter((p) => p.length > 0)
44
+ .map((p) => path.resolve(p));
45
+ dirs.push(...paths);
46
+ }
32
47
  }
33
- // Fall back to environment variable
48
+ // Also check environment variable (comma-separated supported)
34
49
  const envDir = process.env.SKILLS_DIR;
35
50
  if (envDir) {
36
- return path.resolve(envDir);
51
+ const envPaths = envDir
52
+ .split(PATH_LIST_SEPARATOR)
53
+ .map((p) => p.trim())
54
+ .filter((p) => p.length > 0)
55
+ .map((p) => path.resolve(p));
56
+ dirs.push(...envPaths);
37
57
  }
38
- return null;
58
+ // Deduplicate by resolved path
59
+ return [...new Set(dirs)];
39
60
  }
40
61
  /**
41
62
  * Shared state for skill management.
@@ -46,20 +67,37 @@ const skillState = {
46
67
  instructions: "",
47
68
  };
48
69
  /**
49
- * Discover skills from configured directory.
50
- * Checks both the directory itself and standard subdirectories.
70
+ * Discover skills from multiple configured directories.
71
+ * Each directory is checked along with its standard subdirectories.
72
+ * Handles duplicate skill names by keeping first occurrence.
51
73
  */
52
- function discoverSkillsFromDir(skillsDir) {
74
+ function discoverSkillsFromDirs(skillsDirs) {
53
75
  const allSkills = [];
54
- // Check if the directory itself contains skills
55
- const directSkills = discoverSkills(skillsDir);
56
- allSkills.push(...directSkills);
57
- // Also check standard subdirectories
58
- for (const subdir of SKILL_SUBDIRS) {
59
- const subPath = path.join(skillsDir, subdir);
60
- if (fs.existsSync(subPath)) {
61
- const subdirSkills = discoverSkills(subPath);
62
- allSkills.push(...subdirSkills);
76
+ const seenNames = new Map(); // name -> source directory
77
+ for (const skillsDir of skillsDirs) {
78
+ if (!fs.existsSync(skillsDir)) {
79
+ console.error(`Warning: Skills directory not found: ${skillsDir}`);
80
+ continue;
81
+ }
82
+ console.error(`Scanning skills directory: ${skillsDir}`);
83
+ // Check if the directory itself contains skills
84
+ const dirSkills = discoverSkills(skillsDir);
85
+ // Also check standard subdirectories
86
+ for (const subdir of SKILL_SUBDIRS) {
87
+ const subPath = path.join(skillsDir, subdir);
88
+ if (fs.existsSync(subPath)) {
89
+ dirSkills.push(...discoverSkills(subPath));
90
+ }
91
+ }
92
+ // Add skills, checking for duplicates
93
+ for (const skill of dirSkills) {
94
+ if (seenNames.has(skill.name)) {
95
+ console.error(`Warning: Duplicate skill "${skill.name}" found in ${path.dirname(skill.path)} ` +
96
+ `(already loaded from ${seenNames.get(skill.name)})`);
97
+ continue; // Skip duplicate
98
+ }
99
+ seenNames.set(skill.name, path.dirname(skill.path));
100
+ allSkills.push(skill);
63
101
  }
64
102
  }
65
103
  return allSkills;
@@ -69,16 +107,17 @@ function discoverSkillsFromDir(skillsDir) {
69
107
  */
70
108
  const subscriptionManager = createSubscriptionManager();
71
109
  async function main() {
72
- const skillsDir = getSkillsDir();
73
- if (!skillsDir) {
110
+ const skillsDirs = getSkillsDirs();
111
+ if (skillsDirs.length === 0) {
74
112
  console.error("No skills directory configured.");
75
- console.error("Usage: skill-jack-mcp /path/to/skills");
113
+ console.error("Usage: skill-jack-mcp /path/to/skills [/path/to/more/skills ...]");
76
114
  console.error(" or: SKILLS_DIR=/path/to/skills skill-jack-mcp");
115
+ console.error(" or: SKILLS_DIR=/path1,/path2 skill-jack-mcp");
77
116
  process.exit(1);
78
117
  }
79
- console.error(`Skills directory: ${skillsDir}`);
118
+ console.error(`Skills directories: ${skillsDirs.join(", ")}`);
80
119
  // Discover skills at startup
81
- const skills = discoverSkillsFromDir(skillsDir);
120
+ const skills = discoverSkillsFromDirs(skillsDirs);
82
121
  skillState.skillMap = createSkillMap(skills);
83
122
  skillState.instructions = generateInstructions(skills);
84
123
  console.error(`Discovered ${skills.length} skill(s)`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olaservo/skill-jack-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server that discovers and serves Agent Skills. I know kung fu.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",