Skip to main content
Glama
khokan

mcp-gsuite

by khokan
README.md
# mcp-gsuite

[![MCPize](https://mcpize.com/badge/@mcpize/mcpize?type=hosted)](https://mcpize.com)

A **Model Context Protocol (MCP) server** for interacting with **Google Workspace** — Gmail & Google Calendar — via a streamable HTTP transport. Built with TypeScript, Express, and the official Google APIs Node.js client.

---

## Features

- šŸ“§ **Gmail** — list, search, send, and label emails
- šŸ“… **Google Calendar** — list, create, update, and delete events
- šŸ” **OAuth 2.0** — offline access via refresh tokens (no re-auth needed)
- ⚔ **Stateful sessions** — each MCP client gets its own isolated session
- 🐳 **Docker-ready** — includes a production-grade Dockerfile
- ā˜ļø **Cloud Run compatible** — graceful SIGTERM/SIGINT shutdown handling

---

## Quick Start

```bash
npm install
npm run dev     # Start with hot reload (tsx watch)
```

**Local development:**
- **MCP endpoint:** `http://localhost:8080/mcp`
- **Health check:** `http://localhost:8080/health`

**Remote (MCPize hosted):**
- **MCP endpoint:** `https://mcp-gsuite.mcpize.run/mcp`

---

## Prerequisites

1. A **Google Cloud project** with the following APIs enabled:
   - Gmail API
   - Google Calendar API
2. **OAuth 2.0 Web Application credentials** — download as `credentials.json` from Google Cloud Console
3. A **refresh token** for offline access (see below)

### credentials.json format

```json
{
  "web": {
    "client_id": "YOUR_CLIENT_ID.apps.googleusercontent.com",
    "client_secret": "YOUR_CLIENT_SECRET",
    "redirect_uris": [
      "https://your-deployed-url/mcp",
      "http://localhost:4100/mcp"
    ],
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token"
  }
}
```

### Getting a Refresh Token

```bash
npm run generate    # Automated OAuth flow (recommended)
# or
node get-refresh-token.js
```

The script will open a browser for Google sign-in and save the token to `.env` automatically.

---

## Environment Variables

Copy `.env.example` to `.env` and fill in:

```bash
cp .env.example .env
```

| Variable | Description | Required |
|---|---|---|
| `GOOGLE_CLIENT_ID` | OAuth client ID from Google Cloud Console | āœ… |
| `GOOGLE_CLIENT_SECRET` | OAuth client secret | āœ… |
| `GOOGLE_REFRESH_TOKEN` | Refresh token for offline access | āœ… |
| `PORT` | Server port (default: `8080`) | āŒ |
| `NODE_ENV` | Set to `production` to disable dev logging | āŒ |

> **Note:** The server will **throw an error on startup** if any of the three required credentials are missing.

---

## Development

```bash
npm install         # Install dependencies
npm run dev         # Development mode with hot reload (tsx watch)
npm run build       # Compile TypeScript to dist/
npm start           # Run compiled server (node dist/index.js)
npm test            # Run unit tests (vitest)
npm run test:smoke  # Run smoke/connectivity test
```

---

## Project Structure

```
ā”œā”€ā”€ src/
│   └── index.ts                # MCP server — all tool handlers & Express setup
ā”œā”€ā”€ tests/
│   └── tools.test.ts           # Vitest unit tests
ā”œā”€ā”€ scripts/
│   ā”œā”€ā”€ list-events.mjs         # Standalone calendar event listing script
│   └── smoke-test.js           # Basic connectivity smoke test
ā”œā”€ā”€ get-refresh-token.js        # Automated OAuth flow helper
ā”œā”€ā”€ credentials.json            # Google OAuth client credentials (not committed)
ā”œā”€ā”€ token.json                  # Cached OAuth tokens (auto-generated, not committed)
ā”œā”€ā”€ package.json                # Dependencies and npm scripts
ā”œā”€ā”€ tsconfig.json               # TypeScript compiler configuration
ā”œā”€ā”€ mcpize.yaml                 # MCPize deployment manifest
ā”œā”€ā”€ Dockerfile                  # Container build instructions
ā”œā”€ā”€ .env                        # Your local secrets (not committed)
└── .env.example                # Environment variables template
```

---

## MCP Tools

### šŸ“§ Gmail

| Tool | Description | Key Parameters |
|---|---|---|
| `list_emails` | List recent emails from Gmail inbox | `maxResults` (1–500, default 10), `query` (Gmail filter) |
| `search_emails` | Search emails with Gmail query syntax | `query` (required), `maxResults` (1–500, default 10) |
| `send_email` | Send an email (plain text + HTML) | `to`, `subject`, `body`, `cc?`, `bcc?` |
| `modify_email` | Add or remove Gmail labels on a message | `id`, `addLabels[]`, `removeLabels[]` |

**Gmail search query examples:**
```
from:someone@example.com
subject:invoice
is:unread after:2024/01/01
```

### šŸ“… Google Calendar

| Tool | Description | Key Parameters |
|---|---|---|
| `list_events` | List upcoming calendar events | `maxResults` (1–250, default 10), `timeMin?`, `timeMax?` |
| `create_event` | Create a new calendar event | `summary`, `start`, `end`, `location?`, `description?`, `attendees[]?` |
| `update_event` | Update an existing calendar event (partial patch) | `eventId`, any of: `summary`, `location`, `description`, `start`, `end`, `attendees[]` |
| `delete_event` | Delete a calendar event | `eventId` |

> All datetime values must be valid **ISO 8601** strings (e.g., `2024-12-25T10:00:00Z`).

---

## Architecture

The server uses **stateful Streamable HTTP sessions** from the MCP SDK:

- Each new MCP `initialize` request creates a fresh server + transport instance
- Sessions are tracked in memory by `mcp-session-id` header
- A single authenticated `OAuth2` client is shared across Gmail and Calendar API calls within a session
- Input validation is handled via **Zod schemas** on all tool inputs

```
Client → POST /mcp (initialize) → creates session
Client → POST /mcp (tool call, mcp-session-id: <id>) → reuses session
```

---

## Testing

```bash
npm test                              # Run vitest unit tests
npx @anthropic-ai/mcp-inspector      # Interactive MCP tool testing UI
npm run test:smoke                    # Smoke test against running server
```

Connect MCP Inspector to `http://localhost:8080/mcp` to test tools interactively.

---

## Deployment

### MCPize (recommended)

```bash
mcpize deploy
```

Configured via [`mcpize.yaml`](./mcpize.yaml):
- **Runtime:** TypeScript
- **Build:** `npm ci && npm run build`
- **Start:** `node dist/index.js` (HTTP transport on `$PORT`)

### Docker

```bash
docker build -t mcp-gsuite .
docker run -p 8080:8080 \
  -e GOOGLE_CLIENT_ID=... \
  -e GOOGLE_CLIENT_SECRET=... \
  -e GOOGLE_REFRESH_TOKEN=... \
  mcp-gsuite
```

### Google Cloud Run

The server handles `SIGTERM` gracefully for zero-downtime Cloud Run deploys.

---

## Tech Stack

| Package | Version | Purpose |
|---|---|---|
| `@modelcontextprotocol/sdk` | ^1.23.0 | MCP server + Streamable HTTP transport |
| `googleapis` | ^134.0.0 | Gmail & Calendar API client |
| `express` | ^5.1.0 | HTTP server framework |
| `zod` | ^4.0.0 | Runtime input validation |
| `dotenv` | ^16.4.5 | Environment variable loading |
| `chalk` | ^5.4.1 | Colored terminal output |
| `tsx` | ^4.19.4 | TypeScript dev runner (hot reload) |

**Node.js requirement:** `>=20.0.0`

---

## License

MIT