# Google Workspace MCP — Architecture Design

## FastMCP Features We'll Use

Based on thorough study of FastMCP v3 documentation, here's which features
apply to our Google Workspace multi-account MCP server and why.

---

### 1. Composition (Server Mounting)

**What**: Mount separate FastMCP sub-servers per Google service under namespaces.

**Why**: Keeps each service modular. Tools are auto-prefixed (e.g. `gmail_search`,
`drive_upload`, `sheets_read`, `calendar_list_events`). Adding a new service
later (e.g. Google Tasks, Contacts) is just mounting another sub-server.

```python
main = FastMCP("Google Workspace")
main.mount(gmail_server, namespace="gmail")
main.mount(drive_server, namespace="drive")
main.mount(sheets_server, namespace="sheets")
main.mount(calendar_server, namespace="calendar")
```

---

### 2. Lifespan (Startup/Shutdown)

**What**: Initialize shared resources once at server startup, clean up on shutdown.

**Why**: Load config.json and warm the credential cache once, not per-request.
Google API service objects can be pre-built and reused.

```python
from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(server):
    config = load_config()
    credentials = {p.name: get_credentials(p.name) for p in config.profiles}
    yield {"config": config, "credentials": credentials}

mcp = FastMCP("Google Workspace", lifespan=lifespan)
```

---

### 3. Dependencies (Depends / Injection)

**What**: Inject authenticated Google API clients into tool functions automatically.
Dependency parameters are hidden from the MCP schema — clients never see them.

**Why**: Every tool needs an authenticated API client for the right profile.
Instead of boilerplate in every function, inject it:

```python
from fastmcp import Depends

def get_gmail_service(profile: str = "default"):
    creds = get_credentials(profile)
    return build("gmail", "v1", credentials=creds)

@mcp.tool
def gmail_search(query: str, profile: str = "default",
                 gmail = Depends(get_gmail_service)):
    return gmail.users().messages().list(userId="me", q=query).execute()
```

---

### 4. Context (CurrentContext)

**What**: Access logging, progress reporting, session state, and request metadata.

**Why**:
- **Logging**: `ctx.info("Searching Gmail for profile=business")`
- **Progress**: Report progress during large operations (Drive uploads, batch reads)
- **Session state**: Remember which profile the user prefers across calls

```python
from fastmcp import Context

@mcp.tool
async def drive_upload(file_path: str, folder_id: str,
                       ctx: Context = CurrentContext()):
    await ctx.info(f"Uploading {file_path} to folder {folder_id}")
    await ctx.report_progress(0.5, "Uploading...")
    # ... upload logic ...
    await ctx.report_progress(1.0, "Done")
```

---

### 5. Tags & Visibility

**What**: Tag tools by service and control which are visible per session.

**Why**: If a user only needs Gmail tools, we can hide Drive/Sheets/Calendar
to reduce noise in tool listings. Tools tagged `gmail`, `drive`, `sheets`,
`calendar` can be filtered.

```python
@mcp.tool(tags={"gmail", "read"})
def gmail_search(...): ...

@mcp.tool(tags={"drive", "write"})
def drive_upload(...): ...

# Per-session filtering
ctx.enable_components(tags={"gmail"}, only=True)  # show only Gmail tools
```

---

### 6. Middleware

**What**: Cross-cutting logic that intercepts every request/response.

**Why**:
- **Rate limiting**: Respect Google API quotas (e.g., Gmail 250 quota units/sec)
- **Error handling**: Catch Google API errors, convert to user-friendly messages
- **Audit logging**: Log every tool call with profile, timestamp, result status

```python
class GoogleApiErrorHandler(Middleware):
    async def on_call_tool(self, context, call_next):
        try:
            return await call_next(context)
        except HttpError as e:
            if e.resp.status == 429:
                raise ToolError("Google API rate limit hit. Try again shortly.")
            raise ToolError(f"Google API error: {e.resp.status} {e.reason}")
```

---

### 7. Background Tasks (for future)

**What**: Run long operations asynchronously with progress tracking.

**Why**: Batch email exports, large Drive folder scans, or multi-file uploads
can take a while. Background tasks let the client check progress.

```python
@mcp.tool(task=True)
async def drive_bulk_upload(folder_id: str, file_paths: list[str],
                            progress: Progress = Progress()):
    await progress.set_total(len(file_paths))
    for path in file_paths:
        await upload_file(path, folder_id)
        await progress.increment()
```

**Note**: Requires `pip install "fastmcp[tasks]"`. Can add later.

---

### 8. Testing

**What**: Built-in pytest patterns with in-memory client.

**Why**: Test every tool without hitting Google APIs (mock the service layer).

