@mcp-ts/sdk 1.3.7 → 1.3.9

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.
Files changed (61) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +398 -404
  3. package/dist/adapters/agui-middleware.js.map +1 -1
  4. package/dist/adapters/agui-middleware.mjs.map +1 -1
  5. package/dist/bin/mcp-ts.js +0 -0
  6. package/dist/bin/mcp-ts.js.map +1 -1
  7. package/dist/bin/mcp-ts.mjs +0 -0
  8. package/dist/bin/mcp-ts.mjs.map +1 -1
  9. package/dist/client/index.js.map +1 -1
  10. package/dist/client/index.mjs.map +1 -1
  11. package/dist/client/react.d.mts +2 -2
  12. package/dist/client/react.d.ts +2 -2
  13. package/dist/client/react.js +25 -2
  14. package/dist/client/react.js.map +1 -1
  15. package/dist/client/react.mjs +26 -3
  16. package/dist/client/react.mjs.map +1 -1
  17. package/dist/client/vue.js.map +1 -1
  18. package/dist/client/vue.mjs.map +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/server/index.js.map +1 -1
  22. package/dist/server/index.mjs.map +1 -1
  23. package/dist/shared/index.js.map +1 -1
  24. package/dist/shared/index.mjs.map +1 -1
  25. package/package.json +185 -185
  26. package/src/adapters/agui-middleware.ts +382 -382
  27. package/src/bin/mcp-ts.ts +102 -102
  28. package/src/client/core/app-host.ts +417 -417
  29. package/src/client/core/sse-client.ts +371 -371
  30. package/src/client/core/types.ts +31 -31
  31. package/src/client/index.ts +27 -27
  32. package/src/client/react/index.ts +16 -16
  33. package/src/client/react/use-app-host.ts +73 -73
  34. package/src/client/react/use-mcp-apps.tsx +247 -214
  35. package/src/client/react/use-mcp.ts +641 -641
  36. package/src/client/vue/index.ts +10 -10
  37. package/src/client/vue/use-mcp.ts +617 -617
  38. package/src/index.ts +11 -11
  39. package/src/server/handlers/nextjs-handler.ts +204 -204
  40. package/src/server/handlers/sse-handler.ts +631 -631
  41. package/src/server/index.ts +57 -57
  42. package/src/server/mcp/multi-session-client.ts +228 -228
  43. package/src/server/mcp/oauth-client.ts +1188 -1188
  44. package/src/server/mcp/storage-oauth-provider.ts +272 -272
  45. package/src/server/storage/file-backend.ts +157 -157
  46. package/src/server/storage/index.ts +176 -176
  47. package/src/server/storage/memory-backend.ts +123 -123
  48. package/src/server/storage/redis-backend.ts +276 -276
  49. package/src/server/storage/redis.ts +160 -160
  50. package/src/server/storage/sqlite-backend.ts +182 -182
  51. package/src/server/storage/supabase-backend.ts +228 -228
  52. package/src/server/storage/types.ts +116 -116
  53. package/src/shared/constants.ts +29 -29
  54. package/src/shared/errors.ts +133 -133
  55. package/src/shared/event-routing.ts +28 -28
  56. package/src/shared/events.ts +180 -180
  57. package/src/shared/index.ts +75 -75
  58. package/src/shared/tool-utils.ts +61 -61
  59. package/src/shared/types.ts +282 -282
  60. package/src/shared/utils.ts +38 -38
  61. package/supabase/migrations/20260330195700_install_mcp_sessions.sql +84 -84
@@ -1,42 +1,42 @@
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
-
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
40
  /**
41
41
  * Configuration for McpMiddleware
42
42
  */
@@ -44,10 +44,10 @@ export interface McpMiddlewareConfig {
44
44
  /** Pre-loaded tools with handlers (required) */
45
45
  tools: AguiTool[];
46
46
  }
