diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index e319d18..154666c 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,5 +1,6 @@ {"id":"google_workspace_mcp-016","title":"fix: correct MCP registry PyPI ownership metadata","description":"Twine/PyPI rejects project.urls mcp-name because URL values must be valid URLs. For MCP registry PyPI verification, use README marker mcp-name: \u003cserver-name\u003e and ensure server.json name uses io.github.\u003cuser\u003e/\u003cserver\u003e format.","status":"closed","priority":1,"issue_type":"bug","owner":"tbarrettwilsdon@gmail.com","created_at":"2026-02-08T20:04:06.49156-05:00","created_by":"Taylor Wilsdon","updated_at":"2026-02-08T20:05:35.18854-05:00","closed_at":"2026-02-08T20:05:35.18854-05:00","close_reason":"Closed"} {"id":"google_workspace_mcp-0fl","title":"enh: add MCP registry publish to local release.py flow","description":"Extend scripts/release.py to sync server.json version and publish to MCP Registry via mcp-publisher during local release process, so publishing does not depend on GitHub Actions.","status":"closed","priority":2,"issue_type":"task","owner":"tbarrettwilsdon@gmail.com","created_at":"2026-02-08T21:03:51.388408-05:00","created_by":"Taylor Wilsdon","updated_at":"2026-02-08T21:06:05.334395-05:00","closed_at":"2026-02-08T21:06:05.334395-05:00","close_reason":"Closed"} +{"id":"google_workspace_mcp-0lv","title":"fix: preserve PKCE code_verifier across legacy OAuth callback","status":"closed","priority":1,"issue_type":"bug","owner":"tbarrettwilsdon@gmail.com","created_at":"2026-02-28T09:56:17.914665-04:00","created_by":"Taylor Wilsdon","updated_at":"2026-02-28T09:58:46.892165-04:00","closed_at":"2026-02-28T09:58:46.892165-04:00","close_reason":"Implemented PKCE code_verifier state continuity in legacy OAuth callback path and added tests"} {"id":"google_workspace_mcp-2mc","title":"release: cut next PyPI version and publish MCP registry entry","description":"Run local release flow: uv run python scripts/release.py to publish PyPI + MCP Registry via mcp-publisher. Verify package version on PyPI and server listing in registry search endpoint.","status":"open","priority":2,"issue_type":"task","owner":"tbarrettwilsdon@gmail.com","created_at":"2026-02-08T20:00:39.779476-05:00","created_by":"Taylor Wilsdon","updated_at":"2026-02-08T21:06:15.613447-05:00"} {"id":"google_workspace_mcp-3bn","title":"Fix AppScript run_script_function schema for Gemini API","description":"The run_script_function tool has a 'parameters' parameter defined as Optional[List[Any]] which causes schema generation issues with the Gemini API. The error is: 'GenerateContentRequest.tools[0].function_declarations[125].parameters.properties[parameters].items: missing field'. Need to fix the type annotation to generate proper JSON schema with items field.","status":"closed","priority":2,"issue_type":"bug","owner":"tbarrettwilsdon@gmail.com","created_at":"2026-02-09T14:16:48.857746-05:00","created_by":"Taylor Wilsdon","updated_at":"2026-02-09T14:21:43.5927-05:00","closed_at":"2026-02-09T14:21:43.5927-05:00","close_reason":"Fixed by changing parameters type from Optional[List[Any]] to Optional[list] in run_script_function. This ensures proper JSON schema generation with items field for Gemini API compatibility."} {"id":"google_workspace_mcp-631","title":"Address copilot feedback for docs/sheets hyperlink range and extraction","status":"closed","priority":2,"issue_type":"task","owner":"tbarrettwilsdon@gmail.com","created_at":"2026-02-08T18:36:22.330879-05:00","created_by":"Taylor Wilsdon","updated_at":"2026-02-08T18:38:04.356856-05:00","closed_at":"2026-02-08T18:38:04.356856-05:00","close_reason":"Closed"} @@ -12,6 +13,7 @@ {"id":"google_workspace_mcp-gpb","title":"Address PR feedback for docs list nesting and sheets hyperlink fetch","status":"closed","priority":2,"issue_type":"task","owner":"tbarrettwilsdon@gmail.com","created_at":"2026-02-08T17:48:48.31354-05:00","created_by":"Taylor Wilsdon","updated_at":"2026-02-08T17:51:53.608353-05:00","closed_at":"2026-02-08T17:51:53.608353-05:00","close_reason":"Closed"} {"id":"google_workspace_mcp-ic8","title":"enh: support writing hyperlink URLs in modify_sheet_values","description":"Issue #434 also requested hyperlink creation/writes. Current implementation reads hyperlinks in read_sheet_values but modify_sheet_values does not expose first-class hyperlink writes.","status":"open","priority":3,"issue_type":"task","owner":"tbarrettwilsdon@gmail.com","created_at":"2026-02-08T17:42:10.590658-05:00","created_by":"Taylor Wilsdon","updated_at":"2026-02-08T17:42:10.590658-05:00"} {"id":"google_workspace_mcp-jf2","title":"ci: make PyPI publish step rerun-safe with skip-existing","description":"GitHub Actions reruns on same tag fail because PyPI rejects duplicate file uploads. Add skip-existing=true to pypa/gh-action-pypi-publish so reruns proceed to MCP publish.","status":"closed","priority":2,"issue_type":"bug","owner":"tbarrettwilsdon@gmail.com","created_at":"2026-02-08T20:59:58.461102-05:00","created_by":"Taylor Wilsdon","updated_at":"2026-02-08T21:00:32.121469-05:00","closed_at":"2026-02-08T21:00:32.121469-05:00","close_reason":"Closed"} +{"id":"google_workspace_mcp-le6","title":"test: stabilize oauth callback redirect URI tests with OAuthConfig singleton reset","description":"tests/test_oauth_callback_server.py currently fails in this environment because get_oauth_redirect_uri uses cached OAuthConfig state that ignores per-test env var mutations. Add deterministic config reset/fixture strategy.","status":"open","priority":3,"issue_type":"task","owner":"tbarrettwilsdon@gmail.com","created_at":"2026-02-28T09:59:11.402699-04:00","created_by":"Taylor Wilsdon","updated_at":"2026-02-28T09:59:11.402699-04:00"} {"id":"google_workspace_mcp-qfl","title":"Fix stdio multi-account session binding","status":"in_progress","priority":1,"issue_type":"task","owner":"tbarrettwilsdon@gmail.com","created_at":"2026-02-07T13:27:09.466282-05:00","created_by":"Taylor Wilsdon","updated_at":"2026-02-07T13:27:22.857227-05:00"} {"id":"google_workspace_mcp-qr5","title":"fix: include RFC Message-ID threading headers in thread content output","status":"closed","priority":2,"issue_type":"bug","owner":"tbarrettwilsdon@gmail.com","created_at":"2026-02-11T11:44:41.966911-05:00","created_by":"Taylor Wilsdon","updated_at":"2026-02-11T11:46:11.355237-05:00","closed_at":"2026-02-11T11:46:11.355237-05:00","close_reason":"Closed"} {"id":"google_workspace_mcp-xia","title":"fix: CLI should unwrap FastAPI Body defaults when invoking tools","description":"CLI mode invokes tool functions directly and currently passes FastAPI Body marker objects as defaults for omitted args. This breaks gmail send/draft with errors like Body has no attribute lower/len. Update CLI invocation to normalize Param defaults and return clear missing-required errors.","status":"closed","priority":1,"issue_type":"bug","owner":"tbarrettwilsdon@gmail.com","created_at":"2026-02-10T12:33:06.83139-05:00","created_by":"Taylor Wilsdon","updated_at":"2026-02-10T12:36:35.051947-05:00","closed_at":"2026-02-10T12:36:35.051947-05:00","close_reason":"Implemented CLI FastAPI default normalization + regression tests","labels":["cli","gmail"]} diff --git a/auth/google_auth.py b/auth/google_auth.py index c5086e7..942d2f5 100644 --- a/auth/google_auth.py +++ b/auth/google_auth.py @@ -291,16 +291,27 @@ def check_client_secrets() -> Optional[str]: def create_oauth_flow( - scopes: List[str], redirect_uri: str, state: Optional[str] = None + scopes: List[str], + redirect_uri: str, + state: Optional[str] = None, + code_verifier: Optional[str] = None, ) -> Flow: """Creates an OAuth flow using environment variables or client secrets file.""" + flow_kwargs = { + "scopes": scopes, + "redirect_uri": redirect_uri, + "state": state, + } + if code_verifier: + flow_kwargs["code_verifier"] = code_verifier + # Preserve the original verifier when re-creating the flow in callback. + flow_kwargs["autogenerate_code_verifier"] = False + # Try environment variables first env_config = load_client_secrets_from_env() if env_config: # Use client config directly - flow = Flow.from_client_config( - env_config, scopes=scopes, redirect_uri=redirect_uri, state=state - ) + flow = Flow.from_client_config(env_config, **flow_kwargs) logger.debug("Created OAuth flow from environment variables") return flow @@ -312,9 +323,7 @@ def create_oauth_flow( flow = Flow.from_client_secrets_file( CONFIG_CLIENT_SECRETS_PATH, - scopes=scopes, - redirect_uri=redirect_uri, - state=state, + **flow_kwargs, ) logger.debug( f"Created OAuth flow from client secrets file: {CONFIG_CLIENT_SECRETS_PATH}" @@ -389,7 +398,11 @@ async def start_auth_flow( ) store = get_oauth21_session_store() - store.store_oauth_state(oauth_state, session_id=session_id) + store.store_oauth_state( + oauth_state, + session_id=session_id, + code_verifier=flow.code_verifier, + ) logger.info( f"Auth flow started for {user_display_name}. Advise user to visit: {auth_url}" @@ -502,7 +515,12 @@ def handle_auth_callback( state_info.get("session_id") or "", ) - flow = create_oauth_flow(scopes=scopes, redirect_uri=redirect_uri, state=state) + flow = create_oauth_flow( + scopes=scopes, + redirect_uri=redirect_uri, + state=state, + code_verifier=state_info.get("code_verifier"), + ) # Exchange the authorization code for credentials # Note: fetch_token will use the redirect_uri configured in the flow diff --git a/auth/oauth21_session_store.py b/auth/oauth21_session_store.py index 2893154..f659de2 100644 --- a/auth/oauth21_session_store.py +++ b/auth/oauth21_session_store.py @@ -221,6 +221,7 @@ class OAuth21SessionStore: state: str, session_id: Optional[str] = None, expires_in_seconds: int = 600, + code_verifier: Optional[str] = None, ) -> None: """Persist an OAuth state value for later validation.""" if not state: @@ -236,6 +237,7 @@ class OAuth21SessionStore: "session_id": session_id, "expires_at": expiry, "created_at": now, + "code_verifier": code_verifier, } logger.debug( "Stored OAuth state %s (expires at %s)",