media-agent-mcp 0.3.8__tar.gz → 0.3.10__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.
Files changed (26) hide show
  1. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/PKG-INFO +2 -1
  2. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/pyproject.toml +2 -1
  3. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/__init__.py +3 -1
  4. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/async_server.py +42 -37
  5. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/async_wrapper.py +9 -9
  6. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp.egg-info/PKG-INFO +2 -1
  7. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp.egg-info/requires.txt +1 -0
  8. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/README.md +0 -0
  9. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/setup.cfg +0 -0
  10. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/ai_models/__init__.py +0 -0
  11. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/ai_models/seed16.py +0 -0
  12. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/ai_models/seedance.py +0 -0
  13. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/ai_models/seededit.py +0 -0
  14. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/ai_models/seedream.py +0 -0
  15. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/media_selectors/__init__.py +0 -0
  16. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/media_selectors/image_selector.py +0 -0
  17. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/media_selectors/video_selector.py +0 -0
  18. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/server.py +0 -0
  19. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/storage/__init__.py +0 -0
  20. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/storage/tos_client.py +0 -0
  21. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/video/__init__.py +0 -0
  22. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp/video/processor.py +0 -0
  23. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp.egg-info/SOURCES.txt +0 -0
  24. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp.egg-info/dependency_links.txt +0 -0
  25. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp.egg-info/entry_points.txt +0 -0
  26. {media_agent_mcp-0.3.8 → media_agent_mcp-0.3.10}/src/media_agent_mcp.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: media-agent-mcp
3
- Version: 0.3.8
3
+ Version: 0.3.10
4
4
  Summary: A Model Context Protocol server for media processing with AI tools
5
5
  Author-email: Media Agent Team <team@mediaagent.com>
6
6
  Keywords: mcp,ai,media,video,image,processing
@@ -22,6 +22,7 @@ Requires-Dist: numpy>=1.24.0
22
22
  Requires-Dist: python-dotenv>=1.0.0
23
23
  Requires-Dist: volcengine-python-sdk>=1.0.0
24
24
  Requires-Dist: volcengine>=1.0.194
25
+ Requires-Dist: tenacity>=8.2.3
25
26
 
26
27
  # Media Agent MCP
27
28
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "media-agent-mcp"
3
- version = "0.3.8"
3
+ version = "0.3.10"
4
4
  description = "A Model Context Protocol server for media processing with AI tools"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -27,6 +27,7 @@ dependencies = [
27
27
  "python-dotenv>=1.0.0",
28
28
  "volcengine-python-sdk>=1.0.0",
29
29
  "volcengine>=1.0.194",
30
+ "tenacity>=8.2.3",
30
31
  ]
31
32
 
32
33
  [project.scripts]
@@ -2,6 +2,8 @@
2
2
 
3
3
  from . import ai_models, media_selectors, storage, video
4
4
  from .server import main
5
+ from .async_server import main as async_main
6
+ from . import async_wrapper
5
7
 
6
8
  __version__ = "0.1.0"
7
- __all__ = ['ai_models', 'media_selectors', 'storage', 'video', 'main']
9
+ __all__ = ['ai_models', 'media_selectors', 'storage', 'video', 'main', 'async_main', 'async_wrapper']
@@ -22,6 +22,17 @@ from typing import List, Optional
22
22
  import json
23
23
  from dotenv import load_dotenv
24
24
  import uvicorn
25
+ from tenacity import retry, stop_after_attempt, wait_fixed
26
+
27
+ def _is_result_error(result):
28
+ """Return True if the result is a dictionary with status 'error'."""
29
+ if isinstance(result, str):
30
+ try:
31
+ data = json.loads(result)
32
+ return isinstance(data, dict) and data.get('status') == 'error'
33
+ except json.JSONDecodeError:
34
+ return False
35
+ return isinstance(result, dict) and result.get('status') == 'error'
25
36
 
26
37
  from mcp.server.fastmcp import FastMCP
27
38
 
@@ -49,7 +60,8 @@ mcp = FastMCP("Media-Agent-MCP-Async")
49
60
 
50
61
 
51
62
  @mcp.tool()
52
- async def video_concat_tool_async(video_urls: List[str]) -> str:
63
+ @retry(stop=stop_after_attempt(3), wait=wait_fixed(2), retry_if_result=_is_result_error)
64
+ async def video_concat_tool(video_urls: List[str]) -> dict:
53
65
  """
54
66
  Asynchronously concatenate multiple videos from URLs and upload to TOS.
55
67
 
@@ -57,13 +69,14 @@ async def video_concat_tool_async(video_urls: List[str]) -> str:
57
69
  video_urls: List of video URLs to concatenate in order
58
70
 
59
71
  Returns:
60
- JSON string with status, data, and message
72
+ Dictionary with status, data, and message
61
73
  """
