Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REFRESH_TOKEN=
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,12 @@ wheels/

# Virtual environments
.venv
.venv-oauth/
oauth_consent.py
token.json
credentials.json
credentials.json
.env
.env.*
!.env.example
# Google OAuth client secrets (never commit)
client_secret_*.json
112 changes: 112 additions & 0 deletions deploy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Deploying `contacts.nlma.io`

Runbook for the NLMA Hostinger VPS (`178.16.141.166`), matching the
conventions used by the other `*.nlma.io` MCP servers.

```
client --TLS--> nginx (443) --proxy--> 127.0.0.1:8020 (MCP HTTP transport)
```

- Install path: `/opt/contacts-mcp`
- Internal port: `8020` (loopback only)
- Runtime: systemd + Python 3.12 venv (`User=root`, like sibling services)
- TLS: certbot on the VPS
- Auth: none at nginx by default (matches sibling MCPs). Add basic auth
or a bearer check in `contacts.nlma.io` if needed.

## Prerequisites

- DNS A record for `contacts.nlma.io` → `178.16.141.166`.
- Google OAuth: either `credentials.json` or `GOOGLE_CLIENT_ID` /
`GOOGLE_CLIENT_SECRET` / `GOOGLE_REFRESH_TOKEN` in `.env`.

## Provision

```bash
ssh root@178.16.141.166

mkdir -p /opt/contacts-mcp
cd /opt/contacts-mcp
git clone https://github.com/NextLevelManagementAdvisors/mcp-google-contacts-server.git .
git checkout deploy/contacts-nlma-io

python3.12 -m venv .venv
.venv/bin/pip install --upgrade pip
.venv/bin/pip install .

cp .env.example .env
# Edit .env with GOOGLE_CLIENT_ID / _SECRET / _REFRESH_TOKEN
# OR scp credentials.json to /opt/contacts-mcp/credentials.json
```

## systemd

```bash
cp /opt/contacts-mcp/deploy/systemd/contacts-mcp.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now contacts-mcp
systemctl status contacts-mcp
journalctl -u contacts-mcp -n 50 --no-pager

curl -fsS http://127.0.0.1:8020/ # sanity check
```

## nginx + TLS

```bash
cp /opt/contacts-mcp/deploy/nginx.conf.example /etc/nginx/sites-available/contacts.nlma.io
ln -s /etc/nginx/sites-available/contacts.nlma.io /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

certbot --nginx -d contacts.nlma.io

curl -fsS https://contacts.nlma.io/
```

## Update

```bash
cd /opt/contacts-mcp
git pull
.venv/bin/pip install .
systemctl restart contacts-mcp
```

## Rollback

```bash
cd /opt/contacts-mcp
git log --oneline -5
git checkout <previous-sha>
.venv/bin/pip install .
systemctl restart contacts-mcp
```

## Security notes

- MCP server binds loopback only; public access is nginx → proxy.
- `.env` and `credentials.json` are gitignored. Transfer out-of-band; never commit.
- If this hostname ever gets customer-facing traffic, add basic auth or a
bearer token check in the nginx config before that happens.

## Multi-tenant (per-user Google OAuth) mode

Set `AUTH_MODE=multi` to let any Google user sign in and query their own
contacts. Requires a **Web application** OAuth client (not Desktop) with
redirect URI `https://contacts.nlma.io/oauth/google/callback` and a
published OAuth consent screen (External, test users for launch).

Additional `.env` keys:

```
AUTH_MODE=multi
GOOGLE_WEB_CLIENT_ID=...
GOOGLE_WEB_CLIENT_SECRET=...
OAUTH_ISSUER_URL=https://contacts.nlma.io
GOOGLE_OAUTH_REDIRECT_URI=https://contacts.nlma.io/oauth/google/callback
# DB_PATH=/opt/contacts-mcp/data.db # optional override
```

Per-user refresh tokens land in `/opt/contacts-mcp/data.db`. Treat that
file like a secret: `chmod 600`, no world-readable backups. Remove a
user: `sqlite3 data.db "delete from users where google_email='x@y'; delete from access_tokens where google_email='x@y'; delete from refresh_tokens where google_email='x@y';"`
24 changes: 24 additions & 0 deletions deploy/nginx.conf.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# /etc/nginx/sites-available/contacts.nlma.io
# Matches the convention used by other *.nlma.io MCP servers.
# TLS stanzas are added by certbot on first run:
# sudo certbot --nginx -d contacts.nlma.io

server {
server_name contacts.nlma.io;
location /.well-known/acme-challenge/ { root /var/www/html; }
location / {
proxy_pass http://127.0.0.1:8020;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}

listen 80;
}
16 changes: 16 additions & 0 deletions deploy/systemd/contacts-mcp.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# /etc/systemd/system/contacts-mcp.service
[Unit]
Description=MCP Google Contacts Server
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/opt/contacts-mcp
EnvironmentFile=/opt/contacts-mcp/.env
ExecStart=/opt/contacts-mcp/.venv/bin/mcp-google-contacts --transport http --host 127.0.0.1 --port 8020 --credentials-file /opt/contacts-mcp/credentials.json
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
Loading