@nqminds/mcp-client 1.0.5 → 1.0.8

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/dist/MCPChat.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import React from "react";
2
2
  import type { MCPChatProps } from "./types";
3
- export declare function MCPChat({ companyNumber, apiEndpoint, customStyles, className }: MCPChatProps): React.JSX.Element;
3
+ export declare function MCPChat({ companyNumber, apiEndpoint, customStyles, className, }: MCPChatProps): React.JSX.Element;
4
4
  //# sourceMappingURL=MCPChat.d.ts.map
@@ -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,qBAoVd"}
1
+ {"version":3,"file":"MCPChat.d.ts","sourceRoot":"","sources":["../src/MCPChat.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAGxE,OAAO,KAAK,EAAyB,YAAY,EAAe,MAAM,SAAS,CAAC;AA+ChF,wBAAgB,OAAO,CAAC,EACtB,aAAa,EACb,WAA6B,EAC7B,YAAiB,EACjB,SAAc,GACf,EAAE,YAAY,qBAgfd"}
package/dist/MCPChat.js CHANGED
@@ -1,66 +1,107 @@
1
1
  "use client";
2
- import React, { useState, useRef, useEffect } from "react";
2
+ import React, { useState, useRef, useEffect, useCallback } from "react";
3
3
  import ReactMarkdown from "react-markdown";
4
- export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customStyles = {}, className = "" }) {
4
+ import remarkGfm from "remark-gfm";
5
+ const DEFAULT_ACTIONS = [
6
+ {
7
+ label: "Company overview",
8
+ icon: "🏢",
9
+ prompt: "Please provide a comprehensive company overview. Include key business information, company structure, size, registered details, and any notable financial highlights.",
10
+ },
11
+ {
12
+ label: "Competitor analysis",
13
+ icon: "⚔️",
14
+ prompt: "Please perform a competitor analysis. Identify the main competitors in this sector, assess market positioning, and highlight competitive advantages or disadvantages.",
15
+ },
16
+ {
17
+ label: "Risk analysis",
18
+ icon: "⚠️",
19
+ prompt: "Please perform a thorough risk analysis. Identify key business risks, financial risks, operational risks, and any concerning patterns in the data.",
20
+ },
21
+ {
22
+ label: "Online sentiment",
23
+ icon: "🌐",
24
+ prompt: "Please assess the online sentiment and public perception. Look for relevant news coverage, reviews, reputational issues, and overall public standing.",
25
+ },
26
+ {
27
+ label: "SWOT analysis",
28
+ icon: "📊",
29
+ prompt: "Please perform a full SWOT analysis. Cover Strengths, Weaknesses, Opportunities, and Threats in detail.",
30
+ },
31
+ {
32
+ label: "Research key people",
33
+ icon: "👤",
34
+ prompt: "Please research the key people associated with this company. Include directors, persons of significant control, major shareholders, and relevant background information.",
35
+ },
36
+ ];
37
+ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customStyles = {}, className = "", }) {
5
38
  const [messages, setMessages] = useState([]);
6
39
  const [input, setInput] = useState("");
7
40
  const [isLoading, setIsLoading] = useState(false);
8
41
  const [thinkingSteps, setThinkingSteps] = useState([]);
9
- const [isExpanded, setIsExpanded] = useState(false);
42
+ const [isOpen, setIsOpen] = useState(false);
43
+ const [preparingAction, setPreparingAction] = useState(null);
44
+ const [directPromptOpen, setDirectPromptOpen] = useState(false);
45
+ const [directPromptText, setDirectPromptText] = useState("");
46
+ const [theme, setTheme] = useState(() => {
47
+ if (typeof window !== "undefined") {
48
+ return localStorage.getItem("mcp-chat-theme") ?? "light";
49
+ }
50
+ return "light";
51
+ });
10
52
  const messagesEndRef = useRef(null);
11
53
  const thinkingEndRef = useRef(null);
54
+ const inputRef = useRef(null);
55
+ const directPromptRef = useRef(null);
12
56
  const abortControllerRef = useRef(null);
13
- // Merge custom styles with default CSS variables
14
- const containerStyle = {
15
- ...customStyles,
16
- };
17
57
  const scrollToBottom = () => {
18
58
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
19
59
  };
20
60
  useEffect(() => {
21
61
  scrollToBottom();
22
62
  }, [messages]);
63
+ // Focus input when panel opens in "chat now" mode
64
+ useEffect(() => {
65
+ if (isOpen) {
66
+ setTimeout(() => inputRef.current?.focus(), 150);
67
+ }
68
+ }, [isOpen]);
23
69
  const cancelRequest = () => {
24
70
  if (abortControllerRef.current) {
25
71
  abortControllerRef.current.abort();
26
72
  abortControllerRef.current = null;
27
73
  setIsLoading(false);
28
74
  setThinkingSteps([]);
29
- // Remove any streaming message that was in progress
75
+ setPreparingAction(null);
30
76
  setMessages((prev) => prev.filter((m) => !m.isStreaming));
31
77
  }
32
78
  };
33
- const handleSubmit = async (e) => {
34
- e.preventDefault();
35
- if (!input.trim() || isLoading)
79
+ const sendMessage = useCallback(async (text, hidden = false) => {
80
+ if (!text.trim() || isLoading)
36
81
  return;
37
- // Set loading state immediately to show cancel button
38
82
  setIsLoading(true);
39
83
  const userMessage = {
40
84
  role: "user",
41
- content: input.trim(),
85
+ content: text.trim(),
42
86
  timestamp: new Date(),
87
+ hidden,
43
88
  };
44
89
  setMessages((prev) => [...prev, userMessage]);
45
90
  setInput("");
46
91
  setThinkingSteps([]);
47
- // Add initial thinking step
48
92
  let thinkingStepCounter = 0;
49
93
  const addThinkingStep = (message) => {
50
94
  setThinkingSteps((prev) => [
51
95
  ...prev,
52
96
  { id: `${Date.now()}-${thinkingStepCounter++}`, message, timestamp: new Date() },
53
97
  ]);
54
- // Auto-scroll thinking window
55
98
  setTimeout(() => {
56
99
  thinkingEndRef.current?.scrollIntoView({ behavior: "smooth" });
57
100
  }, 50);
58
101
  };
59
- // Create abort controller for this request
60
102
  const abortController = new AbortController();
61
103
  abortControllerRef.current = abortController;
62
104
  try {
63
- // Call your API route that communicates with MCP
64
105
  const response = await fetch(apiEndpoint, {
65
106
  method: "POST",
66
107
  headers: { "Content-Type": "application/json" },
@@ -70,15 +111,12 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
70
111
  }),
71
112
  signal: abortController.signal,
72
113
  });
73
- if (!response.ok) {
114
+ if (!response.ok)
74
115
  throw new Error("Failed to get response");
75
- }
76
116
  const reader = response.body?.getReader();
77
117
  const decoder = new TextDecoder();
78
- if (!reader) {
118
+ if (!reader)
79
119
  throw new Error("No response body");
80
- }
81
- // Create a streaming message
82
120
  const streamingMessage = {
83
121
  role: "assistant",
84
122
  content: "",
@@ -98,21 +136,18 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
98
136
  for (const line of lines) {
99
137
  if (line.startsWith("data: ")) {
100
138
  const data = line.slice(6);
101
- if (data === "[DONE]") {
139
+ if (data === "[DONE]")
102
140
  continue;
103
- }
104
141
  try {
105
142
  const parsed = JSON.parse(data);
106
143
  if (parsed.type === "thinking") {
107
144
  addThinkingStep(parsed.message || "Processing...");
108
145
  }
109
146
  else if (parsed.type === "content") {
110
- // Update the streaming message with new content
111
147
  setMessages((prev) => {
112
148
  const updated = [...prev];
113
149
  const lastIndex = updated.length - 1;
114
150
  if (lastIndex >= 0 && updated[lastIndex].isStreaming) {
115
- // Create a new message object instead of mutating
116
151
  updated[lastIndex] = {
117
152
  ...updated[lastIndex],
118
153
  content: updated[lastIndex].content + (parsed.chunk || ""),
@@ -122,22 +157,16 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
122
157
  });
123
158
  }
124
159
  else if (parsed.type === "done") {
125
- // Mark streaming as complete
126
160
  setMessages((prev) => {
127
161
  const updated = [...prev];
128
162
  const lastIndex = updated.length - 1;
129
163
  if (lastIndex >= 0 && updated[lastIndex].isStreaming) {
130
- // Create a new message object instead of mutating
131
- updated[lastIndex] = {
132
- ...updated[lastIndex],
133
- isStreaming: false,
134
- };
164
+ updated[lastIndex] = { ...updated[lastIndex], isStreaming: false };
135
165
  }
136
166
  return updated;
137
167
  });
138
168
  }
139
169
  else if (parsed.type === "error") {
140
- // Handle error from stream
141
170
  throw new Error(parsed.message || "Stream error");
142
171
  }
143
172
  }
@@ -150,34 +179,70 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
150
179
  }
151
180
  }
152
181
  catch (error) {
153
- // Don't show error message if request was cancelled
154
- if (error instanceof Error && error.name === 'AbortError') {
182
+ if (error instanceof Error && error.name === "AbortError") {
155
183
  console.log("Request was cancelled");
156
184
  return;
157
185
  }
158
186
  console.error("Error:", error);
159
- const errorMessage = {
160
- role: "assistant",
161
- content: "Sorry, I encountered an error. Please try again.",
162
- timestamp: new Date(),
163
- };
164
- setMessages((prev) => {
165
- // Remove streaming message if it exists
166
- const filtered = prev.filter((m) => !m.isStreaming);
167
- return [...filtered, errorMessage];
168
- });
187
+ setMessages((prev) => [
188
+ ...prev.filter((m) => !m.isStreaming),
189
+ {
190
+ role: "assistant",
191
+ content: "Sorry, I encountered an error. Please try again.",
192
+ timestamp: new Date(),
193
+ },
194
+ ]);
169
195
  }
170
196
  finally {
171
- // Clean up abort controller
172
197
  abortControllerRef.current = null;
173
198
  setIsLoading(false);
174
- // Clear thinking steps after a brief delay
199
+ setPreparingAction(null);
175
200
  setTimeout(() => setThinkingSteps([]), 2000);
176
201
  }
202
+ }, [apiEndpoint, companyNumber, isLoading]);
203
+ const handleSubmit = async (e) => {
204
+ e.preventDefault();
205
+ await sendMessage(input, false);
206
+ };
207
+ const handleActionButton = async (action) => {
208
+ const prompt = companyNumber
209
+ ? `${action.prompt}\n\nCompany number: ${companyNumber}`
210
+ : action.prompt;
211
+ setPreparingAction(action.label);
212
+ // messages will be added by sendMessage → switches view automatically
213
+ setTimeout(() => sendMessage(prompt, true), 50);
214
+ };
215
+ const handleChatNow = () => {
216
+ // Just focus the input — no prompt sent
217
+ setTimeout(() => inputRef.current?.focus(), 50);
218
+ };
219
+ const openDirectPrompt = (prefill = "") => {
220
+ setDirectPromptText(prefill);
221
+ setDirectPromptOpen(true);
222
+ setTimeout(() => {
223
+ if (directPromptRef.current) {
224
+ directPromptRef.current.focus();
225
+ directPromptRef.current.selectionStart = prefill.length;
226
+ directPromptRef.current.selectionEnd = prefill.length;
227
+ }
228
+ }, 80);
229
+ };
230
+ const sendDirectPrompt = async () => {
231
+ if (!directPromptText.trim())
232
+ return;
233
+ setDirectPromptOpen(false);
234
+ // Send as a visible message so you can see exactly what went to the agent
235
+ await sendMessage(directPromptText, false);
236
+ setDirectPromptText("");
237
+ };
238
+ const toggleTheme = () => {
239
+ const next = theme === "dark" ? "light" : "dark";
240
+ setTheme(next);
241
+ if (typeof window !== "undefined")
242
+ localStorage.setItem("mcp-chat-theme", next);
177
243
  };
178
244
  const clearChat = async () => {
179
245
  setMessages([]);
180
- // Also clear server-side conversation history
181
246
  try {
182
247
  await fetch(apiEndpoint, {
183
248
  method: "DELETE",
@@ -185,45 +250,83 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
185
250
  body: JSON.stringify({ sessionId: "default" }),
186
251
  });
187
252
  }
188
- catch (error) {
189
- console.error("Failed to clear server conversation:", error);
253
+ catch (err) {
254
+ console.error("Failed to clear server conversation:", err);
190
255
  }
191
256
  };
192
- if (!isExpanded) {
193
- return (React.createElement("div", { className: `mcp-chat-trigger ${className}`, style: containerStyle },
194
- React.createElement("button", { onClick: () => setIsExpanded(true), className: "mcp-chat-trigger-button" }, "\uD83D\uDCAC Ask AI Assistant")));
195
- }
196
- return (React.createElement("div", { className: `mcp-chat-container ${className}`, style: containerStyle },
197
- React.createElement("div", { className: "mcp-chat-header" },
198
- React.createElement("h3", { className: "mcp-chat-title" }, "AI Assistant"),
199
- React.createElement("div", { className: "mcp-chat-header-actions" },
200
- messages.length > 0 && (React.createElement("button", { onClick: clearChat, className: "mcp-chat-button mcp-chat-button-secondary" }, "Clear")),
201
- React.createElement("button", { onClick: () => setIsExpanded(false), className: "mcp-chat-button mcp-chat-button-secondary" }, "\u2715"))),
202
- React.createElement("div", { className: "mcp-chat-messages" },
203
- messages.length === 0 && (React.createElement("div", { className: "mcp-chat-welcome" },
204
- React.createElement("p", { className: "mcp-chat-welcome-title" }, "\uD83D\uDC4B Hi! I can help you with company information."),
205
- companyNumber && (React.createElement("p", { className: "mcp-chat-welcome-subtitle" },
206
- "Currently viewing: ",
207
- React.createElement("strong", null, companyNumber))),
208
- React.createElement("p", { className: "mcp-chat-welcome-subtitle" }, "Try asking:"),
209
- React.createElement("ul", { className: "mcp-chat-suggestions" },
210
- React.createElement("li", null, "\u201CSay hello\u201D"),
211
- companyNumber && (React.createElement(React.Fragment, null,
212
- React.createElement("li", null, "\u201CGet company info\u201D"),
213
- React.createElement("li", null, "\u201CShow me financial data from 2020\u201D")))))),
214
- messages.map((msg, idx) => (React.createElement("div", { key: idx, className: `mcp-chat-message ${msg.role === "user" ? "mcp-chat-message-user" : "mcp-chat-message-assistant"}` },
215
- React.createElement("div", { className: "mcp-chat-message-bubble" },
216
- msg.role === "assistant" ? (React.createElement("div", { className: "mcp-chat-message-content markdown-content" },
217
- React.createElement(ReactMarkdown, null, msg.content))) : (React.createElement("div", { className: "mcp-chat-message-content" }, msg.content)),
218
- React.createElement("div", { className: "mcp-chat-message-timestamp" }, msg.timestamp.toLocaleTimeString()))))),
219
- isLoading && thinkingSteps.length > 0 && (React.createElement("div", { className: "mcp-chat-message mcp-chat-message-assistant" },
220
- React.createElement("div", { className: "mcp-chat-thinking" },
221
- React.createElement("div", { className: "mcp-chat-thinking-title" }, "\uD83D\uDCAD Processing..."),
222
- React.createElement("div", { className: "mcp-chat-thinking-steps" },
223
- thinkingSteps.map((step) => (React.createElement("div", { key: step.id, className: "mcp-chat-thinking-step" }, step.message))),
224
- React.createElement("div", { ref: thinkingEndRef }))))),
225
- React.createElement("div", { ref: messagesEndRef })),
226
- React.createElement("form", { onSubmit: handleSubmit, className: "mcp-chat-input-form" },
227
- React.createElement("input", { type: "text", value: input, onChange: (e) => setInput(e.target.value), placeholder: "Ask a question...", className: "mcp-chat-input", disabled: isLoading }),
228
- 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")))));
257
+ const visibleMessages = messages.filter((m) => !m.hidden);
258
+ const showHome = visibleMessages.length === 0 && !isLoading;
259
+ return (React.createElement("div", { className: `mcp-root ${className}`, style: customStyles, "data-theme": theme },
260
+ !isOpen && (React.createElement("button", { className: "mcp-float-icon", onClick: () => setIsOpen(true), "aria-label": "Open AI assistant", title: "Open AI assistant" }, "\uD83E\uDD16")),
261
+ isOpen && (React.createElement("div", { className: "mcp-overlay" },
262
+ React.createElement("div", { className: "mcp-panel" },
263
+ React.createElement("div", { className: "mcp-chat-header" },
264
+ React.createElement("div", { className: "mcp-chat-header-left" },
265
+ React.createElement("span", { className: "mcp-chat-header-icon" }, "\uD83E\uDD16"),
266
+ React.createElement("h3", { className: "mcp-chat-title" }, "AI Assistant"),
267
+ companyNumber && (React.createElement("span", { className: "mcp-chat-header-company" }, companyNumber))),
268
+ React.createElement("div", { className: "mcp-chat-header-actions" },
269
+ React.createElement("button", { onClick: () => openDirectPrompt(), className: "mcp-chat-button mcp-btn-dev", title: "Open direct-prompt editor (dev tool)" }, "\uD83D\uDD27 Direct"),
270
+ React.createElement("button", { onClick: toggleTheme, className: "mcp-btn-theme", "aria-label": theme === "dark" ? "Switch to light mode" : "Switch to dark mode", title: theme === "dark" ? "Light mode" : "Dark mode" }, theme === "dark" ? "☀️" : "🌙"),
271
+ visibleMessages.length > 0 && (React.createElement("button", { onClick: clearChat, className: "mcp-chat-button mcp-chat-button-secondary", title: "Clear conversation" }, "Clear")),
272
+ React.createElement("button", { onClick: () => setIsOpen(false), className: "mcp-chat-button mcp-chat-button-secondary mcp-btn-icon", "aria-label": "Minimise", title: "Minimise" }, "\u25BC"))),
273
+ directPromptOpen && (React.createElement("div", { className: "mcp-direct-overlay" },
274
+ React.createElement("div", { className: "mcp-direct-panel" },
275
+ React.createElement("div", { className: "mcp-direct-header" },
276
+ React.createElement("span", { className: "mcp-direct-title" }, "\uD83D\uDD27 Direct Prompt"),
277
+ React.createElement("button", { className: "mcp-chat-button mcp-chat-button-secondary mcp-btn-icon", onClick: () => setDirectPromptOpen(false), title: "Close" }, "\u2715")),
278
+ React.createElement("div", { className: "mcp-direct-presets" },
279
+ React.createElement("span", { className: "mcp-direct-presets-label" }, "Load preset:"),
280
+ DEFAULT_ACTIONS.map((a) => (React.createElement("button", { key: a.label, className: "mcp-direct-preset-chip", onClick: () => openDirectPrompt(companyNumber
281
+ ? `${a.prompt}\n\nCompany number: ${companyNumber}`
282
+ : a.prompt), title: a.label },
283
+ a.icon,
284
+ " ",
285
+ a.label)))),
286
+ React.createElement("textarea", { ref: directPromptRef, className: "mcp-direct-textarea", value: directPromptText, onChange: (e) => setDirectPromptText(e.target.value), placeholder: "Type or paste any prompt here \u2014 it will be sent verbatim to the agent and shown in the chat\u2026", rows: 12, onKeyDown: (e) => {
287
+ // Ctrl/Cmd+Enter to send
288
+ if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
289
+ e.preventDefault();
290
+ sendDirectPrompt();
291
+ }
292
+ } }),
293
+ React.createElement("div", { className: "mcp-direct-footer" },
294
+ React.createElement("span", { className: "mcp-direct-hint" }, "Ctrl+Enter to send"),
295
+ React.createElement("div", { className: "mcp-direct-footer-actions" },
296
+ React.createElement("button", { className: "mcp-chat-button mcp-chat-button-secondary", onClick: () => setDirectPromptText("") }, "Clear"),
297
+ React.createElement("button", { className: "mcp-chat-button mcp-btn-dev", onClick: sendDirectPrompt, disabled: !directPromptText.trim() || isLoading }, "Send to agent \u2192")))))),
298
+ preparingAction && (React.createElement("div", { className: "mcp-preparing-banner" },
299
+ React.createElement("span", { className: "mcp-preparing-spinner" }),
300
+ "Preparing ",
301
+ React.createElement("strong", null, preparingAction),
302
+ " \u2014 this may take a moment\u2026")),
303
+ React.createElement("div", { className: "mcp-chat-messages" },
304
+ showHome && (React.createElement("div", { className: "mcp-home" },
305
+ React.createElement("p", { className: "mcp-home-title" }, "\uD83D\uDC4B What would you like to know?"),
306
+ companyNumber && (React.createElement("p", { className: "mcp-home-subtitle" },
307
+ "Company: ",
308
+ React.createElement("strong", null, companyNumber))),
309
+ React.createElement("div", { className: "mcp-action-grid" }, DEFAULT_ACTIONS.map((action) => (React.createElement("button", { key: action.label, className: "mcp-action-card", onClick: () => handleActionButton(action) },
310
+ React.createElement("span", { className: "mcp-action-card-icon" }, action.icon),
311
+ React.createElement("span", { className: "mcp-action-card-label" }, action.label))))),
312
+ React.createElement("button", { className: "mcp-chat-now-button", onClick: handleChatNow }, "\uD83D\uDCAC Or just chat\u2026"))),
313
+ visibleMessages.map((msg, idx) => (React.createElement("div", { key: idx, className: `mcp-chat-message ${msg.role === "user" ? "mcp-chat-message-user" : "mcp-chat-message-assistant"}` },
314
+ React.createElement("div", { className: "mcp-chat-message-bubble" },
315
+ msg.role === "assistant" ? (React.createElement("div", { className: "mcp-chat-message-content markdown-content" },
316
+ React.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm] }, msg.content))) : (React.createElement("div", { className: "mcp-chat-message-content" }, msg.content)),
317
+ React.createElement("div", { className: "mcp-chat-message-timestamp" }, msg.timestamp.toLocaleTimeString()))))),
318
+ isLoading && (React.createElement("div", { className: "mcp-chat-message mcp-chat-message-assistant" },
319
+ React.createElement("div", { className: "mcp-chat-thinking" },
320
+ React.createElement("div", { className: "mcp-chat-thinking-title" },
321
+ React.createElement("span", { className: "mcp-preparing-spinner" }),
322
+ thinkingSteps.length === 0
323
+ ? " Thinking…"
324
+ : " Processing…"),
325
+ thinkingSteps.length > 0 && (React.createElement("div", { className: "mcp-chat-thinking-steps" },
326
+ thinkingSteps.map((step) => (React.createElement("div", { key: step.id, className: "mcp-chat-thinking-step" }, step.message))),
327
+ React.createElement("div", { ref: thinkingEndRef })))))),
328
+ React.createElement("div", { ref: messagesEndRef })),
329
+ React.createElement("form", { onSubmit: handleSubmit, className: "mcp-chat-input-form" },
330
+ React.createElement("input", { ref: inputRef, type: "text", value: input, onChange: (e) => setInput(e.target.value), placeholder: "Ask a question\u2026", className: "mcp-chat-input", disabled: isLoading }),
331
+ 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"))))))));
229
332
  }
@@ -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;YAkCrB,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;IAId,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B"}
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;YA4ErB,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;IA+MrH,YAAY,IAAI,IAAI;IAMd,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B"}
@@ -33,6 +33,47 @@ 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
+ - Use rich Markdown formatting — the UI renders it fully (bold, italic, headings, tables, code blocks)
56
+ - Use **bold** for key facts, names, amounts, and important values
57
+ - Use ## and ### headings to organise longer responses into clear sections
58
+ - Use tables whenever comparing multiple entities or showing structured data (e.g. list of officers, financial figures across years, search results) — prefer tables over bullet lists for multi-field data
59
+ - Use bullet lists only for genuinely unordered or enumerable items (e.g. a list of risks, a list of SIC codes) — do NOT default to bullets for everything
60
+ - Convert dates to readable format (e.g., "15 March 2023" instead of "2023-03-15")
61
+ - Format addresses as natural inline text, not as structured fields
62
+ - When showing company officers or PSCs, use a table with columns like Name, Role, Nationality, DOB rather than a bullet per person
63
+ - When showing financial figures, use a table with Year / Metric / Value columns
64
+ - Only include the most relevant information — don't dump all available fields
65
+ - Avoid walls of bullet points; use prose sentences for narrative context and reserve lists/tables for structured data
66
+
67
+ When responding:
68
+ - Be concise and direct
69
+ - Use tools to fetch accurate, up-to-date Companies House data
70
+ - Track key identifiers (company numbers, PSC names, etc.) across the conversation
71
+ - If unclear what the user is referring to, check your previous response first before asking for clarification
72
+ - Never expose internal implementation details like "MCP Server" or tool names to users`,
73
+ },
74
+ ],
75
+ },
76
+ ];
36
77
  }
37
78
  async compactConversation() {
38
79
  try {
@@ -156,7 +197,20 @@ export class MCPClientOpenAI {
156
197
  const functionArgs = typeof functionCall.arguments === 'string'
157
198
  ? JSON.parse(functionCall.arguments)
158
199
  : functionCall.arguments;
159
- onThinking?.(`🔧 Using tool: ${functionName}`);
200
+ // Build a descriptive thinking message with key arguments
201
+ let toolDesc = functionName;
202
+ if (functionName === "fetch_webpage" && functionArgs.url) {
203
+ try {
204
+ toolDesc = `fetch_webpage → ${new URL(functionArgs.url).hostname}`;
205
+ }
206
+ catch {
207
+ toolDesc = `fetch_webpage → ${functionArgs.url}`;
208
+ }
209
+ }
210
+ else if (functionName === "web_search" && functionArgs.query) {
211
+ toolDesc = `web_search → "${functionArgs.query}"`;
212
+ }
213
+ onThinking?.(`🔧 ${toolDesc}`);
160
214
  try {
161
215
  // Execute the tool via MCP
162
216
  const result = await this.client.callTool({
@@ -229,7 +283,9 @@ export class MCPClientOpenAI {
229
283
  return finalResponse;
230
284
  }
231
285
  clearHistory() {
232
- this.conversationHistory = [];
286
+ // Keep the system message (first item) when clearing history
287
+ const systemMessage = this.conversationHistory[0];
288
+ this.conversationHistory = systemMessage ? [systemMessage] : [];
233
289
  }
234
290
  async cleanup() {
235
291
  await this.client.close();