Skip to main content
Glama

MCP Toolkit

by zxfgds
index.ts7.14 kB
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; import { Config } from '../../config/config.js'; import { ToolDefinition, ToolResponse, createErrorResponse, createSuccessResponse } from '../types.js'; import * as fs from 'fs'; import * as path from 'path'; // 扩展AxiosRequestConfig类型 interface ExtendedAxiosRequestConfig extends AxiosRequestConfig { timing?: { startTime: number; endTime?: number; }; } // 格式化响应数据 function formatResponse(response: AxiosResponse) { const config = response.config as ExtendedAxiosRequestConfig; return { status: response.status, statusText: response.statusText, headers: response.headers, data: response.data, timing: config.timing ? { total: (config.timing.endTime || 0) - config.timing.startTime } : undefined }; } // 格式化文件大小 function formatFileSize(bytes: number): string { const units = ['B', 'KB', 'MB', 'GB']; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${size.toFixed(2)} ${units[unitIndex]}`; } // 网络管理器类 class NetworkManager { private downloadDir: string; constructor( private readonly config: Config ) { // 使用当前目录下的 downloads 目录存储下载的文件 this.downloadDir = path.join(process.cwd(), 'downloads'); // 确保下载目录存在 if (!fs.existsSync(this.downloadDir)) { fs.mkdirSync(this.downloadDir, { recursive: true }); } } // 生成文件名 private generateFileName(url: string, contentType?: string): string { const urlObj = new URL(url); let fileName = path.basename(urlObj.pathname) || 'download'; // 如果没有扩展名,尝试从 Content-Type 添加 if (!path.extname(fileName) && contentType) { const ext = contentType.split('/').pop()?.split('+')[0]; if (ext && ext !== 'octet-stream') { fileName += `.${ext}`; } } // 添加时间戳 const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const nameWithoutExt = path.basename(fileName, path.extname(fileName)); return `${nameWithoutExt}-${timestamp}${path.extname(fileName)}`; } // 发送HTTP请求 async sendRequest(args: { url: string; method?: string; headers?: Record<string, string>; data?: any; params?: Record<string, string>; timeout?: number; download?: boolean; downloadPath?: string; }): Promise<ToolResponse> { try { // 构建请求配置 const requestConfig: ExtendedAxiosRequestConfig = { url: args.url, method: args.method || 'GET', headers: args.headers || {}, data: args.data, params: args.params, timeout: args.timeout || 30000, validateStatus: null, // 不抛出HTTP错误 timing: { startTime: Date.now() }, responseType: args.download ? 'stream' : 'json' }; // 发送请求 const response = await axios(requestConfig); // 更新计时 const config = response.config as ExtendedAxiosRequestConfig; if (config.timing) { config.timing.endTime = Date.now(); } // 如果是下载请求 if (args.download) { // 确保下载目录存在 if (!fs.existsSync(this.downloadDir)) { await fs.promises.mkdir(this.downloadDir, { recursive: true }); } // 生成文件名 const fileName = args.downloadPath || this.generateFileName(args.url, response.headers['content-type']); const filePath = path.join(this.downloadDir, fileName); // 获取文件大小 const totalSize = parseInt(response.headers['content-length'] || '0', 10); let downloadedSize = 0; // 创建写入流 const writer = fs.createWriteStream(filePath); // 下载文件 await new Promise<void>((resolve, reject) => { response.data.pipe(writer); response.data.on('data', (chunk: Buffer) => { downloadedSize += chunk.length; if (totalSize > 0) { const progress = (downloadedSize / totalSize * 100).toFixed(2); const downloaded = formatFileSize(downloadedSize); const total = formatFileSize(totalSize); console.error(`下载进度: ${progress}% (${downloaded}/${total})`); } }); writer.on('finish', resolve); writer.on('error', reject); }); return createSuccessResponse({ type: 'download', data: { path: filePath, size: downloadedSize, contentType: response.headers['content-type'], timing: config.timing ? { total: config.timing.endTime! - config.timing.startTime } : undefined } }); } // 格式化并返回响应 return createSuccessResponse({ type: 'json', data: formatResponse(response) }); } catch (error: unknown) { if (axios.isAxiosError(error)) { return createErrorResponse(`请求失败: ${error.message}`); } return createErrorResponse(`未知错误: ${error instanceof Error ? error.message : '未知错误'}`); } } // 清理资源 dispose(): void { // 不需要清理资源 } } // 创建网络工具 export function createNetworkTools( config: Config ): ToolDefinition[] { const manager = new NetworkManager(config); return [ { name: 'http_request', description: '发送HTTP请求并返回完整响应信息', inputSchema: { type: 'object', properties: { url: { type: 'string', description: '请求URL' }, method: { type: 'string', description: '请求方法(GET, POST, PUT, DELETE等)', enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'] }, headers: { type: 'object', description: '请求头', additionalProperties: true }, data: { type: 'object', description: '请求体数据', additionalProperties: true }, params: { type: 'object', description: 'URL查询参数', additionalProperties: true }, timeout: { type: 'number', description: '请求超时时间(毫秒)' }, download: { type: 'boolean', description: '是否下载文件。如果为 true,将把响应保存为文件。' }, downloadPath: { type: 'string', description: '下载文件的保存路径(相对于当前工作目录的 downloads 目录)。如果不指定,将根据 URL 和内容类型自动生成文件名。' } }, required: ['url'] }, handler: args => manager.sendRequest(args) } ]; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zxfgds/mcp-toolkit'

If you have feedback or need assistance with the MCP directory API, please join our Discord server