flowmind 1.0.0 → 1.1.0
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/README.md +81 -2
- package/README_CN.md +329 -2
- package/bin/flowmind.js +574 -8
- package/config/ai-config.example.json +64 -0
- package/config/claude-mcp-config.example.json +12 -0
- package/core/ai/base-model.js +70 -0
- package/core/ai/index.js +29 -0
- package/core/ai/model-manager.js +320 -0
- package/core/ai/prompts/extraction.js +38 -0
- package/core/ai/prompts/index.js +11 -0
- package/core/ai/prompts/intent.js +43 -0
- package/core/ai/prompts/learning.js +46 -0
- package/core/ai/prompts/selection.js +38 -0
- package/core/ai/prompts/summary.js +35 -0
- package/core/ai/providers/anthropic.js +93 -0
- package/core/ai/providers/deepseek.js +80 -0
- package/core/ai/providers/ernie.js +111 -0
- package/core/ai/providers/glm.js +80 -0
- package/core/ai/providers/mimo.js +80 -0
- package/core/ai/providers/ollama.js +147 -0
- package/core/ai/providers/openai.js +82 -0
- package/core/ai/providers/qwen.js +80 -0
- package/core/index.js +74 -11
- package/core/mcp-compatibility.js +4 -4
- package/core/providers/yapi/yapi-adapter.js +2 -2
- package/core/providers/yuque/yuque-adapter.js +2 -2
- package/core/skill-loader.js +53 -11
- package/mcp/server.js +313 -0
- package/package.json +9 -3
- package/scripts/migrate-config.js +2 -2
- package/skills/yapi-sync-interface/SKILL.md +1 -1
- package/skills/yuque-sync-design/SKILL.md +6 -6
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qwen Provider - 阿里云通义千问模型接入
|
|
3
|
+
* 支持 Qwen-Turbo、Qwen-Plus、Qwen-Max 等模型
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const BaseModel = require('../base-model');
|
|
7
|
+
|
|
8
|
+
class QwenProvider extends BaseModel {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
super('qwen', config);
|
|
11
|
+
this.apiKey = config.apiKey || process.env.DASHSCOPE_API_KEY;
|
|
12
|
+
this.model = config.model || 'qwen-turbo';
|
|
13
|
+
this.baseUrl = config.baseUrl || 'https://dashscope.aliyuncs.com/compatible-mode/v1';
|
|
14
|
+
this.temperature = config.temperature ?? 0.3;
|
|
15
|
+
this.maxTokens = config.maxTokens ?? 2000;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async init() {
|
|
19
|
+
if (!this.apiKey) {
|
|
20
|
+
throw new Error('DashScope API key is required. Set DASHSCOPE_API_KEY environment variable or provide in config.');
|
|
21
|
+
}
|
|
22
|
+
this.initialized = true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
validateConfig() {
|
|
26
|
+
return !!this.apiKey;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async chat(messages, options = {}) {
|
|
30
|
+
if (!this.initialized) {
|
|
31
|
+
await this.init();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
model: options.model || this.model,
|
|
42
|
+
messages: messages,
|
|
43
|
+
temperature: options.temperature ?? this.temperature,
|
|
44
|
+
max_tokens: options.maxTokens ?? this.maxTokens
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const error = await response.text();
|
|
50
|
+
throw new Error(`Qwen API error: ${response.status} - ${error}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
return data.choices[0].message.content;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async complete(prompt, options = {}) {
|
|
58
|
+
return this.chat([{ role: 'user', content: prompt }], options);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async isAvailable() {
|
|
62
|
+
try {
|
|
63
|
+
if (!this.apiKey) return false;
|
|
64
|
+
return true;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getInfo() {
|
|
71
|
+
return {
|
|
72
|
+
...super.getInfo(),
|
|
73
|
+
model: this.model,
|
|
74
|
+
baseUrl: this.baseUrl,
|
|
75
|
+
provider: 'Alibaba Cloud'
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = QwenProvider;
|
package/core/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const LearningEngine = require('./learning-engine');
|
|
|
8
8
|
const SceneMatcher = require('./scene-matcher');
|
|
9
9
|
const ConfigManager = require('./config-manager');
|
|
10
10
|
const ComponentRegistry = require('./component-registry');
|
|
11
|
+
const ModelManager = require('./ai/model-manager');
|
|
11
12
|
|
|
12
13
|
class FlowMind {
|
|
13
14
|
constructor(options = {}) {
|
|
@@ -16,6 +17,7 @@ class FlowMind {
|
|
|
16
17
|
this.matcher = new SceneMatcher(this.config, this.learning);
|
|
17
18
|
this.components = new ComponentRegistry(this.config);
|
|
18
19
|
this.skills = new SkillLoader(this.config, this.learning, this.components);
|
|
20
|
+
this.ai = new ModelManager(options.ai || {});
|
|
19
21
|
this.initialized = false;
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -32,6 +34,13 @@ class FlowMind {
|
|
|
32
34
|
await this.skills.loadAll();
|
|
33
35
|
await this.matcher.loadScenes();
|
|
34
36
|
|
|
37
|
+
// Initialize AI model manager
|
|
38
|
+
try {
|
|
39
|
+
await this.ai.init();
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.warn(`AI model initialization failed: ${error.message}. Falling back to rule-based engine.`);
|
|
42
|
+
}
|
|
43
|
+
|
|
35
44
|
this.initialized = true;
|
|
36
45
|
return this;
|
|
37
46
|
}
|
|
@@ -47,32 +56,70 @@ class FlowMind {
|
|
|
47
56
|
const startTime = Date.now();
|
|
48
57
|
|
|
49
58
|
try {
|
|
50
|
-
// 1.
|
|
51
|
-
const
|
|
59
|
+
// 1. AI Intent Understanding (if available)
|
|
60
|
+
const intent = await this.ai.understandIntent(input, context);
|
|
61
|
+
|
|
62
|
+
// 2. Check for learning patterns (corrections, feedback)
|
|
63
|
+
// Use AI to analyze learning feedback if available
|
|
64
|
+
const aiLearningResult = await this.ai.analyzeLearningFeedback(input, context);
|
|
65
|
+
const learningResult = aiLearningResult?.isLearning
|
|
66
|
+
? aiLearningResult
|
|
67
|
+
: await this.learning.detectLearning(input, context);
|
|
52
68
|
if (learningResult) {
|
|
53
69
|
return this.formatLearningResponse(learningResult);
|
|
54
70
|
}
|
|
55
71
|
|
|
56
|
-
//
|
|
57
|
-
const sceneMatch = await this.matcher.match(input);
|
|
72
|
+
// 3. Check scene mappings (with AI intent if available)
|
|
73
|
+
const sceneMatch = await this.matcher.match(input, intent);
|
|
58
74
|
if (sceneMatch && sceneMatch.confidence >= 0.7) {
|
|
59
75
|
return this.executeSceneWorkflow(sceneMatch, input, context);
|
|
60
76
|
}
|
|
61
77
|
|
|
62
|
-
//
|
|
63
|
-
|
|
78
|
+
// 4. Select skill (AI-assisted if available)
|
|
79
|
+
let skill = null;
|
|
80
|
+
const candidates = await this.skills.getCandidates(input, context);
|
|
81
|
+
|
|
82
|
+
if (candidates.length > 0) {
|
|
83
|
+
// Use AI to select skill if available
|
|
84
|
+
const aiSelection = await this.ai.selectSkill(input, candidates);
|
|
85
|
+
if (aiSelection && aiSelection.selectedSkill) {
|
|
86
|
+
skill = this.skills.get(aiSelection.selectedSkill);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Fallback to rule-based selection
|
|
91
|
+
if (!skill) {
|
|
92
|
+
skill = await this.skills.select(input, context);
|
|
93
|
+
}
|
|
94
|
+
|
|
64
95
|
if (!skill) {
|
|
65
96
|
return this.formatError('No matching skill found', input);
|
|
66
97
|
}
|
|
67
98
|
|
|
68
|
-
//
|
|
69
|
-
const
|
|
99
|
+
// 5. Extract parameters using AI (if available)
|
|
100
|
+
const extractedParams = await this.ai.extractParameters(input, skill.name);
|
|
101
|
+
|
|
102
|
+
// 6. Execute with learning applied
|
|
103
|
+
const enhancedContext = {
|
|
104
|
+
...context,
|
|
105
|
+
...extractedParams,
|
|
106
|
+
intent: intent
|
|
107
|
+
};
|
|
108
|
+
const result = await this.executeWithLearning(skill, input, enhancedContext);
|
|
70
109
|
|
|
71
|
-
//
|
|
72
|
-
|
|
110
|
+
// 7. Generate AI summary (if available)
|
|
111
|
+
const summary = await this.ai.summarizeResult(result, {
|
|
112
|
+
skill: skill.name,
|
|
113
|
+
intent: intent
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// 8. Format and return
|
|
117
|
+
return this.formatResult(summary || result, {
|
|
73
118
|
skill: skill.name,
|
|
74
119
|
duration: Date.now() - startTime,
|
|
75
|
-
sceneMatch: sceneMatch
|
|
120
|
+
sceneMatch: sceneMatch,
|
|
121
|
+
intent: intent,
|
|
122
|
+
aiEnhanced: !!summary
|
|
76
123
|
});
|
|
77
124
|
|
|
78
125
|
} catch (error) {
|
|
@@ -205,6 +252,22 @@ class FlowMind {
|
|
|
205
252
|
return this.components.getStatus();
|
|
206
253
|
}
|
|
207
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Get AI model status
|
|
257
|
+
* @returns {object}
|
|
258
|
+
*/
|
|
259
|
+
getAIStatus() {
|
|
260
|
+
return this.ai.getStatus();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Check if AI is available
|
|
265
|
+
* @returns {boolean}
|
|
266
|
+
*/
|
|
267
|
+
hasAI() {
|
|
268
|
+
return this.ai.hasAvailableProvider();
|
|
269
|
+
}
|
|
270
|
+
|
|
208
271
|
/**
|
|
209
272
|
* Export learnings
|
|
210
273
|
*/
|
|
@@ -41,14 +41,14 @@ const McpServerMapping = Object.freeze({
|
|
|
41
41
|
},
|
|
42
42
|
|
|
43
43
|
// API Documentation
|
|
44
|
-
'
|
|
44
|
+
'yapi-mcp': {
|
|
45
45
|
type: ComponentType.API_DOC,
|
|
46
46
|
provider: 'yapi',
|
|
47
47
|
description: 'YApi API documentation management'
|
|
48
48
|
},
|
|
49
49
|
|
|
50
50
|
// Knowledge Base
|
|
51
|
-
'
|
|
51
|
+
'yuque-mcp': {
|
|
52
52
|
type: ComponentType.KNOWLEDGE_BASE,
|
|
53
53
|
provider: 'yuque',
|
|
54
54
|
description: 'Yuque knowledge base management'
|
|
@@ -77,8 +77,8 @@ const ProviderToMcp = Object.freeze({
|
|
|
77
77
|
'databaseManager:aliyun-dms': 'aliyun-dms-mcp-server',
|
|
78
78
|
'databaseQuery:aliyun-rds-query': 'friday-rds-redis-query',
|
|
79
79
|
'redisMonitor:aliyun-redis': 'friday-aliyun-sz-rds-redis',
|
|
80
|
-
'apiDoc:yapi': '
|
|
81
|
-
'knowledgeBase:yuque': '
|
|
80
|
+
'apiDoc:yapi': 'yapi-mcp',
|
|
81
|
+
'knowledgeBase:yuque': 'yuque-mcp',
|
|
82
82
|
'workflow:friday-flow': 'friday-auto-flow',
|
|
83
83
|
'report:friday-report': 'friday-auto-report'
|
|
84
84
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* YApi API Documentation Adapter
|
|
3
|
-
* Wraps the
|
|
3
|
+
* Wraps the yapi-mcp MCP server for YApi platform management
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const ApiDocAdapter = require('../../adapters/api-doc-adapter');
|
|
@@ -24,7 +24,7 @@ class YapiAdapter extends ApiDocAdapter {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
get mcpServer() {
|
|
27
|
-
return '
|
|
27
|
+
return 'yapi-mcp';
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
async searchApis(params) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Yuque Knowledge Base Adapter
|
|
3
|
-
* Wraps the
|
|
3
|
+
* Wraps the yuque-mcp MCP server for Yuque platform management
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const KnowledgeBaseAdapter = require('../../adapters/knowledge-base-adapter');
|
|
@@ -23,7 +23,7 @@ class YuqueAdapter extends KnowledgeBaseAdapter {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
get mcpServer() {
|
|
26
|
-
return '
|
|
26
|
+
return 'yuque-mcp';
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
async getRepos(params) {
|
package/core/skill-loader.js
CHANGED
|
@@ -96,10 +96,35 @@ class SkillLoader {
|
|
|
96
96
|
|
|
97
97
|
// Parse YAML-like frontmatter
|
|
98
98
|
const lines = frontmatter.split('\n');
|
|
99
|
+
let currentKey = null;
|
|
100
|
+
let currentObj = null;
|
|
101
|
+
|
|
99
102
|
for (const line of lines) {
|
|
103
|
+
// Top-level key: value
|
|
100
104
|
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
101
105
|
if (match) {
|
|
102
|
-
|
|
106
|
+
const key = match[1];
|
|
107
|
+
const value = match[2].replace(/^["']|["']$/g, '');
|
|
108
|
+
definition[key] = value;
|
|
109
|
+
currentKey = key;
|
|
110
|
+
currentObj = null;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Nested object (e.g., metadata:)
|
|
115
|
+
const nestedMatch = line.match(/^(\w+):\s*$/);
|
|
116
|
+
if (nestedMatch) {
|
|
117
|
+
currentKey = nestedMatch[1];
|
|
118
|
+
definition[currentKey] = {};
|
|
119
|
+
currentObj = definition[currentKey];
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Nested key: value (indented)
|
|
124
|
+
const nestedValueMatch = line.match(/^\s+(\w+):\s*(.+)$/);
|
|
125
|
+
if (nestedValueMatch && currentObj) {
|
|
126
|
+
currentObj[nestedValueMatch[1]] = nestedValueMatch[2].replace(/^["']|["']$/g, '');
|
|
127
|
+
continue;
|
|
103
128
|
}
|
|
104
129
|
}
|
|
105
130
|
|
|
@@ -114,7 +139,7 @@ class SkillLoader {
|
|
|
114
139
|
}
|
|
115
140
|
|
|
116
141
|
// Extract trigger patterns
|
|
117
|
-
const triggerMatch = content.match(/## Trigger
|
|
142
|
+
const triggerMatch = content.match(/## Trigger Patterns\n([\s\S]*?)(?=\n##|$)/);
|
|
118
143
|
if (triggerMatch) {
|
|
119
144
|
definition.triggers = this.extractTriggers(triggerMatch[1]);
|
|
120
145
|
}
|
|
@@ -200,6 +225,23 @@ class SkillLoader {
|
|
|
200
225
|
* Select best skill for input
|
|
201
226
|
*/
|
|
202
227
|
async select(input, context = {}) {
|
|
228
|
+
const candidates = await this.getCandidates(input, context);
|
|
229
|
+
|
|
230
|
+
if (candidates.length === 0) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Sort by score (highest first)
|
|
235
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
236
|
+
|
|
237
|
+
return candidates[0].skill;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get candidate skills for input
|
|
242
|
+
* Returns skills that can handle the input with their scores
|
|
243
|
+
*/
|
|
244
|
+
async getCandidates(input, context = {}) {
|
|
203
245
|
const candidates = [];
|
|
204
246
|
|
|
205
247
|
for (const [name, skill] of this.skills) {
|
|
@@ -207,7 +249,11 @@ class SkillLoader {
|
|
|
207
249
|
const canHandle = await skill.canHandle(input, context);
|
|
208
250
|
if (canHandle) {
|
|
209
251
|
candidates.push({
|
|
252
|
+
name: skill.name,
|
|
210
253
|
skill: skill,
|
|
254
|
+
description: skill.definition?.description || '',
|
|
255
|
+
triggers: skill.definition?.triggers || [],
|
|
256
|
+
category: skill.definition?.category || skill.definition?.metadata?.category || '',
|
|
211
257
|
score: this.calculateSkillScore(skill, input, context)
|
|
212
258
|
});
|
|
213
259
|
}
|
|
@@ -216,14 +262,7 @@ class SkillLoader {
|
|
|
216
262
|
}
|
|
217
263
|
}
|
|
218
264
|
|
|
219
|
-
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Sort by score (highest first)
|
|
224
|
-
candidates.sort((a, b) => b.score - a.score);
|
|
225
|
-
|
|
226
|
-
return candidates[0].skill;
|
|
265
|
+
return candidates;
|
|
227
266
|
}
|
|
228
267
|
|
|
229
268
|
/**
|
|
@@ -266,7 +305,10 @@ class SkillLoader {
|
|
|
266
305
|
return Array.from(this.skills.values()).map(s => ({
|
|
267
306
|
name: s.name,
|
|
268
307
|
description: s.definition?.description,
|
|
269
|
-
category: s.definition?.category
|
|
308
|
+
category: s.definition?.category || s.definition?.metadata?.category,
|
|
309
|
+
version: s.definition?.version || s.definition?.metadata?.version,
|
|
310
|
+
author: s.definition?.author || s.definition?.metadata?.author,
|
|
311
|
+
path: s.path
|
|
270
312
|
}));
|
|
271
313
|
}
|
|
272
314
|
|
package/mcp/server.js
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FlowMind MCP Server
|
|
5
|
+
* 让 Claude/Codex 可以直接调用 FlowMind 内部流程
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const FlowMind = require('../core');
|
|
9
|
+
|
|
10
|
+
// MCP Server 实现
|
|
11
|
+
class FlowMindMCPServer {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.flowmind = null;
|
|
14
|
+
this.initialized = false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async init() {
|
|
18
|
+
if (this.initialized) return;
|
|
19
|
+
|
|
20
|
+
this.flowmind = new FlowMind();
|
|
21
|
+
await this.flowmind.init();
|
|
22
|
+
this.initialized = true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 获取所有可用工具
|
|
27
|
+
*/
|
|
28
|
+
getTools() {
|
|
29
|
+
const skills = this.flowmind.skills.list();
|
|
30
|
+
const tools = [];
|
|
31
|
+
|
|
32
|
+
// 添加核心工具
|
|
33
|
+
tools.push({
|
|
34
|
+
name: 'flowmind_process',
|
|
35
|
+
description: 'Process a request using FlowMind AI agent. This is the main entry point for using FlowMind.',
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
input: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description: 'The request to process'
|
|
42
|
+
},
|
|
43
|
+
context: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
description: 'Optional context for the request'
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
required: ['input']
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
tools.push({
|
|
53
|
+
name: 'flowmind_list_skills',
|
|
54
|
+
description: 'List all available FlowMind skills',
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
tools.push({
|
|
62
|
+
name: 'flowmind_get_skill',
|
|
63
|
+
description: 'Get detailed information about a specific skill',
|
|
64
|
+
inputSchema: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
name: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
description: 'Skill name'
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
required: ['name']
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
tools.push({
|
|
77
|
+
name: 'flowmind_ai_status',
|
|
78
|
+
description: 'Get AI model status and configuration',
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
tools.push({
|
|
86
|
+
name: 'flowmind_learning_stats',
|
|
87
|
+
description: 'Get learning statistics',
|
|
88
|
+
inputSchema: {
|
|
89
|
+
type: 'object',
|
|
90
|
+
properties: {}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// 添加每个技能作为独立工具
|
|
95
|
+
for (const skill of skills) {
|
|
96
|
+
tools.push({
|
|
97
|
+
name: `flowmind_skill_${skill.name}`,
|
|
98
|
+
description: skill.description || `Execute ${skill.name} skill`,
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: 'object',
|
|
101
|
+
properties: {
|
|
102
|
+
input: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
description: 'Input for the skill'
|
|
105
|
+
},
|
|
106
|
+
context: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
description: 'Optional context'
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
required: ['input']
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return tools;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 调用工具
|
|
121
|
+
*/
|
|
122
|
+
async callTool(name, args) {
|
|
123
|
+
await this.init();
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
// 核心工具
|
|
127
|
+
if (name === 'flowmind_process') {
|
|
128
|
+
const result = await this.flowmind.process(args.input, args.context || {});
|
|
129
|
+
return {
|
|
130
|
+
content: [{
|
|
131
|
+
type: 'text',
|
|
132
|
+
text: JSON.stringify(result, null, 2)
|
|
133
|
+
}]
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (name === 'flowmind_list_skills') {
|
|
138
|
+
const skills = this.flowmind.skills.list();
|
|
139
|
+
return {
|
|
140
|
+
content: [{
|
|
141
|
+
type: 'text',
|
|
142
|
+
text: JSON.stringify({ skills }, null, 2)
|
|
143
|
+
}]
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (name === 'flowmind_get_skill') {
|
|
148
|
+
const skill = this.flowmind.skills.get(args.name);
|
|
149
|
+
if (!skill) {
|
|
150
|
+
return {
|
|
151
|
+
content: [{
|
|
152
|
+
type: 'text',
|
|
153
|
+
text: JSON.stringify({ error: `Skill not found: ${args.name}` })
|
|
154
|
+
}],
|
|
155
|
+
isError: true
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
content: [{
|
|
160
|
+
type: 'text',
|
|
161
|
+
text: JSON.stringify({
|
|
162
|
+
name: skill.name,
|
|
163
|
+
description: skill.definition?.description,
|
|
164
|
+
category: skill.definition?.category,
|
|
165
|
+
triggers: skill.definition?.triggers,
|
|
166
|
+
componentDependencies: skill.definition?.componentDependencies
|
|
167
|
+
}, null, 2)
|
|
168
|
+
}]
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (name === 'flowmind_ai_status') {
|
|
173
|
+
const status = this.flowmind.getAIStatus();
|
|
174
|
+
return {
|
|
175
|
+
content: [{
|
|
176
|
+
type: 'text',
|
|
177
|
+
text: JSON.stringify(status, null, 2)
|
|
178
|
+
}]
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (name === 'flowmind_learning_stats') {
|
|
183
|
+
const stats = await this.flowmind.getStats();
|
|
184
|
+
return {
|
|
185
|
+
content: [{
|
|
186
|
+
type: 'text',
|
|
187
|
+
text: JSON.stringify(stats, null, 2)
|
|
188
|
+
}]
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 技能工具
|
|
193
|
+
if (name.startsWith('flowmind_skill_')) {
|
|
194
|
+
const skillName = name.replace('flowmind_skill_', '');
|
|
195
|
+
const skill = this.flowmind.skills.get(skillName);
|
|
196
|
+
|
|
197
|
+
if (!skill) {
|
|
198
|
+
return {
|
|
199
|
+
content: [{
|
|
200
|
+
type: 'text',
|
|
201
|
+
text: JSON.stringify({ error: `Skill not found: ${skillName}` })
|
|
202
|
+
}],
|
|
203
|
+
isError: true
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const result = await this.flowmind.executeWithLearning(skill, args.input, args.context || {});
|
|
208
|
+
return {
|
|
209
|
+
content: [{
|
|
210
|
+
type: 'text',
|
|
211
|
+
text: JSON.stringify(result, null, 2)
|
|
212
|
+
}]
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
content: [{
|
|
218
|
+
type: 'text',
|
|
219
|
+
text: JSON.stringify({ error: `Unknown tool: ${name}` })
|
|
220
|
+
}],
|
|
221
|
+
isError: true
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
} catch (error) {
|
|
225
|
+
return {
|
|
226
|
+
content: [{
|
|
227
|
+
type: 'text',
|
|
228
|
+
text: JSON.stringify({ error: error.message })
|
|
229
|
+
}],
|
|
230
|
+
isError: true
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 启动 MCP Server
|
|
237
|
+
async function main() {
|
|
238
|
+
const server = new FlowMindMCPServer();
|
|
239
|
+
|
|
240
|
+
// 读取 stdin,写入 stdout
|
|
241
|
+
const readline = require('readline');
|
|
242
|
+
const rl = readline.createInterface({
|
|
243
|
+
input: process.stdin,
|
|
244
|
+
output: process.stdout,
|
|
245
|
+
terminal: false
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
rl.on('line', async (line) => {
|
|
249
|
+
try {
|
|
250
|
+
const request = JSON.parse(line);
|
|
251
|
+
let response;
|
|
252
|
+
|
|
253
|
+
if (request.method === 'initialize') {
|
|
254
|
+
response = {
|
|
255
|
+
jsonrpc: '2.0',
|
|
256
|
+
id: request.id,
|
|
257
|
+
result: {
|
|
258
|
+
protocolVersion: '2024-11-05',
|
|
259
|
+
capabilities: {
|
|
260
|
+
tools: {}
|
|
261
|
+
},
|
|
262
|
+
serverInfo: {
|
|
263
|
+
name: 'flowmind',
|
|
264
|
+
version: '1.0.1'
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
} else if (request.method === 'tools/list') {
|
|
269
|
+
await server.init();
|
|
270
|
+
const tools = server.getTools();
|
|
271
|
+
response = {
|
|
272
|
+
jsonrpc: '2.0',
|
|
273
|
+
id: request.id,
|
|
274
|
+
result: { tools }
|
|
275
|
+
};
|
|
276
|
+
} else if (request.method === 'tools/call') {
|
|
277
|
+
const result = await server.callTool(request.params.name, request.params.arguments || {});
|
|
278
|
+
response = {
|
|
279
|
+
jsonrpc: '2.0',
|
|
280
|
+
id: request.id,
|
|
281
|
+
result
|
|
282
|
+
};
|
|
283
|
+
} else {
|
|
284
|
+
response = {
|
|
285
|
+
jsonrpc: '2.0',
|
|
286
|
+
id: request.id,
|
|
287
|
+
error: {
|
|
288
|
+
code: -32601,
|
|
289
|
+
message: `Method not found: ${request.method}`
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
295
|
+
} catch (error) {
|
|
296
|
+
const response = {
|
|
297
|
+
jsonrpc: '2.0',
|
|
298
|
+
id: null,
|
|
299
|
+
error: {
|
|
300
|
+
code: -32700,
|
|
301
|
+
message: 'Parse error'
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// 初始化
|
|
309
|
+
await server.init();
|
|
310
|
+
console.error('FlowMind MCP Server started');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
main().catch(console.error);
|