gdocs-daily-mcp-remote

gdocs-daily-mcp-remote forgejo

Notes

Board 1
  • gdocs-daily-mcp-remote board-gdocs-daily-mcp-remote

    gdocs-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-review story-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-serverDomain Model — Google Docs entities, OAuth session, MCP tool surface
    • arch:oauth-proxyData Flow — 3-party OAuth dance, daily note retrieval sequence, not-found handling
    • arch:k8s-deployDeployment — Tailscale funnel, k8s namespace, Harbor/ArgoCD pipeline, onboarding checklist

    Key components for arch: labels on tickets:

    arch: label Component Diagram
    arch:mcp-server FastMCP + Starlette server, tool definitions Domain + Data Flow
    arch:oauth-proxy mcp-remote-auth integration, token store Data Flow
    arch:google-docs-api Drive search + Docs content extraction Domain + Data Flow
    arch:k8s-deploy Namespace, deployment, PVC, secrets, funnel Deployment
    arch:ci-pipeline Woodpecker, Kaniko, Harbor, ArgoCD updater Deployment
    arch:terraform pal-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-remote

    Deployment: 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"| Deploy
    

    Components

    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 per feedback_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

    1. Create Forgejo repo forgejo_admin/gdocs-daily-mcp-remote
    2. Create Google OAuth web credentials in GCP console (new client, same project)
    3. Add service entry to pal-e-services/terraform/k3s.tfvars, run tofu apply -lock=false
    4. Create kustomize overlay in pal-e-deployments/overlays/gdocs-daily-mcp-remote/prod/
    5. Create k8s Secret with OAuth creds + session secret
    6. Push initial code → Woodpecker builds → Harbor → ArgoCD deploys
    7. Run onboarding flow to pre-authorize Google account
    8. Add MCP remote URL to Claude.ai settings
    • arch-domain-gdocs-daily-mcp-remote — entity model
    • arch-dataflow-gdocs-daily-mcp-remote — runtime sequence diagrams
    • story-gdocs-daily-mobile-daily-review — user story
    • service-onboarding-sop — SOP for adding new services to the platform
  • Data Flow: gdocs-daily-mcp-remote arch-dataflow-gdocs-daily-mcp-remote

    Data 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 it
    

    Flow 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 note
    

    Flow 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_request callback 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.
    • arch-domain-gdocs-daily-mcp-remote — entity model
    • arch-deployment-gdocs-daily-mcp-remote — infrastructure topology
    • story-gdocs-daily-mobile-daily-review — user story
  • Domain Model: gdocs-daily-mcp-remote arch-domain-gdocs-daily-mcp-remote

    Domain 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.readonly and drive.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.
    • arch-dataflow-gdocs-daily-mcp-remote — runtime flow from Claude.ai to Google Docs API
    • arch-deployment-gdocs-daily-mcp-remote — k8s deployment topology
    • story-gdocs-daily-mobile-daily-review — the user story this serves
User Story 1
  • Mobile Daily Note Review story-gdocs-daily-mobile-daily-review

    story: 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.

    • arch-deployment-gdocs-daily-mcp-remote — k8s deployment with Tailscale funnel
    • arch-dataflow-gdocs-daily-mcp-remote — Claude.ai → MCP remote → Google Docs API flow
    • arch-domain-gdocs-daily-mcp-remote — daily note document model
    • project-pal-e-platform — parent project (bootstrap repo hosts deployment infra)
    • board-pal-e-platform — project board
    • mcp-remote-auth — shared OAuth proxy library (existing infrastructure)