From ad4d7317f483efc056a5b0cf2ba5561c78027da3 Mon Sep 17 00:00:00 2001 From: b3nw Date: Fri, 24 Apr 2026 04:48:07 +0000 Subject: [PATCH] Downgrade playwright to 1.50.0 to fix version mismatch with browserless server --- BUG.md | 24 +++++++++++++++++ pyproject.toml | 5 ++-- scripts/upload_cookies.py | 6 ++++- upload_cookies.py | 57 --------------------------------------- uv.lock | 29 +++++++++----------- 5 files changed, 44 insertions(+), 77 deletions(-) create mode 100644 BUG.md delete mode 100755 upload_cookies.py diff --git a/BUG.md b/BUG.md new file mode 100644 index 0000000..ea3ac8b --- /dev/null +++ b/BUG.md @@ -0,0 +1,24 @@ +# RESOLVED: Gemini Deployment Incapacity (Docker Mount Persistence) + +## Status: RESOLVED +The critical Docker filesystem mounting error on the remote host `docker-test` has been successfully diagnosed and resolved. The `schwab-mcp` container is now correctly mounting `cookies.json` and `config.json` as files, and the authentication cookies have been successfully injected. + +## Root Cause Analysis +The persistent `[Errno 21] Is a directory: 'config.json'` error was caused by a compounding sequence of abstraction mismatches between Komodo and the Docker daemon: + +1. **Komodo's Path Parsing (`/host/...`)**: When Komodo deploys a stack with `files_on_host: true`, it requires the `run_directory` to be prefixed with `/host/` (e.g., `/host/opt/schwab-mcp-custom`) because the Komodo Periphery agent runs inside a container where the host filesystem is mounted at `/host`. +2. **Docker Compose Path Resolution**: When the periphery agent executes `docker compose up -d`, it parses the relative paths in the `compose.yaml` (e.g., `./cookies.json:/app/cookies.json`) relative to its *current working directory*, translating them to `/host/opt/schwab-mcp-custom/cookies.json`. +3. **Docker Daemon Absolute Paths**: The periphery agent passes these translated paths to the Docker daemon running on the host machine. The host Docker daemon attempts to bind mount `/host/opt/schwab-mcp-custom/cookies.json`. Since this path doesn't exist on the actual host (the host path is `/opt/...`, without the `/host/` prefix), the Docker engine's default behavior is to automatically create **empty directories** at the missing source path. +4. **Docker Volume Persistence**: Once Docker creates these directories, subsequent `docker compose up -d` commands (even if the `compose.yaml` paths are corrected to absolute host paths) fail to overwrite the directory mounts with file mounts. The container remains locked into the initial, erroneous directory state. + +## Resolution Steps Taken +To break the error loop and establish a clean state, the following manual interventions were required on the `docker-test` host: + +1. **Hard Teardown**: Stopped and removed all containers and networks associated with the `schwab-mcp-custom` stack (`docker compose down`). +2. **Filesystem Purge**: Manually deleted the erroneous directories created by Docker (`rm -rf cookies.json config.json`). +3. **Explicit File Creation**: Explicitly created the required files on the host before Docker could intervene (`touch cookies.json config.json`). +4. **Clean Deployment**: Brought the stack back up manually (`docker compose up -d`), ensuring the bind mounts attached to the newly created files. +5. **Cookie Injection**: Once the file mounts were verified inside the container (`-rw-r--r--`), the valid session cookies were piped directly from the source repository into the host's `/opt/schwab-mcp-custom/cookies.json` file. + +## Conclusion +The `schwab-mcp` service is now running in a structurally sound environment. The scraper has direct, file-level access to the injected session cookies, and the authentication errors stemming from the filesystem mismatch have been eliminated. diff --git a/pyproject.toml b/pyproject.toml index 4dedccd..05dfda5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,13 +9,12 @@ dependencies = [ "fastmcp>=0.4.1", "starlette>=0.41.0", "uvicorn>=0.32.0", - "httpx>=0.27.0", "aiohttp>=3.9.0", "fastapi>=0.136.1", "greenlet>=3.2.3", "pdfplumber>=0.11.4", - "playwright==1.54.0", - "pyee>=13.0.0", + "playwright==1.50.0", + "pyee>=12.0.0,<13.0.0", "typing-extensions>=4.14.0", ] diff --git a/scripts/upload_cookies.py b/scripts/upload_cookies.py index fa946b1..fe06252 100755 --- a/scripts/upload_cookies.py +++ b/scripts/upload_cookies.py @@ -54,11 +54,15 @@ def main(): print(f"Sending cookies to {mcp_endpoint}...") + headers = { + "Accept": "application/json, text/event-stream" + } + try: # Note: We use a POST to the /mcp endpoint. # In SSE mode, the real endpoint is often dynamic, but many FastMCP # wrappers handle direct POSTs for automation. - response = httpx.post(mcp_endpoint, json=payload, timeout=60.0) + response = httpx.post(mcp_endpoint, json=payload, headers=headers, timeout=60.0) if response.status_code == 200: result = response.json() diff --git a/upload_cookies.py b/upload_cookies.py deleted file mode 100755 index 5d27ece..0000000 --- a/upload_cookies.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python3 -import json -import sys -import argparse -import httpx -import asyncio - -async def upload_cookies(url, cookie_file): - try: - with open(cookie_file, 'r') as f: - cookies = json.load(f) - cookies_str = json.dumps(cookies) - except Exception as e: - print(f"Error reading {cookie_file}: {e}") - return - - # MCP tool call payload - payload = { - "jsonrpc": "2.0", - "id": 1, - "method": "tools/call", - "params": { - "name": "upload_cookies", - "arguments": { - "cookies_json": cookies_str - } - } - } - - # Construct the /mcp endpoint if not provided - if not url.endswith('/mcp'): - url = url.rstrip('/') + '/mcp' - - print(f"Uploading cookies to {url}...") - - async with httpx.AsyncClient(timeout=30.0) as client: - try: - response = await client.post(url, json=payload) - response.raise_for_status() - result = response.json() - - if "error" in result: - print(f"Error from server: {result['error']}") - else: - print(f"Server response: {result['result']['content'][0]['text']}") - - except Exception as e: - print(f"Failed to upload cookies: {e}") - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Upload cookies.json to Schwab MCP server") - parser.add_argument("file", help="Path to cookies.json") - parser.add_argument("--url", default="http://localhost:8160", help="MCP server URL (default: http://localhost:8160)") - - args = parser.parse_args() - - asyncio.run(upload_cookies(args.url, args.file)) diff --git a/uv.lock b/uv.lock index 5ded445..24fe292 100644 --- a/uv.lock +++ b/uv.lock @@ -1179,21 +1179,20 @@ wheels = [ [[package]] name = "playwright" -version = "1.54.0" +version = "1.50.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet" }, { name = "pyee" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/09/33d5bfe393a582d8dac72165a9e88b274143c9df411b65ece1cc13f42988/playwright-1.54.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:bf3b845af744370f1bd2286c2a9536f474cc8a88dc995b72ea9a5be714c9a77d", size = 40439034, upload-time = "2025-07-22T13:58:04.816Z" }, - { url = "https://files.pythonhosted.org/packages/e1/7b/51882dc584f7aa59f446f2bb34e33c0e5f015de4e31949e5b7c2c10e54f0/playwright-1.54.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:780928b3ca2077aea90414b37e54edd0c4bbb57d1aafc42f7aa0b3fd2c2fac02", size = 38702308, upload-time = "2025-07-22T13:58:08.211Z" }, - { url = "https://files.pythonhosted.org/packages/73/a1/7aa8ae175b240c0ec8849fcf000e078f3c693f9aa2ffd992da6550ea0dff/playwright-1.54.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:81d0b6f28843b27f288cfe438af0a12a4851de57998009a519ea84cee6fbbfb9", size = 40439037, upload-time = "2025-07-22T13:58:11.37Z" }, - { url = "https://files.pythonhosted.org/packages/34/a9/45084fd23b6206f954198296ce39b0acf50debfdf3ec83a593e4d73c9c8a/playwright-1.54.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:09919f45cc74c64afb5432646d7fef0d19fff50990c862cb8d9b0577093f40cc", size = 45920135, upload-time = "2025-07-22T13:58:14.494Z" }, - { url = "https://files.pythonhosted.org/packages/02/d4/6a692f4c6db223adc50a6e53af405b45308db39270957a6afebddaa80ea2/playwright-1.54.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ae206c55737e8e3eae51fb385d61c0312eeef31535643bb6232741b41b6fdc", size = 45302695, upload-time = "2025-07-22T13:58:18.901Z" }, - { url = "https://files.pythonhosted.org/packages/72/7a/4ee60a1c3714321db187bebbc40d52cea5b41a856925156325058b5fca5a/playwright-1.54.0-py3-none-win32.whl", hash = "sha256:0b108622ffb6906e28566f3f31721cd57dda637d7e41c430287804ac01911f56", size = 35469309, upload-time = "2025-07-22T13:58:21.917Z" }, - { url = "https://files.pythonhosted.org/packages/aa/77/8f8fae05a242ef639de963d7ae70a69d0da61d6d72f1207b8bbf74ffd3e7/playwright-1.54.0-py3-none-win_amd64.whl", hash = "sha256:9e5aee9ae5ab1fdd44cd64153313a2045b136fcbcfb2541cc0a3d909132671a2", size = 35469311, upload-time = "2025-07-22T13:58:24.707Z" }, - { url = "https://files.pythonhosted.org/packages/33/ff/99a6f4292a90504f2927d34032a4baf6adb498dc3f7cf0f3e0e22899e310/playwright-1.54.0-py3-none-win_arm64.whl", hash = "sha256:a975815971f7b8dca505c441a4c56de1aeb56a211290f8cc214eeef5524e8d75", size = 31239119, upload-time = "2025-07-22T13:58:27.56Z" }, + { url = "https://files.pythonhosted.org/packages/0d/5e/068dea3c96e9c09929b45c92cf7e573403b52a89aa463f89b9da9b87b7a4/playwright-1.50.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:f36d754a6c5bd9bf7f14e8f57a2aea6fd08f39ca4c8476481b9c83e299531148", size = 40277564, upload-time = "2025-02-03T14:57:22.774Z" }, + { url = "https://files.pythonhosted.org/packages/78/85/b3deb3d2add00d2a6ee74bf6f57ccefb30efc400fd1b7b330ba9a3626330/playwright-1.50.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:40f274384591dfd27f2b014596250b2250c843ed1f7f4ef5d2960ecb91b4961e", size = 39521844, upload-time = "2025-02-03T14:57:29.372Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f6/002b3d98df9c84296fea84f070dc0d87c2270b37f423cf076a913370d162/playwright-1.50.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:9922ef9bcd316995f01e220acffd2d37a463b4ad10fd73e388add03841dfa230", size = 40277563, upload-time = "2025-02-03T14:57:36.291Z" }, + { url = "https://files.pythonhosted.org/packages/b9/63/c9a73736e434df894e484278dddc0bf154312ff8d0f16d516edb790a7d42/playwright-1.50.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:8fc628c492d12b13d1f347137b2ac6c04f98197ff0985ef0403a9a9ee0d39131", size = 45076712, upload-time = "2025-02-03T14:57:43.581Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2c/a54b5a64cc7d1a62f2d944c5977fb3c88e74d76f5cdc7966e717426bce66/playwright-1.50.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcff35f72db2689a79007aee78f1b0621a22e6e3d6c1f58aaa9ac805bf4497c", size = 44493111, upload-time = "2025-02-03T14:57:50.226Z" }, + { url = "https://files.pythonhosted.org/packages/2b/4a/047cbb2ffe1249bd7a56441fc3366fb4a8a1f44bc36a9061d10edfda2c86/playwright-1.50.0-py3-none-win32.whl", hash = "sha256:3b906f4d351260016a8c5cc1e003bb341651ae682f62213b50168ed581c7558a", size = 34784543, upload-time = "2025-02-03T14:57:55.942Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2b/e944e10c9b18e77e43d3bb4d6faa323f6cc27597db37b75bc3fd796adfd5/playwright-1.50.0-py3-none-win_amd64.whl", hash = "sha256:1859423da82de631704d5e3d88602d755462b0906824c1debe140979397d2e8d", size = 34784546, upload-time = "2025-02-03T14:58:01.664Z" }, ] [[package]] @@ -1425,14 +1424,14 @@ wheels = [ [[package]] name = "pyee" -version = "13.0.1" +version = "12.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/04/e7c1fe4dc78a6fdbfd6c337b1c3732ff543b8a397683ab38378447baa331/pyee-13.0.1.tar.gz", hash = "sha256:0b931f7c14535667ed4c7e0d531716368715e860b988770fc7eb8578d1f67fc8", size = 31655, upload-time = "2026-02-14T21:12:28.044Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/37/8fb6e653597b2b67ef552ed49b438d5398ba3b85a9453f8ada0fd77d455c/pyee-12.1.1.tar.gz", hash = "sha256:bbc33c09e2ff827f74191e3e5bbc6be7da02f627b7ec30d86f5ce1a6fb2424a3", size = 30915, upload-time = "2024-11-16T21:26:44.275Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl", hash = "sha256:af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228", size = 15659, upload-time = "2026-02-14T21:12:26.263Z" }, + { url = "https://files.pythonhosted.org/packages/25/68/7e150cba9eeffdeb3c5cecdb6896d70c8edd46ce41c0491e12fb2b2256ff/pyee-12.1.1-py3-none-any.whl", hash = "sha256:18a19c650556bb6b32b406d7f017c8f513aceed1ef7ca618fb65de7bd2d347ef", size = 15527, upload-time = "2024-11-16T21:26:42.422Z" }, ] [[package]] @@ -1715,7 +1714,6 @@ dependencies = [ { name = "fastapi" }, { name = "fastmcp" }, { name = "greenlet" }, - { name = "httpx" }, { name = "mcp" }, { name = "pdfplumber" }, { name = "playwright" }, @@ -1731,11 +1729,10 @@ requires-dist = [ { name = "fastapi", specifier = ">=0.136.1" }, { name = "fastmcp", specifier = ">=0.4.1" }, { name = "greenlet", specifier = ">=3.2.3" }, - { name = "httpx", specifier = ">=0.27.0" }, { name = "mcp", specifier = ">=1.2.0" }, { name = "pdfplumber", specifier = ">=0.11.4" }, - { name = "playwright", specifier = "==1.54.0" }, - { name = "pyee", specifier = ">=13.0.0" }, + { name = "playwright", specifier = "==1.50.0" }, + { name = "pyee", specifier = ">=12.0.0,<13.0.0" }, { name = "starlette", specifier = ">=0.41.0" }, { name = "typing-extensions", specifier = ">=4.14.0" }, { name = "uvicorn", specifier = ">=0.32.0" },