62
74
  return await async_video_concat_tool(video_urls)
63
75
 
64
76
 
65
77
  @mcp.tool()
66
- async def video_last_frame_tool_async(video_url: str) -> str:
78
+ @retry(stop=stop_after_attempt(3), wait=wait_fixed(2), retry_if_result=_is_result_error)
79
+ async def video_last_frame_tool(video_url: str) -> dict:
67
80
  """
68
81
  Asynchronously extract the last frame from a video file and upload to TOS.
69
82
 
@@ -71,13 +84,14 @@ async def video_last_frame_tool_async(video_url: str) -> str:
71
84
  video_url: URL or path to the video file
72
85
 
73
86
  Returns:
74
- JSON string with status, data, and message
87
+ Dictionary with status, data, and message
75
88
  """
76
89
  return await async_video_last_frame_tool(video_url)
77
90
 
78
91
 
79
92
  @mcp.tool()
80
- async def seedream_generate_image_tool_async(prompt: str, size: str = "1024x1024") -> str:
93
+ @retry(stop=stop_after_attempt(3), wait=wait_fixed(2), retry_if_result=_is_result_error)
94
+ async def seedream_generate_image_tool(prompt: str, size: str = "1024x1024") -> dict:
81
95
  """
82
96
  Asynchronously generate an image using Seedream AI model.
83
97
 
@@ -86,15 +100,16 @@ async def seedream_generate_image_tool_async(prompt: str, size: str = "1024x1024
86
100
  size: Size of the image (e.g., "1024x1024")
87
101
 
88
102
  Returns:
89
- JSON string with status, data, and message
103
+ Dictionary with status, data, and message
90
104
  """
91
105
  return await async_seedream_generate_image_tool(prompt, size)
92
106
 
93
107
 
94
108
  @mcp.tool()
95
- async def seedance_generate_video_tool_async(prompt: str, first_frame_image: str,
109
+ @retry(stop=stop_after_attempt(3), wait=wait_fixed(2), retry_if_result=_is_result_error)
110
+ async def seedance_generate_video_tool(prompt: str, first_frame_image: str,
96
111
  last_frame_image: str = None, duration: int = 5,
97
- resolution: str = "720p") -> str:
112
+ resolution: str = "720p") -> dict:
98
113
  """
99
114
  Asynchronously generate a video using Seedance AI model with first/last frame images.
100
115
 
@@ -106,14 +121,15 @@ async def seedance_generate_video_tool_async(prompt: str, first_frame_image: str
106
121
  resolution: Video resolution (480p, 720p)
107
122
 
108
123
  Returns:
109
- JSON string with status, data, and message
124
+ Dictionary with status, data, and message
110
125
  """
111
126
  return await async_seedance_generate_video_tool(prompt, first_frame_image, last_frame_image, duration, resolution)
112
127
 
113
128
 
114
129
  @mcp.tool()
115
- async def seededit_tool_async(image_url: str, prompt: str, seed: int = -1,
116
- scale: float = 0.5, charactor_keep: bool = False) -> str:
130
+ @retry(stop=stop_after_attempt(3), wait=wait_fixed(2), retry_if_result=_is_result_error)
131
+ async def seededit_tool(image_url: str, prompt: str, seed: int = -1,
132
+ scale: float = 0.5, charactor_keep: bool = False) -> dict:
117
133
  """
118
134
  Asynchronously edit an image using Seededit model.
119
135
 
@@ -125,13 +141,14 @@ async def seededit_tool_async(image_url: str, prompt: str, seed: int = -1,
125
141
  charactor_keep: whether to keep the main character in this image, if you wanna change the main character, please keep False
126
142
 
127
143
  Returns:
128
- JSON string with status, data, and message
144
+ Dictionary with status, data, and message
129
145
  """
130
146
  return await async_seededit_tool(image_url, prompt, seed, scale, charactor_keep)
131
147
 
132
148
 
133
149
  @mcp.tool()
134
- async def vlm_vision_task_tool_async(messages: List) -> str:
150
+ @retry(stop=stop_after_attempt(3), wait=wait_fixed(2), retry_if_result=_is_result_error)
151
+ async def vlm_vision_task_tool(messages: List) -> dict:
135
152
  """
136
153
  Asynchronously perform vision-language tasks using VLM model.
137
154
 
@@ -139,13 +156,14 @@ async def vlm_vision_task_tool_async(messages: List) -> str:
139
156
  messages: OpenAI-compatible messages format
140
157
 
141
158
  Returns:
142
- JSON string with status, data, and message
159
+ Dictionary with status, data, and message
143
160
  """
