From 35bf708d37c4700284b34225df5180c46253fa31 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 16 Sep 2026 16:10:08 +0000 Subject: [PATCH] test: optional live SSE dogfood capture with integration marker (#65) Add pytest.mark.integration capture-only dogfood test (skips without URL/token env), document manual/nightly recipe in docs/downstream.md, and ship an opt-in Dogfood SSE workflow gated by ENABLE_DOGFOOD_SSE. Co-authored-by: Abhinaysai Kamineni --- .github/workflows/dogfood-sse.yml | 30 +++++++++++++ docs/downstream.md | 51 ++++++++++++++++++--- pyproject.toml | 3 ++ tests/test_dogfood_sse.py | 74 +++++++++++++++++++++++++++++++ 4 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/dogfood-sse.yml create mode 100644 tests/test_dogfood_sse.py diff --git a/.github/workflows/dogfood-sse.yml b/.github/workflows/dogfood-sse.yml new file mode 100644 index 0000000..56d417e --- /dev/null +++ b/.github/workflows/dogfood-sse.yml @@ -0,0 +1,30 @@ +# Optional live SSE dogfood capture (#65). +# Skips the job when TOOL_SEMANTICS_DOGFOOD_SSE_URL secret is unset. + +name: Dogfood SSE + +on: + workflow_dispatch: + schedule: + - cron: "0 6 * * 1" + +permissions: + contents: read + +jobs: + capture: + if: ${{ vars.ENABLE_DOGFOOD_SSE == 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: "3.12" + cache: pip + - run: python -m pip install --upgrade pip + - run: pip install -e ".[dev]" + - name: Live SSE capture (integration) + env: + TOOL_SEMANTICS_DOGFOOD_SSE_URL: ${{ secrets.TOOL_SEMANTICS_DOGFOOD_SSE_URL }} + TOOL_SEMANTICS_DOGFOOD_SSE_TOKEN: ${{ secrets.TOOL_SEMANTICS_DOGFOOD_SSE_TOKEN }} + run: pytest -m integration tests/test_dogfood_sse.py diff --git a/docs/downstream.md b/docs/downstream.md index d08302d..f95b2a3 100644 --- a/docs/downstream.md +++ b/docs/downstream.md @@ -37,19 +37,60 @@ that is not covered by `ProbeMetrics` / `StabilityReport`. ## market-pulse-mcp — dogfood SSE capture -Optional live-capture smoke against the org MCP server: +Optional live-capture smoke against the org MCP server (or any SSE MCP endpoint). +**Capture-only** — discovered tools are never executed. + +### Manual / nightly recipe ```bash +export TOOL_SEMANTICS_DOGFOOD_SSE_URL="$MARKET_PULSE_SSE_URL" # required +export TOOL_SEMANTICS_DOGFOOD_SSE_TOKEN="$TOKEN" # if auth required + pip install "tool-semantics>=0.4.0" -tool-semantics capture-mcp --sse "$MARKET_PULSE_SSE_URL" \ - --header "Authorization: Bearer $TOKEN" \ +# CLI path +tool-semantics capture-mcp --sse "$TOOL_SEMANTICS_DOGFOOD_SSE_URL" \ + --header "Authorization: Bearer $TOOL_SEMANTICS_DOGFOOD_SSE_TOKEN" \ -o .tool-semantics/market-pulse.json + +# Integration test (skipped in PR CI when env is unset) +pytest -m integration tests/test_dogfood_sse.py +``` + +Aliases accepted by the test: `MARKET_PULSE_SSE_URL`, `MARKET_PULSE_SSE_TOKEN` / `TOKEN`. + +Optional GitHub Actions job (enable when repository secrets exist): + +```yaml +# .github/workflows/dogfood-sse.yml +name: Dogfood SSE +on: + workflow_dispatch: + schedule: + - cron: "0 6 * * 1" # weekly Monday 06:00 UTC +jobs: + capture: + if: ${{ secrets.TOOL_SEMANTICS_DOGFOOD_SSE_URL != '' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: "3.12" + - run: pip install -e ".[dev]" + - run: pytest -m integration tests/test_dogfood_sse.py + env: + TOOL_SEMANTICS_DOGFOOD_SSE_URL: ${{ secrets.TOOL_SEMANTICS_DOGFOOD_SSE_URL }} + TOOL_SEMANTICS_DOGFOOD_SSE_TOKEN: ${{ secrets.TOOL_SEMANTICS_DOGFOOD_SSE_TOKEN }} ``` Confirm: -- Snapshot protocol / metadata transport is SSE (`mcp-sse` / `sse`) -- Auth header values never appear in snapshot JSON / metadata +- Snapshot protocol is `mcp-sse` (metadata `transport: sse`) +- Auth header **values** never appear in snapshot JSON / metadata (only header names) - Tools list is non-empty and redaction still applies +Automated assertions live in [`tests/test_dogfood_sse.py`](../tests/test_dogfood_sse.py) +(`pytest.mark.integration`). Default PR CI does not set credentials, so the test +skips and stays green. + No market-pulse-mcp code changes are required for this dogfood path. diff --git a/pyproject.toml b/pyproject.toml index ec64032..5bcebf6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,9 @@ packages = ["src/tool_semantics"] [tool.pytest.ini_options] testpaths = ["tests"] addopts = "-q" +markers = [ + "integration: live external services; skipped unless dogfood env credentials are set", +] [tool.ruff] line-length = 100 diff --git a/tests/test_dogfood_sse.py b/tests/test_dogfood_sse.py new file mode 100644 index 0000000..38877bf --- /dev/null +++ b/tests/test_dogfood_sse.py @@ -0,0 +1,74 @@ +"""Optional live dogfood capture against a real MCP SSE server (#65). + +Skipped in default PR CI when credentials are absent. Capture-only — never +executes discovered tools. Tokens come from env / CI secrets and must not appear +in snapshot JSON. +""" + +from __future__ import annotations + +import json +import os +import re + +import pytest + +from tool_semantics.mcp_capture import capture_mcp_sse + +pytestmark = pytest.mark.integration + +_URL_ENV = "TOOL_SEMANTICS_DOGFOOD_SSE_URL" +_TOKEN_ENV = "TOOL_SEMANTICS_DOGFOOD_SSE_TOKEN" +# Also accept the market-pulse names documented in docs/downstream.md +_URL_ENV_ALIASES = ("MARKET_PULSE_SSE_URL",) +_TOKEN_ENV_ALIASES = ("MARKET_PULSE_SSE_TOKEN", "TOKEN") + + +def _env_first(*names: str) -> str | None: + for name in names: + value = os.environ.get(name) + if value: + return value + return None + + +def _require_dogfood_creds() -> tuple[str, str | None]: + url = _env_first(_URL_ENV, *_URL_ENV_ALIASES) + token = _env_first(_TOKEN_ENV, *_TOKEN_ENV_ALIASES) + if not url: + pytest.skip(f"Set {_URL_ENV} (or MARKET_PULSE_SSE_URL) to run live SSE dogfood capture") + return url, token + + +def _assert_no_secrets_in_snapshot(payload: dict[str, object], token: str | None) -> None: + dumped = json.dumps(payload, default=str) + if token: + assert token not in dumped + # Common Bearer forms + assert f"Bearer {token}" not in dumped + # Auth header values must never be persisted; only header *names* are OK. + assert not re.search(r'"authorization"\s*:\s*"Bearer ', dumped, re.IGNORECASE) + + +def test_live_dogfood_sse_capture_only() -> None: + """Capture tools from a live SSE MCP server; never call tools/call.""" + url, token = _require_dogfood_creds() + headers: dict[str, str] = {} + if token: + headers["Authorization"] = f"Bearer {token}" + + snapshot = capture_mcp_sse(url, headers=headers or None, timeout=30.0, redact=True) + + assert snapshot.protocol == "mcp-sse" + assert snapshot.metadata.get("transport") == "sse" + assert len(snapshot.tools) > 0 + + payload = json.loads(snapshot.model_dump_json(by_alias=True)) + _assert_no_secrets_in_snapshot(payload, token) + + # Capture path only lists interface — ensure we did not invent tool results. + assert "tool_results" not in payload + assert snapshot.metadata.get("request_header_names") is not None + header_names = snapshot.metadata.get("request_header_names") + if token and isinstance(header_names, list): + assert any(str(name).lower() == "authorization" for name in header_names)