@nqminds/mcp-client 1.0.4 → 1.0.7
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 +8 -8
- package/dist/MCPChat.d.ts.map +1 -1
- package/dist/MCPChat.js +28 -8
- package/dist/api-helpers.d.ts.map +1 -1
- package/dist/api-helpers.js +35 -6
- package/dist/openai-client.d.ts +1 -1
- package/dist/openai-client.d.ts.map +1 -1
- package/dist/openai-client.js +53 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @nqminds/mcp-client
|
|
2
2
|
|
|
3
3
|
A complete, ready-to-use React component and backend client for MCP (Model Context Protocol) AI chat with OpenAI integration. Includes both the UI component and the OpenAI-powered backend logic.
|
|
4
4
|
|
|
@@ -13,7 +13,7 @@ A complete, ready-to-use React component and backend client for MCP (Model Conte
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm install @
|
|
16
|
+
npm install @nqminds/mcp-client
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Quick Start
|
|
@@ -33,7 +33,7 @@ OPENAI_MODEL=chatgpt-5-mini
|
|
|
33
33
|
Create `app/api/mcp/chat/route.ts`:
|
|
34
34
|
|
|
35
35
|
```typescript
|
|
36
|
-
import { createMCPChatHandler, createMCPClearHandler } from "@
|
|
36
|
+
import { createMCPChatHandler, createMCPClearHandler } from "@nqminds/mcp-client/server";
|
|
37
37
|
|
|
38
38
|
const chatHandler = createMCPChatHandler({
|
|
39
39
|
openaiApiKey: process.env.OPENAI_API_KEY!,
|
|
@@ -55,8 +55,8 @@ export async function DELETE(req: Request) {
|
|
|
55
55
|
### 3. Add the component to your app
|
|
56
56
|
|
|
57
57
|
```tsx
|
|
58
|
-
import { MCPChat } from '@
|
|
59
|
-
import '@
|
|
58
|
+
import { MCPChat } from '@nqminds/mcp-client';
|
|
59
|
+
import '@nqminds/mcp-client/dist/styles/MCPChat.css';
|
|
60
60
|
|
|
61
61
|
export default function Page() {
|
|
62
62
|
return (
|
|
@@ -71,7 +71,7 @@ export default function Page() {
|
|
|
71
71
|
### 4. Import CSS in your layout
|
|
72
72
|
|
|
73
73
|
```tsx
|
|
74
|
-
import '@
|
|
74
|
+
import '@nqminds/mcp-client/dist/styles/MCPChat.css';
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
That's it! Your MCP chat is ready to use.
|
|
@@ -117,7 +117,7 @@ Available CSS variables:
|
|
|
117
117
|
If you need more control, use the client directly (server-side only):
|
|
118
118
|
|
|
119
119
|
```typescript
|
|
120
|
-
import { MCPClientOpenAI } from '@
|
|
120
|
+
import { MCPClientOpenAI } from '@nqminds/mcp-client/server';
|
|
121
121
|
|
|
122
122
|
const client = new MCPClientOpenAI({
|
|
123
123
|
openaiApiKey: process.env.OPENAI_API_KEY!,
|
|
@@ -138,7 +138,7 @@ await client.cleanup();
|
|
|
138
138
|
Create your own streaming handler (server-side):
|
|
139
139
|
|
|
140
140
|
```typescript
|
|
141
|
-
import { MCPClientOpenAI } from '@
|
|
141
|
+
import { MCPClientOpenAI } from '@nqminds/mcp-client/server';
|
|
142
142
|
|
|
143
143
|
export async function POST(req: Request) {
|
|
144
144
|
const { message } = await req.json();
|
package/dist/MCPChat.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MCPChat.d.ts","sourceRoot":"","sources":["../src/MCPChat.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAE3D,OAAO,KAAK,EAAyB,YAAY,EAAe,MAAM,SAAS,CAAC;AAEhF,wBAAgB,OAAO,CAAC,EACtB,aAAa,EACb,WAA6B,EAC7B,YAAiB,EACjB,SAAc,EACf,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"MCPChat.d.ts","sourceRoot":"","sources":["../src/MCPChat.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAE3D,OAAO,KAAK,EAAyB,YAAY,EAAe,MAAM,SAAS,CAAC;AAEhF,wBAAgB,OAAO,CAAC,EACtB,aAAa,EACb,WAA6B,EAC7B,YAAiB,EACjB,SAAc,EACf,EAAE,YAAY,qBAmVd"}
|
package/dist/MCPChat.js
CHANGED
|
@@ -9,6 +9,7 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
|
|
|
9
9
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
10
10
|
const messagesEndRef = useRef(null);
|
|
11
11
|
const thinkingEndRef = useRef(null);
|
|
12
|
+
const abortControllerRef = useRef(null);
|
|
12
13
|
// Merge custom styles with default CSS variables
|
|
13
14
|
const containerStyle = {
|
|
14
15
|
...customStyles,
|
|
@@ -19,10 +20,22 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
|
|
|
19
20
|
useEffect(() => {
|
|
20
21
|
scrollToBottom();
|
|
21
22
|
}, [messages]);
|
|
23
|
+
const cancelRequest = () => {
|
|
24
|
+
if (abortControllerRef.current) {
|
|
25
|
+
abortControllerRef.current.abort();
|
|
26
|
+
abortControllerRef.current = null;
|
|
27
|
+
setIsLoading(false);
|
|
28
|
+
setThinkingSteps([]);
|
|
29
|
+
// Remove any streaming message that was in progress
|
|
30
|
+
setMessages((prev) => prev.filter((m) => !m.isStreaming));
|
|
31
|
+
}
|
|
32
|
+
};
|
|
22
33
|
const handleSubmit = async (e) => {
|
|
23
34
|
e.preventDefault();
|
|
24
35
|
if (!input.trim() || isLoading)
|
|
25
36
|
return;
|
|
37
|
+
// Set loading state immediately to show cancel button
|
|
38
|
+
setIsLoading(true);
|
|
26
39
|
const userMessage = {
|
|
27
40
|
role: "user",
|
|
28
41
|
content: input.trim(),
|
|
@@ -30,7 +43,6 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
|
|
|
30
43
|
};
|
|
31
44
|
setMessages((prev) => [...prev, userMessage]);
|
|
32
45
|
setInput("");
|
|
33
|
-
setIsLoading(true);
|
|
34
46
|
setThinkingSteps([]);
|
|
35
47
|
// Add initial thinking step
|
|
36
48
|
let thinkingStepCounter = 0;
|
|
@@ -44,7 +56,9 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
|
|
|
44
56
|
thinkingEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
45
57
|
}, 50);
|
|
46
58
|
};
|
|
47
|
-
|
|
59
|
+
// Create abort controller for this request
|
|
60
|
+
const abortController = new AbortController();
|
|
61
|
+
abortControllerRef.current = abortController;
|
|
48
62
|
try {
|
|
49
63
|
// Call your API route that communicates with MCP
|
|
50
64
|
const response = await fetch(apiEndpoint, {
|
|
@@ -54,6 +68,7 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
|
|
|
54
68
|
message: userMessage.content,
|
|
55
69
|
context: companyNumber ? { company_number: companyNumber } : undefined,
|
|
56
70
|
}),
|
|
71
|
+
signal: abortController.signal,
|
|
57
72
|
});
|
|
58
73
|
if (!response.ok) {
|
|
59
74
|
throw new Error("Failed to get response");
|
|
@@ -135,6 +150,11 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
|
|
|
135
150
|
}
|
|
136
151
|
}
|
|
137
152
|
catch (error) {
|
|
153
|
+
// Don't show error message if request was cancelled
|
|
154
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
155
|
+
console.log("Request was cancelled");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
138
158
|
console.error("Error:", error);
|
|
139
159
|
const errorMessage = {
|
|
140
160
|
role: "assistant",
|
|
@@ -148,6 +168,8 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
|
|
|
148
168
|
});
|
|
149
169
|
}
|
|
150
170
|
finally {
|
|
171
|
+
// Clean up abort controller
|
|
172
|
+
abortControllerRef.current = null;
|
|
151
173
|
setIsLoading(false);
|
|
152
174
|
// Clear thinking steps after a brief delay
|
|
153
175
|
setTimeout(() => setThinkingSteps([]), 2000);
|
|
@@ -184,11 +206,9 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
|
|
|
184
206
|
"Currently viewing: ",
|
|
185
207
|
React.createElement("strong", null, companyNumber))),
|
|
186
208
|
React.createElement("p", { className: "mcp-chat-welcome-subtitle" }, "Try asking:"),
|
|
187
|
-
React.createElement("ul", { className: "mcp-chat-suggestions" },
|
|
188
|
-
React.createElement("li", null, "\
|
|
189
|
-
|
|
190
|
-
React.createElement("li", null, "\u201CGet company info\u201D"),
|
|
191
|
-
React.createElement("li", null, "\u201CShow me financial data from 2020\u201D")))))),
|
|
209
|
+
React.createElement("ul", { className: "mcp-chat-suggestions" }, companyNumber && (React.createElement(React.Fragment, null,
|
|
210
|
+
React.createElement("li", null, "\u201CGet company info\u201D"),
|
|
211
|
+
React.createElement("li", null, "\u201CShow me financial data from 2020\u201D")))))),
|
|
192
212
|
messages.map((msg, idx) => (React.createElement("div", { key: idx, className: `mcp-chat-message ${msg.role === "user" ? "mcp-chat-message-user" : "mcp-chat-message-assistant"}` },
|
|
193
213
|
React.createElement("div", { className: "mcp-chat-message-bubble" },
|
|
194
214
|
msg.role === "assistant" ? (React.createElement("div", { className: "mcp-chat-message-content markdown-content" },
|
|
@@ -203,5 +223,5 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
|
|
|
203
223
|
React.createElement("div", { ref: messagesEndRef })),
|
|
204
224
|
React.createElement("form", { onSubmit: handleSubmit, className: "mcp-chat-input-form" },
|
|
205
225
|
React.createElement("input", { type: "text", value: input, onChange: (e) => setInput(e.target.value), placeholder: "Ask a question...", className: "mcp-chat-input", disabled: isLoading }),
|
|
206
|
-
React.createElement("button", { type: "submit", className: "mcp-chat-button mcp-chat-button-primary", disabled:
|
|
226
|
+
isLoading ? (React.createElement("button", { type: "button", onClick: cancelRequest, className: "mcp-chat-button mcp-chat-button-secondary" }, "Cancel")) : (React.createElement("button", { type: "submit", className: "mcp-chat-button mcp-chat-button-primary", disabled: !input.trim() }, "Send")))));
|
|
207
227
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-helpers.d.ts","sourceRoot":"","sources":["../src/api-helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,sBAAsB,IACnD,SAAS,OAAO,
|
|
1
|
+
{"version":3,"file":"api-helpers.d.ts","sourceRoot":"","sources":["../src/api-helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,sBAAsB,IACnD,SAAS,OAAO,uBAgG/B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,KACrB,SAAS,OAAO,uBAU/B;AAED;;GAEG;AACH,wBAAsB,iBAAiB,kBAKtC"}
|
package/dist/api-helpers.js
CHANGED
|
@@ -21,22 +21,43 @@ export function createMCPChatHandler(config) {
|
|
|
21
21
|
await client.connect();
|
|
22
22
|
clients.set(sessionId, client);
|
|
23
23
|
}
|
|
24
|
+
// Create an AbortController to handle client disconnection
|
|
25
|
+
const abortController = new AbortController();
|
|
24
26
|
// Create a streaming response
|
|
25
27
|
const stream = new ReadableStream({
|
|
26
28
|
async start(controller) {
|
|
27
29
|
const encoder = new TextEncoder();
|
|
28
30
|
const sendEvent = (type, data) => {
|
|
29
|
-
|
|
31
|
+
// Check if client disconnected before sending more data
|
|
32
|
+
if (abortController.signal.aborted) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type, ...data })}\n\n`));
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
// Client disconnected, abort processing
|
|
40
|
+
abortController.abort();
|
|
41
|
+
}
|
|
30
42
|
};
|
|
31
43
|
try {
|
|
32
44
|
sendEvent("thinking", { message: "🤔 Analyzing your question..." });
|
|
33
|
-
// Process the query with thinking callback
|
|
45
|
+
// Process the query with thinking callback and abort signal
|
|
34
46
|
const response = await client.processQuery(context ? `${message}\nContext: ${JSON.stringify(context)}` : message, (thinkingMessage) => {
|
|
35
47
|
sendEvent("thinking", { message: thinkingMessage });
|
|
36
|
-
}
|
|
48
|
+
}, abortController.signal // Pass abort signal to enable cancellation
|
|
49
|
+
);
|
|
50
|
+
// Check if aborted before streaming response
|
|
51
|
+
if (abortController.signal.aborted) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
37
54
|
// Stream the response in chunks
|
|
38
55
|
const chunkSize = 10;
|
|
39
56
|
for (let i = 0; i < response.length; i += chunkSize) {
|
|
57
|
+
// Check for cancellation between chunks
|
|
58
|
+
if (abortController.signal.aborted) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
40
61
|
const chunk = response.slice(i, i + chunkSize);
|
|
41
62
|
sendEvent("content", { chunk });
|
|
42
63
|
// Small delay for better streaming effect
|
|
@@ -45,14 +66,22 @@ export function createMCPChatHandler(config) {
|
|
|
45
66
|
sendEvent("done", {});
|
|
46
67
|
}
|
|
47
68
|
catch (error) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
69
|
+
// Don't send error if request was aborted
|
|
70
|
+
if (!abortController.signal.aborted) {
|
|
71
|
+
sendEvent("error", {
|
|
72
|
+
message: error instanceof Error ? error.message : "An error occurred",
|
|
73
|
+
});
|
|
74
|
+
}
|
|
51
75
|
}
|
|
52
76
|
finally {
|
|
53
77
|
controller.close();
|
|
54
78
|
}
|
|
55
79
|
},
|
|
80
|
+
cancel() {
|
|
81
|
+
// This is called when client disconnects/cancels
|
|
82
|
+
console.log("Client disconnected, aborting server processing");
|
|
83
|
+
abortController.abort();
|
|
84
|
+
}
|
|
56
85
|
});
|
|
57
86
|
return new Response(stream, {
|
|
58
87
|
headers: {
|
package/dist/openai-client.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export declare class MCPClientOpenAI {
|
|
|
19
19
|
constructor(config: MCPClientConfig);
|
|
20
20
|
private compactConversation;
|
|
21
21
|
connect(): Promise<void>;
|
|
22
|
-
processQuery(query: string, onThinking?: (message: string) => void): Promise<string>;
|
|
22
|
+
processQuery(query: string, onThinking?: (message: string) => void, abortSignal?: AbortSignal): Promise<string>;
|
|
23
23
|
clearHistory(): void;
|
|
24
24
|
cleanup(): Promise<void>;
|
|
25
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-client.d.ts","sourceRoot":"","sources":["../src/openai-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,MAAM,CAA4B;gBAE9B,MAAM,EAAE,eAAe;
|
|
1
|
+
{"version":3,"file":"openai-client.d.ts","sourceRoot":"","sources":["../src/openai-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,MAAM,CAA4B;gBAE9B,MAAM,EAAE,eAAe;YAwErB,mBAAmB;IAoB3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAoMrH,YAAY,IAAI,IAAI;IAMd,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B"}
|
package/dist/openai-client.js
CHANGED
|
@@ -33,6 +33,43 @@ export class MCPClientOpenAI {
|
|
|
33
33
|
}, {
|
|
34
34
|
capabilities: {},
|
|
35
35
|
});
|
|
36
|
+
// Initialize conversation with system message
|
|
37
|
+
this.conversationHistory = [
|
|
38
|
+
{
|
|
39
|
+
type: "message",
|
|
40
|
+
role: "system",
|
|
41
|
+
content: [
|
|
42
|
+
{
|
|
43
|
+
type: "input_text",
|
|
44
|
+
text: `You are a helpful assistant with access to Companies House data through specialized tools.
|
|
45
|
+
|
|
46
|
+
CRITICAL CONTEXT AWARENESS RULES:
|
|
47
|
+
1. Carefully track ALL entities you mention in your responses (company numbers, names, people, dates, etc.)
|
|
48
|
+
2. When the user refers to "that company," "the person," "those results," or uses similar references, ALWAYS look back at what you just discussed in the immediately preceding messages
|
|
49
|
+
3. If you mentioned specific company numbers, names, or other identifiers, remember them for follow-up questions
|
|
50
|
+
4. Before saying "I don't have a record of X," review your recent responses to check if you did mention it
|
|
51
|
+
5. Maintain awareness of the conversation flow - if you just provided information about something, the user's next question likely refers to it
|
|
52
|
+
|
|
53
|
+
RESPONSE FORMATTING RULES:
|
|
54
|
+
- NEVER show raw JSON data to users unless they explicitly ask for "JSON", "raw data", or similar
|
|
55
|
+
- Always present data in natural, human-readable format using clear sentences and bullet points
|
|
56
|
+
- Convert dates to readable format (e.g., "15 March 2023" instead of "2023-03-15")
|
|
57
|
+
- Format addresses as natural text, not as structured fields
|
|
58
|
+
- Present lists of items as bulleted lists or comma-separated values
|
|
59
|
+
- Use tables/structured format only when it significantly improves readability
|
|
60
|
+
- When showing company officers or PSCs, write like: "John Smith (born March 1975, British nationality)" rather than showing field names
|
|
61
|
+
- Only include the most relevant information - don't dump all available fields
|
|
62
|
+
|
|
63
|
+
When responding:
|
|
64
|
+
- Be concise and direct
|
|
65
|
+
- Use tools to fetch accurate, up-to-date Companies House data
|
|
66
|
+
- Track key identifiers (company numbers, PSC names, etc.) across the conversation
|
|
67
|
+
- If unclear what the user is referring to, check your previous response first before asking for clarification
|
|
68
|
+
- Never expose internal implementation details like "MCP Server" or tool names to users`,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
];
|
|
36
73
|
}
|
|
37
74
|
async compactConversation() {
|
|
38
75
|
try {
|
|
@@ -56,7 +93,11 @@ export class MCPClientOpenAI {
|
|
|
56
93
|
async connect() {
|
|
57
94
|
await this.client.connect(this.transport);
|
|
58
95
|
}
|
|
59
|
-
async processQuery(query, onThinking) {
|
|
96
|
+
async processQuery(query, onThinking, abortSignal) {
|
|
97
|
+
// Check for cancellation at start
|
|
98
|
+
if (abortSignal?.aborted) {
|
|
99
|
+
throw new Error("Request was cancelled");
|
|
100
|
+
}
|
|
60
101
|
// Check if we should compact
|
|
61
102
|
const shouldCompact = this.conversationHistory.length >= 40 &&
|
|
62
103
|
(Date.now() - this.lastCompaction > 10 * 60 * 1000);
|
|
@@ -91,6 +132,10 @@ export class MCPClientOpenAI {
|
|
|
91
132
|
let outOfToolCalls = false;
|
|
92
133
|
while (loopCount < maxLoops) {
|
|
93
134
|
loopCount++;
|
|
135
|
+
// Check for cancellation before each API call
|
|
136
|
+
if (abortSignal?.aborted) {
|
|
137
|
+
throw new Error("Request was cancelled");
|
|
138
|
+
}
|
|
94
139
|
// Call OpenAI Responses API with error handling
|
|
95
140
|
let response;
|
|
96
141
|
try {
|
|
@@ -140,6 +185,10 @@ export class MCPClientOpenAI {
|
|
|
140
185
|
if (functionCalls.length > 0) {
|
|
141
186
|
this.conversationHistory.push(...output);
|
|
142
187
|
for (const functionCall of functionCalls) {
|
|
188
|
+
// Check for cancellation before each tool call
|
|
189
|
+
if (abortSignal?.aborted) {
|
|
190
|
+
throw new Error("Request was cancelled");
|
|
191
|
+
}
|
|
143
192
|
const functionName = functionCall.name;
|
|
144
193
|
const functionArgs = typeof functionCall.arguments === 'string'
|
|
145
194
|
? JSON.parse(functionCall.arguments)
|
|
@@ -217,7 +266,9 @@ export class MCPClientOpenAI {
|
|
|
217
266
|
return finalResponse;
|
|
218
267
|
}
|
|
219
268
|
clearHistory() {
|
|
220
|
-
|
|
269
|
+
// Keep the system message (first item) when clearing history
|
|
270
|
+
const systemMessage = this.conversationHistory[0];
|
|
271
|
+
this.conversationHistory = systemMessage ? [systemMessage] : [];
|
|
221
272
|
}
|
|
222
273
|
async cleanup() {
|
|
223
274
|
await this.client.close();
|