cos-mcp
Version:
基于MCP协议的腾讯云COS MCP Server,无需编码即可让大模型快速接入腾讯云存储(COS)和数据万象(CI)能力
363 lines • 12.3 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { z } from 'zod';
import axios from 'axios';
import { PassThrough } from 'stream';
import { generateOutPutFileId } from '../utils.js';
import { TEXT_TYPES } from '../constant.js';
export const UploadFileParamsSchema = z.object({
filePath: z.string().optional(),
targetDir: z.string().optional(),
fileName: z.string().optional(),
sourceUrl: z.string().optional()
});
export const UploadStringParamsSchema = z.object({
content: z.string(),
fileName: z.string(),
targetDir: z.string().optional(),
contentType: z.string().optional()
});
export const UploadBase64ParamsSchema = z.object({
base64Content: z.string(),
fileName: z.string(),
targetDir: z.string().optional(),
contentType: z.string().optional()
});
export const UploadBufferParamsSchema = z.object({
content: z.string(),
fileName: z.string(),
targetDir: z.string().optional(),
contentType: z.string().optional(),
encoding: z.enum(['hex', 'base64', 'utf8', 'ascii', 'binary']).optional()
});
export class CosService {
bucket;
region;
cos;
constructor(bucket, region, cos) {
this.bucket = bucket;
this.region = region;
this.cos = cos;
}
processBase64(base64 = '', contentType = '') {
// 基础验证
if (typeof base64 !== 'string') {
throw new Error('base64参数必须是字符串');
}
if (typeof contentType !== 'string') {
throw new Error('contentType参数必须是字符串');
}
let base64Data = base64;
let finalContentType = contentType;
// 检查base64是否包含数据头
const dataUriRegex = /^data:([^;]+);base64,/;
const match = base64.match(dataUriRegex);
if (match) {
// 如果base64有数据头,提取纯数据和contentType
const headerEndIndex = base64.indexOf(',') + 1;
base64Data = base64.substring(headerEndIndex);
// 如果没有传递contentType,则从base64头中提取
if (!contentType.trim()) {
finalContentType = match[1];
}
}
return {
base64Data: base64Data.trim(),
contentType: finalContentType.trim()
};
}
/**
* 构建COS路径
* @param fileName 文件名
* @param targetDir 目标目录
* @returns COS路径
*/
buildCosPath(fileName, targetDir) {
if (!targetDir) {
return fileName;
}
// 规范化目标目录:移除头尾斜杠
const normalizedDir = targetDir.replace(/^\/+|\/+$/g, '');
return normalizedDir ? `${normalizedDir}/${fileName}` : fileName;
}
// /**
// * 上传文件到COS
// * @param params 上传参数
// * @returns 上传结果
// */
async uploadFile(params) {
// 验证并解析参数
const validParams = UploadFileParamsSchema.parse(params);
const { filePath, targetDir = '', fileName } = validParams;
try {
// 检查文件是否存在
if (!filePath || !fs.existsSync(filePath)) {
return {
isSuccess: false,
message: '此路径上文件不存在',
data: '此路径上文件不存在: ' + filePath,
};
}
// 确定文件名
const actualFileName = fileName || path.basename(filePath);
// 构建COS路径
const cosPath = this.buildCosPath(actualFileName, targetDir);
// 上传文件
const cosParams = {
Bucket: this.bucket,
Region: this.region,
Key: cosPath,
FilePath: filePath,
};
const result = await this.cos.uploadFile(cosParams);
return {
isSuccess: true,
message: '上传成功',
data: result,
};
}
catch (error) {
return {
isSuccess: false,
message: '上传失败',
data: error,
};
}
}
/**
* 上传字符串内容到COS
* @param params 上传参数
* @returns 上传结果
*/
async uploadString(params) {
const validParams = UploadStringParamsSchema.parse(params);
const { content, fileName, targetDir = '', contentType = 'text/plain' } = validParams;
try {
// 构建COS路径
const cosPath = this.buildCosPath(fileName, targetDir);
// 上传字符串内容
const cosParams = {
Bucket: this.bucket,
Region: this.region,
Key: cosPath,
Body: content,
ContentType: contentType,
};
const result = await this.cos.putObject(cosParams);
return {
isSuccess: true,
message: '上传成功',
data: result,
};
}
catch (error) {
return {
isSuccess: false,
message: '上传失败',
data: error,
};
}
}
/**
* 上传base64内容到COS
* @param params 上传参数
* @returns 上传结果
*/
async uploadBase64(params) {
const validParams = UploadBase64ParamsSchema.parse(params);
let { base64Content, fileName, targetDir = '' } = validParams;
try {
// 构建COS路径
const cosPath = this.buildCosPath(fileName, targetDir);
// 处理base64
let { base64Data, contentType } = this.processBase64(base64Content, validParams.contentType);
// 将base64转换为Buffer
const buffer = Buffer.from(base64Data, 'base64');
// 上传buffer内容
const cosParams = {
Bucket: this.bucket,
Region: this.region,
Key: cosPath,
Body: buffer,
ContentType: contentType || 'application/octet-stream',
};
const result = await this.cos.putObject(cosParams);
return {
isSuccess: true,
message: '上传成功',
data: result,
};
}
catch (error) {
return {
isSuccess: false,
message: '上传失败',
data: error,
};
}
}
/**
* 上传buffer内容到COS
* @param params 上传参数
* @returns 上传结果
*/
async uploadBuffer(params) {
const validParams = UploadBufferParamsSchema.parse(params);
const { content, fileName, targetDir = '', contentType = 'application/octet-stream', encoding } = validParams;
try {
// 构建COS路径
const cosPath = this.buildCosPath(fileName, targetDir);
// 根据编码类型转换为Buffer
const buffer = encoding ? Buffer.from(content, encoding) : Buffer.from(content);
// 上传buffer内容
const cosParams = {
Bucket: this.bucket,
Region: this.region,
Key: cosPath,
Body: buffer,
ContentType: contentType,
};
const result = await this.cos.putObject(cosParams);
return {
isSuccess: true,
message: '上传成功',
data: result,
};
}
catch (error) {
return {
isSuccess: false,
message: '上传失败',
data: error,
};
}
}
// /**
// * 上传文件到COS
// * @param params 上传参数
// * @returns 上传结果
// */
async uploadFileSourceUrl(params) {
// 验证并解析参数
const validParams = UploadFileParamsSchema.parse(params);
const { targetDir = '', fileName, sourceUrl } = validParams;
try {
const response = await axios({
method: 'get',
url: sourceUrl,
responseType: 'stream'
});
const actualFileName = fileName ? fileName : generateOutPutFileId('');
const cosPath = this.buildCosPath(actualFileName, targetDir);
const req = response.data;
const passThrough = new PassThrough();
const result = await this.cos.putObject({
Bucket: this.bucket,
Region: this.region,
Key: cosPath,
Body: req.pipe(passThrough),
});
return {
isSuccess: true,
message: '上传成功',
data: result,
};
}
catch (error) {
return {
isSuccess: false,
message: '上传失败',
data: error,
};
}
}
async getObject(objectKey = '/') {
try {
// 下载文件
const cosParams = {
Bucket: this.bucket,
Region: this.region,
Key: objectKey,
};
const result = await this.cos.getObject(cosParams);
// 统一处理 buffer
const buffer = Buffer.isBuffer(result.Body) ? result.Body : Buffer.from(result.Body ?? '');
// 获取 Content-Type,统一小写
let contentType = result.headers && (result.headers['content-type'] || result.headers['Content-Type']);
contentType = typeof contentType === 'string' ? contentType.toLowerCase() : '';
let mcpData;
if (contentType.startsWith('image/')) {
mcpData = { type: 'image', data: buffer.toString('base64'), mimeType: contentType };
}
else if (contentType.startsWith('audio/')) {
mcpData = { type: 'audio', data: buffer.toString('base64'), mimeType: contentType };
}
else if (contentType.startsWith('text/') || TEXT_TYPES.includes(contentType)) {
mcpData = { type: 'text', text: buffer.toString('utf-8') };
}
else {
mcpData = { type: 'text', text: buffer.toString('base64') };
}
return {
isSuccess: true,
message: '下载文件成功',
data: mcpData,
};
}
catch (error) {
return {
isSuccess: false,
message: '下载文件失败',
data: error instanceof Error ? error.message : error,
};
}
}
// 获取文件列表
async getBucket(Prefix = '') {
try {
const result = await this.cos.getBucket({
Bucket: this.bucket,
Region: this.region,
Prefix,
Delimiter: '',
});
return {
isSuccess: true,
message: '获取列表成功',
data: result,
};
}
catch (error) {
return {
isSuccess: false,
message: '获取列表失败',
data: error,
};
}
}
//获取带签名的objecturl
async getObjectUrl(ObjectKey) {
try {
const result = await new Promise((resolve, reject) => {
this.cos.getObjectUrl({
Bucket: this.bucket,
Region: this.region,
Key: ObjectKey,
}, (error, data) => (error ? reject(error) : resolve(data)));
});
return {
isSuccess: true,
message: '获取带签名 ObjectUrl 成功',
data: result,
};
}
catch (error) {
return {
isSuccess: false,
message: '获取带签名 ObjectUrl 失败',
data: error,
};
}
}
}
//# sourceMappingURL=cos.service.js.map