vibeindex-mcp 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/README.md +127 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +300 -0
- package/package.json +33 -0
- package/src/index.ts +392 -0
- package/tsconfig.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Vibe Index MCP Server
|
|
2
|
+
|
|
3
|
+
Claude Code에서 [Vibe Index](https://vibeindex.ai)의 스킬, 플러그인, MCP 서버를 검색하고 설치할 수 있는 MCP 서버입니다.
|
|
4
|
+
|
|
5
|
+
## 설치
|
|
6
|
+
|
|
7
|
+
### Claude Desktop
|
|
8
|
+
|
|
9
|
+
`claude_desktop_config.json`에 추가:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"vibeindex": {
|
|
15
|
+
"command": "npx",
|
|
16
|
+
"args": ["-y", "@vibeindex/mcp-server"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Claude Code
|
|
23
|
+
|
|
24
|
+
`.claude/settings.json`에 추가:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"vibeindex": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["-y", "@vibeindex/mcp-server"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 사용 가능한 도구
|
|
38
|
+
|
|
39
|
+
### search
|
|
40
|
+
리소스 검색
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
"git 관련 스킬 찾아줘"
|
|
44
|
+
"database MCP 서버 검색해줘"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**파라미터:**
|
|
48
|
+
- `query` (필수): 검색어
|
|
49
|
+
- `type` (선택): all, skill, plugin, mcp, marketplace
|
|
50
|
+
- `limit` (선택): 결과 개수 (기본 10, 최대 20)
|
|
51
|
+
|
|
52
|
+
### trending
|
|
53
|
+
트렌딩 리소스 조회
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
"요즘 인기있는 스킬 뭐야?"
|
|
57
|
+
"이번 주 트렌딩 보여줘"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**파라미터:**
|
|
61
|
+
- `period` (선택): day, week, month (기본 week)
|
|
62
|
+
- `limit` (선택): 결과 개수 (기본 10)
|
|
63
|
+
|
|
64
|
+
### top
|
|
65
|
+
스타 순 상위 리소스
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
"가장 인기있는 MCP 서버 알려줘"
|
|
69
|
+
"스타 많은 플러그인 top 5"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**파라미터:**
|
|
73
|
+
- `type` (선택): all, skill, plugin, mcp, marketplace
|
|
74
|
+
- `limit` (선택): 결과 개수 (기본 10)
|
|
75
|
+
|
|
76
|
+
### install
|
|
77
|
+
설치 명령어 조회
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
"commit-message 스킬 설치 방법 알려줘"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**파라미터:**
|
|
84
|
+
- `name` (필수): 리소스 이름
|
|
85
|
+
- `type` (선택): 리소스 타입
|
|
86
|
+
|
|
87
|
+
### categories
|
|
88
|
+
카테고리 및 태그 목록
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
"어떤 카테고리가 있어?"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 예시
|
|
95
|
+
|
|
96
|
+
Claude Code에서:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
User: git 관련 스킬 찾아줘
|
|
100
|
+
Claude: [search 도구 사용] 검색 결과를 보여드립니다...
|
|
101
|
+
|
|
102
|
+
User: 그 중에 commit-message 설치해줘
|
|
103
|
+
Claude: [install 도구 사용] 설치 명령어입니다:
|
|
104
|
+
claude skill add owner/repo/commit-message
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 개발
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# 의존성 설치
|
|
111
|
+
npm install
|
|
112
|
+
|
|
113
|
+
# 빌드
|
|
114
|
+
npm run build
|
|
115
|
+
|
|
116
|
+
# 개발 모드 (watch)
|
|
117
|
+
npm run dev
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 라이선스
|
|
121
|
+
|
|
122
|
+
MIT
|
|
123
|
+
|
|
124
|
+
## 링크
|
|
125
|
+
|
|
126
|
+
- [Vibe Index](https://vibeindex.ai)
|
|
127
|
+
- [GitHub](https://github.com/vibeindex/mcp-server)
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
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 { z } from "zod";
|
|
5
|
+
const API_BASE = "https://vibeindex.ai/api";
|
|
6
|
+
// Helper to fetch from Vibe Index API
|
|
7
|
+
async function fetchAPI(endpoint) {
|
|
8
|
+
const response = await fetch(`${API_BASE}${endpoint}`);
|
|
9
|
+
if (!response.ok) {
|
|
10
|
+
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
11
|
+
}
|
|
12
|
+
return response.json();
|
|
13
|
+
}
|
|
14
|
+
// Generate install command based on resource type
|
|
15
|
+
function getInstallCommand(resource) {
|
|
16
|
+
const { resource_type, github_owner, github_repo, name } = resource;
|
|
17
|
+
switch (resource_type) {
|
|
18
|
+
case "skill":
|
|
19
|
+
return `claude skill add ${github_owner}/${github_repo}/${name}`;
|
|
20
|
+
case "plugin":
|
|
21
|
+
return `claude plugin add ${github_owner}/${github_repo}`;
|
|
22
|
+
case "mcp":
|
|
23
|
+
return `# Add to claude_desktop_config.json or .claude/settings.json
|
|
24
|
+
# See: ${resource.github_url}`;
|
|
25
|
+
case "marketplace":
|
|
26
|
+
return `# Browse marketplace: https://vibeindex.ai/marketplaces/${github_owner}/${github_repo}`;
|
|
27
|
+
default:
|
|
28
|
+
return `# See: ${resource.github_url}`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Format resource for display
|
|
32
|
+
function formatResource(resource, includeInstall = false) {
|
|
33
|
+
const typeEmoji = {
|
|
34
|
+
skill: "🎯",
|
|
35
|
+
plugin: "🔌",
|
|
36
|
+
mcp: "🔗",
|
|
37
|
+
marketplace: "🏪",
|
|
38
|
+
};
|
|
39
|
+
const lines = [
|
|
40
|
+
`${typeEmoji[resource.resource_type] || "📦"} **${resource.name}**`,
|
|
41
|
+
` Type: ${resource.resource_type}${resource.is_official ? " (Official)" : ""}`,
|
|
42
|
+
` Stars: ⭐ ${resource.stars.toLocaleString()}`,
|
|
43
|
+
` Repo: ${resource.github_owner}/${resource.github_repo}`,
|
|
44
|
+
` Description: ${resource.description || "No description"}`,
|
|
45
|
+
];
|
|
46
|
+
if (resource.tags?.length > 0) {
|
|
47
|
+
lines.push(` Tags: ${resource.tags.slice(0, 5).join(", ")}`);
|
|
48
|
+
}
|
|
49
|
+
if (includeInstall) {
|
|
50
|
+
lines.push(` Install: \`${getInstallCommand(resource)}\``);
|
|
51
|
+
}
|
|
52
|
+
lines.push(` URL: https://vibeindex.ai/${resource.resource_type}s/${resource.github_owner}/${resource.github_repo}/${resource.name}`);
|
|
53
|
+
return lines.join("\n");
|
|
54
|
+
}
|
|
55
|
+
// Create MCP server
|
|
56
|
+
const server = new McpServer({
|
|
57
|
+
name: "vibeindex",
|
|
58
|
+
version: "1.0.0",
|
|
59
|
+
});
|
|
60
|
+
// Tool: Search resources
|
|
61
|
+
server.tool("search", "Search for Claude Code skills, plugins, MCP servers, and marketplaces on Vibe Index", {
|
|
62
|
+
query: z.string().describe("Search query (e.g., 'git', 'database', 'docker')"),
|
|
63
|
+
type: z
|
|
64
|
+
.enum(["all", "skill", "plugin", "mcp", "marketplace"])
|
|
65
|
+
.optional()
|
|
66
|
+
.describe("Filter by resource type (default: all)"),
|
|
67
|
+
limit: z
|
|
68
|
+
.number()
|
|
69
|
+
.min(1)
|
|
70
|
+
.max(20)
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Number of results to return (default: 10, max: 20)"),
|
|
73
|
+
}, async ({ query, type = "all", limit = 10 }) => {
|
|
74
|
+
try {
|
|
75
|
+
const typeFilter = type !== "all" ? `&type=${type}` : "";
|
|
76
|
+
const data = await fetchAPI(`/resources?search=${encodeURIComponent(query)}${typeFilter}&pageSize=${limit}`);
|
|
77
|
+
if (data.data.length === 0) {
|
|
78
|
+
return {
|
|
79
|
+
content: [
|
|
80
|
+
{
|
|
81
|
+
type: "text",
|
|
82
|
+
text: `No results found for "${query}"${type !== "all" ? ` in ${type}s` : ""}. Try different keywords or browse at https://vibeindex.ai`,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const results = data.data.map((r) => formatResource(r, true)).join("\n\n---\n\n");
|
|
88
|
+
return {
|
|
89
|
+
content: [
|
|
90
|
+
{
|
|
91
|
+
type: "text",
|
|
92
|
+
text: `## Search Results for "${query}" (${data.total} total)\n\n${results}\n\n---\nBrowse more at https://vibeindex.ai/search?q=${encodeURIComponent(query)}`,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
return {
|
|
99
|
+
content: [
|
|
100
|
+
{
|
|
101
|
+
type: "text",
|
|
102
|
+
text: `Error searching: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
isError: true,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
// Tool: Get trending/rising resources
|
|
110
|
+
server.tool("trending", "Get trending and rising Claude Code resources from Vibe Index", {
|
|
111
|
+
period: z
|
|
112
|
+
.enum(["day", "week", "month"])
|
|
113
|
+
.optional()
|
|
114
|
+
.describe("Time period for trending (default: week)"),
|
|
115
|
+
limit: z
|
|
116
|
+
.number()
|
|
117
|
+
.min(1)
|
|
118
|
+
.max(20)
|
|
119
|
+
.optional()
|
|
120
|
+
.describe("Number of results (default: 10)"),
|
|
121
|
+
}, async ({ period = "week", limit = 10 }) => {
|
|
122
|
+
try {
|
|
123
|
+
const data = await fetchAPI(`/rising-stars?period=${period}&limit=${limit}`);
|
|
124
|
+
if (!data.rising || data.rising.length === 0) {
|
|
125
|
+
return {
|
|
126
|
+
content: [
|
|
127
|
+
{
|
|
128
|
+
type: "text",
|
|
129
|
+
text: `No trending resources found for the past ${period}. Check https://vibeindex.ai for the latest.`,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const periodLabel = { day: "24 hours", week: "week", month: "month" }[period];
|
|
135
|
+
const results = data.rising
|
|
136
|
+
.map((r, i) => {
|
|
137
|
+
const base = formatResource(r, true);
|
|
138
|
+
return `**#${i + 1}** (+${r.stars_today} stars this ${period})\n${base}`;
|
|
139
|
+
})
|
|
140
|
+
.join("\n\n---\n\n");
|
|
141
|
+
return {
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: "text",
|
|
145
|
+
text: `## 🔥 Trending Resources (Past ${periodLabel})\n\n${results}\n\n---\nSee full rankings at https://vibeindex.ai`,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
return {
|
|
152
|
+
content: [
|
|
153
|
+
{
|
|
154
|
+
type: "text",
|
|
155
|
+
text: `Error fetching trending: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
isError: true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
// Tool: Get top resources by stars
|
|
163
|
+
server.tool("top", "Get top Claude Code resources by stars", {
|
|
164
|
+
type: z
|
|
165
|
+
.enum(["all", "skill", "plugin", "mcp", "marketplace"])
|
|
166
|
+
.optional()
|
|
167
|
+
.describe("Filter by resource type (default: all)"),
|
|
168
|
+
limit: z
|
|
169
|
+
.number()
|
|
170
|
+
.min(1)
|
|
171
|
+
.max(20)
|
|
172
|
+
.optional()
|
|
173
|
+
.describe("Number of results (default: 10)"),
|
|
174
|
+
}, async ({ type = "all", limit = 10 }) => {
|
|
175
|
+
try {
|
|
176
|
+
const typeFilter = type !== "all" ? `&type=${type}` : "";
|
|
177
|
+
const data = await fetchAPI(`/resources?sort=stars${typeFilter}&pageSize=${limit}`);
|
|
178
|
+
if (data.data.length === 0) {
|
|
179
|
+
return {
|
|
180
|
+
content: [
|
|
181
|
+
{
|
|
182
|
+
type: "text",
|
|
183
|
+
text: `No resources found. Check https://vibeindex.ai`,
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const typeLabel = type === "all" ? "Resources" : `${type}s`;
|
|
189
|
+
const results = data.data
|
|
190
|
+
.map((r, i) => `**#${i + 1}**\n${formatResource(r, true)}`)
|
|
191
|
+
.join("\n\n---\n\n");
|
|
192
|
+
return {
|
|
193
|
+
content: [
|
|
194
|
+
{
|
|
195
|
+
type: "text",
|
|
196
|
+
text: `## ⭐ Top ${typeLabel} by Stars\n\n${results}\n\n---\nBrowse all at https://vibeindex.ai/browse?sort=stars${type !== "all" ? `&type=${type}` : ""}`,
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
return {
|
|
203
|
+
content: [
|
|
204
|
+
{
|
|
205
|
+
type: "text",
|
|
206
|
+
text: `Error fetching top resources: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
isError: true,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
// Tool: Get install command
|
|
214
|
+
server.tool("install", "Get the install command for a Claude Code resource", {
|
|
215
|
+
name: z.string().describe("Resource name to get install command for"),
|
|
216
|
+
type: z
|
|
217
|
+
.enum(["skill", "plugin", "mcp", "marketplace"])
|
|
218
|
+
.optional()
|
|
219
|
+
.describe("Resource type (helps find exact match)"),
|
|
220
|
+
}, async ({ name, type }) => {
|
|
221
|
+
try {
|
|
222
|
+
const typeFilter = type ? `&type=${type}` : "";
|
|
223
|
+
const data = await fetchAPI(`/resources?search=${encodeURIComponent(name)}${typeFilter}&pageSize=5`);
|
|
224
|
+
// Find exact or close match
|
|
225
|
+
const exactMatch = data.data.find((r) => r.name.toLowerCase() === name.toLowerCase());
|
|
226
|
+
const resource = exactMatch || data.data[0];
|
|
227
|
+
if (!resource) {
|
|
228
|
+
return {
|
|
229
|
+
content: [
|
|
230
|
+
{
|
|
231
|
+
type: "text",
|
|
232
|
+
text: `Resource "${name}" not found. Try searching with: search "${name}"`,
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const installCmd = getInstallCommand(resource);
|
|
238
|
+
return {
|
|
239
|
+
content: [
|
|
240
|
+
{
|
|
241
|
+
type: "text",
|
|
242
|
+
text: `## Install ${resource.name}\n\n**Type:** ${resource.resource_type}\n**Repository:** ${resource.github_owner}/${resource.github_repo}\n\n**Install Command:**\n\`\`\`bash\n${installCmd}\n\`\`\`\n\n**More info:** https://vibeindex.ai/${resource.resource_type}s/${resource.github_owner}/${resource.github_repo}/${resource.name}`,
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
return {
|
|
249
|
+
content: [
|
|
250
|
+
{
|
|
251
|
+
type: "text",
|
|
252
|
+
text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
isError: true,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
// Tool: Browse categories
|
|
260
|
+
server.tool("categories", "List available categories and tags for Claude Code resources", {}, async () => {
|
|
261
|
+
const categories = {
|
|
262
|
+
"Resource Types": [
|
|
263
|
+
"🎯 Skills - Reusable agent capabilities",
|
|
264
|
+
"🔌 Plugins - Extend Claude Code functionality",
|
|
265
|
+
"🔗 MCP Servers - Model Context Protocol integrations",
|
|
266
|
+
"🏪 Marketplaces - Collections of plugins/skills",
|
|
267
|
+
],
|
|
268
|
+
"Popular Tags": [
|
|
269
|
+
"git, github - Version control",
|
|
270
|
+
"database, sql, postgres - Data management",
|
|
271
|
+
"docker, kubernetes - Containers & orchestration",
|
|
272
|
+
"aws, gcp, azure - Cloud platforms",
|
|
273
|
+
"react, vue, nextjs - Frontend frameworks",
|
|
274
|
+
"python, typescript, rust - Programming languages",
|
|
275
|
+
"testing, lint, ci-cd - Development tools",
|
|
276
|
+
"ai, llm, openai - AI/ML tools",
|
|
277
|
+
],
|
|
278
|
+
};
|
|
279
|
+
const text = Object.entries(categories)
|
|
280
|
+
.map(([cat, items]) => `### ${cat}\n${items.map((i) => `- ${i}`).join("\n")}`)
|
|
281
|
+
.join("\n\n");
|
|
282
|
+
return {
|
|
283
|
+
content: [
|
|
284
|
+
{
|
|
285
|
+
type: "text",
|
|
286
|
+
text: `## Vibe Index Categories\n\n${text}\n\n---\nUse \`search <keyword>\` to find resources in any category.\nBrowse all at https://vibeindex.ai/explore`,
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
};
|
|
290
|
+
});
|
|
291
|
+
// Start server
|
|
292
|
+
async function main() {
|
|
293
|
+
const transport = new StdioServerTransport();
|
|
294
|
+
await server.connect(transport);
|
|
295
|
+
console.error("Vibe Index MCP Server running on stdio");
|
|
296
|
+
}
|
|
297
|
+
main().catch((error) => {
|
|
298
|
+
console.error("Fatal error:", error);
|
|
299
|
+
process.exit(1);
|
|
300
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vibeindex-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Vibe Index - search Claude Code skills, plugins, and MCP servers",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vibeindex-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"claude",
|
|
18
|
+
"claude-code",
|
|
19
|
+
"vibeindex",
|
|
20
|
+
"skills",
|
|
21
|
+
"plugins"
|
|
22
|
+
],
|
|
23
|
+
"author": "JoLab",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
27
|
+
"zod": "^3.23.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^20.0.0",
|
|
31
|
+
"typescript": "^5.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
const API_BASE = "https://vibeindex.ai/api";
|
|
8
|
+
|
|
9
|
+
interface Resource {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
description_ko?: string;
|
|
14
|
+
resource_type: "skill" | "plugin" | "mcp" | "marketplace";
|
|
15
|
+
github_owner: string;
|
|
16
|
+
github_repo: string;
|
|
17
|
+
github_url: string;
|
|
18
|
+
stars: number;
|
|
19
|
+
tags: string[];
|
|
20
|
+
is_official?: boolean;
|
|
21
|
+
install_command?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface SearchResponse {
|
|
25
|
+
data: Resource[];
|
|
26
|
+
total: number;
|
|
27
|
+
page: number;
|
|
28
|
+
limit: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface TrendingResponse {
|
|
32
|
+
rising: Array<Resource & { stars_today: number; trending_rank: number }>;
|
|
33
|
+
period: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Helper to fetch from Vibe Index API
|
|
37
|
+
async function fetchAPI<T>(endpoint: string): Promise<T> {
|
|
38
|
+
const response = await fetch(`${API_BASE}${endpoint}`);
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
41
|
+
}
|
|
42
|
+
return response.json() as Promise<T>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Generate install command based on resource type
|
|
46
|
+
function getInstallCommand(resource: Resource): string {
|
|
47
|
+
const { resource_type, github_owner, github_repo, name } = resource;
|
|
48
|
+
|
|
49
|
+
switch (resource_type) {
|
|
50
|
+
case "skill":
|
|
51
|
+
return `claude skill add ${github_owner}/${github_repo}/${name}`;
|
|
52
|
+
case "plugin":
|
|
53
|
+
return `claude plugin add ${github_owner}/${github_repo}`;
|
|
54
|
+
case "mcp":
|
|
55
|
+
return `# Add to claude_desktop_config.json or .claude/settings.json
|
|
56
|
+
# See: ${resource.github_url}`;
|
|
57
|
+
case "marketplace":
|
|
58
|
+
return `# Browse marketplace: https://vibeindex.ai/marketplaces/${github_owner}/${github_repo}`;
|
|
59
|
+
default:
|
|
60
|
+
return `# See: ${resource.github_url}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Format resource for display
|
|
65
|
+
function formatResource(resource: Resource, includeInstall = false): string {
|
|
66
|
+
const typeEmoji: Record<string, string> = {
|
|
67
|
+
skill: "🎯",
|
|
68
|
+
plugin: "🔌",
|
|
69
|
+
mcp: "🔗",
|
|
70
|
+
marketplace: "🏪",
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const lines = [
|
|
74
|
+
`${typeEmoji[resource.resource_type] || "📦"} **${resource.name}**`,
|
|
75
|
+
` Type: ${resource.resource_type}${resource.is_official ? " (Official)" : ""}`,
|
|
76
|
+
` Stars: ⭐ ${resource.stars.toLocaleString()}`,
|
|
77
|
+
` Repo: ${resource.github_owner}/${resource.github_repo}`,
|
|
78
|
+
` Description: ${resource.description || "No description"}`,
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
if (resource.tags?.length > 0) {
|
|
82
|
+
lines.push(` Tags: ${resource.tags.slice(0, 5).join(", ")}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (includeInstall) {
|
|
86
|
+
lines.push(` Install: \`${getInstallCommand(resource)}\``);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
lines.push(` URL: https://vibeindex.ai/${resource.resource_type}s/${resource.github_owner}/${resource.github_repo}/${resource.name}`);
|
|
90
|
+
|
|
91
|
+
return lines.join("\n");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Create MCP server
|
|
95
|
+
const server = new McpServer({
|
|
96
|
+
name: "vibeindex",
|
|
97
|
+
version: "1.0.0",
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Tool: Search resources
|
|
101
|
+
server.tool(
|
|
102
|
+
"search",
|
|
103
|
+
"Search for Claude Code skills, plugins, MCP servers, and marketplaces on Vibe Index",
|
|
104
|
+
{
|
|
105
|
+
query: z.string().describe("Search query (e.g., 'git', 'database', 'docker')"),
|
|
106
|
+
type: z
|
|
107
|
+
.enum(["all", "skill", "plugin", "mcp", "marketplace"])
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("Filter by resource type (default: all)"),
|
|
110
|
+
limit: z
|
|
111
|
+
.number()
|
|
112
|
+
.min(1)
|
|
113
|
+
.max(20)
|
|
114
|
+
.optional()
|
|
115
|
+
.describe("Number of results to return (default: 10, max: 20)"),
|
|
116
|
+
},
|
|
117
|
+
async ({ query, type = "all", limit = 10 }) => {
|
|
118
|
+
try {
|
|
119
|
+
const typeFilter = type !== "all" ? `&type=${type}` : "";
|
|
120
|
+
const data = await fetchAPI<SearchResponse>(
|
|
121
|
+
`/resources?search=${encodeURIComponent(query)}${typeFilter}&pageSize=${limit}`
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (data.data.length === 0) {
|
|
125
|
+
return {
|
|
126
|
+
content: [
|
|
127
|
+
{
|
|
128
|
+
type: "text" as const,
|
|
129
|
+
text: `No results found for "${query}"${type !== "all" ? ` in ${type}s` : ""}. Try different keywords or browse at https://vibeindex.ai`,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const results = data.data.map((r) => formatResource(r, true)).join("\n\n---\n\n");
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
content: [
|
|
139
|
+
{
|
|
140
|
+
type: "text" as const,
|
|
141
|
+
text: `## Search Results for "${query}" (${data.total} total)\n\n${results}\n\n---\nBrowse more at https://vibeindex.ai/search?q=${encodeURIComponent(query)}`,
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
} catch (error) {
|
|
146
|
+
return {
|
|
147
|
+
content: [
|
|
148
|
+
{
|
|
149
|
+
type: "text" as const,
|
|
150
|
+
text: `Error searching: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
isError: true,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// Tool: Get trending/rising resources
|
|
160
|
+
server.tool(
|
|
161
|
+
"trending",
|
|
162
|
+
"Get trending and rising Claude Code resources from Vibe Index",
|
|
163
|
+
{
|
|
164
|
+
period: z
|
|
165
|
+
.enum(["day", "week", "month"])
|
|
166
|
+
.optional()
|
|
167
|
+
.describe("Time period for trending (default: week)"),
|
|
168
|
+
limit: z
|
|
169
|
+
.number()
|
|
170
|
+
.min(1)
|
|
171
|
+
.max(20)
|
|
172
|
+
.optional()
|
|
173
|
+
.describe("Number of results (default: 10)"),
|
|
174
|
+
},
|
|
175
|
+
async ({ period = "week", limit = 10 }) => {
|
|
176
|
+
try {
|
|
177
|
+
const data = await fetchAPI<TrendingResponse>(
|
|
178
|
+
`/rising-stars?period=${period}&limit=${limit}`
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
if (!data.rising || data.rising.length === 0) {
|
|
182
|
+
return {
|
|
183
|
+
content: [
|
|
184
|
+
{
|
|
185
|
+
type: "text" as const,
|
|
186
|
+
text: `No trending resources found for the past ${period}. Check https://vibeindex.ai for the latest.`,
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const periodLabel = { day: "24 hours", week: "week", month: "month" }[period];
|
|
193
|
+
const results = data.rising
|
|
194
|
+
.map((r, i) => {
|
|
195
|
+
const base = formatResource(r, true);
|
|
196
|
+
return `**#${i + 1}** (+${r.stars_today} stars this ${period})\n${base}`;
|
|
197
|
+
})
|
|
198
|
+
.join("\n\n---\n\n");
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
content: [
|
|
202
|
+
{
|
|
203
|
+
type: "text" as const,
|
|
204
|
+
text: `## 🔥 Trending Resources (Past ${periodLabel})\n\n${results}\n\n---\nSee full rankings at https://vibeindex.ai`,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
} catch (error) {
|
|
209
|
+
return {
|
|
210
|
+
content: [
|
|
211
|
+
{
|
|
212
|
+
type: "text" as const,
|
|
213
|
+
text: `Error fetching trending: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
isError: true,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// Tool: Get top resources by stars
|
|
223
|
+
server.tool(
|
|
224
|
+
"top",
|
|
225
|
+
"Get top Claude Code resources by stars",
|
|
226
|
+
{
|
|
227
|
+
type: z
|
|
228
|
+
.enum(["all", "skill", "plugin", "mcp", "marketplace"])
|
|
229
|
+
.optional()
|
|
230
|
+
.describe("Filter by resource type (default: all)"),
|
|
231
|
+
limit: z
|
|
232
|
+
.number()
|
|
233
|
+
.min(1)
|
|
234
|
+
.max(20)
|
|
235
|
+
.optional()
|
|
236
|
+
.describe("Number of results (default: 10)"),
|
|
237
|
+
},
|
|
238
|
+
async ({ type = "all", limit = 10 }) => {
|
|
239
|
+
try {
|
|
240
|
+
const typeFilter = type !== "all" ? `&type=${type}` : "";
|
|
241
|
+
const data = await fetchAPI<SearchResponse>(
|
|
242
|
+
`/resources?sort=stars${typeFilter}&pageSize=${limit}`
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
if (data.data.length === 0) {
|
|
246
|
+
return {
|
|
247
|
+
content: [
|
|
248
|
+
{
|
|
249
|
+
type: "text" as const,
|
|
250
|
+
text: `No resources found. Check https://vibeindex.ai`,
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const typeLabel = type === "all" ? "Resources" : `${type}s`;
|
|
257
|
+
const results = data.data
|
|
258
|
+
.map((r, i) => `**#${i + 1}**\n${formatResource(r, true)}`)
|
|
259
|
+
.join("\n\n---\n\n");
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
content: [
|
|
263
|
+
{
|
|
264
|
+
type: "text" as const,
|
|
265
|
+
text: `## ⭐ Top ${typeLabel} by Stars\n\n${results}\n\n---\nBrowse all at https://vibeindex.ai/browse?sort=stars${type !== "all" ? `&type=${type}` : ""}`,
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
};
|
|
269
|
+
} catch (error) {
|
|
270
|
+
return {
|
|
271
|
+
content: [
|
|
272
|
+
{
|
|
273
|
+
type: "text" as const,
|
|
274
|
+
text: `Error fetching top resources: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
isError: true,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
// Tool: Get install command
|
|
284
|
+
server.tool(
|
|
285
|
+
"install",
|
|
286
|
+
"Get the install command for a Claude Code resource",
|
|
287
|
+
{
|
|
288
|
+
name: z.string().describe("Resource name to get install command for"),
|
|
289
|
+
type: z
|
|
290
|
+
.enum(["skill", "plugin", "mcp", "marketplace"])
|
|
291
|
+
.optional()
|
|
292
|
+
.describe("Resource type (helps find exact match)"),
|
|
293
|
+
},
|
|
294
|
+
async ({ name, type }) => {
|
|
295
|
+
try {
|
|
296
|
+
const typeFilter = type ? `&type=${type}` : "";
|
|
297
|
+
const data = await fetchAPI<SearchResponse>(
|
|
298
|
+
`/resources?search=${encodeURIComponent(name)}${typeFilter}&pageSize=5`
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
// Find exact or close match
|
|
302
|
+
const exactMatch = data.data.find(
|
|
303
|
+
(r) => r.name.toLowerCase() === name.toLowerCase()
|
|
304
|
+
);
|
|
305
|
+
const resource = exactMatch || data.data[0];
|
|
306
|
+
|
|
307
|
+
if (!resource) {
|
|
308
|
+
return {
|
|
309
|
+
content: [
|
|
310
|
+
{
|
|
311
|
+
type: "text" as const,
|
|
312
|
+
text: `Resource "${name}" not found. Try searching with: search "${name}"`,
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const installCmd = getInstallCommand(resource);
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
content: [
|
|
322
|
+
{
|
|
323
|
+
type: "text" as const,
|
|
324
|
+
text: `## Install ${resource.name}\n\n**Type:** ${resource.resource_type}\n**Repository:** ${resource.github_owner}/${resource.github_repo}\n\n**Install Command:**\n\`\`\`bash\n${installCmd}\n\`\`\`\n\n**More info:** https://vibeindex.ai/${resource.resource_type}s/${resource.github_owner}/${resource.github_repo}/${resource.name}`,
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
};
|
|
328
|
+
} catch (error) {
|
|
329
|
+
return {
|
|
330
|
+
content: [
|
|
331
|
+
{
|
|
332
|
+
type: "text" as const,
|
|
333
|
+
text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
isError: true,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
// Tool: Browse categories
|
|
343
|
+
server.tool(
|
|
344
|
+
"categories",
|
|
345
|
+
"List available categories and tags for Claude Code resources",
|
|
346
|
+
{},
|
|
347
|
+
async () => {
|
|
348
|
+
const categories = {
|
|
349
|
+
"Resource Types": [
|
|
350
|
+
"🎯 Skills - Reusable agent capabilities",
|
|
351
|
+
"🔌 Plugins - Extend Claude Code functionality",
|
|
352
|
+
"🔗 MCP Servers - Model Context Protocol integrations",
|
|
353
|
+
"🏪 Marketplaces - Collections of plugins/skills",
|
|
354
|
+
],
|
|
355
|
+
"Popular Tags": [
|
|
356
|
+
"git, github - Version control",
|
|
357
|
+
"database, sql, postgres - Data management",
|
|
358
|
+
"docker, kubernetes - Containers & orchestration",
|
|
359
|
+
"aws, gcp, azure - Cloud platforms",
|
|
360
|
+
"react, vue, nextjs - Frontend frameworks",
|
|
361
|
+
"python, typescript, rust - Programming languages",
|
|
362
|
+
"testing, lint, ci-cd - Development tools",
|
|
363
|
+
"ai, llm, openai - AI/ML tools",
|
|
364
|
+
],
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const text = Object.entries(categories)
|
|
368
|
+
.map(([cat, items]) => `### ${cat}\n${items.map((i) => `- ${i}`).join("\n")}`)
|
|
369
|
+
.join("\n\n");
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
content: [
|
|
373
|
+
{
|
|
374
|
+
type: "text" as const,
|
|
375
|
+
text: `## Vibe Index Categories\n\n${text}\n\n---\nUse \`search <keyword>\` to find resources in any category.\nBrowse all at https://vibeindex.ai/explore`,
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
// Start server
|
|
383
|
+
async function main() {
|
|
384
|
+
const transport = new StdioServerTransport();
|
|
385
|
+
await server.connect(transport);
|
|
386
|
+
console.error("Vibe Index MCP Server running on stdio");
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
main().catch((error) => {
|
|
390
|
+
console.error("Fatal error:", error);
|
|
391
|
+
process.exit(1);
|
|
392
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|