Skip to main content
Glama
README.md
# mise-en-space

## Status

**Robustness:** Stable — in daily use, regular releases (currently v0.7.4)
**Works with:** Claude Code, Amp, Gemini CLI (any MCP client)
**Install:** Configure as MCP server (see below)
**Requires:** Python 3.11+, Google OAuth credentials

An MCP sous-chef for Google Workspace that provides a *mise en place* for knowledge work. Peel and pith removed, everything prepped and in its place, ready for Claude to cook with.

## Why another tool for LLMs to use Google Workspace?

[Google's official Workspace MCP](https://github.com/gemini-cli-extensions/workspace) ![Stars](https://img.shields.io/github/stars/gemini-cli-extensions/workspace?style=social) has 50 tools and requires multiple round-trips for basic tasks — search Gmail, get back a list of IDs, call again for each message, all of it burning context. Because it's essentially a thin wrapper over the Workspace APIs, the tool definitions alone take up ~15k of tokens every session.

Looking around for others, I found plenty of inspiration, but also some snags:
    
 - [taylorwilsdon/google_workspace_mcp](https://github.com/taylorwilsdon/google_workspace_mcp) ![Stars](https://img.shields.io/github/stars/taylorwilsdon/google_workspace_mcp?style=social) covers every Google service, but returns all content inline — a 70-slide deck or 30-message thread lands straight in your context window
 - [felores/gdrive-mcp-server](https://github.com/felores/gdrive-mcp-server) ![Stars](https://img.shields.io/github/stars/felores/gdrive-mcp-server?style=social) deposits files to disk (Docs→Markdown, Sheets→CSV) the way I wanted, and also used a clever trick to get Drive to do high quality conversions, but only does Google Drive, so its coverage was limited for my needs
 - [GongRzhe/Gmail-MCP-Server](https://github.com/GongRzhe/Gmail-MCP-Server) ![Stars](https://img.shields.io/github/stars/GongRzhe/Gmail-MCP-Server?style=social) — pre-built Gmail filter templates. Good ergonomics for a single source, but again, just a single source. 
- [aaronsb/google-workspace-mcp](https://github.com/aaronsb/google-workspace-mcp) ![Stars](https://img.shields.io/github/stars/aaronsb/google-workspace-mcp?style=social) — deposits files to disk with per-account folders. The right idea for file handling IMO - don't spam the caller's context window, but I didn't need multi-account support
- [a-bonus/google-docs-mcp](https://github.com/a-bonus/google-docs-mcp) ![Stars](https://img.shields.io/github/stars/a-bonus/google-docs-mcp?style=social) — tab-aware Docs extraction. Everyone else ignores multi-tab documents.

I wanted something that had the best of all these ideas:

- **Sous-chef philosophy.** Fetch a doc and get the comments too. Fetch an email and get the attachments extracted. Don't make the chef ask for every ingredient separately.
- **Clean extraction.** PDF text comes from poppler's `pdftotext -layout` — measured best on a 636-probe census of real corporate PDFs (97.4% verbatim value survival, 100% on big-table pages) — with Drive server-side OCR as the fallback for scans. Office files convert automatically.
- **Opinionated, LLM-first control surface** 3 tools not 50 - search, fetch, do. ~3k tokens of tool definitions and everything routes through the same three verbs.
- **One call, rich results.** Gmail search returns subjects, senders, snippets, and attachment names — not a bag of IDs requiring N+1 follow-ups.
- **Filesystem-deposits.** Content goes to disk as markdown/CSV, not into the context window. Claude reads (and greps) what it needs.
- **Companion Skill.** I like the pattern where we provide a tool and a companion [Skill](https://docs.anthropic.com/en/docs/claude-code/skills) that acts as the instruction manual on how to use it.
- **[MCP](https://modelcontextprotocol.io) Optional.** Option for CLI based interactions e.g. if you want to use a different agent harness like pi.

## The 3 Verbs

| Verb | Purpose | Deposits files? |
|------|---------|-----------------|
| `search` | Find files and emails across Drive and Gmail (plus activity and calendar) | Yes — results JSON |
| `fetch` | Extract content to `.mise/` as markdown/CSV | Yes — content folder |
| `do` | Act on Workspace — 23 operations: create, copy, move, rename, share, overwrite, prepend, append, replace_text, draft, reply_draft, archive, star, label, comment, comment_reply, suggest, trash, respond, create_event, update_event, freebusy, setup_oauth | Varies |

## CLI

For agents without MCP support — search and fetch in full, plus `create` (the most common `do` operation):

```bash
mise search "quarterly reports"
mise search "from:alice budget" --sources gmail
mise fetch 1abc123def456
mise create "Title" --content "# Markdown content"
```

## Skills

<!-- GENERATED:SKILLS:START -->
1 skill, tabled from `skills/*/SKILL.md` frontmatter by [render-skills.py](https://github.com/spm1001/batterie-de-savoir/blob/main/scripts/render-skills.py) — regenerate from this repo's root with
`uv run --script ../batterie-de-savoir/scripts/render-skills.py .`

| Skill | What it does |
|-------|--------------|
| `/mise` | Orchestrates content fetching via the mise MCP server's search/fetch/do tools |
<!-- GENERATED:SKILLS:END -->

## Supported Content Types

| What's in the larder | What the chef gets |
|--------|-------------|
| Google Docs | Markdown + open comments |
| Google Sheets | CSV + chart PNGs + open comments |
| Google Slides | Markdown + selective thumbnails + open comments |
| Gmail threads | Markdown with signature stripping via [talon](https://github.com/mailgun/talon), attachment extraction |
| PDFs | Layout-aligned text (`pdftotext -layout` → Drive OCR fallback for scans) |
| Office files (DOCX/XLSX/PPTX) | Markdown or CSV via Drive conversion |
| Video/Audio | AI summary + metadata (requires a chrome-debug browser session) |
| Images | Deposited as-is; SVG rendered to PNG |

## Architecture

```
server.py       MCP server (thin wrappers around tools; ≤500 lines, enforced)
cli.py          CLI interface (same tools, no MCP)
mise_en_space/  Library facade — import mise for in-process use (see below)
tools/          Business logic — routing, orchestration, do() dispatch, remote mode
adapters/       Thin Google API wrappers (one per service)
extractors/     Pure functions — no I/O, no MCP awareness (testable without APIs)
workspace/      File deposit management
resources/      MCP resource text (mise://docs/*)
skills/         Claude skill (auto-discovered by plugin system)
```

**Layer rules:**
- Extractors never import from adapters or tools (no I/O)
- Tools wire adapters → extractors → workspace
- Server and CLI are both thin wrappers around tools
- All of it mechanically enforced by `tests/unit/test_architecture.py` — including the root-level utility files, by discovery

Adding a new content type means: adapter (API call), extractor (parse), tool (wire + deposit). The layers are independent.

## Using mise as a library

Services that run headless (glaneur's nightly harvest, Garni's Cloud Run agents) import mise in-process instead of speaking MCP. The contract is the `mise_en_space` package — one class, the same three verbs:

```python
from mise_en_space import Mise

ws = Mise(ambient=True, base_path=workdir)   # Cloud Run SA via ADC; also:
                                             # token_path=..., credentials=..., or Mise()
listing = ws.search(folder_id=CORPUS)        # discovery
result = ws.fetch(file_id)                   # deposit: manifest.json + content.md
doc = ws.do("create", title=..., content=markdown, folder_id=SHARED_DRIVE_FOLDER)
```

The worked example — hydrate a Drive folder, write a Doc back, as a service account — is [`examples/hydrate_and_write_back.py`](examples/hydrate_and_write_back.py); its header covers installing the wheel (you supply jeton yourself — uv source maps don't ride wheel metadata) and the service-account facts that bite (writes land only in Shared Drives). The full credential and deposit contract is the package docstring: `python -c "import mise_en_space; help(mise_en_space)"`.

Three facts a headless PDF pipeline needs. **The bulk-text tool is poppler's `pdftotext -layout`** — the extraction primary since suite 1.67 — and the wheel cannot bundle it: put `poppler-utils` in the consumer image (Dockerfile `apt-get install poppler-utils`) or PDF text silently degrades to markitdown (worse on tables) or Drive conversion (slow, needs write scope), with only a cue warning to say so. `fetch(file_id, thumbnails=False)` skips page/slide PNG rendering — measured 154s → 59s and 77 MB → 0 of deposit weight on a 256-page annual report — so pass it whenever nothing will look at pixels. And **no extraction path guarantees page boundaries**: form-feed survival is per-PDF (markitdown kept a two-page fixture's marker and dropped all 255 of that same annual report's), so a page-citing consumer must gate on the measured fields every PDF deposit carries — `page_markers` (form feeds in content.md) against `pdf_pages` (poppler's count, when available) — and heed the warning cue that fires whenever per-page citations can't be derived. Never infer page fidelity from `extraction_method`.

## Setup

### 1. Clone and install

```bash
git clone https://github.com/spm1001/mise-en-space.git
cd mise-en-space
uv sync    # requires uv — https://docs.astral.sh/uv/
```

**System prerequisite: poppler.** PDF text extraction (`pdftotext -layout`) and PDF/deck page thumbnails shell out to [poppler](https://poppler.freedesktop.org/):

```bash
sudo apt-get install poppler-utils   # Debian/Ubuntu
brew install poppler                 # macOS
```

Without it mise still works — PDF text falls back to markitdown (measurably worse on tables) or Drive conversion, and thumbnails are skipped — and both the session-start hook and every affected fetch say so rather than degrading silently.

### 2. Google OAuth

mise-en-space uses [jeton](https://github.com/spm1001/jeton) for OAuth.

**Quick version:** `credentials.json` ships with the repo. Just run `uv run python -m auth --auto`.

```bash
uv run python -m auth --auto             # Opens browser + localhost listener (machine with a browser)
uv run python -m auth                    # Headless — prints the consent URL to paste into any browser
uv run python -m auth --code URL_OR_CODE # Exchange the code from the headless flow
```

With `--auto`, grant permissions in the browser and you're done.

**Scopes requested:** Drive (read+write), Gmail (read+write), Docs/Sheets/Slides (read+write), Drive Activity, Drive Labels, Calendar, Forms, and the staff directory (read). The directory scope is `admin.directory.user.readonly`, which reads more alarming than it is — mise only ever calls the Directory API's `domain_public` view, documented by Google as available to any user on the domain, and never the administrator view. See [`oauth_config.py`](oauth_config.py) for the full list and rationale.

<details>
<summary>Bringing your own GCP project (advanced)</summary>

If you prefer your own OAuth credentials instead of the bundled ones:

1. Create or select a [Google Cloud project](https://console.cloud.google.com)
2. Enable these APIs in [APIs & Services > Library](https://console.cloud.google.com/apis/library):
   - Google Drive API, Gmail API, Google Docs API, Google Sheets API
   - Google Slides API, Google Calendar API, Drive Activity API, Drive Labels API
3. Configure [OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent) (External, add your email as test user)
4. Create [OAuth credentials](https://console.cloud.google.com/apis/credentials) (Web Application type)
   - Add `http://localhost:3000/oauth/callback` as an authorized redirect URI
5. Download the JSON and replace `credentials.json` in the repo root
6. Run `uv run python -m auth`

</details>

**Troubleshooting:**

| Problem | Fix |
|---------|-----|
| `redirect_uri_mismatch` | Only applies if using your own GCP project — add `http://localhost:3000/oauth/callback` to redirect URIs |
| `access_denied` | Add your email as a test user on the OAuth consent screen |
| `No browser available` | Use `--manual` flag, or SSH with port forwarding (`-L 3000:localhost:3000`) |

### 3. Add to Claude as MCP server

Add to `~/.claude.json`:

```json
{
  "mcpServers": {
    "mise": {
      "type": "stdio",
      "command": "uv",
      "args": ["--directory", "/path/to/mise-en-space", "run", "python", "server.py"]
    }
  }
}
```

### 4. Link the skill (recommended)

The `skills/` directory contains a Claude skill that teaches Claude *how* to use mise effectively — Gmail operators, the exploration loop, comment checking patterns. The plugin system auto-discovers skills from `skills/*/SKILL.md`.

```bash
# For pi
ln -s /path/to/mise-en-space/skills/mise ~/.pi/agent/skills/mise
```

Without the skill, Claude can call the tools but won't know the patterns that make them useful (like following `email_context` hints or filtering large results with jq).

### 5. Email attachment extractor (optional)

The `apps-script/` directory contains a Google Apps Script that runs in your Google account, extracting email attachments to dated Drive folders (`Email Attachments/YYYY-MM/`). This enables Drive fullText search to find content inside PDF attachments — the "pre-exfil detection" pattern that makes mise searches across Gmail and Drive seamless.

See [`apps-script/README.md`](apps-script/README.md) for setup instructions.

## What to Expect (Latency)

MCP server startup is ~1.3s (import + first auth). After that, the server stays alive — subsequent calls skip startup.

| Operation | Typical | Range | Notes |
|-----------|---------|-------|-------|
| **Search (single source)** | ~1s | 0.2–1.3s | Drive and Gmail similar |
| **Search (Drive + Gmail)** | ~0.8s | 0.6–1.1s | Parallel — faster than either alone |
| **Fetch: Google Doc** | ~2s | 1.7–3.1s | Single API call |
| **Fetch: Gmail thread** | ~2.4s | 1.8–3.0s | Thread + message batch |
| **Fetch: PDF** | ~2.5s | 2.1–3.0s | pdftotext (since 1.67; extraction itself is ~55× faster than the markitdown benchmarked here — download and thumbnails dominate); scans fall back to Drive OCR (5–15s) |
| **Fetch: Google Sheet** | ~4s | 1.9–5.9s | 2 API calls (metadata + values) |
| **Fetch: Slides (7 slides)** | ~6s | 3.1–9.3s | ~0.5s per thumbnail, sequential |
| **Fetch: XLSX** | ~6s | 6.1–6.7s | Drive upload → convert → export |
| **Fetch: DOCX** | ~9s | 8.3–9.9s | Same pipeline, larger payloads |

*Benchmarked 9 Feb 2026 at [`fd5f9d0`](../../commit/fd5f9d0), 3 runs each, warm server, London → Google APIs.*

**The slow paths:** Office files (DOCX/XLSX) are unavoidably slow — Drive does server-side conversion (upload → convert → export → cleanup). Gmail attachments that are Office files are listed but not auto-extracted for this reason; use `fetch(thread_id, attachment="file.xlsx")` on demand.

Detailed timing data and flow diagrams: [`docs/information-flow.md`](docs/information-flow.md)

## The Kitchen

Mise en Space is part of [Batterie de Savoir](https://spm1001.github.io/batterie-de-savoir/) — a suite of tools for AI-assisted knowledge work. See the [full brigade and design principles](https://spm1001.github.io/batterie-de-savoir/) for how the tools fit together.

TDQS

A4.1/5.0

Scored across 3 tools

Disambiguation5/5

Each tool targets a distinct aspect: 'do' performs actions on Google Workspace, 'fetch' retrieves specific content to a local directory, and 'search' finds items across services. No overlap in primary purpose.

Naming Consistency5/5

All tool names are single, lowercase verbs ('do', 'fetch', 'search'), creating a uniform and predictable pattern. No mixing of styles.

Tool Count4/5

Only 3 tools, but each is highly capable (e.g., 'do' encompasses many sub-operations). The count is slightly low for the broad domain, but the tools are well-scoped.

Completeness4/5

Covers essential operations: create, edit, fetch, search, share, and email. Minor gaps like delete/trash are absent, but the core workflows are supported.

Maintenance

ActivityActive
ResponsivenessNo issues