UNPKG

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
/** * 文件操作 */ 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; } }