Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/oauth-verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: OAuth Verify

on:
push:
branches: [main, feature/sdk-oauth-verify]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read

jobs:
custom-idp-oauth:
name: Custom IdP OAuth (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-14]

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip

- name: Install package
run: python -m pip install -e ".[dev]"

- name: Run custom IdP OAuth verification
run: scripts/test-oauth-custom-idp.sh
9 changes: 3 additions & 6 deletions .github/workflows/verify-examples.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
name: Verify SDK Examples

on:
pull_request:
branches: [main]
push:
branches: [iml_verify_auto]
schedule:
- cron: '17 10 * * 1'
workflow_dispatch:
inputs:
mode:
Expand Down Expand Up @@ -66,9 +66,7 @@ jobs:
python -m venv native-preflight
source native-preflight/bin/activate
python -m pip install --upgrade pip
if [ "${{ github.event_name }}" = "pull_request" ]; then
python -m pip install -e . "gopher-mcp-python-native-${{ matrix.platform }}"
elif [ "$VERIFY_PYPI_VERSION" = "latest" ]; then
if [ "$VERIFY_PYPI_VERSION" = "latest" ]; then
python -m pip install gopher-mcp-python gopher-mcp-python-native-${{ matrix.platform }}
else
python -m pip install "gopher-mcp-python==${VERIFY_PYPI_VERSION}" "gopher-mcp-python-native-${{ matrix.platform }}==${VERIFY_PYPI_VERSION}"
Expand Down Expand Up @@ -181,7 +179,6 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GOPHER_API_KEY: ${{ secrets.GOPHER_API_KEY }}
GOPHER_MCP_URL: ${{ secrets.GOPHER_MCP_URL }}
SDK_INSTALL_SPEC: ${{ github.event_name == 'pull_request' && github.workspace || '' }}
run: |
unset GOPHER_SDK_TEST
VERIFY_LIVE_PROMPT="Get my mail profile" \
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ Result class with status and metadata.
pytest
```

For deterministic OAuth auto verification with a local custom IdP and local MCP
server/gateway endpoints, see
[`docs/oauth-auto-custom-idp.md`](docs/oauth-auto-custom-idp.md).

### Code Formatting

This project uses Black for code formatting and Ruff for linting.
Expand Down
70 changes: 70 additions & 0 deletions docs/oauth-auto-custom-idp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# OAuth Auto Verification With Custom IdP

The stable OAuth auto verification path uses local test fixtures instead of
Gmail, hosted Gopher services, or real OAuth provider credentials. It verifies
the Python SDK behavior that matters for automatic OAuth:

- discovering OAuth protection from an MCP endpoint
- reading protected resource metadata
- using OAuth authorization server metadata
- refreshing a cached token through the token endpoint
- injecting the refreshed bearer token into `GopherAgent.create_with_url`
runtime options before the native FFI call

The tests cover both endpoint shapes used by deployments:

- direct MCP server endpoint
- MCP gateway endpoint

Fixture credentials such as `test-client`, `test-secret`, and
`test-refresh-token` are local test data. They are not GitHub Secrets, and the
suite asserts that fixture secrets do not appear in captured output or errors.

## Local Command

Run the deterministic custom IdP suite with:

```bash
scripts/test-oauth-custom-idp.sh
```

The script runs:

```bash
python -m pytest \
tests/test_oauth_auto_custom_idp.py \
tests/test_oauth_auto_custom_idp_failures.py \
tests/test_custom_oauth_test_idp.py \
tests/test_custom_protected_mcp_endpoints.py \
tests/test_oauth_test_token_helper.py
```

Extra pytest arguments can be passed through:

```bash
scripts/test-oauth-custom-idp.sh -q
```

## CI Coverage

`.github/workflows/oauth-verify.yml` runs the same suite on pull requests and
manual dispatch. It installs the package with development dependencies and does
not require hosted endpoints, Gmail accounts, OAuth client secrets, refresh
tokens, LLM provider keys, or other real credentials.

## Live Smoke Tests

Gmail or real Gopher endpoint verification remains useful as an optional smoke
test because it proves compatibility with external provider policy, hosted
gateway configuration, and real account consent. Those checks are operationally
different from SDK correctness tests: they depend on provider availability,
account security rules, valid refresh tokens, and live service configuration.

Keep live smoke tests manual, scheduled, or otherwise separate from the stable
pull-request gate. In this repository, `.github/workflows/oauth-verify.yml` is
the pull-request OAuth gate, while `.github/workflows/verify-examples.yml` is
reserved for scheduled, branch-triggered, or manually dispatched example smoke
verification. The API example workflow and docs live under `examples/api/`.

For a possible full native end-to-end extension, see
[`oauth-auto-native-follow-up.md`](oauth-auto-native-follow-up.md).
45 changes: 45 additions & 0 deletions docs/oauth-auto-native-follow-up.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Optional Native OAuth E2E Follow-Up

The custom IdP OAuth verification suite is the stable pull-request signal. It
starts local OAuth and protected MCP endpoint harnesses, lets
`GopherAgent.create_with_url` resolve OAuth automatically, and asserts that the
native FFI boundary receives runtime options containing the refreshed bearer
token.

A full native end-to-end test can be added later, but it should stay separate
from the stable PR gate unless it is fully deterministic.

## Proposed Shape

Reuse the same model as `docs/oauth-auto-custom-idp.md`:

- local custom OAuth/OIDC IdP
- local protected MCP server endpoint
- local protected MCP gateway endpoint
- refresh-token-backed OAuth setup

Add one deterministic protected MCP tool:

```text
tool: whoami
input: {}
output: { "subject": "test-user@example.test" }
```

Then create a real `GopherAgent` through `GopherAgent.create_with_url` and run a
query that must call the protected `whoami` tool. The test should assert the
authenticated result, not just successful agent creation.

## When To Enable

Keep this path manual, scheduled, or non-blocking until these dependencies are
controlled:

- native package availability for each target platform
- deterministic LLM/provider behavior or a reliable test provider
- stable local MCP tool execution behavior
- clear runtime bounds suitable for CI

Until those are in place, the focused custom IdP tests remain the correct PR
gate because they verify the SDK OAuth flow without external service or native
runtime flake.
25 changes: 25 additions & 0 deletions scripts/test-oauth-custom-idp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"

cd "${REPO_ROOT}"

PYTHON_BIN="${PYTHON:-}"
if [ -z "${PYTHON_BIN}" ]; then
if command -v python >/dev/null 2>&1; then
PYTHON_BIN="python"
else
PYTHON_BIN="python3"
fi
fi

"${PYTHON_BIN}" -m pytest \
tests/test_oauth_auto_custom_idp.py \
tests/test_oauth_auto_custom_idp_failures.py \
tests/test_custom_oauth_test_idp.py \
tests/test_custom_protected_mcp_endpoints.py \
tests/test_oauth_test_token_helper.py \
"$@"
1 change: 1 addition & 0 deletions tests/helpers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Shared test helpers."""
Loading
Loading