# 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:
--------------------------------------------------------------------------------
```
# Dependency directories
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Build outputs
dist/
build/
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# IDEs and editors
.idea/
.vscode/
*.swp
*.swo
.DS_Store
.vs/
*.sublime-workspace
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
#Cline
.clinerules
memory-bank/
CLAUDE.md
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Outlook Calendar MCP Tool
A Model Context Protocol (MCP) server that allows Claude to access and manage your local Microsoft Outlook calendar (Windows only).
<a href="https://glama.ai/mcp/servers/08enllwrbp">
<img width="380" height="200" src="https://glama.ai/mcp/servers/08enllwrbp/badge" alt="Outlook Calendar MCP server" />
</a>
[](https://opensource.org/licenses/MIT)
## Features
- **View Calendar Events**: List events within a date range, view event details, check attendee status
- **Manage Calendar Events**: Create new events and meetings, update existing events
- **Calendar Intelligence**: Find free time slots for scheduling, identify optimal meeting times
- **Multiple Calendar Support**: Access different calendars in your Outlook profile
## Prerequisites
- Windows operating system
- Microsoft Outlook desktop client installed
- **VBScript support** (see VBScript Installation below if you're on Windows 11 24H2+)
- Node.js (version 14.x or higher)
- npm (comes with Node.js)
### VBScript Installation (Windows 11 24H2+ Users)
**Important**: Starting with Windows 11 24H2, VBScript is no longer installed by default and must be enabled as an optional feature.
If you're experiencing issues with the MCP server not working after a Windows update, you likely need to install VBScript:
1. Open **Settings** (Windows + I)
2. Go to **Apps** → **Optional features**
3. Click **"View features"** next to **"Add an optional feature"**
4. Search for **"VBScript"**
5. Select **VBScript** and click **Install**
6. Restart your computer after installation
**VBScript Deprecation Timeline:**
- **Phase 1** (Late 2024+): Available as optional feature in Windows 11 24H2
- **Phase 2** (~2027): Will no longer be enabled by default
- **Phase 3** (Future): Complete removal from Windows
*Note: Thanks to community feedback about VBScript deprecation, I'm considering architectural improvements to make the project more future-proof.*
## Installation
### Option 1: Install from npm
```bash
npm install -g outlook-calendar-mcp
```
You can also run it directly without installation using npx:
```bash
npx outlook-calendar-mcp
```
### Option 2: Install from source
1. Clone this repository or download the source code
2. Install dependencies:
```bash
npm install
```
3. Run the server:
```bash
npm start
```
## MCP Server Configuration
To use this tool with Claude, you need to add it to your MCP settings configuration file.
### For Claude Desktop App
Add the following to your Claude Desktop configuration file (located at `%APPDATA%\Claude\claude_desktop_config.json`):
#### If installed globally via npm:
```json
{
"mcpServers": {
"outlook-calendar": {
"command": "outlook-calendar-mcp",
"args": [],
"env": {}
}
}
}
```
#### Using npx (without installation):
```json
{
"mcpServers": {
"outlook-calendar": {
"command": "npx",
"args": ["-y", "outlook-calendar-mcp"],
"env": {}
}
}
}
```
#### If installed from source:
```json
{
"mcpServers": {
"outlook-calendar": {
"command": "node",
"args": ["path/to/outlook-calendar-mcp/src/index.js"],
"env": {}
}
}
}
```
### For Claude VSCode Extension
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`):
#### If installed globally via npm:
```json
{
"mcpServers": {
"outlook-calendar": {
"command": "outlook-calendar-mcp",
"args": [],
"env": {}
}
}
}
```
#### Using npx (without installation):
```json
{
"mcpServers": {
"outlook-calendar": {
"command": "npx",
"args": ["-y", "outlook-calendar-mcp"],
"env": {}
}
}
}
```
#### If installed from source:
```json
{
"mcpServers": {
"outlook-calendar": {
"command": "node",
"args": ["path/to/outlook-calendar-mcp/src/index.js"],
"env": {}
}
}
}
```
For source installation, replace `path/to/outlook-calendar-mcp` with the actual path to where you installed this tool.
## Usage
Once configured, Claude will have access to the following tools:
### List Calendar Events
```
list_events
- startDate: Start date in MM/DD/YYYY format
- endDate: End date in MM/DD/YYYY format (optional)
- calendar: Calendar name (optional)
```
Example: "List my calendar events for next week"
### Create Calendar Event
```
create_event
- subject: Event subject/title
- startDate: Start date in MM/DD/YYYY format
- startTime: Start time in HH:MM AM/PM format
- endDate: End date in MM/DD/YYYY format (optional)
- endTime: End time in HH:MM AM/PM format (optional)
- location: Event location (optional)
- body: Event description (optional)
- isMeeting: Whether this is a meeting with attendees (optional)
- attendees: Semicolon-separated list of attendee email addresses (optional)
- calendar: Calendar name (optional)
```
Example: "Add a meeting with John about the project proposal on Friday at 2 PM"
### Find Free Time Slots
```
find_free_slots
- startDate: Start date in MM/DD/YYYY format
- endDate: End date in MM/DD/YYYY format (optional)
- duration: Duration in minutes (optional)
- workDayStart: Work day start hour (0-23) (optional)
- workDayEnd: Work day end hour (0-23) (optional)
- calendar: Calendar name (optional)
```
Example: "When am I free for a 1-hour meeting this week?"
### Get Attendee Status
```
get_attendee_status
- eventId: Event ID
- calendar: Calendar name (optional)
```
Example: "Who hasn't responded to my team meeting invitation?"
> **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.
### Update Calendar Event
```
update_event
- eventId: Event ID to update
- subject: New event subject/title (optional)
- startDate: New start date in MM/DD/YYYY format (optional)
- startTime: New start time in HH:MM AM/PM format (optional)
- endDate: New end date in MM/DD/YYYY format (optional)
- endTime: New end time in HH:MM AM/PM format (optional)
- location: New event location (optional)
- body: New event description (optional)
- calendar: Calendar name (optional)
```
Example: "Update my team meeting tomorrow to start at 3 PM instead of 2 PM"
### Get Calendars
```
get_calendars
```
Example: "Show me my available calendars"
## Security Notes
- On first use, Outlook may display security prompts to allow script access
- The tool only accesses your local Outlook client and does not send calendar data to external servers
- All calendar operations are performed locally on your computer
## Troubleshooting
- **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
- **"Script execution failed" errors**: Usually indicates VBScript is not available or Outlook is not accessible
- **Outlook Security Prompts**: If you see security prompts from Outlook, you need to allow the script to access your Outlook data
- **Script Execution Policy**: If you encounter script execution errors, you may need to adjust your PowerShell execution policy
- **Path Issues**: Ensure the path in your MCP configuration file points to the correct location of the tool
## Contributing
We welcome contributions to the Outlook Calendar MCP Tool! Please see our [Contributing Guide](CONTRIBUTING.md) for details on how to get started.
By participating in this project, you agree to abide by our [Code of Conduct](CODE_OF_CONDUCT.md).
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
```
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
```markdown
# Contributing to Outlook Calendar MCP Tool
Thank you for considering contributing to the Outlook Calendar MCP Tool! This document provides guidelines and instructions for contributing to this project.
## Code of Conduct
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.
## How to Contribute
There are many ways to contribute to this project:
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.
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.
3. **Code Contributions**: Want to fix a bug or implement a feature? Follow the steps below:
### Pull Request Process
1. **Fork the Repository**: Create your own fork of the project.
2. **Create a Branch**: Create a branch for your changes with a descriptive name.
```
git checkout -b feature/your-feature-name
```
3. **Make Your Changes**: Implement your changes, following the coding standards and practices used in the project.
4. **Test Your Changes**: Ensure your changes work as expected and don't break existing functionality.
5. **Update Documentation**: Update any relevant documentation, including README.md, if necessary.
6. **Submit a Pull Request**: Push your changes to your fork and submit a pull request to the main repository.
7. **Code Review**: Wait for a maintainer to review your pull request. You may need to make additional changes based on feedback.
## Development Setup
To set up the project for development:
1. Clone the repository:
```
git clone https://github.com/yourusername/outlook-calendar-mcp.git
cd outlook-calendar-mcp
```
2. Install dependencies:
```
npm install
```
3. Make your changes and test them locally.
## Coding Standards
Please follow these coding standards when contributing:
1. **JavaScript/Node.js**:
- Use ES6 syntax
- Follow the existing code style
- Use async/await for asynchronous operations
- Add JSDoc comments for functions and complex logic
2. **VBScript**:
- Use Option Explicit for all scripts
- Follow the existing code style
- Use structured error handling with descriptive messages
- Add comments for functions and complex logic
## Testing
Before submitting a pull request, please test your changes thoroughly:
1. Test all affected functionality
2. Ensure error handling works correctly
3. Verify that your changes don't break existing features
## License
By contributing to this project, you agree that your contributions will be licensed under the project's MIT License.
## Questions?
If you have any questions about contributing, please create an issue with the tag "question".
Thank you for your contribution!
```
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
```markdown
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Project maintainers are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the project maintainers responsible for enforcement.
All complaints will be reviewed and investigated promptly and fairly.
All project maintainers are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Project maintainers will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from project maintainers, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
```
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
```yaml
# This workflow will publish a package to npm when a release is created
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
name: Node.js Package
on:
release:
types: [created]
jobs:
publish-npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "outlook-calendar-mcp",
"version": "1.0.5",
"description": "MCP server for Outlook Calendar integration",
"main": "src/index.js",
"type": "module",
"author": "Meraj Mehrabi",
"bugs": "https://github.com/merajmehrabi/Outlook_Calendar_MCP/issues",
"license": "MIT",
"bin": {
"outlook-calendar-mcp": "./src/index.js"
},
"scripts": {
"start": "chcp 65001 >nul 2>&1 && node src/index.js",
"start:utf8": "chcp 65001 >nul 2>&1 && node src/index.js",
"test": "chcp 65001 >nul 2>&1 && node test-outlook-connection.js",
"test:outlook": "chcp 65001 >nul 2>&1 && node test-outlook-connection.js"
},
"keywords": [
"outlook",
"calendar",
"mcp",
"claude",
"Model Context Protocol"
],
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.3"
}
}
```
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
```javascript
#!/usr/bin/env node
/**
* index.js - Entry point for the Outlook Calendar MCP server
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError
} from '@modelcontextprotocol/sdk/types.js';
import { defineOutlookTools } from './outlookTools.js';
/**
* Main class for the Outlook Calendar MCP server
*/
class OutlookCalendarServer {
constructor() {
// Initialize the MCP server
this.server = new Server(
{
name: 'outlook-calendar-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Define the tools
this.tools = defineOutlookTools();
// Set up request handlers
this.setupToolHandlers();
// Error handling
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
/**
* Sets up the tool request handlers
*/
setupToolHandlers() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
const toolsList = Object.values(this.tools).map(tool => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
}));
return {
tools: toolsList,
};
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
// Find the requested tool
const tool = this.tools[name];
if (!tool) {
throw new McpError(
ErrorCode.MethodNotFound,
`Tool not found: ${name}`
);
}
try {
// Call the tool handler
return await tool.handler(args);
} catch (error) {
console.error(`Error executing tool ${name}:`, error);
return {
content: [
{
type: 'text',
text: `Error executing tool ${name}: ${error.message}`,
},
],
isError: true,
};
}
});
}
/**
* Starts the MCP server
*/
async run() {
try {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Outlook Calendar MCP server running on stdio');
} catch (error) {
console.error('Failed to start MCP server:', error);
process.exit(1);
}
}
}
// Create and run the server
const server = new OutlookCalendarServer();
server.run().catch(console.error);
```
--------------------------------------------------------------------------------
/src/scriptRunner.js:
--------------------------------------------------------------------------------
```javascript
/**
* scriptRunner.js - Handles execution of VBScript files and processes their output
*/
import { exec } from 'child_process';
import path from 'path';
import { fileURLToPath } from 'url';
// Get the directory name of the current module
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Constants
const SCRIPTS_DIR = path.resolve(__dirname, '../scripts');
const SUCCESS_PREFIX = 'SUCCESS:';
const ERROR_PREFIX = 'ERROR:';
/**
* Executes a VBScript file with the given parameters
* @param {string} scriptName - Name of the script file (without .vbs extension)
* @param {Object} params - Parameters to pass to the script
* @returns {Promise<Object>} - Promise that resolves with the script output
*/
export async function executeScript(scriptName, params = {}) {
return new Promise((resolve, reject) => {
// Build the command with UTF-8 support
const scriptPath = path.join(SCRIPTS_DIR, `${scriptName}.vbs`);
let command = `chcp 65001 >nul 2>&1 && cscript //NoLogo "${scriptPath}"`;
// Add parameters
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null && value !== '') {
// Handle special characters in values
const escapedValue = value.toString().replace(/"/g, '\\"');
command += ` /${key}:"${escapedValue}"`;
}
}
// Execute the command with UTF-8 encoding
exec(command, { encoding: 'utf8' }, (error, stdout, stderr) => {
// Check for execution errors
if (error && !stdout.includes(SUCCESS_PREFIX)) {
return reject(new Error(`Script execution failed: ${error.message}`));
}
// Check for script errors
if (stdout.includes(ERROR_PREFIX)) {
const errorMessage = stdout.substring(stdout.indexOf(ERROR_PREFIX) + ERROR_PREFIX.length).trim();
return reject(new Error(`Script error: ${errorMessage}`));
}
// Process successful output
if (stdout.includes(SUCCESS_PREFIX)) {
try {
const jsonStr = stdout.substring(stdout.indexOf(SUCCESS_PREFIX) + SUCCESS_PREFIX.length).trim();
const result = JSON.parse(jsonStr);
return resolve(result);
} catch (parseError) {
return reject(new Error(`Failed to parse script output: ${parseError.message}`));
}
}
// If we get here, something unexpected happened
reject(new Error(`Unexpected script output: ${stdout}`));
});
});
}
/**
* Lists calendar events within a specified date range
* @param {string} startDate - Start date in MM/DD/YYYY format
* @param {string} endDate - End date in MM/DD/YYYY format (optional)
* @param {string} calendar - Calendar name (optional)
* @returns {Promise<Array>} - Promise that resolves with an array of events
*/
export async function listEvents(startDate, endDate, calendar) {
return executeScript('listEvents', { startDate, endDate, calendar });
}
/**
* Creates a new calendar event
* @param {Object} eventDetails - Event details
* @returns {Promise<Object>} - Promise that resolves with the created event ID
*/
export async function createEvent(eventDetails) {
return executeScript('createEvent', eventDetails);
}
/**
* Finds free time slots in the calendar
* @param {string} startDate - Start date in MM/DD/YYYY format
* @param {string} endDate - End date in MM/DD/YYYY format (optional)
* @param {number} duration - Duration in minutes (optional)
* @param {number} workDayStart - Work day start hour (0-23) (optional)
* @param {number} workDayEnd - Work day end hour (0-23) (optional)
* @param {string} calendar - Calendar name (optional)
* @returns {Promise<Array>} - Promise that resolves with an array of free time slots
*/
export async function findFreeSlots(startDate, endDate, duration, workDayStart, workDayEnd, calendar) {
return executeScript('findFreeSlots', {
startDate,
endDate,
duration,
workDayStart,
workDayEnd,
calendar
});
}
/**
* Gets the response status of meeting attendees
* @param {string} eventId - Event ID
* @param {string} calendar - Calendar name (optional)
* @returns {Promise<Object>} - Promise that resolves with meeting details and attendee status
*/
export async function getAttendeeStatus(eventId, calendar) {
return executeScript('getAttendeeStatus', { eventId, calendar });
}
/**
* Deletes a calendar event by its ID
* @param {string} eventId - Event ID
* @param {string} calendar - Calendar name (optional)
* @returns {Promise<Object>} - Promise that resolves with the deletion result
*/
export async function deleteEvent(eventId, calendar) {
return executeScript('deleteEvent', { eventId, calendar });
}
/**
* Updates an existing calendar event
* @param {string} eventId - Event ID to update
* @param {string} subject - New subject (optional)
* @param {string} startDate - New start date in MM/DD/YYYY format (optional)
* @param {string} startTime - New start time in HH:MM AM/PM format (optional)
* @param {string} endDate - New end date in MM/DD/YYYY format (optional)
* @param {string} endTime - New end time in HH:MM AM/PM format (optional)
* @param {string} location - New location (optional)
* @param {string} body - New body/description (optional)
* @param {string} calendar - Calendar name (optional)
* @returns {Promise<Object>} - Promise that resolves with the update result
*/
export async function updateEvent(eventId, subject, startDate, startTime, endDate, endTime, location, body, calendar) {
return executeScript('updateEvent', {
eventId,
subject,
startDate,
startTime,
endDate,
endTime,
location,
body,
calendar
});
}
/**
* Lists available calendars
* @returns {Promise<Array>} - Promise that resolves with an array of calendars
*/
export async function getCalendars() {
return executeScript('getCalendars');
}
```
--------------------------------------------------------------------------------
/test-outlook-connection.js:
--------------------------------------------------------------------------------
```javascript
/**
* test-outlook-connection.js - Comprehensive test script for Outlook calendar operations
*
* This script tests if the MCP tool can access and manipulate your Outlook calendar by:
* 1. Testing connection by listing available calendars
* 2. Testing READ operation by listing events
* 3. Testing WRITE operation by creating a test event
* 4. Testing DELETE operation by removing the test event
*/
import { exec } from 'child_process';
import path from 'path';
import { fileURLToPath } from 'url';
import { promisify } from 'util';
// Convert exec to promise-based
const execPromise = promisify(exec);
// Get the directory name of the current module
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Script paths
const scriptsDir = path.join(__dirname, 'scripts');
const getCalendarsScript = path.join(scriptsDir, 'getCalendars.vbs');
const listEventsScript = path.join(scriptsDir, 'listEvents.vbs');
const createEventScript = path.join(scriptsDir, 'createEvent.vbs');
const updateEventScript = path.join(scriptsDir, 'updateEvent.vbs');
const deleteEventScript = path.join(scriptsDir, 'deleteEvent.vbs');
// Test event details
const testEventSubject = `Test Event ${new Date().toISOString()}`;
const today = new Date();
const formattedDate = `${today.getMonth() + 1}/${today.getDate()}/${today.getFullYear()}`;
let testEventId = null;
// Helper function to parse script output
function parseScriptOutput(stdout) {
if (stdout.includes('SUCCESS:')) {
const jsonStr = stdout.substring(stdout.indexOf('SUCCESS:') + 'SUCCESS:'.length).trim();
try {
return { success: true, data: JSON.parse(jsonStr) };
} catch (parseError) {
return { success: false, error: `Error parsing JSON: ${parseError.message}`, raw: jsonStr };
}
} else if (stdout.includes('ERROR:')) {
const errorMessage = stdout.substring(stdout.indexOf('ERROR:') + 'ERROR:'.length).trim();
return { success: false, error: errorMessage };
} else {
return { success: false, error: 'Unexpected script output', raw: stdout };
}
}
// Execute a VBScript with parameters
async function executeScript(scriptPath, params = []) {
const paramString = params.map(p => `/${p.name}:"${p.value}"`).join(' ');
const command = `cscript //NoLogo "${scriptPath}" ${paramString}`;
try {
const { stdout, stderr } = await execPromise(command);
if (stderr) {
return { success: false, error: stderr };
}
return parseScriptOutput(stdout);
} catch (error) {
return { success: false, error: error.message };
}
}
// Main test function
async function runTests() {
console.log('🔍 TESTING OUTLOOK CALENDAR MCP TOOL');
console.log('====================================');
// Test 1: Connection Test
console.log('\n📋 TEST 1: Connection Test (getCalendars.vbs)');
console.log('-------------------------------------------');
const connectionResult = await executeScript(getCalendarsScript);
if (connectionResult.success) {
console.log('✅ Connection test passed!');
console.log('Available calendars:');
console.log(JSON.stringify(connectionResult.data, null, 2));
} else {
console.error('❌ Connection test failed:', connectionResult.error);
console.error('Cannot proceed with further tests without Outlook connection.');
process.exit(1);
}
// Test 2: Read Test
console.log('\n📋 TEST 2: Read Test (listEvents.vbs)');
console.log('-------------------------------------------');
const readResult = await executeScript(listEventsScript, [
{ name: 'startDate', value: formattedDate },
{ name: 'endDate', value: formattedDate }
]);
if (readResult.success) {
console.log('✅ Read test passed!');
console.log(`Found ${readResult.data.length} events for today (${formattedDate}).`);
} else {
console.error('❌ Read test failed:', readResult.error);
console.error('Cannot proceed with further tests.');
process.exit(1);
}
// Test 3: Write Test
console.log('\n📋 TEST 3: Write Test (createEvent.vbs)');
console.log('-------------------------------------------');
const writeResult = await executeScript(createEventScript, [
{ name: 'subject', value: testEventSubject },
{ name: 'startDate', value: formattedDate },
{ name: 'startTime', value: '2:00 PM' },
{ name: 'endDate', value: formattedDate },
{ name: 'endTime', value: '2:30 PM' },
{ name: 'location', value: 'Test Location' },
{ name: 'body', value: 'This is a test event created by the Outlook Calendar MCP Tool test script.' }
]);
if (writeResult.success) {
testEventId = writeResult.data.eventId;
console.log('✅ Write test passed!');
console.log(`Created test event with ID: ${testEventId}`);
} else {
console.error('❌ Write test failed:', writeResult.error);
console.error('Cannot proceed with delete test.');
process.exit(1);
}
// Test 4: Update Test
console.log('\n📋 TEST 4: Update Test (updateEvent.vbs)');
console.log('-------------------------------------------');
if (!testEventId) {
console.error('❌ Update test skipped: No event ID from write test.');
process.exit(1);
}
const updateResult = await executeScript(updateEventScript, [
{ name: 'eventId', value: testEventId },
{ name: 'subject', value: `${testEventSubject} - UPDATED` },
{ name: 'location', value: 'Updated Test Location' }
]);
if (updateResult.success && updateResult.data.success) {
console.log('✅ Update test passed!');
console.log(`Successfully updated test event with ID: ${testEventId}`);
} else {
console.error('❌ Update test failed:', updateResult.error || 'Unknown error');
process.exit(1);
}
// Test 5: Delete Test
console.log('\n📋 TEST 5: Delete Test (deleteEvent.vbs)');
console.log('-------------------------------------------');
if (!testEventId) {
console.error('❌ Delete test skipped: No event ID from write test.');
process.exit(1);
}
const deleteResult = await executeScript(deleteEventScript, [
{ name: 'eventId', value: testEventId }
]);
if (deleteResult.success && deleteResult.data.success) {
console.log('✅ Delete test passed!');
console.log(`Successfully deleted test event with ID: ${testEventId}`);
} else {
console.error('❌ Delete test failed:', deleteResult.error || 'Unknown error');
process.exit(1);
}
// Summary
console.log('\n📋 TEST SUMMARY');
console.log('-------------------------------------------');
console.log('✅ Connection Test: PASSED');
console.log('✅ Read Test: PASSED');
console.log('✅ Write Test: PASSED');
console.log('✅ Update Test: PASSED');
console.log('✅ Delete Test: PASSED');
console.log('\n🎉 All tests passed! The Outlook Calendar MCP Tool is working correctly.');
}
// Run the tests
runTests().catch(error => {
console.error('Error running tests:', error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/src/outlookTools.js:
--------------------------------------------------------------------------------
```javascript
/**
* outlookTools.js - Defines MCP tools for Outlook calendar operations
*/
import {
listEvents,
createEvent,
findFreeSlots,
getAttendeeStatus,
getCalendars,
deleteEvent,
updateEvent
} from './scriptRunner.js';
/**
* Defines the MCP tools for Outlook calendar operations
* @returns {Object} - Object containing tool definitions
*/
export function defineOutlookTools() {
return {
// List calendar events
list_events: {
name: 'list_events',
description: 'List calendar events within a specified date range',
inputSchema: {
type: 'object',
properties: {
startDate: {
type: 'string',
description: 'Start date in MM/DD/YYYY format'
},
endDate: {
type: 'string',
description: 'End date in MM/DD/YYYY format'
},
calendar: {
type: 'string',
description: 'Calendar name (optional)'
}
},
required: ['startDate', 'endDate']
},
handler: async ({ startDate, endDate, calendar }) => {
try {
const events = await listEvents(startDate, endDate, calendar);
return {
content: [
{
type: 'text',
text: JSON.stringify(events, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error listing events: ${error.message}`
}
],
isError: true
};
}
}
},
// Create calendar event
create_event: {
name: 'create_event',
description: 'Create a new calendar event or meeting',
inputSchema: {
type: 'object',
properties: {
subject: {
type: 'string',
description: 'Event subject/title'
},
startDate: {
type: 'string',
description: 'Start date in MM/DD/YYYY format'
},
startTime: {
type: 'string',
description: 'Start time in HH:MM AM/PM format'
},
endDate: {
type: 'string',
description: 'End date in MM/DD/YYYY format (optional, defaults to start date)'
},
endTime: {
type: 'string',
description: 'End time in HH:MM AM/PM format (optional, defaults to 30 minutes after start time)'
},
location: {
type: 'string',
description: 'Event location (optional)'
},
body: {
type: 'string',
description: 'Event description/body (optional)'
},
isMeeting: {
type: 'boolean',
description: 'Whether this is a meeting with attendees (optional, defaults to false)'
},
attendees: {
type: 'string',
description: 'Semicolon-separated list of attendee email addresses (optional)'
},
calendar: {
type: 'string',
description: 'Calendar name (optional)'
}
},
required: ['subject', 'startDate', 'startTime']
},
handler: async (eventDetails) => {
try {
const result = await createEvent(eventDetails);
return {
content: [
{
type: 'text',
text: `Event created successfully with ID: ${result.eventId}`
}
]
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error creating event: ${error.message}`
}
],
isError: true
};
}
}
},
// Find free time slots
find_free_slots: {
name: 'find_free_slots',
description: 'Find available time slots in the calendar',
inputSchema: {
type: 'object',
properties: {
startDate: {
type: 'string',
description: 'Start date in MM/DD/YYYY format'
},
endDate: {
type: 'string',
description: 'End date in MM/DD/YYYY format (optional, defaults to 7 days from start date)'
},
duration: {
type: 'number',
description: 'Duration in minutes (optional, defaults to 30)'
},
workDayStart: {
type: 'number',
description: 'Work day start hour (0-23) (optional, defaults to 9)'
},
workDayEnd: {
type: 'number',
description: 'Work day end hour (0-23) (optional, defaults to 17)'
},
calendar: {
type: 'string',
description: 'Calendar name (optional)'
}
},
required: ['startDate']
},
handler: async ({ startDate, endDate, duration, workDayStart, workDayEnd, calendar }) => {
try {
const freeSlots = await findFreeSlots(startDate, endDate, duration, workDayStart, workDayEnd, calendar);
return {
content: [
{
type: 'text',
text: JSON.stringify(freeSlots, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error finding free slots: ${error.message}`
}
],
isError: true
};
}
}
},
// Get attendee status
get_attendee_status: {
name: 'get_attendee_status',
description: 'Check the response status of meeting attendees',
inputSchema: {
type: 'object',
properties: {
eventId: {
type: 'string',
description: 'Event ID'
},
calendar: {
type: 'string',
description: 'Calendar name (optional)'
}
},
required: ['eventId']
},
handler: async ({ eventId, calendar }) => {
try {
const attendeeStatus = await getAttendeeStatus(eventId, calendar);
return {
content: [
{
type: 'text',
text: JSON.stringify(attendeeStatus, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error getting attendee status: ${error.message}`
}
],
isError: true
};
}
}
},
// Delete calendar event
delete_event: {
name: 'delete_event',
description: 'Delete a calendar event by its ID',
inputSchema: {
type: 'object',
properties: {
eventId: {
type: 'string',
description: 'Event ID to delete'
},
calendar: {
type: 'string',
description: 'Calendar name (optional)'
}
},
required: ['eventId']
},
handler: async ({ eventId, calendar }) => {
try {
const result = await deleteEvent(eventId, calendar);
return {
content: [
{
type: 'text',
text: result.success
? `Event deleted successfully`
: `Failed to delete event`
}
]
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error deleting event: ${error.message}`
}
],
isError: true
};
}
}
},
// Update calendar event
update_event: {
name: 'update_event',
description: 'Update an existing calendar event',
inputSchema: {
type: 'object',
properties: {
eventId: {
type: 'string',
description: 'Event ID to update'
},
subject: {
type: 'string',
description: 'New event subject/title (optional)'
},
startDate: {
type: 'string',
description: 'New start date in MM/DD/YYYY format (optional)'
},
startTime: {
type: 'string',
description: 'New start time in HH:MM AM/PM format (optional)'
},
endDate: {
type: 'string',
description: 'New end date in MM/DD/YYYY format (optional)'
},
endTime: {
type: 'string',
description: 'New end time in HH:MM AM/PM format (optional)'
},
location: {
type: 'string',
description: 'New event location (optional)'
},
body: {
type: 'string',
description: 'New event description/body (optional)'
},
calendar: {
type: 'string',
description: 'Calendar name (optional)'
}
},
required: ['eventId']
},
handler: async ({ eventId, subject, startDate, startTime, endDate, endTime, location, body, calendar }) => {
try {
const result = await updateEvent(eventId, subject, startDate, startTime, endDate, endTime, location, body, calendar);
return {
content: [
{
type: 'text',
text: result.success
? `Event updated successfully`
: `Failed to update event`
}
]
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error updating event: ${error.message}`
}
],
isError: true
};
}
}
},
// Get calendars
get_calendars: {
name: 'get_calendars',
description: 'List available calendars',
inputSchema: {
type: 'object',
properties: {}
},
handler: async () => {
try {
const calendars = await getCalendars();
return {
content: [
{
type: 'text',
text: JSON.stringify(calendars, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error getting calendars: ${error.message}`
}
],
isError: true
};
}
}
}
};
}
```