@mcp-ts/sdk 1.5.1 → 1.5.3
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 +89 -27
- package/dist/adapters/agui-adapter.d.mts +1 -1
- package/dist/adapters/agui-adapter.d.ts +1 -1
- package/dist/adapters/agui-adapter.js +50 -10
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs +50 -10
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- package/dist/adapters/agui-middleware.d.mts +5 -1
- package/dist/adapters/agui-middleware.d.ts +5 -1
- package/dist/adapters/agui-middleware.js +116 -49
- package/dist/adapters/agui-middleware.js.map +1 -1
- package/dist/adapters/agui-middleware.mjs +117 -50
- package/dist/adapters/agui-middleware.mjs.map +1 -1
- package/dist/adapters/ai-adapter.d.mts +1 -1
- package/dist/adapters/ai-adapter.d.ts +1 -1
- package/dist/adapters/ai-adapter.js +49 -9
- package/dist/adapters/ai-adapter.js.map +1 -1
- package/dist/adapters/ai-adapter.mjs +49 -9
- package/dist/adapters/ai-adapter.mjs.map +1 -1
- package/dist/adapters/langchain-adapter.d.mts +1 -1
- package/dist/adapters/langchain-adapter.d.ts +1 -1
- package/dist/adapters/langchain-adapter.js +49 -9
- package/dist/adapters/langchain-adapter.js.map +1 -1
- package/dist/adapters/langchain-adapter.mjs +49 -9
- package/dist/adapters/langchain-adapter.mjs.map +1 -1
- package/dist/client/react.d.mts +10 -0
- package/dist/client/react.d.ts +10 -0
- package/dist/client/react.js +23 -0
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +23 -0
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +10 -0
- package/dist/client/vue.d.ts +10 -0
- package/dist/client/vue.js +17 -0
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +17 -0
- package/dist/client/vue.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +123 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +123 -28
- package/dist/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +2 -2
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.js +123 -28
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +123 -28
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-XnWVxPzv.d.mts → tool-router-DK0RJblO.d.mts} +3 -0
- package/dist/{tool-router-Bo8qZbsD.d.ts → tool-router-DsKhRmJm.d.ts} +3 -0
- package/package.json +3 -3
- package/src/adapters/agui-adapter.ts +7 -7
- package/src/adapters/agui-middleware.ts +163 -59
- package/src/adapters/ai-adapter.ts +5 -5
- package/src/adapters/langchain-adapter.ts +5 -5
- package/src/client/react/use-mcp.ts +48 -0
- package/src/client/vue/use-mcp.ts +42 -0
- package/src/shared/meta-tools.ts +73 -15
- package/src/shared/tool-index.ts +85 -12
- package/src/shared/tool-router.ts +8 -7
|
@@ -246,12 +246,12 @@ export class AguiAdapter {
|
|
|
246
246
|
const filteredTools = await router.getFilteredTools();
|
|
247
247
|
|
|
248
248
|
return filteredTools.map(tool => {
|
|
249
|
-
const routedTool = tool as typeof tool & { sessionId?: string; serverName?: string };
|
|
250
|
-
const namespace = routedTool.
|
|
249
|
+
const routedTool = tool as typeof tool & { sessionId?: string; serverId?: string; serverName?: string };
|
|
250
|
+
const namespace = routedTool.serverId ?? routedTool.sessionId;
|
|
251
251
|
return {
|
|
252
252
|
name: isMetaTool(tool.name)
|
|
253
253
|
? tool.name
|
|
254
|
-
: this.getRouterToolKey(tool.name, routedTool.sessionId, routedTool.
|
|
254
|
+
: this.getRouterToolKey(tool.name, routedTool.sessionId, routedTool.serverId),
|
|
255
255
|
description: tool.description || `Execute ${tool.name}`,
|
|
256
256
|
parameters: cleanSchema(tool.inputSchema),
|
|
257
257
|
handler: async (args: any) => {
|
|
@@ -279,19 +279,19 @@ export class AguiAdapter {
|
|
|
279
279
|
private async getToolDefinitionsViaRouter(router: ToolRouter): Promise<AguiToolDefinition[]> {
|
|
280
280
|
const filteredTools = await router.getFilteredTools();
|
|
281
281
|
return filteredTools.map(tool => {
|
|
282
|
-
const routedTool = tool as typeof tool & { sessionId?: string; serverName?: string };
|
|
282
|
+
const routedTool = tool as typeof tool & { sessionId?: string; serverId?: string; serverName?: string };
|
|
283
283
|
return {
|
|
284
284
|
name: isMetaTool(tool.name)
|
|
285
285
|
? tool.name
|
|
286
|
-
: this.getRouterToolKey(tool.name, routedTool.sessionId, routedTool.
|
|
286
|
+
: this.getRouterToolKey(tool.name, routedTool.sessionId, routedTool.serverId),
|
|
287
287
|
description: tool.description || `Execute ${tool.name}`,
|
|
288
288
|
parameters: cleanSchema(tool.inputSchema)
|
|
289
289
|
};
|
|
290
290
|
});
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
private getRouterToolKey(toolName: string, sessionId?: string,
|
|
294
|
-
const namespace = sessionId ??
|
|
293
|
+
private getRouterToolKey(toolName: string, sessionId?: string, serverId?: string): string {
|
|
294
|
+
const namespace = sessionId ?? serverId ?? 'mcp';
|
|
295
295
|
const normalized = namespace
|
|
296
296
|
.toLowerCase()
|
|
297
297
|
.replace(/[^a-z0-9]+/g, '_')
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @requires rxjs - Uses RxJS Observables for event streaming
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { Observable, Subscriber } from 'rxjs';
|
|
11
|
+
import { Observable, Subscriber, Subscription } from 'rxjs';
|
|
12
12
|
import {
|
|
13
13
|
Middleware,
|
|
14
14
|
EventType,
|
|
@@ -28,11 +28,19 @@ interface ToolResult {
|
|
|
28
28
|
messageId: string;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
interface SnapshotToolCall {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
arguments: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
/** State for tracking tool calls during a run */
|
|
32
38
|
interface RunState {
|
|
33
39
|
toolCallArgsBuffer: Map<string, string>;
|
|
34
40
|
toolCallNames: Map<string, string>;
|
|
35
41
|
pendingMcpCalls: Set<string>;
|
|
42
|
+
completedMcpCalls: Set<string>;
|
|
43
|
+
assistantMessageId?: string;
|
|
36
44
|
textContent?: string;
|
|
37
45
|
error: boolean;
|
|
38
46
|
}
|
|
@@ -87,6 +95,10 @@ export class McpMiddleware extends Middleware {
|
|
|
87
95
|
}
|
|
88
96
|
}
|
|
89
97
|
|
|
98
|
+
private normalizeArgsString(argsString: string): string {
|
|
99
|
+
return JSON.stringify(this.parseArgs(argsString));
|
|
100
|
+
}
|
|
101
|
+
|
|
90
102
|
private async executeTool(toolName: string, args: Record<string, any>): Promise<string> {
|
|
91
103
|
const tool = this.tools.find(t => t.name === toolName);
|
|
92
104
|
if (!tool?.handler) {
|
|
@@ -116,13 +128,82 @@ export class McpMiddleware extends Middleware {
|
|
|
116
128
|
if (!anyInput.runId) anyInput.runId = this.generateId('mcp_run');
|
|
117
129
|
}
|
|
118
130
|
|
|
131
|
+
private getSnapshotToolCalls(message: any): SnapshotToolCall[] {
|
|
132
|
+
const rawToolCalls = Array.isArray(message?.toolCalls)
|
|
133
|
+
? message.toolCalls
|
|
134
|
+
: (Array.isArray(message?.tool_calls) ? message.tool_calls : []);
|
|
135
|
+
|
|
136
|
+
return rawToolCalls.flatMap((toolCall: any) => {
|
|
137
|
+
if (!toolCall || typeof toolCall !== 'object') return [];
|
|
138
|
+
|
|
139
|
+
const id = typeof toolCall.id === 'string' ? toolCall.id : undefined;
|
|
140
|
+
const fn = toolCall.function && typeof toolCall.function === 'object'
|
|
141
|
+
? toolCall.function
|
|
142
|
+
: undefined;
|
|
143
|
+
const name = typeof fn?.name === 'string'
|
|
144
|
+
? fn.name
|
|
145
|
+
: (typeof toolCall.name === 'string' ? toolCall.name : undefined);
|
|
146
|
+
|
|
147
|
+
if (!id || !name) return [];
|
|
148
|
+
|
|
149
|
+
const rawArgs = fn?.arguments ?? toolCall.arguments ?? toolCall.args;
|
|
150
|
+
const argsString = typeof rawArgs === 'string'
|
|
151
|
+
? rawArgs
|
|
152
|
+
: (rawArgs && typeof rawArgs === 'object' ? JSON.stringify(rawArgs) : '{}');
|
|
153
|
+
|
|
154
|
+
return [{ id, name, arguments: argsString }];
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private getResolvedToolCallIds(messages: any[]): Set<string> {
|
|
159
|
+
const resolved = new Set<string>();
|
|
160
|
+
for (const message of messages) {
|
|
161
|
+
if (message?.role !== 'tool') continue;
|
|
162
|
+
const toolCallId = message.toolCallId ?? message.tool_call_id;
|
|
163
|
+
if (typeof toolCallId === 'string') {
|
|
164
|
+
resolved.add(toolCallId);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return resolved;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private shouldFilterMessagesSnapshot(event: BaseEvent, state: RunState): boolean {
|
|
171
|
+
if (state.completedMcpCalls.size === 0 || state.pendingMcpCalls.size > 0) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Only suppress snapshots that replay an assistant answer we already streamed.
|
|
176
|
+
// Snapshot-only final answers still need to reach the UI.
|
|
177
|
+
if (!state.assistantMessageId && !state.textContent) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const messages = (event as any).messages;
|
|
182
|
+
if (!Array.isArray(messages)) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const lastAssistant = [...messages].reverse().find((message) => message?.role === 'assistant');
|
|
187
|
+
if (!lastAssistant) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
(typeof lastAssistant.id === 'string' && lastAssistant.id === state.assistantMessageId) ||
|
|
193
|
+
(typeof lastAssistant.content === 'string' && lastAssistant.content === state.textContent)
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
119
197
|
/** Process tool call events and update state */
|
|
120
198
|
private handleToolCallEvent(event: BaseEvent, state: RunState): void {
|
|
121
|
-
const { toolCallArgsBuffer, toolCallNames, pendingMcpCalls } = state;
|
|
199
|
+
const { toolCallArgsBuffer, toolCallNames, pendingMcpCalls, completedMcpCalls } = state;
|
|
122
200
|
|
|
123
201
|
// Accumulate text content for reconstruction
|
|
124
|
-
if (event.type === EventType.TEXT_MESSAGE_CHUNK) {
|
|
202
|
+
if (event.type === EventType.TEXT_MESSAGE_CHUNK || event.type === EventType.TEXT_MESSAGE_CONTENT) {
|
|
125
203
|
const e = event as any;
|
|
204
|
+
if (typeof e.messageId === 'string') {
|
|
205
|
+
state.assistantMessageId = e.messageId;
|
|
206
|
+
}
|
|
126
207
|
if (e.delta) {
|
|
127
208
|
state.textContent = (state.textContent || '') + e.delta;
|
|
128
209
|
}
|
|
@@ -132,7 +213,7 @@ export class McpMiddleware extends Middleware {
|
|
|
132
213
|
const e = event as any;
|
|
133
214
|
if (e.toolCallId && e.toolCallName) {
|
|
134
215
|
toolCallNames.set(e.toolCallId, e.toolCallName);
|
|
135
|
-
if (this.isMcpTool(e.toolCallName)) {
|
|
216
|
+
if (this.isMcpTool(e.toolCallName) && !completedMcpCalls.has(e.toolCallId)) {
|
|
136
217
|
pendingMcpCalls.add(e.toolCallId);
|
|
137
218
|
}
|
|
138
219
|
console.log(`[McpMiddleware] TOOL_CALL_START: ${e.toolCallName} (id: ${e.toolCallId}, isMCP: ${this.isMcpTool(e.toolCallName)})`);
|
|
@@ -152,33 +233,52 @@ export class McpMiddleware extends Middleware {
|
|
|
152
233
|
console.log(`[McpMiddleware] TOOL_CALL_END: ${toolCallNames.get(e.toolCallId) ?? 'unknown'} (id: ${e.toolCallId})`);
|
|
153
234
|
}
|
|
154
235
|
|
|
236
|
+
if (event.type === EventType.TOOL_CALL_RESULT) {
|
|
237
|
+
const e = event as any;
|
|
238
|
+
if (typeof e.toolCallId === 'string') {
|
|
239
|
+
pendingMcpCalls.delete(e.toolCallId);
|
|
240
|
+
completedMcpCalls.add(e.toolCallId);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
155
244
|
// Workaround: Extract parallel tool calls from MESSAGES_SNAPSHOT
|
|
156
245
|
if (event.type === EventType.MESSAGES_SNAPSHOT) {
|
|
157
246
|
const messages = (event as any).messages || [];
|
|
158
247
|
if (messages.length > 0) {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
248
|
+
const resolvedToolCallIds = this.getResolvedToolCallIds(messages);
|
|
249
|
+
|
|
250
|
+
for (const toolCallId of [...pendingMcpCalls]) {
|
|
251
|
+
if (resolvedToolCallIds.has(toolCallId) || completedMcpCalls.has(toolCallId)) {
|
|
252
|
+
pendingMcpCalls.delete(toolCallId);
|
|
253
|
+
}
|
|
163
254
|
}
|
|
164
255
|
|
|
165
|
-
// Discover tools
|
|
256
|
+
// Discover unresolved tools. LangGraph snapshots include historical
|
|
257
|
+
// assistant tool calls, so only calls without a tool result are pending.
|
|
166
258
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
167
259
|
const msg = messages[i];
|
|
168
|
-
const tools =
|
|
169
|
-
|
|
260
|
+
const tools = this.getSnapshotToolCalls(msg)
|
|
261
|
+
.filter(tc =>
|
|
262
|
+
!resolvedToolCallIds.has(tc.id) &&
|
|
263
|
+
!completedMcpCalls.has(tc.id) &&
|
|
264
|
+
this.isMcpTool(tc.name)
|
|
265
|
+
);
|
|
170
266
|
|
|
171
267
|
if (msg.role === 'assistant' && tools.length > 0) {
|
|
172
268
|
for (const tc of tools) {
|
|
173
|
-
if (
|
|
174
|
-
toolCallNames.set(tc.id, tc.
|
|
175
|
-
toolCallArgsBuffer.set(tc.id, tc.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
console.log(`[McpMiddleware] MESSAGES_SNAPSHOT: Discovered ${tc.function.name} (id: ${tc.id})`);
|
|
179
|
-
}
|
|
269
|
+
if (!toolCallNames.has(tc.id)) {
|
|
270
|
+
toolCallNames.set(tc.id, tc.name);
|
|
271
|
+
toolCallArgsBuffer.set(tc.id, tc.arguments || '{}');
|
|
272
|
+
pendingMcpCalls.add(tc.id);
|
|
273
|
+
console.log(`[McpMiddleware] MESSAGES_SNAPSHOT: Discovered ${tc.name} (id: ${tc.id})`);
|
|
180
274
|
}
|
|
181
275
|
}
|
|
276
|
+
if (typeof msg.id === 'string') {
|
|
277
|
+
state.assistantMessageId = msg.id;
|
|
278
|
+
}
|
|
279
|
+
if (typeof msg.content === 'string') {
|
|
280
|
+
state.textContent = msg.content;
|
|
281
|
+
}
|
|
182
282
|
break;
|
|
183
283
|
}
|
|
184
284
|
}
|
|
@@ -188,10 +288,15 @@ export class McpMiddleware extends Middleware {
|
|
|
188
288
|
|
|
189
289
|
/** Execute pending MCP tools and return results */
|
|
190
290
|
private async executeTools(state: RunState): Promise<ToolResult[]> {
|
|
191
|
-
const { toolCallArgsBuffer, toolCallNames, pendingMcpCalls } = state;
|
|
291
|
+
const { toolCallArgsBuffer, toolCallNames, pendingMcpCalls, completedMcpCalls } = state;
|
|
192
292
|
const results: ToolResult[] = [];
|
|
193
293
|
|
|
194
294
|
const promises = [...pendingMcpCalls].map(async (toolCallId) => {
|
|
295
|
+
if (completedMcpCalls.has(toolCallId)) {
|
|
296
|
+
pendingMcpCalls.delete(toolCallId);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
195
300
|
const toolName = toolCallNames.get(toolCallId);
|
|
196
301
|
if (!toolName) return;
|
|
197
302
|
|
|
@@ -206,6 +311,7 @@ export class McpMiddleware extends Middleware {
|
|
|
206
311
|
messageId: this.generateId('mcp_result'),
|
|
207
312
|
});
|
|
208
313
|
pendingMcpCalls.delete(toolCallId);
|
|
314
|
+
completedMcpCalls.add(toolCallId);
|
|
209
315
|
});
|
|
210
316
|
|
|
211
317
|
await Promise.all(promises);
|
|
@@ -229,10 +335,13 @@ export class McpMiddleware extends Middleware {
|
|
|
229
335
|
|
|
230
336
|
run(input: RunAgentInput, next: AbstractAgent): Observable<BaseEvent> {
|
|
231
337
|
return new Observable<BaseEvent>((observer: Subscriber<BaseEvent>) => {
|
|
338
|
+
const subscriptions = new Set<Subscription>();
|
|
339
|
+
let continuationInProgress = false;
|
|
232
340
|
const state: RunState = {
|
|
233
341
|
toolCallArgsBuffer: new Map(),
|
|
234
342
|
toolCallNames: new Map(),
|
|
235
343
|
pendingMcpCalls: new Set(),
|
|
344
|
+
completedMcpCalls: new Set(),
|
|
236
345
|
textContent: '',
|
|
237
346
|
error: false,
|
|
238
347
|
};
|
|
@@ -265,12 +374,13 @@ export class McpMiddleware extends Middleware {
|
|
|
265
374
|
}
|
|
266
375
|
|
|
267
376
|
console.log(`[McpMiddleware] RUN_FINISHED with ${state.pendingMcpCalls.size} pending calls`);
|
|
377
|
+
continuationInProgress = true;
|
|
268
378
|
|
|
269
379
|
// Reconstruct the Assistant Message that triggered these tools
|
|
270
380
|
const toolCalls = [];
|
|
271
381
|
for (const toolCallId of state.pendingMcpCalls) {
|
|
272
382
|
const name = state.toolCallNames.get(toolCallId);
|
|
273
|
-
const args = state.toolCallArgsBuffer.get(toolCallId) || '{}';
|
|
383
|
+
const args = this.normalizeArgsString(state.toolCallArgsBuffer.get(toolCallId) || '{}');
|
|
274
384
|
if (name) {
|
|
275
385
|
toolCalls.push({
|
|
276
386
|
id: toolCallId,
|
|
@@ -283,10 +393,10 @@ export class McpMiddleware extends Middleware {
|
|
|
283
393
|
// Add the Assistant Message to history FIRST
|
|
284
394
|
if (toolCalls.length > 0 || state.textContent) {
|
|
285
395
|
const assistantMsg = {
|
|
286
|
-
id: this.generateId('msg_ast'),
|
|
396
|
+
id: state.assistantMessageId || this.generateId('msg_ast'),
|
|
287
397
|
role: 'assistant',
|
|
288
398
|
content: state.textContent || null, // Ensure null if empty string for strict LLMs
|
|
289
|
-
|
|
399
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
290
400
|
};
|
|
291
401
|
input.messages.push(assistantMsg as any);
|
|
292
402
|
console.log(`[McpMiddleware] Added assistant message to history before tools: ${state.textContent?.slice(0, 50)}... [${toolCalls.length} tools]`);
|
|
@@ -304,7 +414,7 @@ export class McpMiddleware extends Middleware {
|
|
|
304
414
|
input.messages.push({
|
|
305
415
|
id: messageId,
|
|
306
416
|
role: 'tool',
|
|
307
|
-
|
|
417
|
+
toolCallId,
|
|
308
418
|
content: result,
|
|
309
419
|
} as any);
|
|
310
420
|
}
|
|
@@ -312,35 +422,45 @@ export class McpMiddleware extends Middleware {
|
|
|
312
422
|
// Reset state for next turn
|
|
313
423
|
state.toolCallArgsBuffer.clear();
|
|
314
424
|
state.toolCallNames.clear();
|
|
425
|
+
state.assistantMessageId = undefined;
|
|
315
426
|
state.textContent = ''; // Clear text content for next turn
|
|
316
427
|
|
|
317
|
-
anyInput.runId = this.generateId('mcp_run');
|
|
318
428
|
console.log(`[McpMiddleware] === CONTINUATION RUN === messages: ${input.messages.length}`);
|
|
319
429
|
|
|
320
|
-
|
|
321
|
-
|
|
430
|
+
subscribeToRun(true);
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const subscribeToRun = (isContinuation: boolean): Subscription => {
|
|
434
|
+
let subscription = new Subscription();
|
|
435
|
+
subscription = next.run(input).subscribe({
|
|
322
436
|
next: (event) => {
|
|
323
437
|
if (state.error) return;
|
|
324
438
|
|
|
325
439
|
this.handleToolCallEvent(event, state);
|
|
326
440
|
|
|
327
441
|
if (event.type === EventType.RUN_ERROR) {
|
|
328
|
-
console.log(`[McpMiddleware] RUN_ERROR received in continuation`);
|
|
442
|
+
console.log(`[McpMiddleware] RUN_ERROR received${isContinuation ? ' in continuation' : ''}`);
|
|
329
443
|
state.error = true;
|
|
330
444
|
observer.next(event);
|
|
331
445
|
observer.complete();
|
|
332
446
|
return;
|
|
333
447
|
}
|
|
334
448
|
|
|
335
|
-
if (event.type === EventType.RUN_STARTED) {
|
|
449
|
+
if (isContinuation && event.type === EventType.RUN_STARTED) {
|
|
336
450
|
console.log(`[McpMiddleware] Filtering RUN_STARTED from continuation`);
|
|
337
451
|
return;
|
|
338
452
|
}
|
|
339
453
|
|
|
454
|
+
if (event.type === EventType.MESSAGES_SNAPSHOT && this.shouldFilterMessagesSnapshot(event, state)) {
|
|
455
|
+
console.log(`[McpMiddleware] Filtering completed MCP MESSAGES_SNAPSHOT to preserve streamed message order`);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
340
459
|
if (event.type === EventType.RUN_FINISHED) {
|
|
341
460
|
if (state.pendingMcpCalls.size > 0) {
|
|
342
461
|
handleRunFinished();
|
|
343
462
|
} else {
|
|
463
|
+
continuationInProgress = false;
|
|
344
464
|
observer.next(event);
|
|
345
465
|
observer.complete();
|
|
346
466
|
}
|
|
@@ -350,44 +470,28 @@ export class McpMiddleware extends Middleware {
|
|
|
350
470
|
},
|
|
351
471
|
error: (err) => {
|
|
352
472
|
state.error = true;
|
|
473
|
+
continuationInProgress = false;
|
|
353
474
|
observer.error(err);
|
|
354
475
|
},
|
|
355
476
|
complete: () => {
|
|
356
|
-
|
|
477
|
+
subscriptions.delete(subscription);
|
|
478
|
+
if (!state.error && state.pendingMcpCalls.size === 0 && !continuationInProgress) observer.complete();
|
|
357
479
|
},
|
|
358
480
|
});
|
|
481
|
+
if (!subscription.closed) {
|
|
482
|
+
subscriptions.add(subscription);
|
|
483
|
+
}
|
|
484
|
+
return subscription;
|
|
359
485
|
};
|
|
360
486
|
|
|
361
|
-
|
|
362
|
-
next: (event) => {
|
|
363
|
-
if (state.error) return;
|
|
364
|
-
|
|
365
|
-
this.handleToolCallEvent(event, state);
|
|
487
|
+
subscribeToRun(false);
|
|
366
488
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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();
|
|
489
|
+
return () => {
|
|
490
|
+
for (const subscription of subscriptions) {
|
|
491
|
+
subscription.unsubscribe();
|
|
492
|
+
}
|
|
493
|
+
subscriptions.clear();
|
|
494
|
+
};
|
|
391
495
|
});
|
|
392
496
|
}
|
|
393
497
|
}
|
|
@@ -410,4 +514,4 @@ export { createMcpMiddleware as createMcpToolMiddleware };
|
|
|
410
514
|
|
|
411
515
|
// Re-exports
|
|
412
516
|
export { Middleware, EventType };
|
|
413
|
-
export type { RunAgentInput, BaseEvent, AbstractAgent, ToolCallEndEvent, Tool };
|
|
517
|
+
export type { RunAgentInput, BaseEvent, AbstractAgent, ToolCallEndEvent, Tool };
|
|
@@ -137,11 +137,11 @@ export class AIAdapter {
|
|
|
137
137
|
// @ts-ignore: ToolSet type inference can be tricky with dynamic imports
|
|
138
138
|
return Object.fromEntries(
|
|
139
139
|
filteredTools.map((tool) => {
|
|
140
|
-
const routedTool = tool as typeof tool & { sessionId?: string; serverName?: string };
|
|
141
|
-
const namespace = routedTool.
|
|
140
|
+
const routedTool = tool as typeof tool & { sessionId?: string; serverId?: string; serverName?: string };
|
|
141
|
+
const namespace = routedTool.serverId ?? routedTool.sessionId;
|
|
142
142
|
const toolKey = isMetaTool(tool.name)
|
|
143
143
|
? tool.name
|
|
144
|
-
: this.getRouterToolKey(tool.name, routedTool.sessionId, routedTool.
|
|
144
|
+
: this.getRouterToolKey(tool.name, routedTool.sessionId, routedTool.serverId);
|
|
145
145
|
|
|
146
146
|
return [
|
|
147
147
|
toolKey,
|
|
@@ -172,8 +172,8 @@ export class AIAdapter {
|
|
|
172
172
|
);
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
private getRouterToolKey(toolName: string, sessionId?: string,
|
|
176
|
-
const namespace = sessionId ??
|
|
175
|
+
private getRouterToolKey(toolName: string, sessionId?: string, serverId?: string): string {
|
|
176
|
+
const namespace = sessionId ?? serverId ?? 'mcp';
|
|
177
177
|
const normalized = namespace
|
|
178
178
|
.toLowerCase()
|
|
179
179
|
.replace(/[^a-z0-9]+/g, '_')
|
|
@@ -144,14 +144,14 @@ export class LangChainAdapter {
|
|
|
144
144
|
const filteredTools = await router.getFilteredTools();
|
|
145
145
|
|
|
146
146
|
return filteredTools.map((tool) => {
|
|
147
|
-
const routedTool = tool as typeof tool & { sessionId?: string; serverName?: string };
|
|
148
|
-
const namespace = routedTool.
|
|
147
|
+
const routedTool = tool as typeof tool & { sessionId?: string; serverId?: string; serverName?: string };
|
|
148
|
+
const namespace = routedTool.serverId ?? routedTool.sessionId;
|
|
149
149
|
const schema = this.jsonSchemaToZod(tool.inputSchema);
|
|
150
150
|
|
|
151
151
|
return new this.DynamicStructuredTool!({
|
|
152
152
|
name: isMetaTool(tool.name)
|
|
153
153
|
? tool.name
|
|
154
|
-
: this.getRouterToolKey(tool.name, routedTool.sessionId, routedTool.
|
|
154
|
+
: this.getRouterToolKey(tool.name, routedTool.sessionId, routedTool.serverId),
|
|
155
155
|
description: tool.description || `Tool ${tool.name}`,
|
|
156
156
|
schema: schema,
|
|
157
157
|
func: async (args: any) => {
|
|
@@ -183,8 +183,8 @@ export class LangChainAdapter {
|
|
|
183
183
|
});
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
private getRouterToolKey(toolName: string, sessionId?: string,
|
|
187
|
-
const namespace = sessionId ??
|
|
186
|
+
private getRouterToolKey(toolName: string, sessionId?: string, serverId?: string): string {
|
|
187
|
+
const namespace = sessionId ?? serverId ?? 'mcp';
|
|
188
188
|
const normalized = namespace
|
|
189
189
|
.toLowerCase()
|
|
190
190
|
.replace(/[^a-z0-9]+/g, '_')
|
|
@@ -118,6 +118,17 @@ export interface McpClient {
|
|
|
118
118
|
*/
|
|
119
119
|
disconnect: (sessionId: string) => Promise<void>;
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Reconnect to an MCP server (disconnects existing session first)
|
|
123
|
+
*/
|
|
124
|
+
reconnect: (params: {
|
|
125
|
+
serverId: string;
|
|
126
|
+
serverName: string;
|
|
127
|
+
serverUrl: string;
|
|
128
|
+
callbackUrl: string;
|
|
129
|
+
transportType?: 'sse' | 'streamable_http';
|
|
130
|
+
}) => Promise<string>;
|
|
131
|
+
|
|
121
132
|
/**
|
|
122
133
|
* Get connection by session ID
|
|
123
134
|
*/
|
|
@@ -499,6 +510,41 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
499
510
|
[]
|
|
500
511
|
);
|
|
501
512
|
|
|
513
|
+
/**
|
|
514
|
+
* Reconnect to an MCP server (tears down existing session, then connects fresh)
|
|
515
|
+
*/
|
|
516
|
+
const reconnect = useCallback(
|
|
517
|
+
async (params: {
|
|
518
|
+
serverId: string;
|
|
519
|
+
serverName: string;
|
|
520
|
+
serverUrl: string;
|
|
521
|
+
callbackUrl: string;
|
|
522
|
+
transportType?: 'sse' | 'streamable_http';
|
|
523
|
+
}): Promise<string> => {
|
|
524
|
+
if (!clientRef.current) {
|
|
525
|
+
throw new Error('SSE client not initialized');
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Find and disconnect existing session for the same server
|
|
529
|
+
const existing = connections.find(
|
|
530
|
+
(c: McpConnection) => c.serverId === params.serverId || c.serverUrl === params.serverUrl
|
|
531
|
+
);
|
|
532
|
+
if (existing) {
|
|
533
|
+
await clientRef.current.disconnectFromServer(existing.sessionId);
|
|
534
|
+
if (isMountedRef.current) {
|
|
535
|
+
setConnections((prev: McpConnection[]) =>
|
|
536
|
+
prev.filter((c: McpConnection) => c.sessionId !== existing.sessionId)
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Connect fresh
|
|
542
|
+
const result = await clientRef.current.connectToServer(params);
|
|
543
|
+
return result.sessionId;
|
|
544
|
+
},
|
|
545
|
+
[connections]
|
|
546
|
+
);
|
|
547
|
+
|
|
502
548
|
/**
|
|
503
549
|
* Disconnect from an MCP server
|
|
504
550
|
*/
|
|
@@ -668,6 +714,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
668
714
|
status,
|
|
669
715
|
isInitializing,
|
|
670
716
|
connect,
|
|
717
|
+
reconnect,
|
|
671
718
|
disconnect,
|
|
672
719
|
getConnection,
|
|
673
720
|
getConnectionByServerId,
|
|
@@ -691,6 +738,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
691
738
|
status,
|
|
692
739
|
isInitializing,
|
|
693
740
|
connect,
|
|
741
|
+
reconnect,
|
|
694
742
|
disconnect,
|
|
695
743
|
getConnection,
|
|
696
744
|
getConnectionByServerId,
|
|
@@ -117,6 +117,17 @@ export interface McpClient {
|
|
|
117
117
|
*/
|
|
118
118
|
disconnect: (sessionId: string) => Promise<void>;
|
|
119
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Reconnect to an MCP server (disconnects existing session first)
|
|
122
|
+
*/
|
|
123
|
+
reconnect: (params: {
|
|
124
|
+
serverId: string;
|
|
125
|
+
serverName: string;
|
|
126
|
+
serverUrl: string;
|
|
127
|
+
callbackUrl: string;
|
|
128
|
+
transportType?: 'sse' | 'streamable_http';
|
|
129
|
+
}) => Promise<string>;
|
|
130
|
+
|
|
120
131
|
/**
|
|
121
132
|
* Get connection by session ID
|
|
122
133
|
*/
|
|
@@ -492,6 +503,36 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
492
503
|
return result.sessionId;
|
|
493
504
|
};
|
|
494
505
|
|
|
506
|
+
/**
|
|
507
|
+
* Reconnect to an MCP server (tears down existing session, then connects fresh)
|
|
508
|
+
*/
|
|
509
|
+
const reconnect = async (params: {
|
|
510
|
+
serverId: string;
|
|
511
|
+
serverName: string;
|
|
512
|
+
serverUrl: string;
|
|
513
|
+
callbackUrl: string;
|
|
514
|
+
transportType?: 'sse' | 'streamable_http';
|
|
515
|
+
}): Promise<string> => {
|
|
516
|
+
if (!clientRef.value) {
|
|
517
|
+
throw new Error('SSE client not initialized');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Find and disconnect existing session for the same server
|
|
521
|
+
const existing = connections.value.find(
|
|
522
|
+
(c) => c.serverId === params.serverId || c.serverUrl === params.serverUrl
|
|
523
|
+
);
|
|
524
|
+
if (existing) {
|
|
525
|
+
await clientRef.value.disconnectFromServer(existing.sessionId);
|
|
526
|
+
if (isMountedRef.value) {
|
|
527
|
+
connections.value = connections.value.filter((c) => c.sessionId !== existing.sessionId);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Connect fresh
|
|
532
|
+
const result = await clientRef.value.connectToServer(params);
|
|
533
|
+
return result.sessionId;
|
|
534
|
+
};
|
|
535
|
+
|
|
495
536
|
/**
|
|
496
537
|
* Disconnect from an MCP server
|
|
497
538
|
*/
|
|
@@ -642,6 +683,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
642
683
|
status: status as unknown as { value: 'connecting' | 'connected' | 'disconnected' | 'error' },
|
|
643
684
|
isInitializing: isInitializing as unknown as { value: boolean },
|
|
644
685
|
connect,
|
|
686
|
+
reconnect,
|
|
645
687
|
disconnect,
|
|
646
688
|
getConnection,
|
|
647
689
|
getConnectionByServerId,
|