mcp-connector
Version:
MCP Remote Proxy Server for Streamable HTTP with OAuth Support.
265 lines • 10.1 kB
JavaScript
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync, } from "fs";
import { join } from "path";
import { homedir } from "os";
import { createHash } from "crypto";
export class TokenManager {
tokenStorePath;
mcpConnectRootPath;
logger;
url = null;
tokenCache = {};
constructor(logger, url) {
this.mcpConnectRootPath = join(homedir(), ".mcp-connector");
this.tokenStorePath = join(homedir(), ".mcp-connector");
if (url) {
this.url = url;
this.tokenStorePath = join(homedir(), ".mcp-connector", TokenManager.hashUrl(url));
}
this.logger = logger;
this.ensureStoreExists();
this.ensureRootPathExists();
}
ensureStoreExists() {
try {
if (!existsSync(this.tokenStorePath)) {
mkdirSync(this.tokenStorePath, { recursive: true });
this.logger.debug(`Created token store directory: ${this.tokenStorePath}`);
}
}
catch (error) {
this.logger.warn("Failed to create token store directory:", error);
}
}
ensureRootPathExists() {
try {
if (!existsSync(this.mcpConnectRootPath)) {
mkdirSync(this.mcpConnectRootPath, { recursive: true });
this.logger.debug(`Created mcp connector directory: ${this.mcpConnectRootPath}`);
}
}
catch (error) {
this.logger.warn("Failed to create mcp connector root directory:", error);
}
}
static hashUrl(url) {
return createHash("sha256").update(url).digest("hex");
}
getTokenFilePath() {
if (this.url === null)
return null;
const urlHash = TokenManager.hashUrl(this.url);
return join(this.tokenStorePath, `${urlHash}.token`);
}
addUrlInHashMapFile(hash, url) {
try {
const mapFilePath = join(this.mcpConnectRootPath, "url-hash-map.json");
const mapData = this.getUrlInHashMapFile();
mapData[hash] = url;
writeFileSync(mapFilePath, JSON.stringify(mapData, null, 2), "utf8");
this.logger.debug(`added URL-hash mapping for ${url}`);
}
catch (error) {
this.logger.warn(`Failed to add URL-hash mapping for ${url}:`, error);
}
}
getUrlInHashMapFile() {
let mapData = {};
try {
const mapFilePath = join(this.mcpConnectRootPath, "url-hash-map.json");
if (existsSync(mapFilePath)) {
const existingData = readFileSync(mapFilePath, "utf8");
mapData = JSON.parse(existingData);
}
this.logger.debug(`Retrived URL-hash mapping`);
}
catch (error) {
this.logger.warn(`Failed to get URL-hash mapping:`, error);
}
return mapData;
}
removeUrlInHashMapFile(hash) {
const mapData = {};
try {
const mapFilePath = join(this.mcpConnectRootPath, "url-hash-map.json");
const mapData = this.getUrlInHashMapFile();
delete mapData[hash];
writeFileSync(mapFilePath, JSON.stringify(mapData, null, 2), "utf8");
this.logger.debug(`Removed URL-hash mapping`);
}
catch (error) {
this.logger.warn(`Failed to get URL-hash mapping:`, error);
}
return mapData;
}
saveToken(url, tokenDetails) {
try {
const tokenFilePath = this.getTokenFilePath();
if (tokenFilePath == null) {
this.logger.info(`URL is null couldn't save token.`);
return;
}
tokenDetails.createdAt = Date.now();
writeFileSync(tokenFilePath, JSON.stringify(tokenDetails, null, 2), "utf8");
const hashUrl = TokenManager.hashUrl(url);
this.addUrlInHashMapFile(hashUrl, url);
this.tokenCache[hashUrl] = tokenDetails;
this.logger.debug(`Saved auth token for ${url}`);
}
catch (error) {
this.logger.warn(`Failed to save auth token for ${url}:`, error);
}
}
getToken(url) {
const hashUrl = TokenManager.hashUrl(url);
if (this.tokenCache[hashUrl]) {
return this.tokenCache[hashUrl];
}
try {
const tokenFilePath = this.getTokenFilePath();
if (tokenFilePath == null) {
this.logger.info(`URL is null couldn't save token.`);
return null;
}
if (!existsSync(tokenFilePath)) {
this.removeUrlInHashMapFile(hashUrl);
return null;
}
const tokenData = JSON.parse(readFileSync(tokenFilePath, "utf8"));
this.tokenCache[hashUrl] = tokenData;
return tokenData;
}
catch (error) {
this.logger.warn(`Failed to load auth token for ${url}:`, error);
return null;
}
}
listTokens(isArgLog = false) {
try {
let tokenFiles = readdirSync(this.tokenStorePath, {
withFileTypes: true,
});
const urlMapData = this.getUrlInHashMapFile();
const tokensList = [];
tokenFiles = tokenFiles.filter((file) => file.isDirectory());
tokenFiles.map((currDir) => {
const getFilesinDir = readdirSync(join(currDir.parentPath, currDir.name), {
withFileTypes: true,
}).filter((file) => file.isFile() && file.name.endsWith(".token"));
getFilesinDir.map((file) => {
try {
if (!urlMapData[file.name.replace(".token", "")]) {
unlinkSync(join(file.parentPath, file.name));
}
const tokenData = JSON.parse(readFileSync(join(file.parentPath, file.name), "utf8"));
tokensList.push({
url: urlMapData[file.name.replace(".token", "")] || "Unknown URL",
tokenDetails: tokenData,
});
}
catch {
if (isArgLog) {
console.warn(`Failed to parse token file ${file}`);
}
else {
this.logger.warn(`Failed to parse token file ${file}`);
}
}
return null;
});
});
return tokensList;
}
catch (error) {
if (isArgLog) {
console.warn("Failed to list stored tokens:", error);
}
else {
this.logger.warn("Failed to list stored tokens:", error);
}
return [];
}
}
removeToken(url) {
try {
const hashUrl = TokenManager.hashUrl(url);
const tokenFilePath = this.getTokenFilePath();
if (tokenFilePath == null) {
this.logger.info(`URL is null couldn't save token.`);
return false;
}
if (existsSync(tokenFilePath)) {
unlinkSync(tokenFilePath);
this.removeUrlInHashMapFile(hashUrl);
delete this.tokenCache[hashUrl];
this.logger.debug(`Removed token for ${url}`);
return true;
}
return false;
}
catch (error) {
this.logger.warn(`Failed to remove token for ${url}:`, error);
return false;
}
}
getStorePath() {
return this.tokenStorePath;
}
getTokenCount() {
try {
const tokenFiles = readdirSync(this.tokenStorePath);
return tokenFiles.filter((file) => file.endsWith(".token")).length;
}
catch (error) {
this.logger.warn("Failed to count tokens:", error);
return 0;
}
}
cleanExpiredTokens() {
try {
const tokenFiles = readdirSync(this.tokenStorePath);
const urlMapData = this.getUrlInHashMapFile();
tokenFiles.forEach((file) => {
if (file.endsWith(".token")) {
const tokenData = JSON.parse(readFileSync(join(this.tokenStorePath, file), "utf8"));
if ((tokenData.expires_in &&
tokenData.expires_in &&
Date.now() >
tokenData.expires_in * 1000) ||
(!tokenData.expires_in && !tokenData.refresh_token)) {
unlinkSync(join(this.tokenStorePath, file));
delete urlMapData[file.replace(".token", "")];
this.logger.debug(`Removed expired token for ${urlMapData[file.replace(".token", "")]}`);
}
}
});
writeFileSync(join(this.mcpConnectRootPath, "url-hash-map.json"), JSON.stringify(urlMapData, null, 2), "utf8");
}
catch (error) {
this.logger.warn("Failed to clean expired tokens:", error);
}
}
updateUrlHashMapFile(map) {
try {
const mapFilePath = join(this.mcpConnectRootPath, "url-hash-map.json");
writeFileSync(mapFilePath, JSON.stringify(map, null, 2), "utf8");
if (this.url) {
this.logger.info(`Updated URL-hash mapping file:`);
}
else {
console.log(`Updated URL-hash mapping file:`);
}
}
catch (error) {
if (this.url) {
this.logger.warn(`Failed to update URL-hash mapping file:`, error);
}
else {
console.warn(`Failed to update URL-hash mapping file:`, error);
}
}
}
getMcpConnectFolderPath() {
return this.mcpConnectRootPath;
}
}
//# sourceMappingURL=token-manager.js.map