egw-chat-cli-http.jsā¢12.4 kB
#!/usr/bin/env node
const readline = require('readline');
const http = require('http');
require('dotenv').config();
class EGWChatCLIHTTP {
constructor() {
this.isConnected = false;
this.llmApiKey = null;
this.llmBaseUrl = process.env.LLM_BASE_URL || 'https://api.openai.com/v1';
this.llmModel = process.env.LLM_MODEL || 'deepseek-chat';
this.promptForApiKey();
}
promptForApiKey() {
const readline = require('readline');
console.log('\nš DeepSeek API Key Required');
console.log('ā'.repeat(50));
console.log('š” To use EGW Chat CLI, you need a DeepSeek API key');
console.log('š Get your free API key at: https://platform.deepseek.com/');
console.log('š Your API key is used only for this session and not stored');
console.log('ā'.repeat(50));
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: 'š Enter your DeepSeek API key: '
});
rl.question('', (apiKey) => {
if (!apiKey || apiKey.trim() === '') {
console.error('ā No API key provided. Exiting...');
process.exit(1);
}
this.llmApiKey = apiKey.trim();
rl.close();
console.log('ā
API key accepted. Starting chat interface...\n');
this.setupChatInterface();
});
}
async sendMCPRequest(request) {
return new Promise((resolve, reject) => {
const postData = JSON.stringify(request);
const options = {
hostname: 'localhost',
port: 3000,
path: '/mcp',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData)
}
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const response = JSON.parse(data);
resolve(response);
} catch (error) {
reject(new Error(`Invalid JSON response: ${error.message}`));
}
});
});
req.on('error', (error) => {
reject(error);
});
req.write(postData);
req.end();
});
}
setupChatInterface() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: 'š EGW Chat > '
});
console.log('\nš Welcome to EGW Writings Chat CLI (HTTP)!');
console.log('š¬ Ask questions about Ellen G. White writings, search for content, or get book information');
console.log('š Connected to local MCP server at http://localhost:3000/mcp');
console.log('šŖ Type "exit", "quit", or press Ctrl+C to leave\n');
// Test connection first
this.testConnection().then(() => {
rl.prompt();
}).catch(error => {
console.error('ā Failed to connect to MCP server:', error.message);
console.log('š” Make sure the local MCP server is running on port 3000');
console.log('š” Run: cd egw_writings_mcp_server/apps/local-server && node src/server-universal.js');
rl.prompt();
});
rl.on('line', async (input) => {
if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
console.log('š Goodbye!');
rl.close();
process.exit(0);
}
if (input.trim()) {
await this.processUserMessage(input.trim());
}
rl.prompt();
});
rl.on('close', () => {
process.exit(0);
});
}
async testConnection() {
console.log('š Testing connection to local MCP server...');
try {
const response = await this.sendMCPRequest({
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {
protocolVersion: '2025-06-18',
capabilities: {},
clientInfo: {
name: 'egw-chat-cli-http',
version: '1.0.0'
}
}
});
if (response.result) {
this.isConnected = true;
console.log('ā
Connected to Local EGW MCP Server!');
// Get available tools
const toolsResponse = await this.sendMCPRequest({
jsonrpc: '2.0',
id: 2,
method: 'tools/list'
});
if (toolsResponse.result && toolsResponse.result.tools) {
this.availableTools = toolsResponse.result.tools;
console.log(`š Available tools: ${this.availableTools.map(t => t.name).join(', ')}\n`);
}
}
} catch (error) {
throw error;
}
}
async processUserMessage(userMessage) {
try {
console.log('š¤ Processing your query...');
// Check if MCP is connected
if (!this.isConnected) {
console.log('ā ļø MCP server not connected. Please check server status.');
return;
}
// First, let LLM analyze what MCP tool to use
const toolChoice = await this.chooseToolWithLLM(userMessage);
if (toolChoice.tool) {
console.log(`š Using ${toolChoice.tool} tool to search EGW writings...`);
// Execute chosen MCP tool
const result = await this.executeMCPTool(toolChoice.tool, toolChoice.params);
// Let LLM format response based on tool results
const formattedResponse = await this.formatResponseWithLLM(userMessage, result, toolChoice.tool);
console.log('\nš Answer:');
console.log('ā'.repeat(50));
console.log(formattedResponse);
console.log('ā'.repeat(50) + '\n');
} else {
// Direct chat response without tools
const response = await this.callLLM(userMessage);
console.log('\nš¬ Response:');
console.log('ā'.repeat(50));
console.log(response);
console.log('ā'.repeat(50) + '\n');
}
} catch (error) {
console.error('ā Error processing your request:', error.message);
}
}
async chooseToolWithLLM(userMessage) {
const systemPrompt = `You are an assistant that helps users interact with EGW (Ellen G. White) writings database.
Available MCP tools:
1. search_local - Search EGW writings database for specific content (basic search)
2. get_local_book - Get information about a specific book by ID
3. get_local_content - Get content from a specific book (with pagination)
4. list_local_books - List all available books
5. get_database_stats - Get database statistics
6. find_egw_quotes - Find specific EGW quotes containing a search term with proper filtering for genuine EGW content (BEST for finding quotes)
Analyze user's message and determine if a tool should be used and which one.
IMPORTANT: For quote searches or finding specific EGW writings, prefer "find_egw_quotes" over "search_local" as it provides better filtering and formatting.
SMART SEARCH PRIORITY: When user asks for quotes on a topic, prioritize most likely search term:
- Single words: "love" -> search for "love" first, then "loving", then "charity"
- Single words: "faith" -> search for "faith" first, then "faithful", then "belief"
- Single words: "prayer" -> search for "prayer" first, then "praying", then "pray"
- Single words: "hope" -> search for "hope" first, then "hopeful", then "trust"
- Multi-word phrases: "sunday law" -> search for exact phrase "sunday law" first
- Multi-word phrases: Use exact phrase matching for compound terms
For multi-word queries, use exact phrase as primary search term.
If no results are found for primary term, MCP tool will automatically try variations.
Respond with ONLY a JSON object in this format:
{
"tool": "tool_name or null",
"params": {"param1": "value1", "param2": "value2"} or {}
}
Examples:
- "find quotes about prayer" -> {"tool": "find_egw_quotes", "params": {"query": "prayer", "numQuotes": 3}}
- "quotes on faith" -> {"tool": "find_egw_quotes", "params": {"query": "faith", "numQuotes": 3}}
- "what did EGW say about faith" -> {"tool": "find_egw_quotes", "params": {"query": "faith", "numQuotes": 3}}
- "tell me about book 5" -> {"tool": "get_local_book", "params": {"bookId": 5}}
- "list all books" -> {"tool": "list_local_books", "params": {"limit": 20}}
- "how many books do you have" -> {"tool": "get_database_stats", "params": {}}
- "hello" -> {"tool": null, "params": {}}
- general questions about EGW -> {"tool": null, "params": {}}`;
try {
const response = await this.callLLMAPI(systemPrompt, userMessage);
return JSON.parse(response);
} catch (error) {
console.log('ā ļø Could not determine tool, using direct chat');
return { tool: null, params: {} };
}
}
async executeMCPTool(toolName, params) {
try {
const response = await this.sendMCPRequest({
jsonrpc: '2.0',
id: Date.now(),
method: 'tools/call',
params: {
name: toolName,
arguments: params
}
});
if (response.error) {
throw new Error(response.error.message);
}
console.log('š MCP Response received successfully');
return response.result;
} catch (error) {
throw new Error(`Tool execution failed: ${error.message}`);
}
}
async formatResponseWithLLM(userMessage, toolResult, toolUsed) {
// CRITICAL: For find_egw_quotes, ALWAYS display raw formatted quotes without ANY LLM processing
if (toolUsed === 'find_egw_quotes') {
// Handle different response structures from MCP
let actualResult = toolResult;
// If result is wrapped in content array (MCP response format)
if (toolResult.content && Array.isArray(toolResult.content) && toolResult.content[0] && toolResult.content[0].text) {
try {
actualResult = JSON.parse(toolResult.content[0].text);
} catch (parseError) {
console.log('DEBUG: Could not parse nested content, using original');
}
}
if (actualResult.success && actualResult.formatted_output) {
// Return exact formatted output from database tool - NO SUMMARIZATION, NO COMMENTS
return actualResult.formatted_output;
} else if (actualResult.success === false) {
// Return error message directly without LLM processing
return `ā ${actualResult.message || 'No quotes found'}`;
} else {
// Fallback for unexpected result format
return JSON.stringify(actualResult, null, 2);
}
}
// For all other tools, use LLM for formatting
const systemPrompt = `You are a helpful assistant that provides information about Ellen G. White's writings.
The user asked: "${userMessage}"
Tool used: ${toolUsed}
Tool result: ${JSON.stringify(toolResult)}
Please provide a helpful, friendly response based on tool results. If search results were returned, summarize the findings and provide context. If book information was requested, present it clearly. If statistics were requested, explain what they mean.
Be conversational and helpful. Focus on spiritual and practical insights from EGW's writings.`;
return await this.callLLMAPI(systemPrompt, '');
}
async callLLMAPI(systemPrompt, userMessage) {
// Use OpenAI SDK for compatibility
const OpenAI = require('openai');
const client = new OpenAI({
apiKey: this.llmApiKey,
baseURL: this.llmBaseUrl
});
const messages = [
{ role: 'system', content: systemPrompt },
...(userMessage ? [{ role: 'user', content: userMessage }] : [])
];
try {
const completion = await client.chat.completions.create({
model: this.llmModel,
messages: messages,
max_tokens: 1000,
temperature: 0.7
});
return completion.choices[0].message.content;
} catch (error) {
throw new Error(`LLM API error: ${error.message}`);
}
}
async callLLM(message) {
return await this.callLLMAPI(
'You are a helpful assistant knowledgeable about Ellen G. White and her writings. Provide helpful, accurate information about her spiritual insights, books, and teachings.',
message
);
}
}
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\nš Goodbye!');
process.exit(0);
});
// Start CLI
if (require.main === module) {
new EGWChatCLIHTTP();
}
module.exports = EGWChatCLIHTTP;