questrade-mcp-server
Version:
MCP server for Questrade API integration
90 lines (89 loc) • 3.27 kB
JavaScript
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
export class TokenManager {
tokenFilePath;
constructor() {
let defaultTokenDir;
if (process.env.QUESTRADE_TOKEN_DIR) {
defaultTokenDir = process.env.QUESTRADE_TOKEN_DIR;
}
else {
try {
defaultTokenDir = path.join(os.homedir(), '.questrade-mcp');
}
catch {
defaultTokenDir = path.join(os.tmpdir(), 'questrade-mcp');
}
}
this.tokenFilePath = path.join(defaultTokenDir, 'tokens.json');
try {
if (!fs.existsSync(defaultTokenDir)) {
fs.mkdirSync(defaultTokenDir, { recursive: true });
}
}
catch {
// Directory creation failed - will fall back to environment variables
}
}
async loadTokens() {
const envToken = process.env.QUESTRADE_REFRESH_TOKEN;
try {
if (fs.existsSync(this.tokenFilePath)) {
const tokenData = JSON.parse(await fs.promises.readFile(this.tokenFilePath, 'utf8'));
// If the env var has a new token the file has never seen, the user rotated manually — reset.
if (envToken && tokenData.bootstrapToken && envToken !== tokenData.bootstrapToken) {
return {
refreshToken: envToken,
accessToken: undefined,
apiUrl: undefined
};
}
return {
refreshToken: tokenData.refreshToken,
accessToken: tokenData.accessToken,
apiUrl: tokenData.apiUrl
};
}
}
catch (error) {
// Fall back to environment variables on file read error
}
return {
refreshToken: envToken,
accessToken: process.env.QUESTRADE_ACCESS_TOKEN,
apiUrl: process.env.QUESTRADE_API_URL
};
}
async saveTokens(refreshToken, accessToken, apiUrl) {
try {
// Preserve bootstrapToken if file already exists, otherwise record the current env var token.
let bootstrapToken = process.env.QUESTRADE_REFRESH_TOKEN;
try {
if (fs.existsSync(this.tokenFilePath)) {
const existing = JSON.parse(await fs.promises.readFile(this.tokenFilePath, 'utf8'));
if (existing.bootstrapToken)
bootstrapToken = existing.bootstrapToken;
}
}
catch { }
const tokenData = {
bootstrapToken,
refreshToken,
accessToken,
apiUrl,
lastUpdated: new Date().toISOString()
};
await fs.promises.writeFile(this.tokenFilePath, JSON.stringify(tokenData, null, 2), 'utf8');
}
catch (error) {
// Token save failed - will continue using environment variables
}
}
getTokenFilePath() {
return this.tokenFilePath;
}
async hasTokenFile() {
return fs.existsSync(this.tokenFilePath);
}
}