@skills-hub-ai/mcp 0.1.4 → 0.1.6

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/api.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ export interface SkillSearchResult {
2
+ slug: string;
3
+ name: string;
4
+ description: string;
5
+ qualityScore: number | null;
6
+ installCount: number;
7
+ avgRating: number | null;
8
+ latestVersion: string;
9
+ category: {
10
+ name: string;
11
+ slug: string;
12
+ };
13
+ isComposition: boolean;
14
+ tags: string[];
15
+ }
16
+ export interface SkillDetailResult {
17
+ slug: string;
18
+ name: string;
19
+ description: string;
20
+ instructions: string;
21
+ qualityScore: number | null;
22
+ installCount: number;
23
+ avgRating: number | null;
24
+ latestVersion: string;
25
+ category: {
26
+ name: string;
27
+ slug: string;
28
+ };
29
+ platforms: string[];
30
+ tags: string[];
31
+ isComposition: boolean;
32
+ composition: {
33
+ children: Array<{
34
+ skill: {
35
+ slug: string;
36
+ name: string;
37
+ qualityScore: number | null;
38
+ };
39
+ sortOrder: number;
40
+ isParallel: boolean;
41
+ }>;
42
+ } | null;
43
+ }
44
+ export declare function searchSkills(query: string, category?: string, limit?: number, org?: string): Promise<SkillSearchResult[]>;
45
+ export declare function getSkillDetail(slug: string): Promise<SkillDetailResult>;
46
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE;QACX,QAAQ,EAAE,KAAK,CAAC;YACd,KAAK,EAAE;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAC;gBAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;aAAE,CAAC;YACnE,SAAS,EAAE,MAAM,CAAC;YAClB,UAAU,EAAE,OAAO,CAAC;SACrB,CAAC,CAAC;KACJ,GAAG,IAAI,CAAC;CACV;AAED,wBAAsB,YAAY,CAChC,KAAK,EAAE,MAAM,EACb,QAAQ,CAAC,EAAE,MAAM,EACjB,KAAK,SAAK,EACV,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAY9B;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAO7E"}
package/dist/api.js ADDED
@@ -0,0 +1,33 @@
1
+ const API_URL = process.env.SKILLS_HUB_API_URL || "https://api.skills-hub.ai";
2
+ const API_TOKEN = process.env.SKILLS_HUB_API_TOKEN || "";
3
+ const TIMEOUT_MS = 30_000;
4
+ function fetchWithTimeout(url) {
5
+ const controller = new AbortController();
6
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
7
+ const headers = {};
8
+ if (API_TOKEN) {
9
+ headers["Authorization"] = `ApiKey ${API_TOKEN}`;
10
+ }
11
+ return fetch(url, { signal: controller.signal, headers }).finally(() => clearTimeout(timer));
12
+ }
13
+ export async function searchSkills(query, category, limit = 10, org) {
14
+ const params = new URLSearchParams({ q: query, limit: String(limit), sort: "relevance" });
15
+ if (category)
16
+ params.set("category", category);
17
+ if (org)
18
+ params.set("org", org);
19
+ const res = await fetchWithTimeout(`${API_URL}/api/v1/search?${params}`);
20
+ if (!res.ok) {
21
+ throw new Error(`Search failed: ${res.status} ${res.statusText}`);
22
+ }
23
+ const data = (await res.json());
24
+ return data.data || [];
25
+ }
26
+ export async function getSkillDetail(slug) {
27
+ const res = await fetchWithTimeout(`${API_URL}/api/v1/skills/${encodeURIComponent(slug)}`);
28
+ if (!res.ok) {
29
+ throw new Error(`Skill not found: ${slug} (${res.status})`);
30
+ }
31
+ return (await res.json());
32
+ }
33
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,2BAA2B,CAAC;AAC9E,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,EAAE,CAAC;AACzD,MAAM,UAAU,GAAG,MAAM,CAAC;AAE1B,SAAS,gBAAgB,CAAC,GAAW;IACnC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,UAAU,CAAC,CAAC;IAC/D,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,SAAS,EAAE,CAAC;IACnD,CAAC;IACD,OAAO,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;AAC/F,CAAC;AAqCD,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAa,EACb,QAAiB,EACjB,KAAK,GAAG,EAAE,EACV,GAAY;IAEZ,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;IAC1F,IAAI,QAAQ;QAAE,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC/C,IAAI,GAAG;QAAE,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAEhC,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,OAAO,kBAAkB,MAAM,EAAE,CAAC,CAAC;IACzE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,kBAAkB,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAmC,CAAC;IAClE,OAAO,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY;IAC/C,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,OAAO,kBAAkB,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3F,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,KAAK,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAsB,CAAC;AACjD,CAAC"}
package/dist/discover.js CHANGED
@@ -43,7 +43,7 @@ function scanDir(dir, out) {
43
43
  export function discoverSkills() {
44
44
  const now = Date.now();
45
45
  if (cache && now - cacheTime < CACHE_TTL_MS) {
46
- return cache;
46
+ return new Map(cache);
47
47
  }
48
48
  const skills = new Map();
49
49
  for (const dir of getSkillPaths()) {
@@ -1 +1 @@
1
- {"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,YAAY,EAAoB,MAAM,6BAA6B,CAAC;AAE7E,MAAM,YAAY,GAAG,MAAM,CAAC;AAE5B,IAAI,KAAK,GAAoC,IAAI,CAAC;AAClD,IAAI,SAAS,GAAG,CAAC,CAAC;AAElB,gDAAgD;AAChD,SAAS,aAAa;IACpB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,OAAO;QACL,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC;QAC/B,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC;KAChC,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,SAAS,OAAO,CAAC,GAAW,EAAE,GAA6B;IACzD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO;IAE7B,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,SAAS;QAEnC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QACxB,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS,CAAC,qCAAqC;QAElE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,SAAS;QAErC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;YACrC,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACnC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;IACH,CAAC;AACH,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,cAAc;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,KAAK,IAAI,GAAG,GAAG,SAAS,GAAG,YAAY,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC9C,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACvB,CAAC;IAED,KAAK,GAAG,MAAM,CAAC;IACf,SAAS,GAAG,GAAG,CAAC;IAChB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,UAAU;IACxB,KAAK,GAAG,IAAI,CAAC;IACb,SAAS,GAAG,CAAC,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,YAAY,EAAoB,MAAM,6BAA6B,CAAC;AAE7E,MAAM,YAAY,GAAG,MAAM,CAAC;AAE5B,IAAI,KAAK,GAAoC,IAAI,CAAC;AAClD,IAAI,SAAS,GAAG,CAAC,CAAC;AAElB,gDAAgD;AAChD,SAAS,aAAa;IACpB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,OAAO;QACL,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC;QAC/B,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC;KAChC,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,SAAS,OAAO,CAAC,GAAW,EAAE,GAA6B;IACzD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO;IAE7B,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,SAAS;QAEnC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QACxB,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS,CAAC,qCAAqC;QAElE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,SAAS;QAErC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;YACrC,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACnC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;IACH,CAAC;AACH,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,cAAc;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,KAAK,IAAI,GAAG,GAAG,SAAS,GAAG,YAAY,EAAE,CAAC;QAC5C,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC9C,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACvB,CAAC;IAED,KAAK,GAAG,MAAM,CAAC;IACf,SAAS,GAAG,GAAG,CAAC;IAChB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,UAAU;IACxB,KAAK,GAAG,IAAI,CAAC;IACb,SAAS,GAAG,CAAC,CAAC;AAChB,CAAC"}
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { ListPromptsRequestSchema, GetPromptRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
4
+ import { ListPromptsRequestSchema, GetPromptRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
5
5
  import { discoverSkills } from "./discover.js";
6
- const server = new Server({ name: "skills-hub", version: "0.1.0" }, { capabilities: { prompts: { listChanged: true } } });
6
+ import { searchSkills, getSkillDetail } from "./api.js";
7
+ import { installSkill } from "./installer.js";
8
+ const server = new Server({ name: "skills-hub", version: "0.1.0" }, { capabilities: { prompts: { listChanged: true }, tools: {} } });
7
9
  server.setRequestHandler(ListPromptsRequestSchema, async () => {
8
10
  const skills = discoverSkills();
9
11
  return {
@@ -41,6 +43,151 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
41
43
  ],
42
44
  };
43
45
  });
46
+ /* ------------------------------------------------------------------ */
47
+ /* Tools — catalog search, skill detail, install, list installed */
48
+ /* ------------------------------------------------------------------ */
49
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
50
+ tools: [
51
+ {
52
+ name: "search_skills",
53
+ description: "Search the skills-hub.ai catalog for skills matching a query. Supports org-scoped search to include organization-private skills (requires SKILLS_HUB_API_TOKEN).",
54
+ inputSchema: {
55
+ type: "object",
56
+ properties: {
57
+ query: { type: "string", description: "Search query (e.g. 'code review', 'testing React')" },
58
+ category: { type: "string", description: "Filter by category slug (e.g. 'review', 'test', 'build')" },
59
+ limit: { type: "number", description: "Max results (default 10)" },
60
+ organization: { type: "string", description: "Organization slug to include org-private skills in results (requires authentication)" },
61
+ },
62
+ required: ["query"],
63
+ },
64
+ },
65
+ {
66
+ name: "get_skill_detail",
67
+ description: "Get full details about a skill including instructions, composition children, and metadata",
68
+ inputSchema: {
69
+ type: "object",
70
+ properties: {
71
+ slug: { type: "string", description: "Skill slug (e.g. 'review-code')" },
72
+ },
73
+ required: ["slug"],
74
+ },
75
+ },
76
+ {
77
+ name: "install_skill",
78
+ description: "Install a skill from skills-hub.ai to the local skills directory",
79
+ inputSchema: {
80
+ type: "object",
81
+ properties: {
82
+ slug: { type: "string", description: "Skill slug to install" },
83
+ target: { type: "string", enum: ["claude-code", "cursor"], description: "Install target (default: claude-code)" },
84
+ },
85
+ required: ["slug"],
86
+ },
87
+ },
88
+ {
89
+ name: "list_installed_skills",
90
+ description: "List all locally installed skills",
91
+ inputSchema: {
92
+ type: "object",
93
+ properties: {},
94
+ },
95
+ },
96
+ ],
97
+ }));
98
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
99
+ const { name, arguments: args } = request.params;
100
+ try {
101
+ switch (name) {
102
+ case "search_skills": {
103
+ const query = args?.query;
104
+ if (!query)
105
+ throw new McpError(ErrorCode.InvalidParams, "query is required");
106
+ const limit = Math.min(Math.max(args?.limit || 10, 1), 100);
107
+ const results = await searchSkills(query, args?.category, limit, args?.organization);
108
+ return {
109
+ content: [{
110
+ type: "text",
111
+ text: JSON.stringify(results.map((s) => ({
112
+ slug: s.slug,
113
+ name: s.name,
114
+ description: s.description,
115
+ qualityScore: s.qualityScore,
116
+ installCount: s.installCount,
117
+ avgRating: s.avgRating,
118
+ category: s.category.name,
119
+ isComposition: s.isComposition,
120
+ tags: s.tags,
121
+ })), null, 2),
122
+ }],
123
+ };
124
+ }
125
+ case "get_skill_detail": {
126
+ const slug = args?.slug;
127
+ if (!slug)
128
+ throw new McpError(ErrorCode.InvalidParams, "slug is required");
129
+ const detail = await getSkillDetail(slug);
130
+ return {
131
+ content: [{
132
+ type: "text",
133
+ text: JSON.stringify({
134
+ slug: detail.slug,
135
+ name: detail.name,
136
+ description: detail.description,
137
+ qualityScore: detail.qualityScore,
138
+ installCount: detail.installCount,
139
+ avgRating: detail.avgRating,
140
+ latestVersion: detail.latestVersion,
141
+ category: detail.category,
142
+ platforms: detail.platforms,
143
+ tags: detail.tags,
144
+ isComposition: detail.isComposition,
145
+ composition: detail.composition,
146
+ instructionsPreview: detail.instructions?.slice(0, 500) + (detail.instructions?.length > 500 ? "..." : ""),
147
+ }, null, 2),
148
+ }],
149
+ };
150
+ }
151
+ case "install_skill": {
152
+ const slug = args?.slug;
153
+ if (!slug)
154
+ throw new McpError(ErrorCode.InvalidParams, "slug is required");
155
+ const target = args?.target || "claude-code";
156
+ const result = await installSkill(slug, target);
157
+ return {
158
+ content: [{
159
+ type: "text",
160
+ text: JSON.stringify(result, null, 2),
161
+ }],
162
+ };
163
+ }
164
+ case "list_installed_skills": {
165
+ const skills = discoverSkills();
166
+ const list = Array.from(skills.entries()).map(([slug, skill]) => ({
167
+ slug,
168
+ name: skill.name,
169
+ description: skill.description,
170
+ version: skill.version,
171
+ category: skill.category || null,
172
+ }));
173
+ return {
174
+ content: [{
175
+ type: "text",
176
+ text: JSON.stringify(list, null, 2),
177
+ }],
178
+ };
179
+ }
180
+ default:
181
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
182
+ }
183
+ }
184
+ catch (err) {
185
+ if (err instanceof McpError)
186
+ throw err;
187
+ const message = err instanceof Error ? err.message : "Unknown error";
188
+ throw new McpError(ErrorCode.InternalError, message);
189
+ }
190
+ });
44
191
  async function main() {
45
192
  const transport = new StdioServerTransport();
46
193
  await server.connect(transport);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,SAAS,EACT,QAAQ,GACT,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,EACxC,EAAE,YAAY,EAAE,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE,CACrD,CAAC;AAEF,MAAM,CAAC,iBAAiB,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE;IAC5D,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAEhC,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5D,IAAI,EAAE,IAAI;YACV,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,SAAS,EAAE;gBACT;oBACE,IAAI,EAAE,OAAO;oBACb,WAAW,EAAE,uCAAuC;oBACpD,QAAQ,EAAE,KAAK;iBAChB;aACF;SACF,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IACjE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IACjD,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAE/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,QAAQ,CAChB,SAAS,CAAC,cAAc,EACxB,kBAAkB,IAAI,6BAA6B,IAAI,kBAAkB,CAC1E,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,GAAG,KAAK,CAAC,YAAY,CAAC;IAC9B,IAAI,IAAI,EAAE,KAAK,EAAE,CAAC;QAChB,IAAI,IAAI,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;IAC9B,CAAC;IAED,OAAO;QACL,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,MAAe;gBACrB,OAAO,EAAE,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE;aACzC;SACF;KACF,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,kDAAkD;IAClD,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;AACjD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export interface InstallResult {
2
+ slug: string;
3
+ version: string;
4
+ path: string;
5
+ dependencies: string[];
6
+ }
7
+ /** Install a single skill from the API to the local skills directory */
8
+ export declare function installSkill(slug: string, target?: "claude-code" | "cursor"): Promise<InstallResult>;
9
+ //# sourceMappingURL=installer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wEAAwE;AACxE,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,aAAa,GAAG,QAAwB,GAC/C,OAAO,CAAC,aAAa,CAAC,CA+BxB"}
@@ -0,0 +1,94 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { getSkillDetail } from "./api.js";
5
+ function validateSlug(slug) {
6
+ if (!slug || /[/\\]|\.\./.test(slug)) {
7
+ throw new Error(`Invalid slug: ${slug}`);
8
+ }
9
+ }
10
+ function yamlEscape(value) {
11
+ if (!value)
12
+ return value ?? "";
13
+ if (/[:\-#{}\[\]&*!|>'"%@`]/.test(value) || value.includes("\n")) {
14
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
15
+ }
16
+ return value;
17
+ }
18
+ function getSkillsDir(target) {
19
+ const home = homedir();
20
+ return target === "cursor"
21
+ ? join(home, ".cursor", "skills")
22
+ : join(home, ".claude", "skills");
23
+ }
24
+ /** Install a single skill from the API to the local skills directory */
25
+ export async function installSkill(slug, target = "claude-code") {
26
+ validateSlug(slug);
27
+ const skill = await getSkillDetail(slug);
28
+ const skillDir = join(getSkillsDir(target), slug);
29
+ mkdirSync(skillDir, { recursive: true });
30
+ const content = `---
31
+ name: ${yamlEscape(skill.name)}
32
+ description: ${yamlEscape(skill.description)}
33
+ version: ${yamlEscape(skill.latestVersion)}
34
+ category: ${yamlEscape(skill.category.slug)}
35
+ ---
36
+
37
+ ${skill.instructions}
38
+ `;
39
+ writeFileSync(join(skillDir, "SKILL.md"), content);
40
+ // Install composition dependencies
41
+ const deps = [];
42
+ if (skill.composition?.children.length) {
43
+ await installDependencies(skill, target, deps);
44
+ }
45
+ return {
46
+ slug,
47
+ version: skill.latestVersion,
48
+ path: skillDir,
49
+ dependencies: deps,
50
+ };
51
+ }
52
+ /** BFS install of composition children (max depth 5) */
53
+ async function installDependencies(skill, target, installed, visited = new Set(), depth = 0) {
54
+ if (depth > 5 || !skill.composition?.children.length)
55
+ return;
56
+ const skillsDir = getSkillsDir(target);
57
+ for (const child of skill.composition.children) {
58
+ const childSlug = child.skill.slug;
59
+ if (visited.has(childSlug))
60
+ continue;
61
+ visited.add(childSlug);
62
+ try {
63
+ validateSlug(childSlug);
64
+ }
65
+ catch {
66
+ continue;
67
+ }
68
+ // Skip if already installed locally
69
+ if (existsSync(join(skillsDir, childSlug, "SKILL.md")))
70
+ continue;
71
+ try {
72
+ const childDetail = await getSkillDetail(childSlug);
73
+ const childDir = join(skillsDir, childSlug);
74
+ mkdirSync(childDir, { recursive: true });
75
+ const content = `---
76
+ name: ${yamlEscape(childDetail.name)}
77
+ description: ${yamlEscape(childDetail.description)}
78
+ version: ${yamlEscape(childDetail.latestVersion)}
79
+ category: ${yamlEscape(childDetail.category.slug)}
80
+ ---
81
+
82
+ ${childDetail.instructions}
83
+ `;
84
+ writeFileSync(join(childDir, "SKILL.md"), content);
85
+ installed.push(childSlug);
86
+ // Recurse if this child is also a composition
87
+ await installDependencies(childDetail, target, installed, visited, depth + 1);
88
+ }
89
+ catch {
90
+ // Skip failed children, don't block the parent install
91
+ }
92
+ }
93
+ }
94
+ //# sourceMappingURL=installer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer.js","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skills-hub-ai/mcp",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "MCP server for skills-hub.ai — serve installed skills as prompts in any MCP-compatible AI tool",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@modelcontextprotocol/sdk": "^1.27.1",
31
- "@skills-hub-ai/skill-parser": "0.1.1"
31
+ "@skills-hub-ai/skill-parser": "0.2.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^22.0.0",
@@ -0,0 +1,164 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { searchSkills, getSkillDetail } from "./api.js";
3
+
4
+ const mockFetch = vi.fn();
5
+ vi.stubGlobal("fetch", mockFetch);
6
+
7
+ beforeEach(() => {
8
+ vi.clearAllMocks();
9
+ });
10
+
11
+ describe("auth header", () => {
12
+ it("omits Authorization header when SKILLS_HUB_API_TOKEN is not set", async () => {
13
+ mockFetch.mockResolvedValue({
14
+ ok: true,
15
+ json: () => Promise.resolve({ data: [] }),
16
+ });
17
+
18
+ await searchSkills("test");
19
+
20
+ const opts = mockFetch.mock.calls[0][1] as RequestInit;
21
+ expect((opts.headers as Record<string, string>)["Authorization"]).toBeUndefined();
22
+ });
23
+
24
+ it("omits Authorization header on getSkillDetail when token not set", async () => {
25
+ mockFetch.mockResolvedValue({
26
+ ok: true,
27
+ json: () => Promise.resolve({ slug: "test" }),
28
+ });
29
+
30
+ await getSkillDetail("test-skill");
31
+
32
+ const opts = mockFetch.mock.calls[0][1] as RequestInit;
33
+ expect((opts.headers as Record<string, string>)["Authorization"]).toBeUndefined();
34
+ });
35
+ });
36
+
37
+ describe("searchSkills", () => {
38
+ it("calls search API with query", async () => {
39
+ mockFetch.mockResolvedValue({
40
+ ok: true,
41
+ json: () => Promise.resolve({
42
+ data: [
43
+ { slug: "review-code", name: "Review Code", description: "AI code review", qualityScore: 92, installCount: 150, avgRating: 4.8, latestVersion: "1.0.0", category: { name: "Review", slug: "review" }, isComposition: false, tags: ["review"] },
44
+ ],
45
+ }),
46
+ });
47
+
48
+ const results = await searchSkills("code review");
49
+
50
+ expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("/api/v1/search?"), expect.objectContaining({ signal: expect.any(AbortSignal) }));
51
+ expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("q=code+review"), expect.anything());
52
+ expect(results).toHaveLength(1);
53
+ expect(results[0].slug).toBe("review-code");
54
+ });
55
+
56
+ it("passes category filter", async () => {
57
+ mockFetch.mockResolvedValue({
58
+ ok: true,
59
+ json: () => Promise.resolve({ data: [] }),
60
+ });
61
+
62
+ await searchSkills("test", "build");
63
+
64
+ expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("category=build"), expect.anything());
65
+ });
66
+
67
+ it("passes limit", async () => {
68
+ mockFetch.mockResolvedValue({
69
+ ok: true,
70
+ json: () => Promise.resolve({ data: [] }),
71
+ });
72
+
73
+ await searchSkills("test", undefined, 5);
74
+
75
+ expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("limit=5"), expect.anything());
76
+ });
77
+
78
+ it("throws on API error", async () => {
79
+ mockFetch.mockResolvedValue({ ok: false, status: 500, statusText: "Internal Server Error" });
80
+
81
+ await expect(searchSkills("fail")).rejects.toThrow("Search failed: 500");
82
+ });
83
+
84
+ it("returns empty array when no data", async () => {
85
+ mockFetch.mockResolvedValue({
86
+ ok: true,
87
+ json: () => Promise.resolve({}),
88
+ });
89
+
90
+ const results = await searchSkills("nothing");
91
+ expect(results).toEqual([]);
92
+ });
93
+
94
+ it("passes org query parameter when provided", async () => {
95
+ mockFetch.mockResolvedValue({
96
+ ok: true,
97
+ json: () => Promise.resolve({ data: [] }),
98
+ });
99
+
100
+ await searchSkills("testing", undefined, 10, "my-org");
101
+
102
+ expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("org=my-org"), expect.anything());
103
+ });
104
+
105
+ it("omits org query parameter when not provided", async () => {
106
+ mockFetch.mockResolvedValue({
107
+ ok: true,
108
+ json: () => Promise.resolve({ data: [] }),
109
+ });
110
+
111
+ await searchSkills("testing");
112
+
113
+ const url = mockFetch.mock.calls[0][0] as string;
114
+ expect(url).not.toContain("org=");
115
+ });
116
+ });
117
+
118
+ describe("getSkillDetail", () => {
119
+ it("fetches skill by slug", async () => {
120
+ const mockSkill = {
121
+ slug: "review-code",
122
+ name: "Review Code",
123
+ description: "AI code review",
124
+ instructions: "Review the code",
125
+ qualityScore: 92,
126
+ installCount: 150,
127
+ avgRating: 4.8,
128
+ latestVersion: "1.0.0",
129
+ category: { name: "Review", slug: "review" },
130
+ platforms: ["CLAUDE_CODE"],
131
+ tags: ["review"],
132
+ isComposition: false,
133
+ composition: null,
134
+ };
135
+
136
+ mockFetch.mockResolvedValue({
137
+ ok: true,
138
+ json: () => Promise.resolve(mockSkill),
139
+ });
140
+
141
+ const result = await getSkillDetail("review-code");
142
+
143
+ expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("/api/v1/skills/review-code"), expect.anything());
144
+ expect(result.slug).toBe("review-code");
145
+ expect(result.instructions).toBe("Review the code");
146
+ });
147
+
148
+ it("throws on not found", async () => {
149
+ mockFetch.mockResolvedValue({ ok: false, status: 404, statusText: "Not Found" });
150
+
151
+ await expect(getSkillDetail("nonexistent")).rejects.toThrow("Skill not found: nonexistent");
152
+ });
153
+
154
+ it("URL-encodes the slug", async () => {
155
+ mockFetch.mockResolvedValue({
156
+ ok: true,
157
+ json: () => Promise.resolve({ slug: "my-skill" }),
158
+ });
159
+
160
+ await getSkillDetail("my-skill");
161
+
162
+ expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("/api/v1/skills/my-skill"), expect.anything());
163
+ });
164
+ });