feat: initial gdocs-daily-mcp-remote codebase #1

Open
forgejo_admin wants to merge 0 commits from 288-initial-codebase into main

Summary

Initial codebase for gdocs-daily-mcp-remote -- a single-tool MCP remote server that reads Google Docs daily notes (named MM-DD-YYYY) via OAuth 2.0 over Streamable HTTP.

Changes

  • src/gdocs_daily_mcp_remote/server.py -- FastMCP instance with get_daily_note tool, Google OAuth config via mcp-remote-auth, ContextVar-based per-request access token injection, /metrics endpoint
  • src/gdocs_daily_mcp_remote/gdocs_client.py -- Google Docs/Drive API wrapper using httpx directly (no google-api-python-client). Searches Drive by title, extracts doc content as Markdown with heading/list/bold formatting
  • src/gdocs_daily_mcp_remote/__main__.py -- Entry point for python -m gdocs_daily_mcp_remote
  • pyproject.toml -- Package config with mcp-remote-auth, httpx, uvicorn dependencies
  • Dockerfile.k8s -- Container image following gmail-mcp-remote pattern
  • .woodpecker.yaml -- CI pipeline: ruff lint/format check + kaniko build-and-push
  • .env.example -- Environment variable template
  • requirements.txt -- Pip requirements mirror of pyproject.toml dependencies

Design Decisions

  • No client_patch.py / inner provider package -- Unlike gmail-mcp-remote which wraps a separate gmail-mcp package and monkey-patches get_client(), this server defines the MCP tool directly on the FastMCP instance. One tool does not justify a separate package.
  • ContextVar for access token -- The _setup_gdocs_client callback refreshes the Google access token from the stored refresh_token and sets it on a ContextVar. The tool reads it directly. No monkey-patching needed.
  • httpx over google-api-python-client -- Two API calls (Drive search + Docs get) do not justify pulling in the heavy google-api-python-client. Raw httpx keeps the image small and dependencies minimal.
  • Markdown extraction -- Converts Google Docs JSON structure to readable Markdown: headings as # levels, bullets as - with nesting indentation, bold as **.
  • Port 8003 -- gmail uses 8002, gcal uses 8001, so this defaults to 8003.

Test Plan

  • Verify ruff check/format passes (done locally)
  • Deploy to k8s, onboard a Google account, call get_daily_note tool
  • Verify date lookup works for today (Mountain Time) and explicit MM-DD-YYYY dates
  • Verify "not found" message for nonexistent dates

Review Checklist

  • Ruff lint passes
  • Ruff format passes
  • Follows gmail-mcp-remote / mcp-remote-auth patterns
  • No google-api-python-client dependency
  • Woodpecker CI pipeline configured
  • Dockerfile follows existing pattern

None -- new repo, no existing notes.

Closes forgejo_admin/pal-e-platform#288

## Summary Initial codebase for gdocs-daily-mcp-remote -- a single-tool MCP remote server that reads Google Docs daily notes (named MM-DD-YYYY) via OAuth 2.0 over Streamable HTTP. ## Changes - `src/gdocs_daily_mcp_remote/server.py` -- FastMCP instance with `get_daily_note` tool, Google OAuth config via mcp-remote-auth, ContextVar-based per-request access token injection, /metrics endpoint - `src/gdocs_daily_mcp_remote/gdocs_client.py` -- Google Docs/Drive API wrapper using httpx directly (no google-api-python-client). Searches Drive by title, extracts doc content as Markdown with heading/list/bold formatting - `src/gdocs_daily_mcp_remote/__main__.py` -- Entry point for `python -m gdocs_daily_mcp_remote` - `pyproject.toml` -- Package config with mcp-remote-auth, httpx, uvicorn dependencies - `Dockerfile.k8s` -- Container image following gmail-mcp-remote pattern - `.woodpecker.yaml` -- CI pipeline: ruff lint/format check + kaniko build-and-push - `.env.example` -- Environment variable template - `requirements.txt` -- Pip requirements mirror of pyproject.toml dependencies ## Design Decisions - **No client_patch.py / inner provider package** -- Unlike gmail-mcp-remote which wraps a separate gmail-mcp package and monkey-patches get_client(), this server defines the MCP tool directly on the FastMCP instance. One tool does not justify a separate package. - **ContextVar for access token** -- The `_setup_gdocs_client` callback refreshes the Google access token from the stored refresh_token and sets it on a ContextVar. The tool reads it directly. No monkey-patching needed. - **httpx over google-api-python-client** -- Two API calls (Drive search + Docs get) do not justify pulling in the heavy google-api-python-client. Raw httpx keeps the image small and dependencies minimal. - **Markdown extraction** -- Converts Google Docs JSON structure to readable Markdown: headings as `#` levels, bullets as `- ` with nesting indentation, bold as `**`. - **Port 8003** -- gmail uses 8002, gcal uses 8001, so this defaults to 8003. ## Test Plan - [ ] Verify ruff check/format passes (done locally) - [ ] Deploy to k8s, onboard a Google account, call `get_daily_note` tool - [ ] Verify date lookup works for today (Mountain Time) and explicit MM-DD-YYYY dates - [ ] Verify "not found" message for nonexistent dates ## Review Checklist - [x] Ruff lint passes - [x] Ruff format passes - [x] Follows gmail-mcp-remote / mcp-remote-auth patterns - [x] No google-api-python-client dependency - [x] Woodpecker CI pipeline configured - [x] Dockerfile follows existing pattern ## Related Notes None -- new repo, no existing notes. ## Related Closes forgejo_admin/pal-e-platform#288
Author
Owner

