UNPKG

cnb-mcp-server

Version:

MCP Server for the cnb API, enabling file operations, repository management, search functionality, and more.

458 lines (457 loc) 18 kB
/** * 仓库相关操作 */ import { z } from 'zod'; import { makeRequest, getQueryParams } from '../common/utils.js'; import { RepositorySchema } from '../common/types.js'; // 搜索仓库参数Schema export const SearchRepositoriesSchema = z.object({ query: z.string().describe('搜索关键字'), page: z.number().default(1).describe('页码,默认为1'), perPage: z.number().default(10).describe('每页数量,默认为10') }); // 创建仓库参数Schema export const CreateRepositoryOptionsSchema = z.object({ name: z.string().describe('仓库名称'), description: z.string().optional().describe('仓库描述'), visibility: z.enum(['public', 'private', 'secret']).default('public').describe('仓库可见性,默认为public'), license: z.string().optional().describe('许可证'), group: z.string().optional().describe('组织路径') }); // Fork仓库参数Schema export const ForkRepositorySchema = z.object({ owner: z.string().describe('原仓库拥有者'), repo: z.string().describe('原仓库名称'), group: z.string().optional().describe('要Fork到的组织名称'), branch: z.string().optional().describe('要Fork的分支,默认为仓库默认分支'), name: z.string().optional().describe('新仓库名称,默认与原仓库相同'), description: z.string().optional().describe('新仓库描述') }); /** * 搜索仓库 */ export async function searchRepositories(query, page = 1, perPage = 10, orderBy = 'stars', desc = true, accessToken) { try { // 构造完整URL和请求头 const params = getQueryParams({ key: query, page, page_size: perPage, order_by: orderBy, desc }); const url = `https://cnb.cool/search/public-repos${params}`; const headers = { 'Accept': 'application/vnd.cnb.web+json', 'User-Agent': 'CNB-MCP-Server/1.0.0', 'Host': 'cnb.cool', 'Connection': 'keep-alive' }; console.log('搜索仓库请求URL:', url); // 直接使用fetch发送请求 const response = await fetch(url, { method: 'GET', headers }); console.log('搜索仓库响应状态:', response.status); if (response.ok) { const result = await response.json(); console.log(`搜索到 ${result.items?.length || 0} 个仓库`); return result; } else { // 处理错误 const errorText = await response.text(); console.log('搜索仓库错误:', errorText); throw new Error(`搜索仓库失败: ${response.status} ${errorText}`); } } catch (error) { console.log('搜索仓库请求错误:', error instanceof Error ? error.message : String(error)); throw error; } } /** * 获取用户所在的组织列表 */ export async function getUserGroups(page = 1, pageSize = 10, role = 'Guest', accessToken) { const params = getQueryParams({ page, page_size: pageSize, role }); const response = await makeRequest(`/user/groups${params}`, { headers: { 'accept': 'application/json', 'Authorization': accessToken || process.env.CNB_ACCESS_TOKEN || '' } }, accessToken); // 直接将结果解析为Group类型数组 return Array.isArray(response) ? response : []; } /** * 获取当前用户的仓库列表 */ export async function getCurrentUserRepositories(page = 1, pageSize = 10, desc = false, accessToken) { try { // 构造完整URL和请求头 const token = accessToken || process.env.CNB_ACCESS_TOKEN || ''; const params = getQueryParams({ page, page_size: pageSize, desc }); const url = `https://api.cnb.cool/user/repos${params}`; const headers = { 'accept': 'application/json', 'Authorization': token }; console.log('请求URL:', url); // 直接使用fetch发送请求 const response = await fetch(url, { method: 'GET', headers }); console.log('响应状态:', response.status); if (response.ok) { const result = await response.json(); return result; } else { // 处理错误 const errorText = await response.text(); console.log('错误响应:', errorText); throw new Error(`Failed to get user repositories: ${response.status} ${errorText}`); } } catch (error) { console.log('获取用户仓库列表错误:', error instanceof Error ? error.message : String(error)); throw error; } } /** * 检查仓库是否存在于当前用户的仓库列表中 */ export async function checkRepositoryExists(name, accessToken) { try { // 获取用户仓库列表 const repos = await getCurrentUserRepositories(1, 50, false, accessToken); // 检查是否存在同名仓库 const exists = repos.some((repo) => repo.name === name); console.log(`仓库 ${name} ${exists ? '已存在' : '不存在'}`); return exists; } catch (error) { console.log('检查仓库存在性错误:', error instanceof Error ? error.message : String(error)); return false; // 出错时默认不存在 } } /** * 获取指定仓库的详细信息 */ export async function getRepository(owner, repo, accessToken) { try { // 构造完整URL和请求头 const token = accessToken || process.env.CNB_ACCESS_TOKEN || ''; const url = `https://api.cnb.cool/${owner}/${repo}`; const headers = { 'accept': 'application/json', 'Authorization': token }; console.log('请求URL:', url); // 直接使用fetch发送请求 const response = await fetch(url, { method: 'GET', headers }); console.log('响应状态:', response.status); if (response.ok) { const result = await response.json(); return result; } else { // 处理错误 const errorText = await response.text(); console.log('错误响应:', errorText); throw new Error(`Failed to get repository info: ${response.status} ${errorText}`); } } catch (error) { console.log('获取仓库信息错误:', error instanceof Error ? error.message : String(error)); throw error; } } /** * 创建仓库 */ export async function createRepository(options) { try { // 输出调试信息 console.log('创建仓库参数:', JSON.stringify(options)); // 如果没有指定组织,则获取默认组织 const group = options.group || (await getUserGroups(1, 10, 'Guest'))[0]?.path; if (!group) { throw new Error('未指定组织且无法获取默认组织'); } console.log('使用组织:', group); // 先检查仓库是否已存在 const exists = await checkRepositoryExists(options.name); if (exists) { console.log(`仓库 ${options.name} 已存在,直接获取信息`); // 如果已存在,直接返回仓库信息 const repoInfo = await getRepository(group, options.name); return { id: String(repoInfo.id), name: repoInfo.name, full_name: repoInfo.path, owner: { id: '', username: repoInfo.last_update_username || '', nickname: repoInfo.last_update_nickname || '' } }; } // 准备请求体 const body = { name: options.name, description: options.description || '', visibility: options.visibility || 'public', license: options.license || '' }; console.log('请求体:', JSON.stringify(body)); // 构造完整URL和请求头 const token = process.env.CNB_ACCESS_TOKEN || ''; const url = `https://api.cnb.cool/${group}/-/repos`; const headers = { 'accept': 'application/json', 'Authorization': token, 'Content-Type': 'application/json' }; console.log('请求URL:', url); // 直接使用fetch发送请求 try { const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(body) }); console.log('响应状态:', response.status); if (response.status === 201) { // 创建成功,获取仓库信息 console.log('仓库创建成功,正在获取仓库信息'); try { // 创建成功后,获取仓库详细信息 const repoInfo = await getRepository(group, options.name); return { id: String(repoInfo.id), name: repoInfo.name, full_name: repoInfo.path, owner: { id: '', username: repoInfo.last_update_username || '', nickname: repoInfo.last_update_nickname || '' } }; } catch (infoError) { console.log('获取新建仓库信息失败,返回基本信息:', infoError instanceof Error ? infoError.message : String(infoError)); // 如果获取仓库信息失败,返回基本信息 return { id: '', name: options.name, full_name: `${group}/${options.name}`, owner: { id: '', username: '', nickname: '' }, }; } } else if (response.status === 409) { // 冲突,仓库可能已存在 console.log('仓库已存在,正在获取仓库信息'); const repoInfo = await getRepository(group, options.name); return { id: String(repoInfo.id), name: repoInfo.name, full_name: repoInfo.path, owner: { id: '', username: repoInfo.last_update_username || '', nickname: repoInfo.last_update_nickname || '' } }; } else { // 其他错误 const errorText = await response.text(); console.log('错误响应:', errorText); throw new Error(`Failed to create repository: ${response.status} ${errorText}`); } } catch (error) { console.log('请求错误:', error instanceof Error ? error.message : String(error)); throw error; } } catch (error) { console.log('createRepository错误:', error instanceof Error ? error.message : String(error)); throw error; } } /** * Fork仓库 */ export async function forkRepository(owner, repo, organization, branch, name, description, accessToken) { try { // 首先获取原仓库信息 console.log(`获取原仓库信息: ${owner}/${repo}`); const originalRepo = await getRepository(owner, repo, accessToken); console.log(`原仓库信息获取成功:`, originalRepo); // 构造完整URL和请求头 const token = accessToken || process.env.CNB_ACCESS_TOKEN || ''; const url = `https://api.cnb.cool/${owner}/${repo}/-/forks`; const headers = { 'accept': 'application/json', 'Authorization': token, 'Content-Type': 'application/json' }; // 准备请求体,使用原仓库信息作为默认值 const body = {}; // 如果用户没有指定组织,获取用户的第一个组织 if (organization) { body.group = organization; } else { console.log('未指定组织,尝试获取用户的默认组织'); try { const userGroups = await getUserGroups(1, 10, 'Guest', accessToken); if (userGroups && userGroups.length > 0) { body.group = userGroups[0].path; console.log(`使用默认组织: ${body.group}`); } else { console.log('用户没有可用的组织'); throw new Error('用户没有可用的组织,无法执行Fork操作'); } } catch (groupError) { console.log('获取用户组织失败:', groupError instanceof Error ? groupError.message : String(groupError)); throw new Error('获取用户组织失败,请明确指定要Fork到的组织'); } } // 如果用户没有指定分支,使用原仓库的默认分支 if (branch) { body.branch = branch; } else if (originalRepo.default_branch) { body.branch = originalRepo.default_branch; } // 如果用户没有指定名称,使用原仓库的名称 if (name) { body.name = name; } else { body.name = originalRepo.name; } // 如果用户没有指定描述,使用原仓库的描述 if (description) { body.description = description; } else if (originalRepo.description) { body.description = originalRepo.description; } console.log(`Fork仓库请求URL: ${url}`); console.log(`Fork请求体: ${JSON.stringify(body)}`); // 发送请求 const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(body) }); console.log(`Fork仓库响应状态: ${response.status}`); if (response.status === 201) { // fork成功,可能没有返回内容,或者返回了新仓库信息 try { // 尝试获取响应体文本 const responseText = await response.text(); // 检查响应体是否为空 console.log('Fork仓库成功,但响应体为空'); // 创建返回对象,包含必要的成功信息 const newRepo = body.name || repo; const newOwner = body.group || owner; const successResult = { id: '', name: newRepo, full_name: `${newOwner}/${newRepo}`, owner: { id: '', username: '', nickname: '' }, message: 'Fork操作成功', status: 'success', fork_target: `${owner}/${repo}`, fork_result: `${newOwner}/${newRepo}` }; console.log('Fork仓库成功,返回信息:', successResult); // 尝试获取新仓库的详细信息 try { const repoInfo = await getRepository(newOwner, newRepo, accessToken); // 合并仓库信息到结果中 return { ...successResult, id: String(repoInfo.id), description: repoInfo.description || '', visibility: repoInfo.visibility_level || '' }; } catch (infoError) { console.log('获取Fork后仓库信息失败,返回基本成功信息:', infoError instanceof Error ? infoError.message : String(infoError)); return successResult; } } catch (e) { // 如果获取响应内容失败,返回基本成功信息 console.log('处理Fork响应时发生错误:', e instanceof Error ? e.message : String(e)); const newRepo = body.name || repo; const newOwner = body.group || owner; return { id: '', name: newRepo, full_name: `${newOwner}/${newRepo}`, owner: { id: '', username: '', nickname: '' }, message: 'Fork操作成功', status: 'success', fork_target: `${owner}/${repo}`, fork_result: `${newOwner}/${newRepo}` }; } } else { // 处理错误 const errorText = await response.text(); console.log('Fork仓库错误:', errorText); throw new Error(`Fork仓库失败: ${response.status} ${errorText}`); } } catch (error) { console.log('Fork仓库请求错误:', error instanceof Error ? error.message : String(error)); throw error; } } /** * 获取用户的仓库列表 */ export async function getUserRepositories(username, page = 1, perPage = 10, filter_type, order_by, desc = false, accessToken) { const params = getQueryParams({ page, page_size: perPage, filter_type, order_by, desc }); const response = await makeRequest(`/${username}/-/repos${params}`, {}, accessToken); return response.map((repo) => RepositorySchema.parse(repo)); }