#
tokens: 21765/50000 5/5 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── bun.lock
├── index.ts
├── install.sh
├── package.json
└── README.md
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | node_modules/
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Claude Outlook MCP Tool
  2 | 
  3 | This is a Model Context Protocol (MCP) tool that allows Claude to interact with Microsoft Outlook for macOS.
  4 | 
  5 | <a href="https://glama.ai/mcp/servers/0j71n92wnh">
  6 |   <img width="380" height="200" src="https://glama.ai/mcp/servers/0j71n92wnh/badge" alt="Claude Outlook Tool MCP server" />
  7 | </a>
  8 | 
  9 | ## Features
 10 | 
 11 | - Mail:
 12 |   - Read unread and regular emails
 13 |   - Search emails by keywords
 14 |   - Send emails with to, cc, and bcc recipients
 15 |   - **Send HTML-formatted emails**
 16 |   - **Attach files to emails**
 17 |   - List mail folders
 18 | - Calendar:
 19 |   - View today's events
 20 |   - View upcoming events
 21 |   - Search for events
 22 |   - Create new calendar events
 23 | - Contacts:
 24 |   - List contacts
 25 |   - Search contacts by name
 26 | 
 27 | ## Prerequisites
 28 | 
 29 | - macOS with Apple Silicon (M1/M2/M3) or Intel chip
 30 | - [Microsoft Outlook for Mac](https://apps.apple.com/us/app/microsoft-outlook/id985367838) installed and configured
 31 | - [Bun](https://bun.sh/) installed
 32 | - [Claude desktop app](https://claude.ai/desktop) installed
 33 | 
 34 | ## Installation
 35 | 
 36 | 1. Clone this repository:
 37 | 
 38 | ```bash
 39 | git clone https://github.com/syedazharmbnr1/claude-outlook-mcp.git
 40 | cd claude-outlook-mcp
 41 | ```
 42 | 
 43 | 2. Install dependencies:
 44 | 
 45 | ```bash
 46 | bun install
 47 | ```
 48 | 
 49 | 3. Make sure the script is executable:
 50 | 
 51 | ```bash
 52 | chmod +x index.ts
 53 | ```
 54 | 
 55 | 4. Update your Claude Desktop configuration:
 56 | 
 57 | Edit your `claude_desktop_config.json` file (located at `~/Library/Application Support/Claude/claude_desktop_config.json`) to include this tool:
 58 | 
 59 | ```json
 60 | {
 61 |   "mcpServers": {
 62 |     "outlook-mcp": {
 63 |       "command": "/Users/YOURUSERNAME/.bun/bin/bun",
 64 |       "args": ["run", "/path/to/claude-outlook-mcp/index.ts"]
 65 |     }
 66 |   }
 67 | }
 68 | ```
 69 | 
 70 | Make sure to replace `YOURUSERNAME` with your actual macOS username and adjust the path to where you cloned this repository.
 71 | 
 72 | 5. Restart Claude Desktop app
 73 | 
 74 | 6. Grant permissions:
 75 |    - Go to System Preferences > Privacy & Security > Privacy
 76 |    - Give Terminal (or your preferred terminal app) access to Accessibility features
 77 |    - You may see permission prompts when the tool is first used
 78 | 
 79 | ## Usage
 80 | 
 81 | Once installed, you can use the Outlook tool directly from Claude by asking questions like:
 82 | 
 83 | - "Can you check my unread emails in Outlook?"
 84 | - "Search my Outlook emails for the quarterly report"
 85 | - "Send an email to [email protected] with the subject 'Meeting Tomorrow'"
 86 | - "What's on my calendar today?"
 87 | - "Create a meeting for tomorrow at 2pm"
 88 | - "Find the contact information for Jane Smith"
 89 | 
 90 | ## Examples
 91 | 
 92 | ### Email Operations
 93 | 
 94 | ```
 95 | Check my unread emails in Outlook
 96 | ```
 97 | 
 98 | ```
 99 | Send an email to [email protected] with subject "Project Update" and the following body: Here's the latest update on our project. We've completed phase 1 and are moving on to phase 2.
100 | ```
101 | 
102 | ```
103 | Send an HTML email to [email protected] with subject "Weekly Report" and attach the quarterly_results.pdf file
104 | ```
105 | 
106 | ```
107 | Search my emails for "budget meeting"
108 | ```
109 | 
110 | ### Calendar Operations
111 | 
112 | ```
113 | What events do I have today?
114 | ```
115 | 
116 | ```
117 | Create a calendar event for a team meeting tomorrow from 2pm to 3pm
118 | ```
119 | 
120 | ```
121 | Show me my upcoming events for the next 2 weeks
122 | ```
123 | 
124 | ### Contact Operations
125 | 
126 | ```
127 | List all my Outlook contacts
128 | ```
129 | 
130 | ```
131 | Search for contact information for Jane Smith
132 | ```
133 | 
134 | ## Advanced Features
135 | 
136 | ### HTML Email Support
137 | 
138 | You can send rich HTML-formatted emails by setting the `isHtml` parameter to true:
139 | 
140 | ```
141 | Send an HTML email to [email protected] with the subject "Project Update" and body "<h1>Project Update</h1><p>We've made <b>significant progress</b> on the project.</p>"
142 | ```
143 | 
144 | ### File Attachments
145 | 
146 | You can attach files to your emails by providing the file paths in the `attachments` parameter:
147 | 
148 | ```
149 | Send an email to [email protected] with subject "Monthly Report" and attach the reports/march_2025.pdf file
150 | ```
151 | 
152 | For best results with attachments:
153 | - Use absolute file paths when possible
154 | - Make sure the files are accessible to the process running the MCP tool
155 | - Attachments will automatically be handled with robust error detection
156 | 
157 | ## Troubleshooting
158 | 
159 | If you encounter issues with attachments:
160 | - Check if the file exists and is readable
161 | - Use absolute file paths instead of relative paths
162 | - Make sure the user running the process has permission to read the file
163 | 
164 | If you encounter the error `Cannot find module '@modelcontextprotocol/sdk/server/index.js'`:
165 | 
166 | 1. Make sure you've run `bun install` to install all dependencies
167 | 2. Try installing the MCP SDK explicitly:
168 |    ```bash
169 |    bun add @modelcontextprotocol/sdk@^1.5.0
170 |    ```
171 | 3. Check if the module exists in your node_modules directory:
172 |    ```bash
173 |    ls -la node_modules/@modelcontextprotocol/sdk/server/
174 |    ```
175 | 
176 | If the error persists, try creating a new project with Bun:
177 | 
178 | ```bash
179 | mkdir -p ~/yourpath/claude-outlook-mcp
180 | cd ~/yourpath/claude-outlook-mcp
181 | bun init -y
182 | ```
183 | 
184 | Then copy the package.json and index.ts files to the new directory and run:
185 | 
186 | ```bash
187 | bun install
188 | bun run index.ts
189 | ```
190 | 
191 | Update your claude_desktop_config.json to point to the new location.
192 | 
193 | ## License
194 | 
195 | MIT
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |     "name": "claude-outlook-mcp",
 3 |     "version": "1.0.0",
 4 |     "module": "index.ts",
 5 |     "type": "module",
 6 |     "description": "A Claude MCP tool to interact with Microsoft Outlook for macOS",
 7 |     "author": "Syed Azhar",
 8 |     "license": "MIT",
 9 |     "repository": {
10 |       "type": "git",
11 |       "url": "git+https://github.com/syedazharmbnr1/claude-outlook-mcp.git"
12 |     },
13 |     "keywords": [
14 |       "mcp",
15 |       "claude",
16 |       "outlook",
17 |       "microsoft365"
18 |     ],
19 |     "scripts": {
20 |       "dev": "bun run index.ts",
21 |       "start": "bun run index.ts"
22 |     },
23 |     "dependencies": {
24 |       "@jxa/global-type": "^1.3.6",
25 |       "@jxa/run": "^1.3.6",
26 |       "@modelcontextprotocol/sdk": "^1.5.0",
27 |       "run-applescript": "^7.0.0"
28 |     },
29 |     "devDependencies": {
30 |       "@types/bun": "latest",
31 |       "@types/node": "^22.13.4"
32 |     }
33 |   }
```

--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | 
 3 | # Colors for output
 4 | GREEN='\033[0;32m'
 5 | YELLOW='\033[1;33m'
 6 | RED='\033[0;31m'
 7 | NC='\033[0m' # No Color
 8 | 
 9 | echo -e "${GREEN}Installing Claude Outlook MCP Tool...${NC}"
10 | 
11 | # Check if Bun is installed
12 | if ! command -v bun &> /dev/null; then
13 |     echo -e "${RED}Bun is not installed. Please install Bun first:${NC}"
14 |     echo -e "${YELLOW}curl -fsSL https://bun.sh/install | bash${NC}"
15 |     exit 1
16 | fi
17 | 
18 | # Install dependencies
19 | echo -e "${GREEN}Installing dependencies...${NC}"
20 | bun install
21 | 
22 | if [ $? -ne 0 ]; then
23 |     echo -e "${RED}Failed to install dependencies. Trying with explicit MCP SDK...${NC}"
24 |     bun add @modelcontextprotocol/sdk@^1.5.0
25 |     bun install
26 | fi
27 | 
28 | # Make script executable
29 | chmod +x index.ts
30 | 
31 | # Get current username
32 | USERNAME=$(whoami)
33 | INSTALL_PATH=$(pwd)
34 | 
35 | # Create claude_desktop_config.json snippet
36 | CONFIG_SNIPPET=$(cat << EOF
37 | {
38 |   "mcpServers": {
39 |     "outlook-mcp": {
40 |       "command": "/Users/$USERNAME/.bun/bin/bun",
41 |       "args": ["run", "$INSTALL_PATH/index.ts"]
42 |     }
43 |   }
44 | }
45 | EOF
46 | )
47 | 
48 | echo -e "${GREEN}Installation complete!${NC}"
49 | echo -e "${YELLOW}Please add the following to your Claude Desktop config file at:${NC}"
50 | echo -e "${YELLOW}~/Library/Application Support/Claude/claude_desktop_config.json${NC}"
51 | echo ""
52 | echo -e "${GREEN}$CONFIG_SNIPPET${NC}"
53 | echo ""
54 | echo -e "${YELLOW}Don't forget to restart Claude Desktop app after making these changes.${NC}"
55 | echo -e "${YELLOW}You may need to grant Terminal access to Accessibility features in System Preferences.${NC}"
```

--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------

```typescript
   1 | #!/usr/bin/env bun
   2 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
   3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
   4 | import {
   5 |   CallToolRequestSchema,
   6 |   ListToolsRequestSchema,
   7 |   type Tool,
   8 | } from "@modelcontextprotocol/sdk/types.js";
   9 | import { runAppleScript } from 'run-applescript';
  10 | 
  11 | // ====================================================
  12 | // 1. Tool Definitions
  13 | // ====================================================
  14 | 
  15 | // Define Outlook Mail tool
  16 | const OUTLOOK_MAIL_TOOL: Tool = {
  17 |   name: "outlook_mail",
  18 |   description: "Interact with Microsoft Outlook for macOS - read, search, send, and manage emails",
  19 |   inputSchema: {
  20 |     type: "object",
  21 |     properties: {
  22 |       operation: {
  23 |         type: "string",
  24 |         description: "Operation to perform: 'unread', 'search', 'send', 'folders', or 'read'",
  25 |         enum: ["unread", "search", "send", "folders", "read"]
  26 |       },
  27 |       folder: {
  28 |         type: "string",
  29 |         description: "Email folder to use (optional - if not provided, uses inbox or searches across all folders)"
  30 |       },
  31 |       limit: {
  32 |         type: "number",
  33 |         description: "Number of emails to retrieve (optional, for unread, read, and search operations)"
  34 |       },
  35 |       searchTerm: {
  36 |         type: "string",
  37 |         description: "Text to search for in emails (required for search operation)"
  38 |       },
  39 |       to: {
  40 |         type: "string",
  41 |         description: "Recipient email address (required for send operation)"
  42 |       },
  43 |       subject: {
  44 |         type: "string",
  45 |         description: "Email subject (required for send operation)"
  46 |       },
  47 |       body: {
  48 |         type: "string",
  49 |         description: "Email body content (required for send operation)"
  50 |       },
  51 |       isHtml: {
  52 |         type: "boolean",
  53 |         description: "Whether the body content is HTML (optional for send operation, default: false)"
  54 |       },
  55 |       cc: {
  56 |         type: "string",
  57 |         description: "CC email address (optional for send operation)"
  58 |       },
  59 |       bcc: {
  60 |         type: "string",
  61 |         description: "BCC email address (optional for send operation)"
  62 |       },
  63 |       attachments: {
  64 |         type: "array",
  65 |         description: "File paths to attach to the email (optional for send operation)",
  66 |         items: {
  67 |           type: "string"
  68 |         }
  69 |       }
  70 |     },
  71 |     required: ["operation"]
  72 |   }
  73 | };
  74 | 
  75 | // Define Outlook Calendar tool
  76 | const OUTLOOK_CALENDAR_TOOL: Tool = {
  77 |   name: "outlook_calendar",
  78 |   description: "Interact with Microsoft Outlook for macOS calendar - view, create, and manage events",
  79 |   inputSchema: {
  80 |     type: "object",
  81 |     properties: {
  82 |       operation: {
  83 |         type: "string",
  84 |         description: "Operation to perform: 'today', 'upcoming', 'search', or 'create'",
  85 |         enum: ["today", "upcoming", "search", "create"]
  86 |       },
  87 |       searchTerm: {
  88 |         type: "string",
  89 |         description: "Text to search for in events (required for search operation)"
  90 |       },
  91 |       limit: {
  92 |         type: "number",
  93 |         description: "Number of events to retrieve (optional, for today and upcoming operations)"
  94 |       },
  95 |       days: {
  96 |         type: "number",
  97 |         description: "Number of days to look ahead (optional, for upcoming operation, default: 7)"
  98 |       },
  99 |       subject: {
 100 |         type: "string",
 101 |         description: "Event subject/title (required for create operation)"
 102 |       },
 103 |       start: {
 104 |         type: "string",
 105 |         description: "Start time in ISO format (required for create operation)"
 106 |       },
 107 |       end: {
 108 |         type: "string",
 109 |         description: "End time in ISO format (required for create operation)"
 110 |       },
 111 |       location: {
 112 |         type: "string",
 113 |         description: "Event location (optional for create operation)"
 114 |       },
 115 |       body: {
 116 |         type: "string",
 117 |         description: "Event description/body (optional for create operation)"
 118 |       },
 119 |       attendees: {
 120 |         type: "string",
 121 |         description: "Comma-separated list of attendee email addresses (optional for create operation)"
 122 |       }
 123 |     },
 124 |     required: ["operation"]
 125 |   }
 126 | };
 127 | 
 128 | // Define Outlook Contacts tool
 129 | const OUTLOOK_CONTACTS_TOOL: Tool = {
 130 |   name: "outlook_contacts",
 131 |   description: "Search and retrieve contacts from Microsoft Outlook for macOS",
 132 |   inputSchema: {
 133 |     type: "object",
 134 |     properties: {
 135 |       operation: {
 136 |         type: "string",
 137 |         description: "Operation to perform: 'list' or 'search'",
 138 |         enum: ["list", "search"]
 139 |       },
 140 |       searchTerm: {
 141 |         type: "string",
 142 |         description: "Text to search for in contacts (required for search operation)"
 143 |       },
 144 |       limit: {
 145 |         type: "number",
 146 |         description: "Number of contacts to retrieve (optional)"
 147 |       }
 148 |     },
 149 |     required: ["operation"]
 150 |   }
 151 | };
 152 | 
 153 | // ====================================================
 154 | // 2. Server Setup
 155 | // ====================================================
 156 | 
 157 | console.error("Starting Outlook MCP server...");
 158 | 
 159 | const server = new Server(
 160 |   {
 161 |     name: "Outlook MCP Tool",
 162 |     version: "1.0.0",
 163 |   },
 164 |   {
 165 |     capabilities: {
 166 |       tools: {},
 167 |     },
 168 |   }
 169 | );
 170 | 
 171 | // ====================================================
 172 | // 3. Core Functions
 173 | // ====================================================
 174 | 
 175 | // Check if Outlook is installed and running
 176 | async function checkOutlookAccess(): Promise<boolean> {
 177 |   console.error("[checkOutlookAccess] Checking if Outlook is accessible...");
 178 |   try {
 179 |     const isInstalled = await runAppleScript(`
 180 |       tell application "System Events"
 181 |         set outlookExists to exists application process "Microsoft Outlook"
 182 |         return outlookExists
 183 |       end tell
 184 |     `);
 185 | 
 186 |     if (isInstalled !== "true") {
 187 |       console.error("[checkOutlookAccess] Microsoft Outlook is not installed or running");
 188 |       throw new Error("Microsoft Outlook is not installed or running on this system");
 189 |     }
 190 |     
 191 |     const isRunning = await runAppleScript(`
 192 |       tell application "System Events"
 193 |         set outlookRunning to application process "Microsoft Outlook" exists
 194 |         return outlookRunning
 195 |       end tell
 196 |     `);
 197 | 
 198 |     if (isRunning !== "true") {
 199 |       console.error("[checkOutlookAccess] Microsoft Outlook is not running, attempting to launch...");
 200 |       try {
 201 |         await runAppleScript(`
 202 |           tell application "Microsoft Outlook" to activate
 203 |           delay 2
 204 |         `);
 205 |         console.error("[checkOutlookAccess] Launched Outlook successfully");
 206 |       } catch (activateError) {
 207 |         console.error("[checkOutlookAccess] Error activating Microsoft Outlook:", activateError);
 208 |         throw new Error("Could not activate Microsoft Outlook. Please start it manually.");
 209 |       }
 210 |     } else {
 211 |       console.error("[checkOutlookAccess] Microsoft Outlook is already running");
 212 |     }
 213 |     
 214 |     return true;
 215 |   } catch (error) {
 216 |     console.error("[checkOutlookAccess] Outlook access check failed:", error);
 217 |     throw new Error(
 218 |       `Cannot access Microsoft Outlook. Please make sure Outlook is installed and properly configured. Error: ${error instanceof Error ? error.message : String(error)}`
 219 |     );
 220 |   }
 221 | }
 222 | 
 223 | // ====================================================
 224 | // 4. EMAIL FUNCTIONS
 225 | // ====================================================
 226 | 
 227 | // Function to get unread emails
 228 | async function getUnreadEmails(folder: string = "Inbox", limit: number = 10): Promise<any[]> {
 229 |   console.error(`[getUnreadEmails] Getting unread emails from folder: ${folder}, limit: ${limit}`);
 230 |   await checkOutlookAccess();
 231 |   
 232 |   const folderPath = folder === "Inbox" ? "inbox" : folder;
 233 |   const script = `
 234 |     tell application "Microsoft Outlook"
 235 |       try
 236 |         set theFolder to ${folderPath} -- Use the specified folder or default to inbox
 237 |         set unreadMessages to {}
 238 |         set allMessages to messages of theFolder
 239 |         set i to 0
 240 |         
 241 |         repeat with theMessage in allMessages
 242 |           if read status of theMessage is false then
 243 |             set i to i + 1
 244 |             set msgData to {subject:subject of theMessage, sender:sender of theMessage, ¬
 245 |                        date:time sent of theMessage, id:id of theMessage}
 246 |             
 247 |             -- Try to get content
 248 |             try
 249 |               set msgContent to content of theMessage
 250 |               if length of msgContent > 500 then
 251 |                 set msgContent to (text 1 thru 500 of msgContent) & "..."
 252 |               end if
 253 |               set msgData to msgData & {content:msgContent}
 254 |             on error
 255 |               set msgData to msgData & {content:"[Content not available]"}
 256 |             end try
 257 |             
 258 |             set end of unreadMessages to msgData
 259 |             
 260 |             -- Stop if we've reached the limit
 261 |             if i >= ${limit} then
 262 |               exit repeat
 263 |             end if
 264 |           end if
 265 |         end repeat
 266 |         
 267 |         return unreadMessages
 268 |       on error errMsg
 269 |         return "Error: " & errMsg
 270 |       end try
 271 |     end tell
 272 |   `;
 273 |   
 274 |   try {
 275 |     const result = await runAppleScript(script);
 276 |     console.error(`[getUnreadEmails] Raw result length: ${result.length}`);
 277 |     
 278 |     // Parse the results (AppleScript returns records as text)
 279 |     if (result.startsWith("Error:")) {
 280 |       throw new Error(result);
 281 |     }
 282 |     
 283 |     // Simple parsing for demonstration
 284 |     // In a production environment, you'd want more robust parsing
 285 |     const emails = [];
 286 |     const matches = result.match(/\{([^}]+)\}/g);
 287 |     
 288 |     if (matches && matches.length > 0) {
 289 |       for (const match of matches) {
 290 |         try {
 291 |           const props = match.substring(1, match.length - 1).split(',');
 292 |           const email: any = {};
 293 |           
 294 |           props.forEach(prop => {
 295 |             const parts = prop.split(':');
 296 |             if (parts.length >= 2) {
 297 |               const key = parts[0].trim();
 298 |               const value = parts.slice(1).join(':').trim();
 299 |               email[key] = value;
 300 |             }
 301 |           });
 302 |           
 303 |           if (email.subject || email.sender) {
 304 |             emails.push({
 305 |               subject: email.subject || "No subject",
 306 |               sender: email.sender || "Unknown sender",
 307 |               dateSent: email.date || new Date().toString(),
 308 |               content: email.content || "[Content not available]",
 309 |               id: email.id || ""
 310 |             });
 311 |           }
 312 |         } catch (parseError) {
 313 |           console.error('[getUnreadEmails] Error parsing email match:', parseError);
 314 |         }
 315 |       }
 316 |     }
 317 |     
 318 |     console.error(`[getUnreadEmails] Found ${emails.length} unread emails`);
 319 |     return emails;
 320 |   } catch (error) {
 321 |     console.error("[getUnreadEmails] Error getting unread emails:", error);
 322 |     throw error;
 323 |   }
 324 | }
 325 | 
 326 | // Function to search emails
 327 | async function searchEmails(searchTerm: string, folder: string = "Inbox", limit: number = 10): Promise<any[]> {
 328 |   console.error(`[searchEmails] Searching for "${searchTerm}" in folder: ${folder}, limit: ${limit}`);
 329 |   await checkOutlookAccess();
 330 |   
 331 |   const folderPath = folder === "Inbox" ? "inbox" : folder;
 332 |   const script = `
 333 |     tell application "Microsoft Outlook"
 334 |       try
 335 |         set theFolder to ${folderPath}
 336 |         set searchResults to {}
 337 |         set allMessages to messages of theFolder
 338 |         set i to 0
 339 |         set searchString to "${searchTerm.replace(/"/g, '\\"')}"
 340 |         
 341 |         repeat with theMessage in allMessages
 342 |           if (subject of theMessage contains searchString) or (content of theMessage contains searchString) then
 343 |             set i to i + 1
 344 |             set msgData to {subject:subject of theMessage, sender:sender of theMessage, ¬
 345 |                        date:time sent of theMessage, id:id of theMessage}
 346 |             
 347 |             -- Try to get content
 348 |             try
 349 |               set msgContent to content of theMessage
 350 |               if length of msgContent > 500 then
 351 |                 set msgContent to (text 1 thru 500 of msgContent) & "..."
 352 |               end if
 353 |               set msgData to msgData & {content:msgContent}
 354 |             on error
 355 |               set msgData to msgData & {content:"[Content not available]"}
 356 |             end try
 357 |             
 358 |             set end of searchResults to msgData
 359 |             
 360 |             -- Stop if we've reached the limit
 361 |             if i >= ${limit} then
 362 |               exit repeat
 363 |             end if
 364 |           end if
 365 |         end repeat
 366 |         
 367 |         return searchResults
 368 |       on error errMsg
 369 |         return "Error: " & errMsg
 370 |       end try
 371 |     end tell
 372 |   `;
 373 |   
 374 |   try {
 375 |     const result = await runAppleScript(script);
 376 |     console.error(`[searchEmails] Raw result length: ${result.length}`);
 377 |     
 378 |     // Parse the results
 379 |     if (result.startsWith("Error:")) {
 380 |       throw new Error(result);
 381 |     }
 382 |     
 383 |     // Parse the emails similar to unread emails
 384 |     const emails = [];
 385 |     const matches = result.match(/\{([^}]+)\}/g);
 386 |     
 387 |     if (matches && matches.length > 0) {
 388 |       for (const match of matches) {
 389 |         try {
 390 |           const props = match.substring(1, match.length - 1).split(',');
 391 |           const email: any = {};
 392 |           
 393 |           props.forEach(prop => {
 394 |             const parts = prop.split(':');
 395 |             if (parts.length >= 2) {
 396 |               const key = parts[0].trim();
 397 |               const value = parts.slice(1).join(':').trim();
 398 |               email[key] = value;
 399 |             }
 400 |           });
 401 |           
 402 |           if (email.subject || email.sender) {
 403 |             emails.push({
 404 |               subject: email.subject || "No subject",
 405 |               sender: email.sender || "Unknown sender",
 406 |               dateSent: email.date || new Date().toString(),
 407 |               content: email.content || "[Content not available]",
 408 |               id: email.id || ""
 409 |             });
 410 |           }
 411 |         } catch (parseError) {
 412 |           console.error('[searchEmails] Error parsing email match:', parseError);
 413 |         }
 414 |       }
 415 |     }
 416 |     
 417 |     console.error(`[searchEmails] Found ${emails.length} matching emails`);
 418 |     return emails;
 419 |   } catch (error) {
 420 |     console.error("[searchEmails] Error searching emails:", error);
 421 |     throw error;
 422 |   }
 423 | }
 424 | 
 425 | async function checkAttachmentPath(filePath: string): Promise<string> {
 426 |   try {
 427 |     // Convert to absolute path if relative
 428 |     let fullPath = filePath;
 429 |     if (!filePath.startsWith('/')) {
 430 |       const cwd = process.cwd();
 431 |       fullPath = `${cwd}/${filePath}`;
 432 |     }
 433 |     
 434 |     // Check if the file exists and is readable
 435 |     const fs = require('fs');
 436 |     const { promisify } = require('util');
 437 |     const access = promisify(fs.access);
 438 |     const stat = promisify(fs.stat);
 439 |     
 440 |     try {
 441 |       await access(fullPath, fs.constants.R_OK);
 442 |       const stats = await stat(fullPath);
 443 |       
 444 |       return `File exists and is readable: ${fullPath}\nSize: ${stats.size} bytes\nPermissions: ${stats.mode.toString(8)}\nLast modified: ${stats.mtime}`;
 445 |     } catch (err) {
 446 |       return `ERROR: Cannot access file: ${fullPath}\nError details: ${err.message}`;
 447 |     }
 448 |   } catch (error) {
 449 |     return `Failed to check attachment path: ${error.message}`;
 450 |   }
 451 | }
 452 | 
 453 | // Add a debug version of sending email with attachment to test if files are accessible
 454 | async function debugSendEmailWithAttachment(
 455 |   to: string,
 456 |   subject: string,
 457 |   body: string,
 458 |   attachmentPath: string
 459 | ): Promise<string> {
 460 |   // First check if the file exists and is readable
 461 |   const fileStatus = await checkAttachmentPath(attachmentPath);
 462 |   console.error(`[debugSendEmail] Attachment status: ${fileStatus}`);
 463 |   
 464 |   // Create a simple AppleScript that just attempts to open the file
 465 |   const script = `
 466 |     set theFile to POSIX file "${attachmentPath.replace(/"/g, '\\"')}"
 467 |     try
 468 |       tell application "Finder"
 469 |         set fileExists to exists file theFile
 470 |         set fileInfo to info for file theFile
 471 |         return "File exists: " & fileExists & ", size: " & (size of fileInfo)
 472 |       end tell
 473 |     on error errMsg
 474 |       return "Error accessing file: " & errMsg
 475 |     end try
 476 |   `;
 477 |   
 478 |   try {
 479 |     const result = await runAppleScript(script);
 480 |     console.error(`[debugSendEmail] AppleScript file check: ${result}`);
 481 |     
 482 |     // Now try to actually create a draft with the attachment
 483 |     const emailScript = `
 484 |       tell application "Microsoft Outlook"
 485 |         try
 486 |           set newMessage to make new outgoing message with properties {subject:"DEBUG: ${subject.replace(/"/g, '\\"')}", visible:true}
 487 |           set content of newMessage to "${body.replace(/"/g, '\\"')}"
 488 |           set to recipients of newMessage to {"${to}"}
 489 |           
 490 |           try
 491 |             set attachmentFile to POSIX file "${attachmentPath.replace(/"/g, '\\"')}"
 492 |             make new attachment at newMessage with properties {file:attachmentFile}
 493 |             set attachResult to "Successfully attached file"
 494 |           on error attachErrMsg
 495 |             set attachResult to "Failed to attach file: " & attachErrMsg
 496 |           end try
 497 |           
 498 |           return attachResult
 499 |         on error errMsg
 500 |           return "Error creating email: " & errMsg
 501 |         end try
 502 |       end tell
 503 |     `;
 504 |     
 505 |     const attachResult = await runAppleScript(emailScript);
 506 |     console.error(`[debugSendEmail] Attachment result: ${attachResult}`);
 507 |     
 508 |     return `File check: ${fileStatus}\n\nAttachment test: ${attachResult}`;
 509 |   } catch (error) {
 510 |     console.error("[debugSendEmail] Error during debug:", error);
 511 |     return `Debugging error: ${error.message}\n\nFile check: ${fileStatus}`;
 512 |   }
 513 | }
 514 | // Update the sendEmail function to handle attachments and HTML content
 515 | async function sendEmail(
 516 |   to: string, 
 517 |   subject: string, 
 518 |   body: string, 
 519 |   cc?: string, 
 520 |   bcc?: string, 
 521 |   isHtml: boolean = false,
 522 |   attachments?: string[]
 523 | ): Promise<string> {
 524 |   console.error(`[sendEmail] Sending email to: ${to}, subject: "${subject}"`);
 525 |   console.error(`[sendEmail] Attachments: ${attachments ? JSON.stringify(attachments) : 'none'}`);
 526 |   
 527 |   await checkOutlookAccess();
 528 | 
 529 |   // Extract name from email if possible (for display name)
 530 |   const extractNameFromEmail = (email: string): string => {
 531 |     const namePart = email.split('@')[0];
 532 |     return namePart
 533 |       .split('.')
 534 |       .map(part => part.charAt(0).toUpperCase() + part.slice(1))
 535 |       .join(' ');
 536 |   };
 537 | 
 538 |   // Get name for display
 539 |   const toName = extractNameFromEmail(to);
 540 |   const ccName = cc ? extractNameFromEmail(cc) : "";
 541 |   const bccName = bcc ? extractNameFromEmail(bcc) : "";
 542 | 
 543 |   // Escape special characters
 544 |   const escapedSubject = subject.replace(/"/g, '\\"');
 545 |   const escapedBody = body.replace(/"/g, '\\"').replace(/\n/g, '\\n');
 546 |   
 547 |   // Process attachments: Convert to absolute paths if they are relative
 548 |   let processedAttachments: string[] = [];
 549 |   if (attachments && attachments.length > 0) {
 550 |     processedAttachments = attachments.map(path => {
 551 |       // Check if path is absolute (starts with /)
 552 |       if (path.startsWith('/')) {
 553 |         return path;
 554 |       }
 555 |       // Get current working directory and join with relative path
 556 |       const cwd = process.cwd();
 557 |       return `${cwd}/${path}`;
 558 |     });
 559 |     console.error(`[sendEmail] Processed attachments: ${JSON.stringify(processedAttachments)}`);
 560 |   }
 561 |   
 562 |   // Create attachment script part with better error handling
 563 |   const attachmentScript = processedAttachments.length > 0 
 564 |     ? processedAttachments.map(filePath => {
 565 |       const escapedPath = filePath.replace(/"/g, '\\"');
 566 |       return `
 567 |         try
 568 |           set attachmentFile to POSIX file "${escapedPath}"
 569 |           make new attachment at msg with properties {file:attachmentFile}
 570 |           log "Successfully attached file: ${escapedPath}"
 571 |         on error errMsg
 572 |           log "Failed to attach file: ${escapedPath} - Error: " & errMsg
 573 |         end try
 574 |       `;
 575 |     }).join('\n')
 576 |     : '';
 577 | 
 578 |   // Try approach 1: Using specific syntax for creating a message with attachments
 579 |   try {
 580 |     const script1 = `
 581 |       tell application "Microsoft Outlook"
 582 |         try
 583 |           set msg to make new outgoing message with properties {subject:"${escapedSubject}"}
 584 |           
 585 |           ${isHtml ? 
 586 |             `set content type of msg to HTML
 587 |              set content of msg to "${escapedBody}"` 
 588 |           : 
 589 |             `set content of msg to "${escapedBody}"`
 590 |           }
 591 |           
 592 |           tell msg
 593 |             set recipTo to make new to recipient with properties {email address:{name:"${toName}", address:"${to}"}}
 594 |             ${cc ? `set recipCc to make new cc recipient with properties {email address:{name:"${ccName}", address:"${cc}"}}` : ''}
 595 |             ${bcc ? `set recipBcc to make new bcc recipient with properties {email address:{name:"${bccName}", address:"${bcc}"}}` : ''}
 596 |             
 597 |             ${attachmentScript}
 598 |           end tell
 599 |           
 600 |           -- Delay to allow attachments to be processed
 601 |           delay 1
 602 |           
 603 |           send msg
 604 |           return "Email sent successfully with attachments"
 605 |         on error errMsg
 606 |           return "Error: " & errMsg
 607 |         end try
 608 |       end tell
 609 |     `;
 610 |     
 611 |     console.error("[sendEmail] Executing AppleScript method 1");
 612 |     const result = await runAppleScript(script1);
 613 |     console.error(`[sendEmail] Result (method 1): ${result}`);
 614 |     
 615 |     if (result.startsWith("Error:")) {
 616 |       throw new Error(result);
 617 |     }
 618 |     
 619 |     return result;
 620 |   } catch (error1) {
 621 |     console.error("[sendEmail] Method 1 failed:", error1);
 622 |     
 623 |     // Try approach 2: Using AppleScript's draft window method
 624 |     try {
 625 |       const script2 = `
 626 |         tell application "Microsoft Outlook"
 627 |           try
 628 |             set newDraft to make new draft window
 629 |             set theMessage to item 1 of mail items of newDraft
 630 |             set subject of theMessage to "${escapedSubject}"
 631 |             
 632 |             ${isHtml ? 
 633 |               `set content type of theMessage to HTML
 634 |                set content of theMessage to "${escapedBody}"` 
 635 |             : 
 636 |               `set content of theMessage to "${escapedBody}"`
 637 |             }
 638 |             
 639 |             set to recipients of theMessage to {"${to}"}
 640 |             ${cc ? `set cc recipients of theMessage to {"${cc}"}` : ''}
 641 |             ${bcc ? `set bcc recipients of theMessage to {"${bcc}"}` : ''}
 642 |             
 643 |             ${processedAttachments.map(filePath => {
 644 |               const escapedPath = filePath.replace(/"/g, '\\"');
 645 |               return `
 646 |                 try
 647 |                   set attachmentFile to POSIX file "${escapedPath}"
 648 |                   make new attachment at theMessage with properties {file:attachmentFile}
 649 |                   log "Successfully attached file: ${escapedPath}"
 650 |                 on error attachErrMsg
 651 |                   log "Failed to attach file: ${escapedPath} - Error: " & attachErrMsg
 652 |                 end try
 653 |               `;
 654 |             }).join('\n')}
 655 |             
 656 |             -- Delay to allow attachments to be processed
 657 |             delay 1
 658 |             
 659 |             send theMessage
 660 |             return "Email sent successfully with method 2"
 661 |           on error errMsg
 662 |             return "Error: " & errMsg
 663 |           end try
 664 |         end tell
 665 |       `;
 666 |       
 667 |       console.error("[sendEmail] Executing AppleScript method 2");
 668 |       const result = await runAppleScript(script2);
 669 |       console.error(`[sendEmail] Result (method 2): ${result}`);
 670 |       
 671 |       if (result.startsWith("Error:")) {
 672 |         throw new Error(result);
 673 |       }
 674 |       
 675 |       return result;
 676 |     } catch (error2) {
 677 |       console.error("[sendEmail] Method 2 failed:", error2);
 678 |       
 679 |       // Try approach 3: Create a draft for the user to manually send
 680 |       try {
 681 |         const script3 = `
 682 |           tell application "Microsoft Outlook"
 683 |             try
 684 |               set newMessage to make new outgoing message with properties {subject:"${escapedSubject}", visible:true}
 685 |               
 686 |               ${isHtml ? 
 687 |                 `set content type of newMessage to HTML
 688 |                  set content of newMessage to "${escapedBody}"` 
 689 |               : 
 690 |                 `set content of newMessage to "${escapedBody}"`
 691 |               }
 692 |               
 693 |               set to recipients of newMessage to {"${to}"}
 694 |               ${cc ? `set cc recipients of newMessage to {"${cc}"}` : ''}
 695 |               ${bcc ? `set bcc recipients of newMessage to {"${bcc}"}` : ''}
 696 |               
 697 |               ${processedAttachments.map(filePath => {
 698 |                 const escapedPath = filePath.replace(/"/g, '\\"');
 699 |                 return `
 700 |                   try
 701 |                     set attachmentFile to POSIX file "${escapedPath}"
 702 |                     make new attachment at newMessage with properties {file:attachmentFile}
 703 |                     log "Successfully attached file: ${escapedPath}"
 704 |                   on error attachErrMsg
 705 |                     log "Failed to attach file: ${escapedPath} - Error: " & attachErrMsg
 706 |                   end try
 707 |                 `;
 708 |               }).join('\n')}
 709 |               
 710 |               -- Display the message
 711 |               activate
 712 |               return "Email draft created with attachments. Please review and send manually."
 713 |             on error errMsg
 714 |               return "Error: " & errMsg
 715 |             end try
 716 |           end tell
 717 |         `;
 718 |         
 719 |         console.error("[sendEmail] Executing AppleScript method 3");
 720 |         const result = await runAppleScript(script3);
 721 |         console.error(`[sendEmail] Result (method 3): ${result}`);
 722 |         
 723 |         if (result.startsWith("Error:")) {
 724 |           throw new Error(result);
 725 |         }
 726 |         
 727 |         return "A draft has been created in Outlook with the content and attachments. Please review and send it manually.";
 728 |       } catch (error3) {
 729 |         console.error("[sendEmail] All methods failed:", error3);
 730 |         throw new Error(`Could not send or create email. Please check if Outlook is properly configured and that you have granted necessary permissions. Error details: ${error3}`);
 731 |       }
 732 |     }
 733 |   }
 734 | }
 735 | // Function to get mail folders - this works based on your logs
 736 | async function getMailFolders(): Promise<string[]> {
 737 |     console.error("[getMailFolders] Getting mail folders");
 738 |     await checkOutlookAccess();
 739 |   
 740 |     const script = `
 741 |       tell application "Microsoft Outlook"
 742 |         set folderNames to {}
 743 |         set allFolders to mail folders
 744 |         
 745 |         repeat with theFolder in allFolders
 746 |           set end of folderNames to name of theFolder
 747 |         end repeat
 748 |         
 749 |         return folderNames
 750 |       end tell
 751 |     `;
 752 |   
 753 |     try {
 754 |       const result = await runAppleScript(script);
 755 |       console.error(`[getMailFolders] Result: ${result}`);
 756 |       return result.split(", ");
 757 |     } catch (error) {
 758 |       console.error("[getMailFolders] Error getting mail folders:", error);
 759 |       throw error;
 760 |     }
 761 |   }
 762 |   
 763 |   // Function to read emails in a folder that uses simple AppleScript
 764 | async function readEmails(folder: string = "Inbox", limit: number = 10): Promise<any[]> {
 765 |     console.error(`[readEmails] Reading emails from folder: ${folder}, limit: ${limit}`);
 766 |     await checkOutlookAccess();
 767 |     
 768 |     // Use a simplified approach that should be more compatible
 769 |     const script = `
 770 |       tell application "Microsoft Outlook"
 771 |         try
 772 |           -- Get the folder by name safely
 773 |           set targetFolder to null
 774 |           set allFolders to mail folders
 775 |           repeat with mailFolder in allFolders
 776 |             if name of mailFolder is "${folder}" then
 777 |               set targetFolder to mailFolder
 778 |               exit repeat
 779 |             end if
 780 |           end repeat
 781 |           
 782 |           if targetFolder is null then set targetFolder to inbox
 783 |           
 784 |           -- Get messages
 785 |           set messageList to {}
 786 |           set msgCount to 0
 787 |           set allMsgs to messages of targetFolder
 788 |           
 789 |           repeat with i from 1 to (count of allMsgs)
 790 |             if msgCount >= ${limit} then exit repeat
 791 |             
 792 |             try
 793 |               set theMsg to item i of allMsgs
 794 |               set msgSubject to subject of theMsg
 795 |               set msgSender to sender of theMsg
 796 |               set msgDate to time sent of theMsg
 797 |               
 798 |               -- Create a simple text representation for the message
 799 |               set msgInfo to msgSubject & " | " & msgSender & " | " & msgDate
 800 |               set end of messageList to msgInfo
 801 |               set msgCount to msgCount + 1
 802 |             on error
 803 |               -- Skip problematic messages
 804 |             end try
 805 |           end repeat
 806 |           
 807 |           return messageList
 808 |         on error errMsg
 809 |           return "Error: " & errMsg
 810 |         end try
 811 |       end tell
 812 |     `;
 813 |     
 814 |     try {
 815 |       const result = await runAppleScript(script);
 816 |       
 817 |       if (result.startsWith("Error:")) {
 818 |         throw new Error(result);
 819 |       }
 820 |       
 821 |       // Parse the results in a simple format
 822 |       const emails = result.split(", ").map(msgInfo => {
 823 |         const parts = msgInfo.split(" | ");
 824 |         return {
 825 |           subject: parts[0] || "No subject",
 826 |           sender: parts[1] || "Unknown sender",
 827 |           dateSent: parts[2] || new Date().toString(),
 828 |           content: "Content not retrieved in simple mode"
 829 |         };
 830 |       });
 831 |       
 832 |       console.error(`[readEmails] Found ${emails.length} emails using simplified approach`);
 833 |       return emails;
 834 |     } catch (error) {
 835 |       console.error("[readEmails] Error reading emails:", error);
 836 |       throw error;
 837 |     }
 838 |   }
 839 | 
 840 | // ====================================================
 841 | // 5. CALENDAR FUNCTIONS
 842 | // ====================================================
 843 | 
 844 | // Function to get today's calendar events
 845 | async function getTodayEvents(limit: number = 10): Promise<any[]> {
 846 |   console.error(`[getTodayEvents] Getting today's events, limit: ${limit}`);
 847 |   await checkOutlookAccess();
 848 |   
 849 |   const script = `
 850 |     tell application "Microsoft Outlook"
 851 |       set todayEvents to {}
 852 |       set theCalendar to default calendar
 853 |       set todayDate to current date
 854 |       set startOfDay to todayDate - (time of todayDate)
 855 |       set endOfDay to startOfDay + 1 * days
 856 |       
 857 |       set eventList to events of theCalendar whose start time is greater than or equal to startOfDay and start time is less than endOfDay
 858 |       
 859 |       set eventCount to count of eventList
 860 |       set limitCount to ${limit}
 861 |       
 862 |       if eventCount < limitCount then
 863 |         set limitCount to eventCount
 864 |       end if
 865 |       
 866 |       repeat with i from 1 to limitCount
 867 |         set theEvent to item i of eventList
 868 |         set eventData to {subject:subject of theEvent, ¬
 869 |                      start:start time of theEvent, ¬
 870 |                      end:end time of theEvent, ¬
 871 |                      location:location of theEvent, ¬
 872 |                      id:id of theEvent}
 873 |         
 874 |         set end of todayEvents to eventData
 875 |       end repeat
 876 |       
 877 |       return todayEvents
 878 |     end tell
 879 |   `;
 880 |   
 881 |   try {
 882 |     const result = await runAppleScript(script);
 883 |     console.error(`[getTodayEvents] Raw result length: ${result.length}`);
 884 |     
 885 |     // Parse the results
 886 |     const events = [];
 887 |     const matches = result.match(/\{([^}]+)\}/g);
 888 |     
 889 |     if (matches && matches.length > 0) {
 890 |       for (const match of matches) {
 891 |         try {
 892 |           const props = match.substring(1, match.length - 1).split(',');
 893 |           const event: any = {};
 894 |           
 895 |           props.forEach(prop => {
 896 |             const parts = prop.split(':');
 897 |             if (parts.length >= 2) {
 898 |               const key = parts[0].trim();
 899 |               const value = parts.slice(1).join(':').trim();
 900 |               event[key] = value;
 901 |             }
 902 |           });
 903 |           
 904 |           if (event.subject) {
 905 |             events.push({
 906 |               subject: event.subject,
 907 |               start: event.start,
 908 |               end: event.end,
 909 |               location: event.location || "No location",
 910 |               id: event.id
 911 |             });
 912 |           }
 913 |         } catch (parseError) {
 914 |           console.error('[getTodayEvents] Error parsing event match:', parseError);
 915 |         }
 916 |       }
 917 |     }
 918 |     
 919 |     console.error(`[getTodayEvents] Found ${events.length} events for today`);
 920 |     return events;
 921 |   } catch (error) {
 922 |     console.error("[getTodayEvents] Error getting today's events:", error);
 923 |     throw error;
 924 |   }
 925 | }
 926 | 
 927 | // Function to get upcoming calendar events
 928 | async function getUpcomingEvents(days: number = 7, limit: number = 10): Promise<any[]> {
 929 |   console.error(`[getUpcomingEvents] Getting upcoming events for next ${days} days, limit: ${limit}`);
 930 |   await checkOutlookAccess();
 931 |   
 932 |   const script = `
 933 |     tell application "Microsoft Outlook"
 934 |       set upcomingEvents to {}
 935 |       set theCalendar to default calendar
 936 |       set todayDate to current date
 937 |       set startOfToday to todayDate - (time of todayDate)
 938 |       set endDate to startOfToday + ${days} * days
 939 |       
 940 |       set eventList to events of theCalendar whose start time is greater than or equal to todayDate and start time is less than endDate
 941 |       
 942 |       set eventCount to count of eventList
 943 |       set limitCount to ${limit}
 944 |       
 945 |       if eventCount < limitCount then
 946 |         set limitCount to eventCount
 947 |       end if
 948 |       
 949 |       repeat with i from 1 to limitCount
 950 |         set theEvent to item i of eventList
 951 |         set eventData to {subject:subject of theEvent, ¬
 952 |                      start:start time of theEvent, ¬
 953 |                      end:end time of theEvent, ¬
 954 |                      location:location of theEvent, ¬
 955 |                      id:id of theEvent}
 956 |         
 957 |         set end of upcomingEvents to eventData
 958 |       end repeat
 959 |       
 960 |       return upcomingEvents
 961 |     end tell
 962 |   `;
 963 |   
 964 |   try {
 965 |     const result = await runAppleScript(script);
 966 |     console.error(`[getUpcomingEvents] Raw result length: ${result.length}`);
 967 |     
 968 |     // Parse the results
 969 |     const events = [];
 970 |     const matches = result.match(/\{([^}]+)\}/g);
 971 |     
 972 |     if (matches && matches.length > 0) {
 973 |       for (const match of matches) {
 974 |         try {
 975 |           const props = match.substring(1, match.length - 1).split(',');
 976 |           const event: any = {};
 977 |           
 978 |           props.forEach(prop => {
 979 |             const parts = prop.split(':');
 980 |             if (parts.length >= 2) {
 981 |               const key = parts[0].trim();
 982 |               const value = parts.slice(1).join(':').trim();
 983 |               event[key] = value;
 984 |             }
 985 |           });
 986 |           
 987 |           if (event.subject) {
 988 |             events.push({
 989 |               subject: event.subject,
 990 |               start: event.start,
 991 |               end: event.end,
 992 |               location: event.location || "No location",
 993 |               id: event.id
 994 |             });
 995 |           }
 996 |         } catch (parseError) {
 997 |           console.error('[getUpcomingEvents] Error parsing event match:', parseError);
 998 |         }
 999 |       }
1000 |     }
1001 |     
1002 |     console.error(`[getUpcomingEvents] Found ${events.length} upcoming events`);
1003 |     return events;
1004 |   } catch (error) {
1005 |     console.error("[getUpcomingEvents] Error getting upcoming events:", error);
1006 |     throw error;
1007 |   }
1008 | }
1009 | 
1010 | // Function to search calendar events
1011 | async function searchEvents(searchTerm: string, limit: number = 10): Promise<any[]> {
1012 |   console.error(`[searchEvents] Searching for events with term: "${searchTerm}", limit: ${limit}`);
1013 |   await checkOutlookAccess();
1014 |   
1015 |   const script = `
1016 |     tell application "Microsoft Outlook"
1017 |       set searchResults to {}
1018 |       set theCalendar to default calendar
1019 |       set allEvents to events of theCalendar
1020 |       set i to 0
1021 |       set searchString to "${searchTerm.replace(/"/g, '\\"')}"
1022 |       
1023 |       repeat with theEvent in allEvents
1024 |         if (subject of theEvent contains searchString) or (location of theEvent contains searchString) then
1025 |           set i to i + 1
1026 |           set eventData to {subject:subject of theEvent, ¬
1027 |                        start:start time of theEvent, ¬
1028 |                        end:end time of theEvent, ¬
1029 |                        location:location of theEvent, ¬
1030 |                        id:id of theEvent}
1031 |           
1032 |           set end of searchResults to eventData
1033 |           
1034 |           -- Stop if we've reached the limit
1035 |           if i >= ${limit} then
1036 |             exit repeat
1037 |           end if
1038 |         end if
1039 |       end repeat
1040 |       
1041 |       return searchResults
1042 |     end tell
1043 |   `;
1044 |   
1045 |   try {
1046 |     const result = await runAppleScript(script);
1047 |     console.error(`[searchEvents] Raw result length: ${result.length}`);
1048 |     
1049 |     // Parse the results
1050 |     const events = [];
1051 |     const matches = result.match(/\{([^}]+)\}/g);
1052 |     
1053 |     if (matches && matches.length > 0) {
1054 |       for (const match of matches) {
1055 |         try {
1056 |           const props = match.substring(1, match.length - 1).split(',');
1057 |           const event: any = {};
1058 |           
1059 |           props.forEach(prop => {
1060 |             const parts = prop.split(':');
1061 |             if (parts.length >= 2) {
1062 |               const key = parts[0].trim();
1063 |               const value = parts.slice(1).join(':').trim();
1064 |               event[key] = value;
1065 |             }
1066 |           });
1067 |           
1068 |           if (event.subject) {
1069 |             events.push({
1070 |               subject: event.subject,
1071 |               start: event.start,
1072 |               end: event.end,
1073 |               location: event.location || "No location",
1074 |               id: event.id
1075 |             });
1076 |           }
1077 |         } catch (parseError) {
1078 |           console.error('[searchEvents] Error parsing event match:', parseError);
1079 |         }
1080 |       }
1081 |     }
1082 |     
1083 |     console.error(`[searchEvents] Found ${events.length} matching events`);
1084 |     return events;
1085 |   } catch (error) {
1086 |     console.error("[searchEvents] Error searching events:", error);
1087 |     throw error;
1088 |   }
1089 | }
1090 | 
1091 | // Function to create a calendar event
1092 | async function createEvent(subject: string, start: string, end: string, location?: string, body?: string, attendees?: string): Promise<string> {
1093 |   console.error(`[createEvent] Creating event: "${subject}", start: ${start}, end: ${end}`);
1094 |   await checkOutlookAccess();
1095 |   
1096 |   // Parse the ISO date strings to a format AppleScript can understand
1097 |   const startDate = new Date(start);
1098 |   const endDate = new Date(end);
1099 |   
1100 |   // Format for AppleScript (month/day/year hour:minute:second)
1101 |   const formattedStart = `date "${startDate.getMonth() + 1}/${startDate.getDate()}/${startDate.getFullYear()} ${startDate.getHours()}:${startDate.getMinutes()}:${startDate.getSeconds()}"`;
1102 |   const formattedEnd = `date "${endDate.getMonth() + 1}/${endDate.getDate()}/${endDate.getFullYear()} ${endDate.getHours()}:${endDate.getMinutes()}:${endDate.getSeconds()}"`;
1103 |   
1104 |   // Escape strings for AppleScript
1105 |   const escapedSubject = subject.replace(/"/g, '\\"');
1106 |   const escapedLocation = location ? location.replace(/"/g, '\\"') : "";
1107 |   const escapedBody = body ? body.replace(/"/g, '\\"') : "";
1108 |   
1109 |   let script = `
1110 |     tell application "Microsoft Outlook"
1111 |       set theCalendar to default calendar
1112 |       set newEvent to make new calendar event at theCalendar with properties {subject:"${escapedSubject}", start time:${formattedStart}, end time:${formattedEnd}
1113 |   `;
1114 |   
1115 |   if (location) {
1116 |     script += `, location:"${escapedLocation}"`;
1117 |   }
1118 |   
1119 |   if (body) {
1120 |     script += `, content:"${escapedBody}"`;
1121 |   }
1122 |   
1123 |   script += `}
1124 |   `;
1125 |   
1126 |   // Add attendees if provided
1127 |   if (attendees) {
1128 |     const attendeeList = attendees.split(',').map(email => email.trim());
1129 |     
1130 |     for (const attendee of attendeeList) {
1131 |       const escapedAttendee = attendee.replace(/"/g, '\\"');
1132 |       script += `
1133 |         make new attendee at newEvent with properties {email address:"${escapedAttendee}"}
1134 |       `;
1135 |     }
1136 |   }
1137 |   
1138 |   script += `
1139 |       save newEvent
1140 |       return "Event created successfully"
1141 |     end tell
1142 |   `;
1143 |   
1144 |   try {
1145 |     const result = await runAppleScript(script);
1146 |     console.error(`[createEvent] Result: ${result}`);
1147 |     return result;
1148 |   } catch (error) {
1149 |     console.error("[createEvent] Error creating event:", error);
1150 |     throw error;
1151 |   }
1152 | }
1153 | 
1154 | // ====================================================
1155 | // 6. CONTACTS FUNCTIONS
1156 | // ====================================================
1157 | 
1158 | // Function to list contacts with improved AppleScript syntax
1159 | async function listContacts(limit: number = 20): Promise<any[]> {
1160 |     console.error(`[listContacts] Listing contacts, limit: ${limit}`);
1161 |     await checkOutlookAccess();
1162 |     
1163 |     const script = `
1164 |       tell application "Microsoft Outlook"
1165 |         set contactList to {}
1166 |         set allContactsList to contacts
1167 |         set contactCount to count of allContactsList
1168 |         set limitCount to ${limit}
1169 |         
1170 |         if contactCount < limitCount then
1171 |           set limitCount to contactCount
1172 |         end if
1173 |         
1174 |         repeat with i from 1 to limitCount
1175 |           try
1176 |             set theContact to item i of allContactsList
1177 |             set contactName to full name of theContact
1178 |             
1179 |             -- Create a basic object with name
1180 |             set contactData to {name:contactName}
1181 |             
1182 |             -- Try to get email 
1183 |             try
1184 |               set emailList to email addresses of theContact
1185 |               if (count of emailList) > 0 then
1186 |                 set emailAddr to address of item 1 of emailList
1187 |                 set contactData to contactData & {email:emailAddr}
1188 |               else
1189 |                 set contactData to contactData & {email:"No email"}
1190 |               end if
1191 |             on error
1192 |               set contactData to contactData & {email:"No email"}
1193 |             end try
1194 |             
1195 |             -- Try to get phone
1196 |             try
1197 |               set phoneList to phones of theContact
1198 |               if (count of phoneList) > 0 then
1199 |                 set phoneNum to formatted dial string of item 1 of phoneList
1200 |                 set contactData to contactData & {phone:phoneNum}
1201 |               else
1202 |                 set contactData to contactData & {phone:"No phone"}
1203 |               end if
1204 |             on error
1205 |               set contactData to contactData & {phone:"No phone"}
1206 |             end try
1207 |             
1208 |             set end of contactList to contactData
1209 |           on error
1210 |             -- Skip contacts that can't be processed
1211 |           end try
1212 |         end repeat
1213 |         
1214 |         return contactList
1215 |       end tell
1216 |     `;
1217 |     
1218 |     try {
1219 |       const result = await runAppleScript(script);
1220 |       console.error(`[listContacts] Raw result length: ${result.length}`);
1221 |       
1222 |       // Parse the results
1223 |       const contacts = [];
1224 |       const matches = result.match(/\{([^}]+)\}/g);
1225 |       
1226 |       if (matches && matches.length > 0) {
1227 |         for (const match of matches) {
1228 |           try {
1229 |             const props = match.substring(1, match.length - 1).split(',');
1230 |             const contact: any = {};
1231 |             
1232 |             props.forEach(prop => {
1233 |               const parts = prop.split(':');
1234 |               if (parts.length >= 2) {
1235 |                 const key = parts[0].trim();
1236 |                 const value = parts.slice(1).join(':').trim();
1237 |                 contact[key] = value;
1238 |               }
1239 |             });
1240 |             
1241 |             if (contact.name) {
1242 |               contacts.push({
1243 |                 name: contact.name,
1244 |                 email: contact.email || "No email",
1245 |                 phone: contact.phone || "No phone"
1246 |               });
1247 |             }
1248 |           } catch (parseError) {
1249 |             console.error('[listContacts] Error parsing contact match:', parseError);
1250 |           }
1251 |         }
1252 |       }
1253 |       
1254 |       console.error(`[listContacts] Found ${contacts.length} contacts`);
1255 |       return contacts;
1256 |     } catch (error) {
1257 |       console.error("[listContacts] Error listing contacts:", error);
1258 |       
1259 |       // Try an alternative approach using a simpler script
1260 |       try {
1261 |         const alternativeScript = `
1262 |           tell application "Microsoft Outlook"
1263 |             set contactList to {}
1264 |             set contactCount to count of contacts
1265 |             set limitCount to ${limit}
1266 |             
1267 |             if contactCount < limitCount then
1268 |               set limitCount to contactCount
1269 |             end if
1270 |             
1271 |             repeat with i from 1 to limitCount
1272 |               try
1273 |                 set theContact to item i of contacts
1274 |                 set contactName to full name of theContact
1275 |                 set end of contactList to contactName
1276 |               end try
1277 |             end repeat
1278 |             
1279 |             return contactList
1280 |           end tell
1281 |         `;
1282 |         
1283 |         const result = await runAppleScript(alternativeScript);
1284 |         
1285 |         // Parse the simpler result format (just names)
1286 |         const simplifiedContacts = result.split(", ").map(name => ({
1287 |           name: name,
1288 |           email: "Not available with simplified method",
1289 |           phone: "Not available with simplified method"
1290 |         }));
1291 |         
1292 |         console.error(`[listContacts] Found ${simplifiedContacts.length} contacts using alternative method`);
1293 |         return simplifiedContacts;
1294 |       } catch (altError) {
1295 |         console.error("[listContacts] Alternative method also failed:", altError);
1296 |         throw new Error(`Error accessing contacts. The error might be related to Outlook permissions or configuration: ${error instanceof Error ? error.message : String(error)}`);
1297 |       }
1298 |     }
1299 |   }
1300 | 
1301 | // Function to search contacts
1302 | // Function to search contacts with improved AppleScript syntax
1303 | async function searchContacts(searchTerm: string, limit: number = 10): Promise<any[]> {
1304 |     console.error(`[searchContacts] Searching for contacts with term: "${searchTerm}", limit: ${limit}`);
1305 |     await checkOutlookAccess();
1306 |     
1307 |     const script = `
1308 |       tell application "Microsoft Outlook"
1309 |         set searchResults to {}
1310 |         set allContacts to contacts
1311 |         set i to 0
1312 |         set searchString to "${searchTerm.replace(/"/g, '\\"')}"
1313 |         
1314 |         repeat with theContact in allContacts
1315 |           try
1316 |             set contactName to full name of theContact
1317 |             
1318 |             if contactName contains searchString then
1319 |               set i to i + 1
1320 |               
1321 |               -- Create basic contact info
1322 |               set contactData to {name:contactName}
1323 |               
1324 |               -- Try to get email 
1325 |               try
1326 |                 set emailList to email addresses of theContact
1327 |                 if (count of emailList) > 0 then
1328 |                   set emailAddr to address of item 1 of emailList
1329 |                   set contactData to contactData & {email:emailAddr}
1330 |                 else
1331 |                   set contactData to contactData & {email:"No email"}
1332 |                 end if
1333 |               on error
1334 |                 set contactData to contactData & {email:"No email"}
1335 |               end try
1336 |               
1337 |               -- Try to get phone
1338 |               try
1339 |                 set phoneList to phones of theContact
1340 |                 if (count of phoneList) > 0 then
1341 |                   set phoneNum to formatted dial string of item 1 of phoneList
1342 |                   set contactData to contactData & {phone:phoneNum}
1343 |                 else
1344 |                   set contactData to contactData & {phone:"No phone"}
1345 |                 end if
1346 |               on error
1347 |                 set contactData to contactData & {phone:"No phone"}
1348 |               end try
1349 |               
1350 |               set end of searchResults to contactData
1351 |               
1352 |               -- Stop if we've reached the limit
1353 |               if i >= ${limit} then
1354 |                 exit repeat
1355 |               end if
1356 |             end if
1357 |           on error
1358 |             -- Skip contacts that can't be processed
1359 |           end try
1360 |         end repeat
1361 |         
1362 |         return searchResults
1363 |       end tell
1364 |     `;
1365 |     
1366 |     try {
1367 |       const result = await runAppleScript(script);
1368 |       console.error(`[searchContacts] Raw result length: ${result.length}`);
1369 |       
1370 |       // Parse the results
1371 |       const contacts = [];
1372 |       const matches = result.match(/\{([^}]+)\}/g);
1373 |       
1374 |       if (matches && matches.length > 0) {
1375 |         for (const match of matches) {
1376 |           try {
1377 |             const props = match.substring(1, match.length - 1).split(',');
1378 |             const contact: any = {};
1379 |             
1380 |             props.forEach(prop => {
1381 |               const parts = prop.split(':');
1382 |               if (parts.length >= 2) {
1383 |                 const key = parts[0].trim();
1384 |                 const value = parts.slice(1).join(':').trim();
1385 |                 contact[key] = value;
1386 |               }
1387 |             });
1388 |             
1389 |             if (contact.name) {
1390 |               contacts.push({
1391 |                 name: contact.name,
1392 |                 email: contact.email || "No email",
1393 |                 phone: contact.phone || "No phone"
1394 |               });
1395 |             }
1396 |           } catch (parseError) {
1397 |             console.error('[searchContacts] Error parsing contact match:', parseError);
1398 |           }
1399 |         }
1400 |       }
1401 |       
1402 |       console.error(`[searchContacts] Found ${contacts.length} matching contacts`);
1403 |       return contacts;
1404 |     } catch (error) {
1405 |       console.error("[searchContacts] Error searching contacts:", error);
1406 |       
1407 |       // Try an alternative approach with a simpler script that just returns names
1408 |       try {
1409 |         const alternativeScript = `
1410 |           tell application "Microsoft Outlook"
1411 |             set matchingContacts to {}
1412 |             set searchString to "${searchTerm.replace(/"/g, '\\"')}"
1413 |             set i to 0
1414 |             
1415 |             repeat with theContact in contacts
1416 |               try
1417 |                 set contactName to full name of theContact
1418 |                 if contactName contains searchString then
1419 |                   set i to i + 1
1420 |                   set end of matchingContacts to contactName
1421 |                   if i >= ${limit} then exit repeat
1422 |                 end if
1423 |               end try
1424 |             end repeat
1425 |             
1426 |             return matchingContacts
1427 |           end tell
1428 |         `;
1429 |         
1430 |         const result = await runAppleScript(alternativeScript);
1431 |         
1432 |         // Parse the simpler result format (just names)
1433 |         const simplifiedContacts = result.split(", ").map(name => ({
1434 |           name: name,
1435 |           email: "Not available with simplified method",
1436 |           phone: "Not available with simplified method"
1437 |         }));
1438 |         
1439 |         console.error(`[searchContacts] Found ${simplifiedContacts.length} contacts using alternative method`);
1440 |         return simplifiedContacts;
1441 |       } catch (altError) {
1442 |         console.error("[searchContacts] Alternative method also failed:", altError);
1443 |         throw new Error(`Error searching contacts. The error might be related to Outlook permissions or configuration: ${error instanceof Error ? error.message : String(error)}`);
1444 |       }
1445 |     }
1446 |   }
1447 | 
1448 | // ====================================================
1449 | // 7. TYPE GUARDS
1450 | // ====================================================
1451 | 
1452 | // Type guards for arguments
1453 | function isMailArgs(args: unknown): args is {
1454 |   operation: "unread" | "search" | "send" | "folders" | "read";
1455 |   folder?: string;
1456 |   limit?: number;
1457 |   searchTerm?: string;
1458 |   to?: string;
1459 |   subject?: string;
1460 |   body?: string;
1461 |   isHtml?: boolean;
1462 |   cc?: string;
1463 |   bcc?: string;
1464 |   attachments?: string[];
1465 | } {
1466 |   if (typeof args !== "object" || args === null) return false;
1467 |   
1468 |   const { operation } = args as any;
1469 |   
1470 |   if (!operation || !["unread", "search", "send", "folders", "read"].includes(operation)) {
1471 |     return false;
1472 |   }
1473 |   
1474 |   // Check required fields based on operation
1475 |   switch (operation) {
1476 |     case "search":
1477 |       if (!(args as any).searchTerm) return false;
1478 |       break;
1479 |     case "send":
1480 |       if (!(args as any).to || !(args as any).subject || !(args as any).body) return false;
1481 |       break;
1482 |   }
1483 |   
1484 |   return true;
1485 | }
1486 | 
1487 | function isCalendarArgs(args: unknown): args is {
1488 |   operation: "today" | "upcoming" | "search" | "create";
1489 |   searchTerm?: string;
1490 |   limit?: number;
1491 |   days?: number;
1492 |   subject?: string;
1493 |   start?: string;
1494 |   end?: string;
1495 |   location?: string;
1496 |   body?: string;
1497 |   attendees?: string;
1498 | } {
1499 |   if (typeof args !== "object" || args === null) return false;
1500 |   
1501 |   const { operation } = args as any;
1502 |   
1503 |   if (!operation || !["today", "upcoming", "search", "create"].includes(operation)) {
1504 |     return false;
1505 |   }
1506 |   
1507 |   // Check required fields based on operation
1508 |   switch (operation) {
1509 |     case "search":
1510 |       if (!(args as any).searchTerm) return false;
1511 |       break;
1512 |     case "create":
1513 |       if (!(args as any).subject || !(args as any).start || !(args as any).end) return false;
1514 |       break;
1515 |   }
1516 |   
1517 |   return true;
1518 | }
1519 | 
1520 | function isContactsArgs(args: unknown): args is {
1521 |   operation: "list" | "search";
1522 |   searchTerm?: string;
1523 |   limit?: number;
1524 | } {
1525 |   if (typeof args !== "object" || args === null) return false;
1526 |   
1527 |   const { operation } = args as any;
1528 |   
1529 |   if (!operation || !["list", "search"].includes(operation)) {
1530 |     return false;
1531 |   }
1532 |   
1533 |   // Check required fields based on operation
1534 |   if (operation === "search" && !(args as any).searchTerm) {
1535 |     return false;
1536 |   }
1537 |   
1538 |   return true;
1539 | }
1540 | 
1541 | // ====================================================
1542 | // 8. MCP REQUEST HANDLERS
1543 | // ====================================================
1544 | 
1545 | // Set up request handlers
1546 | server.setRequestHandler(ListToolsRequestSchema, async () => {
1547 |   console.error("[ListToolsRequest] Returning available tools");
1548 |   return {
1549 |     tools: [OUTLOOK_MAIL_TOOL, OUTLOOK_CALENDAR_TOOL, OUTLOOK_CONTACTS_TOOL],
1550 |   };
1551 | });
1552 | 
1553 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
1554 |   try {
1555 |     const { name, arguments: args } = request.params;
1556 |     console.error(`[CallToolRequest] Received request for tool: ${name}`);
1557 | 
1558 |     if (!args) {
1559 |       throw new Error("No arguments provided");
1560 |     }
1561 | 
1562 |     switch (name) {
1563 |       case "outlook_mail": {
1564 |         if (!isMailArgs(args)) {
1565 |           throw new Error("Invalid arguments for outlook_mail tool");
1566 |         }
1567 | 
1568 |         const { operation } = args;
1569 |         console.error(`[CallToolRequest] Mail operation: ${operation}`);
1570 | 
1571 |         switch (operation) {
1572 |           case "unread": {
1573 |             const emails = await getUnreadEmails(args.folder, args.limit);
1574 |             return {
1575 |               content: [{ 
1576 |                 type: "text", 
1577 |                 text: emails.length > 0 ? 
1578 |                   `Found ${emails.length} unread email(s)${args.folder ? ` in folder "${args.folder}"` : ''}\n\n` +
1579 |                   emails.map(email => 
1580 |                     `[${email.dateSent}] From: ${email.sender}\nSubject: ${email.subject}\n${email.content.substring(0, 200)}${email.content.length > 200 ? '...' : ''}`
1581 |                   ).join("\n\n") :
1582 |                   `No unread emails found${args.folder ? ` in folder "${args.folder}"` : ''}`
1583 |               }],
1584 |               isError: false
1585 |             };
1586 |           }
1587 |           
1588 |           case "search": {
1589 |             if (!args.searchTerm) {
1590 |               throw new Error("Search term is required for search operation");
1591 |             }
1592 |             const emails = await searchEmails(args.searchTerm, args.folder, args.limit);
1593 |             return {
1594 |               content: [{ 
1595 |                 type: "text", 
1596 |                 text: emails.length > 0 ? 
1597 |                   `Found ${emails.length} email(s) for "${args.searchTerm}"${args.folder ? ` in folder "${args.folder}"` : ''}\n\n` +
1598 |                   emails.map(email => 
1599 |                     `[${email.dateSent}] From: ${email.sender}\nSubject: ${email.subject}\n${email.content.substring(0, 200)}${email.content.length > 200 ? '...' : ''}`
1600 |                   ).join("\n\n") :
1601 |                   `No emails found for "${args.searchTerm}"${args.folder ? ` in folder "${args.folder}"` : ''}`
1602 |               }],
1603 |               isError: false
1604 |             };
1605 |           }
1606 |           
1607 |           // Update the handler in CallToolRequestSchema
1608 |           case "send": {
1609 |             if (!args.to || !args.subject || !args.body) {
1610 |               throw new Error("Recipient (to), subject, and body are required for send operation");
1611 |             }
1612 |             
1613 |             // Validate attachments if provided
1614 |             if (args.attachments && !Array.isArray(args.attachments)) {
1615 |               throw new Error("Attachments must be an array of file paths");
1616 |             }
1617 |             
1618 |             // Log attachment information for debugging
1619 |             console.error(`[CallTool] Send email with attachments: ${args.attachments ? JSON.stringify(args.attachments) : 'none'}`);
1620 |             
1621 |             const result = await sendEmail(
1622 |               args.to, 
1623 |               args.subject, 
1624 |               args.body, 
1625 |               args.cc, 
1626 |               args.bcc, 
1627 |               args.isHtml || false,
1628 |               args.attachments
1629 |             );
1630 |             
1631 |             return {
1632 |               content: [{ type: "text", text: result }],
1633 |               isError: false
1634 |             };
1635 |           }
1636 |           
1637 |           case "folders": {
1638 |             const folders = await getMailFolders();
1639 |             return {
1640 |               content: [{ 
1641 |                 type: "text", 
1642 |                 text: folders.length > 0 ? 
1643 |                   `Found ${folders.length} mail folders:\n\n${folders.join("\n")}` :
1644 |                   "No mail folders found. Make sure Outlook is running and properly configured."
1645 |               }],
1646 |               isError: false
1647 |             };
1648 |           }
1649 |           
1650 |           case "read": {
1651 |             const emails = await readEmails(args.folder, args.limit);
1652 |             return {
1653 |               content: [{ 
1654 |                 type: "text", 
1655 |                 text: emails.length > 0 ? 
1656 |                   `Found ${emails.length} email(s)${args.folder ? ` in folder "${args.folder}"` : ''}\n\n` +
1657 |                   emails.map(email => 
1658 |                     `[${email.dateSent}] From: ${email.sender}\nSubject: ${email.subject}\n${email.content.substring(0, 200)}${email.content.length > 200 ? '...' : ''}`
1659 |                   ).join("\n\n") :
1660 |                   `No emails found${args.folder ? ` in folder "${args.folder}"` : ''}`
1661 |               }],
1662 |               isError: false
1663 |             };
1664 |           }
1665 |           
1666 |           default:
1667 |             throw new Error(`Unknown mail operation: ${operation}`);
1668 |         }
1669 |       }
1670 |       
1671 |       case "outlook_calendar": {
1672 |         if (!isCalendarArgs(args)) {
1673 |           throw new Error("Invalid arguments for outlook_calendar tool");
1674 |         }
1675 | 
1676 |         const { operation } = args;
1677 |         console.error(`[CallToolRequest] Calendar operation: ${operation}`);
1678 | 
1679 |         switch (operation) {
1680 |           case "today": {
1681 |             const events = await getTodayEvents(args.limit);
1682 |             return {
1683 |               content: [{ 
1684 |                 type: "text", 
1685 |                 text: events.length > 0 ? 
1686 |                   `Found ${events.length} event(s) for today:\n\n` +
1687 |                   events.map(event => 
1688 |                     `${event.subject}\nTime: ${event.start} - ${event.end}\nLocation: ${event.location}`
1689 |                   ).join("\n\n") :
1690 |                   "No events found for today"
1691 |               }],
1692 |               isError: false
1693 |             };
1694 |           }
1695 |           
1696 |           case "upcoming": {
1697 |             const days = args.days || 7;
1698 |             const events = await getUpcomingEvents(days, args.limit);
1699 |             return {
1700 |               content: [{ 
1701 |                 type: "text", 
1702 |                 text: events.length > 0 ? 
1703 |                   `Found ${events.length} upcoming event(s) for the next ${days} days:\n\n` +
1704 |                   events.map(event => 
1705 |                     `${event.subject}\nTime: ${event.start} - ${event.end}\nLocation: ${event.location}`
1706 |                   ).join("\n\n") :
1707 |                   `No upcoming events found for the next ${days} days`
1708 |               }],
1709 |               isError: false
1710 |             };
1711 |           }
1712 |           
1713 |           case "search": {
1714 |             if (!args.searchTerm) {
1715 |               throw new Error("Search term is required for search operation");
1716 |             }
1717 |             const events = await searchEvents(args.searchTerm, args.limit);
1718 |             return {
1719 |               content: [{ 
1720 |                 type: "text", 
1721 |                 text: events.length > 0 ? 
1722 |                   `Found ${events.length} event(s) matching "${args.searchTerm}":\n\n` +
1723 |                   events.map(event => 
1724 |                     `${event.subject}\nTime: ${event.start} - ${event.end}\nLocation: ${event.location}`
1725 |                   ).join("\n\n") :
1726 |                   `No events found matching "${args.searchTerm}"`
1727 |               }],
1728 |               isError: false
1729 |             };
1730 |           }
1731 |           
1732 |           case "create": {
1733 |             if (!args.subject || !args.start || !args.end) {
1734 |               throw new Error("Subject, start time, and end time are required for create operation");
1735 |             }
1736 |             const result = await createEvent(args.subject, args.start, args.end, args.location, args.body, args.attendees);
1737 |             return {
1738 |               content: [{ type: "text", text: result }],
1739 |               isError: false
1740 |             };
1741 |           }
1742 |           
1743 |           default:
1744 |             throw new Error(`Unknown calendar operation: ${operation}`);
1745 |         }
1746 |       }
1747 |       
1748 |       case "outlook_contacts": {
1749 |         if (!isContactsArgs(args)) {
1750 |           throw new Error("Invalid arguments for outlook_contacts tool");
1751 |         }
1752 | 
1753 |         const { operation } = args;
1754 |         console.error(`[CallToolRequest] Contacts operation: ${operation}`);
1755 | 
1756 |         switch (operation) {
1757 |           case "list": {
1758 |             const contacts = await listContacts(args.limit);
1759 |             return {
1760 |               content: [{ 
1761 |                 type: "text", 
1762 |                 text: contacts.length > 0 ? 
1763 |                   `Found ${contacts.length} contact(s):\n\n` +
1764 |                   contacts.map(contact => 
1765 |                     `Name: ${contact.name}\nEmail: ${contact.email}\nPhone: ${contact.phone}`
1766 |                   ).join("\n\n") :
1767 |                   "No contacts found"
1768 |               }],
1769 |               isError: false
1770 |             };
1771 |           }
1772 |           
1773 |           case "search": {
1774 |             if (!args.searchTerm) {
1775 |               throw new Error("Search term is required for search operation");
1776 |             }
1777 |             const contacts = await searchContacts(args.searchTerm, args.limit);
1778 |             return {
1779 |               content: [{ 
1780 |                 type: "text", 
1781 |                 text: contacts.length > 0 ? 
1782 |                   `Found ${contacts.length} contact(s) matching "${args.searchTerm}":\n\n` +
1783 |                   contacts.map(contact => 
1784 |                     `Name: ${contact.name}\nEmail: ${contact.email}\nPhone: ${contact.phone}`
1785 |                   ).join("\n\n") :
1786 |                   `No contacts found matching "${args.searchTerm}"`
1787 |               }],
1788 |               isError: false
1789 |             };
1790 |           }
1791 |           
1792 |           default:
1793 |             throw new Error(`Unknown contacts operation: ${operation}`);
1794 |         }
1795 |       }
1796 | 
1797 |       default:
1798 |         return {
1799 |           content: [{ type: "text", text: `Unknown tool: ${name}` }],
1800 |           isError: true,
1801 |         };
1802 |     }
1803 |   } catch (error) {
1804 |     console.error("[CallToolRequest] Error:", error);
1805 |     return {
1806 |       content: [
1807 |         {
1808 |           type: "text",
1809 |           text: `Error: ${error instanceof Error ? error.message : String(error)}`,
1810 |         },
1811 |       ],
1812 |       isError: true,
1813 |     };
1814 |   }
1815 | });
1816 | 
1817 | // ====================================================
1818 | // 9. START SERVER
1819 | // ====================================================
1820 | 
1821 | // Start the MCP server
1822 | console.error("Initializing Outlook MCP server transport...");
1823 | const transport = new StdioServerTransport();
1824 | 
1825 | (async () => {
1826 |   try {
1827 |     console.error("Connecting to transport...");
1828 |     await server.connect(transport);
1829 |     console.error("Outlook MCP Server running on stdio");
1830 |   } catch (error) {
1831 |     console.error("Failed to initialize MCP server:", error);
1832 |     process.exit(1);
1833 |   }
1834 | })();
```