Skill: cvsloane/ms365 (version 1.0.0)

# Microsoft 365 Integration ## Description Access Microsoft 365 services - Email (Outlook), Calendar, OneDrive, To Do tasks, and Contacts via MS Graph API. ## Activation Activated when user mentions: outlook, email, calendar, onedrive, microsoft, office 365, o365, ms365, my meetings, my emails, schedule meeting, send email, check calendar, to do, microsoft tasks ## Configuration Authentication is cached after first login. No environment variables required for device code flow. For headless/automated operation, set these environment variables: - MS365_MCP_CLIENT_ID - Azure AD app client ID - MS365_MCP_CLIENT_SECRET - Azure AD app secret - MS365_MCP_TENANT_ID - Tenant ID (use "consumers" for personal accounts) ## Available Commands ### Authentication ```bash # Login via device code (interactive) python3 /root/clawd/skills/ms365/ms365_cli.py login # Check authentication status python3 /root/clawd/skills/ms365/ms365_cli.py status # List cached accounts python3 /root/clawd/skills/ms365/ms365_cli.py accounts # Get current user info python3 /root/clawd/skills/ms365/ms365_cli.py user ``` ### Email (Outlook) ```bash # List recent emails python3 /root/clawd/skills/ms365/ms365_cli.py mail list [--top N] # Read specific email python3 /root/clawd/skills/ms365/ms365_cli.py mail read MESSAGE_ID # Send email python3 /root/clawd/skills/ms365/ms365_cli.py mail send --to "[REDACTED_SECRET]" --subject "Subject" --body "Message body" ``` ### Calendar ```bash # List upcoming events python3 /root/clawd/skills/ms365/ms365_cli.py calendar list [--top N] # Create event python3 /root/clawd/skills/ms365/ms365_cli.py calendar create --subject "Meeting" --start "2026-01-15T10:00:00" --end "2026-01-15T11:00:00" [--body "Description"] [--timezone "America/Chicago"] ``` ### OneDrive Files ```bash # List files in root python3 /root/clawd/skills/ms365/ms365_cli.py files list # List files in folder python3 /root/clawd/skills/ms365/ms365_cli.py files list --path "Documents" ``` ### To Do Tasks ```bash # List task lists python3 /root/clawd/skills/ms365/ms365_cli.py tasks lists # Get tasks from a list python3 /root/clawd/skills/ms365/ms365_cli.py tasks get LIST_ID # Create task python3 /root/clawd/skills/ms365/ms365_cli.py tasks create LIST_ID --title "Task title" [--due "2026-01-20"] ``` ### Contacts ```bash # List contacts python3 /root/clawd/skills/ms365/ms365_cli.py contacts list [--top N] # Search contacts python3 /root/clawd/skills/ms365/ms365_cli.py contacts search "John" ``` ## Usage Examples User: "Check my outlook email" Agent: Runs `mail list --top 10` command User: "What meetings do I have today?" Agent: Runs `calendar list` command User: "Send an email to [REDACTED_SECRET] about the project update" Agent: Runs `mail send` with appropriate parameters User: "Show my OneDrive files" Agent: Runs `files list` command User: "Add a task to review the budget" Agent: Lists task lists first, then creates task in appropriate list ## Prompts When helping with Microsoft 365: - Use the ms365_cli.py script for all operations - Check authentication status first if commands fail - If not logged in, guide user through device code login - For calendar events, use ISO 8601 datetime format - Default timezone is America/Chicago - When sending email, confirm recipient and content before sending - For tasks, list available task lists first so user can choose ## Attribution This skill uses the **ms-365-mcp-server** by Softeria. - **NPM Package**: [@softeria/ms-365-mcp-server](https://www.npmjs.com/package/@softeria/ms-365-mcp-server) - **GitHub**: https://github.com/Softeria/ms-365-mcp-server - **License**: MIT

path: ms365_cli.py
content: #!/usr/bin/env python3
"""
MS365 CLI - Command-line interface for Microsoft 365 via MCP server.
Wraps the @softeria/ms-365-mcp-server for use with Clawdbot skills.
"""

import subprocess
import json
import sys
import argparse

