Skip to main content
Glama

AI Code Toolkit

by AgiFlow
CursorService.ts10.7 kB
/** * CursorService * * DESIGN PATTERNS: * - Class-based service pattern for encapsulating business logic * - Interface implementation for dependency injection and testing * - Single Responsibility: Manages Cursor IDE interactions * - Method-based API: Public methods expose service capabilities * * CODING STANDARDS: * - Service class names use PascalCase with 'Service' suffix * - Method names use camelCase with descriptive verbs * - Return types should be explicit (never use implicit any) * - Use async/await for asynchronous operations * - Handle errors with try-catch and throw descriptive Error objects * - Document public methods with JSDoc comments * * AVOID: * - Side effects in constructors (keep them lightweight) * - Mixing concerns (keep services focused on single domain) * - Direct coupling to other services (use dependency injection) * - Exposing internal implementation details */ import * as fs from 'fs-extra'; import * as os from 'node:os'; import * as path from 'node:path'; import type { CodingAgentService, LlmInvocationParams, LlmInvocationResponse, McpSettings, PromptConfig, } from '../types'; import { appendUniqueToFile, appendUniqueWithMarkers } from '../utils/file'; /** * Cursor MCP server configuration format * Configured in ~/.cursor/mcp.json or workspace .cursor/mcp.json */ interface CursorMcpConfig { mcpServers: Record<string, CursorMcpServerConfig>; } /** * Cursor MCP server configuration * Supports stdio (local) servers */ interface CursorMcpServerConfig { command: string; args?: string[]; env?: Record<string, string>; } /** * Service for integrating with Cursor IDE * * Cursor is a fork of VS Code with AI-first features and supports: * * 1. **MCP Configuration** (~/.cursor/mcp.json or .cursor/mcp.json): * - Stdio-based MCP servers only * - JSON format: { "mcpServers": { "name": { "command": "...", "args": [...] } } } * - Can be configured globally (~/.cursor/mcp.json) or per-workspace * * 2. **Cursor Rules** (.cursor/rules/<project-name>.md): * - Custom instructions for AI behavior * - Supports frontmatter with applyTo glob patterns * - Applied to all AI interactions in the IDE * - Multiple rule files can be placed in .cursor/rules/ directory * - Format: * --- * applyTo: "**" * --- * Rule content here * * 3. **Direct link installation**: * - cursor://anysphere.cursor-deeplink/mcp/install?name=X&config=BASE64 * - Allows one-click MCP server installation * * Note: Cursor does not provide a CLI for LLM invocation. It operates through: * - IDE interface (chat panel, inline editing, composer) * - No programmatic API for external invocation * * @see https://docs.snyk.io/integrations/developer-guardrails-for-agentic-workflows/quickstart-guides-for-mcp/cursor-guide * @see https://docs.cursor.com/en/context/rules (if available) */ export class CursorService implements CodingAgentService { private mcpSettings: McpSettings = {}; private promptConfig: PromptConfig = {}; private readonly workspaceRoot: string; constructor(options?: { workspaceRoot?: string }) { this.workspaceRoot = options?.workspaceRoot || process.cwd(); } /** * Check if Cursor is enabled * Detects by checking for .cursor directory or .cursor/rules directory in workspace root */ async isEnabled(): Promise<boolean> { try { const cursorDir = path.join(this.workspaceRoot, '.cursor'); const cursorRulesDir = path.join(this.workspaceRoot, '.cursor', 'rules'); const hasCursorDir = await fs.pathExists(cursorDir); const hasCursorRulesDir = await fs.pathExists(cursorRulesDir); return hasCursorDir || hasCursorRulesDir; } catch (error) { // Handle file system errors gracefully if (error instanceof Error) { // Log specific error types that might need attention if (error.message.includes('EACCES') || error.message.includes('EPERM')) { console.warn( `Cursor detection warning: Permission denied accessing workspace at ${this.workspaceRoot}`, ); } } return false; } } /** * Update MCP (Model Context Protocol) settings for Cursor * * Supports two configuration modes: * * 1. **Global Mode** (default): Writes to ~/.cursor/mcp.json * - Applies to all Cursor workspaces * - Survives workspace changes * * 2. **Workspace Mode**: Writes to {workspaceRoot}/.cursor/mcp.json * - Applies only to current workspace * - Can override global settings * * @param settings - MCP server configurations * @param options - Configuration options * @param options.useWorkspaceConfig - If true, writes to workspace .cursor/mcp.json instead of global config * @throws Error if configuration cannot be written */ async updateMcpSettings( settings: McpSettings, options?: { useWorkspaceConfig?: boolean; }, ): Promise<void> { this.mcpSettings = { ...this.mcpSettings, ...settings }; try { const configDir = options?.useWorkspaceConfig ? path.join(this.workspaceRoot, '.cursor') : path.join(os.homedir(), '.cursor'); const configPath = path.join(configDir, 'mcp.json'); // Ensure config directory exists await fs.ensureDir(configDir); // Read existing config or create new let config: CursorMcpConfig = { mcpServers: {} }; if (await fs.pathExists(configPath)) { const content = await fs.readFile(configPath, 'utf-8'); config = JSON.parse(content); } // Ensure mcpServers key exists if (!config.mcpServers) { config.mcpServers = {}; } // Convert standardized MCP server configs to Cursor format if (settings.servers) { for (const [serverName, serverConfig] of Object.entries(settings.servers)) { // Skip disabled servers if (serverConfig.disabled) { delete config.mcpServers[serverName]; continue; } // Cursor only supports stdio servers if (serverConfig.type !== 'stdio') { console.warn( `Cursor MCP: Skipping server '${serverName}' - Cursor only supports stdio servers, got ${serverConfig.type}`, ); continue; } const cursorConfig: CursorMcpServerConfig = { command: serverConfig.command || '', }; if (serverConfig.args && serverConfig.args.length > 0) { cursorConfig.args = serverConfig.args; } if (serverConfig.env) { cursorConfig.env = serverConfig.env; } config.mcpServers[serverName] = cursorConfig; } } // Write config back with pretty formatting await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`); } catch (error) { throw new Error( `Failed to update Cursor MCP configuration: ${error instanceof Error ? error.message : String(error)}`, ); } } /** * Update prompt configuration for Cursor * * Writes system prompts to Cursor rules file at .cursor/rules/<project-name>.md * * Cursor rules support: * - Frontmatter with applyTo glob patterns * - Global rules (applyTo: "**") * - File-specific rules (applyTo: "*.ts") * - Multiple rule files in .cursor/rules/ directory * * If marker is true, wraps content with AICODE tracking markers. * * @param config - Prompt configuration with systemPrompt * @param options - Configuration options * @param options.applyTo - Glob pattern for rule application (default: "**" for all files) * @param options.filename - Custom filename for the rules file (default: derived from workspace name) * @throws Error if rules file cannot be written */ async updatePrompt( config: PromptConfig, options?: { applyTo?: string; filename?: string; }, ): Promise<void> { this.promptConfig = { ...this.promptConfig, ...config }; if (!config.systemPrompt) { return; } try { const rulesDir = path.join(this.workspaceRoot, '.cursor', 'rules'); // Derive filename from workspace name if not provided const workspaceName = path.basename(this.workspaceRoot); const filename = options?.filename || `${workspaceName}.md`; const rulesPath = path.join(rulesDir, filename); // Ensure .cursor/rules directory exists await fs.ensureDir(rulesDir); // Build frontmatter const applyTo = options?.applyTo || '**'; const frontmatter = `---\napplyTo: "${applyTo}"\n---\n\n`; // Build content with frontmatter const contentWithFrontmatter = `${frontmatter}${config.systemPrompt}`; if (config.marker) { // Use AICODE markers to track the prompt content await appendUniqueWithMarkers( rulesPath, contentWithFrontmatter, config.systemPrompt, `${frontmatter}# Cursor Rules\n\n<!-- AICODE:START -->\n${config.systemPrompt}\n<!-- AICODE:END -->\n`, ); } else { // Append prompt without markers const promptContent = `\n\n${contentWithFrontmatter}\n`; await appendUniqueToFile( rulesPath, promptContent, config.systemPrompt, `${frontmatter}# Cursor Rules\n${config.systemPrompt}\n`, ); } } catch (error) { throw new Error( `Failed to update Cursor prompt configuration: ${error instanceof Error ? error.message : String(error)}`, ); } } /** * Invoke Cursor as an LLM * * NOTE: Cursor does not provide a CLI interface for LLM invocation. * This method is not implemented as Cursor operates through: * * 1. IDE interface (chat panel, Cmd+K, Cmd+L composer) * 2. Inline editing and code completion * 3. No programmatic API for external invocation * * For programmatic access, consider using: * - Claude API directly (Cursor uses Claude models) * - OpenAI API (Cursor supports GPT models) * - Other AI coding tools with CLI support (Claude Code, Codex CLI, etc.) * * @throws Error indicating this operation is not supported */ async invokeAsLlm(_params: LlmInvocationParams): Promise<LlmInvocationResponse> { throw new Error( 'Failed to invoke Cursor as LLM: Cursor does not support direct LLM invocation via CLI. ' + 'Use Cursor through the IDE interface (chat panel, Cmd+K for inline edit, Cmd+L for composer) ' + 'or consider using Claude API, OpenAI API, or other CLI-based coding agents.', ); } }

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/AgiFlow/aicode-toolkit'

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