Skip to main content
Glama
storage.ts19 kB
import { users, type User, type InsertUser, servers, type Server, type InsertServer, tools, type Tool, type InsertTool, apps, type App, type InsertApp, activities, type Activity, type InsertActivity } from "@shared/schema"; import { config } from './config'; import path from 'path'; import fs from 'fs/promises'; export interface IStorage { // User methods getUser(id: number): Promise<User | undefined>; getUserByUsername(username: string): Promise<User | undefined>; createUser(user: InsertUser): Promise<User>; // Server methods getServer(id: number): Promise<Server | undefined>; getServers(): Promise<Server[]>; getServerByPort(port: number): Promise<Server | undefined>; getServerByAddress(address: string, port: number): Promise<Server | undefined>; createServer(server: InsertServer): Promise<Server>; updateServer(id: number, server: Partial<InsertServer>): Promise<Server | undefined>; deleteServer(id: number): Promise<boolean>; // Tool methods getTool(id: number): Promise<Tool | undefined>; getTools(): Promise<Tool[]>; getToolsByServerId(serverId: number): Promise<Tool[]>; getToolByName(name: string, serverId: number): Promise<Tool | undefined>; createTool(tool: InsertTool): Promise<Tool>; updateTool(id: number, tool: Partial<InsertTool>): Promise<Tool | undefined>; deleteTool(id: number): Promise<boolean>; // App methods getApp(id: number): Promise<App | undefined>; getApps(): Promise<App[]>; getAppsByServerId(serverId: number): Promise<App[]>; createApp(app: InsertApp): Promise<App>; updateApp(id: number, app: Partial<InsertApp>): Promise<App | undefined>; deleteApp(id: number): Promise<boolean>; // Activity methods getActivities(limit?: number): Promise<Activity[]>; getActivitiesByServerId(serverId: number, limit?: number): Promise<Activity[]>; getActivitiesByToolId(toolId: number, limit?: number): Promise<Activity[]>; createActivity(activity: InsertActivity): Promise<Activity>; } export class MemStorage implements IStorage { private users: Map<number, User>; private servers: Map<number, Server>; private tools: Map<number, Tool>; private apps: Map<number, App>; private activities: Map<number, Activity>; private userIdCounter: number; private serverIdCounter: number; private toolIdCounter: number; private appIdCounter: number; private activityIdCounter: number; constructor() { this.users = new Map(); this.servers = new Map(); this.tools = new Map(); this.apps = new Map(); this.activities = new Map(); this.userIdCounter = 1; this.serverIdCounter = 1; this.toolIdCounter = 1; this.appIdCounter = 1; this.activityIdCounter = 1; // Always initialize sample data for memory storage this.initializeSampleData(); } private initializeSampleData() { // Create a single MCP Manager server using configuration values const mcpManager: Server = { id: this.serverIdCounter++, name: "MCP Manager", type: "local", address: "0.0.0.0", port: config.MCP_PORT_RANGE_START, // Use configured port range status: "active", cpuUsage: 0, memoryUsage: 0, totalMemory: config.MCP_SERVER_DEFAULT_MEMORY || 8, // Use configured memory models: ["Claude-3-Opus", "Claude-3-Sonnet", "Claude-3-Haiku", "GPT-4"], createdAt: new Date(), lastActive: new Date(), isWorker: true, // Mark as worker to enable communication // Additional fields required by schema repository: null, version: null, description: null, stars: null, forks: null, owner: null, smitheryPackage: null, apiKey: null, commandConfig: null }; this.servers.set(mcpManager.id, mcpManager); // Create a Smithery Package Manager tool const smitheryTool: Tool = { id: this.toolIdCounter++, name: "smithery_manager", description: "Tool for installing and managing Smithery MCP packages", shortDescription: "Manage Smithery packages", serverId: mcpManager.id, installed: true, active: true, categories: ["admin", "smithery"], inputSchema: { description: "Smithery package installation parameters", properties: { packageId: { type: "string", description: "The ID of the Smithery package to install" }, apiKey: { type: "string", description: "API key for authentication with the Smithery package" }, config: { type: "object", description: "Additional configuration for the Smithery package", additionalProperties: true } }, required: ["packageId"], type: "object" }, createdAt: new Date(), lastUsed: null }; this.tools.set(smitheryTool.id, smitheryTool); // Create a single initialization activity const initActivity = { id: this.activityIdCounter++, type: "success", message: `MCP Manager initialized`, serverId: mcpManager.id, appId: null, toolId: null, createdAt: new Date() }; this.activities.set(initActivity.id, initActivity as Activity); } // User methods async getUser(id: number): Promise<User | undefined> { return this.users.get(id); } async getUserByUsername(username: string): Promise<User | undefined> { return Array.from(this.users.values()).find( (user) => user.username === username, ); } async createUser(insertUser: InsertUser): Promise<User> { const id = this.userIdCounter++; const user: User = { ...insertUser, id }; this.users.set(id, user); return user; } // Server methods async getServer(id: number): Promise<Server | undefined> { return this.servers.get(id); } async getServers(): Promise<Server[]> { return Array.from(this.servers.values()); } async getServerByPort(port: number): Promise<Server | undefined> { return Array.from(this.servers.values()).find( (server) => server.port === port ); } async getServerByAddress(address: string, port: number): Promise<Server | undefined> { return Array.from(this.servers.values()).find( (server) => server.address === address && server.port === port ); } async createServer(insertServer: InsertServer): Promise<Server> { const id = this.serverIdCounter++; const server: Server = { ...insertServer, id, status: insertServer.status || "inactive", cpuUsage: insertServer.cpuUsage ?? 0, memoryUsage: insertServer.memoryUsage ?? 0, totalMemory: insertServer.totalMemory ?? 8, models: insertServer.models || [], isWorker: insertServer.isWorker ?? true, // Default to worker mode createdAt: new Date(), lastActive: new Date(), // Ensure all fields required by the schema are present repository: insertServer.repository || null, version: insertServer.version || null, description: insertServer.description || null, stars: insertServer.stars || null, forks: insertServer.forks || null, owner: insertServer.owner || null, smitheryPackage: insertServer.smitheryPackage || null, apiKey: insertServer.apiKey || null, commandConfig: insertServer.commandConfig || null }; this.servers.set(id, server); // Create activity log await this.createActivity({ type: "success", message: `Server '${server.name}' created successfully`, serverId: server.id, appId: null }); return server; } async updateServer(id: number, updatedFields: Partial<InsertServer>): Promise<Server | undefined> { const server = this.servers.get(id); if (!server) return undefined; const updatedServer: Server = { ...server, ...updatedFields, lastActive: new Date() }; this.servers.set(id, updatedServer); // Create activity log await this.createActivity({ type: "info", message: `Server '${server.name}' updated`, serverId: server.id, appId: null }); return updatedServer; } async deleteServer(id: number): Promise<boolean> { const server = this.servers.get(id); if (!server) return false; const success = this.servers.delete(id); // Create activity log if (success) { await this.createActivity({ type: "info", message: `Server '${server.name}' deleted`, serverId: null, appId: null }); } return success; } // Tool methods async getTool(id: number): Promise<Tool | undefined> { return this.tools.get(id); } async getTools(): Promise<Tool[]> { return Array.from(this.tools.values()); } async getToolsByServerId(serverId: number): Promise<Tool[]> { return Array.from(this.tools.values()).filter(tool => tool.serverId === serverId); } async getToolByName(name: string, serverId: number): Promise<Tool | undefined> { return Array.from(this.tools.values()).find( (tool) => tool.name === name && tool.serverId === serverId ); } async createTool(insertTool: InsertTool): Promise<Tool> { const id = this.toolIdCounter++; const tool: Tool = { ...insertTool, id, shortDescription: insertTool.shortDescription || null, active: insertTool.active || false, installed: insertTool.installed || false, categories: insertTool.categories || [], createdAt: new Date(), lastUsed: null }; this.tools.set(id, tool); // Create activity log await this.createActivity({ type: "success", message: `Tool '${tool.name}' created`, serverId: tool.serverId, appId: null, toolId: tool.id }); return tool; } async updateTool(id: number, updatedFields: Partial<InsertTool>): Promise<Tool | undefined> { const tool = this.tools.get(id); if (!tool) return undefined; const updatedTool: Tool = { ...tool, ...updatedFields, lastUsed: updatedFields.active ? new Date() : tool.lastUsed }; this.tools.set(id, updatedTool); // Create activity log await this.createActivity({ type: "info", message: `Tool '${tool.name}' updated`, serverId: tool.serverId, appId: null, toolId: tool.id }); return updatedTool; } async deleteTool(id: number): Promise<boolean> { const tool = this.tools.get(id); if (!tool) return false; const success = this.tools.delete(id); // Create activity log if (success) { await this.createActivity({ type: "info", message: `Tool '${tool.name}' deleted`, serverId: tool.serverId, appId: null, toolId: null }); } return success; } // App methods async getApp(id: number): Promise<App | undefined> { return this.apps.get(id); } async getApps(): Promise<App[]> { return Array.from(this.apps.values()); } async getAppsByServerId(serverId: number): Promise<App[]> { return Array.from(this.apps.values()).filter(app => app.serverId === serverId); } async createApp(insertApp: InsertApp): Promise<App> { const id = this.appIdCounter++; const app: App = { ...insertApp, id, status: insertApp.status || "inactive", version: insertApp.version || null, lastActive: new Date() }; this.apps.set(id, app); // Create activity log await this.createActivity({ type: "info", message: `App '${app.name}' connected`, serverId: app.serverId, appId: app.id }); return app; } async updateApp(id: number, updatedFields: Partial<InsertApp>): Promise<App | undefined> { const app = this.apps.get(id); if (!app) return undefined; const updatedApp: App = { ...app, ...updatedFields, lastActive: new Date() }; this.apps.set(id, updatedApp); return updatedApp; } async deleteApp(id: number): Promise<boolean> { const app = this.apps.get(id); if (!app) return false; const success = this.apps.delete(id); // Create activity log if (success) { await this.createActivity({ type: "info", message: `App '${app.name}' disconnected`, serverId: app.serverId, appId: null }); } return success; } // Activity methods async getActivities(limit?: number): Promise<Activity[]> { const activities = Array.from(this.activities.values()) .sort((a, b) => { if (a.createdAt instanceof Date && b.createdAt instanceof Date) { return b.createdAt.getTime() - a.createdAt.getTime(); } return 0; }); return limit ? activities.slice(0, limit) : activities; } async getActivitiesByServerId(serverId: number, limit?: number): Promise<Activity[]> { const activities = Array.from(this.activities.values()) .filter(activity => activity.serverId === serverId) .sort((a, b) => { if (a.createdAt instanceof Date && b.createdAt instanceof Date) { return b.createdAt.getTime() - a.createdAt.getTime(); } return 0; }); return limit ? activities.slice(0, limit) : activities; } async getActivitiesByToolId(toolId: number, limit?: number): Promise<Activity[]> { const activities = Array.from(this.activities.values()) .filter(activity => activity.toolId === toolId) .sort((a, b) => { if (a.createdAt instanceof Date && b.createdAt instanceof Date) { return b.createdAt.getTime() - a.createdAt.getTime(); } return 0; }); return limit ? activities.slice(0, limit) : activities; } async createActivity(insertActivity: InsertActivity): Promise<Activity> { const id = this.activityIdCounter++; const activity: Activity = { ...insertActivity, id, serverId: insertActivity.serverId ?? null, appId: insertActivity.appId ?? null, toolId: insertActivity.toolId ?? null, createdAt: new Date() }; this.activities.set(id, activity); return activity; } } export const storage = new MemStorage(); // File paths for persistent storage const DATA_DIR = path.resolve('./data'); const SERVERS_FILE = path.join(DATA_DIR, 'servers.json'); const ACTIVITIES_FILE = path.join(DATA_DIR, 'activities.json'); // Initialize storage async function initialize() { // Check if we're using memory storage (default to true if not defined) const useMemoryStorage = config.USE_MEMORY_STORAGE !== 'false'; if (!useMemoryStorage) { try { // Create data directory if it doesn't exist await fs.mkdir(DATA_DIR, { recursive: true }); // Load servers from file if exists try { const serversData = await fs.readFile(SERVERS_FILE, 'utf-8'); const loadedServers = JSON.parse(serversData) as Server[]; servers = loadedServers; // Find the max ID to set nextServerId nextServerId = Math.max(...servers.map(server => server.id), 0) + 1; console.log(`Loaded ${servers.length} servers from storage`); } catch (err) { // File doesn't exist or is invalid, use empty array console.log('No existing servers data found, starting with empty set'); servers = []; } // Load activities from file if exists try { const activitiesData = await fs.readFile(ACTIVITIES_FILE, 'utf-8'); const loadedActivities = JSON.parse(activitiesData) as Activity[]; activities = loadedActivities; // Find the max ID to set nextActivityId nextActivityId = Math.max(...activities.map(activity => activity.id), 0) + 1; console.log(`Loaded ${activities.length} activities from storage`); } catch (err) { // File doesn't exist or is invalid, use empty array console.log('No existing activities data found, starting with empty set'); activities = []; } } catch (error) { console.error('Error initializing storage:', error); // Fall back to memory storage console.log('Falling back to memory storage'); servers = []; activities = []; } } else { console.log('Using in-memory storage'); } } // Save data to files async function saveToFiles() { // Check if we're using memory storage (default to true if not defined) const useMemoryStorage = config.USE_MEMORY_STORAGE !== 'false'; if (!useMemoryStorage) { try { await fs.writeFile(SERVERS_FILE, JSON.stringify(servers, null, 2)); await fs.writeFile(ACTIVITIES_FILE, JSON.stringify(activities, null, 2)); } catch (error) { console.error('Error saving data to files:', error); } } } // Server operations async function getServers(): Promise<Server[]> { return [...servers]; } async function getServer(id: number): Promise<Server | undefined> { return servers.find(server => server.id === id); } async function createServer(data: InsertServer): Promise<Server> { const newServer: Server = { ...data, id: nextServerId++ }; servers.push(newServer); await saveToFiles(); return newServer; } async function updateServer(id: number, data: Partial<Server>): Promise<Server | undefined> { const index = servers.findIndex(server => server.id === id); if (index === -1) { return undefined; } servers[index] = { ...servers[index], ...data }; await saveToFiles(); return servers[index]; } async function deleteServer(id: number): Promise<boolean> { const initialLength = servers.length; servers = servers.filter(server => server.id !== id); const deleted = initialLength > servers.length; if (deleted) { await saveToFiles(); } return deleted; } // Activity operations async function getActivities(limit: number = 100, serverId?: number): Promise<Activity[]> { let result = [...activities]; if (serverId !== undefined) { result = result.filter(activity => activity.serverId === serverId); } // Sort by timestamp (most recent first) result.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); // Apply limit return result.slice(0, limit); } async function createActivity(data: InsertActivity): Promise<Activity> { const newActivity: Activity = { ...data, id: nextActivityId++, timestamp: new Date().toISOString() }; activities.push(newActivity); await saveToFiles(); return newActivity; } // Export storage interface export const storage = { initialize, getServers, getServer, createServer, updateServer, deleteServer, getActivities, createActivity };

Latest Blog Posts

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/samihalawa/2025-FINAL-mcpMaster'

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