skillmesh-mcp 1.0.0 → 1.0.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/index.js CHANGED
@@ -6,6 +6,7 @@ import { z } from 'zod';
6
6
  import * as path from 'path';
7
7
  import * as os2 from 'os';
8
8
  import fs from 'fs-extra';
9
+ import { spawnSync } from 'child_process';
9
10
  import * as yaml from 'js-yaml';
10
11
 
11
12
  var { existsSync } = fs;
@@ -308,93 +309,6 @@ async function createFolder(input) {
308
309
  }
309
310
  }
310
311
 
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
312
  // src/lib/github.ts
399
313
  async function fetchSkillFromGitHub(owner, repo, skillName, githubToken) {
400
314
  const baseUrl = `https://raw.githubusercontent.com/${owner}/${repo}/main`;
@@ -578,64 +492,43 @@ var SearchSkillsInputSchema = z.object({
578
492
  async function searchSkills(input) {
579
493
  try {
580
494
  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
495
  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;
496
+ let rawResults = await searchViaNpxSkillsFind(keyword);
497
+ if (rawResults.length === 0) {
498
+ rawResults = await searchViaSkillScout(keyword);
499
+ }
500
+ if (rawResults.length === 0) {
501
+ return {
502
+ success: true,
503
+ results: [],
504
+ query: keyword,
505
+ totalFound: 0
506
+ };
507
+ }
508
+ const qualityResults = [];
509
+ for (const result of rawResults) {
618
510
  try {
619
- const stars = await getGitHubStars(owner, repo, githubToken);
620
- const lastUpdated = await getLastUpdated(owner, repo, githubToken);
511
+ const stars = await getGitHubStars(result.owner, result.repo, githubToken);
512
+ const lastUpdated = await getLastUpdated(result.owner, result.repo, githubToken);
621
513
  if (stars < minStars) continue;
622
514
  if (lastUpdated) {
623
515
  const ageMonths = (Date.now() - lastUpdated.getTime()) / (1e3 * 60 * 60 * 24 * 30);
624
516
  if (ageMonths > maxAgeMonths) continue;
625
517
  }
626
518
  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;
519
+ const score = stars + recencyBonus;
628
520
  qualityResults.push({
629
- name: repo,
630
- repo,
631
- owner,
632
- description: result.snippet,
521
+ name: result.skillName,
522
+ repo: result.repo,
523
+ owner: result.owner,
524
+ description: result.description || "",
633
525
  stars,
634
526
  lastUpdated: lastUpdated ? lastUpdated.toISOString() : "unknown",
635
- url: result.url,
636
- score
527
+ url: `https://github.com/${result.owner}/${result.repo}`,
528
+ score,
529
+ installRef: `${result.owner}/${result.repo}@${result.skillName}`
637
530
  });
638
- } catch (err) {
531
+ } catch {
639
532
  continue;
640
533
  }
641
534
  }
@@ -658,6 +551,69 @@ async function searchSkills(input) {
658
551
  };
659
552
  }
660
553
  }
554
+ async function searchViaNpxSkillsFind(keyword) {
555
+ try {
556
+ const result = spawnSync("npx", ["skills", "find", keyword], {
557
+ encoding: "utf-8",
558
+ timeout: 3e4,
559
+ // 30 second timeout
560
+ env: { ...process.env }
561
+ });
562
+ if (result.error || result.status !== 0) {
563
+ return [];
564
+ }
565
+ const output = result.stdout || "";
566
+ const results = [];
567
+ const lines = output.split("\n").filter((line) => line.trim());
568
+ for (const line of lines) {
569
+ const match = line.match(/^\s*([^\/\s]+)\/([^@\s]+)@([^\s-]+)(?:\s*-\s*(.*))?/);
570
+ if (match) {
571
+ const [, owner, repo, skillName, description] = match;
572
+ results.push({
573
+ owner,
574
+ repo,
575
+ skillName,
576
+ description: description?.trim()
577
+ });
578
+ }
579
+ }
580
+ return results;
581
+ } catch {
582
+ return [];
583
+ }
584
+ }
585
+ async function searchViaSkillScout(keyword) {
586
+ try {
587
+ const controller = new AbortController();
588
+ const timeoutId = setTimeout(() => controller.abort(), 15e3);
589
+ const response = await fetch(
590
+ `https://api.skillscout.dev/v1/search?q=${encodeURIComponent(keyword)}`,
591
+ {
592
+ signal: controller.signal,
593
+ headers: {
594
+ "Accept": "application/json",
595
+ "User-Agent": "skillmesh-mcp/1.0.0"
596
+ }
597
+ }
598
+ );
599
+ clearTimeout(timeoutId);
600
+ if (!response.ok) {
601
+ return [];
602
+ }
603
+ const data = await response.json();
604
+ if (!data.results || !Array.isArray(data.results)) {
605
+ return [];
606
+ }
607
+ return data.results.filter((r) => r.owner && r.repo && (r.skill_name || r.name)).map((r) => ({
608
+ owner: r.owner,
609
+ repo: r.repo,
610
+ skillName: r.skill_name || r.name,
611
+ description: r.description
612
+ }));
613
+ } catch {
614
+ return [];
615
+ }
616
+ }
661
617
  var SkillMetadataSchema = z.object({
662
618
  name: z.string().min(1, "Skill name is required"),
663
619
  description: z.string().min(1, "Description is required"),
@@ -783,15 +739,43 @@ function checkSkillQuality(parsed) {
783
739
 
784
740
  // src/tools/fetch-skill.ts
785
741
  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")
742
+ owner: z.string().optional().describe("GitHub repository owner"),
743
+ repo: z.string().optional().describe("GitHub repository name"),
744
+ skillName: z.string().optional().describe("Skill name to fetch"),
745
+ githubToken: z.string().optional().describe("Optional GitHub token for higher rate limits"),
746
+ /** Direct install reference in format owner/repo@skillname - if provided, parses owner/repo/skillName from it */
747
+ installRef: z.string().optional().describe("Install reference in format owner/repo@skillname")
790
748
  });
791
749
  async function fetchSkillContent(input) {
792
750
  try {
793
- const { owner, repo, skillName, githubToken } = input;
751
+ let { owner, repo, skillName, githubToken, installRef } = input;
752
+ if (installRef) {
753
+ const match = installRef.match(/^([^\/]+)\/([^@]+)@(.+)$/);
754
+ if (match) {
755
+ owner = match[1];
756
+ repo = match[2];
757
+ skillName = match[3];
758
+ } else {
759
+ return {
760
+ success: false,
761
+ content: "",
762
+ sourceUrl: "",
763
+ found: false,
764
+ error: `Invalid installRef format: "${installRef}". Expected format: owner/repo@skillname`
765
+ };
766
+ }
767
+ }
768
+ if (!owner || !repo || !skillName) {
769
+ return {
770
+ success: false,
771
+ content: "",
772
+ sourceUrl: "",
773
+ found: false,
774
+ error: "Missing required fields: owner, repo, and skillName (or provide installRef)"
775
+ };
776
+ }
794
777
  const token = githubToken || process.env.GITHUB_TOKEN;
778
+ const resolvedInstallRef = `${owner}/${repo}@${skillName}`;
795
779
  const fetchResult = await fetchSkillFromGitHub(owner, repo, skillName, token);
796
780
  if (!fetchResult.found || !fetchResult.content) {
797
781
  return {
@@ -799,6 +783,7 @@ async function fetchSkillContent(input) {
799
783
  content: "",
800
784
  sourceUrl: fetchResult.url,
801
785
  found: false,
786
+ installRef: resolvedInstallRef,
802
787
  error: "SKILL.md not found in repository"
803
788
  };
804
789
  }
@@ -811,6 +796,7 @@ async function fetchSkillContent(input) {
811
796
  content: fetchResult.content,
812
797
  sourceUrl: fetchResult.url,
813
798
  found: true,
799
+ installRef: resolvedInstallRef,
814
800
  quality: {
815
801
  valid: false,
816
802
  issues: ["Failed to parse SKILL.md frontmatter"]
@@ -822,6 +808,7 @@ async function fetchSkillContent(input) {
822
808
  content: fetchResult.content,
823
809
  sourceUrl: fetchResult.url,
824
810
  found: true,
811
+ installRef: resolvedInstallRef,
825
812
  metadata: {
826
813
  name: parsed.metadata.name,
827
814
  description: parsed.metadata.description,
@@ -840,6 +827,7 @@ async function fetchSkillContent(input) {
840
827
  content: fetchResult.content,
841
828
  sourceUrl: fetchResult.url,
842
829
  found: true,
830
+ installRef: resolvedInstallRef,
843
831
  quality: {
844
832
  valid: false,
845
833
  issues: [`Failed to parse SKILL.md: ${parseMessage}`]
@@ -863,6 +851,93 @@ function sanitizeSkillName(name) {
863
851
  return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
864
852
  }
865
853
 
854
+ // src/lib/tavily.ts
855
+ async function searchTavily(query, maxResults = 10) {
856
+ const tavilyKey = process.env.TAVILY_API_KEY;
857
+ if (!tavilyKey) {
858
+ console.error("TAVILY_API_KEY not found in environment, trying Firecrawl fallback");
859
+ return searchFirecrawl(query, maxResults);
860
+ }
861
+ try {
862
+ const controller = new AbortController();
863
+ const timeoutId = setTimeout(() => controller.abort(), 15e3);
864
+ const response = await fetch("https://api.tavily.com/search", {
865
+ method: "POST",
866
+ headers: {
867
+ "Content-Type": "application/json"
868
+ },
869
+ body: JSON.stringify({
870
+ api_key: tavilyKey,
871
+ query,
872
+ max_results: maxResults,
873
+ search_depth: "basic",
874
+ include_answer: false,
875
+ include_images: false
876
+ }),
877
+ signal: controller.signal
878
+ });
879
+ clearTimeout(timeoutId);
880
+ if (!response.ok) {
881
+ console.error("Tavily API error:", response.status, response.statusText);
882
+ return [];
883
+ }
884
+ const data = await response.json();
885
+ if (!data.results || !Array.isArray(data.results)) {
886
+ return [];
887
+ }
888
+ return data.results.map((result) => ({
889
+ title: result.title || "",
890
+ url: result.url || "",
891
+ snippet: result.content || "",
892
+ score: result.score
893
+ }));
894
+ } catch (error) {
895
+ console.error("Tavily search failed:", error);
896
+ return [];
897
+ }
898
+ }
899
+ async function searchFirecrawl(query, maxResults = 10) {
900
+ const firecrawlKey = process.env.FIRECRAWL_API_KEY;
901
+ if (!firecrawlKey) {
902
+ console.error("Neither TAVILY_API_KEY nor FIRECRAWL_API_KEY found in environment");
903
+ return [];
904
+ }
905
+ try {
906
+ const controller = new AbortController();
907
+ const timeoutId = setTimeout(() => controller.abort(), 15e3);
908
+ const response = await fetch("https://api.firecrawl.dev/v1/search", {
909
+ method: "POST",
910
+ headers: {
911
+ "Content-Type": "application/json",
912
+ "Authorization": `Bearer ${firecrawlKey}`
913
+ },
914
+ body: JSON.stringify({
915
+ query,
916
+ limit: maxResults
917
+ }),
918
+ signal: controller.signal
919
+ });
920
+ clearTimeout(timeoutId);
921
+ if (!response.ok) {
922
+ console.error("Firecrawl API error:", response.status, response.statusText);
923
+ return [];
924
+ }
925
+ const data = await response.json();
926
+ if (!data.data || !Array.isArray(data.data)) {
927
+ return [];
928
+ }
929
+ return data.data.map((result) => ({
930
+ title: result.title || "",
931
+ url: result.url || "",
932
+ snippet: result.description || result.content || "",
933
+ score: result.score
934
+ }));
935
+ } catch (error) {
936
+ console.error("Firecrawl search failed:", error);
937
+ return [];
938
+ }
939
+ }
940
+
866
941
  // src/lib/llm.ts
867
942
  var PROVIDER_MODELS = {
868
943
  anthropic: "claude-sonnet-4-5-20250929",
@@ -1188,14 +1263,27 @@ var InstallSkillInputSchema = z.object({
1188
1263
  owner: z.string().optional().describe("GitHub owner (for type: github)"),
1189
1264
  repo: z.string().optional().describe("GitHub repo (for type: github)"),
1190
1265
  content: z.string().optional().describe("Direct SKILL.md content (for type: content)"),
1191
- stars: z.number().optional().describe("GitHub star count (for quality check)")
1266
+ stars: z.number().optional().describe("GitHub star count (for quality check)"),
1267
+ /** Direct install reference in format owner/repo@skillname - if provided, parses owner/repo from it */
1268
+ installRef: z.string().optional().describe("Install reference in format owner/repo@skillname")
1192
1269
  }).describe("Skill source"),
1193
1270
  overwrite: z.boolean().optional().default(false).describe("Overwrite existing skill if present"),
1194
- global: z.boolean().optional().default(false).describe("Install to global scope")
1271
+ global: z.boolean().optional().default(false).describe("Install to global scope"),
1272
+ /** Rich project context for PATH C (LLM generation) - include framework versions, patterns, folder structure */
1273
+ projectContext: z.string().optional().describe("Rich project context for skill generation (frameworks, versions, patterns, folder structure)")
1195
1274
  });
1196
1275
  async function installSkill(input) {
1197
1276
  try {
1198
- const { projectPath, skillName, agents, source, overwrite, global } = input;
1277
+ const { projectPath, skillName, agents, source, overwrite, global, projectContext } = input;
1278
+ let sourceOwner = source.owner;
1279
+ let sourceRepo = source.repo;
1280
+ if (source.installRef && source.type === "github") {
1281
+ const match = source.installRef.match(/^([^\/]+)\/([^@]+)@(.+)$/);
1282
+ if (match) {
1283
+ sourceOwner = match[1];
1284
+ sourceRepo = match[2];
1285
+ }
1286
+ }
1199
1287
  const sanitizedSkillName = sanitizeSkillName(skillName);
1200
1288
  if (!sanitizedSkillName || sanitizedSkillName.length === 0) {
1201
1289
  return {
@@ -1227,20 +1315,20 @@ async function installSkill(input) {
1227
1315
  };
1228
1316
  }
1229
1317
  }
1230
- if (source.type === "github" && source.owner && source.repo) {
1318
+ if (source.type === "github" && sourceOwner && sourceRepo) {
1231
1319
  const stars = source.stars ?? 0;
1232
1320
  if (stars >= 50) {
1233
- return executePathA(source.owner, source.repo, sanitizedSkillName, agents, projectPath, global);
1321
+ return executePathA(sourceOwner, sourceRepo, sanitizedSkillName, agents, projectPath, global);
1234
1322
  }
1235
1323
  }
1236
1324
  let skillContent = null;
1237
1325
  if (source.type === "content" && source.content) {
1238
1326
  skillContent = source.content;
1239
- } else if (source.type === "github" && source.owner && source.repo) {
1327
+ } else if (source.type === "github" && sourceOwner && sourceRepo) {
1240
1328
  const githubToken = process.env.GITHUB_TOKEN;
1241
1329
  const fetchResult = await fetchSkillFromGitHub(
1242
- source.owner,
1243
- source.repo,
1330
+ sourceOwner,
1331
+ sourceRepo,
1244
1332
  sanitizedSkillName,
1245
1333
  githubToken
1246
1334
  );
@@ -1256,10 +1344,10 @@ async function installSkill(input) {
1256
1344
  projectPath,
1257
1345
  global,
1258
1346
  overwrite,
1259
- source.type === "github" ? `${source.owner}/${source.repo}` : "direct-content"
1347
+ source.type === "github" ? `${sourceOwner}/${sourceRepo}` : "direct-content"
1260
1348
  );
1261
1349
  }
1262
- return await executePathC(skillName, sanitizedSkillName, agents, projectPath, global, overwrite);
1350
+ return await executePathC(skillName, sanitizedSkillName, agents, projectPath, global, overwrite, projectContext);
1263
1351
  } catch (err) {
1264
1352
  const message = err instanceof Error ? err.message : String(err);
1265
1353
  return {
@@ -1273,8 +1361,7 @@ async function installSkill(input) {
1273
1361
  function executePathA(owner, repo, skillName, agents, projectPath, global) {
1274
1362
  const agentFlags = agents.map((agent) => `--agent ${agent}`).join(" ");
1275
1363
  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, " ");
1364
+ const command = `npx skills add ${owner}/${repo}@${skillName} ${agentFlags} ${globalFlag} -y`.trim().replace(/\s+/g, " ");
1278
1365
  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
1366
  const instruction = `Execute this command in your terminal now. The command will install the skill to all specified agents without any interactive prompts.
1280
1367
 
@@ -1358,9 +1445,9 @@ async function executePathB(content, skillName, agents, projectPath, global, ove
1358
1445
  error: allSucceeded ? void 0 : "Some installations failed. Check individual agent results."
1359
1446
  };
1360
1447
  }
1361
- async function executePathC(originalSkillName, sanitizedSkillName, agents, projectPath, global, overwrite) {
1448
+ async function executePathC(originalSkillName, sanitizedSkillName, agents, projectPath, global, overwrite, providedContext) {
1362
1449
  try {
1363
- const projectContext = `Generating skill for: ${originalSkillName}
1450
+ const projectContext = providedContext || `Generating skill for: ${originalSkillName}
1364
1451
  Project path: ${projectPath}
1365
1452
  Target agents: ${agents.join(", ")}`;
1366
1453
  const generateResult = await generateSkill({
@@ -1597,11 +1684,11 @@ A SKILL.md file that lives in your agent's skill directory. You read all SKILL.m
1597
1684
 
1598
1685
  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
1686
 
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).
1687
+ 3. search_skills \u2014 Searches for skills using npx skills find (primary) with SkillScout.dev API fallback. Fetches GitHub star count and recency. Returns ranked list filtered by quality (stars >= 50, updated within 12 months). Each result includes an installRef field for direct use with install_skill.
1601
1688
 
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.
1689
+ 4. fetch_skill_content \u2014 Fetches full SKILL.md content from a GitHub repository using raw URL fallbacks. Accepts either owner/repo/skillName OR installRef (format: owner/repo@skillname). Validates YAML frontmatter. Returns content, source URL, installRef, and parsed metadata.
1603
1690
 
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.
1691
+ 5. install_skill \u2014 Core installation tool. Accepts source.installRef (format: owner/repo@skillname) OR source.owner/source.repo. Also accepts projectContext for PATH C generation. 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
1692
 
1606
1693
  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
1694
 
@@ -1666,8 +1753,9 @@ STEP 5: Install skills for each domain
1666
1753
  For each domain you identified:
1667
1754
 
1668
1755
  a. Call search_skills(domain)
1669
- - This searches skills.sh via Tavily
1756
+ - This searches using npx skills find (primary) with SkillScout.dev fallback
1670
1757
  - Returns quality-filtered results (stars >= 50, updated within 12 months)
1758
+ - Each result includes installRef field (format: owner/repo@skillname)
1671
1759
 
1672
1760
  b. If quality results found (stars >= 50):
1673
1761
  - Call fetch_skill_content for the top result
@@ -1675,7 +1763,7 @@ b. If quality results found (stars >= 50):
1675
1763
  - Call install_skill with source type 'github' and the owner/repo/stars data
1676
1764
  - install_skill returns a PATH A response: an npx command string
1677
1765
  - 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
1766
+ - Example command: npx skills add vercel-labs/next-skills@nextjs-patterns --agent claude-code --agent cursor -y
1679
1767
  - The command is fully non-interactive (no prompts) thanks to --agent flags and -y flag
1680
1768
 
1681
1769
  c. If no quality results OR PATH A fails:
@@ -1720,7 +1808,7 @@ PATH A (npx command \u2014 preferred):
1720
1808
  - Used for high-quality GitHub repos (stars >= 50)
1721
1809
  - install_skill returns an npx command string
1722
1810
  - YOU execute it using your bash tool
1723
- - Command format: npx skills add <owner/repo> --skill <name> --agent <agent1> --agent <agent2> -y
1811
+ - Command format: npx skills add <owner/repo@skillname> --agent <agent1> --agent <agent2> -y
1724
1812
  - The npx CLI handles cloning, writing, and directory creation
1725
1813
  - Fully automated with zero interactive prompts
1726
1814
 
@@ -1747,10 +1835,10 @@ You:
1747
1835
  3. [Analyze project] \u2192 Next.js 15, App Router, TypeScript, Prisma, TailwindCSS
1748
1836
  4. [Derive domains] \u2192 nextjs-app-router-patterns, prisma-schema-design, tailwindcss-components
1749
1837
  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
1838
+ - [Call search_skills] \u2192 Found vercel-labs/next-skills (2,341 stars), installRef: vercel-labs/next-skills@nextjs-app-router-patterns
1839
+ - [Call fetch_skill_content with installRef] \u2192 Retrieved SKILL.md
1840
+ - [Call install_skill with source.installRef] \u2192 Returns npx command
1841
+ - [Execute via bash] \u2192 npx skills add vercel-labs/next-skills@nextjs-app-router-patterns --agent claude-code --agent cursor -y
1754
1842
  6. [Repeat for other domains]
1755
1843
  7. [Call list_installed_skills] \u2192 Report summary table
1756
1844
 
@@ -1825,7 +1913,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1825
1913
  },
1826
1914
  {
1827
1915
  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).",
1916
+ description: "Searches for skills using npx skills find (primary) with SkillScout.dev API fallback. Fetches GitHub star count and recency. Returns ranked list filtered by quality (stars >= 50, updated within 12 months). Each result includes installRef field.",
1829
1917
  inputSchema: {
1830
1918
  type: "object",
1831
1919
  properties: {
@@ -1854,33 +1942,37 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1854
1942
  },
1855
1943
  {
1856
1944
  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.",
1945
+ description: "Fetches full SKILL.md content from a GitHub repository using raw URL fallbacks. Accepts either owner/repo/skillName OR installRef (format: owner/repo@skillname). Validates YAML frontmatter. Returns content, source URL, installRef, and parsed metadata.",
1858
1946
  inputSchema: {
1859
1947
  type: "object",
1860
1948
  properties: {
1861
1949
  owner: {
1862
1950
  type: "string",
1863
- description: "GitHub repository owner"
1951
+ description: "GitHub repository owner (optional if installRef provided)"
1864
1952
  },
1865
1953
  repo: {
1866
1954
  type: "string",
1867
- description: "GitHub repository name"
1955
+ description: "GitHub repository name (optional if installRef provided)"
1868
1956
  },
1869
1957
  skillName: {
1870
1958
  type: "string",
1871
- description: "Skill name to fetch"
1959
+ description: "Skill name to fetch (optional if installRef provided)"
1960
+ },
1961
+ installRef: {
1962
+ type: "string",
1963
+ description: "Install reference in format owner/repo@skillname (alternative to owner/repo/skillName)"
1872
1964
  },
1873
1965
  githubToken: {
1874
1966
  type: "string",
1875
1967
  description: "Optional GitHub token for higher rate limits"
1876
1968
  }
1877
1969
  },
1878
- required: ["owner", "repo", "skillName"]
1970
+ required: []
1879
1971
  }
1880
1972
  },
1881
1973
  {
1882
1974
  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.",
1975
+ 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. PATH C: generates skill via LLM if both fail. Handles all selected agents simultaneously.",
1884
1976
  inputSchema: {
1885
1977
  type: "object",
1886
1978
  properties: {
@@ -1913,9 +2005,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1913
2005
  type: "string",
1914
2006
  description: "GitHub repo (for type: github)"
1915
2007
  },
2008
+ installRef: {
2009
+ type: "string",
2010
+ description: "Install reference in format owner/repo@skillname (alternative to owner/repo)"
2011
+ },
1916
2012
  content: {
1917
2013
  type: "string",
1918
2014
  description: "Direct SKILL.md content (for type: content)"
2015
+ },
2016
+ stars: {
2017
+ type: "number",
2018
+ description: "GitHub star count (for quality check, triggers PATH A if >= 50)"
1919
2019
  }
1920
2020
  },
1921
2021
  required: ["type"],
@@ -1930,6 +2030,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1930
2030
  type: "boolean",
1931
2031
  description: "Install to global scope",
1932
2032
  default: false
2033
+ },
2034
+ projectContext: {
2035
+ type: "string",
2036
+ description: "Rich project context for PATH C skill generation (frameworks, versions, patterns, folder structure)"
1933
2037
  }
1934
2038
  },
1935
2039
  required: ["projectPath", "skillName", "agents", "source"]