Skip to main content
Glama
cursorShared.ts5.66 kB
import fs from 'fs'; import path from 'path'; import os from 'os'; import axios from 'axios'; import { setTimeout as delay } from 'timers/promises'; interface CursorConfigSnapshotEntry { path: string; existed: boolean; originalContent?: string; } export interface CursorConfigSnapshot { files: CursorConfigSnapshotEntry[]; } export interface CursorConfigOptions { configPath: string; serverKey: string; url: string; projectConfigPath?: string; } export function expandHome(p: string): string { if (!p.startsWith('~')) return path.resolve(p); return path.resolve(path.join(os.homedir(), p.slice(1))); } export async function ensureCursorConfig({ configPath, serverKey, url, projectConfigPath, }: CursorConfigOptions): Promise<CursorConfigSnapshot> { const targets = [configPath, projectConfigPath].filter((entry): entry is string => Boolean(entry) ); const files: CursorConfigSnapshotEntry[] = []; for (const target of targets) { files.push(await ensureSingleConfig(target, serverKey, url)); } return { files }; } export async function restoreCursorConfig(snapshot: CursorConfigSnapshot): Promise<void> { for (const entry of snapshot.files) { const { path: filePath, existed, originalContent } = entry; try { if (existed) { if (originalContent !== undefined) { await fs.promises.writeFile(filePath, originalContent, 'utf8'); } } else if (await fileExists(filePath)) { await fs.promises.unlink(filePath); } } catch (err) { console.warn('⚠️ Failed to restore Cursor config file:', (err as Error).message); } } } export async function waitForCursorConnection({ startupTimeoutSec, expectedMatch = 'cursor', }: { startupTimeoutSec: number; expectedMatch?: string; }): Promise<void> { const timeoutMs = startupTimeoutSec * 1000; const deadline = Date.now() + timeoutMs; const match = expectedMatch.toLowerCase(); let lastSeenSignature: string | undefined; console.log(`→ Waiting for Cursor agent (${match}) to connect to MCPX`); while (Date.now() < deadline) { try { const response = await axios.get('http://localhost:9000/system-state', { timeout: 5_000, }); const clients = response.data?.connectedClients; if (Array.isArray(clients)) { const found = clients.some((client: any) => { const name = typeof client?.clientInfo?.name === 'string' ? client.clientInfo.name : ''; const tag = typeof client?.consumerTag === 'string' ? client.consumerTag : ''; return name.toLowerCase().includes(match) || tag.toLowerCase().includes(match); }); if (found) { console.log('✅ Cursor agent connected'); return; } const signature = JSON.stringify( clients.map((client: any) => ({ name: typeof client?.clientInfo?.name === 'string' ? client.clientInfo.name : undefined, tag: typeof client?.consumerTag === 'string' ? client.consumerTag : undefined, })) ); if (signature && signature !== lastSeenSignature) { console.log('ℹ️ Connected clients present but no cursor match yet:', signature); lastSeenSignature = signature; } } } catch (err: any) { if (err?.code !== 'ECONNREFUSED') { console.debug('Polling system-state failed:', err?.message ?? err); } } await delay(2_000); } throw new Error(`Timed out waiting ${startupTimeoutSec}s for Cursor agent to connect to MCPX`); } async function ensureDirectory(dir: string): Promise<void> { if (!(await directoryExists(dir))) { await fs.promises.mkdir(dir, { recursive: true }); } } async function fileExists(file: string): Promise<boolean> { try { await fs.promises.access(file, fs.constants.F_OK); return true; } catch { return false; } } async function directoryExists(dir: string): Promise<boolean> { try { const stat = await fs.promises.stat(dir); return stat.isDirectory(); } catch { return false; } } async function ensureSingleConfig( filePath: string, serverKey: string, url: string ): Promise<CursorConfigSnapshotEntry> { await ensureDirectory(path.dirname(filePath)); let originalContent: string | undefined; let existed = false; let existingConfig: Record<string, any> = {}; if (await fileExists(filePath)) { existed = true; originalContent = await fs.promises.readFile(filePath, 'utf8'); try { existingConfig = JSON.parse(originalContent); } catch (err) { throw new Error( `Failed to parse existing Cursor config at ${filePath}: ${(err as Error).message}` ); } } const servers = existingConfig.mcpServers && typeof existingConfig.mcpServers === 'object' ? { ...existingConfig.mcpServers } : {}; const existingEntry = typeof servers[serverKey] === 'object' && servers[serverKey] !== null ? { ...servers[serverKey] } : {}; const headers = typeof existingEntry.headers === 'object' && existingEntry.headers !== null ? { ...existingEntry.headers } : {}; if (!headers['x-lunar-consumer-tag']) { headers['x-lunar-consumer-tag'] = 'Cursor'; } servers[serverKey] = { ...existingEntry, url, headers, }; const updated = { ...existingConfig, mcpServers: servers, }; const newContent = JSON.stringify(updated, null, 2) + '\n'; if (newContent !== originalContent) { await fs.promises.writeFile(filePath, newContent, 'utf8'); } return { path: filePath, existed, originalContent }; }

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/TheLunarCompany/lunar'

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