questrade-mcp-server
Version:
MCP server for Questrade API integration
90 lines (89 loc) • 3.59 kB
JavaScript
import axios from 'axios';
export class QuestradeClient {
http;
config;
tokenManager;
constructor(config, tokenManager) {
this.config = config;
this.tokenManager = tokenManager;
this.http = axios.create({
baseURL: config.apiUrl,
headers: {
'Authorization': `Bearer ${config.accessToken}`,
'Content-Type': 'application/json'
}
});
}
async refreshToken() {
try {
const data = new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: this.config.refreshToken
});
const response = await axios.post('https://login.questrade.com/oauth2/token', data, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
const tokenData = response.data;
this.config.accessToken = tokenData.access_token;
this.config.refreshToken = tokenData.refresh_token;
this.config.apiUrl = tokenData.api_server;
this.http.defaults.baseURL = this.config.apiUrl;
this.http.defaults.headers['Authorization'] = `Bearer ${this.config.accessToken}`;
await this.tokenManager.saveTokens(tokenData.refresh_token, tokenData.access_token, tokenData.api_server);
return tokenData;
}
catch (error) {
if (axios.isAxiosError(error)) {
const errorDetails = error.response?.data || error.message;
throw new Error(`Token refresh failed: ${JSON.stringify(errorDetails)}`);
}
throw error;
}
}
async getAccounts() {
const response = await this.http.get('/v1/accounts');
return response.data.accounts;
}
async getPositions(accountNumber) {
const response = await this.http.get(`/v1/accounts/${accountNumber}/positions`);
return response.data.positions;
}
async getBalances(accountNumber) {
const response = await this.http.get(`/v1/accounts/${accountNumber}/balances`);
return response.data.combinedBalances;
}
async getQuotes(symbolIds) {
const ids = symbolIds.join(',');
const response = await this.http.get(`/v1/markets/quotes/${ids}`);
return response.data.quotes;
}
async searchSymbols(prefix, offset = 0) {
const response = await this.http.get(`/v1/symbols/search?prefix=${encodeURIComponent(prefix)}&offset=${offset}`);
return response.data.symbols;
}
async getSymbol(symbolId) {
const response = await this.http.get(`/v1/symbols/${symbolId}`);
return response.data.symbols;
}
async getOrders(accountNumber, startTime, endTime, stateFilter) {
let url = `/v1/accounts/${accountNumber}/orders`;
const params = new URLSearchParams();
if (startTime)
params.append('startTime', startTime);
if (endTime)
params.append('endTime', endTime);
if (stateFilter)
params.append('stateFilter', stateFilter);
if (params.toString()) {
url += `?${params.toString()}`;
}
const response = await this.http.get(url);
return response.data.orders;
}
async getCandles(symbolId, startTime, endTime, interval) {
const response = await this.http.get(`/v1/markets/candles/${symbolId}?startTime=${startTime}&endTime=${endTime}&interval=${interval}`);
return response.data.candles;
}
}