Skip to main content
Glama

process_note_request

Process natural language requests to interact with Apple Notes, enabling users to create, update, delete, and search notes through text commands.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
textYes

Implementation Reference

  • Primary handler implementation for the 'process_note_request' MCP tool. Handles input validation, JSON unwrapping, natural language parsing, AppleScript generation and execution, and response formatting for Apple Notes operations.
    server.tool("process_note_request", { 
      text: z.string().min(1) 
    }, async ({ text }) => {
      try {
        // Handle JSON input if present
        let processedText = text;
        if (text.trim().startsWith('{') && text.includes('text')) {
          try {
            const jsonData = JSON.parse(text);
            if (jsonData.text) {
              processedText = jsonData.text;
            }
          } catch (err) {
            // Continue with original text
          }
        }
        
        // Process the text request
        const parsedRequest = parseNaturalLanguage(processedText);
        
        if (!parsedRequest) {
          return {
            content: [{ 
              type: "text", 
              text: "I couldn't understand your request. Try phrasing it like:\n- Create a note titled 'shopping list' with items: milk, eggs, bread\n- Add meeting notes to my 'work' note\n- Delete my 'old tasks' note\n- Search for notes with 'project'"
            }]
          };
        }
        
        // Execute the AppleScript
        const script = generateAppleScript(parsedRequest);
        const result = await executeAppleScript(script);
        
        // Format the response based on action type
        let responseText = "";
        switch (parsedRequest.action) {
          case 'create':
            responseText = `Successfully created note "${parsedRequest.title}"`;
            break;
          case 'update':
            responseText = `Successfully updated note "${parsedRequest.title}"`;
            break;
          case 'delete':
            responseText = `Successfully deleted note "${parsedRequest.title}"`;
            break;
          case 'search':
            if (result && result.trim()) {
              const notes = result.split(',').map(note => note.trim());
              responseText = `Found ${notes.length} note(s) matching "${parsedRequest.title}":\n${notes.map(note => `- ${note}`).join('\n')}`;
            } else {
              responseText = `No notes found matching "${parsedRequest.title}"`;
            }
            break;
        }
        
        return {
          content: [{ type: "text", text: responseText }]
        };
      } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        return {
          content: [{ type: "text", text: `Error: ${errorMessage}` }]
        };
      }
    });
  • Zod schema defining the internal NoteRequest structure used by the parser and AppleScript generator for validating parsed natural language inputs.
    const NoteRequestSchema = z.object({
      action: z.enum(['create', 'update', 'delete', 'search']),
      title: z.string().min(1),
      content: z.array(z.string()).optional(),
      folder: z.string().optional(),
    });
  • Helper function that parses natural language text into structured NoteRequest using regex patterns for create/update/delete/search actions.
    export function parseNaturalLanguage(text: string): NoteRequest | null {
      const lowerText = text.toLowerCase();
      
      // Handle long text content as note creation
      if (text.length > 100 && !text.includes('"') && !text.includes("'")) {
        const firstLine = text.split('\n')[0].trim();
        const title = firstLine.split(' ').slice(0, 5).join(' ');
        
        try {
          return NoteRequestSchema.parse({
            action: 'create',
            title: title,
            content: [text]
          });
        } catch (e) {
          // Continue to other patterns
        }
      }
      
      // Handle trip planning specifically
      if (lowerText.includes('trip') || lowerText.includes('travel') || lowerText.includes('vacation')) {
        const locationMatch = 
          text.match(/(?:trip|travel|vacation)(?:\s+(?:to|for|in))?\s+([A-Za-z\s,]+)/) || 
          text.match(/([A-Za-z\s,]+)(?:\s+(?:trip|travel|vacation))/);
        
        if (locationMatch) {
          const location = locationMatch[1].trim();
          const title = `Trip plan: ${location}`;
          
          try {
            return NoteRequestSchema.parse({
              action: 'create',
              title,
              content: [text]
            });
          } catch (e) {
            // Continue to other patterns
          }
        }
      }
      
      // Handle note creation commands
      const createPatterns = [
        /create\s+(?:a\s+)?note\s+(?:titled|called|named)\s+['"](.+?)['"](?:\s+with\s+(?:items|content|text)(?:\s*:|:|\s+of)?)?(?:\s+(.+))?/i,
        /(?:make|add)\s+(?:a\s+)?(?:new\s+)?note\s+(?:titled|called|named)\s+['"](.+?)['"](?:\s+with\s+(?:items|content|text)(?:\s*:|:|\s+of)?)?(?:\s+(.+))?/i,
        /(?:take|write)\s+(?:a\s+)?note\s+(?:titled|called|named)\s+['"](.+?)['"](?:\s+with\s+(?:items|content|text)(?:\s*:|:|\s+of)?)?(?:\s+(.+))?/i
      ];
    
      for (const pattern of createPatterns) {
        const match = text.match(pattern);
        if (match) {
          const title = match[1];
          
          let content: string[] = [];
          if (match[2]) {
            if (match[2].includes(',')) {
              content = match[2].split(',').map(item => item.trim());
            } else if (match[2].includes('\n')) {
              content = match[2].split('\n').map(item => item.trim().replace(/^[-•*]\s*/, ''));
            } else {
              content = [match[2].trim()];
            }
          }
          
          try {
            return NoteRequestSchema.parse({
              action: 'create',
              title,
              content
            });
          } catch (e) {
            return null;
          }
        }
      }
      
      // Handle update commands
      const updatePatterns = [
        /add\s+(.+?)\s+to\s+(?:my\s+)?['"](.+?)['"](?:\s+note)?/i,
        /update\s+(?:my\s+)?['"](.+?)['"](?:\s+note)?\s+(?:with|to(?:\s+add)?)\s+(.+)/i,
        /append\s+(.+?)\s+to\s+(?:my\s+)?['"](.+?)['"](?:\s+note)?/i
      ];
      
      for (const pattern of updatePatterns) {
        const match = text.match(pattern);
        if (match) {
          let content: string, title: string;
          
          if (lowerText.includes("update") && match[1]) {
            title = match[1];
            content = match[2];
          } else {
            content = match[1];
            title = match[2];
          }
          
          if (title && content) {
            try {
              return NoteRequestSchema.parse({
                action: 'update',
                title,
                content: [content.trim()]
              });
            } catch (e) {
              return null;
            }
          }
        }
      }
      
      // Handle delete commands
      const deletePatterns = [
        /delete\s+(?:my\s+)?(?:note\s+)?['"](.+?)['"](?:\s+note)?/i,
        /remove\s+(?:my\s+)?(?:note\s+)?['"](.+?)['"](?:\s+note)?/i,
        /trash\s+(?:my\s+)?(?:note\s+)?['"](.+?)['"](?:\s+note)?/i,
        /get\s+rid\s+of\s+(?:my\s+)?(?:note\s+)?['"](.+?)['"](?:\s+note)?/i
      ];
      
      for (const pattern of deletePatterns) {
        const match = text.match(pattern);
        if (match) {
          const title = match[1];
          try {
            return NoteRequestSchema.parse({
              action: 'delete',
              title
            });
          } catch (e) {
            return null;
          }
        }
      }
      
      // Handle search commands
      const searchPatterns = [
        /(?:search|find|look\s+for)\s+(?:my\s+)?(?:note\s+)?['"](.+?)['"](?:\s+note)?/i,
        /(?:search|find|look)\s+(?:for\s+)?(?:notes?\s+)?(?:with|containing|about)\s+['"](.+?)['"](?:\s+note)?/i
      ];
      
      for (const pattern of searchPatterns) {
        const match = text.match(pattern);
        if (match) {
          const title = match[1];
          try {
            return NoteRequestSchema.parse({
              action: 'search',
              title
            });
          } catch (e) {
            return null;
          }
        }
      }
      
      // Handle simple creation with quoted title
      if (lowerText.includes('create') || lowerText.includes('new') || lowerText.includes('make')) {
        const titleMatch = text.match(/['"](.+?)['"]/) || text.match(/note\s+(?:about|on|for)\s+(.+?)(?:\s|$)/i);
        if (titleMatch) {
          try {
            return NoteRequestSchema.parse({
              action: 'create',
              title: titleMatch[1],
              content: []
            });
          } catch (e) {
            return null;
          }
        }
      }
      
      return null;
    }
  • Helper function that generates AppleScript code for Apple Notes app based on the parsed NoteRequest for the respective action.
    export function generateAppleScript(request: NoteRequest): string {
      const safeTitle = request.title.replace(/"/g, '\\"');
      const safeContent = request.content?.map(item => item.replace(/"/g, '\\"')) || [];
      
      switch (request.action) {
        case 'create':
          return `
            tell application "Notes"
              set newNote to make new note with properties {name:"${safeTitle}"}
              ${safeContent.map(item => `tell newNote to make new paragraph at the end with data "${item}"`).join('\n') || ''}
            end tell
          `;
        
        case 'update':
          return `
            tell application "Notes"
              set noteFound to false
              repeat with theNote in notes
                if name of theNote is "${safeTitle}" then
                  set noteFound to true
                  tell theNote
                    ${safeContent.map(item => `make new paragraph at the end with data "${item}"`).join('\n') || ''}
                  end tell
                  exit repeat
                end if
              end repeat
              if not noteFound then
                set newNote to make new note with properties {name:"${safeTitle}"}
                ${safeContent.map(item => `tell newNote to make new paragraph at the end with data "${item}"`).join('\n') || ''}
              end if
            end tell
          `;
        
        case 'delete':
          return `
            tell application "Notes"
              set noteFound to false
              repeat with theNote in notes
                if name of theNote is "${safeTitle}" then
                  set noteFound to true
                  delete theNote
                  exit repeat
                end if
              end repeat
            end tell
          `;
        
        case 'search':
          return `
            tell application "Notes"
              set matchingNotes to {}
              repeat with theNote in notes
                if name of theNote contains "${safeTitle}" then
                  set end of matchingNotes to name of theNote
                end if
              end repeat
              return matchingNotes
            end tell
          `;
        
        default:
          return '';
      }
    }
  • Helper function that writes AppleScript to a temporary file and executes it using osascript, returning the output.
    export function executeAppleScript(script: string): Promise<string> {
      return new Promise((resolve, reject) => {
        const tempFile = `/tmp/notescript_${Date.now()}.scpt`;
        const writeCmd = `echo '${script.replace(/'/g, "'\\''")}' > ${tempFile}`;
        
        exec(writeCmd, writeErr => {
          if (writeErr) {
            return reject(`Error writing script: ${writeErr}`);
          }
          
          exec(`osascript ${tempFile}`, (execErr, stdout, stderr) => {
            exec(`rm ${tempFile}`);
            
            if (execErr) {
              return reject(`AppleScript execution error: ${stderr}`);
            }
            resolve(stdout);
          });
        });
      });
    }
Install Server

Other Tools

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/Rish-it/Notes-MCP'

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