47
-
48
- /**
49
- * AG-UI Middleware that executes MCP tools server-side.
50
- */
47
+
48
+ /**
49
+ * AG-UI Middleware that executes MCP tools server-side.
50
+ */
51
51
  export class McpMiddleware extends Middleware {
52
52
  private tools: AguiTool[];
53
53
  private toolSchemas: Tool[];
@@ -61,38 +61,38 @@ export class McpMiddleware extends Middleware {
61
61
  parameters: cleanSchema(t.parameters),
62
62
  }));
63
63
  }
64
-
65
- private isMcpTool(toolName: string): boolean {
66
- return this.tools.some(t => t.name === toolName);
67
- }
68
-
69
- private parseArgs(argsString: string): Record<string, any> {
70
- if (!argsString?.trim()) return {};
71
-
72
- try {
73
- return JSON.parse(argsString);
74
- } catch {
75
- // Handle duplicated JSON from streaming issues: {...}{...}
76
- const trimmed = argsString.trim();
77
- if (trimmed.includes('}{')) {
78
- const firstObject = trimmed.slice(0, trimmed.indexOf('}{') + 1);
79
- try {
80
- return JSON.parse(firstObject);
81
- } catch {
82
- console.error(`[McpMiddleware] Failed to parse JSON:`, firstObject);
83
- }
84
- }
85
- console.error(`[McpMiddleware] Failed to parse args:`, argsString);
86
- return {};
87
- }
88
- }
89
-
90
- private async executeTool(toolName: string, args: Record<string, any>): Promise<string> {
91
- const tool = this.tools.find(t => t.name === toolName);
92
- if (!tool?.handler) {
93
- return `Error: Tool ${tool ? 'has no handler' : 'not found'}: ${toolName}`;
94
- }
95
-
64
+
65
+ private isMcpTool(toolName: string): boolean {
66
+ return this.tools.some(t => t.name === toolName);
67
+ }
68
+
69
+ private parseArgs(argsString: string): Record<string, any> {
70
+ if (!argsString?.trim()) return {};
71
+
72
+ try {
73
+ return JSON.parse(argsString);
74
+ } catch {
75
+ // Handle duplicated JSON from streaming issues: {...}{...}
76
+ const trimmed = argsString.trim();
77
+ if (trimmed.includes('}{')) {
78
+ const firstObject = trimmed.slice(0, trimmed.indexOf('}{') + 1);
79
+ try {
80
+ return JSON.parse(firstObject);
81
+ } catch {
82
+ console.error(`[McpMiddleware] Failed to parse JSON:`, firstObject);
83
+ }
84
+ }
85
+ console.error(`[McpMiddleware] Failed to parse args:`, argsString);
86
+ return {};
87
+ }
88
+ }
89
+
90
+ private async executeTool(toolName: string, args: Record<string, any>): Promise<string> {
91
+ const tool = this.tools.find(t => t.name === toolName);
92
+ if (!tool?.handler) {
93
+ return `Error: Tool ${tool ? 'has no handler' : 'not found'}: ${toolName}`;
94
+ }
95
+
96
96
  try {
97
97
  console.log(`[McpMiddleware] Executing tool: ${toolName}`, args);
98
98
  const result = await tool.handler(args);
@@ -100,314 +100,314 @@ export class McpMiddleware extends Middleware {
100
100
 
101
101
  console.log(`[McpMiddleware] Tool result:`, resultStr.slice(0, 200));
102
102
  return resultStr;
103
- } catch (error: any) {
104
- console.error(`[McpMiddleware] Error executing tool:`, error);
105
- return `Error: ${error.message || String(error)}`;
106
- }
107
- }
108
-
109
- private generateId(prefix: string): string {
110
- return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
111
- }
112
-
113
- private ensureIds(input: RunAgentInput): void {
114
- const anyInput = input as any;
115
- if (!anyInput.threadId) anyInput.threadId = this.generateId('mcp_thread');
116
- if (!anyInput.runId) anyInput.runId = this.generateId('mcp_run');
117
- }
118
-
119
- /** Process tool call events and update state */
120
- private handleToolCallEvent(event: BaseEvent, state: RunState): void {
121
- const { toolCallArgsBuffer, toolCallNames, pendingMcpCalls } = state;
122
-
123
- // Accumulate text content for reconstruction
124
- if (event.type === EventType.TEXT_MESSAGE_CHUNK) {
125
- const e = event as any;
126
- if (e.delta) {
127
- state.textContent = (state.textContent || '') + e.delta;
128
- }
129
- }
130
-
131
- if (event.type === EventType.TOOL_CALL_START) {
132
- const e = event as any;
133
- if (e.toolCallId && e.toolCallName) {
134
- toolCallNames.set(e.toolCallId, e.toolCallName);
135
- if (this.isMcpTool(e.toolCallName)) {
136
- pendingMcpCalls.add(e.toolCallId);
137
- }
138
- console.log(`[McpMiddleware] TOOL_CALL_START: ${e.toolCallName} (id: ${e.toolCallId}, isMCP: ${this.isMcpTool(e.toolCallName)})`);
139
- }
140
- }
141
-
142
- if (event.type === EventType.TOOL_CALL_ARGS) {
143
- const e = event as any;
144
- if (e.toolCallId && e.delta) {
145
- const existing = toolCallArgsBuffer.get(e.toolCallId) || '';
146
- toolCallArgsBuffer.set(e.toolCallId, existing + e.delta);
147
- }
148
- }
149
-
150
- if (event.type === EventType.TOOL_CALL_END) {
151
- const e = event as ToolCallEndEvent;
152
- console.log(`[McpMiddleware] TOOL_CALL_END: ${toolCallNames.get(e.toolCallId) ?? 'unknown'} (id: ${e.toolCallId})`);
153
- }
154
-
155
- // Workaround: Extract parallel tool calls from MESSAGES_SNAPSHOT
156
- if (event.type === EventType.MESSAGES_SNAPSHOT) {
157
- const messages = (event as any).messages || [];
158
- if (messages.length > 0) {
159
- const lastMsg = messages[messages.length - 1];
160
- // Update text content from snapshot if available (often more reliable)
161
- if (lastMsg.role === 'assistant' && lastMsg.content) {
162
- state.textContent = lastMsg.content;
163
- }
164
-
165
- // Discover tools
166
- for (let i = messages.length - 1; i >= 0; i--) {
167
- const msg = messages[i];
168
- const tools = Array.isArray(msg.toolCalls) ? msg.toolCalls :
169
- (Array.isArray(msg.tool_calls) ? msg.tool_calls : []);
170
-
171
- if (msg.role === 'assistant' && tools.length > 0) {
172
- for (const tc of tools) {
173
- if (tc.id && tc.function?.name && !toolCallNames.has(tc.id)) {
174
- toolCallNames.set(tc.id, tc.function.name);
175
- toolCallArgsBuffer.set(tc.id, tc.function.arguments || '{}');
176
- if (this.isMcpTool(tc.function.name)) {
177
- pendingMcpCalls.add(tc.id);
178
- console.log(`[McpMiddleware] MESSAGES_SNAPSHOT: Discovered ${tc.function.name} (id: ${tc.id})`);
179
- }
180
- }
181
- }
182
- break;
183
- }
184
- }
185
- }
186
- }
187
- }
188
-
189
- /** Execute pending MCP tools and return results */
190
- private async executeTools(state: RunState): Promise<ToolResult[]> {
191
- const { toolCallArgsBuffer, toolCallNames, pendingMcpCalls } = state;
192
- const results: ToolResult[] = [];
193
-
194
- const promises = [...pendingMcpCalls].map(async (toolCallId) => {
195
- const toolName = toolCallNames.get(toolCallId);
196
- if (!toolName) return;
197
-
198
- const args = this.parseArgs(toolCallArgsBuffer.get(toolCallId) || '{}');
199
- console.log(`[McpMiddleware] Executing pending tool: ${toolName}`);
200
-
201
- const result = await this.executeTool(toolName, args);
202
- results.push({
203
- toolCallId,
204
- toolName,
205
- result,
206
- messageId: this.generateId('mcp_result'),
207
- });
208
- pendingMcpCalls.delete(toolCallId);
209
- });
210
-
211
- await Promise.all(promises);
212
- return results;
213
- }
214
-
215
- /** Emit tool results (without RUN_FINISHED - that's emitted when truly done) */
216
- private emitToolResults(observer: Subscriber<BaseEvent>, results: ToolResult[]): void {
217
- for (const { toolCallId, toolName, result, messageId } of results) {
218
- observer.next({
219
- type: EventType.TOOL_CALL_RESULT,
220
- toolCallId,
221
- messageId,
222
- content: result,
223
- role: 'tool',
224
- timestamp: Date.now(),
225
- } as any);
226
- console.log(`[McpMiddleware] Emitting TOOL_CALL_RESULT for: ${toolName}`);
227
- }
228
- }
229
-
230
- run(input: RunAgentInput, next: AbstractAgent): Observable<BaseEvent> {
231
- return new Observable<BaseEvent>((observer: Subscriber<BaseEvent>) => {
232
- const state: RunState = {
233
- toolCallArgsBuffer: new Map(),
234
- toolCallNames: new Map(),
235
- pendingMcpCalls: new Set(),
236
- textContent: '',
237
- error: false,
238
- };
239
-
240
- this.ensureIds(input);
241
- const anyInput = input as any;
242
-
243
- console.log(`[McpMiddleware] === NEW RUN ===`);
244
- console.log(`[McpMiddleware] threadId: ${anyInput.threadId}, runId: ${anyInput.runId}`);
245
- console.log(`[McpMiddleware] messages: ${input.messages?.length ?? 0}, tools: ${this.tools?.length ?? 0}`);
246
-
247
- // Inject MCP tools
248
- if (this.toolSchemas?.length) {
249
- input.tools = [...(input.tools || []), ...this.toolSchemas];
250
- console.log(`[McpMiddleware] Injected ${this.toolSchemas.length} tools:`, this.toolSchemas.map((t: Tool) => t.name));
251
- }
252
-
253
- const handleRunFinished = async () => {
254
- if (state.error) return; // Don't continue after error
255
-
256
- if (state.pendingMcpCalls.size === 0) {
257
- observer.next({
258
- type: EventType.RUN_FINISHED,
259
- threadId: anyInput.threadId,
260
- runId: anyInput.runId,
261
- timestamp: Date.now(),
262
- } as any);
263
- observer.complete();
264
- return;
265
- }
266
-
267
- console.log(`[McpMiddleware] RUN_FINISHED with ${state.pendingMcpCalls.size} pending calls`);
268
-
269
- // Reconstruct the Assistant Message that triggered these tools
270
- const toolCalls = [];
271
- for (const toolCallId of state.pendingMcpCalls) {
272
- const name = state.toolCallNames.get(toolCallId);
273
- const args = state.toolCallArgsBuffer.get(toolCallId) || '{}';
274
- if (name) {
275
- toolCalls.push({
276
- id: toolCallId,
277
- type: 'function',
278
- function: { name, arguments: args }
279
- });
280
- }
281
- }
282
-
283
- // Add the Assistant Message to history FIRST
284
- if (toolCalls.length > 0 || state.textContent) {
285
- const assistantMsg = {
286
- id: this.generateId('msg_ast'),
287
- role: 'assistant',
288
- content: state.textContent || null, // Ensure null if empty string for strict LLMs
289
- tool_calls: toolCalls.length > 0 ? toolCalls : undefined
290
- };
291
- input.messages.push(assistantMsg as any);
292
- console.log(`[McpMiddleware] Added assistant message to history before tools: ${state.textContent?.slice(0, 50)}... [${toolCalls.length} tools]`);
293
- }
294
-
295
- // Execute tools and emit results (no RUN_FINISHED yet - continuation follows)
296
- const results = await this.executeTools(state);
297
- this.emitToolResults(observer, results);
298
-
299
- // Prepare continuation
300
- console.log(`[McpMiddleware] Triggering continuation with ${results.length} results`);
301
-
302
- // Add tool result messages to history
303
- for (const { toolCallId, result, messageId } of results) {
304
- input.messages.push({
305
- id: messageId,
306
- role: 'tool',
307
- tool_call_id: toolCallId,
308
- content: result,
309
- } as any);
310
- }
311
-
312
- // Reset state for next turn
313
- state.toolCallArgsBuffer.clear();
314
- state.toolCallNames.clear();
315
- state.textContent = ''; // Clear text content for next turn
316
-
317
- anyInput.runId = this.generateId('mcp_run');
318
- console.log(`[McpMiddleware] === CONTINUATION RUN === messages: ${input.messages.length}`);
319
-
320
- // Subscribe to continuation
321
- next.run(input).subscribe({
322
- next: (event) => {
323
- if (state.error) return;
324
-
325
- this.handleToolCallEvent(event, state);
326
-
327
- if (event.type === EventType.RUN_ERROR) {
328
- console.log(`[McpMiddleware] RUN_ERROR received in continuation`);
329
- state.error = true;
330
- observer.next(event);
331
- observer.complete();
332
- return;
333
- }
334
-
335
- if (event.type === EventType.RUN_STARTED) {
336
- console.log(`[McpMiddleware] Filtering RUN_STARTED from continuation`);
337
- return;
338
- }
339
-
340
- if (event.type === EventType.RUN_FINISHED) {
341
- if (state.pendingMcpCalls.size > 0) {
342
- handleRunFinished();
343
- } else {
344
- observer.next(event);
345
- observer.complete();
346
- }
347
- return;
348
- }
349
- observer.next(event);
350
- },
351
- error: (err) => {
352
- state.error = true;
353
- observer.error(err);
354
- },
355
- complete: () => {
356
- if (!state.error && state.pendingMcpCalls.size === 0) observer.complete();
357
- },
358
- });
359
- };
360
-
361
- const subscription = next.run(input).subscribe({
362
- next: (event) => {
363
- if (state.error) return;
364
-
365
- this.handleToolCallEvent(event, state);
366
-
367
- if (event.type === EventType.RUN_ERROR) {
368
- console.log(`[McpMiddleware] RUN_ERROR received`);
369
- state.error = true;
370
- observer.next(event);
371
- observer.complete();
372
- return;
373
- }
374
-
375
- if (event.type === EventType.RUN_FINISHED) {
376
- handleRunFinished();
377
- return;
378
- }
379
- observer.next(event);
380
- },
381
- error: (err) => {
382
- state.error = true;
383
- observer.error(err);
384
- },
385
- complete: () => {
386
- if (!state.error && state.pendingMcpCalls.size === 0) observer.complete();
387
- },
388
- });
389
-
390
- return () => subscription.unsubscribe();
391
- });
392
- }
393
- }
394
-
395
- /**
396
- * Factory function to create MCP middleware.
397
- */
103
+ } catch (error: any) {
104
+ console.error(`[McpMiddleware] Error executing tool:`, error);
105
+ return `Error: ${error.message || String(error)}`;
106
+ }
107
+ }
108
+
109
+ private generateId(prefix: string): string {
110
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
111
+ }
112
+
113
+ private ensureIds(input: RunAgentInput): void {
114
+ const anyInput = input as any;
115
+ if (!anyInput.threadId) anyInput.threadId = this.generateId('mcp_thread');
116
+ if (!anyInput.runId) anyInput.runId = this.generateId('mcp_run');
117
+ }
118
+
119
+ /** Process tool call events and update state */
120
+ private handleToolCallEvent(event: BaseEvent, state: RunState): void {
121
+ const { toolCallArgsBuffer, toolCallNames, pendingMcpCalls } = state;
122
+
123
+ // Accumulate text content for reconstruction
124
+ if (event.type === EventType.TEXT_MESSAGE_CHUNK) {
125
+ const e = event as any;
126
+ if (e.delta) {
127
+ state.textContent = (state.textContent || '') + e.delta;
128
+ }
129
+ }
130
+
131
+ if (event.type === EventType.TOOL_CALL_START) {
132
+ const e = event as any;
133
+ if (e.toolCallId && e.toolCallName) {
134
+ toolCallNames.set(e.toolCallId, e.toolCallName);
135
+ if (this.isMcpTool(e.toolCallName)) {
136
+ pendingMcpCalls.add(e.toolCallId);
137
+ }
138
+ console.log(`[McpMiddleware] TOOL_CALL_START: ${e.toolCallName} (id: ${e.toolCallId}, isMCP: ${this.isMcpTool(e.toolCallName)})`);
139
+ }
140
+ }
141
+
142
+ if (event.type === EventType.TOOL_CALL_ARGS) {
143
+ const e = event as any;
144
+ if (e.toolCallId && e.delta) {
145
+ const existing = toolCallArgsBuffer.get(e.toolCallId) || '';
146
+ toolCallArgsBuffer.set(e.toolCallId, existing + e.delta);
147
+ }
148
+ }
149
+
150
+ if (event.type === EventType.TOOL_CALL_END) {
151
+ const e = event as ToolCallEndEvent;
152
+ console.log(`[McpMiddleware] TOOL_CALL_END: ${toolCallNames.get(e.toolCallId) ?? 'unknown'} (id: ${e.toolCallId})`);
153
+ }
154
+
155
+ // Workaround: Extract parallel tool calls from MESSAGES_SNAPSHOT
156
+ if (event.type === EventType.MESSAGES_SNAPSHOT) {
157
+ const messages = (event as any).messages || [];
158
+ if (messages.length > 0) {
159
+ const lastMsg = messages[messages.length - 1];
160
+ // Update text content from snapshot if available (often more reliable)
161
+ if (lastMsg.role === 'assistant' && lastMsg.content) {
162
+ state.textContent = lastMsg.content;
163
+ }
164
+
165
+ // Discover tools
166
+ for (let i = messages.length - 1; i >= 0; i--) {
167
+ const msg = messages[i];
168
+ const tools = Array.isArray(msg.toolCalls) ? msg.toolCalls :
169
+ (Array.isArray(msg.tool_calls) ? msg.tool_calls : []);
170
+
171
+ if (msg.role === 'assistant' && tools.length > 0) {
172
+ for (const tc of tools) {
173
+ if (tc.id && tc.function?.name && !toolCallNames.has(tc.id)) {
174
+ toolCallNames.set(tc.id, tc.function.name);
175
+ toolCallArgsBuffer.set(tc.id, tc.function.arguments || '{}');
176
+ if (this.isMcpTool(tc.function.name)) {
177
+ pendingMcpCalls.add(tc.id);
178
+ console.log(`[McpMiddleware] MESSAGES_SNAPSHOT: Discovered ${tc.function.name} (id: ${tc.id})`);
179
+ }
180
+ }
181
+ }
182
+ break;
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ /** Execute pending MCP tools and return results */
190
+ private async executeTools(state: RunState): Promise<ToolResult[]> {
191
+ const { toolCallArgsBuffer, toolCallNames, pendingMcpCalls } = state;
192
+ const results: ToolResult[] = [];
193
+
194
+ const promises = [...pendingMcpCalls].map(async (toolCallId) => {
195
+ const toolName = toolCallNames.get(toolCallId);
196
+ if (!toolName) return;
197
+
198
+ const args = this.parseArgs(toolCallArgsBuffer.get(toolCallId) || '{}');
199
+ console.log(`[McpMiddleware] Executing pending tool: ${toolName}`);
200
+
201
+ const result = await this.executeTool(toolName, args);
202
+ results.push({
203
+ toolCallId,
204
+ toolName,
205
+ result,
206
+ messageId: this.generateId('mcp_result'),
207
+ });
208
+ pendingMcpCalls.delete(toolCallId);
209
+ });
210
+
211
+ await Promise.all(promises);
212
+ return results;
213
+ }
214
+
215
+ /** Emit tool results (without RUN_FINISHED - that's emitted when truly done) */
216
+ private emitToolResults(observer: Subscriber<BaseEvent>, results: ToolResult[]): void {
217
+ for (const { toolCallId, toolName, result, messageId } of results) {
218
+ observer.next({
219
+ type: EventType.TOOL_CALL_RESULT,
220
+ toolCallId,
221
+ messageId,
222
+ content: result,
223
+ role: 'tool',
224
+ timestamp: Date.now(),
225
+ } as any);
226
+ console.log(`[McpMiddleware] Emitting TOOL_CALL_RESULT for: ${toolName}`);
227
+ }
228
+ }
229
+
230
+ run(input: RunAgentInput, next: AbstractAgent): Observable<BaseEvent> {
231
+ return new Observable<BaseEvent>((observer: Subscriber<BaseEvent>) => {
232
+ const state: RunState = {
233
+ toolCallArgsBuffer: new Map(),
234
+ toolCallNames: new Map(),
235
+ pendingMcpCalls: new Set(),
236
+ textContent: '',
237
+ error: false,
238
+ };
239
+
240
+ this.ensureIds(input);
241
+ const anyInput = input as any;
242
+
243
+ console.log(`[McpMiddleware] === NEW RUN ===`);
244
+ console.log(`[McpMiddleware] threadId: ${anyInput.threadId}, runId: ${anyInput.runId}`);
245
+ console.log(`[McpMiddleware] messages: ${input.messages?.length ?? 0}, tools: ${this.tools?.length ?? 0}`);
246
+
247
+ // Inject MCP tools
248
+ if (this.toolSchemas?.length) {
249
+ input.tools = [...(input.tools || []), ...this.toolSchemas];
250
+ console.log(`[McpMiddleware] Injected ${this.toolSchemas.length} tools:`, this.toolSchemas.map((t: Tool) => t.name));
251
+ }
252
+
253
+ const handleRunFinished = async () => {
254
+ if (state.error) return; // Don't continue after error
255
+
256
+ if (state.pendingMcpCalls.size === 0) {
257
+ observer.next({
258
+ type: EventType.RUN_FINISHED,
259
+ threadId: anyInput.threadId,
260
+ runId: anyInput.runId,
261
+ timestamp: Date.now(),
262
+ } as any);
263
+ observer.complete();
264
+ return;
265
+ }
266
+
267
+ console.log(`[McpMiddleware] RUN_FINISHED with ${state.pendingMcpCalls.size} pending calls`);
268
+
269
+ // Reconstruct the Assistant Message that triggered these tools
270
+ const toolCalls = [];
271
+ for (const toolCallId of state.pendingMcpCalls) {
272
+ const name = state.toolCallNames.get(toolCallId);
273
+ const args = state.toolCallArgsBuffer.get(toolCallId) || '{}';
274
+ if (name) {
275
+ toolCalls.push({
276
+ id: toolCallId,
277
+ type: 'function',
278
+ function: { name, arguments: args }
279
+ });
280
+ }
281
+ }
282
+
283
+ // Add the Assistant Message to history FIRST
284
+ if (toolCalls.length > 0 || state.textContent) {
285
+ const assistantMsg = {
286
+ id: this.generateId('msg_ast'),
287
+ role: 'assistant',
288
+ content: state.textContent || null, // Ensure null if empty string for strict LLMs
289
+ tool_calls: toolCalls.length > 0 ? toolCalls : undefined
290
+ };
291
+ input.messages.push(assistantMsg as any);
292
+ console.log(`[McpMiddleware] Added assistant message to history before tools: ${state.textContent?.slice(0, 50)}... [${toolCalls.length} tools]`);
293
+ }
294
+
295
+ // Execute tools and emit results (no RUN_FINISHED yet - continuation follows)
296
+ const results = await this.executeTools(state);
297
+ this.emitToolResults(observer, results);
298
+
299
+ // Prepare continuation
300
+ console.log(`[McpMiddleware] Triggering continuation with ${results.length} results`);
301
+
302
+ // Add tool result messages to history
303
+ for (const { toolCallId, result, messageId } of results) {
304
+ input.messages.push({
305
+ id: messageId,
306
+ role: 'tool',
307
+ tool_call_id: toolCallId,
308
+ content: result,
309
+ } as any);
310
+ }
311
+
312
+ // Reset state for next turn
313
+ state.toolCallArgsBuffer.clear();
314
+ state.toolCallNames.clear();
315
+ state.textContent = ''; // Clear text content for next turn
316
+
317
+ anyInput.runId = this.generateId('mcp_run');
318
+ console.log(`[McpMiddleware] === CONTINUATION RUN === messages: ${input.messages.length}`);
319
+
320
+ // Subscribe to continuation
321
+ next.run(input).subscribe({
322
+ next: (event) => {
323
+ if (state.error) return;
324
+
325
+ this.handleToolCallEvent(event, state);
326
+
327
+ if (event.type === EventType.RUN_ERROR) {
328
+ console.log(`[McpMiddleware] RUN_ERROR received in continuation`);
329
+ state.error = true;
330
+ observer.next(event);
331
+ observer.complete();
332
+ return;
333
+ }
334
+
335
+ if (event.type === EventType.RUN_STARTED) {
336
+ console.log(`[McpMiddleware] Filtering RUN_STARTED from continuation`);
337
+ return;
338
+ }
339
+
340
+ if (event.type === EventType.RUN_FINISHED) {
341
+ if (state.pendingMcpCalls.size > 0) {
342
+ handleRunFinished();
343
+ } else {
344
+ observer.next(event);
345
+ observer.complete();
346
+ }
347
+ return;
348
+ }
349
+ observer.next(event);
350
+ },
351
+ error: (err) => {
352
+ state.error = true;
353
+ observer.error(err);
354
+ },
355
+ complete: () => {
356
+ if (!state.error && state.pendingMcpCalls.size === 0) observer.complete();
357
+ },
358
+ });
359
+ };
360
+
361
+ const subscription = next.run(input).subscribe({
362
+ next: (event) => {
363
+ if (state.error) return;
364
+
365
+ this.handleToolCallEvent(event, state);
366
+
367
+ if (event.type === EventType.RUN_ERROR) {
368
+ console.log(`[McpMiddleware] RUN_ERROR received`);
369
+ state.error = true;
370
+ observer.next(event);
371
+ observer.complete();
372
+ return;
373
+ }
374
+
375
+ if (event.type === EventType.RUN_FINISHED) {
376
+ handleRunFinished();
377
+ return;
378
+ }
379
+ observer.next(event);
380
+ },
381
+ error: (err) => {
382
+ state.error = true;
383
+ observer.error(err);
384
+ },
385
+ complete: () => {
386
+ if (!state.error && state.pendingMcpCalls.size === 0) observer.complete();
387
+ },
388
+ });
389
+
390
+ return () => subscription.unsubscribe();
391
+ });
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Factory function to create MCP middleware.
397
+ */
398
398
  export function createMcpMiddleware(
399
399
  options: { tools: AguiTool[] }
400
400
  ) {
401
- const middleware = new McpMiddleware(options);
402
- return (input: RunAgentInput, next: AbstractAgent): Observable<BaseEvent> => {
403
- return middleware.run(input, next);
404
- };
405
- }
406
-
407
- // Legacy exports
408
- export { McpMiddleware as McpToolExecutorMiddleware };
409
- export { createMcpMiddleware as createMcpToolMiddleware };
410
-
411
- // Re-exports
412
- export { Middleware, EventType };
401
+ const middleware = new McpMiddleware(options);
402
+ return (input: RunAgentInput, next: AbstractAgent): Observable<BaseEvent> => {
403
+ return middleware.run(input, next);
404
+ };
405
+ }
406
+
407
+ // Legacy exports
408
+ export { McpMiddleware as McpToolExecutorMiddleware };
409
+ export { createMcpMiddleware as createMcpToolMiddleware };
410
+
411
+ // Re-exports
412
+ export { Middleware, EventType };
413
413
  export type { RunAgentInput, BaseEvent, AbstractAgent, ToolCallEndEvent, Tool };