theta-health-mcp-client
Version:
Theta Health MCP Client for WebSocket connection - Works like @notionhq/notion-mcp-server
165 lines (139 loc) • 4.01 kB
JavaScript
const WebSocket = require('ws');
const readline = require('readline');
class ThetaHealthMCPClient {
constructor(endpoint, token) {
this.endpoint = endpoint;
this.token = token;
this.ws = null;
this.connected = false;
this.rl = null;
this.cleanupTimeout = null;
}
async connect() {
return new Promise((resolve, reject) => {
const wsUrl = `${this.endpoint}?token=${this.token}`;
try {
this.ws = new WebSocket(wsUrl, ['mcp']);
this.ws.on('open', () => {
this.connected = true;
this.setupMessageHandlers();
resolve();
});
this.ws.on('message', (data) => {
// 直接输出到 stdout(MCP 协议要求)
console.log(data.toString());
});
this.ws.on('error', (error) => {
console.error(`❌ WebSocket 错误: ${error.message}`);
reject(error);
});
this.ws.on('close', () => {
this.cleanup();
process.exit(0);
});
// 连接超时处理
setTimeout(() => {
if (!this.connected) {
reject(new Error('连接超时'));
}
}, 5000);
} catch (error) {
console.error(`❌ 连接失败: ${error.message}`);
reject(error);
}
});
}
setupMessageHandlers() {
// 设置 stdin/stdout 接口
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
// 处理来自 stdin 的 MCP 消息
this.rl.on('line', (line) => {
if (line.trim() && this.connected) {
try {
// 验证 JSON 格式
JSON.parse(line);
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(line);
}
} catch (e) {
console.error(`⚠️ 无效的JSON: ${line}`);
}
}
});
this.rl.on('close', () => {
// stdin 关闭后,等待2秒让服务器有时间响应
this.cleanupTimeout = setTimeout(() => {
this.cleanup();
}, 2000);
});
// 处理 stdin 结束(EOF)
process.stdin.on('end', () => {
// stdin 结束后,等待2秒让服务器有时间响应
if (!this.cleanupTimeout) {
this.cleanupTimeout = setTimeout(() => {
this.cleanup();
}, 2000);
}
});
// 处理进程信号
process.on('SIGINT', () => {
this.cleanup();
process.exit(0);
});
process.on('SIGTERM', () => {
this.cleanup();
process.exit(0);
});
}
cleanup() {
if (this.cleanupTimeout) {
clearTimeout(this.cleanupTimeout);
this.cleanupTimeout = null;
}
if (this.rl) {
this.rl.close();
this.rl = null;
}
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.close();
}
}
}
// 主函数
async function main() {
const endpoint = process.env.THETA_HEALTH_ENDPOINT || 'ws://localhost:18022';
const token = process.env.THETA_HEALTH_TOKEN;
if (!token) {
console.error('❌ 缺少 THETA_HEALTH_TOKEN 环境变量');
process.exit(1);
}
const client = new ThetaHealthMCPClient(endpoint, token);
try {
await client.connect();
// 如果没有stdin输入,设置超时退出(防止在某些环境下挂起)
if (process.stdin.isTTY === false) {
// 非交互模式下,如果15秒内没有输入就退出
const timeout = setTimeout(() => {
console.error('⏰ 非交互模式下等待输入超时');
client.cleanup();
process.exit(1);
}, 15000);
// 如果收到输入,清除超时
process.stdin.once('data', () => {
clearTimeout(timeout);
});
}
} catch (error) {
console.error(`❌ 连接失败: ${error.message}`);
process.exit(1);
}
}
if (require.main === module) {
main();
}
module.exports = ThetaHealthMCPClient;