def call_mcp(method: str, params: dict = None) -> dict:
    """Call the MCP server via stdio."""
    # Initialize request
    init_msg = json.dumps({
        "jsonrpc": "2.0",
        "id": 1,
        "method": "initialize",
        "params": {
            "protocolVersion": "2024-11-05",
            "capabilities": {},
            "clientInfo": {"name": "ms365_cli", "version": "1.0"}
        }
    })

    # Tool call request
    call_msg = json.dumps({
        "jsonrpc": "2.0",
        "id": 2,
        "method": "tools/call",
        "params": {
            "name": method,
            "arguments": params or {}
        }
    })

    input_data = f"{init_msg}\n{call_msg}\n"

    try:
        result = subprocess.run(
            ["npx", "-y", "@softeria/ms-365-mcp-server"],
            input=input_data,
            capture_output=True,
            text=True,
            timeout=60
        )

        # Parse the second line (tool call response)
        lines = result.stdout.strip().split('\n')
        if len(lines) >= 2:
            response = json.loads(lines[1])
            if 'result' in response:
                content = response['result'].get('content', [])
                if content and len(content) > 0:
                    text = content[0].get('text', '')
                    # Try to parse as JSON for pretty printing
                    try:
                        data = json.loads(text)
                        return data
                    except:
                        return {"text": text}
            elif 'error' in response:
                return {"error": response['error']}

        return {"error": "Unexpected response", "raw": result.stdout}

    except subprocess.TimeoutExpired:
        return {"error": "Request timed out"}
    except Exception as e:
        return {"error": str(e)}

def format_output(data: dict, compact: bool = False):
    """Format output for display."""
    if compact:
        print(json.dumps(data, indent=2))
    else:
        print(json.dumps(data, indent=2))

def cmd_login(args):
    """Login via device code flow."""
    print("Starting device code login...")
    subprocess.run(["npx", "-y", "@softeria/ms-365-mcp-server", "--login"])

def cmd_status(args):
    """Check authentication status."""
    result = call_mcp("verify-login")
    format_output(result)

def cmd_accounts(args):
    """List cached accounts."""
    result = call_mcp("list-accounts")
    format_output(result)

def cmd_user(args):
    """Get current user info."""
    result = call_mcp("get-current-user")
    format_output(result)

def cmd_mail_list(args):
    """List emails."""
    params = {}
    if args.top:
        params['top'] = args.top
    if args.folder:
        params['folderId'] = args.folder
    result = call_mcp("list-mail-messages", params)
    format_output(result)

def cmd_mail_read(args):
    """Read a specific email."""
    result = call_mcp("get-mail-message", {"messageId": args.id})
    format_output(result)

def cmd_mail_send(args):
    """Send an email."""
    body = {
        "message": {
            "subject": args.subject,
            "body": {
                "contentType": "Text",
                "content": args.body
            },
            "toRecipients": [
                {"emailAddress": {"address": args.to}}
            ]
        }
    }
    result = call_mcp("send-mail", {"body": body})
    format_output(result)

def cmd_calendar_list(args):
    """List calendar events."""
    params = {}
    if args.top:
        params['top'] = args.top
    result = call_mcp("list-calendar-events", params)
    format_output(result)

def cmd_calendar_create(args):
    """Create a calendar event."""
    body = {
        "subject": args.subject,
        "start": {
            "dateTime": args.start,
            "timeZone": args.timezone or "America/Chicago"
        },
        "end": {
            "dateTime": args.end,
            "timeZone": args.timezone or "America/Chicago"
        }
    }
    if args.body:
        body["body"] = {"contentType": "Text", "content": args.body}
    result = call_mcp("create-calendar-event", {"body": body})
    format_output(result)

def cmd_files_list(args):
    """List OneDrive files."""
    params = {"driveId": "me"}
    if args.path:
        params['driveItemId'] = args.path
    else:
        params['driveItemId'] = "root"
    result = call_mcp("list-folder-files", params)
    format_output(result)

def cmd_tasks_list(args):
    """List To Do task lists."""
    result = call_mcp("list-todo-task-lists")
    format_output(result)

def cmd_tasks_get(args):
    """Get tasks from a list."""
    result = call_mcp("list-todo-tasks", {"todoTaskListId": args.list_id})
    format_output(result)

def cmd_tasks_create(args):
    """Create a new task."""
    body = {"title": args.title}
    if args.due:
        body["dueDateTime"] = {"dateTime": args.due, "timeZone": "America/Chicago"}
    result = call_mcp("create-todo-task", {
        "todoTaskListId": args.list_id,
        "body": body
    })
    format_output(result)

def cmd_contacts_list(args):
    """List contacts."""
    params = {}
    if args.top:
        params['top'] = args.top
    result = call_mcp("list-outlook-contacts", params)
    format_output(result)

def cmd_contacts_search(args):
    """Search contacts."""
    result = call_mcp("search-people", {"search": args.query})
    format_output(result)

