@nqminds/mcp-client 1.0.8 → 1.0.9

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.
@@ -1 +1 @@
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"}
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,qBAifd"}
package/dist/MCPChat.js CHANGED
@@ -76,7 +76,7 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
76
76
  setMessages((prev) => prev.filter((m) => !m.isStreaming));
77
77
  }
78
78
  };
79
- const sendMessage = useCallback(async (text, hidden = false) => {
79
+ const sendMessage = useCallback(async (text, hidden = false, bypass = false) => {
80
80
  if (!text.trim() || isLoading)
81
81
  return;
82
82
  setIsLoading(true);
@@ -108,6 +108,7 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
108
108
  body: JSON.stringify({
109
109
  message: userMessage.content,
110
110
  context: companyNumber ? { company_number: companyNumber } : undefined,
111
+ bypassSystemPrompt: bypass || undefined,
111
112
  }),
112
113
  signal: abortController.signal,
113
114
  });
@@ -231,8 +232,8 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
231
232
  if (!directPromptText.trim())
232
233
  return;
233
234
  setDirectPromptOpen(false);
234
- // Send as a visible message so you can see exactly what went to the agent
235
- await sendMessage(directPromptText, false);
235
+ // bypass=true: skips system prompt, sends raw to the model
236
+ await sendMessage(directPromptText, false, true);
236
237
  setDirectPromptText("");
237
238
  };
238
239
  const toggleTheme = () => {
@@ -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,uBAgG/B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,KACrB,SAAS,OAAO,uBAU/B;AAED;;GAEG;AACH,wBAAsB,iBAAiB,kBAKtC"}
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,uBAiG/B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,KACrB,SAAS,OAAO,uBAU/B;AAED;;GAEG;AACH,wBAAsB,iBAAiB,kBAKtC"}
@@ -9,7 +9,7 @@ const clients = new Map();
9
9
  */
10
10
  export function createMCPChatHandler(config) {
11
11
  return async (request) => {
12
- const { message, context, sessionId = "default" } = await request.json();
12
+ const { message, context, sessionId = "default", bypassSystemPrompt = false } = await request.json();
13
13
  // Get or create client for this session
14
14
  let client = clients.get(sessionId);
15
15
  if (!client) {
@@ -41,12 +41,12 @@ export function createMCPChatHandler(config) {
41
41
  }
42
42
  };
43
43
  try {
44
- sendEvent("thinking", { message: "🤔 Analyzing your question..." });
44
+ sendEvent("thinking", { message: bypassSystemPrompt ? "🔧 Sending direct prompt (no system context)…" : "🤔 Analyzing your question..." });
45
45
  // Process the query with thinking callback and abort signal
46
46
  const response = await client.processQuery(context ? `${message}\nContext: ${JSON.stringify(context)}` : message, (thinkingMessage) => {
47
47
  sendEvent("thinking", { message: thinkingMessage });
48
- }, abortController.signal // Pass abort signal to enable cancellation
49
- );
48
+ }, abortController.signal, // Pass abort signal to enable cancellation
49
+ bypassSystemPrompt);
50
50
  // Check if aborted before streaming response
51
51
  if (abortController.signal.aborted) {
52
52
  return;
@@ -18,8 +18,19 @@ export declare class MCPClientOpenAI {
18
18
  private config;
19
19
  constructor(config: MCPClientConfig);
20
20
  private compactConversation;
21
+ /**
22
+ * Fetches the system prompt from the MCP server's registered "system-prompt" prompt
23
+ * and prepends it to conversationHistory. Cached — only runs once per session.
24
+ * Direct Prompt (bypass mode) skips this entirely.
25
+ */
26
+ private ensureSystemPrompt;
21
27
  connect(): Promise<void>;
22
- processQuery(query: string, onThinking?: (message: string) => void, abortSignal?: AbortSignal): Promise<string>;
28
+ processQuery(query: string, onThinking?: (message: string) => void, abortSignal?: AbortSignal, bypassSystemPrompt?: boolean): Promise<string>;
29
+ /**
30
+ * Sends a raw query directly to the model — no system prompt, no conversation history.
31
+ * Used by the Direct Prompt dev tool to test prompts verbatim.
32
+ */
33
+ private processRawQuery;
23
34
  clearHistory(): void;
24
35
  cleanup(): Promise<void>;
25
36
  }
@@ -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;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"}
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;YAsCrB,mBAAmB;IAoBjC;;;;OAIG;YACW,kBAAkB;IA4B1B,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,EAAE,kBAAkB,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAuNjJ;;;OAGG;YACW,eAAe;IA4E7B,YAAY,IAAI,IAAI;IAOd,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B"}
@@ -34,46 +34,8 @@ export class MCPClientOpenAI {
34
34
  capabilities: {},
35
35
  });
36
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
- ];
37
+ // System prompt is fetched from the MCP server on first use (see ensureSystemPrompt)
38
+ this.conversationHistory = [];
77
39
  }
78
40
  async compactConversation() {
79
41
  try {
@@ -94,14 +56,56 @@ When responding:
94
56
  }
95
57
  }
96
58
  }
