skillsmp-mcp-lite 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/LICENSE +21 -0
- package/README.md +115 -0
- package/dist/api.d.ts +80 -0
- package/dist/api.js +95 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +39 -0
- package/dist/schemas.d.ts +33 -0
- package/dist/schemas.js +38 -0
- package/dist/tools/skills.d.ts +5 -0
- package/dist/tools/skills.js +210 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SkillsMP
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# SkillsMP MCP Server
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server that enables AI assistants to search for skills from [SkillsMP](https://skillsmp.com) marketplace before starting any task.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Keyword Search**: Search skills using specific keywords like "PDF", "web scraper", "SEO"
|
|
8
|
+
- **AI Semantic Search**: Find skills using natural language descriptions powered by Cloudflare AI
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install
|
|
14
|
+
npm run build
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Configuration
|
|
18
|
+
|
|
19
|
+
### Environment Variables
|
|
20
|
+
|
|
21
|
+
| Variable | Required | Description |
|
|
22
|
+
| ------------------ | -------- | --------------------- |
|
|
23
|
+
| `SKILLSMP_API_KEY` | Yes | Your SkillsMP API key |
|
|
24
|
+
|
|
25
|
+
Get your API key from: https://skillsmp.com/docs/api
|
|
26
|
+
|
|
27
|
+
### VS Code / Cursor Setup
|
|
28
|
+
|
|
29
|
+
Add to your `settings.json`:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"mcp": {
|
|
34
|
+
"servers": {
|
|
35
|
+
"skillsmp": {
|
|
36
|
+
"command": "node",
|
|
37
|
+
"args": ["path/to/skillsmp-mcp-server/dist/index.js"],
|
|
38
|
+
"env": {
|
|
39
|
+
"SKILLSMP_API_KEY": "sk_live_skillsmp_YOUR_API_KEY"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Claude Desktop Setup
|
|
48
|
+
|
|
49
|
+
Add to your `claude_desktop_config.json`:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"mcpServers": {
|
|
54
|
+
"skillsmp": {
|
|
55
|
+
"command": "node",
|
|
56
|
+
"args": ["/absolute/path/to/skillsmp-mcp-server/dist/index.js"],
|
|
57
|
+
"env": {
|
|
58
|
+
"SKILLSMP_API_KEY": "sk_live_skillsmp_YOUR_API_KEY"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Available Tools
|
|
66
|
+
|
|
67
|
+
### `skillsmp_search_skills`
|
|
68
|
+
|
|
69
|
+
Search for skills using keywords.
|
|
70
|
+
|
|
71
|
+
**Parameters:**
|
|
72
|
+
|
|
73
|
+
- `query` (string, required): Search keywords
|
|
74
|
+
- `page` (number, optional): Page number (default: 1)
|
|
75
|
+
- `limit` (number, optional): Items per page (default: 20, max: 100)
|
|
76
|
+
- `sortBy` (string, optional): Sort by "stars" or "recent"
|
|
77
|
+
|
|
78
|
+
**Example:**
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Search for "PDF manipulation" skills
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `skillsmp_ai_search_skills`
|
|
85
|
+
|
|
86
|
+
AI semantic search for skills using natural language.
|
|
87
|
+
|
|
88
|
+
**Parameters:**
|
|
89
|
+
|
|
90
|
+
- `query` (string, required): Natural language description of what you want to accomplish
|
|
91
|
+
|
|
92
|
+
**Example:**
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
Find skills for "How to create a web scraper that extracts product data"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Usage Workflow
|
|
99
|
+
|
|
100
|
+
The recommended workflow is:
|
|
101
|
+
|
|
102
|
+
1. **Before starting any task**, search for relevant skills
|
|
103
|
+
2. If a matching skill is found, use `npx openskills read <skill-name>` to load it
|
|
104
|
+
3. Follow the skill's instructions to complete the task
|
|
105
|
+
|
|
106
|
+
## API Reference
|
|
107
|
+
|
|
108
|
+
This server uses the SkillsMP REST API:
|
|
109
|
+
|
|
110
|
+
- `GET /api/v1/skills/search` - Keyword search
|
|
111
|
+
- `GET /api/v1/skills/ai-search` - AI semantic search
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SkillsMP API Client and Types
|
|
3
|
+
*/
|
|
4
|
+
export declare const SKILLSMP_API_BASE = "https://skillsmp.com/api/v1";
|
|
5
|
+
export interface Skill {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
author?: string;
|
|
10
|
+
stars?: number;
|
|
11
|
+
updatedAt?: number;
|
|
12
|
+
tags?: string[];
|
|
13
|
+
githubUrl?: string;
|
|
14
|
+
skillUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface Pagination {
|
|
17
|
+
page: number;
|
|
18
|
+
limit: number;
|
|
19
|
+
total: number;
|
|
20
|
+
totalPages: number;
|
|
21
|
+
hasNext: boolean;
|
|
22
|
+
hasPrev: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface SearchResponse {
|
|
25
|
+
success: boolean;
|
|
26
|
+
data: {
|
|
27
|
+
skills: Skill[];
|
|
28
|
+
pagination: Pagination;
|
|
29
|
+
filters?: {
|
|
30
|
+
search: string;
|
|
31
|
+
sortBy: string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
meta?: {
|
|
35
|
+
requestId: string;
|
|
36
|
+
responseTimeMs: number;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface AISearchResponse {
|
|
40
|
+
success: boolean;
|
|
41
|
+
data: {
|
|
42
|
+
object: string;
|
|
43
|
+
search_query: string;
|
|
44
|
+
data: Array<{
|
|
45
|
+
file_id: string;
|
|
46
|
+
filename: string;
|
|
47
|
+
score: number;
|
|
48
|
+
skill?: Skill;
|
|
49
|
+
}>;
|
|
50
|
+
has_more: boolean;
|
|
51
|
+
next_page: string | null;
|
|
52
|
+
};
|
|
53
|
+
meta?: {
|
|
54
|
+
requestId: string;
|
|
55
|
+
responseTimeMs: number;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export interface ApiError {
|
|
59
|
+
success: false;
|
|
60
|
+
error: {
|
|
61
|
+
code: string;
|
|
62
|
+
message: string;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export declare function makeApiRequest<T>(endpoint: string, apiKey: string, params?: Record<string, string | number>): Promise<T>;
|
|
66
|
+
/**
|
|
67
|
+
* Validate search response structure
|
|
68
|
+
*/
|
|
69
|
+
export declare function validateSearchResponse(data: unknown): asserts data is SearchResponse;
|
|
70
|
+
/**
|
|
71
|
+
* Validate AI search response structure
|
|
72
|
+
*/
|
|
73
|
+
export declare function validateAISearchResponse(data: unknown): asserts data is AISearchResponse;
|
|
74
|
+
/**
|
|
75
|
+
* Custom error for API structure changes
|
|
76
|
+
*/
|
|
77
|
+
export declare class ApiStructureError extends Error {
|
|
78
|
+
constructor(message: string);
|
|
79
|
+
}
|
|
80
|
+
export declare function handleApiError(error: unknown): string;
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SkillsMP API Client and Types
|
|
3
|
+
*/
|
|
4
|
+
// API Configuration
|
|
5
|
+
export const SKILLSMP_API_BASE = "https://skillsmp.com/api/v1";
|
|
6
|
+
// API Client
|
|
7
|
+
export async function makeApiRequest(endpoint, apiKey, params) {
|
|
8
|
+
const url = new URL(`${SKILLSMP_API_BASE}/${endpoint}`);
|
|
9
|
+
if (params) {
|
|
10
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
11
|
+
if (value !== undefined && value !== null) {
|
|
12
|
+
url.searchParams.append(key, String(value));
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const response = await fetch(url.toString(), {
|
|
17
|
+
method: "GET",
|
|
18
|
+
headers: {
|
|
19
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
20
|
+
"Content-Type": "application/json"
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
const errorData = await response.json().catch(() => ({}));
|
|
25
|
+
throw new Error(errorData.error?.message ||
|
|
26
|
+
`API request failed with status ${response.status}`);
|
|
27
|
+
}
|
|
28
|
+
return response.json();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validate base response structure (shared logic)
|
|
32
|
+
*/
|
|
33
|
+
function validateBaseResponse(data) {
|
|
34
|
+
if (!data || typeof data !== 'object') {
|
|
35
|
+
throw new ApiStructureError('Invalid response: expected object');
|
|
36
|
+
}
|
|
37
|
+
const response = data;
|
|
38
|
+
if (typeof response.success !== 'boolean') {
|
|
39
|
+
throw new ApiStructureError('Invalid response: missing "success" field');
|
|
40
|
+
}
|
|
41
|
+
if (!response.success) {
|
|
42
|
+
throw new ApiStructureError(`API returned failure: ${JSON.stringify(response)}`);
|
|
43
|
+
}
|
|
44
|
+
if (!response.data || typeof response.data !== 'object') {
|
|
45
|
+
throw new ApiStructureError('Invalid response: missing "data" object');
|
|
46
|
+
}
|
|
47
|
+
return response.data;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Validate search response structure
|
|
51
|
+
*/
|
|
52
|
+
export function validateSearchResponse(data) {
|
|
53
|
+
const responseData = validateBaseResponse(data);
|
|
54
|
+
if (!Array.isArray(responseData.skills)) {
|
|
55
|
+
throw new ApiStructureError('Invalid response structure: expected "data.skills" array. ' +
|
|
56
|
+
`Got: ${JSON.stringify(Object.keys(responseData))}. ` +
|
|
57
|
+
'The SkillsMP API structure may have changed.');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validate AI search response structure
|
|
62
|
+
*/
|
|
63
|
+
export function validateAISearchResponse(data) {
|
|
64
|
+
const responseData = validateBaseResponse(data);
|
|
65
|
+
if (!Array.isArray(responseData.data)) {
|
|
66
|
+
throw new ApiStructureError('Invalid AI search response structure: expected "data.data" array. ' +
|
|
67
|
+
`Got: ${JSON.stringify(Object.keys(responseData))}. ` +
|
|
68
|
+
'The SkillsMP API structure may have changed.');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Custom error for API structure changes
|
|
73
|
+
*/
|
|
74
|
+
export class ApiStructureError extends Error {
|
|
75
|
+
constructor(message) {
|
|
76
|
+
super(message);
|
|
77
|
+
this.name = 'ApiStructureError';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Error Handler
|
|
81
|
+
export function handleApiError(error) {
|
|
82
|
+
if (error instanceof ApiStructureError) {
|
|
83
|
+
return `⚠️ API Structure Error: ${error.message}\n\nThis likely means the SkillsMP API has changed. Please report this issue.`;
|
|
84
|
+
}
|
|
85
|
+
if (error instanceof Error) {
|
|
86
|
+
if (error.message.includes("401")) {
|
|
87
|
+
return "Error: Invalid or missing API key. Please check your SKILLSMP_API_KEY environment variable.";
|
|
88
|
+
}
|
|
89
|
+
if (error.message.includes("429")) {
|
|
90
|
+
return "Error: Rate limit exceeded. Please wait before making more requests.";
|
|
91
|
+
}
|
|
92
|
+
return `Error: ${error.message}`;
|
|
93
|
+
}
|
|
94
|
+
return "Error: An unexpected error occurred";
|
|
95
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { registerSkillsTools } from "./tools/skills.js";
|
|
5
|
+
/**
|
|
6
|
+
* SkillsMP MCP Server
|
|
7
|
+
*
|
|
8
|
+
* This server provides tools to search for AI skills from SkillsMP marketplace
|
|
9
|
+
* before starting any task. It supports both keyword search and AI semantic search.
|
|
10
|
+
*
|
|
11
|
+
* Environment Variables:
|
|
12
|
+
* SKILLSMP_API_KEY - Your SkillsMP API key (required)
|
|
13
|
+
*/
|
|
14
|
+
const API_KEY = process.env.SKILLSMP_API_KEY;
|
|
15
|
+
if (!API_KEY) {
|
|
16
|
+
console.error("Error: SKILLSMP_API_KEY environment variable is required");
|
|
17
|
+
console.error("Get your API key from: https://skillsmp.com/docs/api");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
// Initialize MCP Server
|
|
21
|
+
const server = new McpServer({
|
|
22
|
+
name: "skillsmp-mcp-server",
|
|
23
|
+
version: "1.0.0"
|
|
24
|
+
});
|
|
25
|
+
// Register skills search tools
|
|
26
|
+
registerSkillsTools(server, API_KEY);
|
|
27
|
+
// Start server with stdio transport
|
|
28
|
+
async function main() {
|
|
29
|
+
const transport = new StdioServerTransport();
|
|
30
|
+
await server.connect(transport);
|
|
31
|
+
console.error("SkillsMP MCP Server started");
|
|
32
|
+
console.error("Available tools:");
|
|
33
|
+
console.error(" - skillsmp_search_skills: Keyword search for skills");
|
|
34
|
+
console.error(" - skillsmp_ai_search_skills: AI semantic search for skills");
|
|
35
|
+
}
|
|
36
|
+
main().catch((error) => {
|
|
37
|
+
console.error("Failed to start server:", error);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Zod Schemas for SkillsMP Tools
|
|
4
|
+
*/
|
|
5
|
+
export declare enum SortBy {
|
|
6
|
+
STARS = "stars",
|
|
7
|
+
RECENT = "recent"
|
|
8
|
+
}
|
|
9
|
+
export declare const KeywordSearchSchema: z.ZodObject<{
|
|
10
|
+
query: z.ZodString;
|
|
11
|
+
page: z.ZodDefault<z.ZodNumber>;
|
|
12
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
13
|
+
sortBy: z.ZodOptional<z.ZodNativeEnum<typeof SortBy>>;
|
|
14
|
+
}, "strict", z.ZodTypeAny, {
|
|
15
|
+
query: string;
|
|
16
|
+
page: number;
|
|
17
|
+
limit: number;
|
|
18
|
+
sortBy?: SortBy | undefined;
|
|
19
|
+
}, {
|
|
20
|
+
query: string;
|
|
21
|
+
page?: number | undefined;
|
|
22
|
+
limit?: number | undefined;
|
|
23
|
+
sortBy?: SortBy | undefined;
|
|
24
|
+
}>;
|
|
25
|
+
export type KeywordSearchInput = z.infer<typeof KeywordSearchSchema>;
|
|
26
|
+
export declare const AISearchSchema: z.ZodObject<{
|
|
27
|
+
query: z.ZodString;
|
|
28
|
+
}, "strict", z.ZodTypeAny, {
|
|
29
|
+
query: string;
|
|
30
|
+
}, {
|
|
31
|
+
query: string;
|
|
32
|
+
}>;
|
|
33
|
+
export type AISearchInput = z.infer<typeof AISearchSchema>;
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Zod Schemas for SkillsMP Tools
|
|
4
|
+
*/
|
|
5
|
+
// Sort options enum
|
|
6
|
+
export var SortBy;
|
|
7
|
+
(function (SortBy) {
|
|
8
|
+
SortBy["STARS"] = "stars";
|
|
9
|
+
SortBy["RECENT"] = "recent";
|
|
10
|
+
})(SortBy || (SortBy = {}));
|
|
11
|
+
// Keyword Search Schema
|
|
12
|
+
export const KeywordSearchSchema = z.object({
|
|
13
|
+
query: z.string()
|
|
14
|
+
.min(1, "Query is required")
|
|
15
|
+
.max(200, "Query must not exceed 200 characters")
|
|
16
|
+
.describe("Search keywords for finding skills"),
|
|
17
|
+
page: z.number()
|
|
18
|
+
.int()
|
|
19
|
+
.min(1)
|
|
20
|
+
.default(1)
|
|
21
|
+
.describe("Page number (default: 1)"),
|
|
22
|
+
limit: z.number()
|
|
23
|
+
.int()
|
|
24
|
+
.min(1)
|
|
25
|
+
.max(100)
|
|
26
|
+
.default(20)
|
|
27
|
+
.describe("Items per page (default: 20, max: 100)"),
|
|
28
|
+
sortBy: z.nativeEnum(SortBy)
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Sort by: 'stars' or 'recent'")
|
|
31
|
+
}).strict();
|
|
32
|
+
// AI Semantic Search Schema
|
|
33
|
+
export const AISearchSchema = z.object({
|
|
34
|
+
query: z.string()
|
|
35
|
+
.min(1, "Query is required")
|
|
36
|
+
.max(500, "Query must not exceed 500 characters")
|
|
37
|
+
.describe("Natural language description of what you want to accomplish")
|
|
38
|
+
}).strict();
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { makeApiRequest, handleApiError, validateSearchResponse, validateAISearchResponse } from "../api.js";
|
|
2
|
+
import { KeywordSearchSchema, AISearchSchema } from "../schemas.js";
|
|
3
|
+
/**
|
|
4
|
+
* Register SkillsMP tools on the MCP server
|
|
5
|
+
*/
|
|
6
|
+
export function registerSkillsTools(server, apiKey) {
|
|
7
|
+
// Tool 1: Keyword Search
|
|
8
|
+
server.registerTool("skillsmp_search_skills", {
|
|
9
|
+
title: "Search SkillsMP Skills",
|
|
10
|
+
description: `Search for AI skills using keywords from SkillsMP marketplace.
|
|
11
|
+
|
|
12
|
+
Use this tool to find skills by specific terms like 'SEO', 'web scraper', 'PDF', 'data analysis', etc.
|
|
13
|
+
|
|
14
|
+
**IMPORTANT**: Before starting any task, use this tool to check if there's an existing skill that can help complete the task more effectively.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
- query (string, required): Search keywords
|
|
18
|
+
- page (number, optional): Page number (default: 1)
|
|
19
|
+
- limit (number, optional): Items per page (default: 20, max: 100)
|
|
20
|
+
- sortBy (string, optional): Sort by 'stars' or 'recent'
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
List of matching skills with name, description, author, and star count.
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
- "PDF manipulation" -> Find skills for working with PDFs
|
|
27
|
+
- "web scraper" -> Find web scraping skills
|
|
28
|
+
- "SEO optimization" -> Find SEO-related skills`,
|
|
29
|
+
inputSchema: KeywordSearchSchema,
|
|
30
|
+
annotations: {
|
|
31
|
+
readOnlyHint: true,
|
|
32
|
+
destructiveHint: false,
|
|
33
|
+
idempotentHint: true,
|
|
34
|
+
openWorldHint: true
|
|
35
|
+
}
|
|
36
|
+
}, async (params) => {
|
|
37
|
+
try {
|
|
38
|
+
const queryParams = {
|
|
39
|
+
q: params.query
|
|
40
|
+
};
|
|
41
|
+
if (params.page)
|
|
42
|
+
queryParams.page = params.page;
|
|
43
|
+
if (params.limit)
|
|
44
|
+
queryParams.limit = params.limit;
|
|
45
|
+
if (params.sortBy)
|
|
46
|
+
queryParams.sortBy = params.sortBy;
|
|
47
|
+
const rawData = await makeApiRequest("skills/search", apiKey, queryParams);
|
|
48
|
+
// Validate API response structure before processing
|
|
49
|
+
validateSearchResponse(rawData);
|
|
50
|
+
const skills = rawData.data?.skills || [];
|
|
51
|
+
const pagination = rawData.data?.pagination;
|
|
52
|
+
if (!skills.length) {
|
|
53
|
+
return {
|
|
54
|
+
content: [{
|
|
55
|
+
type: "text",
|
|
56
|
+
text: `No skills found matching '${params.query}'. Try different keywords or use AI semantic search for natural language queries.`
|
|
57
|
+
}]
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const output = formatSkillsResponse(skills, params.query, pagination);
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: "text", text: output }],
|
|
63
|
+
structuredContent: {
|
|
64
|
+
query: params.query,
|
|
65
|
+
count: skills.length,
|
|
66
|
+
skills: skills,
|
|
67
|
+
pagination: pagination
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
return {
|
|
73
|
+
content: [{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: handleApiError(error)
|
|
76
|
+
}]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
// Tool 2: AI Semantic Search
|
|
81
|
+
server.registerTool("skillsmp_ai_search_skills", {
|
|
82
|
+
title: "AI Search SkillsMP Skills",
|
|
83
|
+
description: `AI semantic search for skills using natural language descriptions.
|
|
84
|
+
|
|
85
|
+
Use this when you need to find skills based on what you want to accomplish rather than specific keywords.
|
|
86
|
+
|
|
87
|
+
**IMPORTANT**: Before starting any complex task, use this tool to discover relevant skills that can help.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
- query (string, required): Natural language description of what you want to accomplish
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
List of semantically relevant skills that match your intent.
|
|
94
|
+
|
|
95
|
+
Examples:
|
|
96
|
+
- "How to create a web scraper" -> Find skills for web scraping
|
|
97
|
+
- "Build a dashboard with charts" -> Find data visualization skills
|
|
98
|
+
- "Generate PDF reports from data" -> Find PDF generation skills
|
|
99
|
+
- "Automate social media posting" -> Find social media automation skills`,
|
|
100
|
+
inputSchema: AISearchSchema,
|
|
101
|
+
annotations: {
|
|
102
|
+
readOnlyHint: true,
|
|
103
|
+
destructiveHint: false,
|
|
104
|
+
idempotentHint: true,
|
|
105
|
+
openWorldHint: true
|
|
106
|
+
}
|
|
107
|
+
}, async (params) => {
|
|
108
|
+
try {
|
|
109
|
+
const rawData = await makeApiRequest("skills/ai-search", apiKey, { q: params.query });
|
|
110
|
+
// Validate API response structure before processing
|
|
111
|
+
validateAISearchResponse(rawData);
|
|
112
|
+
// AI search returns results in data.data array with skill objects
|
|
113
|
+
const results = rawData.data?.data || [];
|
|
114
|
+
const skills = results
|
|
115
|
+
.filter(item => item.skill) // Only include items with skill data
|
|
116
|
+
.map(item => ({
|
|
117
|
+
...item.skill,
|
|
118
|
+
score: item.score
|
|
119
|
+
}));
|
|
120
|
+
if (!skills.length) {
|
|
121
|
+
return {
|
|
122
|
+
content: [{
|
|
123
|
+
type: "text",
|
|
124
|
+
text: `No skills found for: "${params.query}". Try rephrasing your query or use keyword search for specific terms.`
|
|
125
|
+
}]
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const output = formatAISearchResponse(skills, params.query);
|
|
129
|
+
return {
|
|
130
|
+
content: [{ type: "text", text: output }],
|
|
131
|
+
structuredContent: {
|
|
132
|
+
query: params.query,
|
|
133
|
+
count: skills.length,
|
|
134
|
+
skills: skills
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return {
|
|
140
|
+
content: [{
|
|
141
|
+
type: "text",
|
|
142
|
+
text: handleApiError(error)
|
|
143
|
+
}]
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Render a single skill as markdown lines
|
|
150
|
+
*/
|
|
151
|
+
function renderSkill(skill, index) {
|
|
152
|
+
const lines = [];
|
|
153
|
+
// Header
|
|
154
|
+
const header = index !== undefined ? `## ${index + 1}. ${skill.name}` : `## ${skill.name}`;
|
|
155
|
+
lines.push(header);
|
|
156
|
+
// Meta info
|
|
157
|
+
const meta = [];
|
|
158
|
+
if (skill.stars !== undefined)
|
|
159
|
+
meta.push(`⭐ ${skill.stars} stars`);
|
|
160
|
+
if (skill.score !== undefined)
|
|
161
|
+
meta.push(`📊 Score: ${(skill.score * 100).toFixed(1)}%`);
|
|
162
|
+
if (meta.length)
|
|
163
|
+
lines.push(meta.join(" | "));
|
|
164
|
+
lines.push("");
|
|
165
|
+
lines.push(skill.description || "No description available");
|
|
166
|
+
if (skill.author) {
|
|
167
|
+
lines.push("");
|
|
168
|
+
lines.push(`**Author**: ${skill.author}`);
|
|
169
|
+
}
|
|
170
|
+
if (skill.skillUrl) {
|
|
171
|
+
lines.push(`**URL**: ${skill.skillUrl}`);
|
|
172
|
+
}
|
|
173
|
+
if (skill.tags?.length) {
|
|
174
|
+
lines.push(`**Tags**: ${skill.tags.join(", ")}`);
|
|
175
|
+
}
|
|
176
|
+
lines.push("", "---", "");
|
|
177
|
+
return lines;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Format skills search response as markdown
|
|
181
|
+
*/
|
|
182
|
+
function formatSkillsResponse(skills, query, pagination) {
|
|
183
|
+
const lines = [
|
|
184
|
+
`# 🔍 Skills Search Results: "${query}"`,
|
|
185
|
+
"",
|
|
186
|
+
`Found ${pagination?.total || skills.length} skill(s) (showing ${skills.length})`,
|
|
187
|
+
""
|
|
188
|
+
];
|
|
189
|
+
skills.forEach(skill => lines.push(...renderSkill(skill)));
|
|
190
|
+
if (pagination?.hasNext) {
|
|
191
|
+
lines.push(`*More results available. Call this tool again with \`page: ${pagination.page + 1}\` to see more.*`);
|
|
192
|
+
}
|
|
193
|
+
return lines.join("\n");
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Format AI search response as markdown
|
|
197
|
+
*/
|
|
198
|
+
function formatAISearchResponse(skills, query) {
|
|
199
|
+
const lines = [
|
|
200
|
+
`# 🤖 AI Semantic Search Results`,
|
|
201
|
+
"",
|
|
202
|
+
`**Query**: "${query}"`,
|
|
203
|
+
"",
|
|
204
|
+
`Found ${skills.length} relevant skill(s)`,
|
|
205
|
+
""
|
|
206
|
+
];
|
|
207
|
+
skills.forEach((skill, i) => lines.push(...renderSkill(skill, i)));
|
|
208
|
+
lines.push("💡 **Tip**: Use `npx openskills read <skill-name>` to load a skill's detailed instructions.");
|
|
209
|
+
return lines.join("\n");
|
|
210
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "skillsmp-mcp-lite",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight MCP server for searching AI skills from SkillsMP before starting any task",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"skillsmp-mcp-lite": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"start": "node dist/index.js",
|
|
16
|
+
"dev": "tsc --watch",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"model-context-protocol",
|
|
22
|
+
"skills",
|
|
23
|
+
"ai",
|
|
24
|
+
"skillsmp",
|
|
25
|
+
"ai-skills",
|
|
26
|
+
"marketplace"
|
|
27
|
+
],
|
|
28
|
+
"author": "clancylllin <poerevan9@gmail.com>",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/boyonglin/skillsmp-mcp-lite.git"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/boyonglin/skillsmp-mcp-lite#readme",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/boyonglin/skillsmp-mcp-lite/issues"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.25.0",
|
|
43
|
+
"zod": "^3.25.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^22.0.0",
|
|
47
|
+
"typescript": "^5.7.0"
|
|
48
|
+
}
|
|
49
|
+
}
|