@npm_xiyuan/mcp-github-trending 1.1.1

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/index.js ADDED
@@ -0,0 +1,370 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import fetch from 'node-fetch';
6
+ import * as cheerio from 'cheerio';
7
+ import { fetchRepoReadme, fetchRepoFile, fetchRepoStructure, fetchRepoCommits, analyzeRepo } from './github-api.js';
8
+ async function fetchTrending(since = 'weekly', language, limit = 10) {
9
+ let url = 'https://github.com/trending';
10
+ if (language) {
11
+ url += `/${language}`;
12
+ }
13
+ const response = await fetch(`${url}?since=${since}`);
14
+ const html = await response.text();
15
+ const $ = cheerio.load(html);
16
+ const repos = [];
17
+ $('article.Box-row').each((i, element) => {
18
+ if (i >= limit)
19
+ return false;
20
+ const $article = $(element);
21
+ const $repoLink = $article.find('h2 a');
22
+ const $desc = $article.find('p');
23
+ const $stars = $article.find('span.d-inline-block.float-sm-right');
24
+ const href = $repoLink.attr('href');
25
+ if (href) {
26
+ repos.push({
27
+ name: $repoLink.text().trim().replace(/\s+/g, ''),
28
+ url: `https://github.com${href}`,
29
+ description: $desc.text().trim(),
30
+ stars: $stars.text().trim()
31
+ });
32
+ }
33
+ });
34
+ return repos;
35
+ }
36
+ async function fetchTrendingDevelopers(since = 'weekly', language, limit = 10) {
37
+ let url = 'https://github.com/trending/developers';
38
+ if (language) {
39
+ url += `/${language}`;
40
+ }
41
+ const response = await fetch(`${url}?since=${since}`);
42
+ const html = await response.text();
43
+ const $ = cheerio.load(html);
44
+ const developers = [];
45
+ $('article.Box-row').each((i, element) => {
46
+ if (i >= limit)
47
+ return false;
48
+ const $article = $(element);
49
+ const $userLink = $article.find('h1 a');
50
+ const $avatar = $article.find('img');
51
+ const $repoLink = $article.find('article a');
52
+ const href = $userLink.attr('href');
53
+ if (href) {
54
+ developers.push({
55
+ username: href.replace('/', ''),
56
+ name: $userLink.text().trim(),
57
+ url: `https://github.com${href}`,
58
+ avatar: $avatar.attr('src'),
59
+ popularRepo: $repoLink.length ? {
60
+ name: $repoLink.text().trim(),
61
+ url: `https://github.com${$repoLink.attr('href')}`,
62
+ description: $article.find('article div').first().text().trim()
63
+ } : undefined
64
+ });
65
+ }
66
+ });
67
+ return developers;
68
+ }
69
+ async function fetchRepoDetails(owner, repo) {
70
+ const url = `https://github.com/${owner}/${repo}`;
71
+ const response = await fetch(url);
72
+ const html = await response.text();
73
+ const $ = cheerio.load(html);
74
+ const parseNumber = (text) => {
75
+ const cleaned = text.replace(/,/g, '').replace('k', '000');
76
+ return parseFloat(cleaned) || 0;
77
+ };
78
+ return {
79
+ fullName: `${owner}/${repo}`,
80
+ stars: parseNumber($('span#repo-stars-counter-star').text().trim()),
81
+ forks: parseNumber($('[href$="/forks"] strong').text().trim()),
82
+ watchers: parseNumber($('[href$="/watchers"] strong').text().trim()),
83
+ openIssues: parseNumber($('[data-testid="issue-link"] .Counter').text().trim()),
84
+ language: $('span.color-fg-default.text-bold.mr-1').first().text().trim() || 'Unknown',
85
+ license: $('[data-testid="license-link"]').text().trim() || undefined,
86
+ topics: $('a.topic-tag').map((_, el) => $(el).text().trim()).get(),
87
+ createdAt: $('relative-time').first().attr('datetime') || '',
88
+ updatedAt: $('relative-time').last().attr('datetime') || ''
89
+ };
90
+ }
91
+ // 创建MCP服务器
92
+ const server = new Server({
93
+ name: 'github-trending',
94
+ version: '1.0.0',
95
+ }, {
96
+ capabilities: {
97
+ tools: {},
98
+ },
99
+ });
100
+ // 注册工具列表
101
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
102
+ return {
103
+ tools: [
104
+ {
105
+ name: 'get_trending_repos',
106
+ description: '获取GitHub trending仓库列表',
107
+ inputSchema: {
108
+ type: 'object',
109
+ properties: {
110
+ since: {
111
+ type: 'string',
112
+ enum: ['daily', 'weekly', 'monthly'],
113
+ description: '时间范围',
114
+ default: 'weekly'
115
+ },
116
+ language: {
117
+ type: 'string',
118
+ description: '编程语言(可选)'
119
+ },
120
+ limit: {
121
+ type: 'number',
122
+ description: '返回数量',
123
+ default: 10
124
+ }
125
+ }
126
+ }
127
+ },
128
+ {
129
+ name: 'get_trending_developers',
130
+ description: '获取GitHub trending开发者列表',
131
+ inputSchema: {
132
+ type: 'object',
133
+ properties: {
134
+ since: {
135
+ type: 'string',
136
+ enum: ['daily', 'weekly', 'monthly'],
137
+ description: '时间范围',
138
+ default: 'weekly'
139
+ },
140
+ language: {
141
+ type: 'string',
142
+ description: '编程语言(可选)'
143
+ },
144
+ limit: {
145
+ type: 'number',
146
+ description: '返回数量',
147
+ default: 10
148
+ }
149
+ }
150
+ }
151
+ },
152
+ {
153
+ name: 'search_trending',
154
+ description: '在trending列表中搜索关键词',
155
+ inputSchema: {
156
+ type: 'object',
157
+ properties: {
158
+ keyword: {
159
+ type: 'string',
160
+ description: '搜索关键词'
161
+ },
162
+ since: {
163
+ type: 'string',
164
+ enum: ['daily', 'weekly', 'monthly'],
165
+ description: '时间范围',
166
+ default: 'weekly'
167
+ },
168
+ language: {
169
+ type: 'string',
170
+ description: '编程语言(可选)'
171
+ }
172
+ },
173
+ required: ['keyword']
174
+ }
175
+ },
176
+ {
177
+ name: 'get_repo_details',
178
+ description: '获取仓库详细信息',
179
+ inputSchema: {
180
+ type: 'object',
181
+ properties: {
182
+ owner: {
183
+ type: 'string',
184
+ description: '仓库所有者'
185
+ },
186
+ repo: {
187
+ type: 'string',
188
+ description: '仓库名称'
189
+ }
190
+ },
191
+ required: ['owner', 'repo']
192
+ }
193
+ },
194
+ {
195
+ name: 'analyze_repo',
196
+ description: '分析GitHub项目,获取README、依赖、结构和最近提交的完整报告',
197
+ inputSchema: {
198
+ type: 'object',
199
+ properties: {
200
+ owner: { type: 'string', description: '仓库所有者' },
201
+ repo: { type: 'string', description: '仓库名称' }
202
+ },
203
+ required: ['owner', 'repo']
204
+ }
205
+ },
206
+ {
207
+ name: 'get_repo_readme',
208
+ description: '获取GitHub仓库的README.md原始内容',
209
+ inputSchema: {
210
+ type: 'object',
211
+ properties: {
212
+ owner: { type: 'string', description: '仓库所有者' },
213
+ repo: { type: 'string', description: '仓库名称' }
214
+ },
215
+ required: ['owner', 'repo']
216
+ }
217
+ },
218
+ {
219
+ name: 'get_repo_file',
220
+ description: '获取GitHub仓库中指定文件的内容',
221
+ inputSchema: {
222
+ type: 'object',
223
+ properties: {
224
+ owner: { type: 'string', description: '仓库所有者' },
225
+ repo: { type: 'string', description: '仓库名称' },
226
+ path: { type: 'string', description: '文件路径' }
227
+ },
228
+ required: ['owner', 'repo', 'path']
229
+ }
230
+ },
231
+ {
232
+ name: 'get_repo_structure',
233
+ description: '获取GitHub仓库的目录结构',
234
+ inputSchema: {
235
+ type: 'object',
236
+ properties: {
237
+ owner: { type: 'string', description: '仓库所有者' },
238
+ repo: { type: 'string', description: '仓库名称' },
239
+ path: { type: 'string', description: '目录路径(可选)', default: '' }
240
+ },
241
+ required: ['owner', 'repo']
242
+ }
243
+ },
244
+ {
245
+ name: 'get_repo_commits',
246
+ description: '获取GitHub仓库的最近提交历史',
247
+ inputSchema: {
248
+ type: 'object',
249
+ properties: {
250
+ owner: { type: 'string', description: '仓库所有者' },
251
+ repo: { type: 'string', description: '仓库名称' },
252
+ limit: { type: 'number', description: '返回数量', default: 10 }
253
+ },
254
+ required: ['owner', 'repo']
255
+ }
256
+ }
257
+ ]
258
+ };
259
+ });
260
+ // 处理工具调用
261
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
262
+ if (request.params.name === 'get_trending_repos') {
263
+ const { since = 'weekly', language, limit = 10 } = request.params.arguments;
264
+ const repos = await fetchTrending(since, language, limit);
265
+ return {
266
+ content: [
267
+ {
268
+ type: 'text',
269
+ text: JSON.stringify(repos, null, 2)
270
+ }
271
+ ]
272
+ };
273
+ }
274
+ if (request.params.name === 'get_trending_developers') {
275
+ const { since = 'weekly', language, limit = 10 } = request.params.arguments;
276
+ const developers = await fetchTrendingDevelopers(since, language, limit);
277
+ return {
278
+ content: [
279
+ {
280
+ type: 'text',
281
+ text: JSON.stringify(developers, null, 2)
282
+ }
283
+ ]
284
+ };
285
+ }
286
+ if (request.params.name === 'search_trending') {
287
+ const { keyword, since = 'weekly', language } = request.params.arguments;
288
+ const repos = await fetchTrending(since, language, 50);
289
+ const filtered = repos.filter(repo => repo.name.toLowerCase().includes(keyword.toLowerCase()) ||
290
+ repo.description.toLowerCase().includes(keyword.toLowerCase()));
291
+ return {
292
+ content: [
293
+ {
294
+ type: 'text',
295
+ text: JSON.stringify(filtered, null, 2)
296
+ }
297
+ ]
298
+ };
299
+ }
300
+ if (request.params.name === 'get_repo_details') {
301
+ const { owner, repo } = request.params.arguments;
302
+ const details = await fetchRepoDetails(owner, repo);
303
+ return {
304
+ content: [
305
+ {
306
+ type: 'text',
307
+ text: JSON.stringify(details, null, 2)
308
+ }
309
+ ]
310
+ };
311
+ }
312
+ if (request.params.name === 'analyze_repo') {
313
+ const { owner, repo } = request.params.arguments;
314
+ try {
315
+ const result = await analyzeRepo(owner, repo, fetchRepoDetails);
316
+ return {
317
+ content: [
318
+ {
319
+ type: 'text',
320
+ text: JSON.stringify(result, null, 2)
321
+ }
322
+ ]
323
+ };
324
+ }
325
+ catch (error) {
326
+ return {
327
+ content: [
328
+ {
329
+ type: 'text',
330
+ text: JSON.stringify({
331
+ error: error.message,
332
+ hint: '请检查仓库名称是否正确,或稍后重试'
333
+ }, null, 2)
334
+ }
335
+ ]
336
+ };
337
+ }
338
+ }
339
+ if (request.params.name === 'get_repo_readme') {
340
+ const { owner, repo } = request.params.arguments;
341
+ const readme = await fetchRepoReadme(owner, repo);
342
+ return { content: [{ type: 'text', text: readme }] };
343
+ }
344
+ if (request.params.name === 'get_repo_file') {
345
+ const { owner, repo, path } = request.params.arguments;
346
+ const content = await fetchRepoFile(owner, repo, path);
347
+ return { content: [{ type: 'text', text: content }] };
348
+ }
349
+ if (request.params.name === 'get_repo_structure') {
350
+ const { owner, repo, path = '' } = request.params.arguments;
351
+ const structure = await fetchRepoStructure(owner, repo, path);
352
+ return { content: [{ type: 'text', text: JSON.stringify(structure, null, 2) }] };
353
+ }
354
+ if (request.params.name === 'get_repo_commits') {
355
+ const { owner, repo, limit = 10 } = request.params.arguments;
356
+ const commits = await fetchRepoCommits(owner, repo, limit);
357
+ return { content: [{ type: 'text', text: JSON.stringify(commits, null, 2) }] };
358
+ }
359
+ throw new Error(`Unknown tool: ${request.params.name}`);
360
+ });
361
+ // 启动服务器
362
+ async function main() {
363
+ const transport = new StdioServerTransport();
364
+ await server.connect(transport);
365
+ console.error('GitHub Trending MCP Server running on stdio');
366
+ }
367
+ main().catch((error) => {
368
+ console.error('Server error:', error);
369
+ process.exit(1);
370
+ });
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@npm_xiyuan/mcp-github-trending",
3
+ "version": "1.1.1",
4
+ "description": "MCP server for GitHub trending repositories and developers",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "mcp-github-trending": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "github",
17
+ "trending",
18
+ "model-context-protocol",
19
+ "claude",
20
+ "ai"
21
+ ],
22
+ "author": "",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/jiyi1990118/mcp-github-trending.git"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md",
31
+ "README_CN.md",
32
+ "LICENSE"
33
+ ],
34
+ "engines": {
35
+ "node": ">=18.0.0"
36
+ },
37
+ "dependencies": {
38
+ "@modelcontextprotocol/sdk": "^1.0.0",
39
+ "cheerio": "^1.0.0",
40
+ "node-fetch": "^3.3.2"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^20.0.0",
44
+ "typescript": "^5.3.0"
45
+ }
46
+ }