Skip to content

enh: cors middleware & html fallback - #537

Closed
taylorwilsdon wants to merge 6 commits into
mainfrom
cors_middleware
Closed

enh: cors middleware & html fallback #537
taylorwilsdon wants to merge 6 commits into
mainfrom
cors_middleware

Conversation

@taylorwilsdon

@taylorwilsdon taylorwilsdon commented Mar 3, 2026

Copy link
Copy Markdown
Owner

& better fallback for html

Closes #536

Summary by CodeRabbit

  • New Features

    • Added CORS support for OAuth endpoints and applied it as the outermost HTTP middleware.
  • Bug Fixes

    • Improved email body formatting: better detection of low-value text (placeholders/footers) and smarter HTML vs. plain-text selection.
  • Tests

    • Added a test that verifies CORS middleware is present and configured.

@taylorwilsdon taylorwilsdon self-assigned this Mar 3, 2026
@coderabbitai

coderabbitai Bot commented Mar 3, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a public cors_middleware and makes it the outermost HTTP middleware for OAuth routes, enhances Gmail body extraction to detect low-value plain text and fall back to HTML, and reformats one test function signature; also adds a test that verifies CORS middleware presence.

Changes

Cohort / File(s) Summary
CORS middleware & test
core/server.py, test_cors.py
Introduces a module-level cors_middleware (starlette.middleware.Middleware using CORSMiddleware), configures it as the outermost middleware (reordering the stack), and adds test_cors.py to assert CORSMiddleware is present in server.http_app().
Gmail body extraction
gmail/gmail_tools.py
Adds LOW_VALUE_TEXT_PLACEHOLDERS, LOW_VALUE_TEXT_FOOTER_MARKERS, LOW_VALUE_TEXT_HTML_DIFF_MIN; updates _format_body_content to normalize HTML/plain text, detect low-value plain text (placeholders/footers/length heuristics), and prefer HTML when plain is empty or low-value.
Test formatting
tests/core/test_well_known_cache_control_middleware.py
Minor signature reformatting of test_configured_server_applies_no_cache_to_served_oauth_discovery_routes (multi-line definition); no behavioral changes.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant CORSMiddleware
    participant WellKnownCacheControl
    participant SessionMiddleware
    participant ServerApp

    rect rgba(200,200,255,0.5)
    Client->>CORSMiddleware: HTTP request
    end

    rect rgba(200,255,200,0.5)
    CORSMiddleware->>WellKnownCacheControl: pass request
    end

    rect rgba(255,200,200,0.5)
    WellKnownCacheControl->>SessionMiddleware: pass request
    SessionMiddleware->>ServerApp: pass request
    ServerApp-->>SessionMiddleware: response
    SessionMiddleware-->>WellKnownCacheControl: response
    WellKnownCacheControl-->>CORSMiddleware: response
    CORSMiddleware-->>Client: response (CORS headers applied)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰
