Skip to main content
Glama

Interactive Brokers MCP Server

by code-rabi
flex-query-storage.ts6.19 kB
import fs from "fs/promises"; import path from "path"; import { Logger } from "./logger.js"; import os from "os"; export interface SavedFlexQuery { id: string; name: string; queryId: string; description?: string; createdAt: string; lastUsed?: string; } export interface FlexQueriesStore { queries: SavedFlexQuery[]; } /** * Storage manager for saved flex queries * Uses a JSON file to persist query information */ export class FlexQueryStorage { private storageFile: string; private store: FlexQueriesStore = { queries: [] }; constructor(storageFile?: string) { // Use user's home directory for storage by default const defaultFile = path.join( os.homedir(), ".interactive-brokers-mcp", "flex-queries.json" ); this.storageFile = storageFile || defaultFile; } /** * Initialize storage - create directory and file if needed */ async initialize(): Promise<void> { try { const dir = path.dirname(this.storageFile); // Create directory if it doesn't exist try { await fs.access(dir); } catch { Logger.log(`[FLEX-QUERY-STORAGE] Creating directory: ${dir}`); await fs.mkdir(dir, { recursive: true }); } // Load existing data or create new file try { await this.load(); } catch { Logger.log(`[FLEX-QUERY-STORAGE] Creating new storage file: ${this.storageFile}`); await this.save(); } } catch (error) { Logger.error("[FLEX-QUERY-STORAGE] Failed to initialize storage:", error); throw error; } } /** * Load queries from storage file */ private async load(): Promise<void> { try { const data = await fs.readFile(this.storageFile, "utf-8"); this.store = JSON.parse(data); Logger.log(`[FLEX-QUERY-STORAGE] Loaded ${this.store.queries.length} queries from storage`); } catch (error) { if ((error as any).code === "ENOENT") { // File doesn't exist yet, use empty store this.store = { queries: [] }; return; } throw error; } } /** * Save queries to storage file */ private async save(): Promise<void> { try { const data = JSON.stringify(this.store, null, 2); await fs.writeFile(this.storageFile, data, "utf-8"); Logger.log(`[FLEX-QUERY-STORAGE] Saved ${this.store.queries.length} queries to storage`); } catch (error) { Logger.error("[FLEX-QUERY-STORAGE] Failed to save queries:", error); throw error; } } /** * Get all saved queries */ async listQueries(): Promise<SavedFlexQuery[]> { await this.load(); // Reload to get latest data return [...this.store.queries]; } /** * Get a saved query by internal ID */ async getQuery(id: string): Promise<SavedFlexQuery | null> { await this.load(); const query = this.store.queries.find((q) => q.id === id); return query || null; } /** * Get a saved query by IB's query ID */ async getQueryByQueryId(queryId: string): Promise<SavedFlexQuery | null> { await this.load(); const query = this.store.queries.find((q) => q.queryId === queryId); return query || null; } /** * Get a saved query by name */ async getQueryByName(name: string): Promise<SavedFlexQuery | null> { await this.load(); const query = this.store.queries.find((q) => q.name.toLowerCase() === name.toLowerCase()); return query || null; } /** * Save a new query */ async saveQuery(query: Omit<SavedFlexQuery, "id" | "createdAt">): Promise<SavedFlexQuery> { await this.load(); // Check if a query with this queryId (IB's ID) already exists const existing = this.store.queries.find((q) => q.queryId === query.queryId); if (existing) { throw new Error(`A query with the IB Query ID "${query.queryId}" already exists. Use updateQuery to modify it.`); } const newQuery: SavedFlexQuery = { ...query, id: this.generateId(), createdAt: new Date().toISOString(), }; this.store.queries.push(newQuery); await this.save(); Logger.log(`[FLEX-QUERY-STORAGE] Saved new query: ${newQuery.name} (Query ID: ${newQuery.queryId})`); return newQuery; } /** * Update an existing query */ async updateQuery(id: string, updates: Partial<Omit<SavedFlexQuery, "id" | "createdAt">>): Promise<SavedFlexQuery> { await this.load(); const index = this.store.queries.findIndex((q) => q.id === id); if (index === -1) { throw new Error(`Query with ID "${id}" not found`); } // If updating name, check for conflicts if (updates.name) { const nameConflict = this.store.queries.find( (q) => q.id !== id && q.name.toLowerCase() === updates.name!.toLowerCase() ); if (nameConflict) { throw new Error(`A query with the name "${updates.name}" already exists`); } } this.store.queries[index] = { ...this.store.queries[index], ...updates, }; await this.save(); Logger.log(`[FLEX-QUERY-STORAGE] Updated query: ${this.store.queries[index].name} (ID: ${id})`); return this.store.queries[index]; } /** * Update the last used timestamp for a query */ async markQueryUsed(id: string): Promise<void> { await this.load(); const query = this.store.queries.find((q) => q.id === id); if (query) { query.lastUsed = new Date().toISOString(); await this.save(); } } /** * Delete a saved query */ async deleteQuery(id: string): Promise<boolean> { await this.load(); const index = this.store.queries.findIndex((q) => q.id === id); if (index === -1) { return false; } const deleted = this.store.queries.splice(index, 1)[0]; await this.save(); Logger.log(`[FLEX-QUERY-STORAGE] Deleted query: ${deleted.name} (ID: ${id})`); return true; } /** * Generate a unique ID for a query */ private generateId(): string { return `query_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Get the storage file path */ getStorageFilePath(): string { return this.storageFile; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/code-rabi/interactive-brokers-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server