@ankimcp/anki-mcp-server
Version:
Model Context Protocol server for Anki - enables AI assistants to interact with your Anki flashcards
126 lines ⢠4.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NgrokService = void 0;
const child_process_1 = require("child_process");
const http_1 = __importDefault(require("http"));
const common_1 = require("@nestjs/common");
class NgrokService {
logger = new common_1.Logger(NgrokService.name);
process = null;
cleanupHandlersRegistered = false;
async start(port) {
if (!(await this.isNgrokInstalled())) {
throw new Error("ngrok is not installed.\n" +
"Install it with: npm install -g ngrok\n" +
"Or download from: https://ngrok.com/download\n" +
"Then setup auth: ngrok config add-authtoken <your-token>");
}
this.process = (0, child_process_1.spawn)("ngrok", ["http", "--host-header=rewrite", port.toString()], {
stdio: "pipe",
});
if (!this.cleanupHandlersRegistered) {
this.registerCleanupHandlers();
this.cleanupHandlersRegistered = true;
}
this.process.on("error", (err) => {
throw new Error(`Failed to start ngrok: ${err.message}`);
});
this.process.on("exit", (code, _signal) => {
if (code !== 0 && code !== null) {
this.logger.error(`ngrok exited with code ${code}`);
}
});
await this.waitForNgrok();
const publicUrl = await this.getTunnelUrl();
return {
publicUrl,
apiUrl: "http://localhost:4040",
};
}
async isNgrokInstalled() {
return new Promise((resolve) => {
const isWindows = process.platform === "win32";
const command = isWindows ? "where" : "which";
const check = (0, child_process_1.spawn)(command, ["ngrok"], {
shell: isWindows,
});
check.on("close", (code) => resolve(code === 0));
check.on("error", () => resolve(false));
});
}
async waitForNgrok(maxAttempts = 20, delayMs = 500) {
for (let i = 0; i < maxAttempts; i++) {
await new Promise((resolve) => setTimeout(resolve, delayMs));
try {
await this.getTunnelUrl();
return;
}
catch {
}
}
throw new Error("ngrok failed to start in time.\n" +
"Make sure you have configured your auth token:\n" +
"ngrok config add-authtoken <your-token>");
}
async getTunnelUrl() {
return new Promise((resolve, reject) => {
http_1.default
.get("http://localhost:4040/api/tunnels", (res) => {
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => {
try {
const response = JSON.parse(data);
const tunnel = response.tunnels?.find((t) => t.proto === "https");
const publicUrl = tunnel?.public_url;
if (publicUrl) {
resolve(publicUrl);
}
else {
reject(new Error("No HTTPS tunnel found in ngrok API response"));
}
}
catch (err) {
reject(new Error(`Failed to parse ngrok API response: ${err}`));
}
});
})
.on("error", (err) => {
reject(new Error(`Failed to connect to ngrok API: ${err.message}`));
});
});
}
registerCleanupHandlers() {
process.on("SIGINT", () => {
this.logger.log("\n\nš Shutting down ngrok tunnel...");
this.cleanup();
process.exit(0);
});
process.on("SIGTERM", () => {
this.cleanup();
process.exit(0);
});
process.on("exit", () => {
this.cleanup();
});
process.on("uncaughtException", (err) => {
this.logger.error("Uncaught exception:", err);
this.cleanup();
process.exit(1);
});
}
cleanup() {
if (this.process && !this.process.killed) {
this.process.kill("SIGTERM");
this.process = null;
}
}
getWebInterfaceUrl() {
return "http://localhost:4040";
}
}
exports.NgrokService = NgrokService;
//# sourceMappingURL=ngrok.service.js.map