fix: monkey-patch mcp cancellation race crash (SDK issue #2416)
Some checks failed
Build and Push Docker Image / build (push) Failing after 28s
Some checks failed
Build and Push Docker Image / build (push) Failing after 28s
Patch RequestResponder.respond() and cancel() at startup to handle the race where a notifications/cancelled arrives between handler return and respond(), which crashes the session with "AssertionError: Request already responded to". Also improve build.sh to handle registry push failures gracefully and auto-restart the container after building.
This commit is contained in:
@@ -6,6 +6,7 @@ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|||||||
SCRAPER_DIR="/home/b3nw/projects/financial/schwab-scraper"
|
SCRAPER_DIR="/home/b3nw/projects/financial/schwab-scraper"
|
||||||
IMAGE="gitea.ext.ben.io/b3nw/schwab-mcp-custom:latest"
|
IMAGE="gitea.ext.ben.io/b3nw/schwab-mcp-custom:latest"
|
||||||
BUILD_HOST="${BUILD_HOST:-docker-test}"
|
BUILD_HOST="${BUILD_HOST:-docker-test}"
|
||||||
|
COMPOSE_DIR="/opt/schwab-mcp-custom"
|
||||||
|
|
||||||
cd "$PROJECT_DIR"
|
cd "$PROJECT_DIR"
|
||||||
|
|
||||||
@@ -28,10 +29,17 @@ echo "==> Building Docker image on $BUILD_HOST..."
|
|||||||
ssh "$BUILD_HOST" "cd /tmp/schwab-mcp-build && docker build -t $IMAGE ."
|
ssh "$BUILD_HOST" "cd /tmp/schwab-mcp-build && docker build -t $IMAGE ."
|
||||||
|
|
||||||
echo "==> Pushing image to registry..."
|
echo "==> Pushing image to registry..."
|
||||||
ssh "$BUILD_HOST" "docker push $IMAGE"
|
if ssh "$BUILD_HOST" "docker push $IMAGE" 2>/dev/null; then
|
||||||
|
echo " Image pushed to registry."
|
||||||
|
else
|
||||||
|
echo " Registry push failed (auth?), image available locally only."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Restarting container..."
|
||||||
|
ssh "$BUILD_HOST" "cd $COMPOSE_DIR && docker compose up -d --force-recreate --pull never --no-deps schwab-mcp"
|
||||||
|
|
||||||
echo "==> Cleaning up..."
|
echo "==> Cleaning up..."
|
||||||
rm -rf vendor/schwab-scraper
|
rm -rf vendor/schwab-scraper
|
||||||
ssh "$BUILD_HOST" "rm -rf /tmp/schwab-mcp-build"
|
ssh "$BUILD_HOST" "rm -rf /tmp/schwab-mcp-build"
|
||||||
|
|
||||||
echo "==> Done! Image pushed: $IMAGE"
|
echo "==> Done! Container restarted with new image."
|
||||||
|
|||||||
35
server.py
35
server.py
@@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
from typing import Optional, Any
|
from typing import Optional, Any
|
||||||
|
|
||||||
@@ -11,6 +12,40 @@ import uvicorn
|
|||||||
# Import the unified API from the schwab_scraper dependency
|
# Import the unified API from the schwab_scraper dependency
|
||||||
import schwab_scraper.unified_api as api
|
import schwab_scraper.unified_api as api
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Monkey-patch mcp.shared.session.RequestResponder to work around a
|
||||||
|
# cancellation race in mcp==1.27.0 (github.com/modelcontextprotocol/
|
||||||
|
# python-sdk/issues/2416). A concurrent notifications/cancelled can set
|
||||||
|
# _completed=True between handler return and respond(), crashing the session
|
||||||
|
# with "AssertionError: Request already responded to".
|
||||||
|
# Remove once upstream ships a fix (likely mcp>=1.28).
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def _patch_request_responder():
|
||||||
|
from mcp.shared.session import RequestResponder
|
||||||
|
|
||||||
|
_orig_respond = RequestResponder.respond
|
||||||
|
|
||||||
|
async def _safe_respond(self, response):
|
||||||
|
if self._completed:
|
||||||
|
logging.debug(
|
||||||
|
"respond() skipped for request %s — already completed (race with cancel)",
|
||||||
|
self.request_id,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
return await _orig_respond(self, response)
|
||||||
|
|
||||||
|
_orig_cancel = RequestResponder.cancel
|
||||||
|
|
||||||
|
async def _safe_cancel(self):
|
||||||
|
if self._completed:
|
||||||
|
return
|
||||||
|
return await _orig_cancel(self)
|
||||||
|
|
||||||
|
RequestResponder.respond = _safe_respond
|
||||||
|
RequestResponder.cancel = _safe_cancel
|
||||||
|
|
||||||
|
_patch_request_responder()
|
||||||
|
|
||||||
# Initialize FastMCP
|
# Initialize FastMCP
|
||||||
mcp = FastMCP("SchwabScraper")
|
mcp = FastMCP("SchwabScraper")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user