```python
import pytest
from fastmcp import Client

@pytest.fixture
def client():
    return Client(mcp)

@pytest.mark.asyncio
async def test_gmail_search(client):
    result = await client.call_tool("gmail_search", {"query": "from:aws", "profile": "business"})
    assert "messages" in result
```

---

### Features We're NOT Using (and why)

| Feature | Why Not |
|---------|---------|
| **FastMCP OAuth/Auth** | Only works with HTTP transport. We use STDIO for Claude Desktop/Code, so we handle our own Google OAuth via named profiles. |
| **Prompts** | Our tools are action-oriented. Prompts are for guiding LLM conversations — not needed here. |
| **Resources** | Our data is dynamic (emails, files). Resources are for static/semi-static data. Tools are the right fit. |
| **Apps/Prefab** | Interactive UIs in conversations — nice but not needed for a utility MCP server. |
| **Sampling** | Requesting the client's LLM to generate text — not applicable to our use case. |
| **Elicitation** | Requesting structured user input mid-tool — could be useful later for OAuth consent, but overkill now. |
| **Storage Backends** | Redis/Postgres for session persistence — overkill for local STDIO usage. |

---

## Project Structure

```
google-workspace-mcp/
├── pyproject.toml
├── README.md
├── ARCHITECTURE.md
├── config.example.json
├── .gitignore
├── src/
│   └── google_workspace_mcp/
│       ├── __init__.py
│       ├── server.py              # Main FastMCP server + composition
│       ├── config.py              # Config loading, profile management
│       ├── auth/
│       │   ├── __init__.py
│       │   ├── oauth.py           # Google OAuth2 + credential caching
│       │   └── setup.py           # CLI: gw-mcp-auth (interactive setup)
│       ├── services/
│       │   ├── __init__.py
│       │   ├── gmail.py           # Google API wrapper for Gmail
│       │   ├── drive.py           # Google API wrapper for Drive
│       │   ├── sheets.py          # Google API wrapper for Sheets
│       │   └── calendar.py        # Google API wrapper for Calendar
│       ├── tools/
│       │   ├── __init__.py
│       │   ├── gmail.py           # @mcp.tool decorated Gmail tools
│       │   ├── drive.py           # @mcp.tool decorated Drive tools
│       │   ├── sheets.py          # @mcp.tool decorated Sheets tools
│       │   ├── calendar.py        # @mcp.tool decorated Calendar tools
│       │   └── profiles.py        # list_profiles tool
│       └── middleware/
│           ├── __init__.py
│           ├── error_handler.py   # Google API error → ToolError
│           └── logging.py         # Audit logging middleware
└── tests/
    ├── conftest.py
    ├── test_gmail.py
    ├── test_drive.py
    ├── test_sheets.py
    └── test_calendar.py
```

### Key Design Decisions

1. **services/ vs tools/ separation**: `services/` wraps Google APIs (reusable, testable).
   `tools/` are thin FastMCP-decorated functions that call services. This makes testing
   easy — mock the service layer, test the tool layer.

2. **Profile as parameter, not composition**: Each tool takes `profile: str` as an optional
   parameter (default = first profile). This is simpler than mounting N copies of each
   sub-server per account. The user says `gmail_search(query="invoice", profile="personal")`
   rather than `personal_gmail_search(query="invoice")`.

3. **STDIO transport**: For Claude Desktop and Claude Code. Can add HTTP later for
   multi-user/remote deployment.

---

## Tool Inventory (24 tools)

### Profiles (1)
- `list_profiles` — List all configured Google accounts

### Gmail (5)
- `gmail_search` — Search with full Gmail query syntax
- `gmail_read` — Read a message by ID
- `gmail_read_thread` — Read full conversation thread
- `gmail_send` — Send or reply to email
- `gmail_get_attachment` — Download attachment

### Drive (7)
- `drive_list` — List/search files
- `drive_upload` — Upload local file
- `drive_upload_base64` — Upload from base64 data
- `drive_download` — Download to local path
- `drive_create_folder` — Create folder
- `drive_move` — Move file between folders
- `drive_trash` — Trash a file

### Sheets (5)
- `sheets_read` — Read data from range
- `sheets_write` — Write data to range
- `sheets_append` — Append rows
- `sheets_create` — Create new spreadsheet
- `sheets_info` — Get spreadsheet metadata

### Calendar (6)
- `calendar_list_events` — List events in time range
- `calendar_create_event` — Create event
- `calendar_update_event` — Update event
- `calendar_delete_event` — Delete event
- `calendar_free_busy` — Check availability
- `calendar_list_calendars` — List all calendars
