@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.
- package/dist/MCPChat.d.ts.map +1 -1
- package/dist/MCPChat.js +4 -3
- package/dist/api-helpers.d.ts.map +1 -1
- package/dist/api-helpers.js +4 -4
- package/dist/openai-client.d.ts +12 -1
- package/dist/openai-client.d.ts.map +1 -1
- package/dist/openai-client.js +127 -43
- package/package.json +1 -1
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,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,
|
|
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
|
-
//
|
|
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,
|
|
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"}
|
package/dist/api-helpers.js
CHANGED
|
@@ -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;
|
package/dist/openai-client.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/openai-client.js
CHANGED
|
@@ -34,46 +34,8 @@ export class MCPClientOpenAI {
|
|
|
34
34
|
capabilities: {},
|
|
35
35
|
});
|
|
36
36
|
// Initialize conversation with system message
|
|
37
|
-
|
|
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
|
|
287
|
-
const
|
|
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() {
|