def main():
    parser = argparse.ArgumentParser(description="MS365 CLI for Clawdbot")
    subparsers = parser.add_subparsers(dest='command', help='Commands')

    # Auth commands
    login_p = subparsers.add_parser('login', help='Login via device code')
    login_p.set_defaults(func=cmd_login)

    status_p = subparsers.add_parser('status', help='Check auth status')
    status_p.set_defaults(func=cmd_status)

    accounts_p = subparsers.add_parser('accounts', help='List cached accounts')
    accounts_p.set_defaults(func=cmd_accounts)

    user_p = subparsers.add_parser('user', help='Get current user info')
    user_p.set_defaults(func=cmd_user)

    # Mail commands
    mail_p = subparsers.add_parser('mail', help='Mail commands')
    mail_sub = mail_p.add_subparsers(dest='mail_cmd')

    mail_list = mail_sub.add_parser('list', help='List emails')
    mail_list.add_argument('--top', type=int, default=10)
    mail_list.add_argument('--folder', help='Folder ID')
    mail_list.set_defaults(func=cmd_mail_list)

    mail_read = mail_sub.add_parser('read', help='Read email')
    mail_read.add_argument('id', help='Message ID')
    mail_read.set_defaults(func=cmd_mail_read)

    mail_send = mail_sub.add_parser('send', help='Send email')
    mail_send.add_argument('--to', required=True)
    mail_send.add_argument('--subject', required=True)
    mail_send.add_argument('--body', required=True)
    mail_send.set_defaults(func=cmd_mail_send)

    # Calendar commands
    cal_p = subparsers.add_parser('calendar', help='Calendar commands')
    cal_sub = cal_p.add_subparsers(dest='cal_cmd')

    cal_list = cal_sub.add_parser('list', help='List events')
    cal_list.add_argument('--top', type=int, default=10)
    cal_list.set_defaults(func=cmd_calendar_list)

    cal_create = cal_sub.add_parser('create', help='Create event')
    cal_create.add_argument('--subject', required=True)
    cal_create.add_argument('--start', required=True, help='ISO datetime')
    cal_create.add_argument('--end', required=True, help='ISO datetime')
    cal_create.add_argument('--body', help='Event description')
    cal_create.add_argument('--timezone', default='America/Chicago')
    cal_create.set_defaults(func=cmd_calendar_create)

    # Files commands
    files_p = subparsers.add_parser('files', help='OneDrive commands')
    files_sub = files_p.add_subparsers(dest='files_cmd')

    files_list = files_sub.add_parser('list', help='List files')
    files_list.add_argument('--path', help='Folder path')
    files_list.set_defaults(func=cmd_files_list)

    # Tasks commands
    tasks_p = subparsers.add_parser('tasks', help='To Do commands')
    tasks_sub = tasks_p.add_subparsers(dest='tasks_cmd')

    tasks_lists = tasks_sub.add_parser('lists', help='List task lists')
    tasks_lists.set_defaults(func=cmd_tasks_list)

    tasks_get = tasks_sub.add_parser('get', help='Get tasks from list')
    tasks_get.add_argument('list_id', help='Task list ID')
    tasks_get.set_defaults(func=cmd_tasks_get)

    tasks_create = tasks_sub.add_parser('create', help='Create task')
    tasks_create.add_argument('list_id', help='Task list ID')
    tasks_create.add_argument('--title', required=True)
    tasks_create.add_argument('--due', help='Due date (ISO format)')
    tasks_create.set_defaults(func=cmd_tasks_create)

    # Contacts commands
    contacts_p = subparsers.add_parser('contacts', help='Contacts commands')
    contacts_sub = contacts_p.add_subparsers(dest='contacts_cmd')

    contacts_list = contacts_sub.add_parser('list', help='List contacts')
    contacts_list.add_argument('--top', type=int, default=20)
    contacts_list.set_defaults(func=cmd_contacts_list)

    contacts_search = contacts_sub.add_parser('search', help='Search contacts')
    contacts_search.add_argument('query', help='Search query')
    contacts_search.set_defaults(func=cmd_contacts_search)

    args = parser.parse_args()

    if not args.command:
        parser.print_help()
        sys.exit(1)

    if hasattr(args, 'func'):
        args.func(args)
    else:
        parser.print_help()
        sys.exit(1)

if __name__ == '__main__':
    main()


path: README.md
content: # MS 365 Skill for Clawdbot

A Clawdbot skill for Microsoft 365 integration via the Graph API.

## Features