59
+ /**
60
+ * Fetches the system prompt from the MCP server's registered "system-prompt" prompt
61
+ * and prepends it to conversationHistory. Cached — only runs once per session.
62
+ * Direct Prompt (bypass mode) skips this entirely.
63
+ */
64
+ async ensureSystemPrompt() {
65
+ // Already loaded if history starts with a system message
66
+ if (this.conversationHistory[0]?.role === "system")
67
+ return;
68
+ try {
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+ const result = await this.client.getPrompt({ name: "system-prompt" });
71
+ const parts = [];
72
+ for (const msg of result.messages) {
73
+ const c = msg.content;
74
+ if (typeof c === "string")
75
+ parts.push(c);
76
+ else if (c?.text)
77
+ parts.push(c.text);
78
+ }
79
+ const text = parts.join("\n\n");
80
+ if (text) {
81
+ this.conversationHistory = [
82
+ {
83
+ type: "message",
84
+ role: "system",
85
+ content: [{ type: "input_text", text }],
86
+ },
87
+ ...this.conversationHistory,
88
+ ];
89
+ }
90
+ }
91
+ catch (error) {
92
+ console.error("[MCPClient] Failed to fetch system prompt from MCP server:", error);
93
+ }
94
+ }
97
95
  async connect() {
98
96
  await this.client.connect(this.transport);
99
97
  }
100
- async processQuery(query, onThinking, abortSignal) {
98
+ async processQuery(query, onThinking, abortSignal, bypassSystemPrompt = false) {
101
99
  // Check for cancellation at start
102
100
  if (abortSignal?.aborted) {
103
101
  throw new Error("Request was cancelled");
104
102
  }
103
+ // Bypass mode: send the raw prompt directly without system message or conversation history
104
+ if (bypassSystemPrompt) {
105
+ return this.processRawQuery(query, onThinking, abortSignal);
106
+ }
107
+ // Load system prompt from MCP server (no-op after first call)
108
+ await this.ensureSystemPrompt();
105
109
  // Check if we should compact
106
110
  const shouldCompact = this.conversationHistory.length >= 40 &&
107
111
  (Date.now() - this.lastCompaction > 10 * 60 * 1000);
@@ -282,9 +286,89 @@ When responding:
282
286
  }
283
287
  return finalResponse;
284
288
  }
289
+ /**
290
+ * Sends a raw query directly to the model — no system prompt, no conversation history.
291
+ * Used by the Direct Prompt dev tool to test prompts verbatim.
292
+ */
293
+ async processRawQuery(query, onThinking, abortSignal) {
294
+ const toolsResponse = await this.client.listTools();
295
+ const tools = toolsResponse.tools.map((tool) => ({
296
+ type: "function",
297
+ name: tool.name,
298
+ description: tool.description || "",
299
+ parameters: tool.inputSchema,
300
+ strict: false,
301
+ }));
302
+ // Isolated history — just this message, no system prompt
303
+ const isolatedHistory = [
304
+ {
305
+ type: "message",
306
+ role: "user",
307
+ content: [{ type: "input_text", text: query }],
308
+ },
309
+ ];
310
+ let loopCount = 0;
311
+ const maxLoops = 15;
312
+ let finalResponse = "";
313
+ while (loopCount < maxLoops) {
314
+ loopCount++;
315
+ if (abortSignal?.aborted)
316
+ throw new Error("Request was cancelled");
317
+ const response = await this.openai.responses.create({
318
+ model: this.config.openaiModel,
319
+ input: isolatedHistory,
320
+ tools,
321
+ });
322
+ const output = response.output;
323
+ const functionCalls = output.filter((item) => item.type === "function_call");
324
+ if (functionCalls.length > 0) {
325
+ isolatedHistory.push(...output);
326
+ for (const functionCall of functionCalls) {
327
+ if (abortSignal?.aborted)
328
+ throw new Error("Request was cancelled");
329
+ const functionName = functionCall.name;
330
+ const functionArgs = typeof functionCall.arguments === "string"
331
+ ? JSON.parse(functionCall.arguments)
332
+ : functionCall.arguments;
333
+ let toolDesc = functionName;
334
+ if (functionName === "fetch_webpage" && functionArgs.url) {
335
+ try {
336
+ toolDesc = `fetch_webpage → ${new URL(functionArgs.url).hostname}`;
337
+ }
338
+ catch {
339
+ toolDesc = `fetch_webpage → ${functionArgs.url}`;
340
+ }
341
+ }
342
+ else if (functionName === "web_search" && functionArgs.query) {
343
+ toolDesc = `web_search → "${functionArgs.query}"`;
344
+ }
345
+ onThinking?.(`🔧 ${toolDesc}`);
346
+ try {
347
+ const result = await this.client.callTool({ name: functionName, arguments: functionArgs });
348
+ isolatedHistory.push({ type: "function_call_output", call_id: functionCall.call_id, output: JSON.stringify(result.content) });
349
+ }
350
+ catch (error) {
351
+ isolatedHistory.push({ type: "function_call_output", call_id: functionCall.call_id, output: `Error: ${error instanceof Error ? error.message : String(error)}` });
352
+ }
353
+ }
354
+ continue;
355
+ }
356
+ for (const item of output) {
357
+ if (item.type === "message" && item.role === "assistant") {
358
+ for (const contentItem of item.content) {
359
+ if (contentItem.type === "output_text")
360
+ finalResponse += contentItem.text;
361
+ }
362
+ }
363
+ }
364
+ break;
365
+ }
366
+ return finalResponse;
367
+ }
285
368
  clearHistory() {
286
- // Keep the system message (first item) when clearing history
287
- const systemMessage = this.conversationHistory[0];
369
+ // Keep system message only if it genuinely is a system role message
370
+ const first = this.conversationHistory[0];
371
+ const systemMessage = first?.role === "system" ? this.conversationHistory[0] : undefined;
288
372
  this.conversationHistory = systemMessage ? [systemMessage] : [];
289
373
  }
290
374
  async cleanup() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nqminds/mcp-client",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Reusable MCP client component with AI chat interface",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",