mcp-client-enhanced.js•6.04 kB
// Enhanced MCP Client with proper session handling
import axios from 'axios';
class MCPClient {
constructor(baseUrl = 'http://localhost:5002') {
this.baseUrl = baseUrl;
this.sessionId = null;
this.requestId = 1;
// Configure axios instance
this.axios = axios.create({
baseURL: baseUrl,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/event-stream'
},
// Don't throw on non-2xx status codes
validateStatus: () => true,
// Get raw response to access headers
transformResponse: (res) => res
});
}
/**
* Send a JSON-RPC request to the MCP server
*/
/**
* Parse Server-Sent Events (SSE) response
*/
_parseSSEResponse(data) {
const lines = data.split('\n').filter(line => line.trim() !== '');
const result = {
event: null,
data: null,
id: null,
retry: null
};
for (const line of lines) {
const colonIndex = line.indexOf(':');
if (colonIndex === -1) continue;
const field = line.slice(0, colonIndex).trim();
let value = line.slice(colonIndex + 1).trim();
// Remove leading space if present
if (value.startsWith(' ')) {
value = value.slice(1);
}
if (field === 'data') {
try {
result.data = JSON.parse(value);
} catch (e) {
result.data = value;
}
} else if (field === 'event') {
result.event = value;
} else if (field === 'id') {
result.id = value;
} else if (field === 'retry') {
result.retry = parseInt(value, 10);
}
}
return result;
}
async _sendRequest(method, params = {}) {
const requestId = this.requestId++;
const requestData = {
jsonrpc: '2.0',
method,
params,
id: requestId
};
// Add session ID to headers if available
const headers = {};
if (this.sessionId) {
headers['mcp-session-id'] = this.sessionId;
}
try {
console.log(`\n=== Sending ${method} request ===`);
console.log('Request:', JSON.stringify(requestData, null, 2));
const response = await this.axios.post('/mcp', requestData, {
headers,
responseType: 'text' // Get raw response text
});
console.log('\n=== Response ===');
console.log('Status:', response.status, response.statusText);
console.log('Headers:', JSON.stringify(response.headers, null, 2));
// Parse SSE response
const sseResponse = this._parseSSEResponse(response.data);
console.log('SSE Response:', JSON.stringify(sseResponse, null, 2));
// Extract JSON-RPC response from SSE data
let responseData = sseResponse.data;
// If data is still a string, try to parse it as JSON
if (typeof responseData === 'string') {
try {
responseData = JSON.parse(responseData);
} catch (e) {
throw new Error('Failed to parse SSE data as JSON');
}
}
// Update session ID if provided in headers
if (response.headers['mcp-session-id']) {
this.sessionId = response.headers['mcp-session-id'];
console.log('Session ID updated:', this.sessionId);
}
// Handle JSON-RPC errors
if (responseData.error) {
throw new Error(`JSON-RPC Error (${responseData.error.code}): ${responseData.error.message}`);
}
return responseData.result;
} catch (error) {
console.error('\n❌ Request failed:');
if (error.response) {
console.error('Status:', error.response.status);
console.error('Headers:', error.response.headers);
console.error('Data:', error.response.data);
} else if (error.request) {
console.error('No response received');
} else {
console.error('Error:', error.message);
}
throw error;
}
}
/**
* Initialize a new MCP session
*/
async initialize() {
const result = await this._sendRequest('initialize', {
protocolVersion: '1.0',
clientInfo: {
name: 'spiderfoot-mcp-client',
version: '1.0.0'
},
capabilities: {}
});
console.log('\n✅ MCP Session initialized');
console.log('Server version:', result.serverInfo.version);
console.log('Protocol version:', result.protocolVersion);
return result;
}
/**
* List available tools
*/
async listTools() {
return this._sendRequest('mcp.list_tools');
}
/**
* Call a specific tool
*/
async callTool(toolName, params = {}) {
return this._sendRequest(`mcp.${toolName}`, params);
}
/**
* Get tool schema
*/
async getToolSchema(toolName) {
return this._sendRequest('mcp.get_tool_schema', {
tool: toolName
});
}
}
// Example usage
async function main() {
const client = new MCPClient();
try {
// 1. Initialize session
console.log('Initializing MCP session...');
await client.initialize();
if (!client.sessionId) {
throw new Error('Failed to establish MCP session');
}
// 2. List available tools
console.log('\nListing available tools...');
const tools = await client.listTools();
console.log('Available tools:', JSON.stringify(tools, null, 2));
// 3. Call spiderfoot_ping
console.log('\nCalling spiderfoot_ping...');
const pingResult = await client.callTool('spiderfoot_ping');
console.log('Ping result:', JSON.stringify(pingResult, null, 2));
// 4. List scans
console.log('\nListing scans...');
const scans = await client.callTool('spiderfoot_scans');
console.log('Scans:', JSON.stringify(scans, null, 2));
} catch (error) {
console.error('\n❌ Error:', error.message);
process.exit(1);
}
}
// Run the example if this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
export default MCPClient;