skillmesh-mcp 1.0.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/dist/index.js ADDED
@@ -0,0 +1,2090 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
+ import { z } from 'zod';
6
+ import * as path from 'path';
7
+ import * as os2 from 'os';
8
+ import fs from 'fs-extra';
9
+ import * as yaml from 'js-yaml';
10
+
11
+ var { existsSync } = fs;
12
+ var AGENT_PATHS = {
13
+ "claude-code": {
14
+ projectPath: ".claude/skills",
15
+ globalPath: "~/.claude/skills"
16
+ },
17
+ "antigravity": {
18
+ projectPath: ".agent/skills",
19
+ globalPath: "~/.gemini/antigravity/global_skills"
20
+ },
21
+ "cursor": {
22
+ projectPath: ".cursor/skills",
23
+ globalPath: "~/.cursor/skills"
24
+ },
25
+ "windsurf": {
26
+ projectPath: ".windsurf/skills",
27
+ globalPath: "~/.codeium/windsurf/skills"
28
+ },
29
+ "gemini-cli": {
30
+ projectPath: ".gemini/skills",
31
+ globalPath: "~/.gemini/skills"
32
+ },
33
+ "codex-cli": {
34
+ projectPath: ".codex/skills",
35
+ globalPath: "~/.codex/skills"
36
+ },
37
+ "kilocode": {
38
+ projectPath: ".kilocode/skills",
39
+ globalPath: "~/.kilocode/skills"
40
+ },
41
+ "trae": {
42
+ projectPath: ".trae/skills",
43
+ globalPath: "~/.trae/skills"
44
+ },
45
+ "void": {
46
+ projectPath: ".void/skills",
47
+ globalPath: "~/.void/skills"
48
+ },
49
+ "continue": {
50
+ projectPath: ".continue/skills",
51
+ globalPath: "~/.continue/skills"
52
+ },
53
+ "cline": {
54
+ projectPath: ".cline/skills",
55
+ globalPath: "~/.cline/skills"
56
+ }
57
+ };
58
+ function getSkillPath(agentId, projectPath, skillName, global = false) {
59
+ const agentConfig = AGENT_PATHS[agentId];
60
+ if (!agentConfig) {
61
+ const fallbackPath = global ? `~/.${agentId}/skills` : `.${agentId}/skills`;
62
+ const basePath2 = global ? expandTilde(fallbackPath) : path.join(projectPath, fallbackPath);
63
+ return path.join(basePath2, skillName, "SKILL.md");
64
+ }
65
+ const basePath = global ? expandTilde(agentConfig.globalPath) : path.join(projectPath, agentConfig.projectPath);
66
+ return path.join(basePath, skillName, "SKILL.md");
67
+ }
68
+ async function detectInstalledAgents(projectPath) {
69
+ const detected = [];
70
+ for (const [agentId, config] of Object.entries(AGENT_PATHS)) {
71
+ const baseDir = config.projectPath.split("/")[0];
72
+ const agentDirPath = path.join(projectPath, baseDir);
73
+ if (existsSync(agentDirPath)) {
74
+ detected.push(agentId);
75
+ }
76
+ }
77
+ return detected;
78
+ }
79
+ function expandTilde(filepath) {
80
+ if (filepath.startsWith("~/") || filepath === "~") {
81
+ return path.join(os2.homedir(), filepath.slice(1));
82
+ }
83
+ return filepath;
84
+ }
85
+ var { existsSync: existsSync2 } = fs;
86
+ var DetectAgentsInputSchema = z.object({
87
+ projectPath: z.string().describe("Absolute path to project root directory"),
88
+ includeGlobal: z.boolean().optional().default(false).describe("Also scan home directory for globally installed agents")
89
+ });
90
+ async function detectAgents(input) {
91
+ try {
92
+ const { projectPath, includeGlobal } = input;
93
+ const projectAgents = await detectInstalledAgents(projectPath);
94
+ const agentMap = /* @__PURE__ */ new Map();
95
+ for (const agentId of projectAgents) {
96
+ const config = AGENT_PATHS[agentId];
97
+ if (!config) continue;
98
+ const projectSkillPath = path.join(projectPath, config.projectPath);
99
+ const globalSkillPath = config.globalPath.replace(/^~/, os2.homedir());
100
+ agentMap.set(agentId, {
101
+ id: agentId,
102
+ name: agentId,
103
+ projectSkillPath,
104
+ globalSkillPath,
105
+ foundInProject: true,
106
+ foundGlobally: false
107
+ });
108
+ }
109
+ if (includeGlobal) {
110
+ const homeDir = os2.homedir();
111
+ for (const [agentId, config] of Object.entries(AGENT_PATHS)) {
112
+ const globalSkillPath = config.globalPath.replace(/^~/, homeDir);
113
+ const globalConfigDir = path.dirname(globalSkillPath);
114
+ const foundGlobally = existsSync2(globalConfigDir);
115
+ if (agentMap.has(agentId)) {
116
+ agentMap.get(agentId).foundGlobally = foundGlobally;
117
+ } else if (foundGlobally) {
118
+ const projectSkillPath = path.join(projectPath, config.projectPath);
119
+ agentMap.set(agentId, {
120
+ id: agentId,
121
+ name: agentId,
122
+ projectSkillPath,
123
+ globalSkillPath,
124
+ foundInProject: false,
125
+ foundGlobally: true
126
+ });
127
+ }
128
+ }
129
+ }
130
+ const agents = Array.from(agentMap.values());
131
+ return {
132
+ success: true,
133
+ agents,
134
+ projectRoot: projectPath
135
+ };
136
+ } catch (err) {
137
+ const message = err instanceof Error ? err.message : String(err);
138
+ return {
139
+ success: false,
140
+ agents: [],
141
+ projectRoot: input.projectPath,
142
+ error: message
143
+ };
144
+ }
145
+ }
146
+ var { ensureDirSync, readJsonSync, writeJsonSync, existsSync: existsSync3 } = fs;
147
+ var DEFAULT_CONFIG = {
148
+ version: "1.0.0",
149
+ installedSkills: []
150
+ };
151
+ function getConfigPath(projectRoot) {
152
+ return path.join(projectRoot, ".skillmesh", "skillmesh.config.json");
153
+ }
154
+ async function readConfig(projectRoot) {
155
+ const configPath = getConfigPath(projectRoot);
156
+ try {
157
+ if (!existsSync3(configPath)) {
158
+ return { ...DEFAULT_CONFIG };
159
+ }
160
+ const config = readJsonSync(configPath);
161
+ return {
162
+ version: config.version || DEFAULT_CONFIG.version,
163
+ installedSkills: config.installedSkills || []
164
+ };
165
+ } catch (error) {
166
+ console.error("Failed to read config, returning default:", error);
167
+ return { ...DEFAULT_CONFIG };
168
+ }
169
+ }
170
+ async function writeConfig(projectRoot, config) {
171
+ const configPath = getConfigPath(projectRoot);
172
+ try {
173
+ ensureDirSync(path.dirname(configPath));
174
+ writeJsonSync(configPath, config, { spaces: 2 });
175
+ } catch (error) {
176
+ console.error("Failed to write config:", error);
177
+ throw error;
178
+ }
179
+ }
180
+ async function trackInstalledSkill(projectRoot, skillName, agents, source) {
181
+ try {
182
+ const config = await readConfig(projectRoot);
183
+ const existingIndex = config.installedSkills.findIndex(
184
+ (skill) => skill.name === skillName
185
+ );
186
+ if (existingIndex !== -1) {
187
+ const existing = config.installedSkills[existingIndex];
188
+ const mergedAgents = Array.from(/* @__PURE__ */ new Set([...existing.agents, ...agents]));
189
+ config.installedSkills[existingIndex] = {
190
+ name: skillName,
191
+ source: source || existing.source,
192
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
193
+ agents: mergedAgents
194
+ };
195
+ } else {
196
+ config.installedSkills.push({
197
+ name: skillName,
198
+ source,
199
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
200
+ agents
201
+ });
202
+ }
203
+ await writeConfig(projectRoot, config);
204
+ } catch (error) {
205
+ console.error("Failed to track installed skill:", error);
206
+ }
207
+ }
208
+ async function untrackSkill(projectRoot, skillName, agentName) {
209
+ try {
210
+ const config = await readConfig(projectRoot);
211
+ const skillIndex = config.installedSkills.findIndex(
212
+ (skill2) => skill2.name === skillName
213
+ );
214
+ if (skillIndex === -1) {
215
+ return;
216
+ }
217
+ const skill = config.installedSkills[skillIndex];
218
+ skill.agents = skill.agents.filter((agent) => agent !== agentName);
219
+ if (skill.agents.length === 0) {
220
+ config.installedSkills.splice(skillIndex, 1);
221
+ } else {
222
+ config.installedSkills[skillIndex] = skill;
223
+ }
224
+ await writeConfig(projectRoot, config);
225
+ } catch (error) {
226
+ console.error("Failed to untrack skill:", error);
227
+ }
228
+ }
229
+ var { ensureDirSync: ensureDirSync2, existsSync: existsSync4 } = fs;
230
+ var CreateFolderInputSchema = z.object({
231
+ projectPath: z.string().describe("Absolute path to project root directory"),
232
+ agents: z.array(z.string()).describe("List of agent names to create folders for"),
233
+ global: z.boolean().optional().default(false).describe("Create in global scope instead of project scope")
234
+ });
235
+ async function createFolder(input) {
236
+ try {
237
+ const { projectPath, agents, global } = input;
238
+ const results = [];
239
+ for (const agentId of agents) {
240
+ const config = AGENT_PATHS[agentId];
241
+ if (!config) {
242
+ results.push({
243
+ agent: agentId,
244
+ path: "",
245
+ created: false,
246
+ alreadyExists: false
247
+ });
248
+ continue;
249
+ }
250
+ let skillDirPath;
251
+ if (global) {
252
+ skillDirPath = config.globalPath.replace(/^~/, os2.homedir());
253
+ } else {
254
+ skillDirPath = path.join(projectPath, config.projectPath);
255
+ }
256
+ const alreadyExists = existsSync4(skillDirPath);
257
+ try {
258
+ ensureDirSync2(skillDirPath);
259
+ results.push({
260
+ agent: agentId,
261
+ path: skillDirPath,
262
+ created: !alreadyExists,
263
+ alreadyExists
264
+ });
265
+ } catch (err) {
266
+ results.push({
267
+ agent: agentId,
268
+ path: skillDirPath,
269
+ created: false,
270
+ alreadyExists: false
271
+ });
272
+ }
273
+ }
274
+ let configCreated = false;
275
+ let configPath;
276
+ if (!global) {
277
+ try {
278
+ const skillmeshDir = path.join(projectPath, ".skillmesh");
279
+ const configFilePath = path.join(skillmeshDir, "skillmesh.config.json");
280
+ const configExists = existsSync4(configFilePath);
281
+ ensureDirSync2(skillmeshDir);
282
+ if (!configExists) {
283
+ await writeConfig(projectPath, {
284
+ version: "1.0.0",
285
+ installedSkills: []
286
+ });
287
+ configCreated = true;
288
+ }
289
+ configPath = configFilePath;
290
+ } catch (err) {
291
+ configCreated = false;
292
+ }
293
+ }
294
+ return {
295
+ success: true,
296
+ results,
297
+ configCreated,
298
+ configPath
299
+ };
300
+ } catch (err) {
301
+ const message = err instanceof Error ? err.message : String(err);
302
+ return {
303
+ success: false,
304
+ results: [],
305
+ configCreated: false,
306
+ error: message
307
+ };
308
+ }
309
+ }
310
+
311
+ // src/lib/tavily.ts
312
+ async function searchTavily(query, maxResults = 10) {
313
+ const tavilyKey = process.env.TAVILY_API_KEY;
314
+ if (!tavilyKey) {
315
+ console.error("TAVILY_API_KEY not found in environment, trying Firecrawl fallback");
316
+ return searchFirecrawl(query, maxResults);
317
+ }
318
+ try {
319
+ const controller = new AbortController();
320
+ const timeoutId = setTimeout(() => controller.abort(), 15e3);
321
+ const response = await fetch("https://api.tavily.com/search", {
322
+ method: "POST",
323
+ headers: {
324
+ "Content-Type": "application/json"
325
+ },
326
+ body: JSON.stringify({
327
+ api_key: tavilyKey,
328
+ query,
329
+ max_results: maxResults,
330
+ search_depth: "basic",
331
+ include_answer: false,
332
+ include_images: false
333
+ }),
334
+ signal: controller.signal
335
+ });
336
+ clearTimeout(timeoutId);
337
+ if (!response.ok) {
338
+ console.error("Tavily API error:", response.status, response.statusText);
339
+ return [];
340
+ }
341
+ const data = await response.json();
342
+ if (!data.results || !Array.isArray(data.results)) {
343
+ return [];
344
+ }
345
+ return data.results.map((result) => ({
346
+ title: result.title || "",
347
+ url: result.url || "",
348
+ snippet: result.content || "",
349
+ score: result.score
350
+ }));
351
+ } catch (error) {
352
+ console.error("Tavily search failed:", error);
353
+ return [];
354
+ }
355
+ }
356
+ async function searchFirecrawl(query, maxResults = 10) {
357
+ const firecrawlKey = process.env.FIRECRAWL_API_KEY;
358
+ if (!firecrawlKey) {
359
+ console.error("Neither TAVILY_API_KEY nor FIRECRAWL_API_KEY found in environment");
360
+ return [];
361
+ }
362
+ try {
363
+ const controller = new AbortController();
364
+ const timeoutId = setTimeout(() => controller.abort(), 15e3);
365
+ const response = await fetch("https://api.firecrawl.dev/v1/search", {
366
+ method: "POST",
367
+ headers: {
368
+ "Content-Type": "application/json",
369
+ "Authorization": `Bearer ${firecrawlKey}`
370
+ },
371
+ body: JSON.stringify({
372
+ query,
373
+ limit: maxResults
374
+ }),
375
+ signal: controller.signal
376
+ });
377
+ clearTimeout(timeoutId);
378
+ if (!response.ok) {
379
+ console.error("Firecrawl API error:", response.status, response.statusText);
380
+ return [];
381
+ }
382
+ const data = await response.json();
383
+ if (!data.data || !Array.isArray(data.data)) {
384
+ return [];
385
+ }
386
+ return data.data.map((result) => ({
387
+ title: result.title || "",
388
+ url: result.url || "",
389
+ snippet: result.description || result.content || "",
390
+ score: result.score
391
+ }));
392
+ } catch (error) {
393
+ console.error("Firecrawl search failed:", error);
394
+ return [];
395
+ }
396
+ }
397
+
398
+ // src/lib/github.ts
399
+ async function fetchSkillFromGitHub(owner, repo, skillName, githubToken) {
400
+ const baseUrl = `https://raw.githubusercontent.com/${owner}/${repo}/main`;
401
+ const paths = [
402
+ `${baseUrl}/.claude/skills/${skillName}/SKILL.md`,
403
+ `${baseUrl}/SKILL.md`,
404
+ `${baseUrl}/skills/${skillName}/SKILL.md`
405
+ ];
406
+ for (const url of paths) {
407
+ const result = await fetchWithTimeout(url, githubToken);
408
+ if (result.found) {
409
+ return result;
410
+ }
411
+ }
412
+ const searchResult = await searchGitHubForSkill(owner, repo, skillName, githubToken);
413
+ if (searchResult.found) {
414
+ return searchResult;
415
+ }
416
+ return {
417
+ content: "",
418
+ url: "",
419
+ found: false
420
+ };
421
+ }
422
+ async function getGitHubStars(owner, repo, githubToken) {
423
+ const url = `https://api.github.com/repos/${owner}/${repo}`;
424
+ try {
425
+ const controller = new AbortController();
426
+ const timeoutId = setTimeout(() => controller.abort(), 15e3);
427
+ const headers = {
428
+ "Accept": "application/vnd.github.v3+json",
429
+ "User-Agent": "skillmesh-mcp"
430
+ };
431
+ if (githubToken) {
432
+ headers["Authorization"] = `Bearer ${githubToken}`;
433
+ }
434
+ const response = await fetch(url, {
435
+ headers,
436
+ signal: controller.signal
437
+ });
438
+ clearTimeout(timeoutId);
439
+ if (!response.ok) {
440
+ return 0;
441
+ }
442
+ const data = await response.json();
443
+ return data.stargazers_count || 0;
444
+ } catch (error) {
445
+ console.error(`Failed to fetch star count for ${owner}/${repo}:`, error);
446
+ return 0;
447
+ }
448
+ }
449
+ async function getLastUpdated(owner, repo, githubToken) {
450
+ const url = `https://api.github.com/repos/${owner}/${repo}`;
451
+ try {
452
+ const controller = new AbortController();
453
+ const timeoutId = setTimeout(() => controller.abort(), 15e3);
454
+ const headers = {
455
+ "Accept": "application/vnd.github.v3+json",
456
+ "User-Agent": "skillmesh-mcp"
457
+ };
458
+ if (githubToken) {
459
+ headers["Authorization"] = `Bearer ${githubToken}`;
460
+ }
461
+ const response = await fetch(url, {
462
+ headers,
463
+ signal: controller.signal
464
+ });
465
+ clearTimeout(timeoutId);
466
+ if (!response.ok) {
467
+ return null;
468
+ }
469
+ const data = await response.json();
470
+ const updatedAt = data.updated_at || data.pushed_at;
471
+ return updatedAt ? new Date(updatedAt) : null;
472
+ } catch (error) {
473
+ console.error(`Failed to fetch last updated for ${owner}/${repo}:`, error);
474
+ return null;
475
+ }
476
+ }
477
+ async function fetchWithTimeout(url, githubToken) {
478
+ try {
479
+ const controller = new AbortController();
480
+ const timeoutId = setTimeout(() => controller.abort(), 15e3);
481
+ const headers = {
482
+ "User-Agent": "skillmesh-mcp"
483
+ };
484
+ if (githubToken && url.includes("api.github.com")) {
485
+ headers["Authorization"] = `Bearer ${githubToken}`;
486
+ }
487
+ const response = await fetch(url, {
488
+ headers,
489
+ signal: controller.signal
490
+ });
491
+ clearTimeout(timeoutId);
492
+ if (!response.ok) {
493
+ return {
494
+ content: "",
495
+ url: "",
496
+ found: false
497
+ };
498
+ }
499
+ const content = await response.text();
500
+ return {
501
+ content,
502
+ url,
503
+ found: true
504
+ };
505
+ } catch (error) {
506
+ return {
507
+ content: "",
508
+ url: "",
509
+ found: false
510
+ };
511
+ }
512
+ }
513
+ async function searchGitHubForSkill(owner, repo, skillName, githubToken) {
514
+ if (!githubToken) {
515
+ return {
516
+ content: "",
517
+ url: "",
518
+ found: false
519
+ };
520
+ }
521
+ try {
522
+ const controller = new AbortController();
523
+ const timeoutId = setTimeout(() => controller.abort(), 15e3);
524
+ const query = encodeURIComponent(
525
+ `filename:SKILL.md repo:${owner}/${repo}`
526
+ );
527
+ const searchUrl = `https://api.github.com/search/code?q=${query}`;
528
+ const response = await fetch(searchUrl, {
529
+ headers: {
530
+ "Accept": "application/vnd.github.v3+json",
531
+ "Authorization": `Bearer ${githubToken}`,
532
+ "User-Agent": "skillmesh-mcp"
533
+ },
534
+ signal: controller.signal
535
+ });
536
+ clearTimeout(timeoutId);
537
+ if (!response.ok) {
538
+ return {
539
+ content: "",
540
+ url: "",
541
+ found: false
542
+ };
543
+ }
544
+ const data = await response.json();
545
+ if (!data.items || data.items.length === 0) {
546
+ return {
547
+ content: "",
548
+ url: "",
549
+ found: false
550
+ };
551
+ }
552
+ let bestMatch = data.items[0];
553
+ for (const item of data.items) {
554
+ if (item.path.includes(skillName)) {
555
+ bestMatch = item;
556
+ break;
557
+ }
558
+ }
559
+ const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/main/${bestMatch.path}`;
560
+ return await fetchWithTimeout(rawUrl, githubToken);
561
+ } catch (error) {
562
+ console.error("GitHub search failed:", error);
563
+ return {
564
+ content: "",
565
+ url: "",
566
+ found: false
567
+ };
568
+ }
569
+ }
570
+
571
+ // src/tools/search-skills.ts
572
+ var SearchSkillsInputSchema = z.object({
573
+ keyword: z.string().describe('Search keyword or skill domain (e.g., "nextjs-app-router", "prisma")'),
574
+ minStars: z.number().optional().default(50).describe("Minimum GitHub stars filter"),
575
+ maxAgeMonths: z.number().optional().default(12).describe("Maximum age in months (updated within)"),
576
+ limit: z.number().optional().default(10).describe("Maximum number of results")
577
+ });
578
+ async function searchSkills(input) {
579
+ try {
580
+ const { keyword, minStars, maxAgeMonths, limit } = input;
581
+ const searchQuery = `site:skills.sh ${keyword}`;
582
+ const searchResults = await searchTavily(searchQuery, limit * 3);
583
+ const qualityResults = [];
584
+ const githubToken = process.env.GITHUB_TOKEN;
585
+ for (const result of searchResults) {
586
+ const githubMatch = result.url.match(/github\.com\/([^\/]+)\/([^\/\?#]+)/);
587
+ if (!githubMatch) {
588
+ const pathMatch = result.url.match(/skills\.sh\/([^\/]+)\/([^\/\?#]+)/);
589
+ if (pathMatch) {
590
+ const [, owner2, repo2] = pathMatch;
591
+ try {
592
+ const stars = await getGitHubStars(owner2, repo2, githubToken);
593
+ const lastUpdated = await getLastUpdated(owner2, repo2, githubToken);
594
+ if (stars < minStars) continue;
595
+ if (lastUpdated) {
596
+ const ageMonths = (Date.now() - lastUpdated.getTime()) / (1e3 * 60 * 60 * 24 * 30);
597
+ if (ageMonths > maxAgeMonths) continue;
598
+ }
599
+ const recencyBonus = lastUpdated ? Math.max(0, 100 - (Date.now() - lastUpdated.getTime()) / (1e3 * 60 * 60 * 24 * 30)) : 0;
600
+ const score = stars + recencyBonus + (result.score || 0) * 10;
601
+ qualityResults.push({
602
+ name: repo2,
603
+ repo: repo2,
604
+ owner: owner2,
605
+ description: result.snippet,
606
+ stars,
607
+ lastUpdated: lastUpdated ? lastUpdated.toISOString() : "unknown",
608
+ url: result.url,
609
+ score
610
+ });
611
+ } catch (err) {
612
+ continue;
613
+ }
614
+ }
615
+ continue;
616
+ }
617
+ const [, owner, repo] = githubMatch;
618
+ try {
619
+ const stars = await getGitHubStars(owner, repo, githubToken);
620
+ const lastUpdated = await getLastUpdated(owner, repo, githubToken);
621
+ if (stars < minStars) continue;
622
+ if (lastUpdated) {
623
+ const ageMonths = (Date.now() - lastUpdated.getTime()) / (1e3 * 60 * 60 * 24 * 30);
624
+ if (ageMonths > maxAgeMonths) continue;
625
+ }
626
+ const recencyBonus = lastUpdated ? Math.max(0, 100 - (Date.now() - lastUpdated.getTime()) / (1e3 * 60 * 60 * 24 * 30)) : 0;
627
+ const score = stars + recencyBonus + (result.score || 0) * 10;
628
+ qualityResults.push({
629
+ name: repo,
630
+ repo,
631
+ owner,
632
+ description: result.snippet,
633
+ stars,
634
+ lastUpdated: lastUpdated ? lastUpdated.toISOString() : "unknown",
635
+ url: result.url,
636
+ score
637
+ });
638
+ } catch (err) {
639
+ continue;
640
+ }
641
+ }
642
+ qualityResults.sort((a, b) => b.score - a.score);
643
+ const limitedResults = qualityResults.slice(0, limit);
644
+ return {
645
+ success: true,
646
+ results: limitedResults,
647
+ query: keyword,
648
+ totalFound: qualityResults.length
649
+ };
650
+ } catch (err) {
651
+ const message = err instanceof Error ? err.message : String(err);
652
+ return {
653
+ success: false,
654
+ results: [],
655
+ query: input.keyword,
656
+ totalFound: 0,
657
+ error: message
658
+ };
659
+ }
660
+ }
661
+ var SkillMetadataSchema = z.object({
662
+ name: z.string().min(1, "Skill name is required"),
663
+ description: z.string().min(1, "Description is required"),
664
+ tags: z.array(z.string()).optional().default([]),
665
+ sources: z.array(z.string().url()).optional().default([]),
666
+ created_by: z.string().optional(),
667
+ created_at: z.string().optional()
668
+ });
669
+ function parseSkillMd(content) {
670
+ try {
671
+ const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
672
+ const match = content.match(frontmatterRegex);
673
+ if (!match) {
674
+ return null;
675
+ }
676
+ const [, frontmatterStr, bodyContent] = match;
677
+ const parsedYaml = yaml.load(frontmatterStr);
678
+ if (!parsedYaml || typeof parsedYaml !== "object") {
679
+ return null;
680
+ }
681
+ const validationResult = SkillMetadataSchema.safeParse(parsedYaml);
682
+ if (!validationResult.success) {
683
+ console.error("Skill metadata validation failed:", validationResult.error.flatten());
684
+ return null;
685
+ }
686
+ return {
687
+ metadata: validationResult.data,
688
+ content: bodyContent.trim(),
689
+ raw: content
690
+ };
691
+ } catch (error) {
692
+ console.error("Failed to parse SKILL.md:", error);
693
+ return null;
694
+ }
695
+ }
696
+ function validateSkillMd(content) {
697
+ const errors = [];
698
+ const parsed = parseSkillMd(content);
699
+ if (!parsed) {
700
+ errors.push("Invalid or missing YAML frontmatter");
701
+ return { valid: false, errors };
702
+ }
703
+ const requiredSections = [
704
+ { name: "Overview", pattern: /##\s+Overview/i },
705
+ { name: "Core Rules", pattern: /##\s+Core\s+Rules/i },
706
+ { name: "Patterns", pattern: /##\s+Patterns/i },
707
+ { name: "Anti-Patterns", pattern: /##\s+Anti-?Patterns/i }
708
+ ];
709
+ for (const section of requiredSections) {
710
+ if (!section.pattern.test(parsed.content)) {
711
+ errors.push(`Missing required section: ${section.name}`);
712
+ }
713
+ }
714
+ return {
715
+ valid: errors.length === 0,
716
+ errors
717
+ };
718
+ }
719
+ function checkSkillQuality(parsed) {
720
+ const feedback = [];
721
+ let score = 100;
722
+ const rulesMatch = parsed.content.match(/##\s+Core\s+Rules([\s\S]*?)(?=##|$)/i);
723
+ if (rulesMatch) {
724
+ const rulesSection = rulesMatch[1];
725
+ const ruleCount = (rulesSection.match(/^\d+\./gm) || []).length;
726
+ if (ruleCount < 6) {
727
+ feedback.push(`Core Rules should have 6-10 rules (found ${ruleCount})`);
728
+ score -= 15;
729
+ }
730
+ } else {
731
+ feedback.push("Core Rules section is missing or improperly formatted");
732
+ score -= 20;
733
+ }
734
+ const patternsMatch = parsed.content.match(/##\s+Patterns(?:\s+and\s+Examples)?([\s\S]*?)(?=##|$)/i);
735
+ if (patternsMatch) {
736
+ const patternsSection = patternsMatch[1];
737
+ const patternCount = (patternsSection.match(/###\s+Pattern\s+\d+/gi) || []).length;
738
+ if (patternCount < 3) {
739
+ feedback.push(`Patterns section should have 3-5 patterns (found ${patternCount})`);
740
+ score -= 15;
741
+ }
742
+ const codeBlockCount = (patternsSection.match(/```/g) || []).length / 2;
743
+ if (codeBlockCount < patternCount) {
744
+ feedback.push("Each pattern should include a complete runnable code example");
745
+ score -= 10;
746
+ }
747
+ } else {
748
+ feedback.push("Patterns section is missing or improperly formatted");
749
+ score -= 20;
750
+ }
751
+ const antiPatternsMatch = parsed.content.match(/##\s+Anti-?Patterns([\s\S]*?)(?=##|$)/i);
752
+ if (antiPatternsMatch) {
753
+ const antiPatternsSection = antiPatternsMatch[1];
754
+ const antiPatternCount = (antiPatternsSection.match(/^-\s+/gm) || []).length;
755
+ if (antiPatternCount < 4) {
756
+ feedback.push(`Anti-Patterns section should have 4-6 items (found ${antiPatternCount})`);
757
+ score -= 10;
758
+ }
759
+ } else {
760
+ feedback.push("Anti-Patterns section is missing");
761
+ score -= 15;
762
+ }
763
+ if (!/##\s+File\s+and\s+Folder\s+Conventions/i.test(parsed.content)) {
764
+ feedback.push("Consider adding File and Folder Conventions section");
765
+ score -= 5;
766
+ }
767
+ if (!/##\s+Useful\s+References/i.test(parsed.content)) {
768
+ feedback.push("Consider adding Useful References section");
769
+ score -= 5;
770
+ }
771
+ score = Math.max(0, score);
772
+ if (score === 100) {
773
+ feedback.push("Excellent! This skill meets gold-standard quality criteria.");
774
+ } else if (score >= 80) {
775
+ feedback.push("Good quality skill with minor improvements suggested.");
776
+ } else if (score >= 60) {
777
+ feedback.push("Acceptable skill but significant improvements recommended.");
778
+ } else {
779
+ feedback.push("This skill needs substantial improvements to meet quality standards.");
780
+ }
781
+ return { score, feedback };
782
+ }
783
+
784
+ // src/tools/fetch-skill.ts
785
+ var FetchSkillInputSchema = z.object({
786
+ owner: z.string().describe("GitHub repository owner"),
787
+ repo: z.string().describe("GitHub repository name"),
788
+ skillName: z.string().describe("Skill name to fetch"),
789
+ githubToken: z.string().optional().describe("Optional GitHub token for higher rate limits")
790
+ });
791
+ async function fetchSkillContent(input) {
792
+ try {
793
+ const { owner, repo, skillName, githubToken } = input;
794
+ const token = githubToken || process.env.GITHUB_TOKEN;
795
+ const fetchResult = await fetchSkillFromGitHub(owner, repo, skillName, token);
796
+ if (!fetchResult.found || !fetchResult.content) {
797
+ return {
798
+ success: false,
799
+ content: "",
800
+ sourceUrl: fetchResult.url,
801
+ found: false,
802
+ error: "SKILL.md not found in repository"
803
+ };
804
+ }
805
+ try {
806
+ const parsed = parseSkillMd(fetchResult.content);
807
+ const validation = validateSkillMd(fetchResult.content);
808
+ if (!parsed) {
809
+ return {
810
+ success: true,
811
+ content: fetchResult.content,
812
+ sourceUrl: fetchResult.url,
813
+ found: true,
814
+ quality: {
815
+ valid: false,
816
+ issues: ["Failed to parse SKILL.md frontmatter"]
817
+ }
818
+ };
819
+ }
820
+ return {
821
+ success: true,
822
+ content: fetchResult.content,
823
+ sourceUrl: fetchResult.url,
824
+ found: true,
825
+ metadata: {
826
+ name: parsed.metadata.name,
827
+ description: parsed.metadata.description,
828
+ tags: parsed.metadata.tags,
829
+ sources: parsed.metadata.sources
830
+ },
831
+ quality: {
832
+ valid: validation.valid,
833
+ issues: validation.errors
834
+ }
835
+ };
836
+ } catch (parseErr) {
837
+ const parseMessage = parseErr instanceof Error ? parseErr.message : String(parseErr);
838
+ return {
839
+ success: true,
840
+ content: fetchResult.content,
841
+ sourceUrl: fetchResult.url,
842
+ found: true,
843
+ quality: {
844
+ valid: false,
845
+ issues: [`Failed to parse SKILL.md: ${parseMessage}`]
846
+ }
847
+ };
848
+ }
849
+ } catch (err) {
850
+ const message = err instanceof Error ? err.message : String(err);
851
+ return {
852
+ success: false,
853
+ content: "",
854
+ sourceUrl: "",
855
+ found: false,
856
+ error: message
857
+ };
858
+ }
859
+ }
860
+
861
+ // src/lib/skill-sanitizer.ts
862
+ function sanitizeSkillName(name) {
863
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
864
+ }
865
+
866
+ // src/lib/llm.ts
867
+ var PROVIDER_MODELS = {
868
+ anthropic: "claude-sonnet-4-5-20250929",
869
+ google: "gemini-3.0-pro",
870
+ openai: "gpt-4o"
871
+ };
872
+ function detectLLMProvider() {
873
+ if (process.env.ANTHROPIC_API_KEY) {
874
+ return {
875
+ provider: "anthropic",
876
+ model: PROVIDER_MODELS.anthropic,
877
+ apiKey: process.env.ANTHROPIC_API_KEY
878
+ };
879
+ }
880
+ if (process.env.GOOGLE_API_KEY) {
881
+ return {
882
+ provider: "google",
883
+ model: PROVIDER_MODELS.google,
884
+ apiKey: process.env.GOOGLE_API_KEY
885
+ };
886
+ }
887
+ if (process.env.OPENAI_API_KEY) {
888
+ return {
889
+ provider: "openai",
890
+ model: PROVIDER_MODELS.openai,
891
+ apiKey: process.env.OPENAI_API_KEY
892
+ };
893
+ }
894
+ return null;
895
+ }
896
+ async function generateWithLLM(systemPrompt, userPrompt, config) {
897
+ const llmConfig = config || detectLLMProvider();
898
+ if (!llmConfig) {
899
+ return 'ERROR: No LLM API key found. Please add one of the following environment variables to your MCP config:\n - ANTHROPIC_API_KEY (recommended)\n - GOOGLE_API_KEY\n - OPENAI_API_KEY\n\nAdd the key to your MCP server configuration under the "env" section.';
900
+ }
901
+ try {
902
+ switch (llmConfig.provider) {
903
+ case "anthropic":
904
+ return await callAnthropic(systemPrompt, userPrompt, llmConfig.apiKey);
905
+ case "google":
906
+ return await callGoogle(systemPrompt, userPrompt, llmConfig.apiKey);
907
+ case "openai":
908
+ return await callOpenAI(systemPrompt, userPrompt, llmConfig.apiKey);
909
+ default:
910
+ return `ERROR: Unknown LLM provider: ${llmConfig.provider}`;
911
+ }
912
+ } catch (error) {
913
+ console.error("LLM generation failed:", error);
914
+ return `ERROR: LLM generation failed: ${error instanceof Error ? error.message : String(error)}`;
915
+ }
916
+ }
917
+ async function callAnthropic(systemPrompt, userPrompt, apiKey) {
918
+ const controller = new AbortController();
919
+ const timeoutId = setTimeout(() => controller.abort(), 6e4);
920
+ try {
921
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
922
+ method: "POST",
923
+ headers: {
924
+ "Content-Type": "application/json",
925
+ "x-api-key": apiKey,
926
+ "anthropic-version": "2023-06-01"
927
+ },
928
+ body: JSON.stringify({
929
+ model: PROVIDER_MODELS.anthropic,
930
+ max_tokens: 4096,
931
+ system: systemPrompt,
932
+ messages: [
933
+ {
934
+ role: "user",
935
+ content: userPrompt
936
+ }
937
+ ]
938
+ }),
939
+ signal: controller.signal
940
+ });
941
+ clearTimeout(timeoutId);
942
+ if (!response.ok) {
943
+ const errorText = await response.text();
944
+ throw new Error(`Anthropic API error ${response.status}: ${errorText}`);
945
+ }
946
+ const data = await response.json();
947
+ if (!data.content || !data.content[0] || !data.content[0].text) {
948
+ throw new Error("Invalid response format from Anthropic API");
949
+ }
950
+ return data.content[0].text;
951
+ } catch (error) {
952
+ clearTimeout(timeoutId);
953
+ throw error;
954
+ }
955
+ }
956
+ async function callGoogle(systemPrompt, userPrompt, apiKey) {
957
+ const controller = new AbortController();
958
+ const timeoutId = setTimeout(() => controller.abort(), 6e4);
959
+ try {
960
+ const combinedPrompt = `${systemPrompt}
961
+
962
+ ${userPrompt}`;
963
+ const response = await fetch(
964
+ `https://generativelanguage.googleapis.com/v1beta/models/${PROVIDER_MODELS.google}:generateContent?key=${apiKey}`,
965
+ {
966
+ method: "POST",
967
+ headers: {
968
+ "Content-Type": "application/json"
969
+ },
970
+ body: JSON.stringify({
971
+ contents: [
972
+ {
973
+ parts: [
974
+ {
975
+ text: combinedPrompt
976
+ }
977
+ ]
978
+ }
979
+ ],
980
+ generationConfig: {
981
+ maxOutputTokens: 4096,
982
+ temperature: 0.7
983
+ }
984
+ }),
985
+ signal: controller.signal
986
+ }
987
+ );
988
+ clearTimeout(timeoutId);
989
+ if (!response.ok) {
990
+ const errorText = await response.text();
991
+ throw new Error(`Google API error ${response.status}: ${errorText}`);
992
+ }
993
+ const data = await response.json();
994
+ if (!data.candidates || !data.candidates[0] || !data.candidates[0].content || !data.candidates[0].content.parts || !data.candidates[0].content.parts[0]) {
995
+ throw new Error("Invalid response format from Google API");
996
+ }
997
+ return data.candidates[0].content.parts[0].text;
998
+ } catch (error) {
999
+ clearTimeout(timeoutId);
1000
+ throw error;
1001
+ }
1002
+ }
1003
+ async function callOpenAI(systemPrompt, userPrompt, apiKey) {
1004
+ const controller = new AbortController();
1005
+ const timeoutId = setTimeout(() => controller.abort(), 6e4);
1006
+ try {
1007
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
1008
+ method: "POST",
1009
+ headers: {
1010
+ "Content-Type": "application/json",
1011
+ "Authorization": `Bearer ${apiKey}`
1012
+ },
1013
+ body: JSON.stringify({
1014
+ model: PROVIDER_MODELS.openai,
1015
+ max_tokens: 4096,
1016
+ messages: [
1017
+ {
1018
+ role: "system",
1019
+ content: systemPrompt
1020
+ },
1021
+ {
1022
+ role: "user",
1023
+ content: userPrompt
1024
+ }
1025
+ ]
1026
+ }),
1027
+ signal: controller.signal
1028
+ });
1029
+ clearTimeout(timeoutId);
1030
+ if (!response.ok) {
1031
+ const errorText = await response.text();
1032
+ throw new Error(`OpenAI API error ${response.status}: ${errorText}`);
1033
+ }
1034
+ const data = await response.json();
1035
+ if (!data.choices || !data.choices[0] || !data.choices[0].message || !data.choices[0].message.content) {
1036
+ throw new Error("Invalid response format from OpenAI API");
1037
+ }
1038
+ return data.choices[0].message.content;
1039
+ } catch (error) {
1040
+ clearTimeout(timeoutId);
1041
+ throw error;
1042
+ }
1043
+ }
1044
+
1045
+ // src/tools/generate-skill.ts
1046
+ var GenerateSkillInputSchema = z.object({
1047
+ skillName: z.string().describe("Skill name/domain to generate"),
1048
+ projectContext: z.string().describe("Rich project context: frameworks, versions, patterns, structure"),
1049
+ researchQuery: z.string().optional().describe("Optional custom web research query"),
1050
+ maxResearchResults: z.number().optional().default(10).describe("Maximum web research results")
1051
+ });
1052
+ async function generateSkill(input) {
1053
+ try {
1054
+ const { skillName, projectContext, researchQuery, maxResearchResults } = input;
1055
+ const sanitizedName = sanitizeSkillName(skillName);
1056
+ const llmConfig = detectLLMProvider();
1057
+ if (!llmConfig) {
1058
+ return {
1059
+ success: false,
1060
+ content: "",
1061
+ skillName: sanitizedName,
1062
+ researchSources: [],
1063
+ error: 'No LLM API key found. Please add one of the following to your MCP server config:\n - ANTHROPIC_API_KEY (for Claude Sonnet 4.5)\n - GOOGLE_API_KEY (for Gemini 3.0 Pro)\n - OPENAI_API_KEY (for GPT-4o)\n\nAdd the key to the "env" section of your MCP config file.'
1064
+ };
1065
+ }
1066
+ const query = researchQuery || `${skillName} best practices patterns conventions documentation`;
1067
+ let researchResults = [];
1068
+ let researchSources = [];
1069
+ try {
1070
+ researchResults = await searchTavily(query, maxResearchResults || 10);
1071
+ researchSources = researchResults.map((r) => r.url);
1072
+ } catch (searchErr) {
1073
+ researchResults = [];
1074
+ }
1075
+ const researchContext = researchResults.map((result, idx) => {
1076
+ return `[${idx + 1}] ${result.title}
1077
+ ${result.snippet}
1078
+ Source: ${result.url}`;
1079
+ }).join("\n\n");
1080
+ const systemPrompt = `You are an expert technical writer creating Agent Skills (SKILL.md files) that teach AI coding agents about specific technologies, frameworks, and best practices.
1081
+
1082
+ CRITICAL REQUIREMENTS:
1083
+ 1. Follow the EXACT structure from the gold-standard example below
1084
+ 2. Include 6-10 Core Rules (numbered list, specific and actionable)
1085
+ 3. Include 3-5 Patterns with complete runnable code examples
1086
+ 4. Include 4-6 Anti-Patterns (bulleted list with explanations)
1087
+ 5. Include File and Folder Conventions section
1088
+ 6. Include Useful References section
1089
+ 7. Use proper YAML frontmatter with name, description, tags, sources
1090
+ 8. All code examples must be complete and runnable
1091
+
1092
+ GOLD-STANDARD EXAMPLE STRUCTURE:
1093
+ ---
1094
+ name: ${sanitizedName}
1095
+ description: One-line description of what this skill covers
1096
+ tags: [tag1, tag2, tag3]
1097
+ sources: [https://example.com/docs]
1098
+ created_by: skillmesh
1099
+ created_at: ${(/* @__PURE__ */ new Date()).toISOString()}
1100
+ ---
1101
+
1102
+ ## Overview
1103
+ 2-3 sentence explanation of what this skill covers and why it matters.
1104
+
1105
+ ## Core Rules
1106
+ 1. Rule one \u2014 specific and actionable with rationale
1107
+ 2. Rule two \u2014 specific and actionable with rationale
1108
+ ... (6-10 total)
1109
+
1110
+ ## Patterns and Examples
1111
+
1112
+ ### Pattern 1 \u2014 Descriptive Name
1113
+ Explain what this pattern does and when to use it.
1114
+ \`\`\`language
1115
+ // Complete runnable code example
1116
+ \`\`\`
1117
+
1118
+ ### Pattern 2 \u2014 Descriptive Name
1119
+ ... (3-5 total patterns)
1120
+
1121
+ ## Anti-Patterns (What NOT To Do)
1122
+ - NEVER do X because Y (specific reason)
1123
+ - NEVER do A because B (specific reason)
1124
+ ... (4-6 total)
1125
+
1126
+ ## File and Folder Conventions
1127
+ Project-specific folder structure and naming rules.
1128
+
1129
+ ## Useful References
1130
+ - https://official-docs-link
1131
+ - https://github.com/source-repo`;
1132
+ const userPrompt = `Generate a gold-standard SKILL.md for: ${skillName}
1133
+
1134
+ PROJECT CONTEXT:
1135
+ ${projectContext}
1136
+
1137
+ WEB RESEARCH RESULTS:
1138
+ ${researchContext || "No research results available. Use your knowledge of best practices."}
1139
+
1140
+ INSTRUCTIONS:
1141
+ 1. Create a comprehensive skill that follows the exact structure shown in the system prompt
1142
+ 2. Use the project context to make rules and patterns specific to this project
1143
+ 3. Reference the research sources in the "sources" frontmatter field
1144
+ 4. Ensure all code examples are complete, runnable, and production-ready
1145
+ 5. Make the skill immediately actionable for an AI agent working in this codebase
1146
+
1147
+ Generate the complete SKILL.md now:`;
1148
+ const generatedContent = await generateWithLLM(systemPrompt, userPrompt, llmConfig);
1149
+ const parsed = parseSkillMd(generatedContent);
1150
+ let quality;
1151
+ if (parsed) {
1152
+ quality = checkSkillQuality(parsed);
1153
+ } else {
1154
+ quality = {
1155
+ score: 0,
1156
+ feedback: ["Generated content does not have valid YAML frontmatter"]
1157
+ };
1158
+ }
1159
+ return {
1160
+ success: true,
1161
+ content: generatedContent,
1162
+ skillName: sanitizedName,
1163
+ researchSources,
1164
+ provider: llmConfig.provider,
1165
+ model: llmConfig.model,
1166
+ quality
1167
+ };
1168
+ } catch (err) {
1169
+ const message = err instanceof Error ? err.message : String(err);
1170
+ return {
1171
+ success: false,
1172
+ content: "",
1173
+ skillName: sanitizeSkillName(input.skillName),
1174
+ researchSources: [],
1175
+ error: message
1176
+ };
1177
+ }
1178
+ }
1179
+
1180
+ // src/tools/install-skill.ts
1181
+ var { ensureDirSync: ensureDirSync3, writeFileSync, existsSync: existsSync5 } = fs;
1182
+ var InstallSkillInputSchema = z.object({
1183
+ projectPath: z.string().describe("Absolute path to project root directory"),
1184
+ skillName: z.string().describe("Skill name to install"),
1185
+ agents: z.array(z.string()).describe("List of target agent names"),
1186
+ source: z.object({
1187
+ type: z.enum(["github", "content"]).describe("Source type"),
1188
+ owner: z.string().optional().describe("GitHub owner (for type: github)"),
1189
+ repo: z.string().optional().describe("GitHub repo (for type: github)"),
1190
+ content: z.string().optional().describe("Direct SKILL.md content (for type: content)"),
1191
+ stars: z.number().optional().describe("GitHub star count (for quality check)")
1192
+ }).describe("Skill source"),
1193
+ overwrite: z.boolean().optional().default(false).describe("Overwrite existing skill if present"),
1194
+ global: z.boolean().optional().default(false).describe("Install to global scope")
1195
+ });
1196
+ async function installSkill(input) {
1197
+ try {
1198
+ const { projectPath, skillName, agents, source, overwrite, global } = input;
1199
+ const sanitizedSkillName = sanitizeSkillName(skillName);
1200
+ if (!sanitizedSkillName || sanitizedSkillName.length === 0) {
1201
+ return {
1202
+ method: "skipped",
1203
+ results: [],
1204
+ skillName,
1205
+ error: `Invalid skill name: "${skillName}". Skill names must contain alphanumeric characters.`
1206
+ };
1207
+ }
1208
+ if (!overwrite) {
1209
+ const existingPaths = [];
1210
+ for (const agent of agents) {
1211
+ const skillPath = getSkillPath(agent, projectPath, sanitizedSkillName, global);
1212
+ if (existsSync5(skillPath)) {
1213
+ existingPaths.push(skillPath);
1214
+ }
1215
+ }
1216
+ if (existingPaths.length > 0) {
1217
+ return {
1218
+ method: "skipped",
1219
+ results: agents.map((agent) => ({
1220
+ agent,
1221
+ path: getSkillPath(agent, projectPath, sanitizedSkillName, global),
1222
+ method: "skipped",
1223
+ success: true,
1224
+ error: "Skill already exists. Use overwrite flag to replace."
1225
+ })),
1226
+ skillName: sanitizedSkillName
1227
+ };
1228
+ }
1229
+ }
1230
+ if (source.type === "github" && source.owner && source.repo) {
1231
+ const stars = source.stars ?? 0;
1232
+ if (stars >= 50) {
1233
+ return executePathA(source.owner, source.repo, sanitizedSkillName, agents, projectPath, global);
1234
+ }
1235
+ }
1236
+ let skillContent = null;
1237
+ if (source.type === "content" && source.content) {
1238
+ skillContent = source.content;
1239
+ } else if (source.type === "github" && source.owner && source.repo) {
1240
+ const githubToken = process.env.GITHUB_TOKEN;
1241
+ const fetchResult = await fetchSkillFromGitHub(
1242
+ source.owner,
1243
+ source.repo,
1244
+ sanitizedSkillName,
1245
+ githubToken
1246
+ );
1247
+ if (fetchResult.found) {
1248
+ skillContent = fetchResult.content;
1249
+ }
1250
+ }
1251
+ if (skillContent) {
1252
+ return await executePathB(
1253
+ skillContent,
1254
+ sanitizedSkillName,
1255
+ agents,
1256
+ projectPath,
1257
+ global,
1258
+ overwrite,
1259
+ source.type === "github" ? `${source.owner}/${source.repo}` : "direct-content"
1260
+ );
1261
+ }
1262
+ return await executePathC(skillName, sanitizedSkillName, agents, projectPath, global, overwrite);
1263
+ } catch (err) {
1264
+ const message = err instanceof Error ? err.message : String(err);
1265
+ return {
1266
+ method: "skipped",
1267
+ results: [],
1268
+ skillName: input.skillName,
1269
+ error: `Installation failed: ${message}`
1270
+ };
1271
+ }
1272
+ }
1273
+ function executePathA(owner, repo, skillName, agents, projectPath, global) {
1274
+ const agentFlags = agents.map((agent) => `--agent ${agent}`).join(" ");
1275
+ const globalFlag = global ? "-g" : "";
1276
+ const repoPath = `${owner}/${repo}`;
1277
+ const command = `npx skills add ${repoPath} --skill ${skillName} ${agentFlags} ${globalFlag} -y`.trim().replace(/\s+/g, " ");
1278
+ const envNote = process.env.GITHUB_TOKEN ? "GITHUB_TOKEN is available in environment (higher rate limits)." : "Note: Set GITHUB_TOKEN environment variable for higher GitHub API rate limits.";
1279
+ const instruction = `Execute this command in your terminal now. The command will install the skill to all specified agents without any interactive prompts.
1280
+
1281
+ ${envNote}`;
1282
+ const results = agents.map((agent) => ({
1283
+ agent,
1284
+ path: getSkillPath(agent, projectPath, skillName, global),
1285
+ method: "npx_command",
1286
+ success: true
1287
+ // Command generation succeeded; actual execution is agent's responsibility
1288
+ }));
1289
+ return {
1290
+ method: "npx_command",
1291
+ command,
1292
+ instruction,
1293
+ results,
1294
+ skillName
1295
+ };
1296
+ }
1297
+ async function executePathB(content, skillName, agents, projectPath, global, overwrite, source) {
1298
+ const results = [];
1299
+ const parsed = parseSkillMd(content);
1300
+ if (!parsed) {
1301
+ return {
1302
+ method: "direct_write",
1303
+ results: [],
1304
+ skillName,
1305
+ error: "Invalid SKILL.md content: missing or invalid YAML frontmatter"
1306
+ };
1307
+ }
1308
+ for (const agent of agents) {
1309
+ try {
1310
+ const skillPath = getSkillPath(agent, projectPath, skillName, global);
1311
+ const skillDir = path.dirname(skillPath);
1312
+ if (existsSync5(skillPath) && !overwrite) {
1313
+ results.push({
1314
+ agent,
1315
+ path: skillPath,
1316
+ method: "skipped",
1317
+ success: true,
1318
+ error: "Skill already exists. Use overwrite flag to replace."
1319
+ });
1320
+ continue;
1321
+ }
1322
+ ensureDirSync3(skillDir);
1323
+ const controller = new AbortController();
1324
+ const timeoutId = setTimeout(() => controller.abort(), 15e3);
1325
+ try {
1326
+ writeFileSync(skillPath, content, "utf-8");
1327
+ clearTimeout(timeoutId);
1328
+ results.push({
1329
+ agent,
1330
+ path: skillPath,
1331
+ method: "direct_write",
1332
+ success: true
1333
+ });
1334
+ } catch (writeErr) {
1335
+ clearTimeout(timeoutId);
1336
+ throw writeErr;
1337
+ }
1338
+ } catch (agentErr) {
1339
+ const message = agentErr instanceof Error ? agentErr.message : String(agentErr);
1340
+ results.push({
1341
+ agent,
1342
+ path: getSkillPath(agent, projectPath, skillName, global),
1343
+ method: "direct_write",
1344
+ success: false,
1345
+ error: `Write failed: ${message}`
1346
+ });
1347
+ }
1348
+ }
1349
+ const successfulAgents = results.filter((r) => r.success).map((r) => r.agent);
1350
+ if (successfulAgents.length > 0) {
1351
+ await trackInstalledSkill(projectPath, skillName, successfulAgents, source);
1352
+ }
1353
+ const allSucceeded = results.every((r) => r.success);
1354
+ return {
1355
+ method: "direct_write",
1356
+ results,
1357
+ skillName,
1358
+ error: allSucceeded ? void 0 : "Some installations failed. Check individual agent results."
1359
+ };
1360
+ }
1361
+ async function executePathC(originalSkillName, sanitizedSkillName, agents, projectPath, global, overwrite) {
1362
+ try {
1363
+ const projectContext = `Generating skill for: ${originalSkillName}
1364
+ Project path: ${projectPath}
1365
+ Target agents: ${agents.join(", ")}`;
1366
+ const generateResult = await generateSkill({
1367
+ skillName: originalSkillName,
1368
+ projectContext,
1369
+ maxResearchResults: 10
1370
+ });
1371
+ if (!generateResult.success || !generateResult.content) {
1372
+ return {
1373
+ method: "generated",
1374
+ results: [],
1375
+ skillName: sanitizedSkillName,
1376
+ error: generateResult.error || "Skill generation failed"
1377
+ };
1378
+ }
1379
+ const pathBResult = await executePathB(
1380
+ generateResult.content,
1381
+ sanitizedSkillName,
1382
+ agents,
1383
+ projectPath,
1384
+ global,
1385
+ overwrite,
1386
+ "generated"
1387
+ );
1388
+ return {
1389
+ ...pathBResult,
1390
+ method: "generated"
1391
+ };
1392
+ } catch (err) {
1393
+ const message = err instanceof Error ? err.message : String(err);
1394
+ return {
1395
+ method: "generated",
1396
+ results: [],
1397
+ skillName: sanitizedSkillName,
1398
+ error: `Generation failed: ${message}`
1399
+ };
1400
+ }
1401
+ }
1402
+ var { readdirSync, statSync, readFileSync, existsSync: existsSync6 } = fs;
1403
+ var ListSkillsInputSchema = z.object({
1404
+ projectPath: z.string().describe("Absolute path to project root directory"),
1405
+ agent: z.string().optional().describe("Filter by specific agent name"),
1406
+ global: z.boolean().optional().default(false).describe("List global skills instead of project skills")
1407
+ });
1408
+ async function listSkills(input) {
1409
+ try {
1410
+ const { projectPath, agent, global } = input;
1411
+ const skills = [];
1412
+ const byAgent = {};
1413
+ let agentsToScan;
1414
+ if (agent) {
1415
+ agentsToScan = [agent];
1416
+ } else {
1417
+ if (global) {
1418
+ agentsToScan = Object.keys(AGENT_PATHS);
1419
+ } else {
1420
+ agentsToScan = await detectInstalledAgents(projectPath);
1421
+ }
1422
+ }
1423
+ for (const agentId of agentsToScan) {
1424
+ const agentConfig = AGENT_PATHS[agentId];
1425
+ if (!agentConfig) continue;
1426
+ let skillsDir;
1427
+ if (global) {
1428
+ skillsDir = agentConfig.globalPath.replace(/^~/, os2.homedir());
1429
+ } else {
1430
+ skillsDir = path.join(projectPath, agentConfig.projectPath);
1431
+ }
1432
+ if (!existsSync6(skillsDir)) {
1433
+ continue;
1434
+ }
1435
+ try {
1436
+ const entries = readdirSync(skillsDir, { withFileTypes: true });
1437
+ for (const entry of entries) {
1438
+ if (!entry.isDirectory()) continue;
1439
+ const skillName = entry.name;
1440
+ const skillPath = path.join(skillsDir, skillName, "SKILL.md");
1441
+ if (!existsSync6(skillPath)) {
1442
+ continue;
1443
+ }
1444
+ try {
1445
+ const content = readFileSync(skillPath, "utf-8");
1446
+ const stats = statSync(skillPath);
1447
+ const parsed = parseSkillMd(content);
1448
+ const skillInfo = {
1449
+ name: skillName,
1450
+ agent: agentId,
1451
+ path: skillPath,
1452
+ description: parsed?.metadata.description || "No description",
1453
+ tags: parsed?.metadata.tags || [],
1454
+ source: parsed?.metadata.sources?.[0],
1455
+ lastModified: stats.mtime.toISOString()
1456
+ };
1457
+ skills.push(skillInfo);
1458
+ byAgent[agentId] = (byAgent[agentId] || 0) + 1;
1459
+ } catch (readErr) {
1460
+ continue;
1461
+ }
1462
+ }
1463
+ } catch (dirErr) {
1464
+ continue;
1465
+ }
1466
+ }
1467
+ skills.sort((a, b) => {
1468
+ if (a.agent !== b.agent) {
1469
+ return a.agent.localeCompare(b.agent);
1470
+ }
1471
+ return a.name.localeCompare(b.name);
1472
+ });
1473
+ return {
1474
+ success: true,
1475
+ skills,
1476
+ totalCount: skills.length,
1477
+ byAgent
1478
+ };
1479
+ } catch (err) {
1480
+ const message = err instanceof Error ? err.message : String(err);
1481
+ return {
1482
+ success: false,
1483
+ skills: [],
1484
+ totalCount: 0,
1485
+ byAgent: {},
1486
+ error: message
1487
+ };
1488
+ }
1489
+ }
1490
+ var { removeSync, existsSync: existsSync7 } = fs;
1491
+ var DeleteSkillInputSchema = z.object({
1492
+ projectPath: z.string().describe("Absolute path to project root directory"),
1493
+ skillName: z.string().describe("Skill name to delete"),
1494
+ agents: z.array(z.string()).optional().describe("Target agents (if omitted, deletes from all agents)"),
1495
+ global: z.boolean().optional().default(false).describe("Delete from global scope")
1496
+ });
1497
+ async function deleteSkill(input) {
1498
+ try {
1499
+ const { projectPath, skillName, agents, global } = input;
1500
+ const sanitizedName = sanitizeSkillName(skillName);
1501
+ let targetAgents;
1502
+ if (agents && agents.length > 0) {
1503
+ targetAgents = agents;
1504
+ } else {
1505
+ if (global) {
1506
+ targetAgents = Object.keys(AGENT_PATHS);
1507
+ } else {
1508
+ targetAgents = await detectInstalledAgents(projectPath);
1509
+ }
1510
+ }
1511
+ const results = [];
1512
+ let totalDeleted = 0;
1513
+ for (const agentId of targetAgents) {
1514
+ const agentConfig = AGENT_PATHS[agentId];
1515
+ if (!agentConfig) {
1516
+ results.push({
1517
+ agent: agentId,
1518
+ path: "",
1519
+ deleted: false,
1520
+ notFound: true,
1521
+ error: "Unknown agent"
1522
+ });
1523
+ continue;
1524
+ }
1525
+ let skillDir;
1526
+ if (global) {
1527
+ const skillsDir = agentConfig.globalPath.replace(/^~/, os2.homedir());
1528
+ skillDir = path.join(skillsDir, sanitizedName);
1529
+ } else {
1530
+ const skillsDir = path.join(projectPath, agentConfig.projectPath);
1531
+ skillDir = path.join(skillsDir, sanitizedName);
1532
+ }
1533
+ if (!existsSync7(skillDir)) {
1534
+ results.push({
1535
+ agent: agentId,
1536
+ path: skillDir,
1537
+ deleted: false,
1538
+ notFound: true
1539
+ });
1540
+ continue;
1541
+ }
1542
+ try {
1543
+ removeSync(skillDir);
1544
+ if (!global) {
1545
+ try {
1546
+ await untrackSkill(projectPath, sanitizedName, agentId);
1547
+ } catch (configErr) {
1548
+ }
1549
+ }
1550
+ results.push({
1551
+ agent: agentId,
1552
+ path: skillDir,
1553
+ deleted: true,
1554
+ notFound: false
1555
+ });
1556
+ totalDeleted++;
1557
+ } catch (deleteErr) {
1558
+ const message = deleteErr instanceof Error ? deleteErr.message : String(deleteErr);
1559
+ results.push({
1560
+ agent: agentId,
1561
+ path: skillDir,
1562
+ deleted: false,
1563
+ notFound: false,
1564
+ error: message
1565
+ });
1566
+ }
1567
+ }
1568
+ return {
1569
+ success: true,
1570
+ results,
1571
+ skillName: sanitizedName,
1572
+ totalDeleted
1573
+ };
1574
+ } catch (err) {
1575
+ const message = err instanceof Error ? err.message : String(err);
1576
+ return {
1577
+ success: false,
1578
+ results: [],
1579
+ skillName: input.skillName,
1580
+ totalDeleted: 0,
1581
+ error: message
1582
+ };
1583
+ }
1584
+ }
1585
+
1586
+ // src/prompts/system-prompt.ts
1587
+ var SYSTEM_PROMPT = `You are connected to SkillMesh, an intelligent Agent Skills manager.
1588
+ You have 8 tools to discover, fetch, generate, and install SKILL.md files that permanently teach you and other agents about this project.
1589
+
1590
+ ## WHAT IS AN AGENT SKILL
1591
+
1592
+ A SKILL.md file that lives in your agent's skill directory. You read all SKILL.md files at the start of every session \u2014 skills are always present when the user prompts. Installing a skill permanently teaches you project-specific rules, patterns, conventions, and best practices.
1593
+
1594
+ ## THE 8 SKILLMESH TOOLS
1595
+
1596
+ 1. detect_installed_agents \u2014 Scans project root and home directory for installed AI coding agents. Returns list with their skill directory paths.
1597
+
1598
+ 2. create_skills_folder \u2014 Initializes skill directory structure for selected agents. Creates .claude/skills/, .cursor/skills/, etc. Also creates .skillmesh/skillmesh.config.json for tracking.
1599
+
1600
+ 3. search_skills \u2014 Searches skills.sh via Tavily for skills matching a keyword. Fetches GitHub star count and recency. Returns ranked list filtered by quality (stars >= 50, updated within 12 months).
1601
+
1602
+ 4. fetch_skill_content \u2014 Fetches full SKILL.md content from a GitHub repository using raw URL fallbacks. Validates YAML frontmatter. Returns content, source URL, and parsed metadata.
1603
+
1604
+ 5. install_skill \u2014 Core installation tool. PATH A: returns npx skills add command string for you to execute via your bash tool. PATH B: writes SKILL.md directly to disk if npx fails. PATH C: calls generate_skill if both fail. Handles all selected agents simultaneously.
1605
+
1606
+ 6. generate_skill \u2014 LLM-powered skill generation. Searches web via Tavily for best practices, sends research + project context to LLM, generates a gold-standard SKILL.md. Only called when PATH A and B fail.
1607
+
1608
+ 7. list_installed_skills \u2014 Reads all agent skill directories in the project. Returns table of installed skills grouped by agent with name, description, source, and last modified date.
1609
+
1610
+ 8. delete_skill \u2014 Removes a specific SKILL.md from one or all agent directories. Returns success/failure per agent.
1611
+
1612
+ ## WHEN TO RUN THE FULL SKILL SETUP WORKFLOW
1613
+
1614
+ Automatically trigger when the user says anything resembling:
1615
+ - 'set up skills'
1616
+ - 'add skills'
1617
+ - 'install skills'
1618
+ - 'analyze my project and add skills'
1619
+ - 'make my agent smarter'
1620
+ - 'skillmesh'
1621
+ - 'initialize skills'
1622
+
1623
+ Run immediately without asking for confirmation.
1624
+
1625
+ ## FULL SKILL SETUP WORKFLOW (execute in this exact order)
1626
+
1627
+ STEP 1: Detect agents
1628
+ - Call detect_installed_agents with current project path
1629
+ - If no agents detected, inform user and stop
1630
+
1631
+ STEP 2: Initialize directories
1632
+ - Call create_skills_folder for all detected agents
1633
+ - This ensures skill directories exist before installation
1634
+
1635
+ STEP 3: Analyze codebase thoroughly
1636
+ Scan the project and identify:
1637
+ - Primary language and framework (e.g., TypeScript, React, Next.js)
1638
+ - Key dependencies and their versions (read package.json, requirements.txt, etc.)
1639
+ - Architectural patterns (e.g., App Router, REST API, GraphQL)
1640
+ - Folder structure and conventions (e.g., /src, /app, /components)
1641
+ - Test setup (Jest, Vitest, Playwright, etc.)
1642
+ - Database and ORM (Prisma, Drizzle, MongoDB, etc.)
1643
+ - Auth system (NextAuth, Clerk, Auth0, etc.)
1644
+ - API patterns (REST, tRPC, GraphQL)
1645
+ - Deployment config (Vercel, Docker, AWS, etc.)
1646
+
1647
+ STEP 4: Derive skill domains
1648
+ From your analysis, identify 4-8 specific skill domains using kebab-case names.
1649
+
1650
+ GOOD examples:
1651
+ - nextjs-app-router-patterns
1652
+ - prisma-multi-tenant-schema
1653
+ - react-hook-form-validation
1654
+ - tailwind-component-architecture
1655
+ - playwright-e2e-testing
1656
+ - trpc-api-patterns
1657
+
1658
+ BAD examples (too generic):
1659
+ - javascript
1660
+ - frontend
1661
+ - react
1662
+ - coding
1663
+ - testing
1664
+
1665
+ STEP 5: Install skills for each domain
1666
+ For each domain you identified:
1667
+
1668
+ a. Call search_skills(domain)
1669
+ - This searches skills.sh via Tavily
1670
+ - Returns quality-filtered results (stars >= 50, updated within 12 months)
1671
+
1672
+ b. If quality results found (stars >= 50):
1673
+ - Call fetch_skill_content for the top result
1674
+ - Review the content to ensure it matches your needs
1675
+ - Call install_skill with source type 'github' and the owner/repo/stars data
1676
+ - install_skill returns a PATH A response: an npx command string
1677
+ - YOU MUST EXECUTE THIS COMMAND using your bash tool immediately
1678
+ - Example command: npx skills add vercel-labs/next-skills --skill nextjs-patterns --agent claude-code --agent cursor -y
1679
+ - The command is fully non-interactive (no prompts) thanks to --agent flags and -y flag
1680
+
1681
+ c. If no quality results OR PATH A fails:
1682
+ - Call install_skill with source type 'github' (if you have owner/repo) or 'content' (if you have raw content)
1683
+ - This triggers PATH B: direct write to disk
1684
+ - If PATH B also fails OR no GitHub source exists:
1685
+ - Call generate_skill with rich projectContext
1686
+ - Then call install_skill with source type 'content' using the generated content
1687
+
1688
+ STEP 6: Report results
1689
+ - Call list_installed_skills to see all installed skills
1690
+ - Present a summary table to the user grouped by agent
1691
+ - Highlight which skills were installed (PATH A, PATH B, or generated)
1692
+
1693
+ ## CRITICAL REQUIREMENTS
1694
+
1695
+ PROJECTCONTEXT QUALITY:
1696
+ When calling generate_skill, always provide detailed projectContext. Include:
1697
+ - Exact framework versions from package.json/requirements.txt
1698
+ - Key architectural patterns observed in the code
1699
+ - Specific folder structure and file naming conventions
1700
+ - Main dependencies and how they interact
1701
+ - Any coding conventions found in existing files
1702
+ The richer the context, the more useful the generated skill.
1703
+
1704
+ EXECUTION RULES:
1705
+ - NEVER ask the user to confirm before running the workflow
1706
+ - NEVER install the same skill twice \u2014 call list_installed_skills first and check for duplicates
1707
+ - ALWAYS install to all detected agents simultaneously
1708
+ - ALWAYS execute npx commands returned by install_skill (PATH A) using your bash tool
1709
+ - NEVER manually write SKILL.md files \u2014 always use install_skill tool
1710
+
1711
+ ERROR HANDLING:
1712
+ - If search_skills returns no results, immediately fall back to generate_skill
1713
+ - If install_skill PATH A fails (npx error), the tool automatically falls back to PATH B
1714
+ - If PATH B fails (fetch error), the tool automatically falls back to PATH C (generate)
1715
+ - Always report errors clearly to the user and show which skills succeeded vs failed
1716
+
1717
+ ## SKILL INSTALLATION PATHS EXPLAINED
1718
+
1719
+ PATH A (npx command \u2014 preferred):
1720
+ - Used for high-quality GitHub repos (stars >= 50)
1721
+ - install_skill returns an npx command string
1722
+ - YOU execute it using your bash tool
1723
+ - Command format: npx skills add <owner/repo> --skill <name> --agent <agent1> --agent <agent2> -y
1724
+ - The npx CLI handles cloning, writing, and directory creation
1725
+ - Fully automated with zero interactive prompts
1726
+
1727
+ PATH B (direct write \u2014 fallback):
1728
+ - Used when PATH A not applicable or GitHub fetch available but stars < 50
1729
+ - install_skill fetches raw SKILL.md from GitHub
1730
+ - Writes directly to all agent directories using fs-extra
1731
+ - Updates .skillmesh/skillmesh.config.json for tracking
1732
+
1733
+ PATH C (LLM generation \u2014 last resort):
1734
+ - Used when no existing skill found on GitHub
1735
+ - Requires ANTHROPIC_API_KEY, GOOGLE_API_KEY, or OPENAI_API_KEY in env
1736
+ - Searches web via Tavily for best practices
1737
+ - Generates gold-standard SKILL.md from research + project context
1738
+ - Writes to all agent directories
1739
+
1740
+ ## TYPICAL USER INTERACTION EXAMPLE
1741
+
1742
+ User: "Set up skills for my Next.js project"
1743
+
1744
+ You:
1745
+ 1. [Call detect_installed_agents] \u2192 Found: claude-code, cursor
1746
+ 2. [Call create_skills_folder] \u2192 Created .claude/skills/ and .cursor/skills/
1747
+ 3. [Analyze project] \u2192 Next.js 15, App Router, TypeScript, Prisma, TailwindCSS
1748
+ 4. [Derive domains] \u2192 nextjs-app-router-patterns, prisma-schema-design, tailwindcss-components
1749
+ 5. For nextjs-app-router-patterns:
1750
+ - [Call search_skills] \u2192 Found vercel-labs/next-skills (2,341 stars)
1751
+ - [Call fetch_skill_content] \u2192 Retrieved SKILL.md
1752
+ - [Call install_skill] \u2192 Returns npx command
1753
+ - [Execute via bash] \u2192 npx skills add vercel-labs/next-skills --skill nextjs-app-router-patterns --agent claude-code --agent cursor -y
1754
+ 6. [Repeat for other domains]
1755
+ 7. [Call list_installed_skills] \u2192 Report summary table
1756
+
1757
+ User sees: "Installed 3 skills across 2 agents (claude-code, cursor). You now have permanent knowledge of Next.js App Router patterns, Prisma schema design, and TailwindCSS component architecture."
1758
+
1759
+ ## NOTES
1760
+
1761
+ - Skill names on disk are sanitized to kebab-case: toLowerCase().replace(/[^a-z0-9-]/g, '-')
1762
+ - GITHUB_TOKEN is optional but recommended for higher API rate limits (5000/hr vs 60/hr)
1763
+ - LLM API keys are only needed for generate_skill (PATH C)
1764
+ - Always use absolute paths, never relative paths
1765
+ - Target directories: .claude/skills/, .cursor/skills/, .agent/skills/, .windsurf/skills/, etc.
1766
+ `;
1767
+
1768
+ // src/index.ts
1769
+ var server = new Server(
1770
+ {
1771
+ name: "skillmesh-mcp",
1772
+ version: "1.0.0"
1773
+ },
1774
+ {
1775
+ capabilities: {
1776
+ tools: {}
1777
+ }
1778
+ }
1779
+ );
1780
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
1781
+ return {
1782
+ tools: [
1783
+ {
1784
+ name: "detect_installed_agents",
1785
+ description: "Scans project root and home directory for installed AI coding agents. Returns list of detected agents with their skill directory paths.",
1786
+ inputSchema: {
1787
+ type: "object",
1788
+ properties: {
1789
+ projectPath: {
1790
+ type: "string",
1791
+ description: "Absolute path to project root directory"
1792
+ },
1793
+ includeGlobal: {
1794
+ type: "boolean",
1795
+ description: "Also scan home directory for globally installed agents",
1796
+ default: false
1797
+ }
1798
+ },
1799
+ required: ["projectPath"]
1800
+ }
1801
+ },
1802
+ {
1803
+ name: "create_skills_folder",
1804
+ description: "Initializes skill directory structure for selected agents. Creates .claude/skills/, .cursor/skills/, etc. Also creates .skillmesh/skillmesh.config.json for tracking.",
1805
+ inputSchema: {
1806
+ type: "object",
1807
+ properties: {
1808
+ projectPath: {
1809
+ type: "string",
1810
+ description: "Absolute path to project root directory"
1811
+ },
1812
+ agents: {
1813
+ type: "array",
1814
+ items: { type: "string" },
1815
+ description: "List of agent names to create folders for"
1816
+ },
1817
+ global: {
1818
+ type: "boolean",
1819
+ description: "Create in global scope instead of project scope",
1820
+ default: false
1821
+ }
1822
+ },
1823
+ required: ["projectPath", "agents"]
1824
+ }
1825
+ },
1826
+ {
1827
+ name: "search_skills",
1828
+ description: "Searches skills.sh via Tavily for skills matching a keyword. Fetches GitHub star count and recency. Returns ranked list filtered by quality (stars >= 50, updated within 12 months).",
1829
+ inputSchema: {
1830
+ type: "object",
1831
+ properties: {
1832
+ keyword: {
1833
+ type: "string",
1834
+ description: 'Search keyword or skill domain (e.g., "nextjs-app-router", "prisma")'
1835
+ },
1836
+ minStars: {
1837
+ type: "number",
1838
+ description: "Minimum GitHub stars filter",
1839
+ default: 50
1840
+ },
1841
+ maxAgeMonths: {
1842
+ type: "number",
1843
+ description: "Maximum age in months (updated within)",
1844
+ default: 12
1845
+ },
1846
+ limit: {
1847
+ type: "number",
1848
+ description: "Maximum number of results",
1849
+ default: 10
1850
+ }
1851
+ },
1852
+ required: ["keyword"]
1853
+ }
1854
+ },
1855
+ {
1856
+ name: "fetch_skill_content",
1857
+ description: "Fetches full SKILL.md content from a GitHub repository using raw URL fallbacks. Validates YAML frontmatter. Returns content, source URL, and parsed metadata.",
1858
+ inputSchema: {
1859
+ type: "object",
1860
+ properties: {
1861
+ owner: {
1862
+ type: "string",
1863
+ description: "GitHub repository owner"
1864
+ },
1865
+ repo: {
1866
+ type: "string",
1867
+ description: "GitHub repository name"
1868
+ },
1869
+ skillName: {
1870
+ type: "string",
1871
+ description: "Skill name to fetch"
1872
+ },
1873
+ githubToken: {
1874
+ type: "string",
1875
+ description: "Optional GitHub token for higher rate limits"
1876
+ }
1877
+ },
1878
+ required: ["owner", "repo", "skillName"]
1879
+ }
1880
+ },
1881
+ {
1882
+ name: "install_skill",
1883
+ description: "Core installation tool. PATH A: returns npx skills add command string for agent to execute. PATH B: writes SKILL.md directly to disk if npx fails. Handles all selected agents simultaneously.",
1884
+ inputSchema: {
1885
+ type: "object",
1886
+ properties: {
1887
+ projectPath: {
1888
+ type: "string",
1889
+ description: "Absolute path to project root directory"
1890
+ },
1891
+ skillName: {
1892
+ type: "string",
1893
+ description: "Skill name to install"
1894
+ },
1895
+ agents: {
1896
+ type: "array",
1897
+ items: { type: "string" },
1898
+ description: "List of target agent names"
1899
+ },
1900
+ source: {
1901
+ type: "object",
1902
+ properties: {
1903
+ type: {
1904
+ type: "string",
1905
+ enum: ["github", "content"],
1906
+ description: "Source type"
1907
+ },
1908
+ owner: {
1909
+ type: "string",
1910
+ description: "GitHub owner (for type: github)"
1911
+ },
1912
+ repo: {
1913
+ type: "string",
1914
+ description: "GitHub repo (for type: github)"
1915
+ },
1916
+ content: {
1917
+ type: "string",
1918
+ description: "Direct SKILL.md content (for type: content)"
1919
+ }
1920
+ },
1921
+ required: ["type"],
1922
+ description: "Skill source"
1923
+ },
1924
+ overwrite: {
1925
+ type: "boolean",
1926
+ description: "Overwrite existing skill if present",
1927
+ default: false
1928
+ },
1929
+ global: {
1930
+ type: "boolean",
1931
+ description: "Install to global scope",
1932
+ default: false
1933
+ }
1934
+ },
1935
+ required: ["projectPath", "skillName", "agents", "source"]
1936
+ }
1937
+ },
1938
+ {
1939
+ name: "generate_skill",
1940
+ description: "LLM-powered skill generation. Searches web via Tavily for best practices, sends research + project context to LLM, generates a gold-standard SKILL.md. Only called when search and fetch fail.",
1941
+ inputSchema: {
1942
+ type: "object",
1943
+ properties: {
1944
+ skillName: {
1945
+ type: "string",
1946
+ description: "Skill name/domain to generate"
1947
+ },
1948
+ projectContext: {
1949
+ type: "string",
1950
+ description: "Rich project context: frameworks, versions, patterns, structure"
1951
+ },
1952
+ researchQuery: {
1953
+ type: "string",
1954
+ description: "Optional custom web research query"
1955
+ },
1956
+ maxResearchResults: {
1957
+ type: "number",
1958
+ description: "Maximum web research results",
1959
+ default: 10
1960
+ }
1961
+ },
1962
+ required: ["skillName", "projectContext"]
1963
+ }
1964
+ },
1965
+ {
1966
+ name: "list_installed_skills",
1967
+ description: "Reads all agent skill directories in the project. Returns table of installed skills grouped by agent with name, description, source, and last modified date.",
1968
+ inputSchema: {
1969
+ type: "object",
1970
+ properties: {
1971
+ projectPath: {
1972
+ type: "string",
1973
+ description: "Absolute path to project root directory"
1974
+ },
1975
+ agent: {
1976
+ type: "string",
1977
+ description: "Filter by specific agent name"
1978
+ },
1979
+ global: {
1980
+ type: "boolean",
1981
+ description: "List global skills instead of project skills",
1982
+ default: false
1983
+ }
1984
+ },
1985
+ required: ["projectPath"]
1986
+ }
1987
+ },
1988
+ {
1989
+ name: "delete_skill",
1990
+ description: "Removes a specific SKILL.md from one or all agent directories. Returns success/failure per agent.",
1991
+ inputSchema: {
1992
+ type: "object",
1993
+ properties: {
1994
+ projectPath: {
1995
+ type: "string",
1996
+ description: "Absolute path to project root directory"
1997
+ },
1998
+ skillName: {
1999
+ type: "string",
2000
+ description: "Skill name to delete"
2001
+ },
2002
+ agents: {
2003
+ type: "array",
2004
+ items: { type: "string" },
2005
+ description: "Target agents (if omitted, deletes from all agents)"
2006
+ },
2007
+ global: {
2008
+ type: "boolean",
2009
+ description: "Delete from global scope",
2010
+ default: false
2011
+ }
2012
+ },
2013
+ required: ["projectPath", "skillName"]
2014
+ }
2015
+ }
2016
+ ]
2017
+ };
2018
+ });
2019
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2020
+ const { name, arguments: args } = request.params;
2021
+ try {
2022
+ switch (name) {
2023
+ case "detect_installed_agents": {
2024
+ const validated = DetectAgentsInputSchema.parse(args);
2025
+ const result = await detectAgents(validated);
2026
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2027
+ }
2028
+ case "create_skills_folder": {
2029
+ const validated = CreateFolderInputSchema.parse(args);
2030
+ const result = await createFolder(validated);
2031
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2032
+ }
2033
+ case "search_skills": {
2034
+ const validated = SearchSkillsInputSchema.parse(args);
2035
+ const result = await searchSkills(validated);
2036
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2037
+ }
2038
+ case "fetch_skill_content": {
2039
+ const validated = FetchSkillInputSchema.parse(args);
2040
+ const result = await fetchSkillContent(validated);
2041
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2042
+ }
2043
+ case "install_skill": {
2044
+ const validated = InstallSkillInputSchema.parse(args);
2045
+ const result = await installSkill(validated);
2046
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2047
+ }
2048
+ case "generate_skill": {
2049
+ const validated = GenerateSkillInputSchema.parse(args);
2050
+ const result = await generateSkill(validated);
2051
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2052
+ }
2053
+ case "list_installed_skills": {
2054
+ const validated = ListSkillsInputSchema.parse(args);
2055
+ const result = await listSkills(validated);
2056
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2057
+ }
2058
+ case "delete_skill": {
2059
+ const validated = DeleteSkillInputSchema.parse(args);
2060
+ const result = await deleteSkill(validated);
2061
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2062
+ }
2063
+ default:
2064
+ throw new Error(`Unknown tool: ${name}`);
2065
+ }
2066
+ } catch (error) {
2067
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2068
+ return {
2069
+ content: [
2070
+ {
2071
+ type: "text",
2072
+ text: JSON.stringify({ error: errorMessage }, null, 2)
2073
+ }
2074
+ ],
2075
+ isError: true
2076
+ };
2077
+ }
2078
+ });
2079
+ async function main() {
2080
+ const transport = new StdioServerTransport();
2081
+ await server.connect(transport);
2082
+ console.error("SkillMesh MCP Server running on stdio");
2083
+ console.error("System prompt loaded:", SYSTEM_PROMPT.substring(0, 100) + "...");
2084
+ }
2085
+ main().catch((error) => {
2086
+ console.error("Fatal error in main():", error);
2087
+ process.exit(1);
2088
+ });
2089
+ //# sourceMappingURL=index.js.map
2090
+ //# sourceMappingURL=index.js.map