CORS hops in, a careful fence,
OAuth endpoints now feel less tense.
HTML saved from plain-text gloom,
Messages bloom instead of doom.
Little rabbit smiles — bugs go bounce!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is extremely minimal and does not follow the required template. It lacks sections for Type of Change, Testing, Checklist, and Additional Notes, and provides no explanation of changes beyond closing an issue. Expand the description to follow the template structure: add Type of Change, Testing, and Checklist sections with details on what was changed and how it was tested.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'enh: cors middleware & html fallback' accurately captures the two main enhancements in this PR—CORS middleware configuration and improved HTML fallback logic—but is somewhat generic with minimal context.
Linked Issues check ✅ Passed The code changes successfully address the primary objective from issue #536: implementing HTML fallback by detecting low-value plain text and using HTML conversion when appropriate. The CORS middleware enhancement supports OAuth endpoints as required.
Out of Scope Changes check ✅ Passed All changes are within scope. The CORS middleware changes support OAuth endpoints, the HTML fallback logic addresses issue #536's requirements, and test files validate the new functionality. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cors_middleware

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/server.py`:
- Around line 45-50: The CORS middleware setup in cors_middleware (using
CORSMiddleware with allow_origins=["*"] and allow_credentials=True) violates
Starlette validation and will raise at runtime; update cors_middleware to either
(A) set allow_credentials=False when using allow_origins=["*"], or (B) replace
the wildcard with an explicit allowlist of trusted origins (e.g.,
["https://example.com"]) and keep allow_credentials=True, and then restrict
where this middleware is applied so credentialed CORS is not the global
outermost layer (move or apply the cors_middleware only to the OAuth endpoints
or route group rather than adding it as the global outer middleware).

In `@gmail/gmail_tools.py`:
- Around line 174-189: The plain text is considered non-low-value only via
markers/footers/suffix heuristics, so very short plain text still beats HTML;
add a minimum meaningful plain-text length constant (e.g.,
MIN_MEANINGFUL_PLAIN_LENGTH) and treat plain text shorter than that as low-value
by incorporating len(plain_lower) < MIN_MEANINGFUL_PLAIN_LENGTH into the
plain_is_low_value determination (or into the use_html decision) so that
use_html becomes true when html_text exists and plain text is present but
beneath the minimum meaningful length; update references to
LOW_VALUE_TEXT_PLACEHOLDERS, LOW_VALUE_TEXT_FOOTER_MARKERS and
LOW_VALUE_TEXT_HTML_DIFF_MIN accordingly when locating plain_is_low_value and
use_html.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 04b9ae0 and b93cc1c.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • core/server.py
  • gmail/gmail_tools.py
  • tests/core/test_well_known_cache_control_middleware.py

Comment thread core/server.py
Comment thread gmail/gmail_tools.py
Comment on lines +174 to 189
plain_is_low_value = plain_lower and (
any(marker in plain_lower for marker in LOW_VALUE_TEXT_PLACEHOLDERS)
or (
any(marker in plain_lower for marker in LOW_VALUE_TEXT_FOOTER_MARKERS)
and len(html_lower) >= len(plain_lower) + LOW_VALUE_TEXT_HTML_DIFF_MIN
)
or (
len(html_lower) >= len(plain_lower) + LOW_VALUE_TEXT_HTML_DIFF_MIN
and html_lower.endswith(plain_lower)
)
)

# Detect useless fallback: HTML comments in text, or HTML is 50x+ longer
use_html = html_stripped and (
not text_stripped
or "<!--" in text_stripped
or len(html_stripped) > len(text_stripped) * 50
# Prefer plain text, but fall back to HTML when plain text is empty or clearly low-value.
use_html = html_text and (
not text_stripped or "<!--" in text_stripped or plain_is_low_value
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add an explicit “minimum meaningful plain-text length” fallback condition.

Current logic only flags low-value plain text via marker/footer/suffix heuristics. Very short non-empty text/plain (e.g., 1–20 chars) can still win over rich HTML content, which misses the “below minimal meaningful length” requirement.

💡 Proposed adjustment
 LOW_VALUE_TEXT_HTML_DIFF_MIN = 80
+LOW_VALUE_TEXT_MIN_MEANINGFUL_LEN = 40
@@
-    plain_is_low_value = plain_lower and (
-        any(marker in plain_lower for marker in LOW_VALUE_TEXT_PLACEHOLDERS)
+    plain_is_low_value = bool(plain_lower) and (
+        (
+            len(plain_lower) < LOW_VALUE_TEXT_MIN_MEANINGFUL_LEN
+            and len(html_lower) >= len(plain_lower) + LOW_VALUE_TEXT_HTML_DIFF_MIN
+        )
+        or any(marker in plain_lower for marker in LOW_VALUE_TEXT_PLACEHOLDERS)
         or (
             any(marker in plain_lower for marker in LOW_VALUE_TEXT_FOOTER_MARKERS)
             and len(html_lower) >= len(plain_lower) + LOW_VALUE_TEXT_HTML_DIFF_MIN
         )
         or (
             len(html_lower) >= len(plain_lower) + LOW_VALUE_TEXT_HTML_DIFF_MIN
             and html_lower.endswith(plain_lower)
         )
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gmail/gmail_tools.py` around lines 174 - 189, The plain text is considered
non-low-value only via markers/footers/suffix heuristics, so very short plain
text still beats HTML; add a minimum meaningful plain-text length constant
(e.g., MIN_MEANINGFUL_PLAIN_LENGTH) and treat plain text shorter than that as
low-value by incorporating len(plain_lower) < MIN_MEANINGFUL_PLAIN_LENGTH into
the plain_is_low_value determination (or into the use_html decision) so that
use_html becomes true when html_text exists and plain text is present but
beneath the minimum meaningful length; update references to
LOW_VALUE_TEXT_PLACEHOLDERS, LOW_VALUE_TEXT_FOOTER_MARKERS and
LOW_VALUE_TEXT_HTML_DIFF_MIN accordingly when locating plain_is_low_value and
use_html.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
core/server.py (1)

44-46: Clarify scope in comments to match behavior.

The comment says this is for “OAuth endpoints,” but the middleware is added globally in http_app. Updating the wording will prevent future confusion.

Also applies to: 104-106

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/server.py` around lines 44 - 46, The comment currently states the CORS
middleware is "for OAuth endpoints" but the middleware is mounted globally on
http_app; update the comment(s) near where CORS is configured (the code that
adds the CORS middleware to http_app) to clarify that this CORS policy applies
application-wide (or specify the actual scoped endpoints if you change
behavior), and keep the note about allow_credentials=True vs
allow_origins=["*"]; adjust both occurrences of the explanatory comment so they
accurately reflect that the middleware is global rather than limited to OAuth
endpoints.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test_cors.py`:
- Around line 6-40: The test currently performs import-time execution and calls
sys.exit in test_cors.py; refactor it into a proper pytest test function (e.g.,
test_cors_middleware_present) that sets required env vars via a fixture or
monkeypatch (instead of global os.environ mutating at import), imports server
and calls server.http_app() inside the test, inspects app.user_middleware for a
middleware with cls.__name__ == "CORSMiddleware", and replace print/ sys.exit
logic with plain assertions (assert any(...)). Ensure env changes are scoped to
the test and not performed at import time so test collection isn't aborted.

