@becrafter/prompt-manager 0.0.18 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/IFLOW.md +175 -0
- package/README.md +1 -1
- package/app/desktop/assets/icons/icon.icns +0 -0
- package/app/desktop/assets/icons/icon.ico +0 -0
- package/app/desktop/assets/icons/icon_1024x1024.png +0 -0
- package/app/desktop/assets/icons/icon_128x128.png +0 -0
- package/app/desktop/assets/icons/icon_16x16.png +0 -0
- package/app/desktop/assets/icons/icon_24x24.png +0 -0
- package/app/desktop/assets/icons/icon_256x256.png +0 -0
- package/app/desktop/assets/icons/icon_32x32.png +0 -0
- package/app/desktop/assets/icons/icon_48x48.png +0 -0
- package/app/desktop/assets/icons/icon_512x512.png +0 -0
- package/app/desktop/assets/icons/icon_64x64.png +0 -0
- package/app/desktop/assets/icons/icon_96x96.png +0 -0
- package/app/desktop/assets/templates/about.html +147 -0
- package/app/desktop/main.js +160 -732
- package/app/desktop/package-lock.json +567 -534
- package/app/desktop/package.json +45 -10
- package/app/desktop/preload.js +7 -0
- package/app/desktop/src/core/error-handler.js +108 -0
- package/app/desktop/src/core/event-emitter.js +84 -0
- package/app/desktop/src/core/logger.js +108 -0
- package/app/desktop/src/core/state-manager.js +125 -0
- package/app/desktop/src/services/module-loader.js +193 -0
- package/app/desktop/src/services/runtime-manager.js +152 -0
- package/app/desktop/src/services/service-manager.js +169 -0
- package/app/desktop/src/services/update-manager.js +268 -0
- package/app/desktop/src/ui/about-dialog-manager.js +208 -0
- package/app/desktop/src/ui/tray-manager.js +202 -0
- package/app/desktop/src/utils/icon-manager.js +141 -0
- package/app/desktop/src/utils/path-utils.js +58 -0
- package/app/desktop/src/utils/resource-paths.js +72 -0
- package/app/desktop/src/utils/template-renderer.js +284 -0
- package/app/desktop/src/utils/version-utils.js +59 -0
- package/examples/prompts/engineer/engineer-professional.yaml +92 -0
- package/examples/prompts/engineer/laowang-engineer.yaml +132 -0
- package/examples/prompts/engineer/nekomata-engineer.yaml +123 -0
- package/examples/prompts/engineer/ojousama-engineer.yaml +124 -0
- package/examples/prompts/workflow/sixstep-workflow.yaml +192 -0
- package/package.json +9 -3
- package/packages/admin-ui/admin.html +1 -1
- package/packages/resources/tools/filesystem/filesystem.tool.js +184 -0
- package/packages/resources/tools/index.js +16 -0
- package/packages/server/mcp/mcp.handler.js +108 -9
- package/packages/server/mcp/mcp.server.js +126 -27
- package/packages/server/mcp/toolx.handler.js +131 -0
- package/packages/server/utils/config.js +1 -1
- package/scripts/build-icons.js +105 -0
- package/scripts/icns-builder/package.json +12 -0
- /package/app/desktop/assets/{icon.png → icons/icon.png} +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 文件系统工具
|
|
6
|
+
* @param {Object} params - 工具参数
|
|
7
|
+
* @param {string} mode - 操作模式
|
|
8
|
+
* @returns {Promise<Object>} 执行结果
|
|
9
|
+
*/
|
|
10
|
+
export default async function filesystem(params, mode = 'execute') {
|
|
11
|
+
// 根据模式执行不同的操作
|
|
12
|
+
switch (mode) {
|
|
13
|
+
case 'manual':
|
|
14
|
+
// 生成 Markdown 格式的手册
|
|
15
|
+
return generateManual();
|
|
16
|
+
|
|
17
|
+
case 'execute':
|
|
18
|
+
// 执行模式 - 实际执行操作
|
|
19
|
+
const { action, path: filePath, content } = params;
|
|
20
|
+
|
|
21
|
+
if (!action) {
|
|
22
|
+
throw new Error('缺少必需参数: action');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!filePath) {
|
|
26
|
+
throw new Error('缺少必需参数: path');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
switch (action) {
|
|
30
|
+
case 'read':
|
|
31
|
+
try {
|
|
32
|
+
const fileContent = await fs.readFile(filePath, 'utf8');
|
|
33
|
+
return {
|
|
34
|
+
success: true,
|
|
35
|
+
action: 'read',
|
|
36
|
+
path: filePath,
|
|
37
|
+
content: fileContent
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new Error(`读取文件失败: ${error.message}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
case 'write':
|
|
44
|
+
if (content === undefined) {
|
|
45
|
+
throw new Error('写入文件需要提供 content 参数');
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
49
|
+
return {
|
|
50
|
+
success: true,
|
|
51
|
+
action: 'write',
|
|
52
|
+
path: filePath,
|
|
53
|
+
message: '文件写入成功'
|
|
54
|
+
};
|
|
55
|
+
} catch (error) {
|
|
56
|
+
throw new Error(`写入文件失败: ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
case 'list':
|
|
60
|
+
try {
|
|
61
|
+
const items = await fs.readdir(filePath);
|
|
62
|
+
return {
|
|
63
|
+
success: true,
|
|
64
|
+
action: 'list',
|
|
65
|
+
path: filePath,
|
|
66
|
+
items: items
|
|
67
|
+
};
|
|
68
|
+
} catch (error) {
|
|
69
|
+
throw new Error(`列出目录内容失败: ${error.message}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
case 'delete':
|
|
73
|
+
try {
|
|
74
|
+
const stats = await fs.stat(filePath);
|
|
75
|
+
if (stats.isDirectory()) {
|
|
76
|
+
await fs.rm(filePath, { recursive: true });
|
|
77
|
+
return {
|
|
78
|
+
success: true,
|
|
79
|
+
action: 'delete',
|
|
80
|
+
path: filePath,
|
|
81
|
+
type: 'directory',
|
|
82
|
+
message: '目录删除成功'
|
|
83
|
+
};
|
|
84
|
+
} else {
|
|
85
|
+
await fs.unlink(filePath);
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
action: 'delete',
|
|
89
|
+
path: filePath,
|
|
90
|
+
type: 'file',
|
|
91
|
+
message: '文件删除成功'
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
throw new Error(`删除文件或目录失败: ${error.message}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
default:
|
|
99
|
+
throw new Error(`不支持的操作类型: ${action}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
default:
|
|
103
|
+
throw new Error(`不支持的模式: ${mode}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 生成 Markdown 格式的手册
|
|
109
|
+
* @returns {string} Markdown 格式的手册
|
|
110
|
+
*/
|
|
111
|
+
function generateManual() {
|
|
112
|
+
return `# 🔧 filesystem
|
|
113
|
+
|
|
114
|
+
> 文件系统工具 - 用于文件操作
|
|
115
|
+
|
|
116
|
+
## 📋 基础信息
|
|
117
|
+
|
|
118
|
+
- **标识**: \`tool://filesystem\`
|
|
119
|
+
- **分类**: 系统工具
|
|
120
|
+
|
|
121
|
+
## ✅ 适用场景
|
|
122
|
+
|
|
123
|
+
- 读取文件内容进行分析
|
|
124
|
+
- 写入文件内容保存数据
|
|
125
|
+
- 列出目录内容查看文件结构
|
|
126
|
+
- 删除文件或目录进行清理
|
|
127
|
+
|
|
128
|
+
## 📝 参数定义
|
|
129
|
+
|
|
130
|
+
### execute 模式参数
|
|
131
|
+
|
|
132
|
+
| 参数 | 类型 | 必需 | 描述 | 默认值 |
|
|
133
|
+
|------|------|------|------|--------|
|
|
134
|
+
| action | string (read|write|list|delete) | ✅ | 操作类型 | - |
|
|
135
|
+
| path | string | ✅ | 文件或目录路径 | - |
|
|
136
|
+
| content | string | ❌ | 写入的文件内容 (仅在action为write时需要) | - |
|
|
137
|
+
|
|
138
|
+
## 💻 使用示例
|
|
139
|
+
|
|
140
|
+
通过 toolx 调用,使用 YAML 格式:
|
|
141
|
+
|
|
142
|
+
\`\`\`yaml
|
|
143
|
+
# 读取文件内容
|
|
144
|
+
tool: tool://filesystem
|
|
145
|
+
mode: execute
|
|
146
|
+
parameters:
|
|
147
|
+
action: read
|
|
148
|
+
path: /path/to/file.txt
|
|
149
|
+
|
|
150
|
+
# 写入文件内容
|
|
151
|
+
tool: tool://filesystem
|
|
152
|
+
mode: execute
|
|
153
|
+
parameters:
|
|
154
|
+
action: write
|
|
155
|
+
path: /path/to/file.txt
|
|
156
|
+
content: "Hello, World!"
|
|
157
|
+
|
|
158
|
+
# 列出目录内容
|
|
159
|
+
tool: tool://filesystem
|
|
160
|
+
mode: execute
|
|
161
|
+
parameters:
|
|
162
|
+
action: list
|
|
163
|
+
path: /path/to/directory
|
|
164
|
+
|
|
165
|
+
# 删除文件或目录
|
|
166
|
+
tool: tool://filesystem
|
|
167
|
+
mode: execute
|
|
168
|
+
parameters:
|
|
169
|
+
action: delete
|
|
170
|
+
path: /path/to/file-or-directory
|
|
171
|
+
\`\`\`
|
|
172
|
+
|
|
173
|
+
## 🚨 业务错误
|
|
174
|
+
|
|
175
|
+
| 错误码 | 描述 | 解决方案 | 可重试 |
|
|
176
|
+
|--------|------|----------|--------|
|
|
177
|
+
| MISSING_ACTION | 缺少必需参数: action | 提供 action 参数 | ❌ |
|
|
178
|
+
| MISSING_PATH | 缺少必需参数: path | 提供 path 参数 | ❌ |
|
|
179
|
+
| READ_FAILED | 读取文件失败 | 检查文件路径是否存在且可读 | ✅ |
|
|
180
|
+
| WRITE_FAILED | 写入文件失败 | 检查文件路径是否可写 | ✅ |
|
|
181
|
+
| LIST_FAILED | 列出目录内容失败 | 检查目录路径是否存在且可读 | ✅ |
|
|
182
|
+
| DELETE_FAILED | 删除文件或目录失败 | 检查文件或目录是否存在且可删除 | ✅ |
|
|
183
|
+
`;
|
|
184
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolX 工具系统 - 提供各种服务内部工具的统一调用接口
|
|
3
|
+
*
|
|
4
|
+
* 工具目录结构:
|
|
5
|
+
* - 所有工具都位于 /tools 目录下
|
|
6
|
+
* - 每个工具是一个独立的 JS 文件
|
|
7
|
+
* - 工具导出一个默认函数,接受参数和模式作为输入
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// 导出所有可用的工具
|
|
11
|
+
export { default as filesystem } from './filesystem/filesystem.tool.js';
|
|
12
|
+
|
|
13
|
+
// 工具列表
|
|
14
|
+
export const availableTools = [
|
|
15
|
+
'filesystem'
|
|
16
|
+
];
|
|
@@ -9,7 +9,7 @@ export async function handleGetPrompt(args) {
|
|
|
9
9
|
const promptId = args.prompt_id || args.name;
|
|
10
10
|
|
|
11
11
|
if (!promptId) {
|
|
12
|
-
throw new Error("缺少必需参数: prompt_id
|
|
12
|
+
throw new Error("缺少必需参数: prompt_id");
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const promptManager = await util.getPromptManager();
|
|
@@ -39,7 +39,7 @@ export async function handleGetPrompt(args) {
|
|
|
39
39
|
return convertToText({
|
|
40
40
|
success: true,
|
|
41
41
|
prompt: promptInfo
|
|
42
|
-
});
|
|
42
|
+
}, 'detail');
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// 处理 search_prompts 工具调用
|
|
@@ -60,7 +60,7 @@ export async function handleSearchPrompts(args) {
|
|
|
60
60
|
query: searchTerm || '',
|
|
61
61
|
count: simplifiedPrompts.length,
|
|
62
62
|
results: simplifiedPrompts
|
|
63
|
-
});
|
|
63
|
+
}, 'list');
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// 实现相似度匹配算法
|
|
@@ -95,7 +95,7 @@ export async function handleSearchPrompts(args) {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
return convertToText(result);
|
|
98
|
+
return convertToText(result, 'list');
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
@@ -150,17 +150,116 @@ function formatResults(results = []) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
/**
|
|
153
|
-
*
|
|
154
|
-
* @param {*}
|
|
153
|
+
* 处理列表格式输出
|
|
154
|
+
* @param {*} result
|
|
155
|
+
* @returns
|
|
156
|
+
*/
|
|
157
|
+
function formatListOutput(result) {
|
|
158
|
+
// 生成当前时间戳
|
|
159
|
+
const now = new Date();
|
|
160
|
+
const formattedDate = `${now.getFullYear()}/${(now.getMonth() + 1).toString().padStart(2, '0')}/${now.getDate().toString().padStart(2, '0')} ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
|
|
161
|
+
|
|
162
|
+
// 构建新的输出格式
|
|
163
|
+
let output = "";
|
|
164
|
+
output += "[PROMPT_HEADER_AREA]\n";
|
|
165
|
+
output += "🎭 **PromptManager 提示词清单**\n";
|
|
166
|
+
output += `📅 ${formattedDate}\n\n`;
|
|
167
|
+
output += "--------------------------------------------------\n";
|
|
168
|
+
output += "[PROMPT_LIST_AREA]\n\n";
|
|
169
|
+
output += `📦 **提示词列表** (${result.count}个)\n`;
|
|
170
|
+
|
|
171
|
+
// 添加提示词列表
|
|
172
|
+
if (result.results && Array.isArray(result.results) && result.results.length > 0) {
|
|
173
|
+
result.results.forEach(prompt => {
|
|
174
|
+
output += `- [${prompt.id}] ${prompt.name}\n`;
|
|
175
|
+
output += ` - ${prompt.description}\n`;
|
|
176
|
+
});
|
|
177
|
+
} else {
|
|
178
|
+
output += "(无提示词)\n";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
output += "\n--------------------------------------------------\n";
|
|
182
|
+
output += "[STATE_AREA]\n";
|
|
183
|
+
output += "📍 **当前状态**:prompts_completed\n";
|
|
184
|
+
|
|
185
|
+
// 返回格式化文本
|
|
186
|
+
return output;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 处理详情格式输出
|
|
191
|
+
* @param {*} result
|
|
192
|
+
* @returns string
|
|
193
|
+
*/
|
|
194
|
+
function formatDetailOutput(result) {
|
|
195
|
+
const prompt = result.prompt;
|
|
196
|
+
|
|
197
|
+
// 生成当前时间戳
|
|
198
|
+
const now = new Date();
|
|
199
|
+
const formattedDate = `${now.getFullYear()}/${(now.getMonth() + 1).toString().padStart(2, '0')}/${now.getDate().toString().padStart(2, '0')} ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
|
|
200
|
+
|
|
201
|
+
// 构建新的输出格式
|
|
202
|
+
let output = "";
|
|
203
|
+
output += "--------------------------------------------------\n";
|
|
204
|
+
output += "[PROMPT_HEADER_AREA]\n";
|
|
205
|
+
output += `- id: ${prompt.id}\n`;
|
|
206
|
+
output += `- name: ${prompt.name}\n`;
|
|
207
|
+
output += `- description: ${prompt.description}\n`;
|
|
208
|
+
output += `- filepath: ${prompt.filePath}\n\n`;
|
|
209
|
+
output += "--------------------------------------------------\n";
|
|
210
|
+
output += "[PROMPT_PARAMS_AREA]\n";
|
|
211
|
+
|
|
212
|
+
// 添加参数信息
|
|
213
|
+
if (prompt.arguments && Array.isArray(prompt.arguments) && prompt.arguments.length > 0) {
|
|
214
|
+
prompt.arguments.forEach(param => {
|
|
215
|
+
const requiredText = param.required ? "必填" : "非必填";
|
|
216
|
+
output += `- ${param.name}: ${param.type}; ${requiredText}; ${param.description}\n`;
|
|
217
|
+
});
|
|
218
|
+
} else {
|
|
219
|
+
output += "(无参数)\n";
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
output += "\n--------------------------------------------------\n";
|
|
223
|
+
output += "[PROMPT_CONTENT_AREA]\n";
|
|
224
|
+
|
|
225
|
+
// 添加消息内容
|
|
226
|
+
if (prompt.messages && Array.isArray(prompt.messages)) {
|
|
227
|
+
const userMessages = prompt.messages.filter(msg => msg.role !== "");
|
|
228
|
+
if (userMessages.length > 0 && userMessages[0].content && userMessages[0].content.text) {
|
|
229
|
+
output += userMessages[0].content.text + "\n";
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
output += "\n[STATE_AREA]\n";
|
|
234
|
+
output += "📍 **当前状态**:prompt_loaded\n";
|
|
235
|
+
|
|
236
|
+
return output;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 将对象转换为格式化的text类型输出
|
|
241
|
+
* @param {*} result
|
|
242
|
+
* @param {string} format - 输出格式类型: 'list' 或 'detail'
|
|
155
243
|
* @returns
|
|
156
244
|
*/
|
|
157
|
-
function convertToText(result) {
|
|
245
|
+
function convertToText(result, format) {
|
|
246
|
+
let ret = ""
|
|
247
|
+
switch (format) {
|
|
248
|
+
case 'list':
|
|
249
|
+
ret = formatListOutput(result);
|
|
250
|
+
break;
|
|
251
|
+
case 'detail':
|
|
252
|
+
ret = formatDetailOutput(result);
|
|
253
|
+
break;
|
|
254
|
+
default:
|
|
255
|
+
ret =JSON.stringify(result, null, 2);
|
|
256
|
+
}
|
|
158
257
|
return {
|
|
159
258
|
content: [
|
|
160
259
|
{
|
|
161
260
|
type: "text",
|
|
162
|
-
text:
|
|
261
|
+
text: ret
|
|
163
262
|
}
|
|
164
263
|
]
|
|
165
264
|
};
|
|
166
|
-
}
|
|
265
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { config } from '../utils/config.js';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
4
|
import {
|
|
4
5
|
handleGetPrompt,
|
|
5
6
|
handleSearchPrompts,
|
|
6
7
|
handleReloadPrompts
|
|
7
8
|
} from './mcp.handler.js';
|
|
9
|
+
import { handleToolX } from './toolx.handler.js';
|
|
8
10
|
|
|
9
11
|
class Server {
|
|
10
12
|
constructor() {
|
|
@@ -19,7 +21,13 @@ class Server {
|
|
|
19
21
|
|
|
20
22
|
registerTools(tools) {
|
|
21
23
|
for (const tool of tools) {
|
|
22
|
-
this.server.
|
|
24
|
+
this.server.registerTool(tool.name,
|
|
25
|
+
{
|
|
26
|
+
description: tool.description,
|
|
27
|
+
inputSchema: tool.inputSchema,
|
|
28
|
+
},
|
|
29
|
+
tool.handler
|
|
30
|
+
);
|
|
23
31
|
}
|
|
24
32
|
}
|
|
25
33
|
|
|
@@ -31,48 +39,139 @@ class Server {
|
|
|
31
39
|
export const getMcpServer = () => {
|
|
32
40
|
const mcpServer = new Server();
|
|
33
41
|
mcpServer.registerTools([
|
|
42
|
+
{
|
|
43
|
+
name: 'search_prompts',
|
|
44
|
+
description: `功能:智能检索提示词库,匹配用户需求\n描述:根据用户输入内容(可为空)搜索匹配的提示词,返回候选提示词的 ID、名称、简短描述。若输入为空则返回全部提示词列表。帮助用户快速定位适合的提示词,无需记忆具体名称。\n\n示例:\n- 用户:"我想写一首诗" → 工具返回:[ID:001, 名称:诗歌创作, 描述:生成古典/现代风格诗歌]\n- 用户:"(无输入)" → 工具返回:完整提示词库概览`,
|
|
45
|
+
inputSchema: {
|
|
46
|
+
name: z.string().optional().describe('提示词名称或关键词,用于搜索匹配提示词'),
|
|
47
|
+
},
|
|
48
|
+
handler: async (args) => {
|
|
49
|
+
return handleSearchPrompts(args);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
34
52
|
{
|
|
35
53
|
name: 'get_prompt',
|
|
36
|
-
description:
|
|
54
|
+
description: `功能:精准获取并应用提示词详情\n描述:根据提示词 ID 或名称 调用具体内容,自动将其嵌入当前对话上下文,无需用户手动复制。支持通过 search_prompts 返回的 ID/名称直接获取。\n\n示例:\n- 用户:"使用 ID 001" → 工具自动加载诗歌创作提示词并生成内容\n- 用户:"调用'营销文案生成'" → 工具匹配名称后应用对应提示词`,
|
|
37
55
|
inputSchema: {
|
|
38
|
-
|
|
39
|
-
properties: {
|
|
40
|
-
prompt_id: {
|
|
41
|
-
type: 'string',
|
|
42
|
-
description: 'the unique identifier of the prompt to retrieve'
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
required: ['prompt_id']
|
|
56
|
+
prompt_id: z.string().describe('提示词的唯一标识 ID/名称'),
|
|
46
57
|
},
|
|
47
58
|
handler: async (args) => {
|
|
48
59
|
return handleGetPrompt(args);
|
|
49
60
|
}
|
|
50
61
|
},
|
|
51
62
|
{
|
|
52
|
-
name: '
|
|
53
|
-
description:
|
|
63
|
+
name: 'toolx',
|
|
64
|
+
description: `ToolX is the Prompt Manager tool runtime for loading and executing various tools.
|
|
65
|
+
|
|
66
|
+
## Why ToolX Exists
|
|
67
|
+
|
|
68
|
+
ToolX is your gateway to the Prompt Manager tool ecosystem. Think of it as:
|
|
69
|
+
- A **universal remote control** for all Prompt Manager tools
|
|
70
|
+
- Your **interface** to specialized capabilities (file operations, etc.)
|
|
71
|
+
- The **bridge** between you (AI agent) and powerful system tools
|
|
72
|
+
|
|
73
|
+
Without ToolX, you cannot access any Prompt Manager ecosystem tools.
|
|
74
|
+
|
|
75
|
+
## When to Use ToolX
|
|
76
|
+
|
|
77
|
+
### Common Scenarios (IF-THEN rules):
|
|
78
|
+
- IF user needs file operations → USE tool://filesystem via toolx
|
|
79
|
+
- IF you see tool:// in any context → USE toolx to call it
|
|
80
|
+
|
|
81
|
+
### First Time Using Any Tool
|
|
82
|
+
⚠️ **MUST run mode: manual first** to read the tool's documentation
|
|
83
|
+
⚠️ Example: toolx with mode: manual for tool://filesystem
|
|
84
|
+
|
|
85
|
+
## How to Use ToolX (Copy These Patterns)
|
|
86
|
+
|
|
87
|
+
### Pattern 1: Read Tool Manual (First Time)
|
|
88
|
+
|
|
89
|
+
**Exact code to use:**
|
|
90
|
+
\`\`\`javascript
|
|
91
|
+
// Call the toolx function with this exact structure:
|
|
92
|
+
toolx({
|
|
93
|
+
yaml: \`tool: tool://filesystem\nmode: manual\`
|
|
94
|
+
})
|
|
95
|
+
\`\`\`
|
|
96
|
+
|
|
97
|
+
**What this does:** Shows you how to use the filesystem tool
|
|
98
|
+
|
|
99
|
+
### Pattern 2: Execute Tool with Parameters
|
|
100
|
+
|
|
101
|
+
**Exact code to use:**
|
|
102
|
+
\`\`\`javascript
|
|
103
|
+
toolx({
|
|
104
|
+
yaml: \`tool: tool://filesystem\nmode: execute\nparameters:\n path: /path/to/file.txt\n action: read\`
|
|
105
|
+
})
|
|
106
|
+
\`\`\`
|
|
107
|
+
|
|
108
|
+
**What this does:** Reads a file at the specified path
|
|
109
|
+
|
|
110
|
+
### Pattern 3: Configure Tool Environment
|
|
111
|
+
|
|
112
|
+
**Exact code to use:**
|
|
113
|
+
\`\`\`javascript
|
|
114
|
+
toolx({
|
|
115
|
+
yaml: \`tool: tool://my-tool\nmode: configure\nparameters:\n API_KEY: sk-xxx123\n TIMEOUT: 30000\`
|
|
116
|
+
})
|
|
117
|
+
\`\`\`
|
|
118
|
+
|
|
119
|
+
**What this does:** Sets environment variables for the tool
|
|
120
|
+
|
|
121
|
+
## Critical Rules (Must Follow)
|
|
122
|
+
|
|
123
|
+
### ✅ Correct Format
|
|
124
|
+
The yaml parameter MUST be a complete YAML document:
|
|
125
|
+
- Start with \`tool: tool://tool-name\`
|
|
126
|
+
- Add \`mode: execute\` (or manual/configure)
|
|
127
|
+
- If needed, add \`parameters:\` section with proper indentation
|
|
128
|
+
|
|
129
|
+
### ❌ Common Mistakes to Avoid
|
|
130
|
+
- DON'T pass just "tool://filesystem" (missing YAML structure)
|
|
131
|
+
- DON'T add @ prefix like "@tool://filesystem" (system handles it)
|
|
132
|
+
- DON'T forget "tool://" prefix (not "tool: filesystem")
|
|
133
|
+
- DON'T forget to read manual first for new tools
|
|
134
|
+
|
|
135
|
+
## Available System Tools
|
|
136
|
+
|
|
137
|
+
Quick reference of built-in tools:
|
|
138
|
+
- **tool://filesystem** - File operations (read/write/list/delete)
|
|
139
|
+
|
|
140
|
+
To see all available tools: check the tools directory
|
|
141
|
+
|
|
142
|
+
## Step-by-Step Workflow
|
|
143
|
+
|
|
144
|
+
### Step 1: Discover Available Tools
|
|
145
|
+
Check the tools directory to see what tools exist
|
|
146
|
+
|
|
147
|
+
### Step 2: Read Tool Manual
|
|
148
|
+
\`\`\`javascript
|
|
149
|
+
toolx({
|
|
150
|
+
yaml: \`tool: tool://TOOLNAME\nmode: manual\`
|
|
151
|
+
})
|
|
152
|
+
\`\`\`
|
|
153
|
+
|
|
154
|
+
### Step 3: Execute Tool
|
|
155
|
+
Copy the example from manual, modify parameters for your needs
|
|
156
|
+
|
|
157
|
+
### Step 4: Handle Errors
|
|
158
|
+
If execution fails, check:
|
|
159
|
+
- Is the tool name correct?
|
|
160
|
+
- Are parameters properly indented?
|
|
161
|
+
- Did you read the manual first?
|
|
162
|
+
|
|
163
|
+
`,
|
|
54
164
|
inputSchema: {
|
|
55
|
-
|
|
56
|
-
properties: {
|
|
57
|
-
name: {
|
|
58
|
-
type: 'string',
|
|
59
|
-
description: 'Optional name filter for prompts'
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
required: []
|
|
165
|
+
yaml: z.string().describe('YAML 格式的工具调用配置')
|
|
63
166
|
},
|
|
64
167
|
handler: async (args) => {
|
|
65
|
-
return
|
|
168
|
+
return handleToolX(args);
|
|
66
169
|
}
|
|
67
|
-
}
|
|
170
|
+
}
|
|
68
171
|
// {
|
|
69
172
|
// name: 'reload_prompts',
|
|
70
173
|
// description: 'Force a reload of all preset prompts to overwrite the cache.',
|
|
71
|
-
// inputSchema: {
|
|
72
|
-
// type: 'object',
|
|
73
|
-
// properties: {},
|
|
74
|
-
// required: []
|
|
75
|
-
// },
|
|
174
|
+
// inputSchema: {},
|
|
76
175
|
// handler: async (args) => {
|
|
77
176
|
// return handleReloadPrompts(args);
|
|
78
177
|
// }
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// 导入自定义模块
|
|
2
|
+
import { config } from '../utils/config.js';
|
|
3
|
+
import { logger } from '../utils/logger.js';
|
|
4
|
+
import { util } from '../utils/util.js';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import YAML from 'yaml';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
|
|
13
|
+
// 处理 toolx 工具调用
|
|
14
|
+
export async function handleToolX(args) {
|
|
15
|
+
const { yaml: yamlInput } = args;
|
|
16
|
+
|
|
17
|
+
if (!yamlInput) {
|
|
18
|
+
throw new Error("缺少必需参数: yaml");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Auto-correct common AI mistakes
|
|
23
|
+
let yamlContent = yamlInput.trim();
|
|
24
|
+
|
|
25
|
+
// Case 1: Just a plain URL string like "tool://filesystem" or "@tool://filesystem"
|
|
26
|
+
if (yamlContent.match(/^@?tool:\/\/[\w-]+$/)) {
|
|
27
|
+
const toolName = yamlContent.replace(/^@?tool:\/\//, '');
|
|
28
|
+
yamlContent = `tool: tool://${toolName}\nmode: execute`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Case 2: Handle escaped backslashes and quotes: tool: \"@tool://xxx\"
|
|
32
|
+
// This happens when AI generates YAML in a JSON string
|
|
33
|
+
yamlContent = yamlContent.replace(/\\\\/g, '\\').replace(/\\"/g, '"');
|
|
34
|
+
|
|
35
|
+
// Case 3: Remove @ prefix from tool URLs: @tool://xxx -> tool://xxx
|
|
36
|
+
yamlContent = yamlContent.replace(/@tool:\/\//g, 'tool://');
|
|
37
|
+
|
|
38
|
+
// Case 4: Remove quotes around tool URLs: tool: "tool://xxx" -> tool: tool://xxx
|
|
39
|
+
yamlContent = yamlContent.replace(/(tool|toolUrl|url):\s*"(tool:\/\/[^\"]+)"/g, '$1: $2');
|
|
40
|
+
|
|
41
|
+
// YAML → JSON conversion
|
|
42
|
+
const configObj = YAML.parse(yamlContent);
|
|
43
|
+
|
|
44
|
+
// Normalize field names (support aliases for AI-friendliness)
|
|
45
|
+
// Priority: tool > toolUrl > url
|
|
46
|
+
const toolIdentifier = configObj.tool || configObj.toolUrl || configObj.url;
|
|
47
|
+
|
|
48
|
+
// Priority: mode > operation
|
|
49
|
+
const operationMode = configObj.mode || configObj.operation;
|
|
50
|
+
|
|
51
|
+
// Validate required fields
|
|
52
|
+
if (!toolIdentifier) {
|
|
53
|
+
throw new Error('Missing required field: tool\nExample: tool: tool://filesystem\nAliases supported: tool / toolUrl / url');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Validate URL format
|
|
57
|
+
if (!toolIdentifier.startsWith('tool://')) {
|
|
58
|
+
throw new Error(`Invalid tool format: ${toolIdentifier}\nMust start with tool://`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Get tool name
|
|
62
|
+
const toolName = toolIdentifier.replace('tool://', '');
|
|
63
|
+
|
|
64
|
+
// Check if tool exists in our local tools directory
|
|
65
|
+
const rootDir = path.join(__dirname, '..', '..', '..');
|
|
66
|
+
const toolsDir = path.join(rootDir, 'packages', 'resources', 'tools');
|
|
67
|
+
const toolPath = path.join(toolsDir, `${toolName}`, `${toolName}.tool.js`);
|
|
68
|
+
|
|
69
|
+
if (!fs.existsSync(toolPath)) {
|
|
70
|
+
// List available tools
|
|
71
|
+
const availableTools = await fs.readdir(toolsDir);
|
|
72
|
+
const toolList = availableTools.filter(file => file.endsWith('.js')).map(file => path.basename(file, '.js'));
|
|
73
|
+
|
|
74
|
+
throw new Error(`Tool '${toolName}' not found\nAvailable tools: ${toolList.join(', ')}\nTools are located in: ${toolsDir}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Load and execute the tool
|
|
78
|
+
const toolModule = await import(toolPath);
|
|
79
|
+
const toolFunction = toolModule.default || toolModule.execute || toolModule.run;
|
|
80
|
+
|
|
81
|
+
if (typeof toolFunction !== 'function') {
|
|
82
|
+
throw new Error(`Tool '${toolName}' does not export a valid function`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Execute the tool with provided parameters
|
|
86
|
+
const result = await toolFunction(configObj.parameters || {}, operationMode || 'execute');
|
|
87
|
+
|
|
88
|
+
// 如果是 manual 模式,直接返回 Markdown 格式的手册
|
|
89
|
+
if (operationMode === 'manual') {
|
|
90
|
+
return {
|
|
91
|
+
content: [
|
|
92
|
+
{
|
|
93
|
+
type: "text",
|
|
94
|
+
text: result
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 其他模式返回 JSON 格式的结果
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: "text",
|
|
105
|
+
text: JSON.stringify({
|
|
106
|
+
success: true,
|
|
107
|
+
tool: toolName,
|
|
108
|
+
mode: operationMode || 'execute',
|
|
109
|
+
result: result
|
|
110
|
+
}, null, 2)
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
};
|
|
114
|
+
} catch (error) {
|
|
115
|
+
// YAML parsing errors
|
|
116
|
+
if (error.name === 'YAMLException') {
|
|
117
|
+
// Check for multiline string issues
|
|
118
|
+
if (error.message.includes('bad indentation') || error.message.includes('mapping entry')) {
|
|
119
|
+
throw new Error(`YAML format error: ${error.message}\n\nMultiline content requires | symbol, example:\ncontent: |\n First line\n Second line\n\nNote: Newline after |, indent content with 2 spaces`);
|
|
120
|
+
}
|
|
121
|
+
throw new Error(`YAML format error: ${error.message}\nCheck indentation (use spaces) and syntax`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Tool not found
|
|
125
|
+
if (error.message?.includes('Tool not found')) {
|
|
126
|
+
throw new Error(error.message);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -95,7 +95,7 @@ export class Config {
|
|
|
95
95
|
|
|
96
96
|
// 其他配置
|
|
97
97
|
this.serverName = process.env.MCP_SERVER_NAME || 'prompt-manager';
|
|
98
|
-
this.serverVersion = process.env.MCP_SERVER_VERSION || '0.0.
|
|
98
|
+
this.serverVersion = process.env.MCP_SERVER_VERSION || '0.0.19';
|
|
99
99
|
this.logLevel = process.env.LOG_LEVEL || 'info';
|
|
100
100
|
this.maxPrompts = parseInt(process.env.MAX_PROMPTS) || 1000;
|
|
101
101
|
this.recursiveScan = process.env.RECURSIVE_SCAN !== 'false'; // 默认启用递归扫描
|