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);
          });
        });
      });
    }
Behavior1/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Tool has no description.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness1/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Tool has no description.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness1/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Tool has no description.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Tool has no description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose1/5

Does the description clearly state what the tool does and how it differs from similar tools?

Tool has no description.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines1/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Tool has no description.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

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