Skip to main content
Glama
nikhil1102-ai

MCP-Style Server — Google Docs & Gmail Integration

README.md
# MCP-Style Server — Google Docs, Drive & Gmail Integration

A lightweight Python server that exposes Google Docs, Drive and Gmail operations as **MCP-style tool endpoints** via FastAPI. Every action requires explicit **human approval** in the terminal before execution.

---

## Features

- **Append to Google Doc** — Append text content to any Google Doc you have access to.
- **Upload to Google Drive** — Upload a file (e.g. the weekly pulse PDF) and get a shareable link.
- **Create Gmail Draft** — Create a draft email in your Gmail account.
- **Human-in-the-Loop** — Every action is printed to the terminal and requires `y/n` approval.
- **OAuth 2.0** — Secure authentication via Google's OAuth 2.0 flow.

---

## Prerequisites

- **Python 3.9+**
- **Google Cloud Project** with the following APIs enabled:
  - Google Docs API
  - Gmail API
- **OAuth 2.0 Client Credentials** (`credentials.json`) downloaded from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials).

---

## Setup

### 1. Clone the repository

```bash
git clone <repo-url>
cd MCP_server
```

### 2. Create a virtual environment (recommended)

```bash
python -m venv venv
# Windows
venv\Scripts\activate
# macOS/Linux
source venv/bin/activate
```

### 3. Install dependencies

```bash
pip install -r requirements.txt
```

### 4. Add your credentials

Place your Google OAuth client secrets file in the project root. The server is pre-configured to look for the credentials file by its downloaded name. If you renamed it, update the `CREDENTIALS_FILE` path in `auth.py`.

### 5. Run the server

```bash
python server.py
```

On the **first run**, a browser window will open asking you to sign in and grant permissions. After approval, a `token.json` file is created and subsequent runs will not require browser login.

---

## Usage

### Append to a Google Doc

```bash
curl -X POST http://127.0.0.1:8000/append_to_doc \
  -H "Content-Type: application/json" \
  -d '{"doc_id": "YOUR_DOC_ID", "content": "Hello from MCP server!"}'
```

Check the **terminal running the server** — you'll see:

```
============================================================
  ACTION: append_to_doc
============================================================
{
  "doc_id": "YOUR_DOC_ID",
  "content": "Hello from MCP server!"
}
============================================================
Approve? (y/n):
```

Type `y` to approve and execute, or `n` to reject.

### Create a Gmail Draft

```bash
curl -X POST http://127.0.0.1:8000/create_email_draft \
  -H "Content-Type: application/json" \
  -d '{"to": "recipient@example.com", "subject": "Test", "body": "Hello!"}'
```

---

## API Reference

### `POST /append_to_doc`

| Field   | Type   | Description                        |
|---------|--------|------------------------------------|
| doc_id  | string | Google Doc ID (from the URL)       |
| content | string | Text content to append             |

**Response (200):**
```json
{ "status": "success", "message": "Content appended to document.", "doc_id": "..." }
```

### `POST /upload_to_drive`

| Field       | Type   | Description                                          |
|-------------|--------|------------------------------------------------------|
| filename    | string | Destination filename, e.g. `pulse_2026-09-07.pdf`    |
| content_b64 | string | File contents, base64-encoded                        |
| mime_type   | string | Optional. Defaults to `application/pdf`              |
| folder_id   | string | Optional. Overrides the `DRIVE_FOLDER_ID` env var     |

**Response (200):**
```json
{
  "status": "success",
  "message": "File uploaded to Drive.",
  "file_id": "...",
  "file_url": "https://drive.google.com/file/d/.../view",
  "shared": true,
  "folder_id": "..."
}
```

Set `DRIVE_FOLDER_ID` to keep every upload in one folder instead of the Drive
root. The ID is the last path segment of the folder's URL:

```
https://drive.google.com/drive/folders/1V4-pvlYj-pQPLC3rgSsnAcEH9jirQhy6
                                       ^--------- this is DRIVE_FOLDER_ID
```

A folder created by hand in the Drive UI works fine with the `drive.file`
scope, as long as the authenticated account can access it.

Uploaded files are given **"anyone with the link can view"** access so that
email recipients can open them without requesting permission. Set
`LINK_SHARING_ENABLED = False` in `drive_tool.py` to keep uploads private.

The base64 body is summarised (not dumped) in the terminal approval prompt.

### `POST /create_email_draft`

| Field     | Type   | Description                        |
|-----------|--------|------------------------------------|
| to        | string | Recipient email address            |
| subject   | string | Email subject line                 |
| body      | string | Email body text (plain-text)       |
| body_html | string | Optional rich HTML body            |

**Response (200):**
```json
{ "status": "success", "message": "Draft created successfully.", "draft_id": "..." }
```

### Error Responses

| Status | Meaning                    |
|--------|----------------------------|
| 400    | Malformed payload (e.g. invalid base64) |
| 403    | Action rejected by operator|
| 401    | Authentication error       |
| 422    | Invalid request payload    |
| 502    | Google API error           |
| 500    | Internal server error      |

