Add files via upload

This commit is contained in:
公明
2026-01-28 20:02:06 +08:00
committed by GitHub
parent 068dbc1209
commit a99387fd6d
3 changed files with 409 additions and 46 deletions

View File

@@ -264,7 +264,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
},
},
"LoginRequest": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"password"},
"properties": map[string]interface{}{
"password": map[string]interface{}{
@@ -292,7 +292,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
},
},
"ChangePasswordRequest": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"oldPassword", "newPassword"},
"properties": map[string]interface{}{
"oldPassword": map[string]interface{}{
@@ -306,7 +306,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
},
},
"UpdateConversationRequest": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"title"},
"properties": map[string]interface{}{
"title": map[string]interface{}{
@@ -343,7 +343,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
},
},
"CreateGroupRequest": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"name"},
"properties": map[string]interface{}{
"name": map[string]interface{}{
@@ -357,7 +357,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
},
},
"UpdateGroupRequest": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"name"},
"properties": map[string]interface{}{
"name": map[string]interface{}{
@@ -371,7 +371,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
},
},
"AddConversationToGroupRequest": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"conversationId", "groupId"},
"properties": map[string]interface{}{
"conversationId": map[string]interface{}{
@@ -385,7 +385,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
},
},
"BatchTaskRequest": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"tasks"},
"properties": map[string]interface{}{
"title": map[string]interface{}{
@@ -436,7 +436,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
},
},
"CancelAgentLoopRequest": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"conversationId"},
"properties": map[string]interface{}{
"conversationId": map[string]interface{}{
@@ -465,7 +465,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
},
},
"CreateVulnerabilityRequest": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"conversation_id", "title", "severity"},
"properties": map[string]interface{}{
"conversation_id": map[string]interface{}{
@@ -657,7 +657,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
},
},
"CreateSkillRequest": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"name", "description"},
"properties": map[string]interface{}{
"name": map[string]interface{}{
@@ -789,7 +789,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
},
},
"AddOrUpdateExternalMCPRequest": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"config"},
"properties": map[string]interface{}{
"config": map[string]interface{}{
@@ -802,9 +802,9 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"description": "攻击链数据",
},
"MCPMessage": map[string]interface{}{
"type": "object",
"type": "object",
"description": "MCP消息符合JSON-RPC 2.0规范)",
"required": []string{"jsonrpc"},
"required": []string{"jsonrpc"},
"properties": map[string]interface{}{
"id": map[string]interface{}{
"description": "消息ID可以是字符串、数字或null。对于请求必须提供对于通知可以省略",
@@ -843,7 +843,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
},
},
"MCPInitializeParams": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"protocolVersion", "capabilities", "clientInfo"},
"properties": map[string]interface{}{
"protocolVersion": map[string]interface{}{
@@ -856,7 +856,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"description": "客户端能力",
},
"clientInfo": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"name", "version"},
"properties": map[string]interface{}{
"name": map[string]interface{}{
@@ -874,7 +874,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
},
},
"MCPCallToolParams": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"name", "arguments"},
"properties": map[string]interface{}{
"name": map[string]interface{}{
@@ -908,7 +908,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"type": "object",
},
"error": map[string]interface{}{
"type": "object",
"type": "object",
"description": "错误信息(如果执行失败)",
"properties": map[string]interface{}{
"code": map[string]interface{}{
@@ -1084,7 +1084,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"post": map[string]interface{}{
"tags": []string{"对话管理"},
"summary": "创建对话",
"description": "创建一个新的安全测试对话。\n\n**重要说明**\n- ✅ 创建的对话会**立即保存到数据库**\n- ✅ 前端页面会**自动刷新**显示新对话\n- ✅ 与前端创建的对话**完全一致**\n\n**创建对话的两种方式**\n\n**方式1推荐** 直接使用 `/api/agent-loop` 发送消息,**不提供** `conversationId` 参数,系统会自动创建新对话并发送消息。这是最简单的方式,一步完成创建和发送。\n\n**方式2** 先调用此端点创建空对话,然后使用返回的 `conversationId` 调用 `/api/agent-loop` 发送消息。适用于需要先创建对话,稍后再发送消息的场景。\n\n**示例**\n```json\n{\n \"title\": \"Web应用安全测试\"\n}\n```",
"description": "创建一个新的安全测试对话。\n**重要说明**\n- ✅ 创建的对话会**立即保存到数据库**\n- ✅ 前端页面会**自动刷新**显示新对话\n- ✅ 与前端创建的对话**完全一致**\n**创建对话的两种方式**\n**方式1推荐** 直接使用 `/api/agent-loop` 发送消息,**不提供** `conversationId` 参数,系统会自动创建新对话并发送消息。这是最简单的方式,一步完成创建和发送。\n**方式2** 先调用此端点创建空对话,然后使用返回的 `conversationId` 调用 `/api/agent-loop` 发送消息。适用于需要先创建对话,稍后再发送消息的场景。\n**示例**\n```json\n{\n \"title\": \"Web应用安全测试\"\n}\n```",
"operationId": "createConversation",
"requestBody": map[string]interface{}{
"required": true,
@@ -1348,7 +1348,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"post": map[string]interface{}{
"tags": []string{"对话交互"},
"summary": "发送消息并获取AI回复非流式",
"description": "向AI发送消息并获取回复非流式响应。**这是与AI交互的核心端点**,与前端聊天功能完全一致。\n\n**重要说明**\n- ✅ 通过此API创建/发送的消息会**立即保存到数据库**\n- ✅ 前端页面会**自动刷新**显示新创建的对话和消息\n- ✅ 所有操作都有**完整的交互痕迹**,就像在前端操作一样\n- ✅ 支持角色配置,可以指定使用哪个测试角色\n\n**推荐使用流程**\n\n1. **先创建对话**:调用 `POST /api/conversations` 创建新对话,获取 `conversationId`\n2. **再发送消息**:使用返回的 `conversationId` 调用此端点发送消息\n\n**使用示例**\n\n**步骤1 - 创建对话:**\n```json\nPOST /api/conversations\n{\n \"title\": \"Web应用安全测试\"\n}\n```\n\n**步骤2 - 发送消息:**\n```json\nPOST /api/agent-loop\n{\n \"conversationId\": \"返回的对话ID\",\n \"message\": \"扫描 http://example.com 的SQL注入漏洞\",\n \"role\": \"渗透测试\"\n}\n```\n\n**其他方式**\n\n如果不提供 `conversationId`,系统会自动创建新对话并发送消息。但**推荐先创建对话**,这样可以更好地管理对话列表。\n\n**响应**返回AI的回复、对话ID和MCP执行ID列表。前端会自动刷新显示新消息。",
"description": "向AI发送消息并获取回复非流式响应。**这是与AI交互的核心端点**,与前端聊天功能完全一致。\n**重要说明**\n- ✅ 通过此API创建/发送的消息会**立即保存到数据库**\n- ✅ 前端页面会**自动刷新**显示新创建的对话和消息\n- ✅ 所有操作都有**完整的交互痕迹**,就像在前端操作一样\n- ✅ 支持角色配置,可以指定使用哪个测试角色\n**推荐使用流程**\n1. **先创建对话**:调用 `POST /api/conversations` 创建新对话,获取 `conversationId`\n2. **再发送消息**:使用返回的 `conversationId` 调用此端点发送消息\n**使用示例**\n**步骤1 - 创建对话:**\n```json\nPOST /api/conversations\n{\n \"title\": \"Web应用安全测试\"\n}\n```\n**步骤2 - 发送消息:**\n```json\nPOST /api/agent-loop\n{\n \"conversationId\": \"返回的对话ID\",\n \"message\": \"扫描 http://example.com 的SQL注入漏洞\",\n \"role\": \"渗透测试\"\n}\n```\n**其他方式**\n如果不提供 `conversationId`,系统会自动创建新对话并发送消息。但**推荐先创建对话**,这样可以更好地管理对话列表。\n**响应**返回AI的回复、对话ID和MCP执行ID列表。前端会自动刷新显示新消息。",
"operationId": "sendMessage",
"requestBody": map[string]interface{}{
"required": true,
@@ -1427,7 +1427,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"post": map[string]interface{}{
"tags": []string{"对话交互"},
"summary": "发送消息并获取AI回复流式",
"description": "向AI发送消息并获取流式回复Server-Sent Events。**这是与AI交互的核心端点**,与前端聊天功能完全一致。\n\n**重要说明**\n- ✅ 通过此API创建/发送的消息会**立即保存到数据库**\n- ✅ 前端页面会**自动刷新**显示新创建的对话和消息\n- ✅ 所有操作都有**完整的交互痕迹**,就像在前端操作一样\n- ✅ 支持角色配置,可以指定使用哪个测试角色\n- ✅ 返回流式响应适合实时显示AI回复\n\n**推荐使用流程**\n\n1. **先创建对话**:调用 `POST /api/conversations` 创建新对话,获取 `conversationId`\n2. **再发送消息**:使用返回的 `conversationId` 调用此端点发送消息\n\n**使用示例**\n\n**步骤1 - 创建对话:**\n```json\nPOST /api/conversations\n{\n \"title\": \"Web应用安全测试\"\n}\n```\n\n**步骤2 - 发送消息(流式):**\n```json\nPOST /api/agent-loop/stream\n{\n \"conversationId\": \"返回的对话ID\",\n \"message\": \"扫描 http://example.com 的SQL注入漏洞\",\n \"role\": \"渗透测试\"\n}\n```\n\n**响应格式**Server-Sent Events (SSE),事件类型包括:\n- `message`: 用户消息确认\n- `response`: AI回复片段\n- `progress`: 进度更新\n- `done`: 完成\n- `error`: 错误\n- `cancelled`: 已取消",
"description": "向AI发送消息并获取流式回复Server-Sent Events。**这是与AI交互的核心端点**,与前端聊天功能完全一致。\n**重要说明**\n- ✅ 通过此API创建/发送的消息会**立即保存到数据库**\n- ✅ 前端页面会**自动刷新**显示新创建的对话和消息\n- ✅ 所有操作都有**完整的交互痕迹**,就像在前端操作一样\n- ✅ 支持角色配置,可以指定使用哪个测试角色\n- ✅ 返回流式响应适合实时显示AI回复\n**推荐使用流程**\n1. **先创建对话**:调用 `POST /api/conversations` 创建新对话,获取 `conversationId`\n2. **再发送消息**:使用返回的 `conversationId` 调用此端点发送消息\n**使用示例**\n**步骤1 - 创建对话:**\n```json\nPOST /api/conversations\n{\n \"title\": \"Web应用安全测试\"\n}\n```\n**步骤2 - 发送消息(流式):**\n```json\nPOST /api/agent-loop/stream\n{\n \"conversationId\": \"返回的对话ID\",\n \"message\": \"扫描 http://example.com 的SQL注入漏洞\",\n \"role\": \"渗透测试\"\n}\n```\n**响应格式**Server-Sent Events (SSE),事件类型包括:\n- `message`: 用户消息确认\n- `response`: AI回复片段\n- `progress`: 进度更新\n- `done`: 完成\n- `error`: 错误\n- `cancelled`: 已取消",
"operationId": "sendMessageStream",
"requestBody": map[string]interface{}{
"required": true,
@@ -1506,16 +1506,16 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"type": "object",
"properties": map[string]interface{}{
"status": map[string]interface{}{
"type": "string",
"example": "cancelling",
"type": "string",
"example": "cancelling",
},
"conversationId": map[string]interface{}{
"type": "string",
"description": "对话ID",
},
"message": map[string]interface{}{
"type": "string",
"example": "已提交取消请求,任务将在当前步骤完成后停止。",
"type": "string",
"example": "已提交取消请求,任务将在当前步骤完成后停止。",
},
},
},
@@ -1799,7 +1799,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"post": map[string]interface{}{
"tags": []string{"批量任务"},
"summary": "添加任务到队列",
"description": "向批量任务队列添加新任务。任务会添加到队列末尾,按照队列顺序依次执行。每个任务会创建一个独立的对话,支持完整的状态跟踪。\n\n**任务格式**\n任务内容是一个字符串描述要执行的安全测试任务。例如\n- \"扫描 http://example.com 的SQL注入漏洞\"\n- \"对 192.168.1.1 进行端口扫描\"\n- \"检测 https://target.com 的XSS漏洞\"\n\n**使用示例**\n```json\n{\n \"task\": \"扫描 http://example.com 的SQL注入漏洞\"\n}\n```",
"description": "向批量任务队列添加新任务。任务会添加到队列末尾,按照队列顺序依次执行。每个任务会创建一个独立的对话,支持完整的状态跟踪。\n**任务格式**\n任务内容是一个字符串描述要执行的安全测试任务。例如\n- \"扫描 http://example.com 的SQL注入漏洞\"\n- \"对 192.168.1.1 进行端口扫描\"\n- \"检测 https://target.com 的XSS漏洞\"\n**使用示例**\n```json\n{\n \"task\": \"扫描 http://example.com 的SQL注入漏洞\"\n}\n```",
"operationId": "addBatchTask",
"parameters": []map[string]interface{}{
{
@@ -1817,7 +1817,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"content": map[string]interface{}{
"application/json": map[string]interface{}{
"schema": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"task"},
"properties": map[string]interface{}{
"task": map[string]interface{}{
@@ -2830,7 +2830,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"content": map[string]interface{}{
"application/json": map[string]interface{}{
"schema": map[string]interface{}{
"type": "object",
"type": "object",
"description": "统计信息",
},
},
@@ -3198,7 +3198,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"content": map[string]interface{}{
"application/json": map[string]interface{}{
"schema": map[string]interface{}{
"type": "object",
"type": "object",
"description": "统计信息",
},
},
@@ -3272,7 +3272,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"content": map[string]interface{}{
"application/json": map[string]interface{}{
"schema": map[string]interface{}{
"type": "array",
"type": "array",
"description": "工具配置列表",
},
},
@@ -3348,7 +3348,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"content": map[string]interface{}{
"application/json": map[string]interface{}{
"schema": map[string]interface{}{
"type": "object",
"type": "object",
"description": "统计信息",
},
},
@@ -3399,7 +3399,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"put": map[string]interface{}{
"tags": []string{"外部MCP管理"},
"summary": "添加或更新外部MCP",
"description": "添加新的外部MCP配置或更新现有配置。\n\n**传输方式**\n支持两种传输方式\n\n**1. stdio标准输入输出**\n```json\n{\n \"config\": {\n \"enabled\": true,\n \"command\": \"node\",\n \"args\": [\"/path/to/mcp-server.js\"],\n \"env\": {}\n }\n}\n```\n\n**2. sseServer-Sent Events**\n```json\n{\n \"config\": {\n \"enabled\": true,\n \"transport\": \"sse\",\n \"url\": \"http://127.0.0.1:8082/sse\",\n \"timeout\": 30\n }\n}\n```\n\n**配置参数说明**\n- `enabled`: 是否启用boolean必需\n- `command`: 命令stdio模式必需\"node\", \"python\"\n- `args`: 命令参数数组stdio模式必需\n- `env`: 环境变量object可选\n- `transport`: 传输方式(\"stdio\" 或 \"sse\"sse模式必需\n- `url`: SSE端点URLsse模式必需\n- `timeout`: 超时时间可选默认30\n- `description`: 描述(可选)",
"description": "添加新的外部MCP配置或更新现有配置。\n**传输方式**\n支持两种传输方式\n**1. stdio标准输入输出**\n```json\n{\n \"config\": {\n \"enabled\": true,\n \"command\": \"node\",\n \"args\": [\"/path/to/mcp-server.js\"],\n \"env\": {}\n }\n}\n```\n**2. sseServer-Sent Events**\n```json\n{\n \"config\": {\n \"enabled\": true,\n \"transport\": \"sse\",\n \"url\": \"http://127.0.0.1:8082/sse\",\n \"timeout\": 30\n }\n}\n```\n**配置参数说明**\n- `enabled`: 是否启用boolean必需\n- `command`: 命令stdio模式必需\"node\", \"python\"\n- `args`: 命令参数数组stdio模式必需\n- `env`: 环境变量object可选\n- `transport`: 传输方式(\"stdio\" 或 \"sse\"sse模式必需\n- `url`: SSE端点URLsse模式必需\n- `timeout`: 超时时间可选默认30\n- `description`: 描述(可选)",
"operationId": "addOrUpdateExternalMCP",
"parameters": []map[string]interface{}{
{
@@ -3460,8 +3460,8 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"type": "object",
"properties": map[string]interface{}{
"message": map[string]interface{}{
"type": "string",
"example": "外部MCP配置已保存",
"type": "string",
"example": "外部MCP配置已保存",
},
},
},
@@ -3671,7 +3671,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"content": map[string]interface{}{
"application/json": map[string]interface{}{
"schema": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"pinned"},
"properties": map[string]interface{}{
"pinned": map[string]interface{}{
@@ -3718,7 +3718,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"content": map[string]interface{}{
"application/json": map[string]interface{}{
"schema": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"pinned"},
"properties": map[string]interface{}{
"pinned": map[string]interface{}{
@@ -3774,7 +3774,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"content": map[string]interface{}{
"application/json": map[string]interface{}{
"schema": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"pinned"},
"properties": map[string]interface{}{
"pinned": map[string]interface{}{
@@ -3877,7 +3877,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"content": map[string]interface{}{
"application/json": map[string]interface{}{
"schema": map[string]interface{}{
"type": "object",
"type": "object",
"description": "知识项数据",
},
},
@@ -3946,7 +3946,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"content": map[string]interface{}{
"application/json": map[string]interface{}{
"schema": map[string]interface{}{
"type": "object",
"type": "object",
"description": "知识项数据",
},
},
@@ -4074,14 +4074,14 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"post": map[string]interface{}{
"tags": []string{"知识库"},
"summary": "搜索知识库",
"description": "在知识库中搜索相关内容。使用向量检索和混合搜索技术,能够根据查询内容的语义相似度和关键词匹配,自动找到最相关的知识片段。\n\n**搜索说明**\n- 支持语义相似度搜索(向量检索)\n- 支持关键词匹配BM25\n- 支持混合搜索(结合向量和关键词)\n- 可以按风险类型过滤SQL注入、XSS、文件上传等\n- 建议先调用 `/api/knowledge/categories` 获取可用的风险类型列表\n\n**使用示例**\n```json\n{\n \"query\": \"SQL注入漏洞的检测方法\",\n \"riskType\": \"SQL注入\",\n \"topK\": 5,\n \"threshold\": 0.7\n}\n```",
"description": "在知识库中搜索相关内容。使用向量检索和混合搜索技术,能够根据查询内容的语义相似度和关键词匹配,自动找到最相关的知识片段。\n**搜索说明**\n- 支持语义相似度搜索(向量检索)\n- 支持关键词匹配BM25\n- 支持混合搜索(结合向量和关键词)\n- 可以按风险类型过滤SQL注入、XSS、文件上传等\n- 建议先调用 `/api/knowledge/categories` 获取可用的风险类型列表\n**使用示例**\n```json\n{\n \"query\": \"SQL注入漏洞的检测方法\",\n \"riskType\": \"SQL注入\",\n \"topK\": 5,\n \"threshold\": 0.7\n}\n```",
"operationId": "searchKnowledge",
"requestBody": map[string]interface{}{
"required": true,
"content": map[string]interface{}{
"application/json": map[string]interface{}{
"schema": map[string]interface{}{
"type": "object",
"type": "object",
"required": []string{"query"},
"properties": map[string]interface{}{
"query": map[string]interface{}{
@@ -4098,8 +4098,8 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"type": "integer",
"description": "可选返回Top-K结果数量默认5",
"default": 5,
"minimum": 1,
"maximum": 50,
"minimum": 1,
"maximum": 50,
"example": 5,
},
"threshold": map[string]interface{}{
@@ -4280,7 +4280,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"post": map[string]interface{}{
"tags": []string{"MCP"},
"summary": "MCP端点",
"description": "MCP (Model Context Protocol) 端点用于处理MCP协议请求。\n\n**协议说明**\n本端点遵循 JSON-RPC 2.0 规范,支持以下方法:\n\n**1. initialize** - 初始化MCP连接\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"init-1\",\n \"method\": \"initialize\",\n \"params\": {\n \"protocolVersion\": \"2024-11-05\",\n \"capabilities\": {},\n \"clientInfo\": {\n \"name\": \"MyClient\",\n \"version\": \"1.0.0\"\n }\n }\n}\n```\n\n**2. tools/list** - 列出所有可用工具\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"list-1\",\n \"method\": \"tools/list\",\n \"params\": {}\n}\n```\n\n**3. tools/call** - 调用工具\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"call-1\",\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"nmap\",\n \"arguments\": {\n \"target\": \"192.168.1.1\",\n \"ports\": \"80,443\"\n }\n }\n}\n```\n\n**4. prompts/list** - 列出所有提示词模板\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"prompts-list-1\",\n \"method\": \"prompts/list\",\n \"params\": {}\n}\n```\n\n**5. prompts/get** - 获取提示词模板\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"prompt-get-1\",\n \"method\": \"prompts/get\",\n \"params\": {\n \"name\": \"prompt-name\",\n \"arguments\": {}\n }\n}\n```\n\n**6. resources/list** - 列出所有资源\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"resources-list-1\",\n \"method\": \"resources/list\",\n \"params\": {}\n}\n```\n\n**7. resources/read** - 读取资源内容\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"resource-read-1\",\n \"method\": \"resources/read\",\n \"params\": {\n \"uri\": \"resource://example\"\n }\n}\n```\n\n**错误代码说明**\n- `-32700`: Parse error - JSON解析错误\n- `-32600`: Invalid Request - 无效请求\n- `-32601`: Method not found - 方法不存在\n- `-32602`: Invalid params - 参数无效\n- `-32603`: Internal error - 内部错误",
"description": "MCP (Model Context Protocol) 端点用于处理MCP协议请求。\n**协议说明**\n本端点遵循 JSON-RPC 2.0 规范,支持以下方法:\n**1. initialize** - 初始化MCP连接\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"init-1\",\n \"method\": \"initialize\",\n \"params\": {\n \"protocolVersion\": \"2024-11-05\",\n \"capabilities\": {},\n \"clientInfo\": {\n \"name\": \"MyClient\",\n \"version\": \"1.0.0\"\n }\n }\n}\n```\n**2. tools/list** - 列出所有可用工具\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"list-1\",\n \"method\": \"tools/list\",\n \"params\": {}\n}\n```\n**3. tools/call** - 调用工具\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"call-1\",\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"nmap\",\n \"arguments\": {\n \"target\": \"192.168.1.1\",\n \"ports\": \"80,443\"\n }\n }\n}\n```\n**4. prompts/list** - 列出所有提示词模板\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"prompts-list-1\",\n \"method\": \"prompts/list\",\n \"params\": {}\n}\n```\n**5. prompts/get** - 获取提示词模板\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"prompt-get-1\",\n \"method\": \"prompts/get\",\n \"params\": {\n \"name\": \"prompt-name\",\n \"arguments\": {}\n }\n}\n```\n**6. resources/list** - 列出所有资源\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"resources-list-1\",\n \"method\": \"resources/list\",\n \"params\": {}\n}\n```\n**7. resources/read** - 读取资源内容\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"resource-read-1\",\n \"method\": \"resources/read\",\n \"params\": {\n \"uri\": \"resource://example\"\n }\n}\n```\n**错误代码说明**\n- `-32700`: Parse error - JSON解析错误\n- `-32600`: Invalid Request - 无效请求\n- `-32601`: Method not found - 方法不存在\n- `-32602`: Invalid params - 参数无效\n- `-32603`: Internal error - 内部错误",
"operationId": "mcpEndpoint",
"requestBody": map[string]interface{}{
"required": true,
@@ -4325,7 +4325,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"method": "initialize",
"params": map[string]interface{}{
"protocolVersion": "2024-11-05",
"capabilities": map[string]interface{}{},
"capabilities": map[string]interface{}{},
"clientInfo": map[string]interface{}{
"name": "MyClient",
"version": "1.0.0",
@@ -4388,7 +4388,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
"$ref": "#/components/schemas/MCPResponse",
},
"example": map[string]interface{}{
"id": nil,
"id": nil,
"error": map[string]interface{}{
"code": -32700,
"message": "Parse error",

View File

@@ -205,7 +205,20 @@ function createEndpointCard(endpoint) {
<div class="api-endpoint-body">
<div class="api-section">
<div class="api-section-title">描述</div>
<div class="api-description">${endpoint.summary || endpoint.description || '无描述'}</div>
${endpoint.summary ? `<div class="api-description" style="font-weight: 500; margin-bottom: 8px; color: var(--text-primary);">${escapeHtml(endpoint.summary)}</div>` : ''}
${endpoint.description ? `
<div class="api-description-toggle">
<button class="description-toggle-btn" onclick="toggleDescription(this)">
<svg class="description-toggle-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"/>
</svg>
<span>查看详细说明</span>
</button>
<div class="api-description-detail" style="display: none;">
${formatDescription(endpoint.description)}
</div>
</div>
` : endpoint.summary ? '' : '<div class="api-description">无描述</div>'}
</div>
${renderParameters(endpoint)}
@@ -756,6 +769,147 @@ function copyCurlCommand(event, method, path) {
}
}
// 格式化描述文本处理markdown格式
function formatDescription(text) {
if (!text) return '';
// 先提取代码块避免代码块内的markdown被处理
let formatted = text;
const codeBlocks = [];
let codeBlockIndex = 0;
// 提取代码块(支持语言标识符,如 ```json 或 ```javascript
formatted = formatted.replace(/```(\w+)?\s*\n?([\s\S]*?)```/g, (match, lang, code) => {
const placeholder = `__CODE_BLOCK_${codeBlockIndex}__`;
codeBlocks[codeBlockIndex] = {
lang: (lang && lang.trim()) || '',
code: code.trim()
};
codeBlockIndex++;
return placeholder;
});
// 提取行内代码避免行内代码内的markdown被处理
const inlineCodes = [];
let inlineCodeIndex = 0;
formatted = formatted.replace(/`([^`\n]+)`/g, (match, code) => {
const placeholder = `__INLINE_CODE_${inlineCodeIndex}__`;
inlineCodes[inlineCodeIndex] = code;
inlineCodeIndex++;
return placeholder;
});
// 转义HTML但保留占位符
formatted = escapeHtml(formatted);
// 恢复行内代码(需要转义,因为占位符已经被转义了)
inlineCodes.forEach((code, index) => {
formatted = formatted.replace(
`__INLINE_CODE_${index}__`,
`<code class="inline-code">${escapeHtml(code)}</code>`
);
});
// 恢复代码块(代码块内容已经转义过,直接使用)
codeBlocks.forEach((block, index) => {
const langLabel = block.lang ? `<span class="code-lang">${escapeHtml(block.lang)}</span>` : '';
// 代码块内容已经在提取时保存,不需要再次转义
formatted = formatted.replace(
`__CODE_BLOCK_${index}__`,
`<pre class="code-block">${langLabel}<code>${escapeHtml(block.code)}</code></pre>`
);
});
// 处理标题(### 标题)
formatted = formatted.replace(/^###\s+(.+)$/gm, '<h3 class="md-h3">$1</h3>');
formatted = formatted.replace(/^##\s+(.+)$/gm, '<h2 class="md-h2">$1</h2>');
formatted = formatted.replace(/^#\s+(.+)$/gm, '<h1 class="md-h1">$1</h1>');
// 处理加粗文本(**text** 或 __text__
formatted = formatted.replace(/\*\*([^*]+?)\*\*/g, '<strong>$1</strong>');
formatted = formatted.replace(/__([^_]+?)__/g, '<strong>$1</strong>');
// 处理斜体(*text* 或 _text_但不与加粗冲突
formatted = formatted.replace(/(?<!\*)\*([^*\n]+?)\*(?!\*)/g, '<em>$1</em>');
formatted = formatted.replace(/(?<!_)_([^_\n]+?)_(?!_)/g, '<em>$1</em>');
// 处理链接 [text](url)
formatted = formatted.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer" class="md-link">$1</a>');
// 处理列表项(有序和无序)
const lines = formatted.split('\n');
const result = [];
let inUnorderedList = false;
let inOrderedList = false;
let orderedListStart = 1;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const unorderedMatch = line.match(/^[-*]\s+(.+)$/);
const orderedMatch = line.match(/^\d+\.\s+(.+)$/);
if (unorderedMatch) {
if (inOrderedList) {
result.push('</ol>');
inOrderedList = false;
}
if (!inUnorderedList) {
result.push('<ul class="md-list">');
inUnorderedList = true;
}
result.push(`<li class="md-list-item">${unorderedMatch[1]}</li>`);
} else if (orderedMatch) {
if (inUnorderedList) {
result.push('</ul>');
inUnorderedList = false;
}
if (!inOrderedList) {
result.push('<ol class="md-list">');
inOrderedList = true;
orderedListStart = parseInt(line.match(/^(\d+)\./)[1]) || 1;
}
result.push(`<li class="md-list-item">${orderedMatch[1]}</li>`);
} else {
if (inUnorderedList) {
result.push('</ul>');
inUnorderedList = false;
}
if (inOrderedList) {
result.push('</ol>');
inOrderedList = false;
}
if (line.trim()) {
result.push(line);
} else if (i < lines.length - 1) {
// 只在非最后一行时添加换行
result.push('<br>');
}
}
}
if (inUnorderedList) {
result.push('</ul>');
}
if (inOrderedList) {
result.push('</ol>');
}
formatted = result.join('\n');
// 处理段落(连续的空行分隔段落)
formatted = formatted.replace(/(<br>\s*){2,}/g, '</p><p class="md-paragraph">');
formatted = '<p class="md-paragraph">' + formatted + '</p>';
// 清理多余的<br>标签(在块级元素前后)
formatted = formatted.replace(/(<\/?(h[1-6]|ul|ol|li|pre|p)[^>]*>)\s*<br>/gi, '$1');
formatted = formatted.replace(/<br>\s*(<\/?(h[1-6]|ul|ol|li|pre|p)[^>]*>)/gi, '$1');
// 将剩余的单个换行符转换为<br>(但避免在块级元素内)
formatted = formatted.replace(/\n(?!<\/?(h[1-6]|ul|ol|li|pre|p|code))/g, '<br>');
return formatted;
}
// HTML转义
function escapeHtml(text) {
const div = document.createElement('div');
@@ -767,3 +921,20 @@ function escapeHtml(text) {
function escapeId(text) {
return text.replace(/[{}]/g, '').replace(/\//g, '-');
}
// 切换描述显示/隐藏
function toggleDescription(button) {
const icon = button.querySelector('.description-toggle-icon');
const detail = button.parentElement.querySelector('.api-description-detail');
const span = button.querySelector('span');
if (detail.style.display === 'none') {
detail.style.display = 'block';
icon.style.transform = 'rotate(180deg)';
span.textContent = '隐藏详细说明';
} else {
detail.style.display = 'none';
icon.style.transform = 'rotate(0deg)';
span.textContent = '查看详细说明';
}
}

View File

@@ -278,6 +278,198 @@
margin-bottom: 16px;
}
.api-description strong {
color: var(--text-primary);
font-weight: 600;
}
.api-description code {
background: var(--bg-secondary);
padding: 2px 6px;
border-radius: 4px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.875em;
color: var(--accent-color);
}
.api-description pre {
background: var(--bg-secondary);
padding: 12px;
border-radius: 6px;
overflow-x: auto;
margin: 8px 0;
font-size: 0.875rem;
}
.api-description pre code {
background: transparent;
padding: 0;
color: var(--text-secondary);
}
.api-description ul {
margin: 8px 0;
padding-left: 24px;
list-style-type: disc;
}
.api-description li {
margin: 4px 0;
}
/* Markdown样式 */
.api-description-detail .md-h1 {
font-size: 1.5rem;
font-weight: 600;
color: var(--text-primary);
margin: 16px 0 12px 0;
padding-bottom: 8px;
border-bottom: 2px solid var(--border-color);
}
.api-description-detail .md-h2 {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
margin: 14px 0 10px 0;
padding-bottom: 6px;
border-bottom: 1px solid var(--border-color);
}
.api-description-detail .md-h3 {
font-size: 1.125rem;
font-weight: 600;
color: var(--text-primary);
margin: 12px 0 8px 0;
}
.api-description-detail .md-paragraph {
margin: 8px 0;
line-height: 1.6;
}
.api-description-detail .md-list {
margin: 8px 0;
padding-left: 24px;
list-style-type: disc;
}
.api-description-detail .md-list ol {
list-style-type: decimal;
margin: 4px 0;
padding-left: 24px;
}
.api-description-detail .md-list-item {
margin: 4px 0;
line-height: 1.6;
}
.api-description-detail .md-link {
color: var(--accent-color);
text-decoration: none;
border-bottom: 1px solid transparent;
transition: all 0.2s ease;
}
.api-description-detail .md-link:hover {
color: var(--accent-hover);
border-bottom-color: var(--accent-hover);
}
.api-description-detail .inline-code {
background: var(--bg-secondary);
padding: 2px 6px;
border-radius: 4px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.875em;
color: var(--accent-color);
}
.api-description-detail .code-block {
background: var(--bg-secondary);
padding: 12px;
border-radius: 6px;
overflow-x: auto;
margin: 12px 0;
font-size: 0.875rem;
border: 1px solid var(--border-color);
}
.api-description-detail .code-block code {
background: transparent;
padding: 0;
color: var(--text-secondary);
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
display: block;
white-space: pre;
}
.api-description-detail .code-lang {
display: block;
font-size: 0.75rem;
color: var(--text-muted);
margin-bottom: 8px;
text-transform: uppercase;
font-weight: 600;
letter-spacing: 0.5px;
}
.api-description-detail strong {
color: var(--text-primary);
font-weight: 600;
}
.api-description-detail em {
font-style: italic;
color: var(--text-secondary);
}
.api-description-toggle {
margin-top: 8px;
}
.description-toggle-btn {
background: none;
border: none;
color: var(--accent-color);
cursor: pointer;
padding: 6px 0;
font-size: 0.875rem;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s ease;
font-weight: 500;
}
.description-toggle-btn:hover {
color: var(--accent-hover);
opacity: 0.8;
}
.description-toggle-icon {
transition: transform 0.2s ease;
}
.api-description-detail {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--border-color);
animation: slideDown 0.2s ease;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.api-params-table {
width: 100%;
border-collapse: collapse;