UNPKG

questrade-mcp-server

Version:

MCP server for Questrade API integration

90 lines (89 loc) 3.59 kB
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; } }