gdocs-daily-mcp-remote
Notes
Board 1
-
gdocs-daily-mcp-remote
board-gdocs-daily-mcp-remotegdocs-daily-mcp-remote
HTTP MCP remote server that reads Google Docs daily notes, deployed on the pal-e cluster. First MCP remote to reach production. Enables conversational daily note review from Claude.ai iOS.
User Stories
Key Story Note Success Metric mobile-daily-reviewstory-gdocs-daily-mobile-daily-review Open Claude.ai on iPhone, ask about today's note, get accurate summary in <5s Architecture
Three-note architecture triplet:
arch:mcp-server— Domain Model — Google Docs entities, OAuth session, MCP tool surfacearch:oauth-proxy— Data Flow — 3-party OAuth dance, daily note retrieval sequence, not-found handlingarch:k8s-deploy— Deployment — Tailscale funnel, k8s namespace, Harbor/ArgoCD pipeline, onboarding checklist
Key components for
arch:labels on tickets:arch: label Component Diagram arch:mcp-serverFastMCP + Starlette server, tool definitions Domain + Data Flow arch:oauth-proxymcp-remote-auth integration, token store Data Flow arch:google-docs-apiDrive search + Docs content extraction Domain + Data Flow arch:k8s-deployNamespace, deployment, PVC, secrets, funnel Deployment arch:ci-pipelineWoodpecker, Kaniko, Harbor, ArgoCD updater Deployment arch:terraformpal-e-services onboarding entry Deployment Acceptance Criteria
- MCP remote server responds to Claude.ai Streamable HTTP MCP requests at
https://gdocs-daily-mcp-remote.tail5b443a.ts.net - OAuth flow grants Google Docs read access and persists refresh token across pod restarts
get_daily_note()tool returns today's note content;get_daily_note(date="MM-DD-YYYY")returns a specific date's note- Missing notes return a clear message, not an error
- Full GitOps pipeline: push to Forgejo main → Woodpecker → Harbor → ArgoCD → running pod
- Claude.ai iOS can be configured with the MCP URL and use the tool without additional setup
Kanban
Board items below. Tickets are Forgejo issues scoped to <5 minutes of agent work.
Architecture 3
-
Deployment: gdocs-daily-mcp-remote
arch-deployment-gdocs-daily-mcp-remoteDeployment: gdocs-daily-mcp-remote
Diagram
graph TB subgraph Internet ClaudeAI["Claude.ai iOS / Web"] GoogleAPI["Google APIs (Drive + Docs)"] end subgraph Tailscale["Tailscale Network"] Funnel["Tailscale Funnelgdocs-daily-mcp-remote.tail5b443a.ts.netHTTPS termination"] end subgraph k3s["k3s Cluster (archbox)"] subgraph ns["namespace: gdocs-daily-mcp-remote"] Ingress["Ingressclass: tailscalefunnel: true"] SVC["ServiceClusterIP:8000"] Deploy["Deploymentgdocs-daily-mcp-remote1 replica, Recreate strategy"] PVC["PVCgdocs-daily-dataencrypted token store"] Secret["Secretgdocs-daily-mcp-secretsOAuth creds, session key"] end subgraph infra["Platform Infrastructure"] Harbor["Harbor Registryharbor.tail5b443a.ts.net"] ArgoCD["ArgoCDImage Updater polls Harbor"] Woodpecker["Woodpecker CIKaniko builds"] end end subgraph Forgejo["Forgejo"] Repo["forgejo_admin/gdocs-daily-mcp-remote"] Deploys["forgejo_admin/pal-e-deploymentsoverlays/gdocs-daily-mcp-remote/prod/"] end ClaudeAI -->|"HTTPS (Streamable HTTP MCP)"| Funnel Funnel --> Ingress Ingress --> SVC SVC --> Deploy Deploy -->|"mount"| PVC Deploy -->|"env from"| Secret Deploy -->|"HTTPS"| GoogleAPI Repo -->|"push to main"| Woodpecker Woodpecker -->|"Kaniko build"| Harbor Harbor -->|"Image Updater detects new tag"| ArgoCD ArgoCD -->|"writes .argocd-source-*"| Deploys ArgoCD -->|"syncs k8s manifests"| DeployComponents
Component Purpose Notes Tailscale Funnel Public HTTPS ingress Terminates TLS. Hostname: gdocs-daily-mcp-remote.tail5b443a.ts.net. No cert-manager needed — Tailscale handles certs.Ingress (tailscale class) k8s Ingress resource with funnel annotation Created by pal-e-services Terraform. tailscale.com/funnel: "true". Auth required perfeedback_funnel_requires_auth.Service ClusterIP routing to pod Port 8000. No LoadBalancer needed — Tailscale operator handles external access. Deployment Single-replica pod running Uvicorn Recreate strategy (token file is stateful). Image from Harbor. Resources: 32Mi request, 256Mi limit. PVC Persistent encrypted token storage Stores Fernet-encrypted tokens.json. Survives pod restarts. local-path provisioner. Secret OAuth credentials and session key Contains: Google OAuth client_id/secret, session_secret, onboard_secret, base_url. Harbor Container image registry Project: gdocs-daily-mcp-remote. CI robot pushes, pull robot for imagePullSecrets.ArgoCD GitOps deployment Watches pal-e-deployments overlay. Image Updater auto-detects new tags (regex: ^[0-9a-f]{7,40}$).Woodpecker CI Build pipeline Triggered on push to main. Kaniko builds Dockerfile.k8s, pushes to Harbor with git SHA tag. Forgejo repo Source code forgejo_admin/gdocs-daily-mcp-remote. Contains server code + .woodpecker.yaml.pal-e-deployments overlay Kustomize overlay for prod Inherits from bases/standard. Patches deployment name, image, probes, secrets. Key Decisions
- Tailscale funnel, not Traefik. Platform convention — all public ingress goes through Tailscale funnels. No cert-manager, no separate TLS config. Funnel requires auth (OAuth satisfies this — unauthenticated requests get redirected to Google consent).
- Single replica with Recreate. Token store is a local encrypted file. No shared state backend. Scaling would require migrating to Redis or a database — unnecessary for a single-user MCP server.
- Kustomize overlay in pal-e-deployments, not in-repo. Following the established pattern — k8s manifests for ArgoCD live in the shared deployments repo. The service repo contains only the app code and CI config.
- Service onboarding via pal-e-services Terraform. One entry in k3s.tfvars creates: Harbor project + robots, k8s namespace, image pull secret, ArgoCD app, Tailscale funnel ingress. Then
tofu apply. - Google OAuth web credentials. New OAuth client in the same GCP project (233869493443) with redirect URI
https://gdocs-daily-mcp-remote.tail5b443a.ts.net/oauth/callback. Scopes: documents.readonly + drive.readonly.
Onboarding Checklist
- Create Forgejo repo
forgejo_admin/gdocs-daily-mcp-remote - Create Google OAuth web credentials in GCP console (new client, same project)
- Add service entry to
pal-e-services/terraform/k3s.tfvars, runtofu apply -lock=false - Create kustomize overlay in
pal-e-deployments/overlays/gdocs-daily-mcp-remote/prod/ - Create k8s Secret with OAuth creds + session secret
- Push initial code → Woodpecker builds → Harbor → ArgoCD deploys
- Run onboarding flow to pre-authorize Google account
- Add MCP remote URL to Claude.ai settings
Related
arch-domain-gdocs-daily-mcp-remote— entity modelarch-dataflow-gdocs-daily-mcp-remote— runtime sequence diagramsstory-gdocs-daily-mobile-daily-review— user storyservice-onboarding-sop— SOP for adding new services to the platform
-
Data Flow: gdocs-daily-mcp-remote
arch-dataflow-gdocs-daily-mcp-remoteData Flow: gdocs-daily-mcp-remote
Diagram
Flow 1: First-Time OAuth Authorization
sequenceDiagram participant User as Lucas (iPhone) participant Claude as Claude.ai iOS participant MCP as gdocs-daily-mcp-remote participant Auth as mcp-remote-auth participant Google as Google OAuth Claude->>MCP: GET /oauth/authorize (client_id, redirect_uri, scopes) MCP->>Auth: Create pending auth state Auth->>MCP: Upstream authorize URL MCP->>User: Redirect to Google consent screen User->>Google: Grant documents.readonly + drive.readonly Google->>MCP: GET /oauth/callback (code, state) MCP->>Auth: Exchange code for refresh_token Auth->>Auth: Encrypt and store refresh_token in TokenStore Auth->>MCP: Our authorization code MCP->>Claude: Redirect with auth code Claude->>MCP: POST /oauth/token (exchange code for access_token) MCP->>Claude: MCP access_token (long-lived, 1 year) Note over Claude,MCP: Claude stores token — all future requests use itFlow 2: Daily Note Retrieval (Happy Path)
sequenceDiagram participant User as Lucas (iPhone) participant Claude as Claude.ai iOS participant MCP as gdocs-daily-mcp-remote participant Drive as Google Drive API participant Docs as Google Docs API User->>Claude: "What's on my daily note today?" Claude->>MCP: tool call: get_daily_note() MCP->>MCP: Resolve current date → "04-12-2026" MCP->>MCP: Verify OAuth token, refresh if needed MCP->>Drive: files.list(q: name="04-12-2026", mimeType=document) Drive->>MCP: [{documentId: "abc123", title: "04-12-2026"}] MCP->>Docs: documents.get(documentId: "abc123") Docs->>MCP: Document JSON (body, paragraphs, text runs) MCP->>MCP: Extract plain text from structured content MCP->>Claude: Tool result: "Here's what's in your 04-12-2026 note: ..." Claude->>User: Conversational summary of daily noteFlow 3: Note Not Found
sequenceDiagram participant Claude as Claude.ai iOS participant MCP as gdocs-daily-mcp-remote participant Drive as Google Drive API Claude->>MCP: tool call: get_daily_note(date="04-13-2026") MCP->>Drive: files.list(q: name="04-13-2026", mimeType=document) Drive->>MCP: [] (empty results) MCP->>Claude: Tool result: "No daily note found for 04-13-2026"Components
Component Purpose Connection Claude.ai iOS User interface — sends MCP tool calls HTTPS to MCP remote via Tailscale funnel gdocs-daily-mcp-remote MCP server — translates tool calls to Google API requests Starlette/Uvicorn on port 8000 in k8s mcp-remote-auth OAuth proxy — manages 3-party token dance Library imported by the server (not a separate service) Google Drive API File search — finds documents by title HTTPS to googleapis.com, authenticated via OAuth access_token Google Docs API Document retrieval — fetches document content HTTPS to googleapis.com, authenticated via OAuth access_token TokenStore (PVC) Encrypted token persistence Local file on PersistentVolumeClaim at /app/data Key Decisions
- Two Google APIs, not one. Google Drive API for search (files.list can filter by name and mimeType), Google Docs API for content retrieval (documents.get returns the structured body). Drive is the index, Docs is the content store.
- mcp-remote-auth is a library, not a sidecar. It's pip-installed and runs in-process. No inter-service communication for auth — it's all in the same pod.
- Token refresh is transparent. The
setup_client_for_requestcallback refreshes the Google access_token using the stored refresh_token before every tool call. Claude never sees token expiry. - MCP access_token is long-lived (1 year). The token Claude.ai stores doesn't expire frequently — it's our own token, not Google's. Google's refresh_token is managed server-side.
Related
arch-domain-gdocs-daily-mcp-remote— entity modelarch-deployment-gdocs-daily-mcp-remote— infrastructure topologystory-gdocs-daily-mobile-daily-review— user story
-
Domain Model: gdocs-daily-mcp-remote
arch-domain-gdocs-daily-mcp-remoteDomain Model: gdocs-daily-mcp-remote
Diagram
erDiagram GOOGLE_ACCOUNT { string email PK string display_name } GOOGLE_DOCS_FOLDER { string folder_id PK string name string parent_id FK } DAILY_NOTE { string document_id PK string title "MM-DD-YYYY format" date note_date "parsed from title" string body_text "extracted plain text" json structured_content "Google Docs JSON" } OAUTH_SESSION { string client_id PK string access_token string refresh_token "Google refresh_token — long-lived" datetime expires_at } MCP_TOOL { string name PK "get_daily_note" string description } GOOGLE_ACCOUNT ||--o{ GOOGLE_DOCS_FOLDER : "owns" GOOGLE_DOCS_FOLDER ||--o{ DAILY_NOTE : "contains" OAUTH_SESSION ||--|| GOOGLE_ACCOUNT : "authenticates" MCP_TOOL ||--o{ DAILY_NOTE : "reads" MCP_TOOL ||--|| OAUTH_SESSION : "uses"Components
Component Purpose Notes GOOGLE_ACCOUNT The Google account whose Docs we read Lucas's personal account. Authenticated via OAuth 2.0 web flow. GOOGLE_DOCS_FOLDER Optional folder scope for daily notes If daily notes live in a specific folder, we can scope searches. Otherwise we search by title across all Docs. DAILY_NOTE A single daily note document Named MM-DD-YYYY. The Google Docs API returns structured JSON (paragraphs, lists, tables). We extract plain text for Claude. OAUTH_SESSION Per-user OAuth token managed by mcp-remote-auth Encrypted in TokenStore on PVC. Refresh token survives pod restarts. Access token refreshed automatically. MCP_TOOL The MCP tool exposed to Claude.ai Primary tool: get_daily_note(date?). Defaults to today. Returns document content as text.Key Decisions
- Title-based lookup, not folder ID. Daily notes are found by searching for documents titled MM-DD-YYYY using the Google Drive API (files.list with q filter). This avoids hardcoding a folder ID and works even if notes move folders.
- Read-only scope. We only need
documents.readonlyanddrive.readonly(for search). No write operations — this is a consumption tool. - Plain text extraction. Google Docs API returns a deeply nested JSON structure (Body → Content → Paragraph → Elements → TextRun). We flatten to plain text with basic formatting preserved (headers, lists, bold). Claude doesn't need the raw JSON.
- No inner MCP package. Unlike gmail-mcp-remote (which wraps gmail-mcp), we build the Google Docs integration directly in the remote server. The API surface is small enough (one tool) that a separate package would be over-engineering.
- Date defaulting. When no date is provided, the tool uses the server's current date (UTC or configured timezone). This enables "what's on my note today?" without parameters.
Related
arch-dataflow-gdocs-daily-mcp-remote— runtime flow from Claude.ai to Google Docs APIarch-deployment-gdocs-daily-mcp-remote— k8s deployment topologystory-gdocs-daily-mobile-daily-review— the user story this serves
User Story 1
-
Mobile Daily Note Review
story-gdocs-daily-mobile-daily-reviewstory: Mobile Daily Note Review
Role
Lucas (platform owner, daily note author)
Key
mobile-daily-review
Want
As Lucas, I want to ask Claude.ai on my phone about the contents of today's Google Docs daily note without opening Google Docs or copy-pasting
So That
So that I can have a conversational review of my daily notes from anywhere — while walking, driving, or between tasks — using Claude.ai's iOS app as the interface
Acceptance Criteria
- Claude.ai iOS connects to the MCP remote server automatically when configured
- MCP server determines the current date and resolves the daily note named MM-DD-YYYY
- Claude can read and discuss the full content of today's daily note
- If no daily note exists for today, the server returns a clear message (not an error)
- OAuth flow works end-to-end: first-time auth via browser, then persistent access via refresh token
- Server is accessible from Claude.ai over public internet via Tailscale funnel with HTTPS
- Response latency under 3 seconds for fetching a typical daily note
Success Metric
Lucas can open Claude.ai on iPhone, ask "what's on my daily note today?", and get an accurate conversational summary within 5 seconds — with zero manual steps beyond the initial OAuth grant.
Related Architecture
arch-deployment-gdocs-daily-mcp-remote— k8s deployment with Tailscale funnelarch-dataflow-gdocs-daily-mcp-remote— Claude.ai → MCP remote → Google Docs API flowarch-domain-gdocs-daily-mcp-remote— daily note document model
Related
project-pal-e-platform— parent project (bootstrap repo hosts deployment infra)board-pal-e-platform— project boardmcp-remote-auth— shared OAuth proxy library (existing infrastructure)