144
161
  return await async_vlm_vision_task_tool(messages)
145
162
 
146
163
 
147
164
  @mcp.tool()
148
- async def image_selector_tool_async(image_paths: List[str], prompt: str) -> str:
165
+ @retry(stop=stop_after_attempt(3), wait=wait_fixed(2), retry_if_result=_is_result_error)
166
+ async def image_selector_tool(image_paths: List[str], prompt: str) -> dict:
149
167
  """
150
168
  Asynchronously select the best image from multiple options using VLM model.
151
169
 
@@ -154,13 +172,14 @@ async def image_selector_tool_async(image_paths: List[str], prompt: str) -> str:
154
172
  prompt: Selection criteria prompt
155
173
 
156
174
  Returns:
157
- JSON string with status, data, and message
175
+ Dictionary with status, data, and message
158
176
  """
159
177
  return await async_image_selector_tool(image_paths, prompt)
160
178
 
161
179
 
162
180
  @mcp.tool()
163
- async def video_selector_tool_async(video_paths: List[str], prompt: str) -> str:
181
+ @retry(stop=stop_after_attempt(3), wait=wait_fixed(2), retry_if_result=_is_result_error)
182
+ async def video_selector_tool(video_paths: List[str], prompt: str) -> dict:
164
183
  """
165
184
  Asynchronously select the best video from multiple options using VLM model.
166
185
 
@@ -169,14 +188,15 @@ async def video_selector_tool_async(video_paths: List[str], prompt: str) -> str:
169
188
  prompt: Selection criteria prompt
170
189
 
171
190
  Returns:
172
- JSON string with status, data, and message
191
+ Dictionary with status, data, and message
173
192
  """
174
193
  return await async_video_selector_tool(video_paths, prompt)
175
194
 
176
195
 
177
196
  @mcp.tool()
178
- async def tos_save_content_tool_async(content: str, file_extension: str = "txt",
179
- object_key: Optional[str] = None) -> str:
197
+ @retry(stop=stop_after_attempt(3), wait=wait_fixed(2), retry_if_result=_is_result_error)
198
+ async def tos_save_content_tool(content: str, file_extension: str = "txt",
199
+ object_key: Optional[str] = None) -> dict:
180
200
  """
181
201
  Asynchronously save content to TOS and return URL.
182
202
 
@@ -186,7 +206,7 @@ async def tos_save_content_tool_async(content: str, file_extension: str = "txt",
186
206
  object_key: Optional key to use for the object in TOS
187
207
 
188
208
  Returns:
189
- JSON string with status, data, and message
209
+ Dictionary with status, data, and message
190
210
  """
191
211
  return await async_tos_save_content_tool(content, file_extension, object_key)
192
212
 
@@ -205,21 +225,6 @@ async def run_multiple_tools_concurrently(*coroutines):
205
225
  return await asyncio.gather(*coroutines, return_exceptions=True)
206
226
 
207
227
 
208
- # Example usage function
209
- async def example_concurrent_usage():
210
- """
211
- Example of how to use multiple tools concurrently.
212
- """
213
- # Example: Generate image and process video concurrently
214
- image_task = seedream_generate_image_tool_async("A beautiful sunset", "1024x1024")
215
- video_task = video_last_frame_tool_async("https://example.com/video.mp4")
216
-
217
- # Run both tasks concurrently
218
- results = await run_multiple_tools_concurrently(image_task, video_task)
219
-
220
- return results
221
-
222
-
223
228
  def main():
224
229
  """Main entry point for the Async MCP server."""
225
230
  # Parse command line arguments
@@ -229,7 +234,7 @@ def main():
229
234
  parser.add_argument('--host', type=str, default='127.0.0.1',
230
235
  help='Host for SSE transport (default: 127.0.0.1)')
231
236
  parser.add_argument('--port', type=int, default=8000,
232
- help='Port for SSE transport (default: 8001)')
237
+ help='Port for SSE transport (default: 8000)')
233
238
  parser.add_argument('--version', action='store_true',
234
239
  help='Show version information')
235
240
 
@@ -85,28 +85,28 @@ def json_response_wrapper(func: Callable) -> Callable:
85
85
  # Async wrapped functions
86
86
  @async_wrapper
87
87
  @json_response_wrapper
88
- def video_concat_tool(video_urls: List[str]) -> str:
88
+ def _sync_video_concat(video_urls: List[str]) -> str:
89
89
  """Synchronous video concatenation wrapper."""
