@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.
Files changed (43) hide show
  1. package/README.md +3 -3
  2. package/dist/adapters/agui-adapter.d.mts +19 -42
  3. package/dist/adapters/agui-adapter.d.ts +19 -42
  4. package/dist/adapters/agui-adapter.js +69 -69
  5. package/dist/adapters/agui-adapter.js.map +1 -1
  6. package/dist/adapters/agui-adapter.mjs +69 -70
  7. package/dist/adapters/agui-adapter.mjs.map +1 -1
  8. package/dist/adapters/agui-middleware.d.mts +24 -136
  9. package/dist/adapters/agui-middleware.d.ts +24 -136
  10. package/dist/adapters/agui-middleware.js +275 -350
  11. package/dist/adapters/agui-middleware.js.map +1 -1
  12. package/dist/adapters/agui-middleware.mjs +275 -350
  13. package/dist/adapters/agui-middleware.mjs.map +1 -1
  14. package/dist/client/index.d.mts +2 -2
  15. package/dist/client/index.d.ts +2 -2
  16. package/dist/client/react.d.mts +2 -2
  17. package/dist/client/react.d.ts +2 -2
  18. package/dist/client/vue.d.mts +2 -2
  19. package/dist/client/vue.d.ts +2 -2
  20. package/dist/index.d.mts +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +2 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.mjs +2 -1
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/server/index.d.mts +3 -3
  27. package/dist/server/index.d.ts +3 -3
  28. package/dist/server/index.js +2 -1
  29. package/dist/server/index.js.map +1 -1
  30. package/dist/server/index.mjs +2 -1
  31. package/dist/server/index.mjs.map +1 -1
  32. package/dist/shared/index.d.mts +1 -1
  33. package/dist/shared/index.d.ts +1 -1
  34. package/dist/shared/index.js.map +1 -1
  35. package/dist/shared/index.mjs.map +1 -1
  36. package/dist/{types-SbDlA2VX.d.mts → types-CLccx9wW.d.mts} +1 -1
  37. package/dist/{types-SbDlA2VX.d.ts → types-CLccx9wW.d.ts} +1 -1
  38. package/package.json +2 -2
  39. package/src/adapters/agui-adapter.ts +98 -109
  40. package/src/adapters/agui-middleware.ts +424 -512
  41. package/src/server/handlers/sse-handler.ts +4 -1
  42. package/src/server/storage/types.ts +1 -1
  43. package/src/shared/types.ts +1 -1
@@ -3,410 +3,335 @@ import { Middleware, EventType } from '@ag-ui/client';
3
3
  export { EventType, Middleware } from '@ag-ui/client';
4
4
 
5
5
  var __defProp = Object.defineProperty;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
6
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
- var __esm = (fn, res) => function __init() {
9
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
- };
11
- var __export = (target, all) => {
12
- for (var name in all)
13
- __defProp(target, name, { get: all[name], enumerable: true });
14
- };
15
7
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
16
8
 
17
9
  // src/adapters/agui-adapter.ts
