@mcp-ts/sdk 1.0.0 → 1.0.1
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 +3 -3
- package/dist/adapters/agui-adapter.d.mts +19 -42
- package/dist/adapters/agui-adapter.d.ts +19 -42
- package/dist/adapters/agui-adapter.js +69 -69
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs +69 -70
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- package/dist/adapters/agui-middleware.d.mts +24 -136
- package/dist/adapters/agui-middleware.d.ts +24 -136
- package/dist/adapters/agui-middleware.js +275 -350
- package/dist/adapters/agui-middleware.js.map +1 -1
- package/dist/adapters/agui-middleware.mjs +275 -350
- package/dist/adapters/agui-middleware.mjs.map +1 -1
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/react.d.mts +2 -2
- package/dist/client/react.d.ts +2 -2
- package/dist/client/vue.d.mts +2 -2
- package/dist/client/vue.d.ts +2 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +3 -3
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +2 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +2 -1
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{types-SbDlA2VX.d.mts → types-CLccx9wW.d.mts} +1 -1
- package/dist/{types-SbDlA2VX.d.ts → types-CLccx9wW.d.ts} +1 -1
- package/package.json +2 -2
- package/src/adapters/agui-adapter.ts +98 -109
- package/src/adapters/agui-middleware.ts +424 -512
- package/src/server/handlers/sse-handler.ts +4 -1
- package/src/server/storage/types.ts +1 -1
- package/src/shared/types.ts +1 -1
|
@@ -1,512 +1,424 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AG-UI Middleware for MCP Tool Execution
|
|
3
|
-
*
|
|
4
|
-
* This middleware intercepts tool calls from remote agents
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
*
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
private
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
console.log(`[McpMiddleware]
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
} catch (e) {
|
|
426
|
-
console.error(`[McpMiddleware] Failed to parse args:`, argsString);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
console.log(`[McpMiddleware] Executing pending tool: ${toolName}`);
|
|
430
|
-
const result = await this.executeTool(toolName, args);
|
|
431
|
-
const messageId = this.generateMessageId();
|
|
432
|
-
|
|
433
|
-
const resultEvent: BaseEvent = {
|
|
434
|
-
type: EventType.TOOL_CALL_RESULT,
|
|
435
|
-
toolCallId,
|
|
436
|
-
messageId,
|
|
437
|
-
content: result,
|
|
438
|
-
role: 'tool',
|
|
439
|
-
timestamp: Date.now(),
|
|
440
|
-
} as any;
|
|
441
|
-
|
|
442
|
-
console.log(`[McpMiddleware] Emitting TOOL_CALL_RESULT for: ${toolName}`);
|
|
443
|
-
observer.next(resultEvent);
|
|
444
|
-
|
|
445
|
-
input.messages.push({
|
|
446
|
-
id: messageId,
|
|
447
|
-
role: 'tool',
|
|
448
|
-
toolCallId,
|
|
449
|
-
content: result,
|
|
450
|
-
} as any);
|
|
451
|
-
|
|
452
|
-
pendingMcpCalls.delete(toolCallId);
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
await Promise.all(callPromises);
|
|
456
|
-
|
|
457
|
-
console.log(`[McpMiddleware] Pending tools executed, emitting RUN_FINISHED`);
|
|
458
|
-
observer.next({
|
|
459
|
-
type: EventType.RUN_FINISHED,
|
|
460
|
-
threadId: (input as any).threadId,
|
|
461
|
-
runId: (input as any).runId,
|
|
462
|
-
timestamp: Date.now(),
|
|
463
|
-
} as any);
|
|
464
|
-
|
|
465
|
-
console.log(`[McpMiddleware] Triggering new run`);
|
|
466
|
-
this.triggerNewRun(observer, input, next, toolCallArgsBuffer, toolCallNames, pendingMcpCalls);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* Factory function to create MCP middleware.
|
|
472
|
-
*
|
|
473
|
-
* This is a convenience wrapper around McpMiddleware that returns a function
|
|
474
|
-
* compatible with the AG-UI middleware pattern.
|
|
475
|
-
*
|
|
476
|
-
* @param client - MCP client or MultiSessionClient
|
|
477
|
-
* @param options - Configuration options
|
|
478
|
-
* @returns Middleware function
|
|
479
|
-
*
|
|
480
|
-
* @example
|
|
481
|
-
* ```typescript
|
|
482
|
-
* import { HttpAgent } from '@ag-ui/client';
|
|
483
|
-
* import { createMcpMiddleware } from '@mcp-ts/sdk/adapters/agui-middleware';
|
|
484
|
-
*
|
|
485
|
-
* const agent = new HttpAgent({ url: 'http://localhost:8000/agent' });
|
|
486
|
-
* agent.use(createMcpMiddleware(multiSessionClient, {
|
|
487
|
-
* toolPrefix: 'server-',
|
|
488
|
-
* actions: mcpActions,
|
|
489
|
-
* }));
|
|
490
|
-
* ```
|
|
491
|
-
*/
|
|
492
|
-
export function createMcpMiddleware(
|
|
493
|
-
client: MCPClient | MultiSessionClient,
|
|
494
|
-
options: { toolPrefix?: string; tools?: AguiTool[] } = {}
|
|
495
|
-
) {
|
|
496
|
-
const middleware = new McpMiddleware({
|
|
497
|
-
client,
|
|
498
|
-
...options,
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
return (input: RunAgentInput, next: AbstractAgent): Observable<BaseEvent> => {
|
|
502
|
-
return middleware.run(input, next);
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// Legacy exports for backward compatibility
|
|
507
|
-
export { McpMiddleware as McpToolExecutorMiddleware };
|
|
508
|
-
export { createMcpMiddleware as createMcpToolMiddleware };
|
|
509
|
-
|
|
510
|
-
// Re-export types for convenience
|
|
511
|
-
export { Middleware, EventType };
|
|
512
|
-
export type { RunAgentInput, BaseEvent, AbstractAgent, ToolCallEndEvent };
|
|
1
|
+
/**
|
|
2
|
+
* AG-UI Middleware for MCP Tool Execution
|
|
3
|
+
*
|
|
4
|
+
* This middleware intercepts tool calls from remote agents and executes
|
|
5
|
+
* MCP tools server-side, returning results back to the agent.
|
|
6
|
+
*
|
|
7
|
+
* @requires @ag-ui/client - Peer dependency for AG-UI types
|
|
8
|
+
* @requires rxjs - Uses RxJS Observables for event streaming
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Observable, Subscriber } from 'rxjs';
|
|
12
|
+
import {
|
|
13
|
+
Middleware,
|
|
14
|
+
EventType,
|
|
15
|
+
type AbstractAgent,
|
|
16
|
+
type RunAgentInput,
|
|
17
|
+
type BaseEvent,
|
|
18
|
+
type ToolCallEndEvent,
|
|
19
|
+
type Tool,
|
|
20
|
+
} from '@ag-ui/client';
|
|
21
|
+
import { type AguiTool, cleanSchema } from './agui-adapter.js';
|
|
22
|
+
|
|
23
|
+
/** Tool execution result for continuation */
|
|
24
|
+
interface ToolResult {
|
|
25
|
+
toolCallId: string;
|
|
26
|
+
toolName: string;
|
|
27
|
+
result: string;
|
|
28
|
+
messageId: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** State for tracking tool calls during a run */
|
|
32
|
+
interface RunState {
|
|
33
|
+
toolCallArgsBuffer: Map<string, string>;
|
|
34
|
+
toolCallNames: Map<string, string>;
|
|
35
|
+
pendingMcpCalls: Set<string>;
|
|
36
|
+
textContent?: string;
|
|
37
|
+
error: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Configuration for McpMiddleware
|
|
42
|
+
*/
|
|
43
|
+
export interface McpMiddlewareConfig {
|
|
44
|
+
/** Pre-loaded tools with handlers (required) */
|
|
45
|
+
tools: AguiTool[];
|
|
46
|
+
/** Max result length in chars (default: 50000) */
|
|
47
|
+
maxResultLength?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* AG-UI Middleware that executes MCP tools server-side.
|
|
52
|
+
*/
|
|
53
|
+
export class McpMiddleware extends Middleware {
|
|
54
|
+
private tools: AguiTool[];
|
|
55
|
+
private toolSchemas: Tool[];
|
|
56
|
+
private maxResultLength: number;
|
|
57
|
+
|
|
58
|
+
constructor(config: McpMiddlewareConfig) {
|
|
59
|
+
super();
|
|
60
|
+
this.tools = config.tools;
|
|
61
|
+
this.maxResultLength = config.maxResultLength ?? 50000;
|
|
62
|
+
this.toolSchemas = this.tools.map((t: AguiTool) => ({
|
|
63
|
+
name: t.name,
|
|
64
|
+
description: t.description,
|
|
65
|
+
parameters: cleanSchema(t.parameters),
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private isMcpTool(toolName: string): boolean {
|
|
70
|
+
return this.tools.some(t => t.name === toolName);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private parseArgs(argsString: string): Record<string, any> {
|
|
74
|
+
if (!argsString?.trim()) return {};
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(argsString);
|
|
78
|
+
} catch {
|
|
79
|
+
// Handle duplicated JSON from streaming issues: {...}{...}
|
|
80
|
+
const trimmed = argsString.trim();
|
|
81
|
+
if (trimmed.includes('}{')) {
|
|
82
|
+
const firstObject = trimmed.slice(0, trimmed.indexOf('}{') + 1);
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(firstObject);
|
|
85
|
+
} catch {
|
|
86
|
+
console.error(`[McpMiddleware] Failed to parse JSON:`, firstObject);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
console.error(`[McpMiddleware] Failed to parse args:`, argsString);
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private async executeTool(toolName: string, args: Record<string, any>): Promise<string> {
|
|
95
|
+
const tool = this.tools.find(t => t.name === toolName);
|
|
96
|
+
if (!tool?.handler) {
|
|
97
|
+
return `Error: Tool ${tool ? 'has no handler' : 'not found'}: ${toolName}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
console.log(`[McpMiddleware] Executing tool: ${toolName}`, args);
|
|
102
|
+
const result = await tool.handler(args);
|
|
103
|
+
let resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
|
104
|
+
|
|
105
|
+
if (resultStr.length > this.maxResultLength) {
|
|
106
|
+
const original = resultStr.length;
|
|
107
|
+
resultStr = resultStr.slice(0, this.maxResultLength) +
|
|
108
|
+
`\n\n[... Truncated from ${original} to ${this.maxResultLength} chars]`;
|
|
109
|
+
console.log(`[McpMiddleware] Tool result truncated from ${original} to ${this.maxResultLength} chars`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`[McpMiddleware] Tool result:`, resultStr.slice(0, 200));
|
|
113
|
+
return resultStr;
|
|
114
|
+
} catch (error: any) {
|
|
115
|
+
console.error(`[McpMiddleware] Error executing tool:`, error);
|
|
116
|
+
return `Error: ${error.message || String(error)}`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private generateId(prefix: string): string {
|
|
121
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private ensureIds(input: RunAgentInput): void {
|
|
125
|
+
const anyInput = input as any;
|
|
126
|
+
if (!anyInput.threadId) anyInput.threadId = this.generateId('mcp_thread');
|
|
127
|
+
if (!anyInput.runId) anyInput.runId = this.generateId('mcp_run');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Process tool call events and update state */
|
|
131
|
+
private handleToolCallEvent(event: BaseEvent, state: RunState): void {
|
|
132
|
+
const { toolCallArgsBuffer, toolCallNames, pendingMcpCalls } = state;
|
|
133
|
+
|
|
134
|
+
// Accumulate text content for reconstruction
|
|
135
|
+
if (event.type === EventType.TEXT_MESSAGE_CHUNK) {
|
|
136
|
+
const e = event as any;
|
|
137
|
+
if (e.delta) {
|
|
138
|
+
state.textContent = (state.textContent || '') + e.delta;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (event.type === EventType.TOOL_CALL_START) {
|
|
143
|
+
const e = event as any;
|
|
144
|
+
if (e.toolCallId && e.toolCallName) {
|
|
145
|
+
toolCallNames.set(e.toolCallId, e.toolCallName);
|
|
146
|
+
if (this.isMcpTool(e.toolCallName)) {
|
|
147
|
+
pendingMcpCalls.add(e.toolCallId);
|
|
148
|
+
}
|
|
149
|
+
console.log(`[McpMiddleware] TOOL_CALL_START: ${e.toolCallName} (id: ${e.toolCallId}, isMCP: ${this.isMcpTool(e.toolCallName)})`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (event.type === EventType.TOOL_CALL_ARGS) {
|
|
154
|
+
const e = event as any;
|
|
155
|
+
if (e.toolCallId && e.delta) {
|
|
156
|
+
const existing = toolCallArgsBuffer.get(e.toolCallId) || '';
|
|
157
|
+
toolCallArgsBuffer.set(e.toolCallId, existing + e.delta);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (event.type === EventType.TOOL_CALL_END) {
|
|
162
|
+
const e = event as ToolCallEndEvent;
|
|
163
|
+
console.log(`[McpMiddleware] TOOL_CALL_END: ${toolCallNames.get(e.toolCallId) ?? 'unknown'} (id: ${e.toolCallId})`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Workaround: Extract parallel tool calls from MESSAGES_SNAPSHOT
|
|
167
|
+
if (event.type === EventType.MESSAGES_SNAPSHOT) {
|
|
168
|
+
const messages = (event as any).messages || [];
|
|
169
|
+
if (messages.length > 0) {
|
|
170
|
+
const lastMsg = messages[messages.length - 1];
|
|
171
|
+
// Update text content from snapshot if available (often more reliable)
|
|
172
|
+
if (lastMsg.role === 'assistant' && lastMsg.content) {
|
|
173
|
+
state.textContent = lastMsg.content;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Discover tools
|
|
177
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
178
|
+
const msg = messages[i];
|
|
179
|
+
const tools = Array.isArray(msg.toolCalls) ? msg.toolCalls :
|
|
180
|
+
(Array.isArray(msg.tool_calls) ? msg.tool_calls : []);
|
|
181
|
+
|
|
182
|
+
if (msg.role === 'assistant' && tools.length > 0) {
|
|
183
|
+
for (const tc of tools) {
|
|
184
|
+
if (tc.id && tc.function?.name && !toolCallNames.has(tc.id)) {
|
|
185
|
+
toolCallNames.set(tc.id, tc.function.name);
|
|
186
|
+
toolCallArgsBuffer.set(tc.id, tc.function.arguments || '{}');
|
|
187
|
+
if (this.isMcpTool(tc.function.name)) {
|
|
188
|
+
pendingMcpCalls.add(tc.id);
|
|
189
|
+
console.log(`[McpMiddleware] MESSAGES_SNAPSHOT: Discovered ${tc.function.name} (id: ${tc.id})`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Execute pending MCP tools and return results */
|
|
201
|
+
private async executeTools(state: RunState): Promise<ToolResult[]> {
|
|
202
|
+
const { toolCallArgsBuffer, toolCallNames, pendingMcpCalls } = state;
|
|
203
|
+
const results: ToolResult[] = [];
|
|
204
|
+
|
|
205
|
+
const promises = [...pendingMcpCalls].map(async (toolCallId) => {
|
|
206
|
+
const toolName = toolCallNames.get(toolCallId);
|
|
207
|
+
if (!toolName) return;
|
|
208
|
+
|
|
209
|
+
const args = this.parseArgs(toolCallArgsBuffer.get(toolCallId) || '{}');
|
|
210
|
+
console.log(`[McpMiddleware] Executing pending tool: ${toolName}`);
|
|
211
|
+
|
|
212
|
+
const result = await this.executeTool(toolName, args);
|
|
213
|
+
results.push({
|
|
214
|
+
toolCallId,
|
|
215
|
+
toolName,
|
|
216
|
+
result,
|
|
217
|
+
messageId: this.generateId('mcp_result'),
|
|
218
|
+
});
|
|
219
|
+
pendingMcpCalls.delete(toolCallId);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
await Promise.all(promises);
|
|
223
|
+
return results;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Emit tool results (without RUN_FINISHED - that's emitted when truly done) */
|
|
227
|
+
private emitToolResults(observer: Subscriber<BaseEvent>, results: ToolResult[]): void {
|
|
228
|
+
for (const { toolCallId, toolName, result, messageId } of results) {
|
|
229
|
+
observer.next({
|
|
230
|
+
type: EventType.TOOL_CALL_RESULT,
|
|
231
|
+
toolCallId,
|
|
232
|
+
messageId,
|
|
233
|
+
content: result,
|
|
234
|
+
role: 'tool',
|
|
235
|
+
timestamp: Date.now(),
|
|
236
|
+
} as any);
|
|
237
|
+
console.log(`[McpMiddleware] Emitting TOOL_CALL_RESULT for: ${toolName}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
run(input: RunAgentInput, next: AbstractAgent): Observable<BaseEvent> {
|
|
242
|
+
return new Observable<BaseEvent>((observer: Subscriber<BaseEvent>) => {
|
|
243
|
+
const state: RunState = {
|
|
244
|
+
toolCallArgsBuffer: new Map(),
|
|
245
|
+
toolCallNames: new Map(),
|
|
246
|
+
pendingMcpCalls: new Set(),
|
|
247
|
+
textContent: '',
|
|
248
|
+
error: false,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
this.ensureIds(input);
|
|
252
|
+
const anyInput = input as any;
|
|
253
|
+
|
|
254
|
+
console.log(`[McpMiddleware] === NEW RUN ===`);
|
|
255
|
+
console.log(`[McpMiddleware] threadId: ${anyInput.threadId}, runId: ${anyInput.runId}`);
|
|
256
|
+
console.log(`[McpMiddleware] messages: ${input.messages?.length ?? 0}, tools: ${this.tools?.length ?? 0}`);
|
|
257
|
+
|
|
258
|
+
// Inject MCP tools
|
|
259
|
+
if (this.toolSchemas?.length) {
|
|
260
|
+
input.tools = [...(input.tools || []), ...this.toolSchemas];
|
|
261
|
+
console.log(`[McpMiddleware] Injected ${this.toolSchemas.length} tools:`, this.toolSchemas.map((t: Tool) => t.name));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const handleRunFinished = async () => {
|
|
265
|
+
if (state.error) return; // Don't continue after error
|
|
266
|
+
|
|
267
|
+
if (state.pendingMcpCalls.size === 0) {
|
|
268
|
+
observer.next({
|
|
269
|
+
type: EventType.RUN_FINISHED,
|
|
270
|
+
threadId: anyInput.threadId,
|
|
271
|
+
runId: anyInput.runId,
|
|
272
|
+
timestamp: Date.now(),
|
|
273
|
+
} as any);
|
|
274
|
+
observer.complete();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
console.log(`[McpMiddleware] RUN_FINISHED with ${state.pendingMcpCalls.size} pending calls`);
|
|
279
|
+
|
|
280
|
+
// Reconstruct the Assistant Message that triggered these tools
|
|
281
|
+
const toolCalls = [];
|
|
282
|
+
for (const toolCallId of state.pendingMcpCalls) {
|
|
283
|
+
const name = state.toolCallNames.get(toolCallId);
|
|
284
|
+
const args = state.toolCallArgsBuffer.get(toolCallId) || '{}';
|
|
285
|
+
if (name) {
|
|
286
|
+
toolCalls.push({
|
|
287
|
+
id: toolCallId,
|
|
288
|
+
type: 'function',
|
|
289
|
+
function: { name, arguments: args }
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Add the Assistant Message to history FIRST
|
|
295
|
+
if (toolCalls.length > 0 || state.textContent) {
|
|
296
|
+
const assistantMsg = {
|
|
297
|
+
id: this.generateId('msg_ast'),
|
|
298
|
+
role: 'assistant',
|
|
299
|
+
content: state.textContent || null, // Ensure null if empty string for strict LLMs
|
|
300
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : undefined
|
|
301
|
+
};
|
|
302
|
+
input.messages.push(assistantMsg as any);
|
|
303
|
+
console.log(`[McpMiddleware] Added assistant message to history before tools: ${state.textContent?.slice(0, 50)}... [${toolCalls.length} tools]`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Execute tools and emit results (no RUN_FINISHED yet - continuation follows)
|
|
307
|
+
const results = await this.executeTools(state);
|
|
308
|
+
this.emitToolResults(observer, results);
|
|
309
|
+
|
|
310
|
+
// Prepare continuation
|
|
311
|
+
console.log(`[McpMiddleware] Triggering continuation with ${results.length} results`);
|
|
312
|
+
|
|
313
|
+
// Add tool result messages to history
|
|
314
|
+
for (const { toolCallId, result, messageId } of results) {
|
|
315
|
+
input.messages.push({
|
|
316
|
+
id: messageId,
|
|
317
|
+
role: 'tool',
|
|
318
|
+
tool_call_id: toolCallId,
|
|
319
|
+
content: result,
|
|
320
|
+
} as any);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Reset state for next turn
|
|
324
|
+
state.toolCallArgsBuffer.clear();
|
|
325
|
+
state.toolCallNames.clear();
|
|
326
|
+
state.textContent = ''; // Clear text content for next turn
|
|
327
|
+
|
|
328
|
+
anyInput.runId = this.generateId('mcp_run');
|
|
329
|
+
console.log(`[McpMiddleware] === CONTINUATION RUN === messages: ${input.messages.length}`);
|
|
330
|
+
|
|
331
|
+
// Subscribe to continuation
|
|
332
|
+
next.run(input).subscribe({
|
|
333
|
+
next: (event) => {
|
|
334
|
+
if (state.error) return;
|
|
335
|
+
|
|
336
|
+
this.handleToolCallEvent(event, state);
|
|
337
|
+
|
|
338
|
+
if (event.type === EventType.RUN_ERROR) {
|
|
339
|
+
console.log(`[McpMiddleware] RUN_ERROR received in continuation`);
|
|
340
|
+
state.error = true;
|
|
341
|
+
observer.next(event);
|
|
342
|
+
observer.complete();
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (event.type === EventType.RUN_STARTED) {
|
|
347
|
+
console.log(`[McpMiddleware] Filtering RUN_STARTED from continuation`);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (event.type === EventType.RUN_FINISHED) {
|
|
352
|
+
if (state.pendingMcpCalls.size > 0) {
|
|
353
|
+
handleRunFinished();
|
|
354
|
+
} else {
|
|
355
|
+
observer.next(event);
|
|
356
|
+
observer.complete();
|
|
357
|
+
}
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
observer.next(event);
|
|
361
|
+
},
|
|
362
|
+
error: (err) => {
|
|
363
|
+
state.error = true;
|
|
364
|
+
observer.error(err);
|
|
365
|
+
},
|
|
366
|
+
complete: () => {
|
|
367
|
+
if (!state.error && state.pendingMcpCalls.size === 0) observer.complete();
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const subscription = next.run(input).subscribe({
|
|
373
|
+
next: (event) => {
|
|
374
|
+
if (state.error) return;
|
|
375
|
+
|
|
376
|
+
this.handleToolCallEvent(event, state);
|
|
377
|
+
|
|
378
|
+
if (event.type === EventType.RUN_ERROR) {
|
|
379
|
+
console.log(`[McpMiddleware] RUN_ERROR received`);
|
|
380
|
+
state.error = true;
|
|
381
|
+
observer.next(event);
|
|
382
|
+
observer.complete();
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (event.type === EventType.RUN_FINISHED) {
|
|
387
|
+
handleRunFinished();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
observer.next(event);
|
|
391
|
+
},
|
|
392
|
+
error: (err) => {
|
|
393
|
+
state.error = true;
|
|
394
|
+
observer.error(err);
|
|
395
|
+
},
|
|
396
|
+
complete: () => {
|
|
397
|
+
if (!state.error && state.pendingMcpCalls.size === 0) observer.complete();
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
return () => subscription.unsubscribe();
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Factory function to create MCP middleware.
|
|
408
|
+
*/
|
|
409
|
+
export function createMcpMiddleware(
|
|
410
|
+
options: { tools: AguiTool[]; maxResultLength?: number }
|
|
411
|
+
) {
|
|
412
|
+
const middleware = new McpMiddleware(options);
|
|
413
|
+
return (input: RunAgentInput, next: AbstractAgent): Observable<BaseEvent> => {
|
|
414
|
+
return middleware.run(input, next);
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Legacy exports
|
|
419
|
+
export { McpMiddleware as McpToolExecutorMiddleware };
|
|
420
|
+
export { createMcpMiddleware as createMcpToolMiddleware };
|
|
421
|
+
|
|
422
|
+
// Re-exports
|
|
423
|
+
export { Middleware, EventType };
|
|
424
|
+
export type { RunAgentInput, BaseEvent, AbstractAgent, ToolCallEndEvent, Tool };
|