outlook-calendar-lite-mcp
README.md
# outlook-calendar-lite-mcp
A lightweight [MCP](https://modelcontextprotocol.io) server for Microsoft Outlook Calendar (Microsoft Graph).
Works with work/school accounts (Microsoft 365) and personal accounts (Outlook.com, Hotmail).
- **Zero dependencies.** Node 22.18+ runs the TypeScript directly: no `npm install`, no build.
- **You run it, you own it.** It runs on your machine with your own Azure app. No third-party server sees your calendar.
- **Token-light.** Tabular output.
- **Permanent ids.** Every event comes with its real Microsoft Graph id, requested as an *immutable id*, so you can store it in a database and use it later.
- **Your time zone, always.** Times are read and written in the mailbox's time zone; every listing shows today's date and the current time, so the AI never guesses "tomorrow".
- **Safe by default.** Anything that notifies other people (invitations, updates, cancellations, replies) shows a preview and waits for the user's approval. Events only on your own calendar don't ask.
- **Login on connect.** The first time the client connects, your browser opens on the Microsoft account picker.
> Português: [README.pt-BR.md](README.pt-BR.md). Tool descriptions are in Portuguese; any LLM handles them fine.
## Prerequisites
- **Node.js 22.18 or newer** (`node --version`). Nothing else to install: no `npm install`, no build.
- **git**, to clone the repository (or download the ZIP from GitHub).
- **A Microsoft account** with a calendar: work/school (Microsoft 365) or personal (Outlook.com, Hotmail).
- **Permission to register an app in Microsoft Entra** (free):
- work account: allowed by default in most companies; if blocked, ask your IT admin to register it or to grant consent;
- personal account: sign in to <https://portal.azure.com> once with a free Azure account, which creates your own directory.
- **An MCP client**: Claude Desktop, Claude Code, Cursor, VS Code or any other client that supports stdio servers.
## Quick start
### 1. Get the code
```bash
git clone https://github.com/almeidatmatheus/outlook-calendar-lite-mcp.git
cd outlook-calendar-lite-mcp
node --version # needs 22.18 or newer
```
### 2. Register your own app in Microsoft Entra (once, ~5 min)
> Already using [outlook-lite-mcp](https://github.com/almeidatmatheus/outlook-lite-mcp)? Reuse the same app: just do step 4 (add `Calendars.ReadWrite`) and use the same client ID.
1. Go to <https://entra.microsoft.com> → **Applications** → **App registrations** → **New registration**.
- **Name:** `outlook-calendar-lite-mcp`
- **Supported account types:**
- personal + any company: **Accounts in any organizational directory and personal Microsoft accounts**
- only your company: **Accounts in this organizational directory only**
- **Register**.
2. On **Overview**, copy the **Application (client) ID** (and the **Directory (tenant) ID** if you chose "this directory only").
3. **Authentication** → **Add a platform** → **Mobile and desktop applications** → custom redirect URI:
```
http://localhost/cb
```
Then, still in **Authentication**, set **Allow public client flows** to **Yes** → **Save**.
No client secret is needed for local use.
4. **API permissions** → **Add a permission** → **Microsoft Graph** → **Delegated permissions**:
`Calendars.ReadWrite`, `MailboxSettings.Read`, `User.Read`, `offline_access`.
(Never *Application permissions*.)
5. Work accounts: if your company requires it, an admin clicks **Grant admin consent for <company>**.
### 3. Add it to your MCP client
**Claude Desktop** (`claude_desktop_config.json`, see [examples/](examples/claude_desktop_config.json)):
```json
{
"mcpServers": {
"calendar": {
"command": "node",
"args": ["/absolute/path/to/outlook-calendar-lite-mcp/src/main.ts"],
"env": { "OUTLOOK_MCP_CLIENT_ID": "<your client id>" }
}
}
}
```
**Claude Code:**
```bash
claude mcp add calendar -e OUTLOOK_MCP_CLIENT_ID=<your client id> -- node /absolute/path/to/outlook-calendar-lite-mcp/src/main.ts
```
**Any other MCP client** (Cursor, VS Code, local models…): command `node /absolute/path/to/outlook-calendar-lite-mcp/src/main.ts`, stdio transport, same env var.
Restart the client. The browser opens on the Microsoft account picker; sign in and you're done.
The token is stored in `~/.outlook-calendar-lite-mcp/default.json` (mode 600) and refreshes itself.
To use another account, ask to "switch account" (tool `cx`, action `trocar`).
On macOS, keep the folder outside `Downloads`/`Desktop`/`Documents` (e.g. `~/outlook-calendar-lite-mcp`), or the client may not be allowed to read it.
### Try it
> "what do I have today?" · "find a free hour with ana@acme.com this week" · "book it and invite her, with Teams" · "move my dentist to 11am" · "accept the ACME meeting"
## Tools
| tool | does |
|---|---|
| `ls` | agenda for a period (today, tomorrow, this week, next week, 7/30 days, or dates) |
| `rd` | read an event: time, place, organizer, attendees with each response, Teams link, recurrence, description |
| `find` | search events by title, place, organizer or attendee |
| `free` | free slots for a meeting, crossing your calendar (and attendees', on work accounts) within working hours |
| `cr` | create an event: all-day, recurring, online (Teams), reminder, attendees. **Asks for confirmation when it invites people**; warns about conflicts |
| `ed` | change an event you organize (time, title, place, attendees…). **Asks for confirmation when attendees are notified** |
| `rm` | cancel (attendees get the cancellation, with an optional message) or remove from your calendar. **Asks for confirmation** |
| `rsp` | reply to an invitation: accept, decline or tentative, with a comment. **Asks for confirmation when the organizer is notified** |
| `cx` | account, time zone, working hours and calendars; `trocar` switches account |
Event ids: the `id` column of `ls`/`find` (and the id returned by `cr`) is the event's permanent Graph id (immutable id). Pass it as `alvo` to the other tools, now or in a later session.
Recurring events: `ed`, `rm` and `rsp` act on one occurrence; pass `serie: true` for the whole series.
Dates are written as `YYYY-MM-DDTHH:mm` in your time zone; `YYYY-MM-DD` alone means all day.
**Confirmation.** If the client supports MCP *elicitation*, the server asks the user directly.
Otherwise the first call returns a preview and a code `cf`; only the identical call with that `cf` executes.
Clients should show the preview to the user and send `cf` only after explicit approval.
**Personal accounts.** Checking other people's availability (`free` with attendees) and Teams meetings are Microsoft 365 features; on Outlook.com, `free` uses only your own calendar and says so.
## Options
| env var | default |
|---|---|
| `OUTLOOK_MCP_CLIENT_ID` | required |
| `OUTLOOK_MCP_TENANT` | `common` (set your tenant id to lock to one company) |
| `OUTLOOK_MCP_PROFILE` | `default` (one profile per account) |
| `OUTLOOK_MCP_LOGIN` | browser; `device` = device code (SSH, no browser) |
| `OUTLOOK_MCP_TZ` | the mailbox's time zone; set an IANA name (e.g. `America/Sao_Paulo`) to override date math |
| `OUTLOOK_MCP_HOME` | `~/.outlook-calendar-lite-mcp` |
Command line: `node src/main.ts login | whoami | logout`.
Behind a corporate proxy: Node 22.21+ with `NODE_USE_ENV_PROXY=1` and `HTTPS_PROXY`.
## Remote mode (optional, self-hosted)
To use it from claude.ai as a custom connector, run your own HTTPS instance:
```bash
export OUTLOOK_MCP_CLIENT_ID=<client id>
export OUTLOOK_MCP_URL=https://your-domain.example # public HTTPS URL
export OUTLOOK_MCP_ALLOW=you@company.com,company.com # who may connect (emails or domains)
node src/main.ts http # listens on PORT (8787)
```
In Entra add the platform **Web** with redirect `https://your-domain.example/ms/cb` (and a client secret in
`OUTLOOK_MCP_CLIENT_SECRET` if Entra asks for one). In claude.ai add a custom connector with `https://your-domain.example/mcp`.
For a quick test, `cloudflared tunnel --url http://localhost:8787` gives you a temporary HTTPS URL.
**Always set `OUTLOOK_MCP_ALLOW`.** Without it anyone with the URL can connect their own account to your server.
## Security
- Delegated permissions only: the server can do what the signed-in user can do, nothing more.
- Tokens live on your machine (`~/.outlook-calendar-lite-mcp`, mode 600). Remote mode keeps them in `http-store.json` on the server.
- **Invitations are untrusted.** An event description can contain text trying to instruct the AI.
The mandatory confirmation before anything that notifies other people is the main defense; don't remove it.
- See [SECURITY.md](SECURITY.md) to report a vulnerability.
## Project layout
```
src/
main.ts entry: stdio (default) | http | login | logout | whoami
mcp.ts MCP protocol: JSON-RPC, schema validation, confirmation (elicitation or cf code)
http.ts remote mode: Streamable HTTP + OAuth server that delegates login to Microsoft
auth.ts Microsoft login: browser (PKCE), device code, refresh, token file
ctx.ts per-user state (token, caches)
g.ts minimal Graph client: get/post/patch/delete, retry on 429/401, immutable ids
ids.ts validates the event ids received by the tools
fmt.ts tabular output, text cleanup
lib.ts time zone and date math, periods, events table, recurrence, shared helpers
tools/ one file per tool + index.ts (ev.ts: event fields shared by create and update)
test/
mock.ts fake Microsoft (login + Graph calendar)
run.ts real MCP clients against the mock, local and remote
```
## Development
```bash
node test/run.ts # 66 checks, no network or Microsoft account needed
npx tsc -p . # type check (optional)
```
## License
[MIT](LICENSE)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo recent community issues