- **Email**: Read, send, search, delete messages
- **Calendar**: View, create, update, delete events
- **OneDrive**: Browse, upload, download files
- **To Do**: Manage tasks and lists
- **Contacts**: Search and view contacts
- **OneNote**: Access notebooks and pages
- **Teams** (org mode): Send messages, access chats
- **SharePoint** (org mode): Access sites and documents

## Installation

### 1. Copy Skill to Clawdbot

Clone this repo to your Clawdbot skills directory:

```bash
# For workspace skills
cd <workspace>/skills
git clone https://github.com/YOUR_USERNAME/m365-skill ms365

# Or for managed skills
cd ~/.clawdbot/skills
git clone https://github.com/YOUR_USERNAME/m365-skill ms365
```

Or download directly from releases.

### 2. Install Dependencies

The skill will auto-install via npm, or manually:

```bash
npm install -g @softeria/ms-365-mcp-server
```

### 3. Set Up Authentication

#### Option A: Azure AD App (Recommended)

This method works headless and is required for Docker deployments.

**Step 1: Create Azure AD App Registration**

1. Go to [Azure Portal](https://portal.azure.com)
2. Navigate to **Azure Active Directory** → **App registrations**
3. Click **New registration**
4. Configure:
   - **Name**: `Clawdbot MS365` (or your choice)
   - **Supported account types**:
     - "Personal Microsoft accounts only" for personal use
     - "Accounts in any organizational directory and personal" for both
   - **Redirect URI**: Select "Web" and enter `http://localhost:3365/callback`
5. Click **Register**

**Step 2: Configure API Permissions**

1. In your app registration, go to **API permissions**
2. Click **Add a permission** → **Microsoft Graph** → **Delegated permissions**
3. Add these permissions:

| Permission | Purpose |
|------------|---------|
| `Mail.ReadWrite` | Read and send emails |
| `Mail.Send` | Send emails |
| `Calendars.ReadWrite` | Manage calendar events |
| `Files.ReadWrite` | Access OneDrive files |
| `Tasks.ReadWrite` | Manage To Do tasks |
| `Contacts.Read` | Read contacts |
| `Notes.Read` | Read OneNote notebooks |
| `User.Read` | Basic profile info |

For organization features, also add:

| Permission | Purpose |
|------------|---------|
| `Chat.ReadWrite` | Teams messaging |
| `Sites.Read.All` | SharePoint access |
| `ChannelMessage.Send` | Post to Teams channels |

4. Click **Grant admin consent** (if you have admin rights)

**Step 3: Create Client Secret**

1. Go to **Certificates & secrets**
2. Click **New client secret**
3. Add description: `Clawdbot`
4. Choose expiration (recommend 24 months)
5. Click **Add**
6. **IMPORTANT**: Copy the secret value immediately (shown only once!)

**Step 4: Note Your Credentials**

From the **Overview** page, note:
- **Application (client) ID** → `MS365_MCP_CLIENT_ID`
- **Directory (tenant) ID** → `MS365_MCP_TENANT_ID`
  - Or use `consumers` for personal accounts only
- **Client secret value** (from step 3) → `MS365_MCP_CLIENT_SECRET`

**Step 5: Set Environment Variables**

Add to your environment (.env file, Docker, or your deployment platform):

```bash
MS365_MCP_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
MS365_MCP_CLIENT_SECRET=your-secret-value
MS365_MCP_TENANT_ID=consumers
```

#### Option B: Device Code Flow

For interactive/testing use. No Azure setup needed.

1. Run any MS365 command
2. You'll see: "To sign in, use a web browser to open https://microsoft.com/devicelogin"
3. Enter the provided code
4. Sign in with your Microsoft account
5. Tokens are cached for future use

**Note**: This doesn't work in pure headless environments.

### 4. Configure mcporter

Add MS365 server to mcporter config. Create/edit `~/.clawdbot/mcporter.json`:

```json
{
  "servers": {
    "ms365": {
      "command": "npx",
      "args": ["-y", "@softeria/ms-365-mcp-server"],
      "env": {
        "MS365_MCP_CLIENT_ID": "${MS365_MCP_CLIENT_ID}",
        "MS365_MCP_CLIENT_SECRET": "${MS365_MCP_CLIENT_SECRET}",
        "MS365_MCP_TENANT_ID": "${MS365_MCP_TENANT_ID}"
      }
    }
  }
}
```

Or use HTTP mode:

```json
{
  "servers": {
    "ms365": {
      "url": "http://localhost:3365"
    }
  }
}
```

### 5. Verify Installation

```bash
# Check server is accessible
mcporter list ms365

# Test email access
mcporter call ms365.list_messages limit=5

# Test calendar access
mcporter call ms365.list_events
```

## Usage

Once installed, simply talk to Clawdbot naturally:

- "Check my email"
- "What meetings do I have tomorrow?"
- "Send an email to [REDACTED_SECRET] about the project update"
- "Add a task to review the budget by Friday"
- "Show my OneDrive files"

## Troubleshooting

### Error: "AADSTS700016: Application not found"
- Verify `MS365_MCP_CLIENT_ID` is correct
- Check the app registration exists in Azure portal

### Error: "AADSTS7000218: Invalid client secret"
- Client secret may have expired
- Create a new secret in Azure portal
- Update `MS365_MCP_CLIENT_SECRET`

### Error: "Insufficient privileges"
- Add required permissions in Azure portal
- Grant admin consent if required
- For org features, ensure admin has approved the app

### Error: "Token expired" / "401 Unauthorized"
- Re-authenticate via device code flow
- Or refresh OAuth tokens

### mcporter can't find ms365 server
- Check mcporter.json configuration
- Verify npm package is installed: `npx @softeria/ms-365-mcp-server --version`

## Organization/Work Account Features

To enable Teams, SharePoint, and shared mailbox access:

1. Set environment variable:
   ```bash
   MS365_MCP_ORG_MODE=true
   ```

2. Update mcporter config to include the flag:
   ```json
   {
     "servers": {
       "ms365": {
         "command": "npx",
         "args": ["-y", "@softeria/ms-365-mcp-server", "--org-mode"]
       }
     }
   }
   ```

3. Ensure your Azure app has the required organization permissions

## Advanced Configuration

### Token-Efficient Output (TOON format)

Reduce token usage by 30-60%:

```bash
MS365_MCP_OUTPUT_FORMAT=toon
```

### Read-Only Mode

Disable all write operations:

```json
{
  "servers": {
    "ms365": {
      "command": "npx",
      "args": ["-y", "@softeria/ms-365-mcp-server", "--read-only"]
    }
  }
}
```

### Discovery Mode

Start with minimal tools, expand on demand:

```json
{
  "servers": {
    "ms365": {
      "command": "npx",
      "args": ["-y", "@softeria/ms-365-mcp-server", "--discovery"]
    }
  }
}
```

## Available Tools Reference

### Email Tools
| Tool | Description |
|------|-------------|
| `list_messages` | List emails in a folder |
| `get_message` | Get full email content |
| `send_message` | Send a new email |
| `reply_message` | Reply to an email |
| `forward_message` | Forward an email |
| `delete_message` | Delete an email |
| `move_message` | Move email to folder |
| `search_messages` | Search emails |

### Calendar Tools
| Tool | Description |
|------|-------------|
| `list_events` | List calendar events |
| `get_event` | Get event details |
| `create_event` | Create new event |
| `update_event` | Update existing event |
| `delete_event` | Delete an event |
| `list_calendars` | List all calendars |

### OneDrive Tools
| Tool | Description |
|------|-------------|
| `list_files` | List files in folder |
| `get_file` | Get file metadata |
| `download_file` | Download file content |
| `upload_file` | Upload a file |
| `create_folder` | Create new folder |
| `delete_file` | Delete file/folder |
| `search_files` | Search for files |

### To Do Tools
| Tool | Description |
|------|-------------|
| `list_task_lists` | List all task lists |
| `list_tasks` | List tasks in a list |
| `create_task` | Create new task |
| `update_task` | Update task |
| `complete_task` | Mark task complete |
| `delete_task` | Delete a task |

### Contact Tools
| Tool | Description |
|------|-------------|
| `list_contacts` | List contacts |
| `get_contact` | Get contact details |
| `search_contacts` | Search contacts |
| `create_contact` | Create new contact |

### OneNote Tools
| Tool | Description |
|------|-------------|
| `list_notebooks` | List notebooks |
| `list_sections` | List notebook sections |
| `list_pages` | List section pages |
| `get_page_content` | Get page content |

### Organization Tools (--org-mode)
| Tool | Description |
|------|-------------|
| `list_teams` | List Teams |
| `list_channels` | List team channels |
| `send_channel_message` | Post to channel |
| `list_chats` | List chat conversations |
| `send_chat_message` | Send chat message |
| `list_sites` | List SharePoint sites |
| `list_site_files` | List site documents |

## Support

- MCP Server Issues: https://github.com/Softeria/ms-365-mcp-server/issues
- Clawdbot Issues: https://github.com/clawdbot/clawdbot/issues

## License

MIT License - Feel free to modify and share!


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (HIGH)