@skilljack/mcp 0.7.0 → 0.7.1
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/dist/github-config.d.ts +83 -0
- package/dist/github-config.js +191 -0
- package/dist/github-polling.d.ts +35 -0
- package/dist/github-polling.js +108 -0
- package/dist/github-sync.d.ts +49 -0
- package/dist/github-sync.js +259 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.js +309 -53
- package/dist/skill-config-tool.d.ts +22 -0
- package/dist/skill-config-tool.js +495 -0
- package/dist/skill-config.d.ts +163 -0
- package/dist/skill-config.js +450 -0
- package/dist/skill-discovery.d.ts +52 -1
- package/dist/skill-discovery.js +70 -1
- package/dist/skill-display-tool.d.ts +22 -0
- package/dist/skill-display-tool.js +302 -0
- package/dist/skill-prompts.d.ts +4 -0
- package/dist/skill-prompts.js +62 -10
- package/dist/skill-resources.d.ts +6 -3
- package/dist/skill-resources.js +10 -94
- package/dist/skill-tool.js +4 -3
- package/dist/subscriptions.d.ts +1 -1
- package/dist/subscriptions.js +1 -1
- package/dist/ui/mcp-app.d.ts +1 -0
- package/dist/ui/mcp-app.html +278 -0
- package/dist/ui/mcp-app.js +484 -0
- package/dist/ui/skill-display.d.ts +1 -0
- package/dist/ui/skill-display.html +188 -0
- package/dist/ui/skill-display.js +269 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,58 +6,166 @@
|
|
|
6
6
|
* Provides global skills with tools for progressive disclosure.
|
|
7
7
|
*
|
|
8
8
|
* Usage:
|
|
9
|
-
* skilljack-mcp /path/to/skills [/path2 ...]
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* skilljack-mcp /path/to/skills [/path2 ...] # Local directories
|
|
10
|
+
* skilljack-mcp --static /path/to/skills # Static mode (no file watching)
|
|
11
|
+
* skilljack-mcp github.com/owner/repo # GitHub repository
|
|
12
|
+
* skilljack-mcp /local github.com/owner/repo # Mixed local + GitHub
|
|
13
|
+
* SKILLS_DIR=/path,github.com/owner/repo skilljack-mcp # Via environment
|
|
14
|
+
* SKILLJACK_STATIC=true skilljack-mcp # Static mode via env
|
|
15
|
+
* (or configure local directories via the skill-config UI)
|
|
16
|
+
*
|
|
17
|
+
* Options:
|
|
18
|
+
* --static Freeze skills list at startup. Disables file watching and
|
|
19
|
+
* tools/prompts listChanged notifications. Resource subscriptions
|
|
20
|
+
* remain fully dynamic.
|
|
12
21
|
*/
|
|
13
22
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
23
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
24
|
import chokidar from "chokidar";
|
|
16
25
|
import * as fs from "node:fs";
|
|
17
26
|
import * as path from "node:path";
|
|
18
|
-
import {
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
import { discoverSkills, createSkillMap, applyInvocationOverrides, DEFAULT_SKILL_SOURCE, BUNDLED_SKILL_SOURCE } from "./skill-discovery.js";
|
|
19
29
|
import { registerSkillTool, getToolDescription } from "./skill-tool.js";
|
|
20
30
|
import { registerSkillResources } from "./skill-resources.js";
|
|
31
|
+
import { registerSkillPrompts, refreshPrompts } from "./skill-prompts.js";
|
|
21
32
|
import { createSubscriptionManager, registerSubscriptionHandlers, refreshSubscriptions, } from "./subscriptions.js";
|
|
33
|
+
import { getActiveDirectories, getSkillInvocationOverrides, getStaticModeFromConfig } from "./skill-config.js";
|
|
34
|
+
import { registerSkillConfigTool } from "./skill-config-tool.js";
|
|
35
|
+
import { registerSkillDisplayTool } from "./skill-display-tool.js";
|
|
36
|
+
import { isGitHubUrl, parseGitHubUrl, isRepoAllowed, getGitHubConfig, getRepoCachePath, } from "./github-config.js";
|
|
37
|
+
import { syncAllRepos } from "./github-sync.js";
|
|
38
|
+
import { createPollingManager } from "./github-polling.js";
|
|
22
39
|
/**
|
|
23
40
|
* Subdirectories to check for skills within the configured directory.
|
|
24
41
|
*/
|
|
25
42
|
const SKILL_SUBDIRS = [".claude/skills", "skills"];
|
|
26
43
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
44
|
+
* Get the path to bundled skills directory.
|
|
45
|
+
* Resolves relative to the compiled module location.
|
|
29
46
|
*/
|
|
30
|
-
|
|
47
|
+
function getBundledSkillsDir() {
|
|
48
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
49
|
+
// From dist/index.js, go up one level to package root, then into skills/
|
|
50
|
+
return path.resolve(currentDir, "..", "skills");
|
|
51
|
+
}
|
|
31
52
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
53
|
+
* Build a directory-to-source map from current configuration.
|
|
54
|
+
* Maps both main directories and their standard subdirectories.
|
|
55
|
+
*
|
|
56
|
+
* @param localDirs - Local skill directories
|
|
57
|
+
* @param githubSpecs - GitHub repository specifications
|
|
58
|
+
* @param cacheDir - GitHub cache directory path
|
|
59
|
+
* @param bundledDir - Optional bundled skills directory
|
|
34
60
|
*/
|
|
35
|
-
function
|
|
36
|
-
const
|
|
37
|
-
//
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
61
|
+
function buildDirectorySourceMap(localDirs, githubSpecs, cacheDir, bundledDir) {
|
|
62
|
+
const map = {};
|
|
63
|
+
// Map local directories
|
|
64
|
+
for (const dir of localDirs) {
|
|
65
|
+
const source = {
|
|
66
|
+
type: "local",
|
|
67
|
+
displayName: "Local",
|
|
68
|
+
};
|
|
69
|
+
map[dir] = source;
|
|
70
|
+
// Also map standard subdirectories
|
|
71
|
+
for (const subdir of SKILL_SUBDIRS) {
|
|
72
|
+
map[path.join(dir, subdir)] = source;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Map GitHub cache directories
|
|
76
|
+
for (const spec of githubSpecs) {
|
|
77
|
+
const cachePath = getRepoCachePath(spec, cacheDir);
|
|
78
|
+
const source = {
|
|
79
|
+
type: "github",
|
|
80
|
+
displayName: `${spec.owner}/${spec.repo}`,
|
|
81
|
+
owner: spec.owner,
|
|
82
|
+
repo: spec.repo,
|
|
83
|
+
};
|
|
84
|
+
map[cachePath] = source;
|
|
85
|
+
// Also map standard subdirectories
|
|
86
|
+
for (const subdir of SKILL_SUBDIRS) {
|
|
87
|
+
map[path.join(cachePath, subdir)] = source;
|
|
47
88
|
}
|
|
48
89
|
}
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.
|
|
55
|
-
|
|
56
|
-
.map((p) => path.resolve(p));
|
|
57
|
-
dirs.push(...envPaths);
|
|
90
|
+
// Map bundled skills directory
|
|
91
|
+
if (bundledDir) {
|
|
92
|
+
map[bundledDir] = BUNDLED_SKILL_SOURCE;
|
|
93
|
+
// Also map standard subdirectories
|
|
94
|
+
for (const subdir of SKILL_SUBDIRS) {
|
|
95
|
+
map[path.join(bundledDir, subdir)] = BUNDLED_SKILL_SOURCE;
|
|
96
|
+
}
|
|
58
97
|
}
|
|
59
|
-
|
|
60
|
-
|
|
98
|
+
return map;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Current skill directories (mutable to support UI-driven changes).
|
|
102
|
+
* This includes both local directories and GitHub cache directories.
|
|
103
|
+
*/
|
|
104
|
+
let currentSkillsDirs = [];
|
|
105
|
+
/**
|
|
106
|
+
* GitHub specs that are currently being polled.
|
|
107
|
+
*/
|
|
108
|
+
let currentGithubSpecs = [];
|
|
109
|
+
/**
|
|
110
|
+
* Current directory-to-source map for skill discovery.
|
|
111
|
+
* Maps directory paths to their source info (local or GitHub).
|
|
112
|
+
*/
|
|
113
|
+
let currentSourceMap = {};
|
|
114
|
+
/**
|
|
115
|
+
* Check if static mode is enabled.
|
|
116
|
+
* Static mode freezes the skills list at startup - no file watching,
|
|
117
|
+
* no listChanged notifications for tools/prompts.
|
|
118
|
+
* Priority: CLI flag > env var > config file
|
|
119
|
+
*/
|
|
120
|
+
function getStaticMode() {
|
|
121
|
+
// Check CLI flag (highest priority)
|
|
122
|
+
const args = process.argv.slice(2);
|
|
123
|
+
if (args.includes("--static")) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
// Check environment variable
|
|
127
|
+
const envValue = process.env.SKILLJACK_STATIC?.toLowerCase();
|
|
128
|
+
if (envValue === "true" || envValue === "1" || envValue === "yes") {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
// Check config file (lowest priority)
|
|
132
|
+
return getStaticModeFromConfig();
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Classify paths as local directories or GitHub repositories.
|
|
136
|
+
* GitHub URLs are detected by checking for "github.com" in the path.
|
|
137
|
+
*/
|
|
138
|
+
function classifyPaths(paths) {
|
|
139
|
+
const localDirs = [];
|
|
140
|
+
const githubSpecs = [];
|
|
141
|
+
for (const p of paths) {
|
|
142
|
+
if (isGitHubUrl(p)) {
|
|
143
|
+
try {
|
|
144
|
+
const spec = parseGitHubUrl(p);
|
|
145
|
+
githubSpecs.push(spec);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
console.error(`Warning: Invalid GitHub URL "${p}": ${error}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Local directory - resolve the path
|
|
153
|
+
localDirs.push(path.resolve(p));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Deduplicate local dirs
|
|
157
|
+
const uniqueLocalDirs = [...new Set(localDirs)];
|
|
158
|
+
// Deduplicate GitHub specs by owner/repo
|
|
159
|
+
const seenRepos = new Set();
|
|
160
|
+
const uniqueGithubSpecs = githubSpecs.filter((spec) => {
|
|
161
|
+
const key = `${spec.owner}/${spec.repo}`;
|
|
162
|
+
if (seenRepos.has(key)) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
seenRepos.add(key);
|
|
166
|
+
return true;
|
|
167
|
+
});
|
|
168
|
+
return { localDirs: uniqueLocalDirs, githubSpecs: uniqueGithubSpecs };
|
|
61
169
|
}
|
|
62
170
|
/**
|
|
63
171
|
* Shared state for skill management.
|
|
@@ -70,8 +178,11 @@ const skillState = {
|
|
|
70
178
|
* Discover skills from multiple configured directories.
|
|
71
179
|
* Each directory is checked along with its standard subdirectories.
|
|
72
180
|
* Handles duplicate skill names by keeping first occurrence.
|
|
181
|
+
*
|
|
182
|
+
* @param skillsDirs - The skill directories to scan
|
|
183
|
+
* @param sourceMap - Map from directory paths to source info
|
|
73
184
|
*/
|
|
74
|
-
function discoverSkillsFromDirs(skillsDirs) {
|
|
185
|
+
function discoverSkillsFromDirs(skillsDirs, sourceMap) {
|
|
75
186
|
const allSkills = [];
|
|
76
187
|
const seenNames = new Map(); // name -> source directory
|
|
77
188
|
for (const skillsDir of skillsDirs) {
|
|
@@ -80,13 +191,17 @@ function discoverSkillsFromDirs(skillsDirs) {
|
|
|
80
191
|
continue;
|
|
81
192
|
}
|
|
82
193
|
console.error(`Scanning skills directory: ${skillsDir}`);
|
|
194
|
+
// Get source info for this directory (default to local if not in map)
|
|
195
|
+
const dirSource = sourceMap[skillsDir] || DEFAULT_SKILL_SOURCE;
|
|
83
196
|
// Check if the directory itself contains skills
|
|
84
|
-
const dirSkills = discoverSkills(skillsDir);
|
|
197
|
+
const dirSkills = discoverSkills(skillsDir, dirSource);
|
|
85
198
|
// Also check standard subdirectories
|
|
86
199
|
for (const subdir of SKILL_SUBDIRS) {
|
|
87
200
|
const subPath = path.join(skillsDir, subdir);
|
|
88
201
|
if (fs.existsSync(subPath)) {
|
|
89
|
-
|
|
202
|
+
// Use subpath source if available, otherwise inherit from parent
|
|
203
|
+
const subSource = sourceMap[subPath] || dirSource;
|
|
204
|
+
dirSkills.push(...discoverSkills(subPath, subSource));
|
|
90
205
|
}
|
|
91
206
|
}
|
|
92
207
|
// Add skills, checking for duplicates
|
|
@@ -114,13 +229,17 @@ const SKILL_REFRESH_DEBOUNCE_MS = 500;
|
|
|
114
229
|
* @param skillsDirs - The configured skill directories
|
|
115
230
|
* @param server - The MCP server instance
|
|
116
231
|
* @param skillTool - The registered skill tool to update
|
|
232
|
+
* @param promptRegistry - For refreshing skill prompts
|
|
117
233
|
* @param subscriptionManager - For refreshing resource subscriptions
|
|
118
234
|
*/
|
|
119
|
-
function refreshSkills(skillsDirs, server, skillTool, subscriptionManager) {
|
|
235
|
+
function refreshSkills(skillsDirs, server, skillTool, promptRegistry, subscriptionManager) {
|
|
120
236
|
console.error("Refreshing skills...");
|
|
121
|
-
// Re-discover all skills
|
|
122
|
-
|
|
237
|
+
// Re-discover all skills using current source map
|
|
238
|
+
let skills = discoverSkillsFromDirs(skillsDirs, currentSourceMap);
|
|
123
239
|
const oldCount = skillState.skillMap.size;
|
|
240
|
+
// Apply invocation overrides from config
|
|
241
|
+
const overrides = getSkillInvocationOverrides();
|
|
242
|
+
skills = applyInvocationOverrides(skills, overrides);
|
|
124
243
|
// Update shared state
|
|
125
244
|
skillState.skillMap = createSkillMap(skills);
|
|
126
245
|
console.error(`Skills refreshed: ${oldCount} -> ${skills.length} skill(s)`);
|
|
@@ -128,6 +247,8 @@ function refreshSkills(skillsDirs, server, skillTool, subscriptionManager) {
|
|
|
128
247
|
skillTool.update({
|
|
129
248
|
description: getToolDescription(skillState),
|
|
130
249
|
});
|
|
250
|
+
// Refresh prompts to match new skill state
|
|
251
|
+
refreshPrompts(server, skillState, promptRegistry);
|
|
131
252
|
// Refresh resource subscriptions to match new skill state
|
|
132
253
|
refreshSubscriptions(subscriptionManager, skillState, (uri) => {
|
|
133
254
|
server.server.notification({
|
|
@@ -148,9 +269,10 @@ function refreshSkills(skillsDirs, server, skillTool, subscriptionManager) {
|
|
|
148
269
|
* @param skillsDirs - The configured skill directories
|
|
149
270
|
* @param server - The MCP server instance
|
|
150
271
|
* @param skillTool - The registered skill tool to update
|
|
272
|
+
* @param promptRegistry - For refreshing skill prompts
|
|
151
273
|
* @param subscriptionManager - For refreshing subscriptions
|
|
152
274
|
*/
|
|
153
|
-
function watchSkillDirectories(skillsDirs, server, skillTool, subscriptionManager) {
|
|
275
|
+
function watchSkillDirectories(skillsDirs, server, skillTool, promptRegistry, subscriptionManager) {
|
|
154
276
|
let refreshTimeout = null;
|
|
155
277
|
const debouncedRefresh = () => {
|
|
156
278
|
if (refreshTimeout) {
|
|
@@ -158,7 +280,7 @@ function watchSkillDirectories(skillsDirs, server, skillTool, subscriptionManage
|
|
|
158
280
|
}
|
|
159
281
|
refreshTimeout = setTimeout(() => {
|
|
160
282
|
refreshTimeout = null;
|
|
161
|
-
refreshSkills(skillsDirs, server, skillTool, subscriptionManager);
|
|
283
|
+
refreshSkills(skillsDirs, server, skillTool, promptRegistry, subscriptionManager);
|
|
162
284
|
}, SKILL_REFRESH_DEBOUNCE_MS);
|
|
163
285
|
};
|
|
164
286
|
// Build list of paths to watch
|
|
@@ -230,36 +352,170 @@ function watchSkillDirectories(skillsDirs, server, skillTool, subscriptionManage
|
|
|
230
352
|
*/
|
|
231
353
|
const subscriptionManager = createSubscriptionManager();
|
|
232
354
|
async function main() {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
355
|
+
// Check if static mode is enabled
|
|
356
|
+
const isStatic = getStaticMode();
|
|
357
|
+
// Get skill directories from CLI args, env var, or config file
|
|
358
|
+
// This returns paths that may include GitHub URLs
|
|
359
|
+
const allPaths = getActiveDirectories();
|
|
360
|
+
// Classify paths as local or GitHub
|
|
361
|
+
const { localDirs, githubSpecs } = classifyPaths(allPaths);
|
|
362
|
+
// Get GitHub configuration
|
|
363
|
+
const githubConfig = getGitHubConfig();
|
|
364
|
+
// Sync GitHub repositories
|
|
365
|
+
let githubDirs = [];
|
|
366
|
+
if (githubSpecs.length > 0) {
|
|
367
|
+
console.error(`GitHub repos: ${githubSpecs.map((s) => `${s.owner}/${s.repo}`).join(", ")}`);
|
|
368
|
+
// Filter by allowlist
|
|
369
|
+
for (const spec of githubSpecs) {
|
|
370
|
+
if (!isRepoAllowed(spec, githubConfig)) {
|
|
371
|
+
console.error(`Blocked: ${spec.owner}/${spec.repo} not in allowed orgs/users. ` +
|
|
372
|
+
`Set GITHUB_ALLOWED_ORGS or GITHUB_ALLOWED_USERS to permit.`);
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
currentGithubSpecs.push(spec);
|
|
376
|
+
}
|
|
377
|
+
if (currentGithubSpecs.length > 0) {
|
|
378
|
+
console.error(`Syncing ${currentGithubSpecs.length} GitHub repo(s)...`);
|
|
379
|
+
const syncOptions = {
|
|
380
|
+
cacheDir: githubConfig.cacheDir,
|
|
381
|
+
token: githubConfig.token,
|
|
382
|
+
shallowClone: true,
|
|
383
|
+
};
|
|
384
|
+
const results = await syncAllRepos(currentGithubSpecs, syncOptions);
|
|
385
|
+
// Collect successful sync paths
|
|
386
|
+
for (const result of results) {
|
|
387
|
+
if (!result.error) {
|
|
388
|
+
githubDirs.push(result.localPath);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
console.error(`Successfully synced ${githubDirs.length}/${currentGithubSpecs.length} repo(s)`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// Get bundled skills directory (ships with the package)
|
|
395
|
+
const bundledSkillsDir = getBundledSkillsDir();
|
|
396
|
+
const hasBundledSkills = fs.existsSync(bundledSkillsDir);
|
|
397
|
+
// Combine all skill directories
|
|
398
|
+
// User directories come first so they can override bundled skills (first-wins deduplication)
|
|
399
|
+
currentSkillsDirs = [...localDirs, ...githubDirs, ...(hasBundledSkills ? [bundledSkillsDir] : [])];
|
|
400
|
+
// Build source map for skill discovery
|
|
401
|
+
currentSourceMap = buildDirectorySourceMap(localDirs, currentGithubSpecs, githubConfig.cacheDir, hasBundledSkills ? bundledSkillsDir : undefined);
|
|
402
|
+
// Log configured directories
|
|
403
|
+
if (localDirs.length > 0) {
|
|
404
|
+
console.error(`Local directories: ${localDirs.join(", ")}`);
|
|
405
|
+
}
|
|
406
|
+
if (githubDirs.length > 0) {
|
|
407
|
+
console.error(`GitHub cache directories: ${githubDirs.join(", ")}`);
|
|
408
|
+
}
|
|
409
|
+
if (hasBundledSkills) {
|
|
410
|
+
console.error(`Bundled skills: ${bundledSkillsDir}`);
|
|
411
|
+
}
|
|
412
|
+
if (isStatic) {
|
|
413
|
+
console.error("Static mode enabled - skills list frozen at startup");
|
|
240
414
|
}
|
|
241
|
-
console.error(`Skills directories: ${skillsDirs.join(", ")}`);
|
|
242
415
|
// Discover skills at startup
|
|
243
|
-
|
|
416
|
+
let skills = discoverSkillsFromDirs(currentSkillsDirs, currentSourceMap);
|
|
417
|
+
// Apply invocation overrides from config
|
|
418
|
+
const overrides = getSkillInvocationOverrides();
|
|
419
|
+
skills = applyInvocationOverrides(skills, overrides);
|
|
244
420
|
skillState.skillMap = createSkillMap(skills);
|
|
245
421
|
console.error(`Discovered ${skills.length} skill(s)`);
|
|
246
422
|
// Create the MCP server
|
|
423
|
+
// In static mode, disable listChanged for tools/prompts (skills list is frozen)
|
|
424
|
+
// Resource subscriptions remain dynamic for individual skill file watching
|
|
247
425
|
const server = new McpServer({
|
|
248
426
|
name: "skilljack-mcp",
|
|
249
427
|
version: "1.0.0",
|
|
250
428
|
}, {
|
|
251
429
|
capabilities: {
|
|
252
|
-
tools: { listChanged:
|
|
430
|
+
tools: { listChanged: !isStatic },
|
|
253
431
|
resources: { subscribe: true, listChanged: true },
|
|
432
|
+
prompts: { listChanged: !isStatic },
|
|
254
433
|
},
|
|
255
434
|
});
|
|
256
|
-
// Register tools and
|
|
435
|
+
// Register tools, resources, and prompts
|
|
257
436
|
const skillTool = registerSkillTool(server, skillState);
|
|
258
437
|
registerSkillResources(server, skillState);
|
|
438
|
+
const promptRegistry = registerSkillPrompts(server, skillState);
|
|
259
439
|
// Register subscription handlers for resource file watching
|
|
260
440
|
registerSubscriptionHandlers(server, skillState, subscriptionManager);
|
|
261
|
-
//
|
|
262
|
-
|
|
441
|
+
// Register skill-config tool for UI-based directory configuration
|
|
442
|
+
// Skip in static mode since skills list is frozen
|
|
443
|
+
if (!isStatic) {
|
|
444
|
+
registerSkillConfigTool(server, skillState, async () => {
|
|
445
|
+
// Callback when directories or GitHub settings change via UI
|
|
446
|
+
// Reload directories from config and refresh skills
|
|
447
|
+
const newPaths = getActiveDirectories();
|
|
448
|
+
const { localDirs: newLocalDirs, githubSpecs: newGithubSpecs } = classifyPaths(newPaths);
|
|
449
|
+
// Get fresh GitHub config (in case allowed orgs/users changed)
|
|
450
|
+
const freshGithubConfig = getGitHubConfig();
|
|
451
|
+
// Filter GitHub specs by allowlist and sync
|
|
452
|
+
const allowedGithubSpecs = [];
|
|
453
|
+
for (const spec of newGithubSpecs) {
|
|
454
|
+
if (isRepoAllowed(spec, freshGithubConfig)) {
|
|
455
|
+
allowedGithubSpecs.push(spec);
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
console.error(`Blocked: ${spec.owner}/${spec.repo} not in allowed orgs/users.`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// Sync any GitHub repos
|
|
462
|
+
let newGithubDirs = [];
|
|
463
|
+
if (allowedGithubSpecs.length > 0) {
|
|
464
|
+
console.error(`Syncing ${allowedGithubSpecs.length} GitHub repo(s)...`);
|
|
465
|
+
const syncOptions = {
|
|
466
|
+
cacheDir: freshGithubConfig.cacheDir,
|
|
467
|
+
token: freshGithubConfig.token,
|
|
468
|
+
shallowClone: true,
|
|
469
|
+
};
|
|
470
|
+
const results = await syncAllRepos(allowedGithubSpecs, syncOptions);
|
|
471
|
+
for (const result of results) {
|
|
472
|
+
if (!result.error) {
|
|
473
|
+
newGithubDirs.push(result.localPath);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
console.error(`Successfully synced ${newGithubDirs.length}/${allowedGithubSpecs.length} repo(s)`);
|
|
477
|
+
}
|
|
478
|
+
// Update current state
|
|
479
|
+
currentGithubSpecs = allowedGithubSpecs;
|
|
480
|
+
githubDirs = newGithubDirs;
|
|
481
|
+
// Include bundled skills (last, so user skills take precedence)
|
|
482
|
+
currentSkillsDirs = [...newLocalDirs, ...newGithubDirs, ...(hasBundledSkills ? [bundledSkillsDir] : [])];
|
|
483
|
+
currentSourceMap = buildDirectorySourceMap(newLocalDirs, allowedGithubSpecs, freshGithubConfig.cacheDir, hasBundledSkills ? bundledSkillsDir : undefined);
|
|
484
|
+
console.error(`Config changed via UI. Directories: ${currentSkillsDirs.join(", ") || "(none)"}`);
|
|
485
|
+
refreshSkills(currentSkillsDirs, server, skillTool, promptRegistry, subscriptionManager);
|
|
486
|
+
});
|
|
487
|
+
// Register skill-display tool for UI-based invocation settings
|
|
488
|
+
registerSkillDisplayTool(server, skillState, () => {
|
|
489
|
+
// Callback when invocation settings change via UI
|
|
490
|
+
// Refresh skills to apply new overrides
|
|
491
|
+
console.error("Invocation settings changed via UI. Refreshing skills...");
|
|
492
|
+
refreshSkills(currentSkillsDirs, server, skillTool, promptRegistry, subscriptionManager);
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
// Set up file watchers for skill directory changes (skip in static mode)
|
|
496
|
+
if (!isStatic && currentSkillsDirs.length > 0) {
|
|
497
|
+
watchSkillDirectories(currentSkillsDirs, server, skillTool, promptRegistry, subscriptionManager);
|
|
498
|
+
}
|
|
499
|
+
// Set up GitHub polling for updates (skip in static mode)
|
|
500
|
+
let pollingManager = null;
|
|
501
|
+
if (!isStatic && currentGithubSpecs.length > 0 && githubConfig.pollIntervalMs > 0) {
|
|
502
|
+
const syncOptions = {
|
|
503
|
+
cacheDir: githubConfig.cacheDir,
|
|
504
|
+
token: githubConfig.token,
|
|
505
|
+
shallowClone: true,
|
|
506
|
+
};
|
|
507
|
+
pollingManager = createPollingManager(currentGithubSpecs, syncOptions, {
|
|
508
|
+
intervalMs: githubConfig.pollIntervalMs,
|
|
509
|
+
onUpdate: (spec, result) => {
|
|
510
|
+
console.error(`GitHub update detected for ${spec.owner}/${spec.repo}`);
|
|
511
|
+
refreshSkills(currentSkillsDirs, server, skillTool, promptRegistry, subscriptionManager);
|
|
512
|
+
},
|
|
513
|
+
onError: (spec, error) => {
|
|
514
|
+
console.error(`GitHub polling error for ${spec.owner}/${spec.repo}: ${error.message}`);
|
|
515
|
+
},
|
|
516
|
+
});
|
|
517
|
+
pollingManager.start();
|
|
518
|
+
}
|
|
263
519
|
// Connect via stdio transport
|
|
264
520
|
const transport = new StdioServerTransport();
|
|
265
521
|
await server.connect(transport);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP App tool registration for skill directory configuration.
|
|
3
|
+
*
|
|
4
|
+
* Registers:
|
|
5
|
+
* - skill-config: Opens the configuration UI
|
|
6
|
+
* - skill-config-add-directory: Adds a directory (UI-only)
|
|
7
|
+
* - skill-config-remove-directory: Removes a directory (UI-only)
|
|
8
|
+
*/
|
|
9
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
import { SkillState } from "./skill-tool.js";
|
|
11
|
+
/**
|
|
12
|
+
* Callback type for when directories or GitHub settings change.
|
|
13
|
+
*/
|
|
14
|
+
export type OnDirectoriesChangedCallback = () => void | Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Register skill-config MCP App tools and resource.
|
|
17
|
+
*
|
|
18
|
+
* @param server - The MCP server instance
|
|
19
|
+
* @param skillState - Shared skill state for getting skill counts
|
|
20
|
+
* @param onDirectoriesChanged - Callback when directories are added/removed
|
|
21
|
+
*/
|
|
22
|
+
export declare function registerSkillConfigTool(server: McpServer, skillState: SkillState, onDirectoriesChanged: OnDirectoriesChangedCallback): void;
|