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