@geekmai/anteey-mcp-client
Version:
Anteey MCP 客户端 - 连接外部 AI 工具与 Anteey 笔记应用
336 lines (293 loc) • 9.06 kB
JavaScript
const http = require("http");
const readline = require("readline");
const { loadConfig } = require("./config");
// 创建 readline 接口用于读取标准输入
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false,
});
// 初始化配置
let config = null;
console.error("Anteey MCP 客户端已启动");
// 加载配置
loadConfig()
.then((loadedConfig) => {
config = loadedConfig;
// 优先使用环境变量,如果没有则使用配置文件
const API_KEY = process.env.ANTEEY_MCP_API_KEY || config.apiKey;
const ANTEEY_MCP_URL = process.env.ANTEEY_MCP_BASE_URL || config.serverUrl;
config.apiKey = API_KEY;
config.serverUrl = ANTEEY_MCP_URL;
console.error(`使用 API 密钥: ${config.apiKey}`);
console.error(`使用 API 地址: ${config.serverUrl}`);
if (!config.apiKey) {
console.error('⚠️ 未设置 API 密钥,请使用 "anteey-mcp config" 设置');
} else {
// 配置加载完成后测试连接
setTimeout(() => testConnection(), 1000);
}
})
.catch((error) => {
console.error("加载配置失败:", error.message);
// 使用默认配置
config = {
apiKey: process.env.ANTEEY_MCP_API_KEY || "",
serverUrl:
process.env.ANTEEY_MCP_BASE_URL || "http://localhost:43211/api/mcp",
timeout: 30000,
};
console.error(`使用默认配置 - API 密钥: ${config.apiKey}`);
console.error(`使用默认配置 - API 地址: ${config.serverUrl}`);
});
// 监听标准输入的每一行
rl.on("line", async (line) => {
try {
console.error(`收到请求: ${line}`);
if (!line.trim()) {
console.error("收到空输入,忽略");
return;
}
// 检查是否是 JSON-RPC 错误消息
if (line.includes('"jsonrpc":"2.0"') && line.includes('"error"')) {
console.error("收到 JSON-RPC 错误消息,忽略");
return;
}
// 解析 JSON 请求
const request = JSON.parse(line);
// 处理 JSON-RPC 请求
if (request.jsonrpc === "2.0") {
await handleJsonRpcRequest(request);
return;
}
// 处理传统请求
if (request.type === "query" || request.action === "query") {
// 处理查询请求 (兼容 Raycast 和 Cursor)
const query = request.query || request.text || "";
await handleQuery(query);
} else if (request.action === "search") {
// 处理 Cursor 的搜索请求
const query = request.text || "";
await handleQuery(query);
} else {
console.error(`不支持的请求类型: ${request.type || request.action}`);
sendResponse({
error: `不支持的请求类型: ${request.type || request.action}`,
});
}
} catch (error) {
console.error("处理请求时出错:", error);
// 尝试以 JSON-RPC 格式发送错误
try {
sendJsonRpcError(null, -32603, error.message || "内部错误");
} catch (e) {
// 如果失败,使用传统格式
sendResponse({ error: error.message || "处理请求时出错" });
}
}
});
// 处理 JSON-RPC 请求
async function handleJsonRpcRequest(request) {
const { id, method, params } = request;
try {
console.error(`处理 JSON-RPC 请求: method=${method}, id=${id}`);
// 处理初始化请求
if (method === "initialize") {
sendJsonRpcResponse(id, {
capabilities: {
search: true,
},
});
return;
}
// 处理搜索请求
if (method === "search") {
if (!config) {
sendJsonRpcError(id, -32603, "配置未加载");
return;
}
const query = params?.query || "";
console.error(`搜索查询: ${query}`);
const notes = await searchNotes(query);
console.error(`找到 ${notes.length} 条笔记`);
sendJsonRpcResponse(id, {
items: notes.map((note) => ({
id: note.id,
title: note.title || "无标题笔记",
subtitle: note.cardType,
text: {
value: note.content || "",
type: "markdown",
},
accessoryTitle: new Date(
parseInt(note.updatedAt)
).toLocaleDateString(),
actions: [
{
title: "打开笔记",
type: "open",
target: `anteey://open?noteId=${note.id}`,
},
],
})),
});
return;
}
// 处理未知方法
console.error(`未知方法: ${method}`);
sendJsonRpcError(id, -32601, `方法不存在: ${method}`);
} catch (error) {
console.error(`处理 JSON-RPC 请求出错:`, error);
sendJsonRpcError(id, -32603, error.message || "内部错误");
}
}
// 发送 JSON-RPC 响应
function sendJsonRpcResponse(id, result) {
const response = {
jsonrpc: "2.0", // 使用双引号而不是单引号
id,
result,
};
console.error(
`发送 JSON-RPC 响应: ${JSON.stringify(response).substring(0, 100)}...`
);
console.log(JSON.stringify(response));
}
// 发送 JSON-RPC 错误
function sendJsonRpcError(id, code, message) {
const response = {
jsonrpc: "2.0", // 使用双引号而不是单引号
id,
error: {
code,
message,
},
};
console.error(`发送 JSON-RPC 错误: ${JSON.stringify(response)}`);
console.log(JSON.stringify(response));
}
// 处理查询请求
async function handleQuery(query) {
try {
console.error(`查询: ${query}`);
// 调用 Anteey MCP API
const notes = await searchNotes(query);
console.error(`找到 ${notes.length} 条笔记`);
// 将笔记转换为 MCP 格式并发送响应
sendResponse({
results: notes.map((note) => ({
title: note.title || "无标题笔记",
content: note.content || "",
url: `anteey://open?noteId=${note.id}`,
})),
});
} catch (error) {
console.error("查询笔记时出错:", error);
sendResponse({ error: error.message || "查询笔记时出错" });
}
}
// 调用 Anteey MCP API 搜索笔记
function searchNotes(query) {
return new Promise((resolve, reject) => {
const url = `${config.serverUrl}/notes/search?query=${encodeURIComponent(
query
)}`;
console.error(`请求 URL: ${url}`);
const req = http.get(
url,
{
headers: {
Authorization: `Bearer ${config.apiKey}`,
},
},
(res) => {
let data = "";
res.on("data", (chunk) => {
data += chunk;
});
res.on("end", () => {
if (res.statusCode !== 200) {
console.error(`API 请求失败: ${res.statusCode}`);
console.error(`响应内容: ${data}`);
reject(new Error(`API 请求失败: ${res.statusCode}`));
return;
}
try {
const response = JSON.parse(data);
if (!response.success) {
reject(new Error(response.error || "未知错误"));
return;
}
resolve(response.data || []);
} catch (error) {
console.error("解析 API 响应时出错:", error);
reject(error);
}
});
}
);
req.on("error", (error) => {
console.error("API 请求出错:", error);
reject(error);
});
req.end();
});
}
// 发送响应到标准输出
function sendResponse(response) {
console.error(
`发送传统响应: ${JSON.stringify(response).substring(0, 100)}...`
);
console.log(JSON.stringify(response));
}
// 初始测试,确认 API 连接正常
async function testConnection() {
try {
console.error("测试 API 连接...");
const url = `${config.serverUrl}/notes/search?query=test&limit=1`;
http
.get(
url,
{
headers: {
Authorization: `Bearer ${config.apiKey}`,
},
},
(res) => {
let data = "";
res.on("data", (chunk) => {
data += chunk;
});
res.on("end", () => {
console.error(`API 状态响应: ${res.statusCode}`);
console.error(`响应内容: ${data}`);
if (res.statusCode === 200) {
console.error("API 连接正常");
} else {
console.error("API 连接异常");
}
});
}
)
.on("error", (error) => {
console.error("API 连接测试失败:", error);
});
} catch (error) {
console.error("测试 API 连接时出错:", error);
}
}
// 处理进程终止
process.on("SIGINT", () => {
console.error("收到 SIGINT,正在退出...");
process.exit(0);
});
process.on("SIGTERM", () => {
console.error("收到 SIGTERM,正在退出...");
process.exit(0);
});
// 注释掉自动测试,避免在配置加载前运行
// testConnection();
// 不要在启动时发送任何响应,等待 Raycast 的请求
// 注释掉原来的初始响应
// sendJsonRpcResponse(0, { status: 'ready' })