skillsmp-mcp-lite 1.0.2 → 1.0.3

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 CHANGED
@@ -2,6 +2,7 @@
2
2
  * SkillsMP API Client and Types
3
3
  */
4
4
  export declare const SKILLSMP_API_BASE = "https://skillsmp.com/api/v1";
5
+ export declare const API_TIMEOUT_MS = 30000;
5
6
  export interface Skill {
6
7
  id: string;
7
8
  name: string;
package/dist/api.js CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
  // API Configuration
5
5
  export const SKILLSMP_API_BASE = "https://skillsmp.com/api/v1";
6
+ export const API_TIMEOUT_MS = 30_000;
6
7
  // API Client
7
8
  export async function makeApiRequest(endpoint, apiKey, params) {
8
9
  const url = new URL(`${SKILLSMP_API_BASE}/${endpoint}`);
@@ -13,13 +14,22 @@ export async function makeApiRequest(endpoint, apiKey, params) {
13
14
  }
14
15
  });
15
16
  }
16
- const response = await fetch(url.toString(), {
17
- method: "GET",
18
- headers: {
19
- "Authorization": `Bearer ${apiKey}`,
20
- "Content-Type": "application/json"
21
- }
22
- });
17
+ const controller = new AbortController();
18
+ const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
19
+ let response;
20
+ try {
21
+ response = await fetch(url.toString(), {
22
+ method: "GET",
23
+ headers: {
24
+ "Authorization": `Bearer ${apiKey}`,
25
+ "Content-Type": "application/json"
26
+ },
27
+ signal: controller.signal
28
+ });
29
+ }
30
+ finally {
31
+ clearTimeout(timeoutId);
32
+ }
23
33
  if (!response.ok) {
24
34
  const errorData = await response.json().catch(() => ({}));
25
35
  throw new Error(errorData.error?.message ||
package/dist/index.js CHANGED
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { createRequire } from "module";
4
5
  import { registerSkillsTools } from "./tools/skills.js";
6
+ const require = createRequire(import.meta.url);
7
+ const { version } = require("../package.json");
5
8
  /**
6
9
  * SkillsMP MCP Server
7
10
  *
@@ -20,7 +23,7 @@ if (!API_KEY) {
20
23
  // Initialize MCP Server
21
24
  const server = new McpServer({
22
25
  name: "skillsmp-mcp-server",
23
- version: "1.0.0"
26
+ version: version
24
27
  });
25
28
  // Register skills search tools
26
29
  registerSkillsTools(server, API_KEY);
@@ -1,8 +1,33 @@
1
- import { exec } from "child_process";
1
+ import { execFile } from "child_process";
2
2
  import { promisify } from "util";
3
3
  import { makeApiRequest, handleApiError, validateSearchResponse, validateAISearchResponse } from "../api.js";
4
4
  import { KeywordSearchSchema, AISearchSchema, InstallAndReadSchema } from "../schemas.js";
5
- const execAsync = promisify(exec);
5
+ const execFileAsync = promisify(execFile);
6
+ // Timeout constants (in milliseconds)
7
+ const TIMEOUTS = {
8
+ READ: 30_000,
9
+ INSTALL: 120_000
10
+ };
11
+ /**
12
+ * Safely extract error message from unknown error type
13
+ */
14
+ function getErrorDetails(error) {
15
+ if (error instanceof Error) {
16
+ const execError = error;
17
+ return {
18
+ message: execError.message,
19
+ stderr: execError.stderr
20
+ };
21
+ }
22
+ if (typeof error === 'object' && error !== null) {
23
+ const e = error;
24
+ return {
25
+ message: String(e.message || 'Unknown error'),
26
+ stderr: typeof e.stderr === 'string' ? e.stderr : undefined
27
+ };
28
+ }
29
+ return { message: String(error) };
30
+ }
6
31
  /**
7
32
  * Register SkillsMP tools on the MCP server
8
33
  */
@@ -177,13 +202,11 @@ Examples:
177
202
  }
178
203
  }, async (params) => {
179
204
  try {
180
- const readCmd = `npx -y openskills read ${params.skillName}`;
205
+ const readArgs = ["-y", "openskills", "read", params.skillName];
181
206
  // Step 1: Check if skill already exists locally
182
- let skillExists = false;
183
207
  try {
184
- const { stdout } = await execAsync(readCmd, { timeout: 30000 });
208
+ const { stdout } = await execFileAsync("npx", readArgs, { timeout: TIMEOUTS.READ });
185
209
  if (stdout && !stdout.includes("not found") && !stdout.includes("Error")) {
186
- skillExists = true;
187
210
  const output = formatInstallAndReadResponse(params.repo, params.skillName, stdout, true);
188
211
  return {
189
212
  content: [{ type: "text", text: output }],
@@ -198,41 +221,39 @@ Examples:
198
221
  }
199
222
  catch {
200
223
  // Skill not found locally, proceed with installation
201
- skillExists = false;
202
224
  }
203
225
  // Step 2: Install if not exists
204
- const installFlags = ["-y"];
226
+ const installArgs = ["-y", "openskills", "install", params.repo];
205
227
  if (params.global)
206
- installFlags.push("--global");
228
+ installArgs.push("--global");
207
229
  if (params.universal)
208
- installFlags.push("--universal");
209
- const installCmd = `npx -y openskills install ${params.repo} ${installFlags.join(" ")}`;
230
+ installArgs.push("--universal");
210
231
  let installOutput;
211
232
  try {
212
- const { stdout, stderr } = await execAsync(installCmd, { timeout: 120000 });
233
+ const { stdout, stderr } = await execFileAsync("npx", installArgs, { timeout: TIMEOUTS.INSTALL });
213
234
  installOutput = stdout || stderr;
214
235
  }
215
236
  catch (installError) {
216
- const error = installError;
237
+ const errorDetails = getErrorDetails(installError);
217
238
  return {
218
239
  content: [{
219
240
  type: "text",
220
- text: `❌ **Installation Failed**\n\nRepository: ${params.repo}\n\nError:\n${error.stderr || error.message || "Unknown error"}`
241
+ text: `❌ **Installation Failed**\n\nRepository: ${params.repo}\n\nError:\n${errorDetails.stderr || errorDetails.message}`
221
242
  }]
222
243
  };
223
244
  }
224
245
  // Step 3: Read the skill after installation
225
246
  let readOutput;
226
247
  try {
227
- const { stdout, stderr } = await execAsync(readCmd, { timeout: 30000 });
248
+ const { stdout, stderr } = await execFileAsync("npx", readArgs, { timeout: TIMEOUTS.READ });
228
249
  readOutput = stdout || stderr;
229
250
  }
230
251
  catch (readError) {
231
- const error = readError;
252
+ const errorDetails = getErrorDetails(readError);
232
253
  return {
233
254
  content: [{
234
255
  type: "text",
235
- text: `✅ **Installation Succeeded** but **Read Failed**\n\nRepository: ${params.repo}\nSkill: ${params.skillName}\n\n**Install Output:**\n${installOutput}\n\n**Read Error:**\n${error.stderr || error.message || "Skill not found"}\n\n💡 **Tip**: Check if the skill name is correct. Use \`npx openskills list\` to see installed skills.`
256
+ text: `✅ **Installation Succeeded** but **Read Failed**\n\nRepository: ${params.repo}\nSkill: ${params.skillName}\n\n**Install Output:**\n${installOutput}\n\n**Read Error:**\n${errorDetails.stderr || errorDetails.message}\n\n💡 **Tip**: Check if the skill name is correct. Use \`npx openskills list\` to see installed skills.`
236
257
  }]
237
258
  };
238
259
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillsmp-mcp-lite",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Lightweight MCP server for searching AI skills from SkillsMP before starting any task",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",