---

Nitpick comments:
In `@core/server.py`:
- Around line 44-46: The comment currently states the CORS middleware is "for
OAuth endpoints" but the middleware is mounted globally on http_app; update the
comment(s) near where CORS is configured (the code that adds the CORS middleware
to http_app) to clarify that this CORS policy applies application-wide (or
specify the actual scoped endpoints if you change behavior), and keep the note
about allow_credentials=True vs allow_origins=["*"]; adjust both occurrences of
the explanatory comment so they accurately reflect that the middleware is global
rather than limited to OAuth endpoints.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b93cc1c and d067645.

📒 Files selected for processing (2)
  • core/server.py
  • test_cors.py

Comment thread test_cors.py
Comment on lines +6 to +40
sys.path.insert(0, ".")

# Set required env vars before importing
import os

os.environ["MCP_ENABLE_OAUTH21"] = "true"
os.environ["WORKSPACE_MCP_STATELESS_MODE"] = "true"
os.environ["GOOGLE_OAUTH_CLIENT_ID"] = "test"
os.environ["GOOGLE_OAUTH_CLIENT_SECRET"] = "test"

from core.server import server

# Get the HTTP app
app = server.http_app()

# Check middleware stack
print("Middleware stack:")
for i, m in enumerate(app.user_middleware):
if hasattr(m, "cls"):
print(f" {i}: {m.cls.__name__}")
else:
print(f" {i}: {type(m).__name__}")

print("\nChecking for CORSMiddleware...")
has_cors = any(
hasattr(m, "cls") and m.cls.__name__ == "CORSMiddleware"
for m in app.user_middleware
)
print(f"CORS middleware found: {has_cors}")

if has_cors:
print("✅ CORS middleware is configured!")
else:
print("❌ CORS middleware is NOT configured!")
sys.exit(1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid import-time execution and sys.exit in test modules.

This file runs assertions at import time and exits the process on failure (Line 40), which can break/abort test collection and leak env mutations into unrelated tests. Please convert it to a real test function with scoped env setup and plain assertions.

✅ Suggested rewrite
-#!/usr/bin/env python3
-"""Quick test to verify CORS middleware is configured correctly."""
-
-import sys
-
-sys.path.insert(0, ".")
-
-# Set required env vars before importing
-import os
-
-os.environ["MCP_ENABLE_OAUTH21"] = "true"
-os.environ["WORKSPACE_MCP_STATELESS_MODE"] = "true"
-os.environ["GOOGLE_OAUTH_CLIENT_ID"] = "test"
-os.environ["GOOGLE_OAUTH_CLIENT_SECRET"] = "test"
-
-from core.server import server
-
-# Get the HTTP app
-app = server.http_app()
-
-# Check middleware stack
-print("Middleware stack:")
-for i, m in enumerate(app.user_middleware):
-    if hasattr(m, "cls"):
-        print(f"  {i}: {m.cls.__name__}")
-    else:
-        print(f"  {i}: {type(m).__name__}")
-
-print("\nChecking for CORSMiddleware...")
-has_cors = any(
-    hasattr(m, "cls") and m.cls.__name__ == "CORSMiddleware"
-    for m in app.user_middleware
-)
-print(f"CORS middleware found: {has_cors}")
-
-if has_cors:
-    print("✅ CORS middleware is configured!")
-else:
-    print("❌ CORS middleware is NOT configured!")
-    sys.exit(1)
+from starlette.middleware.cors import CORSMiddleware
+
+
+def test_http_app_has_cors_middleware(monkeypatch):
+    monkeypatch.setenv("MCP_ENABLE_OAUTH21", "true")
+    monkeypatch.setenv("WORKSPACE_MCP_STATELESS_MODE", "true")
+    monkeypatch.setenv("GOOGLE_OAUTH_CLIENT_ID", "test")
+    monkeypatch.setenv("GOOGLE_OAUTH_CLIENT_SECRET", "test")
+
+    from core.server import server
+
+    app = server.http_app()
+    assert any(
+        getattr(m, "cls", None) is CORSMiddleware for m in app.user_middleware
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test_cors.py` around lines 6 - 40, The test currently performs import-time
execution and calls sys.exit in test_cors.py; refactor it into a proper pytest
test function (e.g., test_cors_middleware_present) that sets required env vars
via a fixture or monkeypatch (instead of global os.environ mutating at import),
imports server and calls server.http_app() inside the test, inspects
app.user_middleware for a middleware with cls.__name__ == "CORSMiddleware", and
replace print/ sys.exit logic with plain assertions (assert any(...)). Ensure
env changes are scoped to the test and not performed at import time so test
collection isn't aborted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

get_gmail_message_content returns empty body for HTML-only emails (no text/plain fallback)

1 participant