cnb-mcp-server
Version:
MCP Server for the cnb API, enabling file operations, repository management, search functionality, and more.
178 lines (177 loc) • 6.32 kB
JavaScript
/**
* 文件操作
*/
import { z } from 'zod';
import { makeRequest } from '../common/utils.js';
import { ContentSchema } from '../common/types.js';
// 获取文件内容参数Schema
export const GetFileContentsSchema = z.object({
owner: z.string().describe('仓库拥有者'),
repo: z.string().describe('仓库名称'),
path: z.string().describe('文件路径'),
ref: z.string().optional().describe('分支、标签或提交SHA')
});
// 创建或更新文件参数Schema
export const CreateOrUpdateFileSchema = z.object({
owner: z.string().describe('仓库拥有者'),
repo: z.string().describe('仓库名称'),
path: z.string().describe('文件路径'),
message: z.string().describe('提交消息'),
content: z.string().describe('文件内容,Base64编码'),
branch: z.string().optional().describe('分支名'),
sha: z.string().optional().describe('更新文件时,文件的当前SHA')
});
// 批量推送文件参数Schema
export const PushFilesSchema = z.object({
owner: z.string().describe('仓库拥有者'),
repo: z.string().describe('仓库名称'),
branch: z.string().describe('分支名'),
message: z.string().describe('提交消息'),
files: z.array(z.object({
path: z.string().describe('文件路径'),
content: z.string().describe('文件内容'),
encoding: z.enum(['utf-8', 'base64']).default('utf-8').describe('内容编码,默认为utf-8'),
mode: z.enum(['100644', '100755', '040000', '160000', '120000']).optional().describe('文件模式')
})).describe('要推送的文件列表')
});
/**
* 获取文件内容
*/
export async function getFileContents(owner, repo, path, ref, accessToken) {
// 调整为CNB API格式的路径
let endpoint = `/${owner}/${repo}/-/git/contents/${encodeURIComponent(path)}`;
if (ref) {
endpoint += `?ref=${encodeURIComponent(ref)}`;
}
const response = await makeRequest(endpoint, {}, accessToken);
return ContentSchema.parse(response);
}
/**
* 创建或更新文件
*/
export async function createOrUpdateFile(owner, repo, path, message, content, branch, sha, accessToken) {
const endpoint = `/repos/${owner}/${repo}/contents/${encodeURIComponent(path)}`;
const body = {
message,
content
};
if (branch) {
body.branch = branch;
}
if (sha) {
body.sha = sha;
}
const response = await makeRequest(endpoint, {
method: 'PUT',
body: JSON.stringify(body)
}, accessToken);
return {
content: ContentSchema.parse(response.content),
commit: {
sha: response.commit.sha,
html_url: response.commit.html_url
}
};
}
/**
* 删除文件
*/
export async function deleteFile(owner, repo, path, message, sha, branch, accessToken) {
const endpoint = `/repos/${owner}/${repo}/contents/${encodeURIComponent(path)}`;
const body = {
message,
sha
};
if (branch) {
body.branch = branch;
}
const response = await makeRequest(endpoint, {
method: 'DELETE',
body: JSON.stringify(body)
}, accessToken);
return {
commit: {
sha: response.commit.sha,
html_url: response.commit.html_url
}
};
}
/**
* 在单个提交中向CNB仓库推送多个文件
*/
export async function pushFiles(owner, repo, branch, message, files, accessToken) {
try {
// 构造完整URL和请求头
const token = accessToken || process.env.CNB_ACCESS_TOKEN || '';
const url = `https://api.cnb.cool/${owner}/${repo}/push`;
const headers = {
'accept': 'application/json',
'Authorization': token,
'Content-Type': 'application/json'
};
// 准备请求体
const requestBody = {
branch,
message,
files
};
console.log(`推送文件到: ${url}`);
console.log(`推送到分支: ${branch}`);
console.log(`推送文件数量: ${files.length}`);
console.log(`提交消息: ${message}`);
// 检查每个文件的编码和路径
files.forEach((file, index) => {
if (!file.path) {
throw new Error(`文件 #${index + 1} 缺少路径`);
}
if (!file.content) {
throw new Error(`文件 ${file.path} 缺少内容`);
}
console.log(`准备推送文件: ${file.path} (编码: ${file.encoding || 'utf-8'})`);
});
// 直接使用fetch发送请求
const response = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify(requestBody)
});
console.log(`推送文件响应状态: ${response.status}`);
if (response.ok) {
try {
const result = await response.json();
console.log('推送文件成功');
return result;
}
catch (e) {
// 可能响应不是JSON格式
console.log('推送文件成功,但响应格式不是JSON');
return { success: true };
}
}
else {
// 处理错误
const errorText = await response.text();
console.log('推送文件错误:', errorText);
// 根据错误码提供更详细的提示
if (response.status === 404) {
throw new Error(`仓库 ${owner}/${repo} 或分支 ${branch} 不存在`);
}
else if (response.status === 401) {
throw new Error('授权失败,请检查访问令牌');
}
else if (response.status === 403) {
throw new Error(`没有权限向 ${owner}/${repo} 推送文件`);
}
else if (response.status === 409) {
throw new Error('文件冲突,可能已存在相同文件但内容不同');
}
else {
throw new Error(`推送文件失败: ${response.status} ${errorText}`);
}
}
}
catch (error) {
console.log('推送文件请求错误:', error instanceof Error ? error.message : String(error));
throw error;
}
}