QA Review -- PR #1

Architecture & Pattern Compliance

The codebase correctly follows the mcp-remote-auth pattern established by gmail-mcp-remote and gcal-mcp-remote. Key pattern elements verified:

  • ProviderConfig with Google OAuth URLs, scopes, extra_authorize_params
  • TokenStore + OAuthProxyProvider wiring
  • configure_mcp_auth / configure_transport_security / register_standard_routes / register_onboarding_routes call sequence
  • build_app_with_middleware in the entry point
  • /metrics endpoint for ServiceMonitor
  • .woodpecker.yaml with test + build-and-push steps
  • Dockerfile.k8s following the same layer pattern

The decision to define the tool directly on the FastMCP instance (no inner provider package, no client_patch.py) is sound for a single-tool server.

Findings

1. [nit] server.py: mid-module imports with noqa comments

The from mcp_remote_auth import ... and from starlette ... imports are placed mid-module after the tool definition. This matches the gmail-mcp-remote pattern (which does it because it must apply the monkey-patch before importing mcp), but here there is no ordering constraint -- the tool uses a lazy import internally. These could be moved to the top of the file for cleaner style. However, since it matches the established pattern across the other remotes, this is acceptable as-is.

2. [nit] gdocs_client.py: title injection in Drive query

search_document_by_title builds the Drive API query with an f-string: name='{title}'. If a daily note title ever contained a single quote, this would break the query. For MM-DD-YYYY date strings this is not a real risk, but a note in the docstring or a simple escape (title.replace("'", "\\'")) would harden it.

3. [ok] ContextVar pattern

The ContextVar approach for per-request access token is clean and correct. The _setup_gdocs_client callback refreshes via synchronous httpx (matching the gmail-mcp-remote pattern where set_client_for_request also uses sync httpx.post), and the tool reads it async. This works because mcp-remote-auth calls setup_client_for_request synchronously before the tool handler runs.

4. [ok] Async httpx in gdocs_client.py

Using httpx.AsyncClient for the Google API calls is correct since the tool handler is async.

5. [ok] CI pipeline

Woodpecker YAML is well-formed. ruff check + format on PR, kaniko build on main push. Matches the gmail-mcp-remote pattern exactly (with repo name swapped).

6. [ok] Dockerfile

Follows the two-stage pip install pattern (requirements.txt first for layer caching, then full package install). EXPOSE 8000 matches the CMD default.

No Blockers

All findings are nits or observations. The code is clean, follows established patterns, and passes ruff lint/format.


VERDICT: APPROVED

## QA Review -- PR #1 ### Architecture & Pattern Compliance The codebase correctly follows the mcp-remote-auth pattern established by gmail-mcp-remote and gcal-mcp-remote. Key pattern elements verified: - ProviderConfig with Google OAuth URLs, scopes, extra_authorize_params - TokenStore + OAuthProxyProvider wiring - configure_mcp_auth / configure_transport_security / register_standard_routes / register_onboarding_routes call sequence - build_app_with_middleware in the entry point - /metrics endpoint for ServiceMonitor - .woodpecker.yaml with test + build-and-push steps - Dockerfile.k8s following the same layer pattern The decision to define the tool directly on the FastMCP instance (no inner provider package, no client_patch.py) is sound for a single-tool server. ### Findings **1. [nit] server.py: mid-module imports with noqa comments** The `from mcp_remote_auth import ...` and `from starlette ...` imports are placed mid-module after the tool definition. This matches the gmail-mcp-remote pattern (which does it because it must apply the monkey-patch before importing mcp), but here there is no ordering constraint -- the tool uses a lazy import internally. These could be moved to the top of the file for cleaner style. However, since it matches the established pattern across the other remotes, this is acceptable as-is. **2. [nit] gdocs_client.py: title injection in Drive query** `search_document_by_title` builds the Drive API query with an f-string: `name='{title}'`. If a daily note title ever contained a single quote, this would break the query. For MM-DD-YYYY date strings this is not a real risk, but a note in the docstring or a simple escape (`title.replace("'", "\\'")`) would harden it. **3. [ok] ContextVar pattern** The ContextVar approach for per-request access token is clean and correct. The `_setup_gdocs_client` callback refreshes via synchronous httpx (matching the gmail-mcp-remote pattern where `set_client_for_request` also uses sync httpx.post), and the tool reads it async. This works because mcp-remote-auth calls setup_client_for_request synchronously before the tool handler runs. **4. [ok] Async httpx in gdocs_client.py** Using `httpx.AsyncClient` for the Google API calls is correct since the tool handler is async. **5. [ok] CI pipeline** Woodpecker YAML is well-formed. ruff check + format on PR, kaniko build on main push. Matches the gmail-mcp-remote pattern exactly (with repo name swapped). **6. [ok] Dockerfile** Follows the two-stage pip install pattern (requirements.txt first for layer caching, then full package install). EXPOSE 8000 matches the CMD default. ### No Blockers All findings are nits or observations. The code is clean, follows established patterns, and passes ruff lint/format. --- **VERDICT: APPROVED**
This pull request can be merged automatically.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin 288-initial-codebase:288-initial-codebase
git switch 288-initial-codebase

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff 288-initial-codebase
git switch 288-initial-codebase
git rebase main
git switch main
git merge --ff-only 288-initial-codebase
git switch 288-initial-codebase
git rebase main
git switch main
git merge --no-ff 288-initial-codebase
git switch main
git merge --squash 288-initial-codebase
git switch main
git merge --ff-only 288-initial-codebase
git switch main
git merge 288-initial-codebase
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/gdocs-daily-mcp-remote!1
No description provided.