Skip to main content
Glama

MCP Toolkit

by zxfgds
index.ts19 kB
import { ToolDefinition, ToolResponse } from '../types.js'; import { Config } from '../../config/config.js'; import { HttpsProxyAgent } from 'https-proxy-agent'; import fetch from 'node-fetch'; import * as fs from 'fs'; import * as path from 'path'; // GitHub API 类型定义 interface GitHubContentItem { type: 'file' | 'dir'; name: string; path: string; content?: string; } interface GitHubTreeItem { type: 'tree' | 'blob'; path: string; } interface GitHubTreeResponse { tree: GitHubTreeItem[]; } interface GitHubRepoSearchItem { full_name: string; stargazers_count: number; description: string | null; } interface GitHubRepoSearchResponse { items: GitHubRepoSearchItem[]; } interface GitHubCodeSearchItem { repository: { full_name: string; }; path: string; score: number; } interface GitHubCodeSearchResponse { items: GitHubCodeSearchItem[]; } interface GitHubRepo { name: string; description: string | null; private: boolean; default_branch: string; } interface CreateRepoParams { name: string; description?: string; private?: boolean; auto_init?: boolean; } interface UpdateRepoParams { name: string; description?: string; private?: boolean; default_branch?: string; } // GitHub 管理器类 class GitHubManager { private baseUrl: string = "https://api.github.com"; private downloadDir: string; constructor( private readonly config: Config ) { // 使用当前目录下的 github 目录存储下载的文件 this.downloadDir = path.join(process.cwd(), 'github'); // 确保下载目录存在 if (!fs.existsSync(this.downloadDir)) { fs.mkdirSync(this.downloadDir, { recursive: true }); } } private getHeaders() { return { 'Authorization': `token ${this.config.github.token}`, 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'project-tools' }; } private async fetchWithProxy<T>(url: string, options: any = {}): Promise<T> { const proxy = this.config.network.proxy; const fetchOptions = { ...options, headers: this.getHeaders(), }; if (proxy) { fetchOptions.agent = new HttpsProxyAgent(proxy); } const response = await fetch(url, fetchOptions); if (!response.ok) { const error = await response.text(); throw new Error(`GitHub API error: ${response.status} - ${error}`); } return await response.json() as Promise<T>; } private validateToken(): void { if (!this.config.github.token) { throw new Error('请先在配置文件中设置有效的 GitHub Token'); } } // 列出仓库内容 async listContents(args: { owner: string; repo: string; path?: string; branch?: string }): Promise<ToolResponse> { try { this.validateToken(); const branch = args.branch || 'main'; let url = `${this.baseUrl}/repos/${args.owner}/${args.repo}/contents`; if (args.path) { url += `/${args.path}`; } url += `?ref=${branch}`; const data = await this.fetchWithProxy<GitHubContentItem | GitHubContentItem[]>(url); const items = Array.isArray(data) ? data : [data]; const formattedText = items.map(item => `${item.type === 'dir' ? '[目录]' : '[文件]'} ${item.path}` ).join('\n'); return { content: [{ type: 'text', text: formattedText || '仓库为空' }] }; } catch (error) { if (error instanceof Error && error.message.includes('404')) { return { content: [{ type: 'text', text: `仓库 ${args.owner}/${args.repo} 不存在或无访问权限` }], isError: true }; } return { content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }], isError: true }; } } // 获取仓库树结构 async getTree(args: { owner: string; repo: string; branch?: string }): Promise<ToolResponse> { try { this.validateToken(); const branch = args.branch || "main"; const url = `${this.baseUrl}/repos/${args.owner}/${args.repo}/git/trees/${branch}?recursive=1`; const data = await this.fetchWithProxy<GitHubTreeResponse>(url); const formattedText = data.tree.map(item => `${item.type === 'tree' ? '[目录]' : '[文件]'} ${item.path}` ).join('\n'); return { content: [{ type: 'text', text: formattedText || '仓库为空' }] }; } catch (error) { if (error instanceof Error && error.message.includes('404')) { return { content: [{ type: 'text', text: `仓库 ${args.owner}/${args.repo} 不存在或无访问权限` }], isError: true }; } return { content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }], isError: true }; } } // 搜索仓库 async searchRepos(args: { query: string }): Promise<ToolResponse> { try { this.validateToken(); const url = `${this.baseUrl}/search/repositories?q=${encodeURIComponent(args.query)}`; const data = await this.fetchWithProxy<GitHubRepoSearchResponse>(url); if (data.items.length === 0) { return { content: [{ type: 'text', text: '未找到匹配的仓库' }] }; } const formattedText = data.items.slice(0, 10).map(repo => `${repo.full_name} (★${repo.stargazers_count})\n${repo.description || '无描述'}` ).join('\n\n'); return { content: [{ type: 'text', text: formattedText }] }; } catch (error) { return { content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }], isError: true }; } } // 搜索代码 async searchCode(args: { query: string }): Promise<ToolResponse> { try { this.validateToken(); const url = `${this.baseUrl}/search/code?q=${encodeURIComponent(args.query)}`; const data = await this.fetchWithProxy<GitHubCodeSearchResponse>(url); if (data.items.length === 0) { return { content: [{ type: 'text', text: '未找到匹配的代码' }] }; } const formattedText = data.items.slice(0, 10).map(item => `[${item.repository.full_name}] ${item.path} (相关度: ${item.score.toFixed(2)})` ).join('\n'); return { content: [{ type: 'text', text: formattedText }] }; } catch (error) { return { content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }], isError: true }; } } // 获取文件内容 async getFileContent(args: { owner: string; repo: string; path: string; save?: boolean }): Promise<ToolResponse> { try { this.validateToken(); const url = `${this.baseUrl}/repos/${args.owner}/${args.repo}/contents/${args.path}`; const data = await this.fetchWithProxy<GitHubContentItem>(url); if (data.type !== 'file' || !data.content) { throw new Error('不是一个文件或文件为空'); } const content = Buffer.from(data.content, 'base64').toString('utf8'); // 如果需要保存文件 if (args.save) { // 确保下载目录存在 if (!fs.existsSync(this.downloadDir)) { await fs.promises.mkdir(this.downloadDir, { recursive: true }); } const savePath = path.join(this.downloadDir, `${args.owner}_${args.repo}_${path.basename(args.path)}`); await fs.promises.writeFile(savePath, content, 'utf8'); return { content: [{ type: 'text', text: `文件已保存到: ${savePath}\n\n${content}` }] }; } return { content: [{ type: 'text', text: content }] }; } catch (error) { if (error instanceof Error && error.message.includes('404')) { return { content: [{ type: 'text', text: `文件 ${args.path} 在仓库 ${args.owner}/${args.repo} 中不存在` }], isError: true }; } return { content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }], isError: true }; } } // 列出用户的仓库 async listRepos(): Promise<ToolResponse> { try { this.validateToken(); const url = `${this.baseUrl}/user/repos?type=owner`; const data = await this.fetchWithProxy<GitHubRepo[]>(url); if (data.length === 0) { return { content: [{ type: 'text', text: '没有找到任何仓库' }] }; } const formattedText = data.map(repo => `[${repo.private ? '私有' : '公开'}] ${repo.name}\n${repo.description || '无描述'}` ).join('\n\n'); return { content: [{ type: 'text', text: formattedText }] }; } catch (error) { return { content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }], isError: true }; } } // 创建新仓库 async createRepo(args: CreateRepoParams): Promise<ToolResponse> { try { this.validateToken(); const url = `${this.baseUrl}/user/repos`; const response = await fetch(url, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify(args) }); if (!response.ok) { const error = await response.text(); throw new Error(`创建仓库失败: ${response.status} - ${error}`); } const data = await response.json() as GitHubRepo; return { content: [{ type: 'text', text: `仓库创建成功: ${data.name}\n${data.description || '无描述'}` }] }; } catch (error) { return { content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }], isError: true }; } } // 更新仓库设置 async updateRepo(args: { owner: string; repo: string; settings: UpdateRepoParams }): Promise<ToolResponse> { try { this.validateToken(); const url = `${this.baseUrl}/repos/${args.owner}/${args.repo}`; const response = await fetch(url, { method: 'PATCH', headers: this.getHeaders(), body: JSON.stringify(args.settings) }); if (!response.ok) { const error = await response.text(); throw new Error(`更新仓库失败: ${response.status} - ${error}`); } const data = await response.json() as GitHubRepo; return { content: [{ type: 'text', text: `仓库更新成功: ${data.name}\n${data.description || '无描述'}` }] }; } catch (error) { return { content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }], isError: true }; } } // 删除仓库 async deleteRepo(args: { owner: string; repo: string }): Promise<ToolResponse> { try { this.validateToken(); const url = `${this.baseUrl}/repos/${args.owner}/${args.repo}`; const response = await fetch(url, { method: 'DELETE', headers: this.getHeaders() }); if (!response.ok) { const error = await response.text(); throw new Error(`删除仓库失败: ${response.status} - ${error}`); } return { content: [{ type: 'text', text: `仓库 ${args.owner}/${args.repo} 已成功删除` }] }; } catch (error) { return { content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }], isError: true }; } } // 清理资源 dispose(): void { // 不需要清理资源 } } // 创建 GitHub 工具 export function createGithubTools( config: Config ): ToolDefinition[] { const manager = new GitHubManager(config); return [ { name: 'github_ls', description: '列出 GitHub 仓库中的文件和目录。当需要浏览 GitHub 仓库内容时使用此工具。我会自动从配置文件读取 token 和代理设置。', inputSchema: { type: 'object', properties: { owner: { type: 'string', description: '仓库所有者' }, repo: { type: 'string', description: '仓库名称' }, path: { type: 'string', description: '目录路径(可选)' }, branch: { type: 'string', description: '分支名称(可选)' } }, required: ['owner', 'repo'] }, handler: args => manager.listContents(args) }, { name: 'github_tree', description: '递归显示 GitHub 仓库的完整目录树结构。当需要了解仓库的整体文件组织时使用此工具,它提供比 github_ls 更全面的视图。', inputSchema: { type: 'object', properties: { owner: { type: 'string', description: '仓库所有者(用户名或组织名)' }, repo: { type: 'string', description: '仓库名称' }, branch: { type: 'string', description: '分支名称,默认为仓库的默认分支' } }, required: ['owner', 'repo'] }, handler: args => manager.getTree(args) }, { name: 'github_search_repo', description: '搜索 GitHub 仓库。当我需要查找特定主题、功能或示例代码的仓库时,我会使用此工具。我会根据上下文自动构建合适的搜索关键词。', inputSchema: { type: 'object', properties: { query: { type: 'string', description: '搜索关键词。支持 GitHub 高级搜索语法,如: "language:javascript stars:>1000"' } }, required: ['query'] }, handler: args => manager.searchRepos(args) }, { name: 'github_search_code', description: '在 GitHub 上搜索代码。当我需要查找特定实现方式、解决方案或代码示例时,我会使用此工具。我会根据上下文自动构建精确的搜索关键词。', inputSchema: { type: 'object', properties: { query: { type: 'string', description: '搜索关键词。支持文件名、代码内容、语言等搜索,如: "filename:webpack.config.js language:javascript"' } }, required: ['query'] }, handler: args => manager.searchCode(args) }, { name: 'github_cat', description: '查看 GitHub 仓库中的文件内容。当我需要分析或参考特定文件的实现时使用此工具。', inputSchema: { type: 'object', properties: { owner: { type: 'string', description: '仓库所有者(用户名或组织名)' }, repo: { type: 'string', description: '仓库名称' }, path: { type: 'string', description: '文件路径,例如: "README.md" 或 "src/index.js"' }, save: { type: 'boolean', description: '是否保存文件到本地(可选)' } }, required: ['owner', 'repo', 'path'] }, handler: args => manager.getFileContent(args) }, { name: 'github_list_repos', description: '列出当前用户的所有仓库。用于查看和管理你的GitHub仓库。', inputSchema: { type: 'object', properties: {}, required: [] }, handler: () => manager.listRepos() }, { name: 'github_create_repo', description: '创建一个新的GitHub仓库。', inputSchema: { type: 'object', properties: { name: { type: 'string', description: '仓库名称' }, description: { type: 'string', description: '仓库描述(可选)' }, private: { type: 'boolean', description: '是否为私有仓库(可选,默认为false)' }, auto_init: { type: 'boolean', description: '是否使用README初始化仓库(可选,默认为false)' } }, required: ['name'] }, handler: args => manager.createRepo(args) }, { name: 'github_update_repo', description: '更新GitHub仓库的设置。', inputSchema: { type: 'object', properties: { owner: { type: 'string', description: '仓库所有者' }, repo: { type: 'string', description: '仓库名称' }, settings: { type: 'object', description: '要更新的设置', properties: { name: { type: 'string', description: '新的仓库名称' }, description: { type: 'string', description: '新的仓库描述' }, private: { type: 'boolean', description: '是否为私有仓库' }, default_branch: { type: 'string', description: '默认分支' } } } }, required: ['owner', 'repo', 'settings'] }, handler: args => manager.updateRepo(args) }, { name: 'github_delete_repo', description: '删除GitHub仓库。请谨慎使用此功能,删除后无法恢复!', inputSchema: { type: 'object', properties: { owner: { type: 'string', description: '仓库所有者' }, repo: { type: 'string', description: '要删除的仓库名称' } }, required: ['owner', 'repo'] }, handler: args => manager.deleteRepo(args) } ]; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zxfgds/mcp-toolkit'

If you have feedback or need assistance with the MCP directory API, please join our Discord server