90
90
  return concat_videos(video_urls)
91
91
 
92
92
 
93
93
  @async_wrapper
94
94
  @json_response_wrapper
95
- def video_last_frame_tool(video_url: str) -> str:
95
+ def _sync_video_last_frame(video_url: str) -> str:
96
96
  """Synchronous video last frame extraction wrapper."""
97
97
  return extract_last_frame(video_url)
98
98
 
99
99
 
100
100
  @async_wrapper
101
101
  @json_response_wrapper
102
- def seedream_generate_image_tool(prompt: str, size: str = "1024x1024") -> str:
102
+ def _sync_seedream_generate_image(prompt: str, size: str = "1024x1024") -> str:
103
103
  """Synchronous image generation wrapper."""
104
104
  return generate_image(prompt, size=size)
105
105
 
106
106
 
107
107
  @async_wrapper
108
108
  @json_response_wrapper
109
- def seedance_generate_video_tool(prompt: str, first_frame_image: str,
109
+ def _sync_seedance_generate_video(prompt: str, first_frame_image: str,
110
110
  last_frame_image: str = None, duration: int = 5,
111
111
  resolution: str = "720p") -> str:
112
112
  """Synchronous video generation wrapper."""
@@ -128,7 +128,7 @@ def seedance_generate_video_tool(prompt: str, first_frame_image: str,
128
128
 
129
129
  @async_wrapper
130
130
  @json_response_wrapper
131
- def seededit_tool(image_url: str, prompt: str, seed: int = -1,
131
+ def _sync_seededit(image_url: str, prompt: str, seed: int = -1,
132
132
  scale: float = 0.5, charactor_keep: bool = False) -> str:
133
133
  """Synchronous image editing wrapper."""
134
134
  return seededit(
@@ -143,14 +143,14 @@ def seededit_tool(image_url: str, prompt: str, seed: int = -1,
143
143
 
144
144
  @async_wrapper
145
145
  @json_response_wrapper
146
- def vlm_vision_task_tool(messages: List) -> str:
146
+ def _sync_vlm_vision_task(messages: List) -> str:
147
147
  """Synchronous VLM vision task wrapper."""
148
148
  from media_agent_mcp.ai_models.seed16 import process_vlm_task
149
149
  return process_vlm_task(messages)
150
150
 
151
151
 
152
152
  @async_wrapper
153
- def image_selector_tool(image_paths: List[str], prompt: str) -> str:
153
+ def _sync_image_selector(image_paths: List[str], prompt: str) -> str:
154
154
  """Synchronous image selector wrapper."""
155
155
  try:
156
156
  result = select_best_image(image_paths, prompt)
@@ -168,7 +168,7 @@ def image_selector_tool(image_paths: List[str], prompt: str) -> str:
168
168
 
169
169
 
170
170
  @async_wrapper
171
- def video_selector_tool(video_paths: List[str], prompt: str) -> str:
171
+ def _sync_video_selector(video_paths: List[str], prompt: str) -> str:
172
172
  """Synchronous video selector wrapper."""
173
173
  try:
174
174
  result = select_best_video(video_paths, prompt)
@@ -186,7 +186,7 @@ def video_selector_tool(video_paths: List[str], prompt: str) -> str:
186
186
 
187
187
 
188
188
  @async_wrapper
189
- def tos_save_content_tool(content: str, file_extension: str = "txt",
189
+ def _sync_tos_save_content(content: str, file_extension: str = "txt",
190
190
  object_key: Optional[str] = None) -> str:
191
191
  """Synchronous TOS content save wrapper."""
192
192
  import tempfile
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: media-agent-mcp
3
- Version: 0.3.8
3
+ Version: 0.3.10
4
4
  Summary: A Model Context Protocol server for media processing with AI tools
5
5
  Author-email: Media Agent Team <team@mediaagent.com>
6
6
  Keywords: mcp,ai,media,video,image,processing
@@ -22,6 +22,7 @@ Requires-Dist: numpy>=1.24.0
22
22
  Requires-Dist: python-dotenv>=1.0.0
23
23
  Requires-Dist: volcengine-python-sdk>=1.0.0
24
24
  Requires-Dist: volcengine>=1.0.194
25
+ Requires-Dist: tenacity>=8.2.3
25
26
 
26
27
  # Media Agent MCP
27
28
 
@@ -9,3 +9,4 @@ numpy>=1.24.0
9
9
  python-dotenv>=1.0.0
10
10
  volcengine-python-sdk>=1.0.0
11
11
  volcengine>=1.0.194
12
+ tenacity>=8.2.3