Google Photos for ChatGPT web UI
by jroth1111
README.md
# Google Photos for ChatGPT web UI
A private, read-only Google Photos MCP server and gallery hosted on **ChatGPT Sites**. Search existing photos, browse library/archive/albums, retrieve metadata and return actual image pixels to ChatGPT. Video views are explicitly labelled poster frames, not footage.
This is source code for deploying **your own instance**, not a shared public photo service. The code is public; your Site, accounts, encrypted session state and cached images must remain private. It uses Google's unofficial web RPCs—not the restricted official Library/Picker APIs. Upstream changes and account/session revocation can break access.
## Architecture
```text
ChatGPT web conversation → managed Sites OAuth → /mcp
Private gallery → same-origin authenticated APIs
│
├─ D1: encrypted session jars, cursors, metadata, leases
├─ R2: private image previews
└─ Google Photos web RPCs + separately scoped image-CDN session
```
ChatGPT authentication and Google authentication are separate. The managed Sites connection supplies the verified caller; Google session cookies stay server-side and are encrypted. No laptop or VPS is needed for ongoing server requests after setup. Import still requires a browser currently signed into your Google account.
| Tool | Purpose |
|---|---|
| `photos_status` | Sanitized connection, account and coverage status |
| `photos_search` | Google's search with cursor pagination |
| `photos_list` | Library, archive, combined or album records |
| `photos_albums` | Paginated album metadata |
| `photos_get` | Metadata for up to 10 IDs |
| `photos_view` | Actual image blocks for up to 2 IDs; labelled video posters |
Follow returned cursors. A first page, empty page or successful HTTP response is not proof of complete library coverage. Image previews are bounded to approximately 320 px and 350 KB each. No original downloads, video playback, write/delete operations, Locked Folder or trash support is promised.
## Install and validate
Requires Node.js **22.13+**, npm, ChatGPT Sites with D1/R2 and native MCP support, and permission to connect your own app. Availability depends on your account/workspace.
```sh
git clone https://github.com/jroth1111/chatgpt-web-ui-google-photos.git
cd chatgpt-web-ui-google-photos
npm ci
npm run check:public
npm test
npm run check
npm run build
```
The tests use synthetic fixtures, local SQLite and mocked Google responses. They do not test your credentials or prove Google accepts your hosted network. The standard Sites starter's local preview may use mock identity: **never expose that development server publicly**.
## Deploy through ChatGPT Sites
**Git push and npm build do not deploy a Site.** Use ChatGPT's Sites workflow to create a new private project from this source and inspect the supported starter/build contract.
1. Provision logical D1 binding `DB`, R2 binding `BUCKET`, and MCP capability. The source-only [.openai/hosting.json](.openai/hosting.json) intentionally contains no project ID.
2. Apply every migration in `drizzle/` in filename order.
3. Set `GOOGLE_EXPECTED_EMAIL` to the exact account to permit and `GOOGLE_ACCOUNT_INDEX` to a hint between 0 and 4.
4. Configure `SESSION_KEY_V1` as a **secret** containing a random 32-byte AES key encoded as base64. Generate it securely, never commit/share it, and retain it across redeployments. Changing it without migrating/reimporting the encrypted jars makes them unreadable.
5. Save/build and deploy through Sites. Keep the audience owner-only; do not enable public access just because this GitHub repository is public.
6. Register/connect the native Sites MCP app and approve the ordinary connection. A browser page login and an MCP connection are separate.
7. Perform the session imports below, then test real images in a fresh ChatGPT conversation.
[.env.example](.env.example) documents non-secret configuration and the empty secret placeholder. The server fails closed if the expected account is absent. One expected Google account is configured per deployment; state is additionally scoped to the native caller identity.
## Connect without a browser helper
Open the private gallery while signed into ChatGPT. In your normal browser, sign into the intended Google Photos account, open Developer Tools → Network, select a request to `photos.google.com`, and **Copy as cURL (bash)**. Paste it **only into the gallery's primary session field** and choose **Verify and connect**.
The importer parses text, never executes cURL, never replays its copied URL/body/tokens, rejects executable/file/ambiguous inputs, probes only fixed Photos account paths, verifies your configured email and atomically stores the resolved index and encrypted cookie jar. Failure leaves the previous connection unchanged. Pasted text is masked by default and cleared on submission.
### Images need a separately scoped session
Google's `photos.fife.usercontent.google.com` image CDN may use different host-only OSID cookies from `photos.google.com`. Metadata can work while previews require a supplementary media session.
In Google Photos, open a photo and find its image request to the **exact CDN host** in Network. Copy that request as cURL and use **Supplemental image session** in the private gallery. Use the same browser login as the primary import. The importer checks stable SID binding, retains primary cookies, validates the expected Photos identity, and merges only CDN-scoped cookies. Foreign-session input is rejected before bootstrap. Session storage is not by itself proof that pixels are accessible: test `photos_view` afterward.
Use a native ChatGPT prompt such as:
> Use Google Photos Bridge to find a photo and describe the actual returned image.
The app name may differ after you create your instance. Select/mention the connected app if your client does not load it automatically. Ordinary Chat mode was exercised in the original hosted implementation; availability in Work mode or other clients must be verified separately.
## Security, recovery and limits
- cURL/cookies can grant sensitive Google account access. Never paste them into ChatGPT, GitHub issues, logs or public forms. See [SECURITY.md](SECURITY.md).
- Google can expire either service session. `MEDIA_SESSION_REQUIRED` is not a reason to redo ChatGPT OAuth or discard a working metadata session.
- `MEDIA_SESSION_SOURCE_MISMATCH`: refresh the primary import from the same browser login before supplementary import.
- Rate limits, busy leases, stale cursors and transient upstream errors are bounded and surfaced. The gallery queues thumbnails instead of silently dropping lease conflicts.
- Private R2 objects are served only after caller and upstream record authorization. Do not publish buckets or signed Google URLs.
- No unlimited throughput, perpetual login or exhaustive shared/partner/photo coverage guarantee is made.
- Keep backups of the database, encrypted objects and the secret through your private operational process; none belongs in this repository.
## Licence and provenance
MIT. Third-party notices are in [ATTRIBUTION.md](ATTRIBUTION.md). All fixtures are synthetic. Distribution packaging follows [chatgpt-web-ui-youtube-transcripts](https://github.com/jroth1111/chatgpt-web-ui-youtube-transcripts); this project does not include its VPS acquisition worker.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues