w
This commit is contained in:
@@ -1,287 +1,44 @@
|
|||||||
# OWA MCP Server
|
# OWA MCP Server
|
||||||
|
|
||||||
MCP (Model Context Protocol) server for any Microsoft Exchange / OWA (Outlook Web Access) deployment. Gives LLM agents access to email, calendar, directory search, folders, availability, and meeting analytics via 30 tools.
|
MCP server for any Microsoft Exchange / OWA deployment. 30 tools for email,
|
||||||
|
calendar, directory search, folder management, availability, and analytics.
|
||||||
Works with any on-premise or hosted Exchange server that exposes OWA.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install
|
|
||||||
pip install -e .
|
|
||||||
|
|
||||||
# Add to your MCP client config with --owa-url
|
|
||||||
# Then use the login tool to paste your session cookies
|
|
||||||
```
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
Add to your MCP client config. Replace `https://owa.example.com` with your OWA URL.
|
|
||||||
|
|
||||||
**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"exchange": {
|
|
||||||
"command": "uvx",
|
|
||||||
"args": [
|
|
||||||
"exchange-mcp-server",
|
|
||||||
"--owa-url", "https://owa.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cursor** (`.cursor/mcp.json`):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"exchange": {
|
|
||||||
"command": "uvx",
|
|
||||||
"args": [
|
|
||||||
"exchange-mcp-server",
|
|
||||||
"--owa-url", "https://owa.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Claude Code** (`.mcp.json`):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"exchange": {
|
|
||||||
"command": "uvx",
|
|
||||||
"args": [
|
|
||||||
"exchange-mcp-server",
|
|
||||||
"--owa-url", "https://owa.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## CLI Arguments
|
## CLI Arguments
|
||||||
|
|
||||||
| Argument | Default | Required | Description |
|
| Argument | Default | Required | Description |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `--owa-url` | — | Yes | Base URL of the OWA instance (e.g. `https://owa.example.com`) |
|
| `--owa-url` | — | Yes | Base URL of the OWA instance |
|
||||||
| `--cookie-file` | `session-cookies.txt` | No | Path to session cookies file |
|
| `--cookie-file` | `session-cookies.txt` | No | Path to session cookies file |
|
||||||
| `--transport` | `stdio` | — | `stdio`, `sse`, or `streamable-http` |
|
| `--transport` | `stdio` | — | `stdio`, `sse`, or `streamable-http` |
|
||||||
| `--token-hash` | — | For HTTP | SHA-256 hash of the bearer token |
|
| `--token-hash` | — | HTTP only | SHA-256 hash of the bearer token |
|
||||||
| `--port` | `8000` | — | HTTP/SSE listener port |
|
| `--port` | `8000` | — | HTTP/SSE listener port |
|
||||||
| `--host` | `127.0.0.1` | — | HTTP/SSE listener host |
|
| `--host` | `127.0.0.1` | — | HTTP/SSE listener host |
|
||||||
| `--enabled-tools` | — | No | Comma-separated whitelist of tool names |
|
| `--enabled-tools` | — | No | Comma-separated whitelist of tool names |
|
||||||
| `--disabled-tools` | — | No | Comma-separated blacklist of tool names |
|
| `--disabled-tools` | — | No | Comma-separated blacklist of tool names |
|
||||||
|
|
||||||
### Tool filtering examples
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Hide only the most dangerous tools
|
|
||||||
exchange-mcp-server --owa-url https://owa.example.com \
|
|
||||||
--disabled-tools "send_email,delete_email,cancel_meeting"
|
|
||||||
|
|
||||||
# Allow only email and calendar tools
|
|
||||||
exchange-mcp-server --owa-url https://owa.example.com \
|
|
||||||
--enabled-tools "get_emails,get_email,send_email,get_calendar_events,create_meeting"
|
|
||||||
|
|
||||||
# Whitelist a set, then remove one from it
|
|
||||||
exchange-mcp-server --owa-url https://owa.example.com \
|
|
||||||
--enabled-tools "get_emails,get_email,send_email,delete_email" \
|
|
||||||
--disabled-tools "delete_email"
|
|
||||||
# → only get_emails, get_email, send_email remain
|
|
||||||
```
|
|
||||||
|
|
||||||
## HTTP Transport
|
|
||||||
|
|
||||||
Run the server over HTTP (streamable-http or SSE) instead of stdio. This is
|
|
||||||
useful when the MCP client cannot spawn a local process, or when you want to
|
|
||||||
expose the server on a network.
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install -e ".[http]" # adds uvicorn + sse-starlette
|
|
||||||
```
|
|
||||||
|
|
||||||
### Start the server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Compute the token hash once (store only the hash, never the plaintext token)
|
|
||||||
TOKEN_HASH=$(echo -n 'my-secret-token' | sha256sum | cut -d' ' -f1)
|
|
||||||
|
|
||||||
# stdio (default, for local MCP clients)
|
|
||||||
exchange-mcp-server --owa-url https://owa.example.com
|
|
||||||
|
|
||||||
# streamable-http (recommended for HTTP)
|
|
||||||
exchange-mcp-server \
|
|
||||||
--owa-url https://owa.example.com \
|
|
||||||
--transport streamable-http \
|
|
||||||
--token-hash "$TOKEN_HASH" \
|
|
||||||
--port 8000 \
|
|
||||||
--host 127.0.0.1
|
|
||||||
|
|
||||||
# SSE
|
|
||||||
exchange-mcp-server \
|
|
||||||
--owa-url https://owa.example.com \
|
|
||||||
--transport sse \
|
|
||||||
--token-hash "$TOKEN_HASH" \
|
|
||||||
--port 8000
|
|
||||||
```
|
|
||||||
|
|
||||||
### CLI arguments
|
|
||||||
|
|
||||||
| Argument | Default | Required | Description |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `--owa-url` | — | Yes | Base URL of the OWA instance (e.g. `https://owa.example.com`) |
|
|
||||||
| `--cookie-file` | `session-cookies.txt` | No | Path to session cookies file |
|
|
||||||
| `--transport` | `stdio` | — | `stdio`, `sse`, or `streamable-http` |
|
|
||||||
| `--token-hash` | — | For HTTP | SHA-256 hash of the bearer token |
|
|
||||||
| `--port` | `8000` | — | HTTP/SSE listener port |
|
|
||||||
| `--host` | `127.0.0.1` | — | HTTP/SSE listener host |
|
|
||||||
| `--enabled-tools` | — | No | Comma-separated whitelist of tool names |
|
|
||||||
| `--disabled-tools` | — | No | Comma-separated blacklist of tool names |
|
|
||||||
|
|
||||||
### Client configuration (HTTP)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"exchange": {
|
|
||||||
"url": "http://127.0.0.1:8000/mcp",
|
|
||||||
"headers": {
|
|
||||||
"Authorization": "Bearer my-secret-token"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Authentication
|
|
||||||
|
|
||||||
The server stores only the SHA-256 hash of your token — the plaintext token
|
|
||||||
never touches the filesystem. Every HTTP request must include:
|
|
||||||
|
|
||||||
```
|
|
||||||
Authorization: Bearer <plaintext-token>
|
|
||||||
```
|
|
||||||
|
|
||||||
The server hashes the incoming token and compares it with the stored hash.
|
|
||||||
Pass the hash via `--token-hash`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
echo -n 'my-secret-token' | sha256sum
|
|
||||||
# → 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
|
|
||||||
```
|
|
||||||
|
|
||||||
### Security notes
|
|
||||||
|
|
||||||
- Always use a strong random token.
|
|
||||||
- Bind to `127.0.0.1` unless you have a reverse proxy / firewall.
|
|
||||||
- For `streamable-http` the client must send `Accept: text/event-stream`.
|
|
||||||
- The `--token-hash` is required whenever `--transport` is not `stdio`.
|
|
||||||
|
|
||||||
## Login
|
|
||||||
|
|
||||||
The `login` MCP tool accepts session cookies that you copy from your browser.
|
|
||||||
|
|
||||||
1. Log into OWA in your browser.
|
|
||||||
2. Open DevTools (F12) → Application → Cookies → select your OWA domain.
|
|
||||||
3. Copy all cookies as `name=value` pairs (one per line).
|
|
||||||
4. Call the `login` tool with the cookies string.
|
|
||||||
|
|
||||||
Example cookies string:
|
|
||||||
|
|
||||||
```
|
|
||||||
X-OWA-CANARY=abc123
|
|
||||||
CookieAuth1=def456
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
login(cookies="X-OWA-CANARY=abc123\nCookieAuth1=def456\n...")
|
|
||||||
```
|
|
||||||
|
|
||||||
On success, cookies are saved to `session-cookies.txt` and all tools become usable. When the session expires, repeat the steps.
|
|
||||||
|
|
||||||
## Tools (30)
|
## Tools (30)
|
||||||
|
|
||||||
### Email (10)
|
### Email (10)
|
||||||
| Tool | Description |
|
`get_emails`, `get_email`, `send_email`, `reply_email`, `forward_email`,
|
||||||
|---|---|
|
`delete_email`, `move_email`, `mark_email_read`, `download_attachments`,
|
||||||
| `get_emails` | List emails from a folder with filtering |
|
`get_email_links`, `search_emails`
|
||||||
| `get_email` | Get full email content by ID |
|
|
||||||
| `send_email` | Send a new email |
|
|
||||||
| `reply_email` | Reply to an email |
|
|
||||||
| `forward_email` | Forward an email |
|
|
||||||
| `delete_email` | Delete an email |
|
|
||||||
| `move_email` | Move email to another folder |
|
|
||||||
| `mark_email_read` | Mark email as read/unread |
|
|
||||||
| `download_attachments` | Download file attachments from an email |
|
|
||||||
| `get_email_links` | Extract hyperlinks from an email body |
|
|
||||||
|
|
||||||
### Calendar (7)
|
### Calendar (7)
|
||||||
| Tool | Description |
|
`get_calendar_events`, `create_meeting`, `update_meeting`, `cancel_meeting`,
|
||||||
|---|---|
|
`respond_to_meeting`, `download_event_attachments`, `get_event_links`
|
||||||
| `get_calendar_events` | Get events in a date range (supports recurring expansion) |
|
|
||||||
| `create_meeting` | Create a meeting with attendees |
|
|
||||||
| `update_meeting` | Update an existing meeting |
|
|
||||||
| `cancel_meeting` | Cancel a meeting and notify attendees |
|
|
||||||
| `respond_to_meeting` | Accept, decline, or tentatively accept |
|
|
||||||
| `download_event_attachments` | Download file attachments from a calendar event |
|
|
||||||
| `get_event_links` | Extract hyperlinks from the event description |
|
|
||||||
|
|
||||||
### Directory (1)
|
### Directory (1)
|
||||||
| Tool | Description |
|
`find_person`
|
||||||
|---|---|
|
|
||||||
| `find_person` | Search people in Active Directory |
|
|
||||||
|
|
||||||
### Folders (7)
|
### Folders (7)
|
||||||
| Tool | Description |
|
`get_folders`, `create_folder`, `rename_folder`, `empty_folder`, `delete_folder`,
|
||||||
|---|---|
|
`move_folder`, `check_session`
|
||||||
| `get_folders` | List mail folders with unread counts |
|
|
||||||
| `create_folder` | Create a new mail folder |
|
|
||||||
| `rename_folder` | Rename an existing folder |
|
|
||||||
| `empty_folder` | Empty all items from a folder |
|
|
||||||
| `delete_folder` | Delete a mail folder |
|
|
||||||
| `move_folder` | Move a folder to a different parent |
|
|
||||||
| `check_session` | Check if the OWA session is authenticated |
|
|
||||||
|
|
||||||
### Availability (2)
|
### Availability (2)
|
||||||
| Tool | Description |
|
`find_free_time`, `find_meeting_time`
|
||||||
|---|---|
|
|
||||||
| `find_free_time` | Find free slots in your calendar |
|
|
||||||
| `find_meeting_time` | Find common free slots for multiple people |
|
|
||||||
|
|
||||||
### Analytics (2)
|
### Analytics (2)
|
||||||
| Tool | Description |
|
`get_meeting_stats`, `get_meeting_contacts`
|
||||||
|---|---|
|
|
||||||
| `get_meeting_stats` | Meeting count statistics for multiple people |
|
|
||||||
| `get_meeting_contacts` | Connection matrix — who you meet with most |
|
|
||||||
|
|
||||||
### Auth (1)
|
### Auth (1)
|
||||||
| Tool | Description |
|
`login`
|
||||||
|---|---|
|
|
||||||
| `login` | Authenticate to OWA using browser session cookies |
|
|
||||||
|
|
||||||
## Files
|
|
||||||
|
|
||||||
```
|
|
||||||
exchange_mcp/
|
|
||||||
server.py # FastMCP server entry point
|
|
||||||
owa_client.py # OWA HTTP client
|
|
||||||
tools/
|
|
||||||
email.py # Email tools
|
|
||||||
calendar.py # Calendar tools
|
|
||||||
people.py # Directory search
|
|
||||||
folders.py # Folder management & session check
|
|
||||||
availability.py # Free time / meeting time
|
|
||||||
analytics.py # Meeting stats & contacts
|
|
||||||
auth.py # Login tool
|
|
||||||
pyproject.toml # Package config
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
"""Exchange MCP Server - access OWA email, calendar, directory via MCP."""
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
"""MCP tool modules for Exchange."""
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""OWA MCP Server — access OWA email, calendar, directory via MCP."""
|
||||||
@@ -9,7 +9,6 @@ mirroring what the OWA web frontend sends.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
from urllib.parse import quote, urlparse
|
from urllib.parse import quote, urlparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -55,7 +54,6 @@ class OWAClient:
|
|||||||
):
|
):
|
||||||
self.cookie_file = Path(
|
self.cookie_file = Path(
|
||||||
cookie_file
|
cookie_file
|
||||||
or os.environ.get("EXCHANGE_COOKIE_FILE", "")
|
|
||||||
or str(Path(__file__).parent.parent / "session-cookies.txt")
|
or str(Path(__file__).parent.parent / "session-cookies.txt")
|
||||||
)
|
)
|
||||||
self.owa_url = (owa_url or "").rstrip("/")
|
self.owa_url = (owa_url or "").rstrip("/")
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Exchange MCP Server.
|
"""OWA MCP Server.
|
||||||
|
|
||||||
Exposes OWA email, calendar, directory, and availability tools via MCP.
|
Exposes OWA email, calendar, directory, and availability tools via MCP.
|
||||||
Uses FastMCP with a lifespan context manager to share a single OWAClient
|
Uses FastMCP with a lifespan context manager to share a single OWAClient
|
||||||
@@ -13,19 +13,12 @@ CLI arguments:
|
|||||||
--host HTTP host (default 127.0.0.1)
|
--host HTTP host (default 127.0.0.1)
|
||||||
--enabled-tools Comma-separated whitelist of tool names
|
--enabled-tools Comma-separated whitelist of tool names
|
||||||
--disabled-tools Comma-separated blacklist of tool names
|
--disabled-tools Comma-separated blacklist of tool names
|
||||||
|
|
||||||
Tool filtering (env fallback):
|
|
||||||
EXCHANGE_ENABLED_TOOLS — comma-separated whitelist (used only when
|
|
||||||
--enabled-tools is not provided).
|
|
||||||
EXCHANGE_DISABLED_TOOLS — comma-separated blacklist (used only when
|
|
||||||
--disabled-tools is not provided).
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncIterator
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@@ -34,8 +27,8 @@ from mcp.server.auth.provider import AccessToken, TokenVerifier
|
|||||||
from mcp.server.auth.settings import AuthSettings
|
from mcp.server.auth.settings import AuthSettings
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
from exchange_mcp.owa_client import OWAClient
|
from owa_mcp.owa_client import OWAClient
|
||||||
from exchange_mcp.server_lifecycle import (
|
from owa_mcp.server_lifecycle import (
|
||||||
HashTokenVerifier,
|
HashTokenVerifier,
|
||||||
SessionKeepalive,
|
SessionKeepalive,
|
||||||
apply_tool_filters,
|
apply_tool_filters,
|
||||||
@@ -55,7 +48,7 @@ class AppContext:
|
|||||||
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
|
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
|
||||||
"""Initialise the OWA client on first connection, then keep the session alive.
|
"""Initialise the OWA client on first connection, then keep the session alive.
|
||||||
|
|
||||||
A :class:`~exchange_mcp.server_lifecycle.SessionKeepalive` task starts
|
A :class:`~owa_mcp.server_lifecycle.SessionKeepalive` task starts
|
||||||
immediately (pings inbox, extends the session, persists fresh cookies) and
|
immediately (pings inbox, extends the session, persists fresh cookies) and
|
||||||
repeats every 5 minutes until the server shuts down.
|
repeats every 5 minutes until the server shuts down.
|
||||||
"""
|
"""
|
||||||
@@ -80,13 +73,13 @@ _lifespan_cookie_file: str | None = None
|
|||||||
|
|
||||||
|
|
||||||
# ── Module-level MCP server (imported by all tool modules) ────────────────
|
# ── Module-level MCP server (imported by all tool modules) ────────────────
|
||||||
mcp: FastMCP = FastMCP("exchange", lifespan=app_lifespan)
|
mcp: FastMCP = FastMCP("owa", lifespan=app_lifespan)
|
||||||
|
|
||||||
|
|
||||||
def _parse_args(argv=None) -> argparse.Namespace:
|
def _parse_args(argv=None) -> argparse.Namespace:
|
||||||
"""Parse command-line arguments."""
|
"""Parse command-line arguments."""
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Exchange MCP Server — OWA/Exchange integration via MCP",
|
description="OWA MCP Server — OWA/Exchange integration via MCP",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--transport",
|
"--transport",
|
||||||
@@ -184,38 +177,25 @@ def main(argv=None) -> None:
|
|||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
# ── Register tools (imports populate module-level `mcp`) ───────────────
|
# ── Register tools (imports populate module-level `mcp`) ───────────────
|
||||||
import exchange_mcp.tools.email # noqa: E402, F401
|
import owa_mcp.tools.email # noqa: E402, F401
|
||||||
import exchange_mcp.tools.calendar # noqa: E402, F401
|
import owa_mcp.tools.calendar # noqa: E402, F401
|
||||||
import exchange_mcp.tools.people # noqa: E402, F401
|
import owa_mcp.tools.people # noqa: E402, F401
|
||||||
import exchange_mcp.tools.folders # noqa: E402, F401
|
import owa_mcp.tools.folders # noqa: E402, F401
|
||||||
import exchange_mcp.tools.availability # noqa: E402, F401
|
import owa_mcp.tools.availability # noqa: E402, F401
|
||||||
import exchange_mcp.tools.analytics # noqa: E402, F401
|
import owa_mcp.tools.analytics # noqa: E402, F401
|
||||||
import exchange_mcp.tools.auth # noqa: E402, F401
|
import owa_mcp.tools.auth # noqa: E402, F401
|
||||||
|
|
||||||
# ── Auth for HTTP transports ───────────────────────────────────────────
|
# ── Auth for HTTP transports ───────────────────────────────────────────
|
||||||
_configure_auth(mcp, args.token_hash)
|
_configure_auth(mcp, args.token_hash)
|
||||||
|
|
||||||
# ── Tool filtering ─────────────────────────────────────────────────────
|
# ── Tool filtering ─────────────────────────────────────────────────────
|
||||||
# CLI args take precedence over env vars.
|
|
||||||
enabled: list[str] | None = None
|
enabled: list[str] | None = None
|
||||||
if args.enabled_tools is not None:
|
if args.enabled_tools is not None:
|
||||||
enabled = [t.strip() for t in args.enabled_tools.split(",") if t.strip()]
|
enabled = [t.strip() for t in args.enabled_tools.split(",") if t.strip()]
|
||||||
elif os.environ.get("EXCHANGE_ENABLED_TOOLS", "").strip():
|
|
||||||
enabled = [
|
|
||||||
t.strip()
|
|
||||||
for t in os.environ["EXCHANGE_ENABLED_TOOLS"].split(",")
|
|
||||||
if t.strip()
|
|
||||||
]
|
|
||||||
|
|
||||||
disabled: list[str] | None = None
|
disabled: list[str] | None = None
|
||||||
if args.disabled_tools is not None:
|
if args.disabled_tools is not None:
|
||||||
disabled = [t.strip() for t in args.disabled_tools.split(",") if t.strip()]
|
disabled = [t.strip() for t in args.disabled_tools.split(",") if t.strip()]
|
||||||
elif os.environ.get("EXCHANGE_DISABLED_TOOLS", "").strip():
|
|
||||||
disabled = [
|
|
||||||
t.strip()
|
|
||||||
for t in os.environ["EXCHANGE_DISABLED_TOOLS"].split(",")
|
|
||||||
if t.strip()
|
|
||||||
]
|
|
||||||
|
|
||||||
if enabled or disabled:
|
if enabled or disabled:
|
||||||
apply_tool_filters(mcp, enabled=enabled, disabled=disabled)
|
apply_tool_filters(mcp, enabled=enabled, disabled=disabled)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Shared lifecycle utilities for the Exchange MCP server.
|
"""Shared lifecycle utilities for the OWA MCP server.
|
||||||
|
|
||||||
Centralises three concerns so that tool modules do not duplicate code:
|
Centralises three concerns so that tool modules do not duplicate code:
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from exchange_mcp.owa_client import OWAClient, SessionExpiredError
|
from owa_mcp.owa_client import OWAClient, SessionExpiredError
|
||||||
from mcp.server.auth.provider import AccessToken, TokenVerifier
|
from mcp.server.auth.provider import AccessToken, TokenVerifier
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""MCP tool modules for OWA."""
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Analytics tools for the Exchange MCP server.
|
"""Analytics tools for the OWA MCP server.
|
||||||
|
|
||||||
Provides meeting statistics and connection matrix analysis
|
Provides meeting statistics and connection matrix analysis
|
||||||
using GetUserAvailability and GetItem APIs.
|
using GetUserAvailability and GetItem APIs.
|
||||||
@@ -10,9 +10,9 @@ from datetime import datetime, timedelta, date
|
|||||||
|
|
||||||
from mcp.server.fastmcp import Context
|
from mcp.server.fastmcp import Context
|
||||||
|
|
||||||
from exchange_mcp.server import mcp, AppContext
|
from owa_mcp.server import mcp, AppContext
|
||||||
from exchange_mcp.owa_client import OWAClient
|
from owa_mcp.owa_client import OWAClient
|
||||||
from exchange_mcp.server_lifecycle import require_client
|
from owa_mcp.server_lifecycle import require_client
|
||||||
|
|
||||||
|
|
||||||
def _get_client(ctx: Context) -> OWAClient:
|
def _get_client(ctx: Context) -> OWAClient:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Authentication tool for the Exchange MCP server.
|
"""Authentication tool for the OWA MCP server.
|
||||||
|
|
||||||
Provides a `login` tool that accepts session cookies from the user,
|
Provides a `login` tool that accepts session cookies from the user,
|
||||||
saves them to disk, loads them into the OWA client, and verifies the session.
|
saves them to disk, loads them into the OWA client, and verifies the session.
|
||||||
@@ -12,9 +12,9 @@ import json
|
|||||||
|
|
||||||
from mcp.server.fastmcp import Context
|
from mcp.server.fastmcp import Context
|
||||||
|
|
||||||
from exchange_mcp.server import mcp, AppContext
|
from owa_mcp.server import mcp, AppContext
|
||||||
from exchange_mcp.owa_client import OWAClient, SessionExpiredError
|
from owa_mcp.owa_client import OWAClient, SessionExpiredError
|
||||||
from exchange_mcp.server_lifecycle import require_client
|
from owa_mcp.server_lifecycle import require_client
|
||||||
|
|
||||||
|
|
||||||
def _get_app_ctx(ctx: Context) -> AppContext:
|
def _get_app_ctx(ctx: Context) -> AppContext:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Availability / free-time tools for the Exchange MCP server.
|
"""Availability / free-time tools for the OWA MCP server.
|
||||||
|
|
||||||
Ports find-free-time.py and find-meeting-time.py logic into MCP tools
|
Ports find-free-time.py and find-meeting-time.py logic into MCP tools
|
||||||
using OWAClient.
|
using OWAClient.
|
||||||
@@ -9,9 +9,9 @@ from datetime import datetime, timedelta
|
|||||||
|
|
||||||
from mcp.server.fastmcp import Context
|
from mcp.server.fastmcp import Context
|
||||||
|
|
||||||
from exchange_mcp.server import mcp, AppContext
|
from owa_mcp.server import mcp, AppContext
|
||||||
from exchange_mcp.owa_client import OWAClient
|
from owa_mcp.owa_client import OWAClient
|
||||||
from exchange_mcp.server_lifecycle import require_client
|
from owa_mcp.server_lifecycle import require_client
|
||||||
|
|
||||||
|
|
||||||
def _get_client(ctx: Context) -> OWAClient:
|
def _get_client(ctx: Context) -> OWAClient:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Calendar tools for the Exchange MCP server.
|
"""Calendar tools for the OWA MCP server.
|
||||||
|
|
||||||
Provides MCP tools for calendar event retrieval, meeting creation,
|
Provides MCP tools for calendar event retrieval, meeting creation,
|
||||||
update, cancellation, and meeting response management via OWA API.
|
update, cancellation, and meeting response management via OWA API.
|
||||||
@@ -11,10 +11,10 @@ from datetime import datetime, timedelta
|
|||||||
|
|
||||||
from mcp.server.fastmcp import Context
|
from mcp.server.fastmcp import Context
|
||||||
|
|
||||||
from exchange_mcp.server import mcp, AppContext
|
from owa_mcp.server import mcp, AppContext
|
||||||
from exchange_mcp.owa_client import OWAClient
|
from owa_mcp.owa_client import OWAClient
|
||||||
from exchange_mcp.server_lifecycle import require_client
|
from owa_mcp.server_lifecycle import require_client
|
||||||
from exchange_mcp.utils import html_to_text, parse_iso_datetime, extract_links_from_html
|
from owa_mcp.utils import html_to_text, parse_iso_datetime, extract_links_from_html
|
||||||
|
|
||||||
|
|
||||||
def _get_client(ctx: Context) -> OWAClient:
|
def _get_client(ctx: Context) -> OWAClient:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Email tools for the Exchange MCP server.
|
"""Email tools for the OWA MCP server.
|
||||||
|
|
||||||
Provides tools for reading, sending, replying, forwarding, and managing
|
Provides tools for reading, sending, replying, forwarding, and managing
|
||||||
emails via the OWA Exchange API.
|
emails via the OWA Exchange API.
|
||||||
@@ -8,10 +8,10 @@ import json
|
|||||||
|
|
||||||
from mcp.server.fastmcp import Context
|
from mcp.server.fastmcp import Context
|
||||||
|
|
||||||
from exchange_mcp.server import mcp, AppContext
|
from owa_mcp.server import mcp, AppContext
|
||||||
from exchange_mcp.owa_client import OWAClient, SessionExpiredError
|
from owa_mcp.owa_client import OWAClient, SessionExpiredError
|
||||||
from exchange_mcp.server_lifecycle import require_client
|
from owa_mcp.server_lifecycle import require_client
|
||||||
from exchange_mcp.utils import html_to_text, extract_links_from_html
|
from owa_mcp.utils import html_to_text, extract_links_from_html
|
||||||
|
|
||||||
|
|
||||||
def _get_client(ctx: Context) -> OWAClient:
|
def _get_client(ctx: Context) -> OWAClient:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Folder tools for the Exchange MCP server.
|
"""Folder tools for the OWA MCP server.
|
||||||
|
|
||||||
Ports the get-folders.py logic into an MCP tool using OWAClient.
|
Ports the get-folders.py logic into an MCP tool using OWAClient.
|
||||||
"""
|
"""
|
||||||
@@ -7,9 +7,9 @@ import json
|
|||||||
|
|
||||||
from mcp.server.fastmcp import Context
|
from mcp.server.fastmcp import Context
|
||||||
|
|
||||||
from exchange_mcp.server import mcp, AppContext
|
from owa_mcp.server import mcp, AppContext
|
||||||
from exchange_mcp.owa_client import OWAClient
|
from owa_mcp.owa_client import OWAClient
|
||||||
from exchange_mcp.server_lifecycle import require_client
|
from owa_mcp.server_lifecycle import require_client
|
||||||
|
|
||||||
|
|
||||||
_DISTINGUISHED_NAMES = {
|
_DISTINGUISHED_NAMES = {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""People / directory search tools for the Exchange MCP server.
|
"""People / directory search tools for the OWA MCP server.
|
||||||
|
|
||||||
Ports the find-person.py logic into an MCP tool using OWAClient.
|
Ports the find-person.py logic into an MCP tool using OWAClient.
|
||||||
"""
|
"""
|
||||||
@@ -7,9 +7,9 @@ import json
|
|||||||
|
|
||||||
from mcp.server.fastmcp import Context
|
from mcp.server.fastmcp import Context
|
||||||
|
|
||||||
from exchange_mcp.server import mcp, AppContext
|
from owa_mcp.server import mcp, AppContext
|
||||||
from exchange_mcp.owa_client import OWAClient
|
from owa_mcp.owa_client import OWAClient
|
||||||
from exchange_mcp.server_lifecycle import require_client
|
from owa_mcp.server_lifecycle import require_client
|
||||||
|
|
||||||
|
|
||||||
def _get_client(ctx: Context) -> OWAClient:
|
def _get_client(ctx: Context) -> OWAClient:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Shared utility functions for the Exchange MCP server.
|
"""Shared utility functions for the OWA MCP server.
|
||||||
|
|
||||||
Extracts duplicated helpers from the standalone scripts:
|
Extracts duplicated helpers from the standalone scripts:
|
||||||
html_to_text, date/time formatting and parsing.
|
html_to_text, date/time formatting and parsing.
|
||||||
+6
-6
@@ -1,11 +1,11 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "exchange-mcp-server"
|
name = "owa-mcp-server"
|
||||||
version = "1.0.1"
|
version = "0.1.0"
|
||||||
description = "MCP server for Exchange / OWA - email, calendar, directory, availability"
|
description = "MCP server for OWA — email, calendar, directory, availability"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
keywords = ["mcp", "exchange", "owa", "outlook", "email", "calendar"]
|
keywords = ["mcp", "owa", "outlook", "exchange", "email", "calendar"]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
]
|
]
|
||||||
@@ -23,7 +23,7 @@ Repository = "https://git.kosyrev.name/zoo/owa-mcp"
|
|||||||
http = ["uvicorn>=0.30.0"]
|
http = ["uvicorn>=0.30.0"]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
exchange-mcp-server = "exchange_mcp.server:main"
|
owa-mcp-server = "owa_mcp.server:main"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=68.0"]
|
requires = ["setuptools>=68.0"]
|
||||||
@@ -31,4 +31,4 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
where = ["."]
|
where = ["."]
|
||||||
include = ["exchange_mcp*"]
|
include = ["owa_mcp*"]
|
||||||
|
|||||||
Reference in New Issue
Block a user