cnb-mcp-server
Version:
MCP Server for the cnb API, enabling file operations, repository management, search functionality, and more.
420 lines (358 loc) • 13.3 kB
JavaScript
/**
* CNB仓库工具测试脚本 - 根据API.txt中的接口优化
*/
// 导入所需模块
const fs = require('fs');
const path = require('path');
const https = require('https');
// 从.env文件加载环境变量
function loadEnv() {
try {
const envPath = path.join(process.cwd(), '.env');
if (fs.existsSync(envPath)) {
const envContent = fs.readFileSync(envPath, 'utf-8');
const envLines = envContent.split('\n');
envLines.forEach(line => {
// 忽略注释和空行
if (line.trim() && !line.startsWith('#')) {
const [key, value] = line.split('=');
if (key && value) {
process.env[key.trim()] = value.trim();
}
}
});
console.log('已从.env文件加载环境变量');
} else {
console.log('.env文件不存在,将使用当前环境变量');
}
} catch (error) {
console.error(`加载.env文件失败: ${error.message}`);
}
}
// 加载环境变量
loadEnv();
// 日志函数
function log(message, type = 'INFO') {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] [${type}] ${message}`);
fs.appendFileSync(path.join(process.cwd(), 'test-log.txt'),
`[${timestamp}] [${type}] ${message}\n`);
}
// 创建HTTP请求函数
async function makeRequest(url, options = {}, body = null) {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const requestOptions = {
hostname: urlObj.hostname,
path: urlObj.pathname + urlObj.search,
method: options.method || 'GET',
headers: {
'Accept': 'application/json',
...(options.headers || {})
}
};
log(`发送 ${requestOptions.method} 请求到 ${url}`, 'DEBUG');
const req = https.request(requestOptions, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
log(`响应状态: ${res.statusCode}`, 'DEBUG');
if (res.statusCode >= 200 && res.statusCode < 300) {
try {
const parsed = JSON.parse(data);
resolve(parsed);
} catch (e) {
log(`解析JSON响应失败: ${e.message}`, 'ERROR');
resolve(data);
}
} else {
log(`请求失败,状态码 ${res.statusCode}: ${data}`, 'ERROR');
reject(new Error(`请求失败,状态码 ${res.statusCode}: ${data}`));
}
});
});
req.on('error', (error) => {
log(`请求错误: ${error.message}`, 'ERROR');
reject(error);
});
if (body) {
log(`请求体: ${typeof body === 'string' ? body : JSON.stringify(body)}`, 'DEBUG');
req.write(typeof body === 'string' ? body : JSON.stringify(body));
}
req.end();
});
}
// 获取用户组织
async function getUserGroups() {
try {
const token = process.env.CNB_ACCESS_TOKEN;
if (!token) {
throw new Error('CNB_ACCESS_TOKEN 环境变量未设置');
}
const response = await makeRequest('https://api.cnb.cool/user/groups', {
headers: {
'Authorization': token
}
});
log(`获取到用户组织: ${JSON.stringify(response)}`, 'INFO');
return response;
} catch (error) {
log(`获取用户组织失败: ${error.message}`, 'ERROR');
throw error;
}
}
// 获取用户仓库列表
async function getUserRepositories() {
try {
const token = process.env.CNB_ACCESS_TOKEN;
if (!token) {
throw new Error('CNB_ACCESS_TOKEN 环境变量未设置');
}
const response = await makeRequest('https://api.cnb.cool/user/repos?page=1&page_size=10&desc=false', {
headers: {
'Authorization': token
}
});
log(`获取到用户仓库列表: ${JSON.stringify(response)}`, 'INFO');
return response;
} catch (error) {
log(`获取用户仓库列表失败: ${error.message}`, 'ERROR');
throw error;
}
}
// 检查仓库是否存在
async function checkRepositoryExists(name, organization = 'FFA') {
try {
const repos = await getUserRepositories();
const exists = repos.some(repo => repo.name === name);
log(`仓库 ${name} ${exists ? '已存在' : '不存在'}`, 'INFO');
return exists;
} catch (error) {
log(`检查仓库存在性失败: ${error.message}`, 'ERROR');
return false; // 出错时默认不存在
}
}
// 获取仓库信息
async function getRepositoryInfo(owner, repo) {
try {
const token = process.env.CNB_ACCESS_TOKEN;
if (!token) {
throw new Error('CNB_ACCESS_TOKEN 环境变量未设置');
}
const response = await makeRequest(`https://api.cnb.cool/${owner}/${repo}`, {
headers: {
'Authorization': token
}
});
log(`获取到仓库信息: ${JSON.stringify(response)}`, 'INFO');
return response;
} catch (error) {
log(`获取仓库信息失败: ${error.message}`, 'ERROR');
throw error;
}
}
// 创建仓库
async function createRepository(name, group = 'FFA') {
try {
// 先检查仓库是否存在
const exists = await checkRepositoryExists(name);
if (exists) {
log(`仓库 ${name} 已存在,无需创建`, 'INFO');
// 直接获取仓库信息
return await getRepositoryInfo(group, name);
}
const token = process.env.CNB_ACCESS_TOKEN;
if (!token) {
throw new Error('CNB_ACCESS_TOKEN 环境变量未设置');
}
const body = {
name,
description: 'CNB MCP服务器项目 - 面向Model Context Protocol的服务端实现',
visibility: 'public'
};
log(`正在创建仓库 ${name}`, 'INFO');
const response = await makeRequest(`https://api.cnb.cool/${group}/-/repos`, {
method: 'POST',
headers: {
'Authorization': token,
'Content-Type': 'application/json'
}
}, body);
if (response && Object.keys(response).length > 0) {
log(`仓库创建成功: ${JSON.stringify(response)}`, 'INFO');
return response;
} else {
log(`仓库创建成功,但返回为空。正在获取仓库信息...`, 'INFO');
// 如果创建成功但返回为空,获取仓库信息
return await getRepositoryInfo(group, name);
}
} catch (error) {
// 如果错误信息包含409冲突,尝试获取仓库信息
if (error.message && error.message.includes('409')) {
log(`仓库可能已存在,正在获取信息: ${error.message}`, 'WARN');
return await getRepositoryInfo(group, name);
}
log(`创建仓库失败: ${error.message}`, 'ERROR');
throw error;
}
}
// 推送文件
async function pushFiles(owner, repo, branch, message, files) {
try {
const token = process.env.CNB_ACCESS_TOKEN;
if (!token) {
throw new Error('CNB_ACCESS_TOKEN 环境变量未设置');
}
log(`正在向 ${owner}/${repo} 推送 ${files.length} 个文件`, 'INFO');
const body = {
branch,
message,
files
};
const response = await makeRequest(`https://api.cnb.cool/${owner}/${repo}/push`, {
method: 'POST',
headers: {
'Authorization': token,
'Content-Type': 'application/json'
}
}, body);
log(`推送文件成功: ${JSON.stringify(response)}`, 'INFO');
return response;
} catch (error) {
log(`推送文件失败: ${error.message}`, 'ERROR');
throw error;
}
}
// 搜索公共仓库
async function searchPublicRepositories(query) {
try {
const token = process.env.CNB_ACCESS_TOKEN;
// 构造请求URL
const url = `https://cnb.cool/search/public-repos?key=${encodeURIComponent(query)}&page=1&page_size=10&order_by=stars&desc=true`;
const headers = {
'Accept': 'application/vnd.cnb.web+json',
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Host': 'cnb.cool',
'Connection': 'keep-alive'
};
log(`搜索公共仓库: ${query}`, 'INFO');
const response = await makeRequest(url, {
headers
});
log(`搜索结果: 找到 ${response.items?.length || 0} 个仓库`, 'INFO');
return response;
} catch (error) {
log(`搜索公共仓库失败: ${error.message}`, 'ERROR');
throw error;
}
}
// Fork仓库
async function forkRepository(owner, repo, targetGroup, options = {}) {
try {
const token = process.env.CNB_ACCESS_TOKEN;
if (!token) {
throw new Error('CNB_ACCESS_TOKEN 环境变量未设置');
}
const body = {
group: targetGroup
};
// 添加可选参数
if (options.branch) body.branch = options.branch;
if (options.name) body.name = options.name;
if (options.description) body.description = options.description;
log(`Fork仓库 ${owner}/${repo} 到 ${targetGroup}`, 'INFO');
const response = await makeRequest(`https://api.cnb.cool/${owner}/${repo}/-/forks`, {
method: 'POST',
headers: {
'Authorization': token,
'Content-Type': 'application/json'
}
}, body);
log(`Fork仓库成功: ${JSON.stringify(response)}`, 'INFO');
return response;
} catch (error) {
log(`Fork仓库失败: ${error.message}`, 'ERROR');
throw error;
}
}
// 主函数
async function main() {
log('开始CNB仓库工具测试', 'INFO');
try {
// 测试1: 获取用户组织
log('测试1: 获取用户组织', 'INFO');
const groups = await getUserGroups();
log(`用户组织: ${JSON.stringify(groups)}`, 'INFO');
const organization = groups && groups.length > 0 ? groups[0].path : 'FFA';
log(`使用组织: ${organization}`, 'INFO');
// 测试2: 获取用户仓库列表
log('测试2: 获取用户仓库列表', 'INFO');
const repos = await getUserRepositories();
log(`找到 ${repos.length} 个仓库`, 'INFO');
// 测试3: 搜索公共仓库
log('测试3: 搜索公共仓库', 'INFO');
try {
const searchResults = await searchPublicRepositories('mcp');
log(`搜索到 ${searchResults.items?.length || 0} 个公共仓库`, 'INFO');
} catch (error) {
log(`搜索仓库出错: ${error.message}`, 'WARN');
}
// 测试4: 创建或获取仓库
const repoName = 'cnb-mcp-server';
log(`测试4: 创建或获取仓库 ${repoName}`, 'INFO');
try {
const repo = await createRepository(repoName, organization);
log(`仓库信息: ${JSON.stringify(repo)}`, 'INFO');
// 如果找到公共仓库,尝试Fork
if (repos.length > 0) {
// 测试5: Fork仓库
const sourceRepo = repos[0];
log(`测试5: 尝试Fork仓库 ${sourceRepo.path}`, 'INFO');
try {
const forkResult = await forkRepository(
sourceRepo.path.split('/')[0],
sourceRepo.name,
organization,
{ description: '通过API Fork的仓库' }
);
log(`Fork结果: ${JSON.stringify(forkResult)}`, 'INFO');
} catch (error) {
log(`Fork仓库出错: ${error.message}`, 'WARN');
}
}
} catch (error) {
log(`创建仓库出错: ${error.message}`, 'WARN');
}
// 测试6: 推送文件
log('测试6: 推送文件', 'INFO');
const files = [
{
path: 'README.md',
content: '# CNB MCP 服务器\n\n这是一个基于TypeScript的服务器项目,用于实现Model Context Protocol(MCP)的服务端,提供对CNB API的访问。\n\n## 功能特性\n\n- 提供统一的API接入方式\n- 支持用户认证和权限管理\n- 实现CNB仓库管理的主要功能\n- 提供日志记录和错误处理\n\n## 安装使用\n\n```bash\nnpm install\nnpm run build\nnpm start\n```\n\n## 配置说明\n\n在启动服务前,需要设置环境变量或创建.env文件:\n\n```\nPORT=3000\nCNB_API_URL=https://api.cnb.cool\nCNB_ACCESS_TOKEN=your_access_token\n```\n\n## API文档\n\n详细的API文档请参考 `/docs` 目录。\n\n## 许可证\n\nMIT\n',
encoding: 'utf-8'
},
{
path: '.env.example',
content: 'PORT=3000\nCNB_API_URL=https://api.cnb.cool\nCNB_ACCESS_TOKEN=your_access_token\n',
encoding: 'utf-8'
}
];
try {
const pushResult = await pushFiles(organization, repoName, 'main', '初始提交:添加README和配置文件示例', files);
log(`推送文件结果: ${JSON.stringify(pushResult)}`, 'INFO');
} catch (error) {
log(`推送文件出错: ${error.message}`, 'ERROR');
}
log('CNB仓库工具测试完成', 'INFO');
} catch (error) {
log(`测试过程中出错: ${error.message}`, 'ERROR');
}
}
// 运行主函数
main().catch(error => {
log(`程序执行出错: ${error.message}`, 'ERROR');
process.exit(1);
});