skillsmp-mcp-lite 1.0.1 → 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/README.md +29 -0
- package/dist/api.d.ts +1 -0
- package/dist/api.js +17 -7
- package/dist/index.js +4 -1
- package/dist/schemas.d.ts +17 -0
- package/dist/schemas.js +18 -0
- package/dist/tools/skills.js +153 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -102,6 +102,17 @@ AI semantic search for skills using natural language.
|
|
|
102
102
|
|-----------|------|----------|-------------|
|
|
103
103
|
| `query` | string | Yes | Natural language description |
|
|
104
104
|
|
|
105
|
+
### `skillsmp_install_and_read_skill`
|
|
106
|
+
|
|
107
|
+
Install a skill from GitHub and immediately read its content.
|
|
108
|
+
|
|
109
|
+
| Parameter | Type | Required | Description |
|
|
110
|
+
|-----------|------|----------|-------------|
|
|
111
|
+
| `repo` | string | Yes | GitHub repository in 'owner/repo' format |
|
|
112
|
+
| `skillName` | string | Yes | Name of the skill to read after installation |
|
|
113
|
+
| `global` | boolean | No | Install globally to ~/.claude/skills/ (default: false) |
|
|
114
|
+
| `universal` | boolean | No | Install to .agent/skills/ for universal usage (default: false) |
|
|
115
|
+
|
|
105
116
|
## Usage Examples
|
|
106
117
|
|
|
107
118
|
Ask your AI assistant:
|
|
@@ -109,6 +120,24 @@ Ask your AI assistant:
|
|
|
109
120
|
- "Search for PDF manipulation skills"
|
|
110
121
|
- "Find skills for building a web scraper"
|
|
111
122
|
- "What skills can help me with SEO optimization?"
|
|
123
|
+
- "Install and load the python-code-review skill from existential-birds/beagle"
|
|
124
|
+
|
|
125
|
+
## AGENTS.md Integration
|
|
126
|
+
|
|
127
|
+
To enable automatic skill discovery, copy the content from [`AGENTS.example.md`](./AGENTS.example.md) and paste it at the top of your `AGENTS.md` file.
|
|
128
|
+
|
|
129
|
+
### How It Works
|
|
130
|
+
|
|
131
|
+
1. AI receives a complex task
|
|
132
|
+
2. AI searches SkillsMP for relevant skills using `skillsmp_search_skills`
|
|
133
|
+
3. If keyword search is insufficient, AI tries `skillsmp_ai_search_skills`
|
|
134
|
+
4. If a relevant skill is found, AI installs and reads it with `skillsmp_install_and_read_skill`
|
|
135
|
+
5. AI follows the skill's instructions to complete the task
|
|
136
|
+
|
|
137
|
+
### Search Tips
|
|
138
|
+
|
|
139
|
+
- **Keyword search**: Keep queries short (1-3 words). Example: `"code review"`, `"typescript"`, `"pdf"`
|
|
140
|
+
- **Semantic search**: Use natural language. Example: `"how to build a landing page with React"`
|
|
112
141
|
|
|
113
142
|
## License
|
|
114
143
|
|
package/dist/api.d.ts
CHANGED
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
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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:
|
|
26
|
+
version: version
|
|
24
27
|
});
|
|
25
28
|
// Register skills search tools
|
|
26
29
|
registerSkillsTools(server, API_KEY);
|
package/dist/schemas.d.ts
CHANGED
|
@@ -31,3 +31,20 @@ export declare const AISearchSchema: z.ZodObject<{
|
|
|
31
31
|
query: string;
|
|
32
32
|
}>;
|
|
33
33
|
export type AISearchInput = z.infer<typeof AISearchSchema>;
|
|
34
|
+
export declare const InstallAndReadSchema: z.ZodObject<{
|
|
35
|
+
repo: z.ZodString;
|
|
36
|
+
skillName: z.ZodString;
|
|
37
|
+
global: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
38
|
+
universal: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
39
|
+
}, "strict", z.ZodTypeAny, {
|
|
40
|
+
repo: string;
|
|
41
|
+
skillName: string;
|
|
42
|
+
global: boolean;
|
|
43
|
+
universal: boolean;
|
|
44
|
+
}, {
|
|
45
|
+
repo: string;
|
|
46
|
+
skillName: string;
|
|
47
|
+
global?: boolean | undefined;
|
|
48
|
+
universal?: boolean | undefined;
|
|
49
|
+
}>;
|
|
50
|
+
export type InstallAndReadInput = z.infer<typeof InstallAndReadSchema>;
|
package/dist/schemas.js
CHANGED
|
@@ -36,3 +36,21 @@ export const AISearchSchema = z.object({
|
|
|
36
36
|
.max(500, "Query must not exceed 500 characters")
|
|
37
37
|
.describe("Natural language description of what you want to accomplish")
|
|
38
38
|
}).strict();
|
|
39
|
+
// Install and Read Skill Schema
|
|
40
|
+
export const InstallAndReadSchema = z.object({
|
|
41
|
+
repo: z.string()
|
|
42
|
+
.min(1, "Repository is required")
|
|
43
|
+
.regex(/^[^/]+\/[^/]+$/, "Repository must be in 'owner/repo' format")
|
|
44
|
+
.describe("GitHub repository in 'owner/repo' format (e.g., 'existential-birds/beagle')"),
|
|
45
|
+
skillName: z.string()
|
|
46
|
+
.min(1, "Skill name is required")
|
|
47
|
+
.describe("Name of the skill to read after installation"),
|
|
48
|
+
global: z.boolean()
|
|
49
|
+
.optional()
|
|
50
|
+
.default(false)
|
|
51
|
+
.describe("Install globally to ~/.claude/skills/ (default: false, installs to project)"),
|
|
52
|
+
universal: z.boolean()
|
|
53
|
+
.optional()
|
|
54
|
+
.default(false)
|
|
55
|
+
.describe("Install to .agent/skills/ for universal AGENTS.md usage (default: false)")
|
|
56
|
+
}).strict();
|
package/dist/tools/skills.js
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
1
3
|
import { makeApiRequest, handleApiError, validateSearchResponse, validateAISearchResponse } from "../api.js";
|
|
2
|
-
import { KeywordSearchSchema, AISearchSchema } from "../schemas.js";
|
|
4
|
+
import { KeywordSearchSchema, AISearchSchema, InstallAndReadSchema } from "../schemas.js";
|
|
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
|
+
}
|
|
3
31
|
/**
|
|
4
32
|
* Register SkillsMP tools on the MCP server
|
|
5
33
|
*/
|
|
@@ -144,6 +172,111 @@ Examples:
|
|
|
144
172
|
};
|
|
145
173
|
}
|
|
146
174
|
});
|
|
175
|
+
// Tool 3: Install and Read Skill
|
|
176
|
+
server.registerTool("skillsmp_install_and_read_skill", {
|
|
177
|
+
title: "Install and Read Skill",
|
|
178
|
+
description: `Install a skill from GitHub and immediately read its content.
|
|
179
|
+
|
|
180
|
+
This tool first checks if the skill is already installed locally. If found, it skips installation and directly reads the content (faster). If not found, it installs from GitHub first.
|
|
181
|
+
|
|
182
|
+
**IMPORTANT**: Use this to quickly load skill instructions without manual steps.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
- repo (string, required): GitHub repository in 'owner/repo' format
|
|
186
|
+
- skillName (string, required): Name of the skill to read after installation
|
|
187
|
+
- global (boolean, optional): Install globally to ~/.claude/skills/ (default: false)
|
|
188
|
+
- universal (boolean, optional): Install to .agent/skills/ for universal usage (default: false)
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
The full content of the skill's instructions (SKILL.md).
|
|
192
|
+
|
|
193
|
+
Examples:
|
|
194
|
+
- repo: "existential-birds/beagle", skillName: "python-code-review"
|
|
195
|
+
- repo: "LA3D/skillhelper", skillName: "code-reviewer"`,
|
|
196
|
+
inputSchema: InstallAndReadSchema,
|
|
197
|
+
annotations: {
|
|
198
|
+
readOnlyHint: false,
|
|
199
|
+
destructiveHint: false,
|
|
200
|
+
idempotentHint: true,
|
|
201
|
+
openWorldHint: true
|
|
202
|
+
}
|
|
203
|
+
}, async (params) => {
|
|
204
|
+
try {
|
|
205
|
+
const readArgs = ["-y", "openskills", "read", params.skillName];
|
|
206
|
+
// Step 1: Check if skill already exists locally
|
|
207
|
+
try {
|
|
208
|
+
const { stdout } = await execFileAsync("npx", readArgs, { timeout: TIMEOUTS.READ });
|
|
209
|
+
if (stdout && !stdout.includes("not found") && !stdout.includes("Error")) {
|
|
210
|
+
const output = formatInstallAndReadResponse(params.repo, params.skillName, stdout, true);
|
|
211
|
+
return {
|
|
212
|
+
content: [{ type: "text", text: output }],
|
|
213
|
+
structuredContent: {
|
|
214
|
+
repo: params.repo,
|
|
215
|
+
skillName: params.skillName,
|
|
216
|
+
skillContent: stdout,
|
|
217
|
+
wasAlreadyInstalled: true
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// Skill not found locally, proceed with installation
|
|
224
|
+
}
|
|
225
|
+
// Step 2: Install if not exists
|
|
226
|
+
const installArgs = ["-y", "openskills", "install", params.repo];
|
|
227
|
+
if (params.global)
|
|
228
|
+
installArgs.push("--global");
|
|
229
|
+
if (params.universal)
|
|
230
|
+
installArgs.push("--universal");
|
|
231
|
+
let installOutput;
|
|
232
|
+
try {
|
|
233
|
+
const { stdout, stderr } = await execFileAsync("npx", installArgs, { timeout: TIMEOUTS.INSTALL });
|
|
234
|
+
installOutput = stdout || stderr;
|
|
235
|
+
}
|
|
236
|
+
catch (installError) {
|
|
237
|
+
const errorDetails = getErrorDetails(installError);
|
|
238
|
+
return {
|
|
239
|
+
content: [{
|
|
240
|
+
type: "text",
|
|
241
|
+
text: `❌ **Installation Failed**\n\nRepository: ${params.repo}\n\nError:\n${errorDetails.stderr || errorDetails.message}`
|
|
242
|
+
}]
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
// Step 3: Read the skill after installation
|
|
246
|
+
let readOutput;
|
|
247
|
+
try {
|
|
248
|
+
const { stdout, stderr } = await execFileAsync("npx", readArgs, { timeout: TIMEOUTS.READ });
|
|
249
|
+
readOutput = stdout || stderr;
|
|
250
|
+
}
|
|
251
|
+
catch (readError) {
|
|
252
|
+
const errorDetails = getErrorDetails(readError);
|
|
253
|
+
return {
|
|
254
|
+
content: [{
|
|
255
|
+
type: "text",
|
|
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.`
|
|
257
|
+
}]
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
const output = formatInstallAndReadResponse(params.repo, params.skillName, readOutput, false);
|
|
261
|
+
return {
|
|
262
|
+
content: [{ type: "text", text: output }],
|
|
263
|
+
structuredContent: {
|
|
264
|
+
repo: params.repo,
|
|
265
|
+
skillName: params.skillName,
|
|
266
|
+
skillContent: readOutput,
|
|
267
|
+
wasAlreadyInstalled: false
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
return {
|
|
273
|
+
content: [{
|
|
274
|
+
type: "text",
|
|
275
|
+
text: `❌ **Error**: ${error instanceof Error ? error.message : "An unexpected error occurred"}`
|
|
276
|
+
}]
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
});
|
|
147
280
|
}
|
|
148
281
|
/**
|
|
149
282
|
* Render a single skill as markdown lines
|
|
@@ -205,6 +338,24 @@ function formatAISearchResponse(skills, query) {
|
|
|
205
338
|
""
|
|
206
339
|
];
|
|
207
340
|
skills.forEach((skill, i) => lines.push(...renderSkill(skill, i)));
|
|
208
|
-
lines.push("💡 **Tip**: Use `
|
|
341
|
+
lines.push("💡 **Tip**: Use `skillsmp_install_and_read_skill` to install and load a skill's instructions.");
|
|
342
|
+
return lines.join("\n");
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Format install and read response as markdown
|
|
346
|
+
*/
|
|
347
|
+
function formatInstallAndReadResponse(repo, skillName, skillContent, wasAlreadyInstalled) {
|
|
348
|
+
const statusIcon = wasAlreadyInstalled ? "📖" : "📦";
|
|
349
|
+
const statusText = wasAlreadyInstalled ? "Skill Loaded (already installed)" : "Skill Installed & Loaded";
|
|
350
|
+
const lines = [
|
|
351
|
+
`# ${statusIcon} ${statusText}: ${skillName}`,
|
|
352
|
+
"",
|
|
353
|
+
`**Repository**: ${repo}`,
|
|
354
|
+
wasAlreadyInstalled ? "**Status**: ⚡ Loaded from local cache (skipped installation)" : "**Status**: ✅ Freshly installed",
|
|
355
|
+
"",
|
|
356
|
+
"---",
|
|
357
|
+
"",
|
|
358
|
+
skillContent
|
|
359
|
+
];
|
|
209
360
|
return lines.join("\n");
|
|
210
361
|
}
|