@mcp-ts/sdk 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/adapters/agui-adapter.d.mts +19 -42
- package/dist/adapters/agui-adapter.d.ts +19 -42
- package/dist/adapters/agui-adapter.js +69 -69
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs +69 -70
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- package/dist/adapters/agui-middleware.d.mts +24 -136
- package/dist/adapters/agui-middleware.d.ts +24 -136
- package/dist/adapters/agui-middleware.js +275 -350
- package/dist/adapters/agui-middleware.js.map +1 -1
- package/dist/adapters/agui-middleware.mjs +275 -350
- package/dist/adapters/agui-middleware.mjs.map +1 -1
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/react.d.mts +2 -2
- package/dist/client/react.d.ts +2 -2
- package/dist/client/vue.d.mts +2 -2
- package/dist/client/vue.d.ts +2 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +3 -3
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +2 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +2 -1
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{types-SbDlA2VX.d.mts → types-CLccx9wW.d.mts} +1 -1
- package/dist/{types-SbDlA2VX.d.ts → types-CLccx9wW.d.ts} +1 -1
- package/package.json +2 -2
- package/src/adapters/agui-adapter.ts +98 -109
- package/src/adapters/agui-middleware.ts +424 -512
- package/src/server/handlers/sse-handler.ts +4 -1
- package/src/server/storage/types.ts +1 -1
- package/src/shared/types.ts +1 -1
|
@@ -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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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, "
|
|
137
|
-
this
|
|
138
|
-
this.
|
|
139
|
-
this.
|
|
140
|
-
this.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
|
56
|
+
return this.tools.some((t) => t.name === toolName);
|
|
161
57
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
|
187
|
-
|
|
188
|
-
|
|
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
|
|
96
|
+
return `Error: ${error.message || String(error)}`;
|
|
192
97
|
}
|
|
193
98
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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 (
|
|
218
|
-
if (
|
|
219
|
-
|
|
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
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
246
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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 (
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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(
|
|
316
|
+
handleRunFinished();
|
|
293
317
|
return;
|
|
294
318
|
}
|
|
295
319
|
observer.next(event);
|
|
296
320
|
},
|
|
297
|
-
error: (
|
|
298
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
};
|