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
JavaScript
/**
* 仓库相关操作
*/
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));
}