Skip to main content
Glama

OmniFocus MCP Enhanced

by jqlts1
scriptExecution.ts6.63 kB
import { exec } from 'child_process'; import { promisify } from 'util'; import { writeFileSync, unlinkSync, readFileSync } from 'fs'; import { join } from 'path'; import { tmpdir } from 'os'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import { existsSync } from 'fs'; const execAsync = promisify(exec); // Helper function to execute OmniFocus scripts export async function executeJXA(script: string): Promise<any[]> { try { // Write the script to a temporary file in the system temp directory const tempFile = join(tmpdir(), `jxa_script_${Date.now()}.js`); // Write the script to the temporary file writeFileSync(tempFile, script); // Execute the script using osascript const { stdout, stderr } = await execAsync(`osascript -l JavaScript ${tempFile}`); if (stderr) { console.error("Script stderr output:", stderr); } // Clean up the temporary file unlinkSync(tempFile); // Parse the output as JSON try { const result = JSON.parse(stdout); return result; } catch (e) { console.error("Failed to parse script output as JSON:", e); // If this contains a "Found X tasks" message, treat it as a successful non-JSON response if (stdout.includes("Found") && stdout.includes("tasks")) { return []; } return []; } } catch (error) { console.error("Failed to execute JXA script:", error); throw error; } } // Function to execute scripts in OmniFocus using the URL scheme // Update src/utils/scriptExecution.ts export async function executeOmniFocusScript(scriptPath: string, args?: any): Promise<any> { try { // Get the actual script path (existing code remains the same) let actualPath; if (scriptPath.startsWith('@')) { const scriptName = scriptPath.substring(1); const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const distPath = join(__dirname, '..', 'utils', 'omnifocusScripts', scriptName); const srcPath = join(__dirname, '..', '..', 'src', 'utils', 'omnifocusScripts', scriptName); if (existsSync(distPath)) { actualPath = distPath; } else if (existsSync(srcPath)) { actualPath = srcPath; } else { actualPath = join(__dirname, '..', 'omnifocusScripts', scriptName); } } else { actualPath = scriptPath; } // Read the script file let scriptContent = readFileSync(actualPath, 'utf8'); // If arguments are provided, inject them into the script if (args && Object.keys(args).length > 0) { const argsJson = JSON.stringify(args); // Inject parameters at the beginning of the script const parameterInjection = ` // Injected parameters const injectedArgs = ${argsJson}; const perspectiveName = injectedArgs.perspectiveName || null; const perspectiveId = injectedArgs.perspectiveId || null; const hideCompleted = injectedArgs.hideCompleted !== undefined ? injectedArgs.hideCompleted : true; const limit = injectedArgs.limit || 100; const includeBuiltIn = injectedArgs.includeBuiltIn !== undefined ? injectedArgs.includeBuiltIn : false; const includeSidebar = injectedArgs.includeSidebar !== undefined ? injectedArgs.includeSidebar : true; const format = injectedArgs.format || "detailed"; `; // Replace any hardcoded parameters in the script with injected ones scriptContent = scriptContent.replace( /let perspectiveName = "今日工作安排"; \/\/ Hardcode for testing/, 'let perspectiveName = injectedArgs.perspectiveName || null;' ); scriptContent = scriptContent.replace( /let perspectiveName = null;/, 'let perspectiveName = injectedArgs.perspectiveName || null;' ); scriptContent = scriptContent.replace( /let perspectiveId = null;/, 'let perspectiveId = injectedArgs.perspectiveId || null;' ); scriptContent = scriptContent.replace( /let hideCompleted = true;/, 'let hideCompleted = injectedArgs.hideCompleted !== undefined ? injectedArgs.hideCompleted : true;' ); scriptContent = scriptContent.replace( /let limit = 100;/, 'let limit = injectedArgs.limit || 100;' ); scriptContent = scriptContent.replace( /let includeBuiltIn = false;/, 'let includeBuiltIn = injectedArgs.includeBuiltIn !== undefined ? injectedArgs.includeBuiltIn : false;' ); scriptContent = scriptContent.replace( /let includeSidebar = true;/, 'let includeSidebar = injectedArgs.includeSidebar !== undefined ? injectedArgs.includeSidebar : true;' ); scriptContent = scriptContent.replace( /let format = "detailed";/, 'let format = injectedArgs.format || "detailed";' ); // Inject the parameters at the beginning of the function scriptContent = scriptContent.replace( '(() => {', `(() => { ${parameterInjection}` ); } // Create a temporary file for our JXA wrapper script const tempFile = join(tmpdir(), `jxa_wrapper_${Date.now()}.js`); // Escape the script content properly for use in JXA const escapedScript = scriptContent.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$'); // Create a JXA script that will execute our OmniJS script in OmniFocus const jxaScript = ` function run() { try { const app = Application('OmniFocus'); app.includeStandardAdditions = true; // Run the OmniJS script in OmniFocus and capture the output const result = app.evaluateJavascript(\`${escapedScript}\`); // Return the result return result; } catch (e) { return JSON.stringify({ error: e.message }); } } `; // Write the JXA script to the temporary file writeFileSync(tempFile, jxaScript); // Execute the JXA script using osascript const { stdout, stderr } = await execAsync(`osascript -l JavaScript ${tempFile}`); // Clean up the temporary file unlinkSync(tempFile); if (stderr) { console.error("Script stderr output:", stderr); } // Parse the output as JSON try { return JSON.parse(stdout); } catch (parseError) { console.error("Error parsing script output:", parseError); return stdout; } } catch (error) { console.error("Failed to execute OmniFocus script:", error); throw error; } }

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/jqlts1/omnifocus-mcp-enhanced'

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