# Directory Structure
```
├── .github
│ └── workflows
│ └── npm-publish.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── scripts
│ ├── createEvent.vbs
│ ├── deleteEvent.vbs
│ ├── findFreeSlots.vbs
│ ├── getAttendeeStatus.vbs
│ ├── getCalendars.vbs
│ ├── listEvents.vbs
│ ├── updateEvent.vbs
│ └── utils.vbs
├── src
│ ├── index.js
│ ├── outlookTools.js
│ └── scriptRunner.js
└── test-outlook-connection.js
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Dependency directories
2 | node_modules/
3 | npm-debug.log
4 | yarn-debug.log
5 | yarn-error.log
6 |
7 | # Environment variables
8 | .env
9 | .env.local
10 | .env.development.local
11 | .env.test.local
12 | .env.production.local
13 |
14 | # Build outputs
15 | dist/
16 | build/
17 |
18 | # Logs
19 | logs
20 | *.log
21 |
22 | # Runtime data
23 | pids
24 | *.pid
25 | *.seed
26 | *.pid.lock
27 |
28 | # Directory for instrumented libs generated by jscoverage/JSCover
29 | lib-cov
30 |
31 | # Coverage directory used by tools like istanbul
32 | coverage
33 |
34 | # nyc test coverage
35 | .nyc_output
36 |
37 | # IDEs and editors
38 | .idea/
39 | .vscode/
40 | *.swp
41 | *.swo
42 | .DS_Store
43 | .vs/
44 | *.sublime-workspace
45 |
46 | # Optional npm cache directory
47 | .npm
48 |
49 | # Optional eslint cache
50 | .eslintcache
51 |
52 | # Optional REPL history
53 | .node_repl_history
54 |
55 | # Output of 'npm pack'
56 | *.tgz
57 |
58 | # Yarn Integrity file
59 | .yarn-integrity
60 |
61 | #Cline
62 | .clinerules
63 | memory-bank/
64 | CLAUDE.md
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Outlook Calendar MCP Tool
2 |
3 | A Model Context Protocol (MCP) server that allows Claude to access and manage your local Microsoft Outlook calendar (Windows only).
4 |
5 | <a href="https://glama.ai/mcp/servers/08enllwrbp">
6 | <img width="380" height="200" src="https://glama.ai/mcp/servers/08enllwrbp/badge" alt="Outlook Calendar MCP server" />
7 | </a>
8 |
9 | [](https://opensource.org/licenses/MIT)
10 |
11 | ## Features
12 |
13 | - **View Calendar Events**: List events within a date range, view event details, check attendee status
14 | - **Manage Calendar Events**: Create new events and meetings, update existing events
15 | - **Calendar Intelligence**: Find free time slots for scheduling, identify optimal meeting times
16 | - **Multiple Calendar Support**: Access different calendars in your Outlook profile
17 |
18 | ## Prerequisites
19 |
20 | - Windows operating system
21 | - Microsoft Outlook desktop client installed
22 | - **VBScript support** (see VBScript Installation below if you're on Windows 11 24H2+)
23 | - Node.js (version 14.x or higher)
24 | - npm (comes with Node.js)
25 |
26 | ### VBScript Installation (Windows 11 24H2+ Users)
27 |
28 | **Important**: Starting with Windows 11 24H2, VBScript is no longer installed by default and must be enabled as an optional feature.
29 |
30 | If you're experiencing issues with the MCP server not working after a Windows update, you likely need to install VBScript:
31 |
32 | 1. Open **Settings** (Windows + I)
33 | 2. Go to **Apps** → **Optional features**
34 | 3. Click **"View features"** next to **"Add an optional feature"**
35 | 4. Search for **"VBScript"**
36 | 5. Select **VBScript** and click **Install**
37 | 6. Restart your computer after installation
38 |
39 | **VBScript Deprecation Timeline:**
40 | - **Phase 1** (Late 2024+): Available as optional feature in Windows 11 24H2
41 | - **Phase 2** (~2027): Will no longer be enabled by default
42 | - **Phase 3** (Future): Complete removal from Windows
43 |
44 | *Note: Thanks to community feedback about VBScript deprecation, I'm considering architectural improvements to make the project more future-proof.*
45 |
46 | ## Installation
47 |
48 | ### Option 1: Install from npm
49 |
50 | ```bash
51 | npm install -g outlook-calendar-mcp
52 | ```
53 |
54 | You can also run it directly without installation using npx:
55 |
56 | ```bash
57 | npx outlook-calendar-mcp
58 | ```
59 |
60 | ### Option 2: Install from source
61 |
62 | 1. Clone this repository or download the source code
63 | 2. Install dependencies:
64 |
65 | ```bash
66 | npm install
67 | ```
68 |
69 | 3. Run the server:
70 |
71 | ```bash
72 | npm start
73 | ```
74 |
75 | ## MCP Server Configuration
76 |
77 | To use this tool with Claude, you need to add it to your MCP settings configuration file.
78 |
79 | ### For Claude Desktop App
80 |
81 | Add the following to your Claude Desktop configuration file (located at `%APPDATA%\Claude\claude_desktop_config.json`):
82 |
83 | #### If installed globally via npm:
84 |
85 | ```json
86 | {
87 | "mcpServers": {
88 | "outlook-calendar": {
89 | "command": "outlook-calendar-mcp",
90 | "args": [],
91 | "env": {}
92 | }
93 | }
94 | }
95 | ```
96 |
97 | #### Using npx (without installation):
98 |
99 | ```json
100 | {
101 | "mcpServers": {
102 | "outlook-calendar": {
103 | "command": "npx",
104 | "args": ["-y", "outlook-calendar-mcp"],
105 | "env": {}
106 | }
107 | }
108 | }
109 | ```
110 |
111 | #### If installed from source:
112 |
113 | ```json
114 | {
115 | "mcpServers": {
116 | "outlook-calendar": {
117 | "command": "node",
118 | "args": ["path/to/outlook-calendar-mcp/src/index.js"],
119 | "env": {}
120 | }
121 | }
122 | }
123 | ```
124 |
125 | ### For Claude VSCode Extension
126 |
127 | Add the following to your Claude VSCode extension MCP settings file (located at `%APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json`):
128 |
129 | #### If installed globally via npm:
130 |
131 | ```json
132 | {
133 | "mcpServers": {
134 | "outlook-calendar": {
135 | "command": "outlook-calendar-mcp",
136 | "args": [],
137 | "env": {}
138 | }
139 | }
140 | }
141 | ```
142 |
143 | #### Using npx (without installation):
144 |
145 | ```json
146 | {
147 | "mcpServers": {
148 | "outlook-calendar": {
149 | "command": "npx",
150 | "args": ["-y", "outlook-calendar-mcp"],
151 | "env": {}
152 | }
153 | }
154 | }
155 | ```
156 |
157 | #### If installed from source:
158 |
159 | ```json
160 | {
161 | "mcpServers": {
162 | "outlook-calendar": {
163 | "command": "node",
164 | "args": ["path/to/outlook-calendar-mcp/src/index.js"],
165 | "env": {}
166 | }
167 | }
168 | }
169 | ```
170 |
171 | For source installation, replace `path/to/outlook-calendar-mcp` with the actual path to where you installed this tool.
172 |
173 | ## Usage
174 |
175 | Once configured, Claude will have access to the following tools:
176 |
177 | ### List Calendar Events
178 |
179 | ```
180 | list_events
181 | - startDate: Start date in MM/DD/YYYY format
182 | - endDate: End date in MM/DD/YYYY format (optional)
183 | - calendar: Calendar name (optional)
184 | ```
185 |
186 | Example: "List my calendar events for next week"
187 |
188 | ### Create Calendar Event
189 |
190 | ```
191 | create_event
192 | - subject: Event subject/title
193 | - startDate: Start date in MM/DD/YYYY format
194 | - startTime: Start time in HH:MM AM/PM format
195 | - endDate: End date in MM/DD/YYYY format (optional)
196 | - endTime: End time in HH:MM AM/PM format (optional)
197 | - location: Event location (optional)
198 | - body: Event description (optional)
199 | - isMeeting: Whether this is a meeting with attendees (optional)
200 | - attendees: Semicolon-separated list of attendee email addresses (optional)
201 | - calendar: Calendar name (optional)
202 | ```
203 |
204 | Example: "Add a meeting with John about the project proposal on Friday at 2 PM"
205 |
206 | ### Find Free Time Slots
207 |
208 | ```
209 | find_free_slots
210 | - startDate: Start date in MM/DD/YYYY format
211 | - endDate: End date in MM/DD/YYYY format (optional)
212 | - duration: Duration in minutes (optional)
213 | - workDayStart: Work day start hour (0-23) (optional)
214 | - workDayEnd: Work day end hour (0-23) (optional)
215 | - calendar: Calendar name (optional)
216 | ```
217 |
218 | Example: "When am I free for a 1-hour meeting this week?"
219 |
220 | ### Get Attendee Status
221 |
222 | ```
223 | get_attendee_status
224 | - eventId: Event ID
225 | - calendar: Calendar name (optional)
226 | ```
227 |
228 | Example: "Who hasn't responded to my team meeting invitation?"
229 |
230 | > **Important Note**: When using operations that require an event ID (update_event, delete_event, get_attendee_status), you must use the `id` field from the list_events response. This is the unique EntryID that Outlook uses to identify events.
231 |
232 | ### Update Calendar Event
233 |
234 | ```
235 | update_event
236 | - eventId: Event ID to update
237 | - subject: New event subject/title (optional)
238 | - startDate: New start date in MM/DD/YYYY format (optional)
239 | - startTime: New start time in HH:MM AM/PM format (optional)
240 | - endDate: New end date in MM/DD/YYYY format (optional)
241 | - endTime: New end time in HH:MM AM/PM format (optional)
242 | - location: New event location (optional)
243 | - body: New event description (optional)
244 | - calendar: Calendar name (optional)
245 | ```
246 |
247 | Example: "Update my team meeting tomorrow to start at 3 PM instead of 2 PM"
248 |
249 | ### Get Calendars
250 |
251 | ```
252 | get_calendars
253 | ```
254 |
255 | Example: "Show me my available calendars"
256 |
257 | ## Security Notes
258 |
259 | - On first use, Outlook may display security prompts to allow script access
260 | - The tool only accesses your local Outlook client and does not send calendar data to external servers
261 | - All calendar operations are performed locally on your computer
262 |
263 | ## Troubleshooting
264 |
265 | - **VBScript Not Available (Windows 11 24H2+)**: If you get errors after a Windows update, VBScript may need to be installed. See [VBScript Installation](#vbscript-installation-windows-11-24h2-users) section above
266 | - **"Script execution failed" errors**: Usually indicates VBScript is not available or Outlook is not accessible
267 | - **Outlook Security Prompts**: If you see security prompts from Outlook, you need to allow the script to access your Outlook data
268 | - **Script Execution Policy**: If you encounter script execution errors, you may need to adjust your PowerShell execution policy
269 | - **Path Issues**: Ensure the path in your MCP configuration file points to the correct location of the tool
270 |
271 | ## Contributing
272 |
273 | We welcome contributions to the Outlook Calendar MCP Tool! Please see our [Contributing Guide](CONTRIBUTING.md) for details on how to get started.
274 |
275 | By participating in this project, you agree to abide by our [Code of Conduct](CODE_OF_CONDUCT.md).
276 |
277 | ## License
278 |
279 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
```
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
```markdown
1 | # Contributing to Outlook Calendar MCP Tool
2 |
3 | Thank you for considering contributing to the Outlook Calendar MCP Tool! This document provides guidelines and instructions for contributing to this project.
4 |
5 | ## Code of Conduct
6 |
7 | By participating in this project, you agree to maintain a respectful and inclusive environment for everyone. Please be kind and courteous to others, and consider the impact of your words and actions.
8 |
9 | ## How to Contribute
10 |
11 | There are many ways to contribute to this project:
12 |
13 | 1. **Reporting Bugs**: If you find a bug, please create an issue with a detailed description of the problem, steps to reproduce it, and your environment details.
14 |
15 | 2. **Suggesting Enhancements**: Have an idea for a new feature or improvement? Create an issue with the tag "enhancement" and describe your suggestion in detail.
16 |
17 | 3. **Code Contributions**: Want to fix a bug or implement a feature? Follow the steps below:
18 |
19 | ### Pull Request Process
20 |
21 | 1. **Fork the Repository**: Create your own fork of the project.
22 |
23 | 2. **Create a Branch**: Create a branch for your changes with a descriptive name.
24 | ```
25 | git checkout -b feature/your-feature-name
26 | ```
27 |
28 | 3. **Make Your Changes**: Implement your changes, following the coding standards and practices used in the project.
29 |
30 | 4. **Test Your Changes**: Ensure your changes work as expected and don't break existing functionality.
31 |
32 | 5. **Update Documentation**: Update any relevant documentation, including README.md, if necessary.
33 |
34 | 6. **Submit a Pull Request**: Push your changes to your fork and submit a pull request to the main repository.
35 |
36 | 7. **Code Review**: Wait for a maintainer to review your pull request. You may need to make additional changes based on feedback.
37 |
38 | ## Development Setup
39 |
40 | To set up the project for development:
41 |
42 | 1. Clone the repository:
43 | ```
44 | git clone https://github.com/yourusername/outlook-calendar-mcp.git
45 | cd outlook-calendar-mcp
46 | ```
47 |
48 | 2. Install dependencies:
49 | ```
50 | npm install
51 | ```
52 |
53 | 3. Make your changes and test them locally.
54 |
55 | ## Coding Standards
56 |
57 | Please follow these coding standards when contributing:
58 |
59 | 1. **JavaScript/Node.js**:
60 | - Use ES6 syntax
61 | - Follow the existing code style
62 | - Use async/await for asynchronous operations
63 | - Add JSDoc comments for functions and complex logic
64 |
65 | 2. **VBScript**:
66 | - Use Option Explicit for all scripts
67 | - Follow the existing code style
68 | - Use structured error handling with descriptive messages
69 | - Add comments for functions and complex logic
70 |
71 | ## Testing
72 |
73 | Before submitting a pull request, please test your changes thoroughly:
74 |
75 | 1. Test all affected functionality
76 | 2. Ensure error handling works correctly
77 | 3. Verify that your changes don't break existing features
78 |
79 | ## License
80 |
81 | By contributing to this project, you agree that your contributions will be licensed under the project's MIT License.
82 |
83 | ## Questions?
84 |
85 | If you have any questions about contributing, please create an issue with the tag "question".
86 |
87 | Thank you for your contribution!
88 |
```
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
```markdown
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Project maintainers are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Project maintainers have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the project maintainers responsible for enforcement.
63 | All complaints will be reviewed and investigated promptly and fairly.
64 |
65 | All project maintainers are obligated to respect the privacy and security of the
66 | reporter of any incident.
67 |
68 | ## Enforcement Guidelines
69 |
70 | Project maintainers will follow these Community Impact Guidelines in determining
71 | the consequences for any action they deem in violation of this Code of Conduct:
72 |
73 | ### 1. Correction
74 |
75 | **Community Impact**: Use of inappropriate language or other behavior deemed
76 | unprofessional or unwelcome in the community.
77 |
78 | **Consequence**: A private, written warning from project maintainers, providing
79 | clarity around the nature of the violation and an explanation of why the
80 | behavior was inappropriate. A public apology may be requested.
81 |
82 | ### 2. Warning
83 |
84 | **Community Impact**: A violation through a single incident or series
85 | of actions.
86 |
87 | **Consequence**: A warning with consequences for continued behavior. No
88 | interaction with the people involved, including unsolicited interaction with
89 | those enforcing the Code of Conduct, for a specified period of time. This
90 | includes avoiding interactions in community spaces as well as external channels
91 | like social media. Violating these terms may lead to a temporary or
92 | permanent ban.
93 |
94 | ### 3. Temporary Ban
95 |
96 | **Community Impact**: A serious violation of community standards, including
97 | sustained inappropriate behavior.
98 |
99 | **Consequence**: A temporary ban from any sort of interaction or public
100 | communication with the community for a specified period of time. No public or
101 | private interaction with the people involved, including unsolicited interaction
102 | with those enforcing the Code of Conduct, is allowed during this period.
103 | Violating these terms may lead to a permanent ban.
104 |
105 | ### 4. Permanent Ban
106 |
107 | **Community Impact**: Demonstrating a pattern of violation of community
108 | standards, including sustained inappropriate behavior, harassment of an
109 | individual, or aggression toward or disparagement of classes of individuals.
110 |
111 | **Consequence**: A permanent ban from any sort of public interaction within
112 | the community.
113 |
114 | ## Attribution
115 |
116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
117 | version 2.0, available at
118 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
119 |
120 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
121 | enforcement ladder](https://github.com/mozilla/diversity).
122 |
123 | [homepage]: https://www.contributor-covenant.org
124 |
125 | For answers to common questions about this code of conduct, see the FAQ at
126 | https://www.contributor-covenant.org/faq. Translations are available at
127 | https://www.contributor-covenant.org/translations.
128 |
```
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
```yaml
1 | # This workflow will publish a package to npm when a release is created
2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3 |
4 | name: Node.js Package
5 |
6 | on:
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | publish-npm:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v4
15 | - uses: actions/setup-node@v4
16 | with:
17 | node-version: 16
18 | registry-url: https://registry.npmjs.org/
19 | - run: npm ci
20 | - run: npm publish
21 | env:
22 | NODE_AUTH_TOKEN: ${{secrets.npm_token}}
23 |
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "outlook-calendar-mcp",
3 | "version": "1.0.5",
4 | "description": "MCP server for Outlook Calendar integration",
5 | "main": "src/index.js",
6 | "type": "module",
7 | "author": "Meraj Mehrabi",
8 | "bugs": "https://github.com/merajmehrabi/Outlook_Calendar_MCP/issues",
9 | "license": "MIT",
10 | "bin": {
11 | "outlook-calendar-mcp": "./src/index.js"
12 | },
13 | "scripts": {
14 | "start": "chcp 65001 >nul 2>&1 && node src/index.js",
15 | "start:utf8": "chcp 65001 >nul 2>&1 && node src/index.js",
16 | "test": "chcp 65001 >nul 2>&1 && node test-outlook-connection.js",
17 | "test:outlook": "chcp 65001 >nul 2>&1 && node test-outlook-connection.js"
18 | },
19 | "keywords": [
20 | "outlook",
21 | "calendar",
22 | "mcp",
23 | "claude",
24 | "Model Context Protocol"
25 | ],
26 | "dependencies": {
27 | "@modelcontextprotocol/sdk": "^1.0.3"
28 | }
29 | }
30 |
```
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 | /**
3 | * index.js - Entry point for the Outlook Calendar MCP server
4 | */
5 |
6 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8 | import {
9 | CallToolRequestSchema,
10 | ErrorCode,
11 | ListToolsRequestSchema,
12 | McpError
13 | } from '@modelcontextprotocol/sdk/types.js';
14 | import { defineOutlookTools } from './outlookTools.js';
15 |
16 | /**
17 | * Main class for the Outlook Calendar MCP server
18 | */
19 | class OutlookCalendarServer {
20 | constructor() {
21 | // Initialize the MCP server
22 | this.server = new Server(
23 | {
24 | name: 'outlook-calendar-server',
25 | version: '1.0.0',
26 | },
27 | {
28 | capabilities: {
29 | tools: {},
30 | },
31 | }
32 | );
33 |
34 | // Define the tools
35 | this.tools = defineOutlookTools();
36 |
37 | // Set up request handlers
38 | this.setupToolHandlers();
39 |
40 | // Error handling
41 | this.server.onerror = (error) => console.error('[MCP Error]', error);
42 | process.on('SIGINT', async () => {
43 | await this.server.close();
44 | process.exit(0);
45 | });
46 | }
47 |
48 | /**
49 | * Sets up the tool request handlers
50 | */
51 | setupToolHandlers() {
52 | // List available tools
53 | this.server.setRequestHandler(ListToolsRequestSchema, async () => {
54 | const toolsList = Object.values(this.tools).map(tool => ({
55 | name: tool.name,
56 | description: tool.description,
57 | inputSchema: tool.inputSchema,
58 | }));
59 |
60 | return {
61 | tools: toolsList,
62 | };
63 | });
64 |
65 | // Handle tool calls
66 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
67 | const { name, arguments: args } = request.params;
68 |
69 | // Find the requested tool
70 | const tool = this.tools[name];
71 |
72 | if (!tool) {
73 | throw new McpError(
74 | ErrorCode.MethodNotFound,
75 | `Tool not found: ${name}`
76 | );
77 | }
78 |
79 | try {
80 | // Call the tool handler
81 | return await tool.handler(args);
82 | } catch (error) {
83 | console.error(`Error executing tool ${name}:`, error);
84 |
85 | return {
86 | content: [
87 | {
88 | type: 'text',
89 | text: `Error executing tool ${name}: ${error.message}`,
90 | },
91 | ],
92 | isError: true,
93 | };
94 | }
95 | });
96 | }
97 |
98 | /**
99 | * Starts the MCP server
100 | */
101 | async run() {
102 | try {
103 | const transport = new StdioServerTransport();
104 | await this.server.connect(transport);
105 | console.error('Outlook Calendar MCP server running on stdio');
106 | } catch (error) {
107 | console.error('Failed to start MCP server:', error);
108 | process.exit(1);
109 | }
110 | }
111 | }
112 |
113 | // Create and run the server
114 | const server = new OutlookCalendarServer();
115 | server.run().catch(console.error);
116 |
```
--------------------------------------------------------------------------------
/src/scriptRunner.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * scriptRunner.js - Handles execution of VBScript files and processes their output
3 | */
4 |
5 | import { exec } from 'child_process';
6 | import path from 'path';
7 | import { fileURLToPath } from 'url';
8 |
9 | // Get the directory name of the current module
10 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
11 |
12 | // Constants
13 | const SCRIPTS_DIR = path.resolve(__dirname, '../scripts');
14 | const SUCCESS_PREFIX = 'SUCCESS:';
15 | const ERROR_PREFIX = 'ERROR:';
16 |
17 | /**
18 | * Executes a VBScript file with the given parameters
19 | * @param {string} scriptName - Name of the script file (without .vbs extension)
20 | * @param {Object} params - Parameters to pass to the script
21 | * @returns {Promise<Object>} - Promise that resolves with the script output
22 | */
23 | export async function executeScript(scriptName, params = {}) {
24 | return new Promise((resolve, reject) => {
25 | // Build the command with UTF-8 support
26 | const scriptPath = path.join(SCRIPTS_DIR, `${scriptName}.vbs`);
27 | let command = `chcp 65001 >nul 2>&1 && cscript //NoLogo "${scriptPath}"`;
28 |
29 | // Add parameters
30 | for (const [key, value] of Object.entries(params)) {
31 | if (value !== undefined && value !== null && value !== '') {
32 | // Handle special characters in values
33 | const escapedValue = value.toString().replace(/"/g, '\\"');
34 | command += ` /${key}:"${escapedValue}"`;
35 | }
36 | }
37 |
38 | // Execute the command with UTF-8 encoding
39 | exec(command, { encoding: 'utf8' }, (error, stdout, stderr) => {
40 | // Check for execution errors
41 | if (error && !stdout.includes(SUCCESS_PREFIX)) {
42 | return reject(new Error(`Script execution failed: ${error.message}`));
43 | }
44 |
45 | // Check for script errors
46 | if (stdout.includes(ERROR_PREFIX)) {
47 | const errorMessage = stdout.substring(stdout.indexOf(ERROR_PREFIX) + ERROR_PREFIX.length).trim();
48 | return reject(new Error(`Script error: ${errorMessage}`));
49 | }
50 |
51 | // Process successful output
52 | if (stdout.includes(SUCCESS_PREFIX)) {
53 | try {
54 | const jsonStr = stdout.substring(stdout.indexOf(SUCCESS_PREFIX) + SUCCESS_PREFIX.length).trim();
55 | const result = JSON.parse(jsonStr);
56 | return resolve(result);
57 | } catch (parseError) {
58 | return reject(new Error(`Failed to parse script output: ${parseError.message}`));
59 | }
60 | }
61 |
62 | // If we get here, something unexpected happened
63 | reject(new Error(`Unexpected script output: ${stdout}`));
64 | });
65 | });
66 | }
67 |
68 | /**
69 | * Lists calendar events within a specified date range
70 | * @param {string} startDate - Start date in MM/DD/YYYY format
71 | * @param {string} endDate - End date in MM/DD/YYYY format (optional)
72 | * @param {string} calendar - Calendar name (optional)
73 | * @returns {Promise<Array>} - Promise that resolves with an array of events
74 | */
75 | export async function listEvents(startDate, endDate, calendar) {
76 | return executeScript('listEvents', { startDate, endDate, calendar });
77 | }
78 |
79 | /**
80 | * Creates a new calendar event
81 | * @param {Object} eventDetails - Event details
82 | * @returns {Promise<Object>} - Promise that resolves with the created event ID
83 | */
84 | export async function createEvent(eventDetails) {
85 | return executeScript('createEvent', eventDetails);
86 | }
87 |
88 | /**
89 | * Finds free time slots in the calendar
90 | * @param {string} startDate - Start date in MM/DD/YYYY format
91 | * @param {string} endDate - End date in MM/DD/YYYY format (optional)
92 | * @param {number} duration - Duration in minutes (optional)
93 | * @param {number} workDayStart - Work day start hour (0-23) (optional)
94 | * @param {number} workDayEnd - Work day end hour (0-23) (optional)
95 | * @param {string} calendar - Calendar name (optional)
96 | * @returns {Promise<Array>} - Promise that resolves with an array of free time slots
97 | */
98 | export async function findFreeSlots(startDate, endDate, duration, workDayStart, workDayEnd, calendar) {
99 | return executeScript('findFreeSlots', {
100 | startDate,
101 | endDate,
102 | duration,
103 | workDayStart,
104 | workDayEnd,
105 | calendar
106 | });
107 | }
108 |
109 | /**
110 | * Gets the response status of meeting attendees
111 | * @param {string} eventId - Event ID
112 | * @param {string} calendar - Calendar name (optional)
113 | * @returns {Promise<Object>} - Promise that resolves with meeting details and attendee status
114 | */
115 | export async function getAttendeeStatus(eventId, calendar) {
116 | return executeScript('getAttendeeStatus', { eventId, calendar });
117 | }
118 |
119 | /**
120 | * Deletes a calendar event by its ID
121 | * @param {string} eventId - Event ID
122 | * @param {string} calendar - Calendar name (optional)
123 | * @returns {Promise<Object>} - Promise that resolves with the deletion result
124 | */
125 | export async function deleteEvent(eventId, calendar) {
126 | return executeScript('deleteEvent', { eventId, calendar });
127 | }
128 |
129 | /**
130 | * Updates an existing calendar event
131 | * @param {string} eventId - Event ID to update
132 | * @param {string} subject - New subject (optional)
133 | * @param {string} startDate - New start date in MM/DD/YYYY format (optional)
134 | * @param {string} startTime - New start time in HH:MM AM/PM format (optional)
135 | * @param {string} endDate - New end date in MM/DD/YYYY format (optional)
136 | * @param {string} endTime - New end time in HH:MM AM/PM format (optional)
137 | * @param {string} location - New location (optional)
138 | * @param {string} body - New body/description (optional)
139 | * @param {string} calendar - Calendar name (optional)
140 | * @returns {Promise<Object>} - Promise that resolves with the update result
141 | */
142 | export async function updateEvent(eventId, subject, startDate, startTime, endDate, endTime, location, body, calendar) {
143 | return executeScript('updateEvent', {
144 | eventId,
145 | subject,
146 | startDate,
147 | startTime,
148 | endDate,
149 | endTime,
150 | location,
151 | body,
152 | calendar
153 | });
154 | }
155 |
156 | /**
157 | * Lists available calendars
158 | * @returns {Promise<Array>} - Promise that resolves with an array of calendars
159 | */
160 | export async function getCalendars() {
161 | return executeScript('getCalendars');
162 | }
163 |
```
--------------------------------------------------------------------------------
/test-outlook-connection.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * test-outlook-connection.js - Comprehensive test script for Outlook calendar operations
3 | *
4 | * This script tests if the MCP tool can access and manipulate your Outlook calendar by:
5 | * 1. Testing connection by listing available calendars
6 | * 2. Testing READ operation by listing events
7 | * 3. Testing WRITE operation by creating a test event
8 | * 4. Testing DELETE operation by removing the test event
9 | */
10 |
11 | import { exec } from 'child_process';
12 | import path from 'path';
13 | import { fileURLToPath } from 'url';
14 | import { promisify } from 'util';
15 |
16 | // Convert exec to promise-based
17 | const execPromise = promisify(exec);
18 |
19 | // Get the directory name of the current module
20 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
21 |
22 | // Script paths
23 | const scriptsDir = path.join(__dirname, 'scripts');
24 | const getCalendarsScript = path.join(scriptsDir, 'getCalendars.vbs');
25 | const listEventsScript = path.join(scriptsDir, 'listEvents.vbs');
26 | const createEventScript = path.join(scriptsDir, 'createEvent.vbs');
27 | const updateEventScript = path.join(scriptsDir, 'updateEvent.vbs');
28 | const deleteEventScript = path.join(scriptsDir, 'deleteEvent.vbs');
29 |
30 | // Test event details
31 | const testEventSubject = `Test Event ${new Date().toISOString()}`;
32 | const today = new Date();
33 | const formattedDate = `${today.getMonth() + 1}/${today.getDate()}/${today.getFullYear()}`;
34 | let testEventId = null;
35 |
36 | // Helper function to parse script output
37 | function parseScriptOutput(stdout) {
38 | if (stdout.includes('SUCCESS:')) {
39 | const jsonStr = stdout.substring(stdout.indexOf('SUCCESS:') + 'SUCCESS:'.length).trim();
40 | try {
41 | return { success: true, data: JSON.parse(jsonStr) };
42 | } catch (parseError) {
43 | return { success: false, error: `Error parsing JSON: ${parseError.message}`, raw: jsonStr };
44 | }
45 | } else if (stdout.includes('ERROR:')) {
46 | const errorMessage = stdout.substring(stdout.indexOf('ERROR:') + 'ERROR:'.length).trim();
47 | return { success: false, error: errorMessage };
48 | } else {
49 | return { success: false, error: 'Unexpected script output', raw: stdout };
50 | }
51 | }
52 |
53 | // Execute a VBScript with parameters
54 | async function executeScript(scriptPath, params = []) {
55 | const paramString = params.map(p => `/${p.name}:"${p.value}"`).join(' ');
56 | const command = `cscript //NoLogo "${scriptPath}" ${paramString}`;
57 |
58 | try {
59 | const { stdout, stderr } = await execPromise(command);
60 |
61 | if (stderr) {
62 | return { success: false, error: stderr };
63 | }
64 |
65 | return parseScriptOutput(stdout);
66 | } catch (error) {
67 | return { success: false, error: error.message };
68 | }
69 | }
70 |
71 | // Main test function
72 | async function runTests() {
73 | console.log('🔍 TESTING OUTLOOK CALENDAR MCP TOOL');
74 | console.log('====================================');
75 |
76 | // Test 1: Connection Test
77 | console.log('\n📋 TEST 1: Connection Test (getCalendars.vbs)');
78 | console.log('-------------------------------------------');
79 |
80 | const connectionResult = await executeScript(getCalendarsScript);
81 |
82 | if (connectionResult.success) {
83 | console.log('✅ Connection test passed!');
84 | console.log('Available calendars:');
85 | console.log(JSON.stringify(connectionResult.data, null, 2));
86 | } else {
87 | console.error('❌ Connection test failed:', connectionResult.error);
88 | console.error('Cannot proceed with further tests without Outlook connection.');
89 | process.exit(1);
90 | }
91 |
92 | // Test 2: Read Test
93 | console.log('\n📋 TEST 2: Read Test (listEvents.vbs)');
94 | console.log('-------------------------------------------');
95 |
96 | const readResult = await executeScript(listEventsScript, [
97 | { name: 'startDate', value: formattedDate },
98 | { name: 'endDate', value: formattedDate }
99 | ]);
100 |
101 | if (readResult.success) {
102 | console.log('✅ Read test passed!');
103 | console.log(`Found ${readResult.data.length} events for today (${formattedDate}).`);
104 | } else {
105 | console.error('❌ Read test failed:', readResult.error);
106 | console.error('Cannot proceed with further tests.');
107 | process.exit(1);
108 | }
109 |
110 | // Test 3: Write Test
111 | console.log('\n📋 TEST 3: Write Test (createEvent.vbs)');
112 | console.log('-------------------------------------------');
113 |
114 | const writeResult = await executeScript(createEventScript, [
115 | { name: 'subject', value: testEventSubject },
116 | { name: 'startDate', value: formattedDate },
117 | { name: 'startTime', value: '2:00 PM' },
118 | { name: 'endDate', value: formattedDate },
119 | { name: 'endTime', value: '2:30 PM' },
120 | { name: 'location', value: 'Test Location' },
121 | { name: 'body', value: 'This is a test event created by the Outlook Calendar MCP Tool test script.' }
122 | ]);
123 |
124 | if (writeResult.success) {
125 | testEventId = writeResult.data.eventId;
126 | console.log('✅ Write test passed!');
127 | console.log(`Created test event with ID: ${testEventId}`);
128 | } else {
129 | console.error('❌ Write test failed:', writeResult.error);
130 | console.error('Cannot proceed with delete test.');
131 | process.exit(1);
132 | }
133 |
134 | // Test 4: Update Test
135 | console.log('\n📋 TEST 4: Update Test (updateEvent.vbs)');
136 | console.log('-------------------------------------------');
137 |
138 | if (!testEventId) {
139 | console.error('❌ Update test skipped: No event ID from write test.');
140 | process.exit(1);
141 | }
142 |
143 | const updateResult = await executeScript(updateEventScript, [
144 | { name: 'eventId', value: testEventId },
145 | { name: 'subject', value: `${testEventSubject} - UPDATED` },
146 | { name: 'location', value: 'Updated Test Location' }
147 | ]);
148 |
149 | if (updateResult.success && updateResult.data.success) {
150 | console.log('✅ Update test passed!');
151 | console.log(`Successfully updated test event with ID: ${testEventId}`);
152 | } else {
153 | console.error('❌ Update test failed:', updateResult.error || 'Unknown error');
154 | process.exit(1);
155 | }
156 |
157 | // Test 5: Delete Test
158 | console.log('\n📋 TEST 5: Delete Test (deleteEvent.vbs)');
159 | console.log('-------------------------------------------');
160 |
161 | if (!testEventId) {
162 | console.error('❌ Delete test skipped: No event ID from write test.');
163 | process.exit(1);
164 | }
165 |
166 | const deleteResult = await executeScript(deleteEventScript, [
167 | { name: 'eventId', value: testEventId }
168 | ]);
169 |
170 | if (deleteResult.success && deleteResult.data.success) {
171 | console.log('✅ Delete test passed!');
172 | console.log(`Successfully deleted test event with ID: ${testEventId}`);
173 | } else {
174 | console.error('❌ Delete test failed:', deleteResult.error || 'Unknown error');
175 | process.exit(1);
176 | }
177 |
178 | // Summary
179 | console.log('\n📋 TEST SUMMARY');
180 | console.log('-------------------------------------------');
181 | console.log('✅ Connection Test: PASSED');
182 | console.log('✅ Read Test: PASSED');
183 | console.log('✅ Write Test: PASSED');
184 | console.log('✅ Update Test: PASSED');
185 | console.log('✅ Delete Test: PASSED');
186 | console.log('\n🎉 All tests passed! The Outlook Calendar MCP Tool is working correctly.');
187 | }
188 |
189 | // Run the tests
190 | runTests().catch(error => {
191 | console.error('Error running tests:', error);
192 | process.exit(1);
193 | });
194 |
```
--------------------------------------------------------------------------------
/src/outlookTools.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * outlookTools.js - Defines MCP tools for Outlook calendar operations
3 | */
4 |
5 | import {
6 | listEvents,
7 | createEvent,
8 | findFreeSlots,
9 | getAttendeeStatus,
10 | getCalendars,
11 | deleteEvent,
12 | updateEvent
13 | } from './scriptRunner.js';
14 |
15 | /**
16 | * Defines the MCP tools for Outlook calendar operations
17 | * @returns {Object} - Object containing tool definitions
18 | */
19 | export function defineOutlookTools() {
20 | return {
21 | // List calendar events
22 | list_events: {
23 | name: 'list_events',
24 | description: 'List calendar events within a specified date range',
25 | inputSchema: {
26 | type: 'object',
27 | properties: {
28 | startDate: {
29 | type: 'string',
30 | description: 'Start date in MM/DD/YYYY format'
31 | },
32 | endDate: {
33 | type: 'string',
34 | description: 'End date in MM/DD/YYYY format'
35 | },
36 | calendar: {
37 | type: 'string',
38 | description: 'Calendar name (optional)'
39 | }
40 | },
41 | required: ['startDate', 'endDate']
42 | },
43 | handler: async ({ startDate, endDate, calendar }) => {
44 | try {
45 | const events = await listEvents(startDate, endDate, calendar);
46 | return {
47 | content: [
48 | {
49 | type: 'text',
50 | text: JSON.stringify(events, null, 2)
51 | }
52 | ]
53 | };
54 | } catch (error) {
55 | return {
56 | content: [
57 | {
58 | type: 'text',
59 | text: `Error listing events: ${error.message}`
60 | }
61 | ],
62 | isError: true
63 | };
64 | }
65 | }
66 | },
67 |
68 | // Create calendar event
69 | create_event: {
70 | name: 'create_event',
71 | description: 'Create a new calendar event or meeting',
72 | inputSchema: {
73 | type: 'object',
74 | properties: {
75 | subject: {
76 | type: 'string',
77 | description: 'Event subject/title'
78 | },
79 | startDate: {
80 | type: 'string',
81 | description: 'Start date in MM/DD/YYYY format'
82 | },
83 | startTime: {
84 | type: 'string',
85 | description: 'Start time in HH:MM AM/PM format'
86 | },
87 | endDate: {
88 | type: 'string',
89 | description: 'End date in MM/DD/YYYY format (optional, defaults to start date)'
90 | },
91 | endTime: {
92 | type: 'string',
93 | description: 'End time in HH:MM AM/PM format (optional, defaults to 30 minutes after start time)'
94 | },
95 | location: {
96 | type: 'string',
97 | description: 'Event location (optional)'
98 | },
99 | body: {
100 | type: 'string',
101 | description: 'Event description/body (optional)'
102 | },
103 | isMeeting: {
104 | type: 'boolean',
105 | description: 'Whether this is a meeting with attendees (optional, defaults to false)'
106 | },
107 | attendees: {
108 | type: 'string',
109 | description: 'Semicolon-separated list of attendee email addresses (optional)'
110 | },
111 | calendar: {
112 | type: 'string',
113 | description: 'Calendar name (optional)'
114 | }
115 | },
116 | required: ['subject', 'startDate', 'startTime']
117 | },
118 | handler: async (eventDetails) => {
119 | try {
120 | const result = await createEvent(eventDetails);
121 | return {
122 | content: [
123 | {
124 | type: 'text',
125 | text: `Event created successfully with ID: ${result.eventId}`
126 | }
127 | ]
128 | };
129 | } catch (error) {
130 | return {
131 | content: [
132 | {
133 | type: 'text',
134 | text: `Error creating event: ${error.message}`
135 | }
136 | ],
137 | isError: true
138 | };
139 | }
140 | }
141 | },
142 |
143 | // Find free time slots
144 | find_free_slots: {
145 | name: 'find_free_slots',
146 | description: 'Find available time slots in the calendar',
147 | inputSchema: {
148 | type: 'object',
149 | properties: {
150 | startDate: {
151 | type: 'string',
152 | description: 'Start date in MM/DD/YYYY format'
153 | },
154 | endDate: {
155 | type: 'string',
156 | description: 'End date in MM/DD/YYYY format (optional, defaults to 7 days from start date)'
157 | },
158 | duration: {
159 | type: 'number',
160 | description: 'Duration in minutes (optional, defaults to 30)'
161 | },
162 | workDayStart: {
163 | type: 'number',
164 | description: 'Work day start hour (0-23) (optional, defaults to 9)'
165 | },
166 | workDayEnd: {
167 | type: 'number',
168 | description: 'Work day end hour (0-23) (optional, defaults to 17)'
169 | },
170 | calendar: {
171 | type: 'string',
172 | description: 'Calendar name (optional)'
173 | }
174 | },
175 | required: ['startDate']
176 | },
177 | handler: async ({ startDate, endDate, duration, workDayStart, workDayEnd, calendar }) => {
178 | try {
179 | const freeSlots = await findFreeSlots(startDate, endDate, duration, workDayStart, workDayEnd, calendar);
180 | return {
181 | content: [
182 | {
183 | type: 'text',
184 | text: JSON.stringify(freeSlots, null, 2)
185 | }
186 | ]
187 | };
188 | } catch (error) {
189 | return {
190 | content: [
191 | {
192 | type: 'text',
193 | text: `Error finding free slots: ${error.message}`
194 | }
195 | ],
196 | isError: true
197 | };
198 | }
199 | }
200 | },
201 |
202 | // Get attendee status
203 | get_attendee_status: {
204 | name: 'get_attendee_status',
205 | description: 'Check the response status of meeting attendees',
206 | inputSchema: {
207 | type: 'object',
208 | properties: {
209 | eventId: {
210 | type: 'string',
211 | description: 'Event ID'
212 | },
213 | calendar: {
214 | type: 'string',
215 | description: 'Calendar name (optional)'
216 | }
217 | },
218 | required: ['eventId']
219 | },
220 | handler: async ({ eventId, calendar }) => {
221 | try {
222 | const attendeeStatus = await getAttendeeStatus(eventId, calendar);
223 | return {
224 | content: [
225 | {
226 | type: 'text',
227 | text: JSON.stringify(attendeeStatus, null, 2)
228 | }
229 | ]
230 | };
231 | } catch (error) {
232 | return {
233 | content: [
234 | {
235 | type: 'text',
236 | text: `Error getting attendee status: ${error.message}`
237 | }
238 | ],
239 | isError: true
240 | };
241 | }
242 | }
243 | },
244 |
245 | // Delete calendar event
246 | delete_event: {
247 | name: 'delete_event',
248 | description: 'Delete a calendar event by its ID',
249 | inputSchema: {
250 | type: 'object',
251 | properties: {
252 | eventId: {
253 | type: 'string',
254 | description: 'Event ID to delete'
255 | },
256 | calendar: {
257 | type: 'string',
258 | description: 'Calendar name (optional)'
259 | }
260 | },
261 | required: ['eventId']
262 | },
263 | handler: async ({ eventId, calendar }) => {
264 | try {
265 | const result = await deleteEvent(eventId, calendar);
266 | return {
267 | content: [
268 | {
269 | type: 'text',
270 | text: result.success
271 | ? `Event deleted successfully`
272 | : `Failed to delete event`
273 | }
274 | ]
275 | };
276 | } catch (error) {
277 | return {
278 | content: [
279 | {
280 | type: 'text',
281 | text: `Error deleting event: ${error.message}`
282 | }
283 | ],
284 | isError: true
285 | };
286 | }
287 | }
288 | },
289 |
290 | // Update calendar event
291 | update_event: {
292 | name: 'update_event',
293 | description: 'Update an existing calendar event',
294 | inputSchema: {
295 | type: 'object',
296 | properties: {
297 | eventId: {
298 | type: 'string',
299 | description: 'Event ID to update'
300 | },
301 | subject: {
302 | type: 'string',
303 | description: 'New event subject/title (optional)'
304 | },
305 | startDate: {
306 | type: 'string',
307 | description: 'New start date in MM/DD/YYYY format (optional)'
308 | },
309 | startTime: {
310 | type: 'string',
311 | description: 'New start time in HH:MM AM/PM format (optional)'
312 | },
313 | endDate: {
314 | type: 'string',
315 | description: 'New end date in MM/DD/YYYY format (optional)'
316 | },
317 | endTime: {
318 | type: 'string',
319 | description: 'New end time in HH:MM AM/PM format (optional)'
320 | },
321 | location: {
322 | type: 'string',
323 | description: 'New event location (optional)'
324 | },
325 | body: {
326 | type: 'string',
327 | description: 'New event description/body (optional)'
328 | },
329 | calendar: {
330 | type: 'string',
331 | description: 'Calendar name (optional)'
332 | }
333 | },
334 | required: ['eventId']
335 | },
336 | handler: async ({ eventId, subject, startDate, startTime, endDate, endTime, location, body, calendar }) => {
337 | try {
338 | const result = await updateEvent(eventId, subject, startDate, startTime, endDate, endTime, location, body, calendar);
339 | return {
340 | content: [
341 | {
342 | type: 'text',
343 | text: result.success
344 | ? `Event updated successfully`
345 | : `Failed to update event`
346 | }
347 | ]
348 | };
349 | } catch (error) {
350 | return {
351 | content: [
352 | {
353 | type: 'text',
354 | text: `Error updating event: ${error.message}`
355 | }
356 | ],
357 | isError: true
358 | };
359 | }
360 | }
361 | },
362 |
363 | // Get calendars
364 | get_calendars: {
365 | name: 'get_calendars',
366 | description: 'List available calendars',
367 | inputSchema: {
368 | type: 'object',
369 | properties: {}
370 | },
371 | handler: async () => {
372 | try {
373 | const calendars = await getCalendars();
374 | return {
375 | content: [
376 | {
377 | type: 'text',
378 | text: JSON.stringify(calendars, null, 2)
379 | }
380 | ]
381 | };
382 | } catch (error) {
383 | return {
384 | content: [
385 | {
386 | type: 'text',
387 | text: `Error getting calendars: ${error.message}`
388 | }
389 | ],
390 | isError: true
391 | };
392 | }
393 | }
394 | }
395 | };
396 | }
397 |
```