UNPKG

mcp-connector

Version:

MCP Remote Proxy Server for Streamable HTTP with OAuth Support.

265 lines 10.1 kB
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