Skip to main content
Glama
index.js88.9 kB
#!/usr/bin/env node /** * ApiPost MCP - API文档管理工具 * 提供简洁高效的API文档创建、查看、修改和删除功能 */ // @ts-nocheck import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; // 环境变量验证 const APIPOST_TOKEN = process.env.APIPOST_TOKEN; if (!APIPOST_TOKEN) { console.error('错误: 请设置 APIPOST_TOKEN 环境变量'); process.exit(1); } const APIPOST_HOST = process.env.APIPOST_HOST || 'https://open.apipost.net'; const APIPOST_SECURITY_MODE = process.env.APIPOST_SECURITY_MODE || 'limited'; // readonly, limited, full const APIPOST_DEFAULT_TEAM_NAME = process.env.APIPOST_DEFAULT_TEAM_NAME; const APIPOST_DEFAULT_PROJECT_NAME = process.env.APIPOST_DEFAULT_PROJECT_NAME; const APIPOST_INLINE_COMMENTS = (process.env.APIPOST_INLINE_COMMENTS || 'false').toLowerCase() === 'true'; const APIPOST_URL_PREFIX = process.env.APIPOST_URL_PREFIX || ''; // URL前缀,如 {{host}} // API客户端 const apiClient = axios.create({ baseURL: APIPOST_HOST, headers: { 'Api-Token': APIPOST_TOKEN, 'Content-Type': 'application/json' } }); // 安全模式检查 function checkSecurityPermission(operation) { switch (APIPOST_SECURITY_MODE.toLowerCase()) { case 'readonly': return operation === 'read'; case 'limited': return operation === 'read' || operation === 'write'; case 'full': return true; default: logWithTime(`⚠️ 未知的安全模式: ${APIPOST_SECURITY_MODE}, 默认为只读模式`); return operation === 'read'; } } // URL前缀处理 function applyUrlPrefix(url) { if (!url || !APIPOST_URL_PREFIX) return url; // 如果url已经包含了前缀,则不重复添加 if (url.startsWith(APIPOST_URL_PREFIX)) return url; // 确保拼接时斜杠正确处理 const prefix = APIPOST_URL_PREFIX.endsWith('/') ? APIPOST_URL_PREFIX.slice(0, -1) : APIPOST_URL_PREFIX; const path = url.startsWith('/') ? url : '/' + url; return prefix + path; } // 生成ID function generateId() { return (Date.now() + Math.floor(Math.random() * 10000)).toString(16); } // 简洁的日志输出 function logWithTime(message, startTime) { console.error(message); } // 统一转换参数格式 function convertParams(paramsList) { if (!paramsList || !Array.isArray(paramsList)) return []; return paramsList.map(param => ({ param_id: generateId(), description: param.desc || param.description || '', field_type: param.type || param.field_type || 'string', is_checked: param.autoParent ? 0 : (param.required ? 1 : (param.is_checked ?? 0)), key: param.key, not_null: param.autoParent ? 0 : (param.required ? 1 : (param.not_null ?? 0)), value: param.autoParent ? '' : (param.example ?? param.value ?? ''), schema: param.schema || { type: param.type || 'string' } })); } // 为字段列表补充父级(object/array)字段,方便在参数表中展示完整路径 function expandFieldListWithParents(fields) { if (!Array.isArray(fields)) return []; const userKeys = new Set(fields.filter(f => f && f.key).map(f => String(f.key))); const result = []; const seenKeys = new Set(); const ensureParent = (keyPath) => { const segments = keyPath.split('.'); let currentPath = ''; segments.forEach((seg, index) => { const isArray = seg.endsWith('[]'); const cleanSeg = isArray ? seg.slice(0, -2) : seg; currentPath = currentPath ? `${currentPath}.${cleanSeg}` : cleanSeg; // 如果用户已显式提供该父级,则不创建自动父级 if (userKeys.has(currentPath)) return; if (!seenKeys.has(currentPath)) { seenKeys.add(currentPath); if (index < segments.length - 1) { result.push({ key: currentPath, type: isArray ? 'array' : 'object', required: false, desc: '', autoParent: true }); } } }); }; fields.forEach(field => { if (!field || !field.key) return; ensureParent(String(field.key)); // 保留原字段(可能覆盖父级) if (!seenKeys.has(field.key)) { seenKeys.add(field.key); result.push(field); } else { // 如果父级已占位,再追加原字段 result.push(field); } }); return result; } // 构建描述映射,便于生成带注释的原始字符串 function buildDescMap(fields) { const map = new Map(); if (!Array.isArray(fields)) return map; fields.forEach(field => { if (!field || !field.key) return; const path = String(field.key).replace(/\[\]/g, '[0]'); if (field.desc || field.description) { map.set(path, field.desc || field.description); } }); return map; } // 将对象转为带行内注释的字符串(非严格 JSON,用于 raw 展示) function stringifyWithComments(value, descMap, path = '', indent = 4, level = 0) { const pad = (lvl) => ' '.repeat(lvl * indent); if (Array.isArray(value)) { if (value.length === 0) return '[]'; const items = value.map((item, index) => { const childPath = `${path}[${index}]`; const childStr = stringifyWithComments(item, descMap, childPath, indent, level + 1); const comment = descMap.get(childPath) ? ` // ${descMap.get(childPath)}` : ''; return `${pad(level + 1)}${childStr}${comment}`; }); return `[\n${items.join(',\n')}\n${pad(level)}]`; } if (value !== null && typeof value === 'object') { const entries = Object.keys(value).map(key => { const childPath = path ? `${path}.${key}` : key; const childStr = stringifyWithComments(value[key], descMap, childPath, indent, level + 1); const comment = descMap.get(childPath) ? ` // ${descMap.get(childPath)}` : ''; return `${pad(level + 1)}"${key}": ${childStr}${comment}`; }); return `{\n${entries.join(',\n')}\n${pad(level)}}`; } // 基本类型 return JSON.stringify(value); } // 按类型提供默认示例值 function defaultValueByType(type) { switch ((type || '').toLowerCase()) { case 'integer': case 'number': return 0; case 'boolean': return false; case 'array': return []; case 'object': return {}; case 'null': return null; default: return ''; } } // 将扁平字段列表(key 带 . 或 [])构造成 JSON 对象 function buildJsonFromFieldList(fields) { const root = {}; if (!Array.isArray(fields)) return root; fields.forEach(field => { // 自动补充的父级节点仅用于展示,不参与值生成 if (field && field.autoParent) return; if (!field || !field.key) return; const path = String(field.key); const segments = path.split('.').map(seg => { if (seg.endsWith('[]')) { return { key: seg.slice(0, -2), isArray: true }; } return { key: seg, isArray: false }; }); let current = root; segments.forEach((seg, index) => { const isLeaf = index === segments.length - 1; if (seg.isArray) { if (!Array.isArray(current[seg.key])) { current[seg.key] = []; } if (current[seg.key].length === 0) { current[seg.key].push({}); } if (isLeaf) { const val = field.example ?? field.value ?? defaultValueByType(field.type); current[seg.key][0] = val; } else { if (typeof current[seg.key][0] !== 'object' || current[seg.key][0] === null) { current[seg.key][0] = {}; } current = current[seg.key][0]; } } else { if (isLeaf) { const val = field.example ?? field.value ?? defaultValueByType(field.type); current[seg.key] = val; } else { if (typeof current[seg.key] !== 'object' || current[seg.key] === null) { current[seg.key] = {}; } current = current[seg.key]; } } }); }); return root; } // 根据 Body 参数构造示例 JSON function generateRequestBodyFromParams(bodyParams) { if (!Array.isArray(bodyParams) || bodyParams.length === 0) return {}; return buildJsonFromFieldList(bodyParams); } // 构造 Body 区块(用于 create/update) function buildBodySection(bodyParams) { const hasBody = Array.isArray(bodyParams) && bodyParams.length > 0; const expandedFields = expandFieldListWithParents(bodyParams || []); const rawBody = generateRequestBodyFromParams(expandedFields); const descMap = buildDescMap(expandedFields); const rawString = hasBody ? (APIPOST_INLINE_COMMENTS ? stringifyWithComments(rawBody, descMap) : JSON.stringify(rawBody, null, 4)) : ''; return { mode: hasBody ? 'json' : 'none', parameter: [], raw: rawString, raw_parameter: convertParams(expandedFields), raw_schema: { type: 'object' }, binary: null }; } // 统一响应数据转换 function generateResponseData(responseConfig) { if (!responseConfig) return { code: 0, message: '操作成功', data: {} }; if (typeof responseConfig === 'string') { try { return JSON.parse(responseConfig); } catch { return { code: 0, message: '操作成功', data: responseConfig }; } } return responseConfig; } function isApiPostResponseExample(resp) { return !!resp && (resp.example_id !== undefined || resp.expect !== undefined || resp.raw !== undefined); } function normalizeResponses(responses, options = {}) { const { fallbackExamples = [], useDefaultWhenMissing = true, keepEmpty = true, isCheckResult = 1 } = options; const hasInput = Array.isArray(responses); const inputLength = hasInput ? responses.length : 0; // 用户显式提供了空数组并且允许保留空响应 if (hasInput && inputLength === 0) { return { example: keepEmpty ? [] : fallbackExamples, is_check_result: isCheckResult }; } // 未提供响应,使用回退或默认 if (!hasInput) { if (fallbackExamples.length > 0) { return { example: fallbackExamples, is_check_result: isCheckResult }; } if (!useDefaultWhenMissing) { return { example: [], is_check_result: isCheckResult }; } const defaultData = generateResponseData(undefined); return { example: [{ example_id: '1', raw: JSON.stringify(defaultData, null, 4), raw_parameter: [], headers: [], expect: { code: '200', content_type: 'application/json', is_default: 1, mock: JSON.stringify(defaultData), name: '成功响应', schema: { type: 'object', properties: {} }, verify_type: 'schema', sleep: 0 } }], is_check_result: isCheckResult }; } // 已经是 ApiPost 的响应结构,直接透传 if (responses.some(isApiPostResponseExample)) { return { example: responses, is_check_result: isCheckResult }; } // 简化格式 -> ApiPost 兼容格式 const converted = responses.map((resp, index) => ({ example_id: String(index + 1), raw: (() => { const fields = Array.isArray(resp.fields) ? resp.fields : []; if (fields.length === 0) { throw new Error('responses.fields 必填且不能为空,data 字段已禁用,请提供字段列表'); } const expandedFields = expandFieldListWithParents(fields); const descMap = buildDescMap(expandedFields); const rawData = buildJsonFromFieldList(expandedFields); return APIPOST_INLINE_COMMENTS && expandedFields.length > 0 ? stringifyWithComments(rawData, descMap) : JSON.stringify(rawData, null, 4); })(), raw_parameter: convertParams(expandFieldListWithParents(resp.fields || [])), headers: [], expect: { code: String(resp.status ?? 200), content_type: 'application/json', is_default: index === 0 ? 1 : -1, mock: JSON.stringify(buildJsonFromFieldList(expandFieldListWithParents(resp.fields || []))), name: resp.name || (index === 0 ? '成功响应' : `响应${index + 1}`), schema: resp.schema || { type: 'object', properties: {} }, verify_type: 'schema', sleep: 0 } })); return { example: converted, is_check_result: isCheckResult }; } // 构建项目路径映射 function buildPathMap(allItems) { const pathMap = new Map(); const itemMap = new Map(); // 建立ID到项目的映射 allItems.forEach(item => { itemMap.set(item.target_id, item); }); // 递归构建路径(带循环检测) function buildPath(targetId, visited = new Set()) { if (pathMap.has(targetId)) { return pathMap.get(targetId); } // 检测循环引用 if (visited.has(targetId)) { console.warn(`检测到循环引用: ${targetId}`); return []; } const item = itemMap.get(targetId); if (!item) return []; visited.add(targetId); const path = []; if (item.parent_id && item.parent_id !== '0') { const parentPath = buildPath(item.parent_id, visited); path.push(...parentPath); } path.push(item.name); pathMap.set(targetId, path); visited.delete(targetId); return path; } // 为所有项目构建路径 allItems.forEach(item => { buildPath(item.target_id); }); return pathMap; } // 递归获取子项目 function getChildrenRecursively(items, parentId, maxDepth, currentDepth = 0) { if (maxDepth !== undefined && currentDepth >= maxDepth) { return []; } const children = items.filter(item => item.parent_id === parentId); const result = [...children]; children.forEach(child => { if (child.target_type === 'folder') { result.push(...getChildrenRecursively(items, child.target_id, maxDepth, currentDepth + 1)); } }); return result; } // 按目录分组项目 function groupByFolder(items, allItems) { const groups = {}; const folderMap = new Map(); // 建立目录映射 allItems.filter(item => item.target_type === 'folder').forEach(folder => { folderMap.set(folder.target_id, folder); }); items.forEach(item => { const parentId = item.parent_id || '0'; const parentName = parentId === '0' ? '根目录' : (folderMap.get(parentId)?.name || `未知目录(${parentId})`); if (!groups[parentName]) { groups[parentName] = []; } groups[parentName].push(item); }); return groups; } // 构建层级结构显示 function buildListDisplay(items, totalCount, filteredCount, showStructure, searchKeyword, parentId, targetType, isLimited, limit, showPath, recursive, depth, groupByFolderFlag, allItems) { let listText = ''; // 标题信息 if (recursive) { listText += `🌲 递归搜索视图`; if (depth !== undefined) listText += ` (深度限制: ${depth})`; listText += `\n`; } else if (parentId !== undefined) { listText += `📁 目录层级视图 (父目录ID: ${parentId})\n`; } else { listText += `📋 项目完整列表\n`; } listText += `总计: ${totalCount}项, 当前显示: ${items.length}项\n\n`; // 筛选信息 const filterInfo = []; if (searchKeyword) filterInfo.push(`搜索: "${searchKeyword}"`); if (parentId !== undefined) filterInfo.push(`父目录: ${parentId === '0' ? '根目录' : parentId}`); if (targetType && targetType !== 'all') filterInfo.push(`类型: ${targetType}`); if (recursive) filterInfo.push(`递归搜索: 是`); if (depth !== undefined) filterInfo.push(`深度限制: ${depth}`); if (filterInfo.length > 0) { listText += `🔍 筛选条件: ${filterInfo.join(' | ')}\n`; listText += `筛选结果: ${filteredCount}项\n\n`; } if (isLimited) { listText += `⚠️ 显示限制: 仅显示前${limit}项,如需查看更多请使用搜索过滤\n\n`; } if (items.length === 0) { listText += '📭 未找到匹配的项目\n\n'; listText += '💡 提示:\n'; listText += '• 尝试调整搜索关键词\n'; listText += '• 检查父目录ID是否正确\n'; listText += '• 使用不同的类型筛选\n'; listText += '• 尝试使用 recursive=true 递归搜索子目录\n'; return listText; } // 构建路径映射(如果需要显示路径) let pathMap; if (showPath && allItems) { pathMap = buildPathMap(allItems); } if (groupByFolderFlag && allItems) { // 按目录分组显示 listText += buildGroupedList(items, allItems, pathMap); } else if (showStructure) { // 树形结构显示 listText += buildTreeStructure(items, pathMap); } else { // 列表模式显示 listText += buildFlatList(items, pathMap); } // 操作提示 listText += '\n💡 使用提示:\n'; listText += '• 使用 parent_id 参数查看特定目录下的内容\n'; listText += '• 使用 target_type="folder" 仅查看目录\n'; listText += '• 使用 target_type="api" 仅查看接口\n'; listText += '• 使用 show_structure=true 查看树形结构\n'; listText += '• 使用 show_path=true 显示完整路径\n'; listText += '• 使用 recursive=true 递归搜索子目录\n'; listText += '• 使用 group_by_folder=true 按目录分组显示\n'; return listText; } // 构建树形结构 function buildTreeStructure(items, pathMap) { let result = '🌳 树形结构:\n\n'; // 按类型分组,目录在前,接口在后 const folders = items.filter(item => item.target_type === 'folder'); const apis = items.filter(item => item.target_type === 'api'); // 显示目录 if (folders.length > 0) { result += '📁 目录:\n'; folders.forEach((folder, index) => { const isLast = index === folders.length - 1 && apis.length === 0; const prefix = isLast ? '└── ' : '├── '; result += `${prefix}${folder.name}\n`; result += ` 📋 ID: ${folder.target_id}\n`; // 显示完整路径 if (pathMap && pathMap.has(folder.target_id)) { const path = pathMap.get(folder.target_id); result += ` 📍 路径: ${path.join(' / ')}\n`; } if (folder.description) { result += ` 📝 描述: ${folder.description}\n`; } result += '\n'; }); } // 显示接口 if (apis.length > 0) { result += '🔗 接口:\n'; apis.forEach((api, index) => { const isLast = index === apis.length - 1; const prefix = isLast ? '└── ' : '├── '; result += `${prefix}${api.name}`; if (api.method) result += ` [${api.method}]`; result += '\n'; result += ` 🌐 URL: ${api.url || '未设置'}\n`; result += ` 📋 ID: ${api.target_id}\n`; // 显示完整路径 if (pathMap && pathMap.has(api.target_id)) { const path = pathMap.get(api.target_id); result += ` 📍 路径: ${path.join(' / ')}\n`; } if (api.description) { result += ` 📝 描述: ${api.description}\n`; } result += '\n'; }); } return result; } // 构建平铺列表 function buildFlatList(items, pathMap) { let result = '📋 项目列表:\n\n'; items.forEach((item, index) => { const num = (index + 1).toString().padStart(2, ' '); if (item.target_type === 'folder') { // 目录项 result += `${num}. 📁 ${item.name}\n`; result += ` 类型: 目录\n`; result += ` ID: ${item.target_id}\n`; result += ` 父目录: ${item.parent_id === '0' ? '根目录' : item.parent_id}\n`; // 显示完整路径 if (pathMap && pathMap.has(item.target_id)) { const path = pathMap.get(item.target_id); result += ` 路径: ${path.join(' / ')}\n`; } if (item.description) { result += ` 描述: ${item.description}\n`; } } else { // 接口项 result += `${num}. 🔗 ${item.name}`; if (item.method) result += ` [${item.method}]`; result += '\n'; result += ` 类型: 接口\n`; result += ` URL: ${item.url || '未设置'}\n`; result += ` ID: ${item.target_id}\n`; result += ` 父目录: ${item.parent_id === '0' ? '根目录' : item.parent_id}\n`; // 显示完整路径 if (pathMap && pathMap.has(item.target_id)) { const path = pathMap.get(item.target_id); result += ` 路径: ${path.join(' / ')}\n`; } if (item.description) { result += ` 描述: ${item.description}\n`; } } result += '\n'; }); return result; } // 构建分组列表 function buildGroupedList(items, allItems, pathMap) { let result = '📂 按目录分组显示:\n\n'; const groups = groupByFolder(items, allItems); const groupNames = Object.keys(groups).sort(); groupNames.forEach((groupName, groupIndex) => { const groupItems = groups[groupName]; const isLastGroup = groupIndex === groupNames.length - 1; result += `📁 ${groupName} (${groupItems.length}项)\n`; result += `${isLastGroup ? ' ' : '│ '}\n`; groupItems.forEach((item, index) => { const isLastItem = index === groupItems.length - 1; const itemPrefix = isLastGroup ? (isLastItem ? ' └── ' : ' ├── ') : (isLastItem ? '│ └── ' : '│ ├── '); if (item.target_type === 'folder') { result += `${itemPrefix}📁 ${item.name}\n`; if (!isLastGroup || !isLastItem) { result += `${isLastGroup ? ' ' : '│ '}📋 ID: ${item.target_id}\n`; } else { result += ` 📋 ID: ${item.target_id}\n`; } } else { result += `${itemPrefix}🔗 ${item.name}`; if (item.method) result += ` [${item.method}]`; result += '\n'; if (!isLastGroup || !isLastItem) { result += `${isLastGroup ? ' ' : '│ '}📋 ID: ${item.target_id}\n`; result += `${isLastGroup ? ' ' : '│ '}🌐 URL: ${item.url || '未设置'}\n`; } else { result += ` 📋 ID: ${item.target_id}\n`; result += ` 🌐 URL: ${item.url || '未设置'}\n`; } } // 显示完整路径 if (pathMap && pathMap.has(item.target_id)) { const path = pathMap.get(item.target_id); if (!isLastGroup || !isLastItem) { result += `${isLastGroup ? ' ' : '│ '}📍 路径: ${path.join(' / ')}\n`; } else { result += ` 📍 路径: ${path.join(' / ')}\n`; } } }); result += '\n'; }); return result; } // 解析API配置 function parseApiConfig(configJson) { if (!configJson) return {}; try { return JSON.parse(configJson); } catch (error) { console.error('解析API配置失败:', error); return {}; } } // 解析单个配置参数 function parseConfigParam(paramJson) { if (!paramJson) return []; try { const parsed = JSON.parse(paramJson); return Array.isArray(parsed) ? parsed : []; } catch (error) { console.error('解析配置参数失败:', error); return []; } } function ensureFieldsHaveDesc(list, context) { if (!Array.isArray(list)) return; const missing = list.filter(item => item && !item.desc && !item.description); if (missing.length > 0) { throw new Error(`${context} 缺少 desc,请为每个字段填写 desc`); } } function ensureResponsesFieldsHaveDesc(responses) { if (!Array.isArray(responses)) return; responses.forEach((resp, index) => { if (resp && Array.isArray(resp.fields)) { ensureFieldsHaveDesc(resp.fields, `responses[${index}].fields`); } else { throw new Error(`responses[${index}] 未提供 fields 或格式不正确`); } }); } // 构建配置对象,同时记录哪些字段被明确提供了 function buildApiConfig(args) { const config = {}; const providedFields = new Set(); if (args.description !== undefined) { config.description = args.description; providedFields.add('description'); } if (args.headers !== undefined) { config.headers = parseConfigParam(args.headers); ensureFieldsHaveDesc(config.headers, 'headers'); providedFields.add('headers'); } if (args.query !== undefined) { config.query = parseConfigParam(args.query); ensureFieldsHaveDesc(config.query, 'query'); providedFields.add('query'); } if (args.body !== undefined) { config.body = parseConfigParam(args.body); ensureFieldsHaveDesc(config.body, 'body'); providedFields.add('body'); } if (args.cookies !== undefined) { config.cookies = parseConfigParam(args.cookies); ensureFieldsHaveDesc(config.cookies, 'cookies'); providedFields.add('cookies'); } if (args.auth !== undefined) { config.auth = parseApiConfig(args.auth); providedFields.add('auth'); } if (args.responses !== undefined) { config.responses = parseConfigParam(args.responses); ensureResponsesFieldsHaveDesc(config.responses); providedFields.add('responses'); } return { config, providedFields }; } // 生成API模板 function generateApiTemplate(method, url, name, config = {}) { return { target_id: generateId(), target_type: 'api', parent_id: '0', name, method, url: applyUrlPrefix(url), protocol: 'http/1.1', description: config.description || `${name} - ${method} ${url}`, version: 3, mark_id: 1, is_force: -1, request: { auth: config.auth || { type: 'inherit' }, pre_tasks: config.pre_tasks || [], post_tasks: config.post_tasks || [], header: { parameter: convertParams(config.headers || []) }, query: { query_add_equal: 1, parameter: convertParams(config.query || []) }, body: buildBodySection(config.body || []), cookie: { cookie_encode: 1, parameter: convertParams(config.cookies || []) }, restful: { parameter: convertParams(config.restful || []) } }, response: normalizeResponses(config.responses, { useDefaultWhenMissing: true, keepEmpty: true, isCheckResult: 1 }), attribute_info: config.attribute_info || {}, tags: config.tags || [] }; } // 工作空间信息 let currentWorkspace = null; // 初始化工作空间 async function initWorkspace(startTime) { try { logWithTime('📈 获取团队列表...', startTime); const teamsResult = await apiClient.get('/open/team/list'); if (!teamsResult.data.data || teamsResult.data.data.length === 0) { console.error('📋 获取团队列表原始数据:', JSON.stringify(teamsResult.data, null, 2)); throw new Error('未找到可用团队'); } // 选择团队:优先使用指定的团队名称,否则使用第一个 let selectedTeam = teamsResult.data.data[0]; if (APIPOST_DEFAULT_TEAM_NAME) { const targetTeam = teamsResult.data.data.find((team) => team.name === APIPOST_DEFAULT_TEAM_NAME); if (targetTeam) { selectedTeam = targetTeam; logWithTime(`🎯 使用指定团队: ${APIPOST_DEFAULT_TEAM_NAME}`, startTime); } else { logWithTime(`⚠️ 未找到指定团队 "${APIPOST_DEFAULT_TEAM_NAME}",使用默认团队`, startTime); } } logWithTime(`✅ 选中团队 团队名称: ${selectedTeam.name} 团队ID: ${selectedTeam.team_id}`, startTime); logWithTime('📁 获取项目列表...', startTime); const projectsResult = await apiClient.get('/open/project/list', { params: { team_id: selectedTeam.team_id, action: 0 } }); if (!projectsResult.data.data || projectsResult.data.data.length === 0) { throw new Error('未找到可用项目'); } // 选择项目:优先使用指定的项目名称,否则使用第一个 let selectedProject = projectsResult.data.data[0]; if (APIPOST_DEFAULT_PROJECT_NAME) { const targetProject = projectsResult.data.data.find((project) => project.name === APIPOST_DEFAULT_PROJECT_NAME); if (targetProject) { selectedProject = targetProject; logWithTime(`🎯 使用指定项目: ${APIPOST_DEFAULT_PROJECT_NAME}`, startTime); } else { logWithTime(`⚠️ 未找到指定项目 "${APIPOST_DEFAULT_PROJECT_NAME}",使用默认项目`, startTime); } } logWithTime(` ✅ 选中项目 项目名称: ${selectedProject.name} 项目ID: ${selectedProject.project_id}`, startTime); currentWorkspace = { teamId: selectedTeam.team_id, teamName: selectedTeam.name, projectId: selectedProject.project_id, projectName: selectedProject.name }; logWithTime(`✨ 工作空间初始化完成 (安全模式: ${APIPOST_SECURITY_MODE})`, startTime); } catch (error) { logWithTime('❌ 工作空间初始化失败: ' + error, startTime); throw error; } } // 创建MCP服务器 const server = new Server({ name: 'apipost-mcp', version: '1.0.0', capabilities: { tools: {} } }); // 工具定义 server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'apipost_test_connection', description: '测试ApiPost MCP连接状态和配置信息,验证服务可用性', inputSchema: { type: 'object', properties: { random_string: { type: 'string', description: 'Dummy parameter for no-parameter tools' } }, required: ['random_string'] } }, { name: 'apipost_workspace', description: '工作空间管理:查看当前工作空间、列出团队和项目、切换工作空间', inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['current', 'list_teams', 'list_projects', 'switch'], description: '操作类型:current(查看当前)、list_teams(列出团队)、list_projects(列出项目)、switch(切换工作空间)' }, team_id: { type: 'string', description: '团队ID(用于list_projects或switch)' }, project_id: { type: 'string', description: '项目ID(用于switch)' }, team_name: { type: 'string', description: '团队名称(用于按名称切换)' }, project_name: { type: 'string', description: '项目名称(用于按名称切换)' }, show_details: { type: 'boolean', description: '是否显示详细信息,默认false' }, show_all: { type: 'boolean', description: '是否显示所有可用的团队和项目,默认false' } }, required: ['action'] } }, { name: 'apipost_create_folder', description: '创建API文档目录,支持在指定父目录下创建新的文件夹', inputSchema: { type: 'object', properties: { name: { type: 'string', description: '目录名称' }, parent_id: { type: 'string', description: '父目录ID,使用"0"表示根目录,默认为"0"' }, description: { type: 'string', description: '目录描述(可选)' } }, required: ['name'], additionalProperties: false } }, { name: 'apipost_smart_create', description: 'API接口文档生成器(字段列表驱动)。规则:responses 只传 fields,不传 data;headers/query/body/cookies 统一用字段列表,嵌套用 .,数组用 [];example 填真实值(不要 JSON 字符串);所有字段含父级都必须写 desc,父级需显式声明。例如:{"key":"data","desc":"返回体","type":"object"},{"key":"data.user","desc":"用户","type":"object"},{"key":"data.user.id","desc":"用户ID","type":"integer","example":1}', inputSchema: { type: 'object', properties: { method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'DELETE'], description: 'HTTP方法' }, url: { type: 'string', description: '接口URL路径' }, name: { type: 'string', description: '接口名称' }, parent_id: { type: 'string', description: '父目录ID,使用"0"表示根目录,默认为"0"' }, description: { type: 'string', description: '接口详细描述(可选)' }, headers: { type: 'string', description: 'Headers字段列表字符串,格式:[{"key":"X-Request-ID","type":"string","required":false,"example":"req-1","desc":"说明"}]' }, query: { type: 'string', description: 'Query字段列表字符串,格式同上。嵌套用 .,数组用 [](如 meta.flags.debug 或 items[].id)。' }, body: { type: 'string', description: 'Body字段列表字符串,仅用字段列表生成 raw/参数描述,example 用真实值,不要放 JSON 字符串。' }, cookies: { type: 'string', description: 'Cookies字段列表字符串,格式同上。' }, auth: { type: 'string', description: '认证配置JSON字符串(可选)。格式:{"type":"bearer","bearer":{"key":"your_token"}}' }, responses: { type: 'string', description: '响应字段列表字符串(必填 fields),格式:[{"name":"成功","status":200,"fields":[{"key":"code","type":"integer","example":0,"desc":"状态码"},{"key":"data.items[].id","type":"string","example":"1"}]}]' } }, required: ['method', 'url', 'name'], additionalProperties: false } }, { name: 'apipost_list', description: '查看项目API列表,支持强化的目录层级搜索和父子关系定位', inputSchema: { type: 'object', properties: { search: { type: 'string', description: '搜索关键词(接口名称、URL、方法、ID、描述)' }, parent_id: { type: 'string', description: '父目录ID,精确查找某个目录下的子项目。使用"0"查看根目录,使用具体ID查看子目录' }, target_type: { type: 'string', enum: ['api', 'folder', 'all'], description: '项目类型筛选:api(仅接口)、folder(仅目录)、all(全部),默认all' }, show_structure: { type: 'boolean', description: '是否显示层级结构(树形展示),默认false为列表模式' }, show_path: { type: 'boolean', description: '是否显示完整路径(从根目录到当前项目的完整路径),默认false' }, recursive: { type: 'boolean', description: '是否递归搜索子目录(搜索指定目录及其所有子目录),默认false仅搜索当前层级' }, depth: { type: 'number', description: '层级深度限制(配合recursive使用,限制搜索深度),默认无限制' }, group_by_folder: { type: 'boolean', description: '是否按目录分组显示结果,默认false' }, limit: { type: 'number', description: '显示数量限制(默认50,最大200)' }, show_all: { type: 'boolean', description: '显示全部项目(忽略limit限制)' } } } }, { name: 'apipost_update', description: '修改API接口文档。规则同创建:responses 只用 fields(必填),不要传 data;headers/query/body/cookies 统一用字段列表,嵌套用 .,数组用 [],example 填真实值;所有字段含父级必须写 desc,父级需显式声明。例如:{"key":"data","desc":"返回体","type":"object"},{"key":"data.user","desc":"用户","type":"object"},{"key":"data.user.id","desc":"用户ID","type":"integer","example":1}', inputSchema: { type: 'object', properties: { target_id: { type: 'string', description: '要修改的接口ID' }, name: { type: 'string', description: '新的接口名称(可选)' }, method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'DELETE'], description: '新的HTTP方法(可选)' }, url: { type: 'string', description: '新的接口URL(可选)' }, description: { type: 'string', description: '接口详细描述(可选)。提供空字符串""可清空描述' }, headers: { type: 'string', description: 'Headers参数JSON数组字符串(可选)。提供"[]"可删除所有headers。格式:[{"key":"Content-Type","desc":"内容类型","type":"string","required":true,"example":"application/json"}]' }, query: { type: 'string', description: 'Query参数JSON数组字符串(可选)。提供"[]"可删除所有query参数。格式:[{"key":"page","desc":"页码","type":"integer","required":false,"example":"1"}]' }, body: { type: 'string', description: 'Body参数JSON数组字符串(可选)。提供"[]"可删除所有body参数。格式:[{"key":"name","desc":"用户名","type":"string","required":true,"example":"张三"}]' }, cookies: { type: 'string', description: 'Cookies参数JSON数组字符串(可选)。提供"[]"可删除所有cookies。格式:[{"key":"session_id","desc":"会话ID","type":"string","required":false,"example":"abc123"}]' }, auth: { type: 'string', description: '认证配置JSON字符串(可选)。提供"{}"可删除认证配置。格式:{"type":"bearer","bearer":{"key":"your_token"}}' }, responses: { type: 'string', description: '响应示例JSON数组字符串(可选)。提供"[]"可删除所有响应示例。格式:[{"name":"成功响应","status":200,"data":{"code":0},"fields":[{"key":"code","desc":"状态码","type":"integer","example":"0"}]}]' } }, required: ['target_id'], additionalProperties: false } }, { name: 'apipost_detail', description: '查看API接口的详细配置信息,包括完整的请求参数、响应格式、认证设置等。', inputSchema: { type: 'object', properties: { target_id: { type: 'string', description: '要查看的接口ID' } }, required: ['target_id'], additionalProperties: false } }, { name: 'apipost_delete', description: '批量删除API接口文档,支持单个或多个接口删除。删除前先用apipost_list查看接口列表获取ID', inputSchema: { type: 'object', properties: { api_ids: { type: 'array', items: { type: 'string' }, description: 'API接口ID数组(可从列表中获取target_id)- 支持单个["id1"]或多个["id1","id2","id3"]' } }, required: ['api_ids'] } } ] })); // 工具处理 server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (!args) { throw new Error('缺少参数'); } const startTime = Date.now(); try { if (!currentWorkspace) { await initWorkspace(startTime); } switch (name) { case 'apipost_test_connection': const connectionInfo = { status: '✅ 连接正常', mcp_version: '1.0.0', api_host: APIPOST_HOST, security_mode: APIPOST_SECURITY_MODE, workspace: currentWorkspace ? { team_name: currentWorkspace.teamName, project_name: currentWorkspace.projectName, project_id: currentWorkspace.projectId } : null, environment: { token_configured: !!APIPOST_TOKEN, host_configured: !!APIPOST_HOST, node_version: process.version, platform: process.platform, url_prefix: APIPOST_URL_PREFIX }, available_operations: { create_api: checkSecurityPermission('write'), update_api: checkSecurityPermission('write'), delete_api: checkSecurityPermission('write'), read_api: checkSecurityPermission('read') }, test_time: new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }) }; return { content: [ { type: 'text', text: `🎉 ApiPost MCP 连接测试成功! 📊 连接状态: ${connectionInfo.status} 🔗 MCP版本: ${connectionInfo.mcp_version} 🌐 API地址: ${connectionInfo.api_host} 🔒 安全模式: ${connectionInfo.security_mode} 🏢 当前工作空间: ${connectionInfo.workspace ? `• 团队: ${connectionInfo.workspace.team_name} • 项目: ${connectionInfo.workspace.project_name} • 项目ID: ${connectionInfo.workspace.project_id}` : '• 工作空间未初始化'} 🔧 环境配置: • Token配置: ${connectionInfo.environment.token_configured ? '✅ 已配置' : '❌ 未配置'} • Host配置: ${connectionInfo.environment.host_configured ? '✅ 已配置' : '❌ 未配置'} • URL前缀: ${connectionInfo.environment.url_prefix || '(未配置)'} • Node版本: ${connectionInfo.environment.node_version} • 系统平台: ${connectionInfo.environment.platform} 🛠️ 可用操作: • 创建接口: ${connectionInfo.available_operations.create_api ? '✅ 允许' : '❌ 禁止'} • 更新接口: ${connectionInfo.available_operations.update_api ? '✅ 允许' : '❌ 禁止'} • 删除接口: ${connectionInfo.available_operations.delete_api ? '✅ 允许' : '❌ 禁止'} • 读取接口: ${connectionInfo.available_operations.read_api ? '✅ 允许' : '❌ 禁止'} ⏰ 测试时间: ${connectionInfo.test_time} 🎯 MCP服务器运行正常,可以开始使用其他工具!` } ] }; case 'apipost_workspace': const action = args.action; switch (action) { case 'current': // 查看当前工作空间 const showAll = args.show_all; let workspaceText = '🏢 当前工作空间信息:\n\n'; if (currentWorkspace) { workspaceText += `📋 团队: ${currentWorkspace.teamName}\n`; workspaceText += ` 🆔 ID: ${currentWorkspace.teamId}\n\n`; workspaceText += `📁 项目: ${currentWorkspace.projectName}\n`; workspaceText += ` 🆔 ID: ${currentWorkspace.projectId}\n\n`; workspaceText += `🔒 安全模式: ${APIPOST_SECURITY_MODE}\n`; } else { workspaceText += '❌ 工作空间未初始化\n'; workspaceText += '💡 请使用 apipost_workspace action:switch 切换到可用的工作空间\n\n'; } if (showAll) { try { workspaceText += '\n📋 可用团队和项目:\n\n'; const allTeamsRes = await apiClient.get('/open/team/list'); const allTeams = allTeamsRes.data.data || []; for (const team of allTeams) { workspaceText += `📋 团队: ${team.name} (${team.team_id})\n`; try { const teamProjectsRes = await apiClient.get('/open/project/list', { params: { team_id: team.team_id, action: 0 } }); const teamProjects = teamProjectsRes.data.data || []; if (teamProjects.length > 0) { teamProjects.forEach((project) => { workspaceText += ` 📁 ${project.name} (${project.project_id})\n`; }); } else { workspaceText += ` 📭 无可用项目\n`; } } catch (error) { workspaceText += ` ❌ 获取项目列表失败\n`; } workspaceText += '\n'; } } catch (error) { workspaceText += `\n❌ 获取可用团队列表失败: ${error}\n`; } } return { content: [{ type: 'text', text: workspaceText }] }; case 'list_teams': // 列出团队 const teamsResult = await apiClient.get('/open/team/list'); if (teamsResult.data.code !== 0) { throw new Error(`获取团队列表失败: ${teamsResult.data.msg}`); } const teams = teamsResult.data.data || []; const showDetails = args.show_details; let teamsText = `📋 可用团队列表 (共 ${teams.length} 个):\n\n`; if (teams.length === 0) { teamsText += '📭 未找到可用团队\n'; } else { teams.forEach((team, index) => { const num = (index + 1).toString().padStart(2, ' '); const isCurrent = currentWorkspace?.teamId === team.team_id ? ' ⭐ 当前' : ''; teamsText += `${num}. ${team.name}${isCurrent}\n`; teamsText += ` 🆔 ID: ${team.team_id}\n`; if (showDetails) { teamsText += ` 📅 创建时间: ${team.created_at || '未知'}\n`; teamsText += ` 👤 创建者: ${team.creator_name || '未知'}\n`; if (team.description) { teamsText += ` 📝 描述: ${team.description}\n`; } } teamsText += '\n'; }); } if (currentWorkspace) { teamsText += `💡 当前团队: ${currentWorkspace.teamName} (${currentWorkspace.teamId})\n`; } teamsText += '\n💡 使用 apipost_workspace action:switch 切换团队和项目'; return { content: [{ type: 'text', text: teamsText }] }; case 'list_projects': // 列出项目 const targetTeamId = args.team_id || currentWorkspace?.teamId; if (!targetTeamId) { throw new Error('请指定团队ID或确保已初始化工作空间'); } const projectsResult = await apiClient.get('/open/project/list', { params: { team_id: targetTeamId, action: 0 } }); if (projectsResult.data.code !== 0) { throw new Error(`获取项目列表失败: ${projectsResult.data.msg}`); } const projects = projectsResult.data.data || []; const showProjectDetails = args.show_details; // 获取团队信息 const teamsRes = await apiClient.get('/open/team/list'); const currentTeam = teamsRes.data.data?.find((t) => t.team_id === targetTeamId); const teamName = currentTeam?.name || targetTeamId; let projectsText = `📁 团队 "${teamName}" 的项目列表 (共 ${projects.length} 个):\n\n`; if (projects.length === 0) { projectsText += '📭 该团队下未找到项目\n'; } else { projects.forEach((project, index) => { const num = (index + 1).toString().padStart(2, ' '); const isCurrent = currentWorkspace?.projectId === project.project_id ? ' ⭐ 当前' : ''; projectsText += `${num}. ${project.name}${isCurrent}\n`; projectsText += ` 🆔 ID: ${project.project_id}\n`; if (showProjectDetails) { projectsText += ` 📅 创建时间: ${project.created_at || '未知'}\n`; projectsText += ` 👤 创建者: ${project.creator_name || '未知'}\n`; if (project.description) { projectsText += ` 📝 描述: ${project.description}\n`; } projectsText += ` 🔒 可见性: ${project.is_public ? '公开' : '私有'}\n`; } projectsText += '\n'; }); } if (currentWorkspace && currentWorkspace.teamId === targetTeamId) { projectsText += `💡 当前项目: ${currentWorkspace.projectName} (${currentWorkspace.projectId})\n`; } projectsText += '\n💡 使用 apipost_workspace action:switch 切换到指定项目'; return { content: [{ type: 'text', text: projectsText }] }; case 'switch': // 切换工作空间 const newTeamId = args.team_id; const newProjectId = args.project_id; const teamNameToSwitch = args.team_name; const projectNameToSwitch = args.project_name; // 如果提供了名称,先查找对应的ID let finalTeamId = newTeamId; let finalProjectId = newProjectId; if (teamNameToSwitch && !newTeamId) { const teamsRes = await apiClient.get('/open/team/list'); const team = teamsRes.data.data?.find((t) => t.name === teamNameToSwitch); if (!team) { throw new Error(`未找到名称为 "${teamNameToSwitch}" 的团队`); } finalTeamId = team.team_id; } if (projectNameToSwitch && !newProjectId) { if (!finalTeamId) { throw new Error('切换到指定项目需要先指定团队'); } const projectsRes = await apiClient.get('/open/project/list', { params: { team_id: finalTeamId, action: 0 } }); const project = projectsRes.data.data?.find((p) => p.name === projectNameToSwitch); if (!project) { throw new Error(`在团队中未找到名称为 "${projectNameToSwitch}" 的项目`); } finalProjectId = project.project_id; } if (!finalTeamId || !finalProjectId) { throw new Error('请提供团队ID和项目ID,或者提供团队名称和项目名称'); } // 验证团队和项目是否存在且可访问 const teamCheckRes = await apiClient.get('/open/team/list'); const targetTeam = teamCheckRes.data.data?.find((t) => t.team_id === finalTeamId); if (!targetTeam) { throw new Error(`团队ID "${finalTeamId}" 不存在或无权限访问`); } const projectCheckRes = await apiClient.get('/open/project/list', { params: { team_id: finalTeamId, action: 0 } }); const targetProject = projectCheckRes.data.data?.find((p) => p.project_id === finalProjectId); if (!targetProject) { throw new Error(`项目ID "${finalProjectId}" 在指定团队中不存在或无权限访问`); } // 更新工作空间 const oldWorkspace = currentWorkspace; currentWorkspace = { teamId: finalTeamId, teamName: targetTeam.name, projectId: finalProjectId, projectName: targetProject.name }; let switchText = '🔄 工作空间切换成功!\n\n'; if (oldWorkspace) { switchText += `📤 原工作空间:\n`; switchText += ` 团队: ${oldWorkspace.teamName} (${oldWorkspace.teamId})\n`; switchText += ` 项目: ${oldWorkspace.projectName} (${oldWorkspace.projectId})\n\n`; } switchText += `📥 新工作空间:\n`; switchText += ` 团队: ${currentWorkspace.teamName} (${currentWorkspace.teamId})\n`; switchText += ` 项目: ${currentWorkspace.projectName} (${currentWorkspace.projectId})\n\n`; switchText += `✨ 现在可以在新的工作空间中进行 API 操作了!`; return { content: [{ type: 'text', text: switchText }] }; default: throw new Error(`未知的操作类型: ${action}. 可用操作: current, list_teams, list_projects, switch`); } case 'apipost_create_folder': if (!checkSecurityPermission('write')) { throw new Error(`🔒 安全模式 "${APIPOST_SECURITY_MODE}" 不允许创建操作。需要 "limited" 或 "full" 模式。`); } const folderName = args.name; const folderParentId = args.parent_id || '0'; const folderDescription = args.description || ''; if (!folderName) { throw new Error('请提供目录名称'); } // 生成目录模板 const folderTemplate = { project_id: currentWorkspace.projectId, target_id: generateId(), parent_id: folderParentId, target_type: 'folder', name: folderName, sort: 0, version: 0, server_id: '0', status: 1, is_changed: 1, is_create: 1, description: folderDescription, request: { header: { parameter: [] }, query: { parameter: [] }, body: { parameter: [] }, cookie: { parameter: [] }, auth: { type: 'inherit', kv: { key: '', value: '', in: 'header' }, bearer: { key: '' }, basic: { username: '', password: '' }, digest: { username: '', password: '', realm: '', nonce: '', algorithm: 'MD5', qop: '', nc: '', cnonce: '', opaque: '', disableRetryRequest: false }, oauth1: { consumerKey: '', consumerSecret: '', signatureMethod: 'HMAC-SHA1', addEmptyParamsToSign: true, includeBodyHash: true, addParamsToHeader: false, realm: '', version: '1.0', nonce: '', timestamp: '', verifier: '', callback: '', tokenSecret: '', token: '', disableHeaderEncoding: false }, hawk: { authId: '', authKey: '', algorithm: '', user: '', nonce: '', extraData: '', app: '', delegation: '', timestamp: '', includePayloadHash: false }, awsv4: { accessKey: '', secretKey: '', region: '', service: '', sessionToken: '', addAuthDataToQuery: false }, ntlm: { username: '', password: '', domain: '', workstation: '', disableRetryRequest: false }, edgegrid: { accessToken: '', clientToken: '', clientSecret: '', nonce: '', timestamp: '', baseURi: '', headersToSign: '' }, noauth: {}, jwt: { addTokenTo: 'header', algorithm: 'HS256', secret: '', isSecretBase64Encoded: false, payload: '', headerPrefix: 'Bearer', queryParamKey: 'token', header: '' }, asap: { alg: 'HS256', iss: '', aud: '', kid: '', privateKey: '', sub: '', claims: '', exp: '' } }, pre_tasks: [], post_tasks: [] }, is_force: -1, is_deleted: -1, is_conflicted: -1, mark_id: '1' }; // 创建目录 const createFolderResult = await apiClient.post('/open/apis/create', folderTemplate); if (createFolderResult.data.code !== 0) { throw new Error(`创建目录失败: ${createFolderResult.data.msg}`); } return { content: [{ type: 'text', text: `目录创建成功!\n名称: ${folderName}\n目录ID: ${folderTemplate.target_id}\n父目录ID: ${folderParentId}${folderDescription ? '\n描述: ' + folderDescription : ''}` }] }; case 'apipost_smart_create': if (!checkSecurityPermission('write')) { throw new Error(`🔒 安全模式 "${APIPOST_SECURITY_MODE}" 不允许创建操作。需要 "limited" 或 "full" 模式。`); } // 构建配置对象 const { config } = buildApiConfig(args); const template = generateApiTemplate(args.method, args.url, args.name, config); template.project_id = currentWorkspace.projectId; // 设置父目录ID template.parent_id = args.parent_id || '0'; const headerCount = config.headers?.length || 0; const queryCount = config.query?.length || 0; const bodyCount = config.body?.length || 0; const responseCount = config.responses?.length || 0; const createResult = await apiClient.post('/open/apis/create', template); if (createResult.data.code !== 0) { throw new Error(`创建失败: ${createResult.data.msg}`); } return { content: [{ type: 'text', text: `API创建成功!\n名称: ${args.name}\n方法: ${args.method}\nURL: ${args.url}\nID: ${createResult.data.data.target_id}\n\n字段统计:\n• Headers: ${headerCount}个\n• Query参数: ${queryCount}个\n• Body参数: ${bodyCount}个\n• 响应示例: ${responseCount}个` }] }; case 'apipost_list': if (!checkSecurityPermission('read')) { throw new Error(`🔒 安全模式 "${APIPOST_SECURITY_MODE}" 不允许读取操作。`); } const searchKeyword = args.search; const parentId = args.parent_id; const targetType = args.target_type || 'all'; const showStructure = args.show_structure; const showPath = args.show_path; const recursive = args.recursive; const depth = args.depth; const groupByFolderFlag = args.group_by_folder; const limit = Math.min(args.limit || 50, 200); const showAll = args.show_all; const listResult = await apiClient.get('/open/apis/list', { params: { project_id: currentWorkspace.projectId } }); if (listResult.data.code !== 0) { throw new Error(`获取列表失败: ${listResult.data.msg}`); } let items = listResult.data.data.list; const totalCount = items.length; const allItems = [...items]; // 保存完整列表用于路径构建和分组 // 递归搜索或按目录过滤 if (recursive && parentId !== undefined) { // 递归搜索指定目录及其子目录 items = getChildrenRecursively(items, parentId, depth); } else if (parentId !== undefined) { // 仅搜索当前层级 items = items.filter((item) => item.parent_id === parentId); } // 按类型过滤 if (targetType !== 'all') { items = items.filter((item) => item.target_type === targetType); } // 搜索过滤 if (searchKeyword) { const keyword = searchKeyword.toLowerCase(); items = items.filter((item) => item.name?.toLowerCase().includes(keyword) || item.url?.toLowerCase().includes(keyword) || item.method?.toLowerCase().includes(keyword) || item.target_id?.toLowerCase().includes(keyword) || item.description?.toLowerCase().includes(keyword)); } // 分页处理 const filteredCount = items.length; let displayItems = items; let isLimited = false; if (!showAll && filteredCount > limit) { displayItems = items.slice(0, limit); isLimited = true; } // 构建显示文本 const listResult_display = buildListDisplay(displayItems, totalCount, filteredCount, showStructure, searchKeyword, parentId, targetType, isLimited, limit, showPath, recursive, depth, groupByFolderFlag, allItems); // 构建日志信息 const filterInfo = []; if (parentId !== undefined) filterInfo.push(`父目录: ${parentId}`); if (targetType !== 'all') filterInfo.push(`类型: ${targetType}`); if (searchKeyword) filterInfo.push(`搜索: "${searchKeyword}"`); if (recursive) filterInfo.push('递归搜索'); if (depth !== undefined) filterInfo.push(`深度限制: ${depth}`); const logInfo = filterInfo.length > 0 ? `\n筛选条件: ${filterInfo.join(', ')}` : ''; const limitInfo = isLimited ? `\n显示限制: 前${limit}条` : ''; return { content: [{ type: 'text', text: listResult_display }] }; case 'apipost_update': if (!checkSecurityPermission('write')) { throw new Error(`🔒 安全模式 "${APIPOST_SECURITY_MODE}" 不允许修改操作。需要 "limited" 或 "full" 模式。`); } const targetId = args.target_id; const newName = args.name; const newMethod = args.method; const newUrl = args.url ? applyUrlPrefix(args.url) : undefined; if (!targetId) { throw new Error('请提供要修改的API接口ID'); } // 获取原接口信息 const getResult = await apiClient.post('/open/apis/details', { project_id: currentWorkspace.projectId, target_ids: [targetId] }); if (getResult.data.code !== 0) { throw new Error(`获取接口详情失败: ${getResult.data.msg}`); } const originalApi = getResult.data.data.list[0]; // 获取数组中的第一个接口 if (!originalApi) { throw new Error(`未找到接口详情 (ID: ${targetId})。可能原因:1) 接口不存在 2) 无权限访问 3) 接口已被删除。请检查接口ID是否正确。`); } // 构建增量更新配置对象 const { config: newConfig, providedFields } = buildApiConfig(args); const mergedDescription = providedFields.has('description') ? newConfig.description : (originalApi.description || ''); const mergedRequest = { auth: providedFields.has('auth') ? (newConfig.auth || { type: 'inherit' }) : (originalApi.request?.auth || { type: 'inherit' }), pre_tasks: originalApi.request?.pre_tasks || [], post_tasks: originalApi.request?.post_tasks || [], header: { parameter: providedFields.has('headers') ? convertParams(newConfig.headers || []) : (originalApi.request?.header?.parameter || []) }, query: { query_add_equal: originalApi.request?.query?.query_add_equal ?? 1, parameter: providedFields.has('query') ? convertParams(newConfig.query || []) : (originalApi.request?.query?.parameter || []) }, body: providedFields.has('body') ? buildBodySection(newConfig.body || []) : (originalApi.request?.body || buildBodySection([])), cookie: { cookie_encode: originalApi.request?.cookie?.cookie_encode ?? 1, parameter: providedFields.has('cookies') ? convertParams(newConfig.cookies || []) : (originalApi.request?.cookie?.parameter || []) }, restful: originalApi.request?.restful || { parameter: [] } }; const responseSection = providedFields.has('responses') ? normalizeResponses(newConfig.responses, { fallbackExamples: [], useDefaultWhenMissing: false, keepEmpty: true, isCheckResult: originalApi.response?.is_check_result ?? 1 }) : { example: originalApi.response?.example || [], is_check_result: originalApi.response?.is_check_result ?? 1 }; const updateTemplate = { project_id: currentWorkspace.projectId, target_id: targetId, parent_id: originalApi.parent_id || '0', target_type: originalApi.target_type || 'api', name: newName || originalApi.name, method: newMethod || originalApi.method, url: newUrl || originalApi.url, protocol: originalApi.protocol || 'http/1.1', description: mergedDescription, version: (originalApi.version || 0) + 1, mark_id: originalApi.mark_id || '1', is_force: originalApi.is_force ?? -1, sort: originalApi.sort ?? 0, status: originalApi.status ?? 1, is_deleted: originalApi.is_deleted ?? -1, is_conflicted: originalApi.is_conflicted ?? -1, request: mergedRequest, response: responseSection, attribute_info: originalApi.attribute_info || {}, tags: originalApi.tags || [] }; // 执行修改 const updateResult = await apiClient.post('/open/apis/update', updateTemplate); if (updateResult.data.code !== 0) { throw new Error(`修改失败: ${updateResult.data.msg}`); } // 统计修改的字段 const changedFields = []; if (newName && newName !== originalApi.name) changedFields.push('名称'); if (newMethod && newMethod !== originalApi.method) changedFields.push('方法'); if (newUrl && newUrl !== originalApi.url) changedFields.push('URL'); // 检查是否有配置相关的更新 if (providedFields.size > 0) changedFields.push('配置'); const changedFieldsText = changedFields.length > 0 ? `\n修改字段: ${changedFields.join(', ')}` : '\n仅更新版本'; let updateText = `接口修改成功!\n接口ID: ${targetId}\n`; if (newName) updateText += `新名称: ${newName}\n`; if (newMethod) updateText += `新方法: ${newMethod}\n`; if (newUrl) updateText += `新URL: ${newUrl}\n`; updateText += `版本: v${updateTemplate.version}\n修改字段: ${changedFields.join(', ') || '仅更新版本'}`; return { content: [{ type: 'text', text: updateText }] }; case 'apipost_detail': if (!checkSecurityPermission('read')) { throw new Error(`🔒 安全模式 "${APIPOST_SECURITY_MODE}" 不允许读取操作。`); } const detailTargetId = args.target_id; if (!detailTargetId) { throw new Error('请提供要查看的API接口ID'); } // 获取接口详情 const detailResult = await apiClient.post('/open/apis/details', { project_id: currentWorkspace.projectId, target_ids: [detailTargetId] }); if (detailResult.data.code !== 0) { throw new Error(`获取接口详情失败: ${detailResult.data.msg}`); } const apiDetail = detailResult.data.data.list[0]; if (!apiDetail) { throw new Error(`未找到接口详情 (ID: ${detailTargetId})。可能原因:1) 接口不存在 2) 无权限访问 3) 接口已被删除。请检查接口ID是否正确。`); } // 格式化接口详情 let detailText = `📋 接口详情\n\n`; detailText += `🏷️ 基本信息\n`; detailText += ` 接口名称: ${apiDetail.name}\n`; detailText += ` 请求方法: ${apiDetail.method}\n`; detailText += ` 请求URL: ${apiDetail.url}\n`; detailText += ` 接口ID: ${detailTargetId}\n`; detailText += ` 版本: v${apiDetail.version || 1}\n`; if (apiDetail.description) { detailText += ` 描述: ${apiDetail.description}\n`; } detailText += `\n`; // Headers参数 const headers = apiDetail.request?.header?.parameter || []; detailText += `📨 Headers参数 (${headers.length}个)\n`; if (headers.length > 0) { headers.forEach((header, index) => { detailText += ` ${index + 1}. ${header.key}: ${header.description || '无描述'}\n`; detailText += ` 类型: ${header.field_type || 'string'}, 必需: ${header.not_null ? '是' : '否'}\n`; if (header.value) detailText += ` 示例: ${header.value}\n`; }); } else { detailText += ` (无Headers参数)\n`; } detailText += `\n`; // Query参数 const queryParams = apiDetail.request?.query?.parameter || []; detailText += `🔍 Query参数 (${queryParams.length}个)\n`; if (queryParams.length > 0) { queryParams.forEach((param, index) => { detailText += ` ${index + 1}. ${param.key}: ${param.description || '无描述'}\n`; detailText += ` 类型: ${param.field_type || 'string'}, 必需: ${param.not_null ? '是' : '否'}\n`; if (param.value) detailText += ` 示例: ${param.value}\n`; }); } else { detailText += ` (无Query参数)\n`; } detailText += `\n`; // Body参数 const bodyParams = apiDetail.request?.body?.raw_parameter || []; detailText += `📝 Body参数 (${bodyParams.length}个)\n`; if (bodyParams.length > 0) { bodyParams.forEach((param, index) => { detailText += ` ${index + 1}. ${param.key}: ${param.description || '无描述'}\n`; detailText += ` 类型: ${param.field_type || 'string'}, 必需: ${param.not_null ? '是' : '否'}\n`; if (param.value) detailText += ` 示例: ${param.value}\n`; }); } else { detailText += ` (无Body参数)\n`; } detailText += `\n`; // Cookies参数 const cookies = apiDetail.request?.cookie?.parameter || []; detailText += `🍪 Cookies参数 (${cookies.length}个)\n`; if (cookies.length > 0) { cookies.forEach((cookie, index) => { detailText += ` ${index + 1}. ${cookie.key}: ${cookie.description || '无描述'}\n`; detailText += ` 类型: ${cookie.field_type || 'string'}, 必需: ${cookie.not_null ? '是' : '否'}\n`; if (cookie.value) detailText += ` 示例: ${cookie.value}\n`; }); } else { detailText += ` (无Cookies参数)\n`; } detailText += `\n`; // 认证配置 const auth = apiDetail.request?.auth || {}; detailText += `🔐 认证配置\n`; if (auth.type && auth.type !== 'inherit') { detailText += ` 类型: ${auth.type}\n`; if (auth.bearer?.key) { detailText += ` Token: ${auth.bearer.key.substring(0, 20)}...\n`; } } else { detailText += ` (继承父级认证或无认证)\n`; } detailText += `\n`; // 响应示例 const responses = apiDetail.response?.example || []; detailText += `📤 响应示例 (${responses.length}个)\n`; if (responses.length > 0) { responses.forEach((resp, index) => { detailText += ` ${index + 1}. ${resp.expect?.name || '响应' + (index + 1)}\n`; detailText += ` 状态码: ${resp.expect?.code || 200}\n`; if (resp.raw) { const rawData = resp.raw.length > 200 ? resp.raw.substring(0, 200) + '...' : resp.raw; detailText += ` 数据: ${rawData}\n`; } }); } else { detailText += ` (无响应示例)\n`; } return { content: [{ type: 'text', text: detailText }] }; case 'apipost_delete': if (!checkSecurityPermission('delete')) { throw new Error(`🔒 安全模式 "${APIPOST_SECURITY_MODE}" 不允许删除操作。需要 "full" 模式。`); } const apiIds = args.api_ids; if (!apiIds || !Array.isArray(apiIds) || apiIds.length === 0) { throw new Error('请提供要删除的API接口ID数组'); } const deleteData = { project_id: currentWorkspace.projectId, target_ids: apiIds }; const deleteResult = await apiClient.post('/open/apis/delete', deleteData); if (deleteResult.data.code !== 0) { throw new Error(`删除失败: ${deleteResult.data.msg}`); } let deleteText = `批量删除完成!\n删除数量: ${apiIds.length} 个接口\n删除的ID:\n`; apiIds.forEach((id, index) => { deleteText += `${index + 1}. ${id}\n`; }); return { content: [{ type: 'text', text: deleteText }] }; default: throw new Error(`未知工具: ${name}`); } } catch (error) { // 增强错误信息,包含文件位置和堆栈信息 let detailedError = ''; if (error instanceof Error) { detailedError = `${error.message}`; // 提取堆栈信息中的关键位置 if (error.stack) { const stackLines = error.stack.split('\n'); const relevantLines = stackLines .filter(line => line.includes('index.ts') || line.includes('apipost-mcp')) .slice(0, 3); if (relevantLines.length > 0) { detailedError += `\n\n📍 错误位置:\n${relevantLines.join('\n')}`; } } } else { detailedError = String(error); } const errorMsg = `工具 '${name}' 执行失败:\n${detailedError}`; logWithTime(`❌ 工具 '${name}' 执行失败: ${error instanceof Error ? error.message : String(error)}`, startTime); return { content: [{ type: 'text', text: `❌ ${errorMsg}\n\n💡 调试提示:\n• 检查传入的参数是否正确\n• 确认接口ID是否存在\n• 验证网络连接和API权限` }], isError: true }; } }); // 启动服务器 async function main() { try { const mainStartTime = Date.now(); console.error('='.repeat(50)); console.error('🚀 ApiPost MCP 启动中...'); console.error(`🔗 连接到: ${APIPOST_HOST}`); console.error(`🔐 Token: ${APIPOST_TOKEN?.substring(0, 8)}...`); // 预初始化工作空间以提高首次调用速度(在MCP连接前完成,避免日志重复) try { console.error('🔄 预初始化工作空间...'); await initWorkspace(mainStartTime); console.error('✨ 工作空间预初始化完成'); } catch (error) { console.error('⚠️ 工作空间预初始化失败,将在首次调用时重试:', error instanceof Error ? error.message : String(error)); // 不阻止服务器启动,在工具调用时再尝试初始化 } const transport = new StdioServerTransport(); await server.connect(transport); console.error('✅ ApiPost MCP 启动成功!'); console.error('📊 可用工具: apipost_create_folder, apipost_smart_create, apipost_list, apipost_update, apipost_delete'); console.error('📈 等待工具调用...'); console.error('='.repeat(50)); } catch (error) { console.error('❌ 启动失败:', error); process.exit(1); } } main();

Latest Blog Posts

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/jlcodes99/apipost-mcp'

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