---

## Project Structure

```
MCP_server/
├── server.py          → FastAPI app with tool endpoints
├── auth.py            → Google OAuth 2.0 authentication
├── docs_tool.py       → Google Docs tool (append content)
├── drive_tool.py      → Google Drive tool (upload file, share link)
├── gmail_tool.py      → Gmail tool (create draft)
├── requirements.txt   → Python dependencies
├── Procfile           → Railway deployment config
├── README.md          → This file
├── .gitignore         → Git ignore rules
└── docs/
    ├── problemStatement.md
    └── architecture.md
```

---

## Deploy to Railway

### Prerequisites

- A [Railway](https://railway.app/) account (free tier available)
- The project pushed to GitHub (see Setup above)
- Google OAuth `token.json` generated locally (run the server once locally first)

### Step 1: Create a New Project on Railway

1. Go to [railway.app](https://railway.app/) and log in.
2. Click **"New Project"** → **"Deploy from GitHub Repo"**.
3. Select your repository: `nikhil1102-ai/custom_mcp_server`.
4. Railway will auto-detect the `Procfile` and configure the start command.

### Step 2: Configure Environment Variables

In your Railway project dashboard, go to **Variables** and add:

| Variable | Value | Description |
|---|---|---|
| `AUTO_APPROVE` | `true` | Auto-approve actions (no terminal in cloud) |
| `HOST` | `0.0.0.0` | Bind to all interfaces (required by Railway) |
| `GOOGLE_TOKEN_JSON` | *(contents of token.json)* | OAuth token for Google APIs (see Step 3) |

> **Note:** Railway automatically sets the `PORT` variable — you do **not** need to add it manually.

### Step 3: Set Up Google OAuth Token

Since Railway has no browser for the OAuth consent flow, you must generate `token.json` locally first:

1. **Run the server locally** once:
   ```bash
   python server.py
   ```
2. **Make any API call** (e.g., append to doc) — this triggers the OAuth browser flow.
3. **Complete the consent** in your browser — `token.json` is created.
4. **Copy the token contents:**
   ```bash
   # Windows (PowerShell)
   Get-Content token.json

   # macOS/Linux
   cat token.json
   ```
5. **Paste the entire JSON** into the `GOOGLE_TOKEN_JSON` Railway variable.

> **Alternative:** You can also upload `token.json` and `credentials.json` as Railway volume mounts if your plan supports it.

### Step 4: Handle Credentials in Production

`auth.py` already handles this automatically — on startup it checks for two environment variables and writes the files to disk if they don't exist:

| Variable | Value |
|---|---|
| `GOOGLE_CREDENTIALS_JSON` | Entire contents of `credentials.json` |
| `GOOGLE_TOKEN_JSON` | Entire contents of `token.json` |

Set both in your Railway **Variables** tab. No manual code changes needed.

### Step 5: Deploy

1. **Push your code** to GitHub:
   ```bash
   git add .
   git commit -m "Add Railway deployment support"
   git push origin main
   ```
2. Railway will **auto-deploy** from the `main` branch.
3. Once deployed, Railway provides a **public URL** like:
   ```
   https://your-app-name.up.railway.app
   ```

### Step 6: Test the Deployed API

```bash
# Replace with your Railway URL
curl -X POST https://your-app-name.up.railway.app/append_to_doc \
  -H "Content-Type: application/json" \
  -d '{"doc_id": "YOUR_DOC_ID", "content": "Hello from Railway!"}'

curl -X POST https://your-app-name.up.railway.app/create_email_draft \
  -H "Content-Type: application/json" \
  -d '{"to": "test@example.com", "subject": "Test", "body": "Deployed!"}'
```

### Railway Environment Summary

| Setting | Value |
|---|---|
| **Start Command** | `uvicorn server:app --host 0.0.0.0 --port $PORT` (from Procfile) |
| **Build Command** | `pip install -r requirements.txt` (auto-detected) |
| **Port** | Auto-assigned by Railway via `$PORT` |
| **Health Check** | `GET /docs` (FastAPI Swagger UI) |

---

## Security Notes

- `credentials.json` and `token.json` are excluded from version control via `.gitignore`.
- The server binds to `127.0.0.1` (localhost) by default — it is **not** exposed to the network.
- On Railway, set `AUTO_APPROVE=true` since there is no terminal for manual approval.
- Only `documents`, `gmail.compose` and `drive.file` OAuth scopes are requested.
  `drive.file` grants access solely to files this app creates — never the
  user's existing Drive contents.
- **Changing `SCOPES` invalidates an existing `token.json`.** Delete it, re-run
  the consent flow locally, then update `GOOGLE_TOKEN_JSON` on Railway. A stale
  token fails with 403 `insufficient authentication scopes`.
- For production, consider adding API key authentication to protect your endpoints.