media-agent-mcp 2.6.3__tar.gz → 2.6.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/PKG-INFO +1 -1
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/pyproject.toml +1 -1
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/async_server.py +53 -1
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/server.py +60 -1
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp.egg-info/PKG-INFO +1 -1
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/README.md +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/setup.cfg +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/__init__.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/ai_models/__init__.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/ai_models/omni_human.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/ai_models/openaiedit.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/ai_models/seed16.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/ai_models/seedance.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/ai_models/seededit.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/ai_models/seedream.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/ai_models/tts.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/async_wrapper.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/audio/combiner.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/audio/speed_controller.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/audio/tts.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/README.md +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/__init__.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/__pycache__/__init__.cpython-312.pyc +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/__pycache__/app.cpython-312.pyc +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/__pycache__/routes_media.cpython-312.pyc +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/__pycache__/routes_omni.cpython-312.pyc +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/__pycache__/routes_subtitles.cpython-312.pyc +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/__pycache__/utils.cpython-312.pyc +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/app.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/fonts/en/EduNSWACTCursive-VariableFont_wght.ttf +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/fonts/en/MozillaText-VariableFont_wght.ttf +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/fonts/en/Roboto_Condensed-Regular.ttf +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/fonts/zh/MaShanZheng-Regular.ttf +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/fonts/zh/NotoSerifSC-VariableFont_wght.ttf +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/fonts/zh/ZCOOLXiaoWei-Regular.ttf +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/pyproject.toml +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/routes_media.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/routes_subtitles.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/utils.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/be/uv.lock +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/install_tools/__init__.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/install_tools/installer.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/media_selectors/__init__.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/media_selectors/image_selector.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/media_selectors/video_selector.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/storage/__init__.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/storage/tos_client.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/video/__init__.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/video/processor.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/video/stack.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/video/subtitle.py +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp.egg-info/SOURCES.txt +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp.egg-info/dependency_links.txt +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp.egg-info/entry_points.txt +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp.egg-info/requires.txt +0 -0
- {media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp.egg-info/top_level.txt +0 -0
@@ -24,6 +24,7 @@ from typing import List, Optional
|
|
24
24
|
import json
|
25
25
|
from dotenv import load_dotenv
|
26
26
|
import uvicorn
|
27
|
+
import anyio
|
27
28
|
from functools import wraps
|
28
29
|
|
29
30
|
def async_retry(max_retries=3, delay=2):
|
@@ -80,6 +81,57 @@ from media_agent_mcp.async_wrapper import (
|
|
80
81
|
logging.basicConfig(level=logging.INFO)
|
81
82
|
logger = logging.getLogger(__name__)
|
82
83
|
|
84
|
+
# Swallow ClosedResourceError from AnyIO (e.g., SSE client disconnected)
|
85
|
+
class IgnoreClosedResourceErrorMiddleware:
|
86
|
+
def __init__(self, app):
|
87
|
+
self.app = app
|
88
|
+
|
89
|
+
async def __call__(self, scope, receive, send):
|
90
|
+
try:
|
91
|
+
await self.app(scope, receive, send)
|
92
|
+
except anyio.ClosedResourceError:
|
93
|
+
logger.warning("SSE client disconnected (ClosedResourceError). Ignoring.")
|
94
|
+
return
|
95
|
+
|
96
|
+
# Enhanced middleware that supports reconnection for SSE clients
|
97
|
+
class ReconnectableSSEMiddleware:
|
98
|
+
def __init__(self, app):
|
99
|
+
self.app = app
|
100
|
+
self.connections = {} # 存储活跃连接
|
101
|
+
|
102
|
+
async def __call__(self, scope, receive, send):
|
103
|
+
# 为每个连接生成唯一ID
|
104
|
+
connection_id = scope.get('client', ('unknown', 0))[0] + ':' + str(scope.get('client', ('unknown', 0))[1])
|
105
|
+
|
106
|
+
# 包装send函数以跟踪连接状态
|
107
|
+
original_send = send
|
108
|
+
|
109
|
+
async def wrapped_send(message):
|
110
|
+
if message.get('type') == 'http.response.start':
|
111
|
+
# 记录新连接
|
112
|
+
self.connections[connection_id] = {'active': True}
|
113
|
+
logger.info(f"New SSE connection established: {connection_id}")
|
114
|
+
elif message.get('type') == 'http.response.body' and message.get('more_body', False) is False:
|
115
|
+
# 连接结束
|
116
|
+
if connection_id in self.connections:
|
117
|
+
self.connections[connection_id]['active'] = False
|
118
|
+
logger.info(f"SSE connection closed normally: {connection_id}")
|
119
|
+
|
120
|
+
# 调用原始send
|
121
|
+
await original_send(message)
|
122
|
+
|
123
|
+
try:
|
124
|
+
# 使用包装后的send函数
|
125
|
+
await self.app(scope, receive, wrapped_send)
|
126
|
+
except anyio.ClosedResourceError:
|
127
|
+
# 客户端断开连接
|
128
|
+
if connection_id in self.connections:
|
129
|
+
self.connections[connection_id]['active'] = False
|
130
|
+
|
131
|
+
logger.warning(f"SSE client disconnected (ClosedResourceError): {connection_id}. Client can reconnect.")
|
132
|
+
# 不抛出异常,允许客户端重连
|
133
|
+
return
|
134
|
+
|
83
135
|
# Initialize FastMCP server (will be configured in main function)
|
84
136
|
load_dotenv()
|
85
137
|
mcp = FastMCP("Media-Agent-MCP-Async")
|
@@ -524,7 +576,7 @@ def main():
|
|
524
576
|
mcp.settings.port = args.port
|
525
577
|
# Use uvicorn to run SSE app with extended keep-alive timeout (5 minutes)
|
526
578
|
uvicorn.run(
|
527
|
-
mcp.sse_app(),
|
579
|
+
ReconnectableSSEMiddleware(mcp.sse_app()),
|
528
580
|
host=args.host,
|
529
581
|
port=args.port,
|
530
582
|
timeout_keep_alive=300
|
@@ -20,6 +20,8 @@ from typing import Optional, Dict, Any
|
|
20
20
|
import json
|
21
21
|
from dotenv import load_dotenv
|
22
22
|
import uvicorn
|
23
|
+
import anyio
|
24
|
+
import uuid
|
23
25
|
|
24
26
|
from mcp.server.fastmcp import FastMCP
|
25
27
|
|
@@ -39,6 +41,63 @@ from media_agent_mcp.media_selectors.video_selector import select_best_video
|
|
39
41
|
logging.basicConfig(level=logging.INFO)
|
40
42
|
logger = logging.getLogger(__name__)
|
41
43
|
|
44
|
+
# Swallow ClosedResourceError from AnyIO (e.g., SSE client disconnected)
|
45
|
+
class IgnoreClosedResourceErrorMiddleware:
|
46
|
+
def __init__(self, app):
|
47
|
+
self.app = app
|
48
|
+
|
49
|
+
async def __call__(self, scope, receive, send):
|
50
|
+
try:
|
51
|
+
await self.app(scope, receive, send)
|
52
|
+
except anyio.ClosedResourceError:
|
53
|
+
logger.warning("SSE client disconnected (ClosedResourceError). Ignoring.")
|
54
|
+
return
|
55
|
+
|
56
|
+
# Enhanced middleware that supports reconnection for SSE clients
|
57
|
+
class ReconnectableSSEMiddleware:
|
58
|
+
def __init__(self, app):
|
59
|
+
self.app = app
|
60
|
+
self.connections = {}
|
61
|
+
|
62
|
+
async def __call__(self, scope, receive, send):
|
63
|
+
# Generate a unique connection ID
|
64
|
+
connection_id = str(uuid.uuid4())
|
65
|
+
|
66
|
+
# Store connection info
|
67
|
+
if scope["type"] == "http":
|
68
|
+
self.connections[connection_id] = {
|
69
|
+
"status": "active",
|
70
|
+
"path": scope.get("path", ""),
|
71
|
+
"created_at": anyio.current_time()
|
72
|
+
}
|
73
|
+
|
74
|
+
# Wrap the send function to track connection state
|
75
|
+
original_send = send
|
76
|
+
|
77
|
+
async def wrapped_send(message):
|
78
|
+
if message.get("type") == "http.response.body" and message.get("more_body", False) is False:
|
79
|
+
# Connection is closing normally
|
80
|
+
if connection_id in self.connections:
|
81
|
+
self.connections[connection_id]["status"] = "closed"
|
82
|
+
return await original_send(message)
|
83
|
+
|
84
|
+
try:
|
85
|
+
# Use the wrapped send function
|
86
|
+
await self.app(scope, receive, wrapped_send)
|
87
|
+
except anyio.ClosedResourceError:
|
88
|
+
# Client disconnected, but we'll allow reconnection
|
89
|
+
if connection_id in self.connections:
|
90
|
+
self.connections[connection_id]["status"] = "disconnected"
|
91
|
+
logger.warning(f"SSE client disconnected (ClosedResourceError). Connection {connection_id[:8]} marked for reconnection.")
|
92
|
+
return
|
93
|
+
finally:
|
94
|
+
# Clean up connection tracking after some time
|
95
|
+
if connection_id in self.connections:
|
96
|
+
self.connections.pop(connection_id, None)
|
97
|
+
else:
|
98
|
+
# For non-HTTP connections, just pass through
|
99
|
+
await self.app(scope, receive, send)
|
100
|
+
|
42
101
|
# Initialize FastMCP server (will be configured in main function)
|
43
102
|
load_dotenv()
|
44
103
|
mcp = FastMCP("Media-Agent-MCP")
|
@@ -577,7 +636,7 @@ def main():
|
|
577
636
|
# Configure and run the server
|
578
637
|
if args.transport == 'sse':
|
579
638
|
# SSE transport
|
580
|
-
uvicorn.run(mcp.create_sse_app(), host=args.host, port=args.port)
|
639
|
+
uvicorn.run(ReconnectableSSEMiddleware(mcp.create_sse_app()), host=args.host, port=args.port)
|
581
640
|
else:
|
582
641
|
# STDIO transport (default)
|
583
642
|
mcp.run()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/audio/speed_controller.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/install_tools/__init__.py
RENAMED
File without changes
|
{media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/install_tools/installer.py
RENAMED
File without changes
|
{media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp/media_selectors/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp.egg-info/dependency_links.txt
RENAMED
File without changes
|
{media_agent_mcp-2.6.3 → media_agent_mcp-2.6.5}/src/media_agent_mcp.egg-info/entry_points.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|