18
- var agui_adapter_exports = {};
19
- __export(agui_adapter_exports, {
20
- AguiAdapter: () => AguiAdapter
21
- });
22
- var AguiAdapter;
23
- var init_agui_adapter = __esm({
24
- "src/adapters/agui-adapter.ts"() {
25
- AguiAdapter = class {
26
- constructor(client, options = {}) {
27
- this.client = client;
28
- this.options = options;
29
- }
30
- /**
31
- * Get tools with handlers for MCP tool execution.
32
- *
33
- * Each tool includes a handler function that:
34
- * 1. Calls the MCP tool via the client
35
- * 2. Extracts text content from the result
36
- * 3. Returns the result as a string or JSON
37
- *
38
- * @returns Array of AguiTool objects
39
- */
40
- async getTools() {
41
- const isMultiSession = typeof this.client.getClients === "function";
42
- if (isMultiSession) {
43
- const clients = this.client.getClients();
44
- const allTools = [];
45
- for (const client of clients) {
46
- const tools = await this.transformTools(client);
47
- allTools.push(...tools);
48
- }
49
- return allTools;
50
- }
51
- return this.transformTools(this.client);
52
- }
53
- async transformTools(client) {
54
- if (!client.isConnected()) {
55
- return [];
56
- }
57
- const result = await client.listTools();
58
- const prefix = this.options.prefix ?? client.getServerId() ?? "mcp";
59
- const tools = [];
60
- for (const tool of result.tools) {
61
- const toolName = `${prefix}_${tool.name}`;
62
- tools.push({
63
- name: toolName,
64
- description: tool.description || `Execute ${tool.name}`,
65
- parameters: tool.inputSchema || { type: "object", properties: {} },
66
- handler: async (args) => {
67
- console.log(`[AguiAdapter] Executing MCP tool: ${tool.name}`, args);
68
- const result2 = await client.callTool(tool.name, args);
69
- if (result2.content && Array.isArray(result2.content)) {
70
- const textContent = result2.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
71
- return textContent || result2;
72
- }
73
- return result2;
74
- }
75
- });
76
- }
77
- return tools;
78
- }
79
- /**
80
- * Get tools as a function (for dynamic loading).
81
- *
82
- * @returns Function that returns a Promise of tools
83
- */
84
- getToolsFunction() {
85
- return async () => this.getTools();
86
- }
87
- /**
88
- * Get tool definitions in JSON Schema format for passing to remote agents.
89
- *
90
- * This format is compatible with:
91
- * - OpenAI's function calling API
92
- * - AG-UI input.tools format
93
- * - Most LLM tool/function calling implementations
94
- *
95
- * @returns Array of AguiToolDefinition objects
96
- */
97
- async getToolDefinitions() {
98
- const isMultiSession = typeof this.client.getClients === "function";
99
- if (isMultiSession) {
100
- const clients = this.client.getClients();
101
- const allTools = [];
102
- for (const client of clients) {
103
- const tools = await this.transformToolDefinitions(client);
104
- allTools.push(...tools);
105
- }
106
- return allTools;
107
- }
108
- return this.transformToolDefinitions(this.client);
109
- }
110
- async transformToolDefinitions(client) {
111
- if (!client.isConnected()) {
112
- return [];
113
- }
114
- const result = await client.listTools();
115
- const prefix = this.options.prefix ?? client.getServerId() ?? "mcp";
116
- const tools = [];
117
- for (const tool of result.tools) {
118
- tools.push({
119
- name: `${prefix}_${tool.name}`,
120
- description: tool.description || `Execute ${tool.name}`,
121
- parameters: tool.inputSchema || { type: "object", properties: {} }
122
- });
123
- }
124
- return tools;
10
+ function cleanSchema(schema) {
11
+ if (!schema) {
12
+ return { type: "object", properties: {} };
13
+ }
14
+ const cleaned = { ...schema };
15
+ delete cleaned.$schema;
16
+ delete cleaned.$id;
17
+ delete cleaned.$comment;
18
+ delete cleaned.$defs;
19
+ delete cleaned.definitions;
20
+ if (cleaned.properties && typeof cleaned.properties === "object") {
21
+ const cleanedProps = {};
22
+ for (const [key, value] of Object.entries(cleaned.properties)) {
23
+ if (typeof value === "object" && value !== null) {
24
+ cleanedProps[key] = cleanSchema(value);
25
+ } else {
26
+ cleanedProps[key] = value;
125
27
  }
126
- };
28
+ }
29
+ cleaned.properties = cleanedProps;
30
+ }
31
+ if (cleaned.items && typeof cleaned.items === "object") {
32
+ cleaned.items = cleanSchema(cleaned.items);
127
33
  }
128
- });
34
+ if (cleaned.additionalProperties && typeof cleaned.additionalProperties === "object") {
35
+ cleaned.additionalProperties = cleanSchema(cleaned.additionalProperties);
36
+ }
37
+ return cleaned;
38
+ }
39
+
40
+ // src/adapters/agui-middleware.ts
129
41
  var McpMiddleware = class extends Middleware {
130
42
  constructor(config) {
131
43
  super();
132
- __publicField(this, "client");
133
- __publicField(this, "toolPrefix");
134
- __publicField(this, "actions");
135
44
  __publicField(this, "tools");
136
- __publicField(this, "actionsLoaded", false);
137
- this.client = config.client;
138
- this.toolPrefix = config.toolPrefix ?? "server-";
139
- this.actions = config.tools ?? null;
140
- this.tools = null;
141
- if (this.actions) {
142
- this.actionsLoaded = true;
143
- this.tools = this.actionsToTools(this.actions);
144
- }
145
- }
146
- /**
147
- * Convert actions to AG-UI tool format
148
- */
149
- actionsToTools(actions) {
150
- return actions.map((action) => ({
151
- name: action.name,
152
- description: action.description,
153
- parameters: action.parameters || { type: "object", properties: {} }
45
+ __publicField(this, "toolSchemas");
46
+ __publicField(this, "maxResultLength");
47
+ this.tools = config.tools;
48
+ this.maxResultLength = config.maxResultLength ?? 5e4;
49
+ this.toolSchemas = this.tools.map((t) => ({
50
+ name: t.name,
51
+ description: t.description,
52
+ parameters: cleanSchema(t.parameters)
154
53
  }));
155
54
  }
156
- /**
157
- * Check if a tool name is an MCP tool (matches the configured prefix)
158
- */
159
55
  isMcpTool(toolName) {
160
- return toolName.startsWith(this.toolPrefix);
56
+ return this.tools.some((t) => t.name === toolName);
161
57
  }
162
- /**
163
- * Load actions from the MCP client if not already loaded
164
- */
165
- async ensureActionsLoaded() {
166
- if (this.actionsLoaded) return;
167
- const { AguiAdapter: AguiAdapter2 } = await Promise.resolve().then(() => (init_agui_adapter(), agui_adapter_exports));
168
- const adapter = new AguiAdapter2(this.client);
169
- this.actions = await adapter.getTools();
170
- this.actionsLoaded = true;
58
+ parseArgs(argsString) {
59
+ if (!argsString?.trim()) return {};
60
+ try {
61
+ return JSON.parse(argsString);
62
+ } catch {
63
+ const trimmed = argsString.trim();
64
+ if (trimmed.includes("}{")) {
65
+ const firstObject = trimmed.slice(0, trimmed.indexOf("}{") + 1);
66
+ try {
67
+ return JSON.parse(firstObject);
68
+ } catch {
69
+ console.error(`[McpMiddleware] Failed to parse JSON:`, firstObject);
70
+ }
71
+ }
72
+ console.error(`[McpMiddleware] Failed to parse args:`, argsString);
73
+ return {};
74
+ }
171
75
  }
172
- /**
173
- * Execute an MCP tool and return the result as a string
174
- */
175
76
  async executeTool(toolName, args) {
176
- await this.ensureActionsLoaded();
177
- const action = this.actions?.find((a) => a.name === toolName);
178
- if (!action) {
179
- return `Error: Tool not found: ${toolName}`;
180
- }
181
- if (!action.handler) {
182
- return `Error: Tool has no handler: ${toolName}`;
77
+ const tool = this.tools.find((t) => t.name === toolName);
78
+ if (!tool?.handler) {
79
+ return `Error: Tool ${tool ? "has no handler" : "not found"}: ${toolName}`;
183
80
  }
184
81
  try {
185
82
  console.log(`[McpMiddleware] Executing tool: ${toolName}`, args);
186
- const result = await action.handler(args);
187
- console.log(`[McpMiddleware] Tool result:`, typeof result === "string" ? result.slice(0, 200) : result);
188
- return typeof result === "string" ? result : JSON.stringify(result);
83
+ const result = await tool.handler(args);
84
+ let resultStr = typeof result === "string" ? result : JSON.stringify(result);
85
+ if (resultStr.length > this.maxResultLength) {
86
+ const original = resultStr.length;
87
+ resultStr = resultStr.slice(0, this.maxResultLength) + `
88
+
89
+ [... Truncated from ${original} to ${this.maxResultLength} chars]`;
90
+ console.log(`[McpMiddleware] Tool result truncated from ${original} to ${this.maxResultLength} chars`);
91
+ }
92
+ console.log(`[McpMiddleware] Tool result:`, resultStr.slice(0, 200));
93
+ return resultStr;
189
94
  } catch (error) {
190
95
  console.error(`[McpMiddleware] Error executing tool:`, error);
191
- return `Error executing tool: ${error.message || String(error)}`;
96
+ return `Error: ${error.message || String(error)}`;
192
97
  }
193
98
  }
194
- /**
195
- * Generate a unique message ID for tool results
196
- */
197
- generateMessageId() {
198
- return `mcp_result_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
99
+ generateId(prefix) {
100
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
101
+ }
102
+ ensureIds(input) {
103
+ const anyInput = input;
104
+ if (!anyInput.threadId) anyInput.threadId = this.generateId("mcp_thread");
105
+ if (!anyInput.runId) anyInput.runId = this.generateId("mcp_run");
106
+ }
107
+ /** Process tool call events and update state */
108
+ handleToolCallEvent(event, state) {
109
+ const { toolCallArgsBuffer, toolCallNames, pendingMcpCalls } = state;
110
+ if (event.type === EventType.TEXT_MESSAGE_CHUNK) {
111
+ const e = event;
112
+ if (e.delta) {
113
+ state.textContent = (state.textContent || "") + e.delta;
114
+ }
115
+ }
116
+ if (event.type === EventType.TOOL_CALL_START) {
117
+ const e = event;
118
+ if (e.toolCallId && e.toolCallName) {
119
+ toolCallNames.set(e.toolCallId, e.toolCallName);
120
+ if (this.isMcpTool(e.toolCallName)) {
121
+ pendingMcpCalls.add(e.toolCallId);
122
+ }
123
+ console.log(`[McpMiddleware] TOOL_CALL_START: ${e.toolCallName} (id: ${e.toolCallId}, isMCP: ${this.isMcpTool(e.toolCallName)})`);
124
+ }
125
+ }
126
+ if (event.type === EventType.TOOL_CALL_ARGS) {
127
+ const e = event;
128
+ if (e.toolCallId && e.delta) {
129
+ const existing = toolCallArgsBuffer.get(e.toolCallId) || "";
130
+ toolCallArgsBuffer.set(e.toolCallId, existing + e.delta);
131
+ }
132
+ }
133
+ if (event.type === EventType.TOOL_CALL_END) {
134
+ const e = event;
135
+ console.log(`[McpMiddleware] TOOL_CALL_END: ${toolCallNames.get(e.toolCallId) ?? "unknown"} (id: ${e.toolCallId})`);
136
+ }
137
+ if (event.type === EventType.MESSAGES_SNAPSHOT) {
138
+ const messages = event.messages || [];
139
+ if (messages.length > 0) {
140
+ const lastMsg = messages[messages.length - 1];
141
+ if (lastMsg.role === "assistant" && lastMsg.content) {
142
+ state.textContent = lastMsg.content;
143
+ }
144
+ for (let i = messages.length - 1; i >= 0; i--) {
145
+ const msg = messages[i];
146
+ const tools = Array.isArray(msg.toolCalls) ? msg.toolCalls : Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
147
+ if (msg.role === "assistant" && tools.length > 0) {
148
+ for (const tc of tools) {
149
+ if (tc.id && tc.function?.name && !toolCallNames.has(tc.id)) {
150
+ toolCallNames.set(tc.id, tc.function.name);
151
+ toolCallArgsBuffer.set(tc.id, tc.function.arguments || "{}");
152
+ if (this.isMcpTool(tc.function.name)) {
153
+ pendingMcpCalls.add(tc.id);
154
+ console.log(`[McpMiddleware] MESSAGES_SNAPSHOT: Discovered ${tc.function.name} (id: ${tc.id})`);
155
+ }
156
+ }
157
+ }
158
+ break;
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
164
+ /** Execute pending MCP tools and return results */
165
+ async executeTools(state) {
166
+ const { toolCallArgsBuffer, toolCallNames, pendingMcpCalls } = state;
167
+ const results = [];
168
+ const promises = [...pendingMcpCalls].map(async (toolCallId) => {
169
+ const toolName = toolCallNames.get(toolCallId);
170
+ if (!toolName) return;
171
+ const args = this.parseArgs(toolCallArgsBuffer.get(toolCallId) || "{}");
172
+ console.log(`[McpMiddleware] Executing pending tool: ${toolName}`);
173
+ const result = await this.executeTool(toolName, args);
174
+ results.push({
175
+ toolCallId,
176
+ toolName,
177
+ result,
178
+ messageId: this.generateId("mcp_result")
179
+ });
180
+ pendingMcpCalls.delete(toolCallId);
181
+ });
182
+ await Promise.all(promises);
183
+ return results;
184
+ }
185
+ /** Emit tool results (without RUN_FINISHED - that's emitted when truly done) */
186
+ emitToolResults(observer, results) {
187
+ for (const { toolCallId, toolName, result, messageId } of results) {
188
+ observer.next({
189
+ type: EventType.TOOL_CALL_RESULT,
190
+ toolCallId,
191
+ messageId,
192
+ content: result,
193
+ role: "tool",
194
+ timestamp: Date.now()
195
+ });
196
+ console.log(`[McpMiddleware] Emitting TOOL_CALL_RESULT for: ${toolName}`);
197
+ }
199
198
  }
200
- /**
201
- * Run the middleware, intercepting and executing MCP tool calls
202
- */
203
199
  run(input, next) {
204
200
  return new Observable((observer) => {
205
- const toolCallArgsBuffer = /* @__PURE__ */ new Map();
206
- const toolCallNames = /* @__PURE__ */ new Map();
207
- const pendingMcpCalls = /* @__PURE__ */ new Set();
208
- console.log(`[McpMiddleware] Starting run with ${this.actions?.length ?? 0} registered actions`);
209
- console.log(`[McpMiddleware] Tool prefix: "${this.toolPrefix}"`);
210
- if (this.tools && this.tools.length > 0) {
211
- const existingTools = input.tools || [];
212
- input.tools = [...existingTools, ...this.tools];
213
- console.log(`[McpMiddleware] Injected ${this.tools.length} MCP tools into input.tools`);
214
- console.log(`[McpMiddleware] Total tools: ${input.tools.length}`);
215
- console.log(`[McpMiddleware] Tool names:`, this.tools.map((t) => t.name));
201
+ const state = {
202
+ toolCallArgsBuffer: /* @__PURE__ */ new Map(),
203
+ toolCallNames: /* @__PURE__ */ new Map(),
204
+ pendingMcpCalls: /* @__PURE__ */ new Set(),
205
+ textContent: "",
206
+ error: false
207
+ };
208
+ this.ensureIds(input);
209
+ const anyInput = input;
210
+ console.log(`[McpMiddleware] === NEW RUN ===`);
211
+ console.log(`[McpMiddleware] threadId: ${anyInput.threadId}, runId: ${anyInput.runId}`);
212
+ console.log(`[McpMiddleware] messages: ${input.messages?.length ?? 0}, tools: ${this.tools?.length ?? 0}`);
213
+ if (this.toolSchemas?.length) {
214
+ input.tools = [...input.tools || [], ...this.toolSchemas];
215
+ console.log(`[McpMiddleware] Injected ${this.toolSchemas.length} tools:`, this.toolSchemas.map((t) => t.name));
216
216
  }
217
- const handleRunFinished = async (event) => {
218
- if (pendingMcpCalls.size === 0) {
219
- observer.next(event);
217
+ const handleRunFinished = async () => {
218
+ if (state.error) return;
219
+ if (state.pendingMcpCalls.size === 0) {
220
+ observer.next({
221
+ type: EventType.RUN_FINISHED,
222
+ threadId: anyInput.threadId,
223
+ runId: anyInput.runId,
224
+ timestamp: Date.now()
225
+ });
220
226
  observer.complete();
221
227
  return;
222
228
  }
223
- console.log(`[McpMiddleware] RUN_FINISHED received with ${pendingMcpCalls.size} pending MCP calls`);
224
- const callPromises = [...pendingMcpCalls].map(async (toolCallId) => {
225
- const toolName = toolCallNames.get(toolCallId);
226
- if (!toolName) return;
227
- const argsString = toolCallArgsBuffer.get(toolCallId) || "{}";
228
- let args = {};
229
- try {
230
- args = JSON.parse(argsString);
231
- } catch (e) {
232
- console.error(`[McpMiddleware] Failed to parse args:`, argsString);
229
+ console.log(`[McpMiddleware] RUN_FINISHED with ${state.pendingMcpCalls.size} pending calls`);
230
+ const toolCalls = [];
231
+ for (const toolCallId of state.pendingMcpCalls) {
232
+ const name = state.toolCallNames.get(toolCallId);
233
+ const args = state.toolCallArgsBuffer.get(toolCallId) || "{}";
234
+ if (name) {
235
+ toolCalls.push({
236
+ id: toolCallId,
237
+ type: "function",
238
+ function: { name, arguments: args }
239
+ });
233
240
  }
234
- console.log(`[McpMiddleware] Executing pending tool: ${toolName}`);
235
- const result = await this.executeTool(toolName, args);
236
- const messageId = this.generateMessageId();
237
- const resultEvent = {
238
- type: EventType.TOOL_CALL_RESULT,
239
- toolCallId,
240
- messageId,
241
- content: result,
242
- role: "tool",
243
- timestamp: Date.now()
241
+ }
242
+ if (toolCalls.length > 0 || state.textContent) {
243
+ const assistantMsg = {
244
+ id: this.generateId("msg_ast"),
245
+ role: "assistant",
246
+ content: state.textContent || null,
247
+ // Ensure null if empty string for strict LLMs
248
+ tool_calls: toolCalls.length > 0 ? toolCalls : void 0
244
249
  };
245
- console.log(`[McpMiddleware] Emitting TOOL_CALL_RESULT for: ${toolName}`);
246
- observer.next(resultEvent);
250
+ input.messages.push(assistantMsg);
251
+ console.log(`[McpMiddleware] Added assistant message to history before tools: ${state.textContent?.slice(0, 50)}... [${toolCalls.length} tools]`);
252
+ }
253
+ const results = await this.executeTools(state);
254
+ this.emitToolResults(observer, results);
255
+ console.log(`[McpMiddleware] Triggering continuation with ${results.length} results`);
256
+ for (const { toolCallId, result, messageId } of results) {
247
257
  input.messages.push({
248
258
  id: messageId,
249
259
  role: "tool",
250
- toolCallId,
260
+ tool_call_id: toolCallId,
251
261
  content: result
252
262
  });
253
- pendingMcpCalls.delete(toolCallId);
254
- });
255
- await Promise.all(callPromises);
256
- console.log(`[McpMiddleware] All MCP tools executed, emitting RUN_FINISHED`);
257
- observer.next({
258
- type: EventType.RUN_FINISHED,
259
- threadId: input.threadId,
260
- runId: input.runId,
261
- timestamp: Date.now()
263
+ }
264
+ state.toolCallArgsBuffer.clear();
265
+ state.toolCallNames.clear();
266
+ state.textContent = "";
267
+ anyInput.runId = this.generateId("mcp_run");
268
+ console.log(`[McpMiddleware] === CONTINUATION RUN === messages: ${input.messages.length}`);
269
+ next.run(input).subscribe({
270
+ next: (event) => {
271
+ if (state.error) return;
272
+ this.handleToolCallEvent(event, state);
273
+ if (event.type === EventType.RUN_ERROR) {
274
+ console.log(`[McpMiddleware] RUN_ERROR received in continuation`);
275
+ state.error = true;
276
+ observer.next(event);
277
+ observer.complete();
278
+ return;
279
+ }
280
+ if (event.type === EventType.RUN_STARTED) {
281
+ console.log(`[McpMiddleware] Filtering RUN_STARTED from continuation`);
282
+ return;
283
+ }
284
+ if (event.type === EventType.RUN_FINISHED) {
285
+ if (state.pendingMcpCalls.size > 0) {
286
+ handleRunFinished();
287
+ } else {
288
+ observer.next(event);
289
+ observer.complete();
290
+ }
291
+ return;
292
+ }
293
+ observer.next(event);
294
+ },
295
+ error: (err) => {
296
+ state.error = true;
297
+ observer.error(err);
298
+ },
299
+ complete: () => {
300
+ if (!state.error && state.pendingMcpCalls.size === 0) observer.complete();
301
+ }
262
302
  });
263
- console.log(`[McpMiddleware] Triggering new run`);
264
- this.triggerNewRun(observer, input, next, toolCallArgsBuffer, toolCallNames, pendingMcpCalls);
265
303
  };
266
304
  const subscription = next.run(input).subscribe({
267
305
  next: (event) => {
268
- if (event.type === EventType.TOOL_CALL_START) {
269
- const startEvent = event;
270
- if (startEvent.toolCallId && startEvent.toolCallName) {
271
- toolCallNames.set(startEvent.toolCallId, startEvent.toolCallName);
272
- const isMcp = this.isMcpTool(startEvent.toolCallName);
273
- console.log(`[McpMiddleware] TOOL_CALL_START: ${startEvent.toolCallName} (id: ${startEvent.toolCallId}, isMCP: ${isMcp})`);
274
- if (isMcp) {
275
- pendingMcpCalls.add(startEvent.toolCallId);
276
- }
277
- }
278
- }
279
- if (event.type === EventType.TOOL_CALL_ARGS) {
280
- const argsEvent = event;
281
- if (argsEvent.toolCallId && argsEvent.delta) {
282
- const existing = toolCallArgsBuffer.get(argsEvent.toolCallId) || "";
283
- toolCallArgsBuffer.set(argsEvent.toolCallId, existing + argsEvent.delta);
284
- }
285
- }
286
- if (event.type === EventType.TOOL_CALL_END) {
287
- const endEvent = event;
288
- const toolName = toolCallNames.get(endEvent.toolCallId);
289
- console.log(`[McpMiddleware] TOOL_CALL_END: ${toolName ?? "unknown"} (id: ${endEvent.toolCallId})`);
306
+ if (state.error) return;
307
+ this.handleToolCallEvent(event, state);
308
+ if (event.type === EventType.RUN_ERROR) {
309
+ console.log(`[McpMiddleware] RUN_ERROR received`);
310
+ state.error = true;
311
+ observer.next(event);
312
+ observer.complete();
313
+ return;
290
314
  }
291
315
  if (event.type === EventType.RUN_FINISHED) {
292
- handleRunFinished(event);
316
+ handleRunFinished();
293
317
  return;
294
318
  }
295
319
  observer.next(event);
296
320
  },
297
- error: (error) => {
298
- observer.error(error);
321
+ error: (err) => {
322
+ state.error = true;
323
+ observer.error(err);
299
324
  },
300
325
  complete: () => {
301
- if (pendingMcpCalls.size === 0) {
302
- observer.complete();
303
- }
304
- }
305
- });
306
- return () => {
307
- subscription.unsubscribe();
308
- };
309
- });
310
- }
311
- triggerNewRun(observer, input, next, toolCallArgsBuffer, toolCallNames, pendingMcpCalls) {
312
- toolCallArgsBuffer.clear();
313
- toolCallNames.clear();
314
- pendingMcpCalls.clear();
315
- console.log(`[McpMiddleware] Starting new run with updated messages`);
316
- next.run(input).subscribe({
317
- next: (event) => {
318
- if (event.type === EventType.TOOL_CALL_START) {
319
- const startEvent = event;
320
- if (startEvent.toolCallId && startEvent.toolCallName) {
321
- toolCallNames.set(startEvent.toolCallId, startEvent.toolCallName);
322
- const isMcp = this.isMcpTool(startEvent.toolCallName);
323
- console.log(`[McpMiddleware] TOOL_CALL_START: ${startEvent.toolCallName} (id: ${startEvent.toolCallId}, isMCP: ${isMcp})`);
324
- if (isMcp) {
325
- pendingMcpCalls.add(startEvent.toolCallId);
326
- }
327
- }
328
- }
329
- if (event.type === EventType.TOOL_CALL_ARGS) {
330
- const argsEvent = event;
331
- if (argsEvent.toolCallId && argsEvent.delta) {
332
- const existing = toolCallArgsBuffer.get(argsEvent.toolCallId) || "";
333
- toolCallArgsBuffer.set(argsEvent.toolCallId, existing + argsEvent.delta);
334
- }
326
+ if (!state.error && state.pendingMcpCalls.size === 0) observer.complete();
335
327
  }
336
- if (event.type === EventType.TOOL_CALL_END) {
337
- const endEvent = event;
338
- const toolName = toolCallNames.get(endEvent.toolCallId);
339
- console.log(`[McpMiddleware] TOOL_CALL_END: ${toolName ?? "unknown"} (id: ${endEvent.toolCallId})`);
340
- }
341
- if (event.type === EventType.RUN_FINISHED) {
342
- if (pendingMcpCalls.size > 0) {
343
- console.log(`[McpMiddleware] RUN_FINISHED with ${pendingMcpCalls.size} pending calls, executing...`);
344
- this.handlePendingCalls(observer, input, next, toolCallArgsBuffer, toolCallNames, pendingMcpCalls);
345
- } else {
346
- observer.next(event);
347
- observer.complete();
348
- }
349
- return;
350
- }
351
- observer.next(event);
352
- },
353
- error: (error) => observer.error(error),
354
- complete: () => {
355
- if (pendingMcpCalls.size === 0) {
356
- observer.complete();
357
- }
358
- }
359
- });
360
- }
361
- async handlePendingCalls(observer, input, next, toolCallArgsBuffer, toolCallNames, pendingMcpCalls) {
362
- const callPromises = [...pendingMcpCalls].map(async (toolCallId) => {
363
- const toolName = toolCallNames.get(toolCallId);
364
- if (!toolName) return;
365
- const argsString = toolCallArgsBuffer.get(toolCallId) || "{}";
366
- let args = {};
367
- try {
368
- args = JSON.parse(argsString);
369
- } catch (e) {
370
- console.error(`[McpMiddleware] Failed to parse args:`, argsString);
371
- }
372
- console.log(`[McpMiddleware] Executing pending tool: ${toolName}`);
373
- const result = await this.executeTool(toolName, args);
374
- const messageId = this.generateMessageId();
375
- const resultEvent = {
376
- type: EventType.TOOL_CALL_RESULT,
377
- toolCallId,
378
- messageId,
379
- content: result,
380
- role: "tool",
381
- timestamp: Date.now()
382
- };
383
- console.log(`[McpMiddleware] Emitting TOOL_CALL_RESULT for: ${toolName}`);
384
- observer.next(resultEvent);
385
- input.messages.push({
386
- id: messageId,
387
- role: "tool",
388
- toolCallId,
389
- content: result
390
328
  });
391
- pendingMcpCalls.delete(toolCallId);
392
- });
393
- await Promise.all(callPromises);
394
- console.log(`[McpMiddleware] Pending tools executed, emitting RUN_FINISHED`);
395
- observer.next({
396
- type: EventType.RUN_FINISHED,
397
- threadId: input.threadId,
398
- runId: input.runId,
399
- timestamp: Date.now()
329
+ return () => subscription.unsubscribe();
400
330
  });
401
- console.log(`[McpMiddleware] Triggering new run`);
402
- this.triggerNewRun(observer, input, next, toolCallArgsBuffer, toolCallNames, pendingMcpCalls);
403
331
  }
404
332
  };
405
- function createMcpMiddleware(client, options = {}) {
406
- const middleware = new McpMiddleware({
407
- client,
408
- ...options
409
- });
333
+ function createMcpMiddleware(options) {
334
+ const middleware = new McpMiddleware(options);
410
335
  return (input, next) => {
411
336
  return middleware.run(input, next);
412
337
  };