From a8655ff207681a9cee5b638fea32dd10dce29db6 Mon Sep 17 00:00:00 2001 From: Rafael Poyiadzi Date: Fri, 27 Feb 2026 21:25:19 +0000 Subject: [PATCH 1/6] Add info-level logging to all MCP tools and upload proxy - Log entry params for browse_lists, use_list, progress, results, list_sessions, balance, and cancel (previously only logged on error) - Log success outcomes (result counts, artifact_ids, balance amounts) - Log upload_id + filename on presigned URL request - Log upload_id + size on proxy start, artifact_id on proxy completion - Log proxy error responses with status and body Co-Authored-By: Claude Opus 4.6 --- everyrow-mcp/src/everyrow_mcp/tools.py | 28 ++++++++++++++++ everyrow-mcp/src/everyrow_mcp/uploads.py | 41 ++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/everyrow-mcp/src/everyrow_mcp/tools.py b/everyrow-mcp/src/everyrow_mcp/tools.py index 92ef4e48..3fb8004e 100644 --- a/everyrow-mcp/src/everyrow_mcp/tools.py +++ b/everyrow-mcp/src/everyrow_mcp/tools.py @@ -137,6 +137,11 @@ async def everyrow_browse_lists( Call with no parameters to see all available lists, or use search/category to narrow results. """ + logger.info( + "everyrow_browse_lists: search=%s category=%s", + params.search, + params.category, + ) client = _get_client(ctx) try: @@ -156,6 +161,7 @@ async def everyrow_browse_lists( ) ] + logger.info("everyrow_browse_lists: found %d list(s)", len(results)) lines = [f"Found {len(results)} built-in list(s):\n"] for i, item in enumerate(results, 1): fields_str = ", ".join(item.fields) if item.fields else "(no fields listed)" @@ -193,6 +199,7 @@ async def everyrow_use_list( The copy is a fast database operation (<1s) — no polling needed. """ + logger.info("everyrow_use_list: artifact_id=%s", params.artifact_id) client = _get_client(ctx) try: @@ -211,6 +218,11 @@ async def everyrow_use_list( except Exception as e: return [TextContent(type="text", text=f"Error importing built-in list: {e!r}")] + logger.info( + "everyrow_use_list: imported artifact_id=%s rows=%d", + result.artifact_id, + len(df), + ) return [ TextContent( type="text", @@ -993,6 +1005,7 @@ async def everyrow_progress( unless the task is completed or failed. The tool handles pacing internally. Do not add commentary between progress calls, just call again immediately. """ + logger.info("everyrow_progress: task_id=%s", params.task_id) client = _get_client(ctx) task_id = params.task_id @@ -1044,6 +1057,7 @@ async def everyrow_results_stdio( Only call this after everyrow_progress reports status 'completed'. Pass output_path (ending in .csv) to save results as a local CSV file. """ + logger.info("everyrow_results (stdio): task_id=%s", params.task_id) client = _get_client(ctx) task_id = params.task_id @@ -1092,6 +1106,12 @@ async def everyrow_results_http( controls how many rows _you_ can read. After results load, tell the user how many rows you can see vs the total. """ + logger.info( + "everyrow_results (http): task_id=%s offset=%s page_size=%s", + params.task_id, + params.offset, + params.page_size, + ) client = _get_client(ctx) task_id = params.task_id mcp_server_url = ctx.request_context.lifespan_context.mcp_server_url @@ -1186,6 +1206,11 @@ async def everyrow_list_sessions( Use this to find past sessions or check what's been run. Results are paginated — 25 sessions per page by default. """ + logger.info( + "everyrow_list_sessions: offset=%s limit=%s", + params.offset, + params.limit, + ) log_client_info(ctx, "everyrow_list_sessions") client = _get_client(ctx) @@ -1251,6 +1276,7 @@ async def everyrow_balance(ctx: EveryRowContext) -> list[TextContent]: Returns the account balance in dollars. Use this to verify available credits before submitting tasks. """ + logger.info("everyrow_balance: called") client = _get_client(ctx) try: @@ -1266,6 +1292,7 @@ async def everyrow_balance(ctx: EveryRowContext) -> list[TextContent]: ) ] + logger.info("everyrow_balance: $%.2f", response.current_balance_dollars) return [ TextContent( type="text", @@ -1341,6 +1368,7 @@ async def everyrow_cancel( params: CancelInput, ctx: EveryRowContext ) -> list[TextContent]: """Cancel a running everyrow task. Use when the user wants to stop a task that is currently processing.""" + logger.info("everyrow_cancel: task_id=%s", params.task_id) log_client_info(ctx, "everyrow_cancel") client = _get_client(ctx) task_id = params.task_id diff --git a/everyrow-mcp/src/everyrow_mcp/uploads.py b/everyrow-mcp/src/everyrow_mcp/uploads.py index e5a7efae..5e48fad6 100644 --- a/everyrow-mcp/src/everyrow_mcp/uploads.py +++ b/everyrow-mcp/src/everyrow_mcp/uploads.py @@ -119,12 +119,13 @@ async def request_upload_url( try: engine_upload_url = data["upload_url"] + upload_id = data["upload_id"] # Rewrite the URL to point at the MCP server instead of the Engine. # The Claude.ai sandbox can reach the MCP server but not api.everyrow.ai. upload_url = _rewrite_upload_url(engine_upload_url, mcp_server_url) result = { "upload_url": upload_url, - "upload_id": data["upload_id"], + "upload_id": upload_id, "expires_in": data["expires_in"], "max_size_bytes": data["max_size_bytes"], "curl_command": f'curl -X PUT -H "Content-Type: text/csv" -T {shlex.quote(params.filename)} {shlex.quote(upload_url)}', @@ -138,6 +139,12 @@ async def request_upload_url( ) ] + logger.info( + "Upload URL requested: upload_id=%s filename=%s expires_in=%s", + upload_id, + params.filename, + data.get("expires_in"), + ) return [TextContent(type="text", text=json.dumps(result))] @@ -205,19 +212,49 @@ async def proxy_upload(request: Request) -> Response: engine_url = f"{engine_url}?{request.url.query}" body = await request.body() + size_bytes = len(body) headers = { k: v for k, v in request.headers.items() if k.lower() in ("content-type", "content-length") } + logger.info( + "Upload proxy started: upload_id=%s size_bytes=%d", + upload_id, + size_bytes, + ) + try: async with httpx.AsyncClient(timeout=_PROXY_TIMEOUT) as http: resp = await http.put(engine_url, content=body, headers=headers) except httpx.HTTPError as exc: - logger.error("Upload proxy failed: %s", exc) + logger.error("Upload proxy failed: upload_id=%s error=%s", upload_id, exc) return JSONResponse({"detail": "Upload proxy error"}, status_code=502) + if resp.status_code >= 400: + logger.warning( + "Upload proxy error response: upload_id=%s status=%d body=%s", + upload_id, + resp.status_code, + resp.text[:200], + ) + else: + # Parse artifact_id from Engine response for traceability + artifact_id = None + try: + resp_data = resp.json() + artifact_id = resp_data.get("artifact_id") + except Exception: + pass + logger.info( + "Upload proxy completed: upload_id=%s status=%d artifact_id=%s size_bytes=%d", + upload_id, + resp.status_code, + artifact_id, + size_bytes, + ) + return Response( content=resp.content, status_code=resp.status_code, From abd4eb0b4d0b7b19737dd9b0defe836550640b53 Mon Sep 17 00:00:00 2001 From: Rafael Poyiadzi Date: Fri, 27 Feb 2026 21:27:51 +0000 Subject: [PATCH 2/6] Use debug-level logging for progress polling, info only on terminal Avoids log noise from the tight polling loop (~every 3s). Only logs at INFO when the task reaches a terminal state (completed/failed/revoked). Co-Authored-By: Claude Opus 4.6 --- everyrow-mcp/src/everyrow_mcp/tools.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/everyrow-mcp/src/everyrow_mcp/tools.py b/everyrow-mcp/src/everyrow_mcp/tools.py index 3fb8004e..3c2f2b08 100644 --- a/everyrow-mcp/src/everyrow_mcp/tools.py +++ b/everyrow-mcp/src/everyrow_mcp/tools.py @@ -1005,7 +1005,7 @@ async def everyrow_progress( unless the task is completed or failed. The tool handles pacing internally. Do not add commentary between progress calls, just call again immediately. """ - logger.info("everyrow_progress: task_id=%s", params.task_id) + logger.debug("everyrow_progress: task_id=%s", params.task_id) client = _get_client(ctx) task_id = params.task_id @@ -1046,6 +1046,10 @@ async def everyrow_progress( ts = TaskState(status_response) ts.write_file(task_id) + # Only log at INFO for terminal states to avoid noise from polling loops + if ts.is_terminal: + logger.info("everyrow_progress: task_id=%s status=%s", task_id, ts.status.value) + return [TextContent(type="text", text=ts.progress_message(task_id))] From b402f6003305abfff8c451428113682ce4a86b7a Mon Sep 17 00:00:00 2001 From: Rafael Poyiadzi Date: Fri, 27 Feb 2026 21:31:48 +0000 Subject: [PATCH 3/6] Log first and last progress poll, skip intermediate calls Uses a module-level set to track which task_ids are being polled. Logs at INFO on the first call ("polling started") and when the task reaches a terminal state. Cleans up the set on terminal so re-polling after a retry still logs correctly. Co-Authored-By: Claude Opus 4.6 --- everyrow-mcp/src/everyrow_mcp/tools.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/everyrow-mcp/src/everyrow_mcp/tools.py b/everyrow-mcp/src/everyrow_mcp/tools.py index 3c2f2b08..6a1e4427 100644 --- a/everyrow-mcp/src/everyrow_mcp/tools.py +++ b/everyrow-mcp/src/everyrow_mcp/tools.py @@ -75,6 +75,9 @@ logger = logging.getLogger(__name__) +# Track which task_ids have been polled so we only log the first and last call. +_progress_seen: set[str] = set() + async def _check_task_ownership(task_id: str) -> list[TextContent] | None: """Verify the current user owns *task_id*. Returns an error response if @@ -1005,9 +1008,12 @@ async def everyrow_progress( unless the task is completed or failed. The tool handles pacing internally. Do not add commentary between progress calls, just call again immediately. """ - logger.debug("everyrow_progress: task_id=%s", params.task_id) client = _get_client(ctx) task_id = params.task_id + first_poll = task_id not in _progress_seen + if first_poll: + logger.info("everyrow_progress: task_id=%s polling started", task_id) + _progress_seen.add(task_id) # ── Cross-user access check ────────────────────────────────── try: @@ -1046,8 +1052,8 @@ async def everyrow_progress( ts = TaskState(status_response) ts.write_file(task_id) - # Only log at INFO for terminal states to avoid noise from polling loops if ts.is_terminal: + _progress_seen.discard(task_id) logger.info("everyrow_progress: task_id=%s status=%s", task_id, ts.status.value) return [TextContent(type="text", text=ts.progress_message(task_id))] From 48601ab111a03f53558bda2772fe5e072ee3bbb7 Mon Sep 17 00:00:00 2001 From: Rafael Poyiadzi Date: Fri, 27 Feb 2026 21:36:38 +0000 Subject: [PATCH 4/6] Revert in-memory progress set, use debug+terminal INFO instead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The in-memory set doesn't work with multiple replicas — different pods don't share state. Instead: log every poll at DEBUG level (invisible at default INFO), log at INFO only on terminal state. Task submission already logs when polling starts implicitly. Co-Authored-By: Claude Opus 4.6 --- everyrow-mcp/src/everyrow_mcp/tools.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/everyrow-mcp/src/everyrow_mcp/tools.py b/everyrow-mcp/src/everyrow_mcp/tools.py index 6a1e4427..679f8c14 100644 --- a/everyrow-mcp/src/everyrow_mcp/tools.py +++ b/everyrow-mcp/src/everyrow_mcp/tools.py @@ -75,9 +75,6 @@ logger = logging.getLogger(__name__) -# Track which task_ids have been polled so we only log the first and last call. -_progress_seen: set[str] = set() - async def _check_task_ownership(task_id: str) -> list[TextContent] | None: """Verify the current user owns *task_id*. Returns an error response if @@ -1008,12 +1005,9 @@ async def everyrow_progress( unless the task is completed or failed. The tool handles pacing internally. Do not add commentary between progress calls, just call again immediately. """ + logger.debug("everyrow_progress: task_id=%s", params.task_id) client = _get_client(ctx) task_id = params.task_id - first_poll = task_id not in _progress_seen - if first_poll: - logger.info("everyrow_progress: task_id=%s polling started", task_id) - _progress_seen.add(task_id) # ── Cross-user access check ────────────────────────────────── try: @@ -1053,7 +1047,6 @@ async def everyrow_progress( ts.write_file(task_id) if ts.is_terminal: - _progress_seen.discard(task_id) logger.info("everyrow_progress: task_id=%s status=%s", task_id, ts.status.value) return [TextContent(type="text", text=ts.progress_message(task_id))] From 97455ba536548d0366c36c270df6854e40fe9ef5 Mon Sep 17 00:00:00 2001 From: Rafael Poyiadzi Date: Fri, 27 Feb 2026 21:59:12 +0000 Subject: [PATCH 5/6] Fix tuple unpacking bug in everyrow_use_list _fetch_task_result returns (df, session_id, artifact_id) but the call site only unpacked 2 values, causing ValueError at runtime every time everyrow_use_list was called. Co-Authored-By: Claude Opus 4.6 --- everyrow-mcp/src/everyrow_mcp/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/everyrow-mcp/src/everyrow_mcp/tools.py b/everyrow-mcp/src/everyrow_mcp/tools.py index 679f8c14..2767ba08 100644 --- a/everyrow-mcp/src/everyrow_mcp/tools.py +++ b/everyrow-mcp/src/everyrow_mcp/tools.py @@ -211,7 +211,7 @@ async def everyrow_use_list( ) # Fetch the copied data and save as CSV - df, _ = await _fetch_task_result(client, str(result.task_id)) + df, _, _ = await _fetch_task_result(client, str(result.task_id)) csv_path = Path.cwd() / f"built-in-list-{result.artifact_id}.csv" df.to_csv(csv_path, index=False) From ab701bee500aae3c61df88c5e65b54286722867d Mon Sep 17 00:00:00 2001 From: Rafael Poyiadzi Date: Fri, 27 Feb 2026 22:55:38 +0000 Subject: [PATCH 6/6] Add logging to everyrow_list_session_tasks New tool added on main was missing info-level logging. Co-Authored-By: Claude Opus 4.6 --- everyrow-mcp/src/everyrow_mcp/tools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/everyrow-mcp/src/everyrow_mcp/tools.py b/everyrow-mcp/src/everyrow_mcp/tools.py index 2767ba08..4bb45a61 100644 --- a/everyrow-mcp/src/everyrow_mcp/tools.py +++ b/everyrow-mcp/src/everyrow_mcp/tools.py @@ -1323,6 +1323,7 @@ async def everyrow_list_session_tasks( Use this to find task IDs for a session so you can display previous results with mcp__display__show_task(task_id, label). """ + logger.info("everyrow_list_session_tasks: session_id=%s", params.session_id) client = _get_client(ctx) try: