@mimik/agent-kit 1.0.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 +718 -0
- package/package.json +11 -0
- package/src/agent.js +456 -0
- package/src/index.js +10 -0
- package/src/mcp-response-parser.js +177 -0
- package/src/mcp-server.js +290 -0
- package/src/oai.js +205 -0
package/src/agent.js
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop, no-plusplus, no-continue, class-methods-use-this */
|
|
2
|
+
|
|
3
|
+
const { createChatCompletionStream } = require('./oai');
|
|
4
|
+
const { MCPServer, convertMCPToolsToOpenAI } = require('./mcp-server');
|
|
5
|
+
|
|
6
|
+
// MCP Agent Class
|
|
7
|
+
class Agent {
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.name = config.name || 'MCP Assistant';
|
|
10
|
+
this.instructions = config.instructions || 'You are a helpful assistant with access to MCP tools.';
|
|
11
|
+
this.maxIterations = config.maxIterations || 5;
|
|
12
|
+
|
|
13
|
+
// LLM configuration grouped together
|
|
14
|
+
const llmConfig = config.llm || {};
|
|
15
|
+
this.llm = {
|
|
16
|
+
model: llmConfig.model || 'lmstudio-community/Qwen3-4B-GGUF',
|
|
17
|
+
temperature: llmConfig.temperature || 0.1,
|
|
18
|
+
max_tokens: llmConfig.max_tokens || 2048,
|
|
19
|
+
endpoint: llmConfig.endpoint || 'http://127.0.0.1:8083/api/milm/v1/chat/completions',
|
|
20
|
+
apiKey: llmConfig.apiKey || 'bearer 1234',
|
|
21
|
+
no_think: llmConfig.no_think || false
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// MCP configuration - support multiple endpoints
|
|
25
|
+
this.mcpEndpoints = config.mcpEndpoints || [config.mcpEndpoint];
|
|
26
|
+
this.httpClient = config.httpClient;
|
|
27
|
+
this.mcpServers = new Map(); // Map of endpoint -> MCPServer instance
|
|
28
|
+
this.tools = [];
|
|
29
|
+
this.toolToServerMap = new Map(); // Map of tool name -> server endpoint
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async initialize() {
|
|
33
|
+
try {
|
|
34
|
+
let totalTools = 0;
|
|
35
|
+
|
|
36
|
+
// Initialize all MCP servers
|
|
37
|
+
for (const endpointObj of this.mcpEndpoints) {
|
|
38
|
+
try {
|
|
39
|
+
let endpoint;
|
|
40
|
+
let apiKey;
|
|
41
|
+
let options;
|
|
42
|
+
if (typeof endpointObj === 'object') {
|
|
43
|
+
endpoint = endpointObj.url;
|
|
44
|
+
apiKey = endpointObj.apiKey;
|
|
45
|
+
options = endpointObj.options;
|
|
46
|
+
} else {
|
|
47
|
+
endpoint = endpointObj;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!endpoint) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// console.log(`[${this.name}] Initializing MCP server: ${endpoint}`);
|
|
54
|
+
|
|
55
|
+
const mcpServer = new MCPServer(this.httpClient, endpoint, apiKey, options);
|
|
56
|
+
await mcpServer.initialize();
|
|
57
|
+
|
|
58
|
+
// Load tools from this server
|
|
59
|
+
const toolsResult = await mcpServer.listTools();
|
|
60
|
+
const serverTools = convertMCPToolsToOpenAI(toolsResult.tools);
|
|
61
|
+
|
|
62
|
+
// Store server and tools
|
|
63
|
+
this.mcpServers.set(endpoint, mcpServer);
|
|
64
|
+
|
|
65
|
+
// Add tools to global list and map them to their server
|
|
66
|
+
for (const tool of serverTools) {
|
|
67
|
+
this.tools.push({
|
|
68
|
+
...tool,
|
|
69
|
+
_mcpEndpoint: endpoint, // Add metadata for routing
|
|
70
|
+
});
|
|
71
|
+
this.toolToServerMap.set(tool.function.name, endpoint);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
totalTools += serverTools.length;
|
|
75
|
+
// console.log(`[${this.name}] Server ${endpoint}: ${serverTools.length} tools loaded`);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
// console.error(`[${this.name}] Failed to initialize ${endpoint}:`, error);
|
|
78
|
+
// Continue with other servers even if one fails
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// console.log(`[${this.name}] Initialized with ${totalTools} tools from ${this.mcpServers.size} servers`);
|
|
83
|
+
return this;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
// console.error(`[${this.name}] Initialization error:`, error);
|
|
86
|
+
this.tools = [];
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async run(userMessage, options = {}) {
|
|
92
|
+
const { toolApproval = null } = options;
|
|
93
|
+
|
|
94
|
+
const stream = true;
|
|
95
|
+
|
|
96
|
+
if (this.mcpServers.size === 0) {
|
|
97
|
+
await this.initialize();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let userPrompt = userMessage;
|
|
101
|
+
|
|
102
|
+
if (this.llm.no_think) {
|
|
103
|
+
userPrompt = `${userMessage} /no_think`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const messages = [
|
|
107
|
+
{ role: 'system', content: this.instructions },
|
|
108
|
+
{ role: 'user', content: userPrompt },
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
if (stream) {
|
|
112
|
+
return this.runStreaming(messages, { toolApproval });
|
|
113
|
+
}
|
|
114
|
+
return this.runSync(messages, { toolApproval });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async* runStreaming(initialMessages, options = {}) {
|
|
118
|
+
const { toolApproval = null } = options;
|
|
119
|
+
const conversationMessages = [...initialMessages];
|
|
120
|
+
let iteration = 0;
|
|
121
|
+
let finalOutput = '';
|
|
122
|
+
|
|
123
|
+
while (iteration < this.maxIterations) {
|
|
124
|
+
iteration++;
|
|
125
|
+
|
|
126
|
+
yield {
|
|
127
|
+
type: 'iteration_start',
|
|
128
|
+
data: { iteration, messages: conversationMessages },
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const stream = await this.createLLMStream(conversationMessages);
|
|
132
|
+
|
|
133
|
+
const assistantMessage = {
|
|
134
|
+
role: 'assistant',
|
|
135
|
+
content: '',
|
|
136
|
+
tool_calls: [],
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const currentToolCalls = {};
|
|
140
|
+
let hasToolCalls = false;
|
|
141
|
+
let finishReason = null;
|
|
142
|
+
|
|
143
|
+
// Process stream
|
|
144
|
+
for await (const chunk of stream) {
|
|
145
|
+
if (chunk.data && chunk.type === 'data') {
|
|
146
|
+
const chunkData = chunk.data;
|
|
147
|
+
|
|
148
|
+
yield {
|
|
149
|
+
type: 'raw_model_stream_event',
|
|
150
|
+
data: { type: 'model', event: chunkData },
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
if (chunkData.choices?.[0]?.delta) {
|
|
154
|
+
const { delta } = chunkData.choices[0];
|
|
155
|
+
|
|
156
|
+
// Handle content
|
|
157
|
+
if (delta.content) {
|
|
158
|
+
if (delta.content.indexOf('<|processing_prompt|>') >= 0) {
|
|
159
|
+
}
|
|
160
|
+
else if (delta.content.indexOf('<|loading_model|>') >= 0) {
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
assistantMessage.content += delta.content;
|
|
164
|
+
finalOutput += delta.content;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
yield {
|
|
168
|
+
type: 'content_delta',
|
|
169
|
+
data: { content: delta.content },
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Handle tool calls
|
|
174
|
+
if (delta.tool_calls) {
|
|
175
|
+
hasToolCalls = true;
|
|
176
|
+
|
|
177
|
+
for (const toolCallDelta of delta.tool_calls) {
|
|
178
|
+
const { index } = toolCallDelta;
|
|
179
|
+
|
|
180
|
+
if (!currentToolCalls[index]) {
|
|
181
|
+
currentToolCalls[index] = {
|
|
182
|
+
id: '',
|
|
183
|
+
type: 'function',
|
|
184
|
+
function: { name: '', arguments: '' },
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (toolCallDelta.id) currentToolCalls[index].id = toolCallDelta.id;
|
|
189
|
+
if (toolCallDelta.function?.name) currentToolCalls[index].function.name += toolCallDelta.function.name;
|
|
190
|
+
if (toolCallDelta.function?.arguments) currentToolCalls[index].function.arguments += toolCallDelta.function.arguments;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
yield {
|
|
194
|
+
type: 'tool_call_delta',
|
|
195
|
+
data: { tool_calls: delta.tool_calls },
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (chunkData.choices[0].finish_reason) {
|
|
200
|
+
finishReason = chunkData.choices[0].finish_reason;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} else if (chunk.type === 'done') {
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Process tool calls if detected
|
|
209
|
+
if (finishReason === 'tool_calls' && hasToolCalls) {
|
|
210
|
+
assistantMessage.tool_calls = Object.values(currentToolCalls).filter((tc) => tc.id && tc.function.name);
|
|
211
|
+
conversationMessages.push(assistantMessage);
|
|
212
|
+
|
|
213
|
+
yield {
|
|
214
|
+
type: 'tool_calls_detected',
|
|
215
|
+
data: { toolCalls: assistantMessage.tool_calls },
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// Handle tool approval if callback provided
|
|
219
|
+
let approvalResult = {
|
|
220
|
+
stopAfterExecution: false,
|
|
221
|
+
approvals: assistantMessage.tool_calls.map(() => true), // Default: approve all
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
if (toolApproval) {
|
|
225
|
+
try {
|
|
226
|
+
const userApprovalResult = await toolApproval(assistantMessage.tool_calls);
|
|
227
|
+
approvalResult = this.normalizeApprovalResult(userApprovalResult, assistantMessage.tool_calls.length);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
// console.error(`[${this.name}] Tool approval callback error:`, error);
|
|
230
|
+
// On error, deny all tools
|
|
231
|
+
approvalResult = {
|
|
232
|
+
stopAfterExecution: false,
|
|
233
|
+
approvals: assistantMessage.tool_calls.map(() => ({
|
|
234
|
+
approve: false,
|
|
235
|
+
reason: 'Approval callback failed',
|
|
236
|
+
})),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
yield {
|
|
242
|
+
type: 'tool_approval_result',
|
|
243
|
+
data: { approvalResult },
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// Process approved and denied tools
|
|
247
|
+
const { approvedTools, denialMessages } = this.processToolApprovals(
|
|
248
|
+
assistantMessage.tool_calls,
|
|
249
|
+
approvalResult.approvals,
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// Add denial messages to conversation
|
|
253
|
+
conversationMessages.push(...denialMessages);
|
|
254
|
+
|
|
255
|
+
// Execute approved tools
|
|
256
|
+
if (approvedTools.length > 0) {
|
|
257
|
+
const toolResults = await this.executeTools(approvedTools);
|
|
258
|
+
conversationMessages.push(...toolResults);
|
|
259
|
+
|
|
260
|
+
yield {
|
|
261
|
+
type: 'tool_results',
|
|
262
|
+
data: { results: toolResults },
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Check if we should stop after execution
|
|
267
|
+
if (approvalResult.stopAfterExecution) {
|
|
268
|
+
yield {
|
|
269
|
+
type: 'conversation_complete',
|
|
270
|
+
data: {
|
|
271
|
+
finalOutput: finalOutput || 'Tool execution completed.',
|
|
272
|
+
messages: conversationMessages,
|
|
273
|
+
iterations: iteration,
|
|
274
|
+
stoppedAfterToolExecution: true,
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
finalOutput: finalOutput || 'Tool execution completed.',
|
|
280
|
+
messages: conversationMessages,
|
|
281
|
+
stoppedAfterToolExecution: true,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Continue conversation with tool results
|
|
286
|
+
continue;
|
|
287
|
+
} else {
|
|
288
|
+
// Conversation finished
|
|
289
|
+
if (assistantMessage.content || assistantMessage.tool_calls.length > 0) {
|
|
290
|
+
conversationMessages.push(assistantMessage);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
yield {
|
|
294
|
+
type: 'conversation_complete',
|
|
295
|
+
data: {
|
|
296
|
+
finalOutput,
|
|
297
|
+
messages: conversationMessages,
|
|
298
|
+
iterations: iteration,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
return { finalOutput, messages: conversationMessages };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
yield {
|
|
307
|
+
type: 'max_iterations_reached',
|
|
308
|
+
data: { maxIterations: this.maxIterations, finalOutput },
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
return { finalOutput, messages: conversationMessages };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async runSync(initialMessages, options = {}) {
|
|
315
|
+
const { toolApproval = null } = options;
|
|
316
|
+
const events = [];
|
|
317
|
+
for await (const event of this.runStreaming(initialMessages, { toolApproval })) {
|
|
318
|
+
events.push(event);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const finalEvent = events[events.length - 1];
|
|
322
|
+
return finalEvent.data;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async createLLMStream(messages) {
|
|
326
|
+
const body = {
|
|
327
|
+
model: this.llm.model,
|
|
328
|
+
messages,
|
|
329
|
+
tools: this.tools,
|
|
330
|
+
tool_choice: 'auto',
|
|
331
|
+
temperature: this.llm.temperature,
|
|
332
|
+
max_tokens: this.llm.max_tokens,
|
|
333
|
+
stream: true,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
return createChatCompletionStream(
|
|
337
|
+
{ http: this.httpClient },
|
|
338
|
+
JSON.stringify(body),
|
|
339
|
+
this.llm.endpoint,
|
|
340
|
+
this.llm.apiKey,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Helper method to normalize approval result from user callback
|
|
345
|
+
normalizeApprovalResult(userResult, toolCount) {
|
|
346
|
+
// Handle simple boolean array format
|
|
347
|
+
if (Array.isArray(userResult)) {
|
|
348
|
+
return {
|
|
349
|
+
stopAfterExecution: false, // Default
|
|
350
|
+
approvals: userResult,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Handle object format
|
|
355
|
+
if (typeof userResult === 'object' && userResult !== null) {
|
|
356
|
+
return {
|
|
357
|
+
stopAfterExecution: userResult.stopAfterExecution || false,
|
|
358
|
+
approvals: userResult.approvals || Array(toolCount).fill(true),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Fallback - approve all
|
|
363
|
+
return {
|
|
364
|
+
stopAfterExecution: false,
|
|
365
|
+
approvals: Array(toolCount).fill(true),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Helper method to process tool approvals and generate denial messages
|
|
370
|
+
processToolApprovals(toolCalls, approvals) {
|
|
371
|
+
const approvedTools = [];
|
|
372
|
+
const denialMessages = [];
|
|
373
|
+
|
|
374
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
375
|
+
const toolCall = toolCalls[i];
|
|
376
|
+
const approval = approvals[i];
|
|
377
|
+
|
|
378
|
+
// Normalize approval to object format
|
|
379
|
+
let normalizedApproval;
|
|
380
|
+
if (typeof approval === 'boolean') {
|
|
381
|
+
normalizedApproval = { approve: approval, reason: null };
|
|
382
|
+
} else if (typeof approval === 'object' && approval !== null) {
|
|
383
|
+
normalizedApproval = {
|
|
384
|
+
approve: approval.approve !== false, // Default to true if not explicitly false
|
|
385
|
+
reason: approval.reason || null,
|
|
386
|
+
};
|
|
387
|
+
} else {
|
|
388
|
+
normalizedApproval = { approve: true, reason: null }; // Default approve
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (normalizedApproval.approve) {
|
|
392
|
+
approvedTools.push(toolCall);
|
|
393
|
+
} else {
|
|
394
|
+
// Create denial message with standard format
|
|
395
|
+
const reason = normalizedApproval.reason
|
|
396
|
+
? ` because: ${normalizedApproval.reason}`
|
|
397
|
+
: '.';
|
|
398
|
+
|
|
399
|
+
denialMessages.push({
|
|
400
|
+
role: 'user',
|
|
401
|
+
content: `I denied the ${toolCall.function.name} tool${reason}`,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return { approvedTools, denialMessages };
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async executeTools(toolCalls) {
|
|
410
|
+
const toolResults = [];
|
|
411
|
+
|
|
412
|
+
for (const toolCall of toolCalls) {
|
|
413
|
+
try {
|
|
414
|
+
// Find which server handles this tool
|
|
415
|
+
const serverEndpoint = this.toolToServerMap.get(toolCall.function.name);
|
|
416
|
+
const mcpServer = this.mcpServers.get(serverEndpoint);
|
|
417
|
+
|
|
418
|
+
if (!mcpServer) {
|
|
419
|
+
throw new Error(`No MCP server found for tool: ${toolCall.function.name}`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
let args = toolCall.function.arguments;
|
|
423
|
+
if (typeof args === 'string') {
|
|
424
|
+
args = JSON.parse(args);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// console.log(`[${this.name}] Calling tool ${toolCall.function.name} on server ${serverEndpoint}`);
|
|
428
|
+
|
|
429
|
+
const result = await mcpServer.callTool({
|
|
430
|
+
name: toolCall.function.name,
|
|
431
|
+
arguments: args,
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
toolResults.push({
|
|
435
|
+
role: 'tool',
|
|
436
|
+
tool_call_id: toolCall.id,
|
|
437
|
+
name: toolCall.function.name,
|
|
438
|
+
content: JSON.stringify(result),
|
|
439
|
+
});
|
|
440
|
+
} catch (error) {
|
|
441
|
+
// console.error(`[${this.name}] Error calling tool ${toolCall.function.name}:`, error);
|
|
442
|
+
|
|
443
|
+
toolResults.push({
|
|
444
|
+
role: 'tool',
|
|
445
|
+
tool_call_id: toolCall.id,
|
|
446
|
+
name: toolCall.function.name,
|
|
447
|
+
content: JSON.stringify({ error: error.message }),
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return toolResults;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
module.exports = { Agent };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const { createChatCompletionStream, parseSSEChunk } = require('./oai');
|
|
2
|
+
const { Agent } = require('./agent');
|
|
3
|
+
const { McpResponseParser } = require('./mcp-response-parser');
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
createChatCompletionStream,
|
|
7
|
+
parseSSEChunk,
|
|
8
|
+
Agent,
|
|
9
|
+
McpResponseParser,
|
|
10
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simplified parser for MCP (Model Context Protocol) tool responses
|
|
3
|
+
* Handles the actual MCP format without over-engineering
|
|
4
|
+
*
|
|
5
|
+
* Expected MCP Tool Response Format:
|
|
6
|
+
* [
|
|
7
|
+
* {
|
|
8
|
+
* "role": "tool",
|
|
9
|
+
* "tool_call_id": "tool_0",
|
|
10
|
+
* "name": "read_text_file",
|
|
11
|
+
* "content": "{\"content\":[{\"type\":\"text\",\"text\":\"actual file content here...\"}]}"
|
|
12
|
+
* }
|
|
13
|
+
* ]
|
|
14
|
+
*
|
|
15
|
+
* The "content" field is a JSON string that when parsed contains:
|
|
16
|
+
* {
|
|
17
|
+
* "content": [
|
|
18
|
+
* {
|
|
19
|
+
* "type": "text|image|resource|etc",
|
|
20
|
+
* "text": "content for text type",
|
|
21
|
+
* "data": "content for image/binary types"
|
|
22
|
+
* }
|
|
23
|
+
* ]
|
|
24
|
+
* }
|
|
25
|
+
*/
|
|
26
|
+
class McpResponseParser {
|
|
27
|
+
constructor(toolResults) {
|
|
28
|
+
this.results = toolResults || [];
|
|
29
|
+
this.contentByType = {};
|
|
30
|
+
|
|
31
|
+
this._parseResults();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Parse the tool results and organize content
|
|
36
|
+
* @private
|
|
37
|
+
*/
|
|
38
|
+
_parseResults() {
|
|
39
|
+
for (const result of this.results) {
|
|
40
|
+
if (result.content) {
|
|
41
|
+
try {
|
|
42
|
+
// Parse the JSON content string
|
|
43
|
+
const parsed = JSON.parse(result.content);
|
|
44
|
+
const contents = parsed.content || [];
|
|
45
|
+
|
|
46
|
+
// Organize content by type
|
|
47
|
+
for (const item of contents) {
|
|
48
|
+
const type = item.type || 'unknown';
|
|
49
|
+
|
|
50
|
+
if (!this.contentByType[type]) {
|
|
51
|
+
this.contentByType[type] = [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.contentByType[type].push({
|
|
55
|
+
...item,
|
|
56
|
+
toolId: result.tool_call_id,
|
|
57
|
+
toolName: result.name,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.log(`Failed to parse tool result content: ${error.message}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// === Tool Information ===
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get all tool IDs
|
|
71
|
+
*/
|
|
72
|
+
getToolIds() {
|
|
73
|
+
return this.results.map(result => result.tool_call_id).filter(Boolean);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get all tool names
|
|
78
|
+
*/
|
|
79
|
+
getToolNames() {
|
|
80
|
+
return this.results.map(result => result.name).filter(Boolean);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get result by tool ID
|
|
85
|
+
*/
|
|
86
|
+
getResultByToolId(toolId) {
|
|
87
|
+
return this.results.find(result => result.tool_call_id === toolId) || null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get result by tool name
|
|
92
|
+
*/
|
|
93
|
+
getResultByToolName(toolName) {
|
|
94
|
+
return this.results.find(result => result.name === toolName) || null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// === Content Access ===
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get content by type
|
|
101
|
+
*/
|
|
102
|
+
getContentByType(type) {
|
|
103
|
+
return this.contentByType[type] || [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get all content organized by type
|
|
108
|
+
*/
|
|
109
|
+
getAllContent() {
|
|
110
|
+
return { ...this.contentByType };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get text content (convenience method)
|
|
115
|
+
*/
|
|
116
|
+
getTextContent() {
|
|
117
|
+
const textItems = this.getContentByType('text');
|
|
118
|
+
if (textItems.length === 0) return null;
|
|
119
|
+
if (textItems.length === 1) return textItems[0].text;
|
|
120
|
+
return textItems.map(item => item.text);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get image content (convenience method)
|
|
125
|
+
*/
|
|
126
|
+
getImageContent() {
|
|
127
|
+
return this.getContentByType('image');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get resource content (convenience method)
|
|
132
|
+
*/
|
|
133
|
+
getResourceContent() {
|
|
134
|
+
return this.getContentByType('resource');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// === Summary Info ===
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check if we have any results
|
|
141
|
+
*/
|
|
142
|
+
hasResults() {
|
|
143
|
+
return this.results.length > 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get available content types
|
|
148
|
+
*/
|
|
149
|
+
getContentTypes() {
|
|
150
|
+
return Object.keys(this.contentByType);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get summary of parsing results
|
|
155
|
+
*/
|
|
156
|
+
getSummary() {
|
|
157
|
+
return {
|
|
158
|
+
toolCount: this.results.length,
|
|
159
|
+
contentTypes: this.getContentTypes(),
|
|
160
|
+
toolIds: this.getToolIds(),
|
|
161
|
+
toolNames: this.getToolNames(),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Convert to JSON for serialization
|
|
167
|
+
*/
|
|
168
|
+
toJSON() {
|
|
169
|
+
return {
|
|
170
|
+
summary: this.getSummary(),
|
|
171
|
+
contentByType: this.contentByType,
|
|
172
|
+
results: this.results,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = { McpResponseParser };
|