@becrafter/prompt-manager 0.0.16 → 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 +66 -80
- 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 +178 -460
- package/app/desktop/package-lock.json +1150 -412
- package/app/desktop/package.json +54 -11
- 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 +10 -3
- package/packages/admin-ui/admin.html +2 -2
- package/packages/resources/tools/filesystem/filesystem.tool.js +184 -0
- package/packages/resources/tools/index.js +16 -0
- package/packages/server/api/admin.routes.js +450 -0
- package/packages/server/api/open.routes.js +89 -0
- package/packages/server/app.js +163 -0
- package/packages/server/mcp/mcp.handler.js +265 -0
- package/packages/server/mcp/mcp.server.js +181 -0
- package/packages/server/mcp/toolx.handler.js +131 -0
- package/packages/server/middlewares/auth.middleware.js +34 -0
- package/packages/server/server.js +42 -908
- package/packages/server/{manager.js → services/manager.js} +13 -5
- package/packages/server/{config.js → utils/config.js} +27 -27
- package/packages/server/utils/util.js +356 -0
- package/scripts/build-icons.js +105 -0
- package/scripts/icns-builder/package.json +12 -0
- package/packages/server/mcp.js +0 -234
- package/packages/server/mcpManager.js +0 -205
- /package/app/desktop/assets/{icon.png → icons/icon.png} +0 -0
- /package/packages/server/{logger.js → utils/logger.js} +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 开放数据接口
|
|
3
|
+
*/
|
|
4
|
+
import express from 'express';
|
|
5
|
+
import { logger } from '../utils/logger.js';
|
|
6
|
+
import { util } from '../utils/util.js';
|
|
7
|
+
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
|
|
10
|
+
router.get('/prompts', (req, res) => {
|
|
11
|
+
try {
|
|
12
|
+
const prompts = util.getPromptsFromFiles();
|
|
13
|
+
|
|
14
|
+
// 过滤出启用的提示词
|
|
15
|
+
const filtered = prompts.filter(prompt => {
|
|
16
|
+
const groupActive = prompt.groupEnabled !== false;
|
|
17
|
+
const promptActive = prompt.enabled === true;
|
|
18
|
+
return groupActive && promptActive;
|
|
19
|
+
});
|
|
20
|
+
logger.debug(`filtered prompts: ${JSON.stringify(filtered)}`);
|
|
21
|
+
|
|
22
|
+
// 判断是否有搜索参数,且搜索参数名为search
|
|
23
|
+
if (req.query.search) {
|
|
24
|
+
const search = req.query.search;
|
|
25
|
+
|
|
26
|
+
// 实现相似度匹配算法
|
|
27
|
+
const searchResults = filtered.map(prompt => {
|
|
28
|
+
const score = util.calculateSimilarityScore(search, prompt);
|
|
29
|
+
return {
|
|
30
|
+
prompt: {
|
|
31
|
+
id: prompt.uniqueId,
|
|
32
|
+
name: prompt.name,
|
|
33
|
+
description: prompt.description || `Prompt: ${prompt.name}`,
|
|
34
|
+
metadata: {
|
|
35
|
+
// fileName: prompt.fileName,
|
|
36
|
+
fullPath: prompt.relativePath
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
score: score
|
|
40
|
+
};
|
|
41
|
+
})
|
|
42
|
+
.filter(result => result.score > 0) // 只返回有匹配的结果
|
|
43
|
+
.sort((a, b) => b.score - a.score); // 按相似度得分降序排列
|
|
44
|
+
|
|
45
|
+
// 只返回prompt字段
|
|
46
|
+
res.json(searchResults.map(result => result.prompt));
|
|
47
|
+
} else {
|
|
48
|
+
// 无搜索参数时,返回精简信息
|
|
49
|
+
const simplifiedPrompts = filtered.map(prompt => ({
|
|
50
|
+
id: prompt.uniqueId,
|
|
51
|
+
name: prompt.name,
|
|
52
|
+
description: prompt.description || `Prompt: ${prompt.name}`,
|
|
53
|
+
metadata: {
|
|
54
|
+
// fileName: prompt.fileName,
|
|
55
|
+
fullPath: prompt.relativePath
|
|
56
|
+
}
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
res.json(simplifiedPrompts);
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
res.status(500).json({ error: error.message });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// 查看提示词内容
|
|
67
|
+
router.post('/process', async (req, res) => {
|
|
68
|
+
try {
|
|
69
|
+
const { promptName, arguments: args } = req.body;
|
|
70
|
+
|
|
71
|
+
if (!promptName) {
|
|
72
|
+
return res.status(400).json({ error: 'Missing promptName' });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const prompts = util.getPromptsFromFiles();
|
|
76
|
+
const prompt = prompts.find(p => p.name === promptName);
|
|
77
|
+
|
|
78
|
+
if (!prompt) {
|
|
79
|
+
return res.status(404).json({ error: `Prompt "${promptName}" not found` });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const processedPrompt = await util.processPromptContent(prompt, args);
|
|
83
|
+
res.json({ processedText: processedPrompt });
|
|
84
|
+
} catch (error) {
|
|
85
|
+
res.status(500).json({ error: error.message });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export const openRouter = router;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { util } from './utils/util.js';
|
|
6
|
+
import { config } from './utils/config.js';
|
|
7
|
+
import { logger } from './utils/logger.js';
|
|
8
|
+
import { adminRouter } from './api/admin.routes.js';
|
|
9
|
+
import { openRouter } from './api/open.routes.js';
|
|
10
|
+
import { getMcpServer } from './mcp/mcp.server.js';
|
|
11
|
+
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
12
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
13
|
+
import { InMemoryEventStore } from '@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js';
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
const app = express();
|
|
17
|
+
const adminUiRoot = util.getAdminUiRoot();
|
|
18
|
+
|
|
19
|
+
// 全局中间件
|
|
20
|
+
app.use(cors());
|
|
21
|
+
app.use(express.json({ limit: '8mb' }));
|
|
22
|
+
app.use(express.urlencoded({ extended: true }));
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
// 为管理员界面提供静态文件服务 - 根路径
|
|
26
|
+
app.use(config.adminPath, express.static(adminUiRoot));
|
|
27
|
+
|
|
28
|
+
// 为管理员界面提供根路径访问(当用户访问 /admin 时显示 admin.html)
|
|
29
|
+
app.get(config.adminPath, (req, res) => {
|
|
30
|
+
res.sendFile(path.join(adminUiRoot, 'admin.html'));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// 为管理员界面提供根路径访问(当用户访问 /admin/ 时显示 admin.html)
|
|
34
|
+
app.get(config.adminPath + '/', (req, res) => {
|
|
35
|
+
res.sendFile(path.join(adminUiRoot, 'admin.html'));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
// 注册后台API
|
|
40
|
+
app.use('/adminapi', adminRouter);
|
|
41
|
+
|
|
42
|
+
// 注册开放API
|
|
43
|
+
app.use('/openapi', openRouter);
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
const transports = {};
|
|
47
|
+
const mcpServers = {}; // 存储每个会话的MCP服务器实例
|
|
48
|
+
|
|
49
|
+
// 挂载MCP流式服务(独立路径前缀,避免冲突)
|
|
50
|
+
app.all('/mcp', (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
let transport;
|
|
53
|
+
const sessionId = req.headers['mcp-session-id'] || '';
|
|
54
|
+
|
|
55
|
+
if (sessionId && transports[sessionId]) {
|
|
56
|
+
const existingTransport = transports[sessionId];
|
|
57
|
+
// Check if the transport is of the correct type
|
|
58
|
+
if (existingTransport instanceof StreamableHTTPServerTransport) {
|
|
59
|
+
// Reuse existing transport
|
|
60
|
+
transport = existingTransport;
|
|
61
|
+
} else {
|
|
62
|
+
// Transport exists but is not a StreamableHTTPServerTransport (could be SSEServerTransport)
|
|
63
|
+
res.status(400).json({
|
|
64
|
+
jsonrpc: '2.0',
|
|
65
|
+
error: {
|
|
66
|
+
code: -32000,
|
|
67
|
+
message: 'Bad Request: Session exists but uses a different transport protocol'
|
|
68
|
+
},
|
|
69
|
+
id: null
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
} else if (!sessionId && req.method === 'POST' && isInitializeRequest(req.body)) {
|
|
74
|
+
const eventStore = new InMemoryEventStore();
|
|
75
|
+
transport = new StreamableHTTPServerTransport({
|
|
76
|
+
sessionIdGenerator: () => randomUUID(),
|
|
77
|
+
eventStore, // Enable resumability
|
|
78
|
+
onsessioninitialized: sessionId => {
|
|
79
|
+
// Store the transport by session ID when session is initialized
|
|
80
|
+
console.log(`StreamableHTTP session initialized with ID: ${sessionId}`);
|
|
81
|
+
transports[sessionId] = transport;
|
|
82
|
+
|
|
83
|
+
// 为新会话创建MCP服务器实例
|
|
84
|
+
mcpServers[sessionId] = getMcpServer();
|
|
85
|
+
mcpServers[sessionId].connect(transport);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Set up onclose handler to clean up transport when closed
|
|
90
|
+
transport.onclose = () => {
|
|
91
|
+
const sid = transport.sessionId;
|
|
92
|
+
if (sid && transports[sid]) {
|
|
93
|
+
console.log(`Transport closed for session ${sid}, removing from transports map`);
|
|
94
|
+
delete transports[sid];
|
|
95
|
+
// 清理MCP服务器实例
|
|
96
|
+
if (mcpServers[sid]) {
|
|
97
|
+
delete mcpServers[sid];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// 添加错误处理
|
|
103
|
+
transport.onerror = (error) => {
|
|
104
|
+
console.error('MCP Transport error:', error);
|
|
105
|
+
};
|
|
106
|
+
} else {
|
|
107
|
+
// Invalid request - no session ID or not initialization request
|
|
108
|
+
res.status(400).json({
|
|
109
|
+
jsonrpc: '2.0',
|
|
110
|
+
error: {
|
|
111
|
+
code: -32000,
|
|
112
|
+
message: 'Bad Request: No valid session ID provided'
|
|
113
|
+
},
|
|
114
|
+
id: null
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Handle the request with the transport
|
|
120
|
+
transport.handleRequest(req, res, req.body);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
logger.error('Error handling MCP request: ' + error.message);
|
|
123
|
+
return res.status(500).json({
|
|
124
|
+
jsonrpc: '2.0',
|
|
125
|
+
error: {
|
|
126
|
+
code: -32603,
|
|
127
|
+
message: 'Internal server error'
|
|
128
|
+
},
|
|
129
|
+
id: null
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// 错误处理中间件
|
|
135
|
+
app.use((err, req, res, next) => {
|
|
136
|
+
console.error(`[服务器错误]: ${err.message}`);
|
|
137
|
+
res.status(500).send('Internal Server Error')
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// 404处理
|
|
141
|
+
app.use((req, res) => {
|
|
142
|
+
res.status(404).send('Not Found')
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// 导出清理函数
|
|
146
|
+
export function cleanupMcpSessions() {
|
|
147
|
+
console.log('Cleaning up MCP sessions');
|
|
148
|
+
// 清理所有活动的传输和服务器实例
|
|
149
|
+
for (const sessionId in transports) {
|
|
150
|
+
try {
|
|
151
|
+
if (transports[sessionId] && typeof transports[sessionId].close === 'function') {
|
|
152
|
+
transports[sessionId].close();
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error(`Error closing transport for session ${sessionId}:`, error);
|
|
156
|
+
}
|
|
157
|
+
delete transports[sessionId];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 注意:mcpServers 对象在 app.js 中不可访问,需要在 server.js 中处理
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export default app;
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// 导入自定义模块
|
|
2
|
+
import { config } from '../utils/config.js';
|
|
3
|
+
import { logger } from '../utils/logger.js';
|
|
4
|
+
import { util } from '../utils/util.js';
|
|
5
|
+
|
|
6
|
+
// 处理 get_prompt 工具调用
|
|
7
|
+
export async function handleGetPrompt(args) {
|
|
8
|
+
// 注意:这里为了兼容性,我们同时支持prompt_id和name参数
|
|
9
|
+
const promptId = args.prompt_id || args.name;
|
|
10
|
+
|
|
11
|
+
if (!promptId) {
|
|
12
|
+
throw new Error("缺少必需参数: prompt_id");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const promptManager = await util.getPromptManager();
|
|
16
|
+
const prompt = promptManager.getPrompt(promptId);
|
|
17
|
+
if (!prompt) {
|
|
18
|
+
throw new Error(`未找到ID为 "${promptId}" 的prompt`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 返回完整的prompt信息
|
|
22
|
+
const promptInfo = {
|
|
23
|
+
id: prompt.uniqueId, // 使用基于文件路径的唯一ID
|
|
24
|
+
name: prompt.name,
|
|
25
|
+
description: prompt.description || `Prompt: ${prompt.name}`,
|
|
26
|
+
messages: prompt.messages || [],
|
|
27
|
+
arguments: prompt.arguments || [],
|
|
28
|
+
filePath: prompt.relativePath, // 添加文件路径信息
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (config.getLogLevel() === 'debug') {
|
|
32
|
+
promptInfo.metadata = {
|
|
33
|
+
uniqueId: prompt.uniqueId,
|
|
34
|
+
fileName: prompt.fileName,
|
|
35
|
+
fullPath: prompt.filePath,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return convertToText({
|
|
40
|
+
success: true,
|
|
41
|
+
prompt: promptInfo
|
|
42
|
+
}, 'detail');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 处理 search_prompts 工具调用
|
|
46
|
+
export async function handleSearchPrompts(args) {
|
|
47
|
+
// 注意:这里为了兼容性,我们同时支持title和name参数
|
|
48
|
+
const searchTerm = args.title || args.name;
|
|
49
|
+
|
|
50
|
+
const logLevel = config.getLogLevel();
|
|
51
|
+
const promptManager = await util.getPromptManager();
|
|
52
|
+
let allPrompts = (await promptManager.loadPrompts()).prompts || [];
|
|
53
|
+
|
|
54
|
+
// 如果搜索词为空,则返回所有提示词
|
|
55
|
+
if (!searchTerm) {
|
|
56
|
+
let simplifiedPrompts = formatResults(allPrompts);
|
|
57
|
+
|
|
58
|
+
return convertToText({
|
|
59
|
+
success: true,
|
|
60
|
+
query: searchTerm || '',
|
|
61
|
+
count: simplifiedPrompts.length,
|
|
62
|
+
results: simplifiedPrompts
|
|
63
|
+
}, 'list');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 实现相似度匹配算法
|
|
67
|
+
const searchResults = allPrompts.map(prompt => {
|
|
68
|
+
prompt.description = prompt.description || `Prompt: ${prompt.name}`;
|
|
69
|
+
prompt.arguments = prompt.arguments || [];
|
|
70
|
+
prompt.hasArguments = prompt.arguments && prompt.arguments.length > 0;
|
|
71
|
+
return {
|
|
72
|
+
prompt: prompt,
|
|
73
|
+
score: util.calculateSimilarityScore(searchTerm, prompt),
|
|
74
|
+
};
|
|
75
|
+
})
|
|
76
|
+
.filter(result => result.score > 0) // 只返回有匹配的结果
|
|
77
|
+
.sort((a, b) => b.score - a.score); // 按相似度得分降序排列
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
let result = {
|
|
81
|
+
success: true,
|
|
82
|
+
query: searchTerm || '',
|
|
83
|
+
count: searchResults.length,
|
|
84
|
+
results: formatResults(searchResults),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (logLevel === 'debug') {
|
|
88
|
+
result.debug = {
|
|
89
|
+
scores: searchResults.map(result => ({
|
|
90
|
+
id: result.prompt.id,
|
|
91
|
+
name: result.prompt.name,
|
|
92
|
+
score: result.score,
|
|
93
|
+
fullPath: result.prompt.filePath,
|
|
94
|
+
}))
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return convertToText(result, 'list');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 处理 reload_prompts 工具调用
|
|
103
|
+
*/
|
|
104
|
+
export async function handleReloadPrompts(args) {
|
|
105
|
+
logger.info('重新加载prompts...');
|
|
106
|
+
|
|
107
|
+
const promptManager = await util.getPromptManager();
|
|
108
|
+
const result = await promptManager.reloadPrompts();
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
content: [
|
|
112
|
+
{
|
|
113
|
+
type: "text",
|
|
114
|
+
text: JSON.stringify({
|
|
115
|
+
success: true,
|
|
116
|
+
message: `重新加载完成: 成功 ${result.success} 个, 失败 ${result.errorCount} 个`,
|
|
117
|
+
result: formatResults(result.prompts)
|
|
118
|
+
}, null, 2)
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 格式化搜索结果
|
|
126
|
+
* @param {*} results
|
|
127
|
+
* @returns
|
|
128
|
+
*/
|
|
129
|
+
function formatResults(results = []) {
|
|
130
|
+
if (!Array.isArray(results)) return [];
|
|
131
|
+
|
|
132
|
+
return results.map(result => {
|
|
133
|
+
const prompt = result.prompt ? result.prompt : result;
|
|
134
|
+
const baseItem = {
|
|
135
|
+
id: prompt.id || prompt.uniqueId || '',
|
|
136
|
+
name: prompt.name || 'Unnamed Prompt',
|
|
137
|
+
description: prompt.description || `Prompt: ${prompt.name || 'Unnamed'}`
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
if (config.getLogLevel() === 'debug') {
|
|
141
|
+
return {
|
|
142
|
+
...baseItem,
|
|
143
|
+
metadata: {
|
|
144
|
+
fullPath: prompt.filePath || ''
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return baseItem;
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
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'
|
|
243
|
+
* @returns
|
|
244
|
+
*/
|
|
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
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
content: [
|
|
259
|
+
{
|
|
260
|
+
type: "text",
|
|
261
|
+
text: ret
|
|
262
|
+
}
|
|
263
|
+
]
|
|
264
|
+
};
|
|
265
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { config } from '../utils/config.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import {
|
|
5
|
+
handleGetPrompt,
|
|
6
|
+
handleSearchPrompts,
|
|
7
|
+
handleReloadPrompts
|
|
8
|
+
} from './mcp.handler.js';
|
|
9
|
+
import { handleToolX } from './toolx.handler.js';
|
|
10
|
+
|
|
11
|
+
class Server {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.server = new McpServer(
|
|
14
|
+
{
|
|
15
|
+
name: 'Prompt Management Server',
|
|
16
|
+
version: config.getServerVersion()
|
|
17
|
+
},
|
|
18
|
+
{ capabilities: { logging: {} } }
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
registerTools(tools) {
|
|
23
|
+
for (const tool of tools) {
|
|
24
|
+
this.server.registerTool(tool.name,
|
|
25
|
+
{
|
|
26
|
+
description: tool.description,
|
|
27
|
+
inputSchema: tool.inputSchema,
|
|
28
|
+
},
|
|
29
|
+
tool.handler
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getServer() {
|
|
35
|
+
return this.server;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const getMcpServer = () => {
|
|
40
|
+
const mcpServer = new Server();
|
|
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
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'get_prompt',
|
|
54
|
+
description: `功能:精准获取并应用提示词详情\n描述:根据提示词 ID 或名称 调用具体内容,自动将其嵌入当前对话上下文,无需用户手动复制。支持通过 search_prompts 返回的 ID/名称直接获取。\n\n示例:\n- 用户:"使用 ID 001" → 工具自动加载诗歌创作提示词并生成内容\n- 用户:"调用'营销文案生成'" → 工具匹配名称后应用对应提示词`,
|
|
55
|
+
inputSchema: {
|
|
56
|
+
prompt_id: z.string().describe('提示词的唯一标识 ID/名称'),
|
|
57
|
+
},
|
|
58
|
+
handler: async (args) => {
|
|
59
|
+
return handleGetPrompt(args);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
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
|
+
`,
|
|
164
|
+
inputSchema: {
|
|
165
|
+
yaml: z.string().describe('YAML 格式的工具调用配置')
|
|
166
|
+
},
|
|
167
|
+
handler: async (args) => {
|
|
168
|
+
return handleToolX(args);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// {
|
|
172
|
+
// name: 'reload_prompts',
|
|
173
|
+
// description: 'Force a reload of all preset prompts to overwrite the cache.',
|
|
174
|
+
// inputSchema: {},
|
|
175
|
+
// handler: async (args) => {
|
|
176
|
+
// return handleReloadPrompts(args);
|
|
177
|
+
// }
|
|
178
|
+
// }
|
|
179
|
+
]);
|
|
180
|
+
return mcpServer.getServer();
|
|
181
|
+
};
|