# Directory Structure ``` ├── .env.example ├── .github │ ├── funding.yml │ └── workflows │ └── docker-build.yml ├── .gitignore ├── build-all.sh ├── build-http.sh ├── build-sse.sh ├── build-stdio.sh ├── build.sh ├── docker-compose.yml ├── Dockerfile.http ├── Dockerfile.sse ├── Dockerfile.stdio ├── examples │ └── basic_usage.py ├── LICENSE ├── main_http.py ├── main.py ├── pyproject.toml ├── README.md ├── src │ └── novareel_mcp_server │ ├── __init__.py │ ├── prompting_guide.py │ ├── server_http.py │ ├── server_sse.py │ └── server.py └── start.sh ``` # Files -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` 1 | # Amazon Nova Reel MCP Server Configuration 2 | # Copy this file to .env and fill in your actual values 3 | 4 | # AWS Credentials (Option 1: Explicit credentials) 5 | AWS_ACCESS_KEY_ID=your_aws_access_key_id_here 6 | AWS_SECRET_ACCESS_KEY=your_aws_secret_access_key_here 7 | AWS_SESSION_TOKEN=your_session_token_here_if_using_temporary_credentials 8 | 9 | # AWS Credentials (Option 2: Use AWS Profile) 10 | # AWS_PROFILE=your_aws_profile_name 11 | 12 | # AWS Configuration 13 | AWS_REGION=us-east-1 14 | 15 | # S3 Bucket for video output (Required) 16 | # This bucket must exist and be accessible with your AWS credentials 17 | S3_BUCKET=your-video-generation-bucket-name 18 | 19 | # Example values: 20 | # AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE 21 | # AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 22 | # AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjEHoaCXVzLWVhc3QtMSJHMEUCIQD... 23 | # AWS_PROFILE=my-profile 24 | # AWS_REGION=us-east-1 25 | # S3_BUCKET=my-nova-reel-videos 26 | 27 | # Notes: 28 | # - Never commit the actual .env file with real credentials to version control 29 | # - You can use either explicit credentials OR an AWS profile, not both 30 | # - For temporary credentials (STS), include AWS_SESSION_TOKEN 31 | # - For local development, AWS_PROFILE is often more convenient 32 | # - Ensure your AWS credentials have the necessary permissions for Bedrock and S3 33 | # - The S3 bucket should be in the same region as your Bedrock service 34 | # - Videos will be stored in the bucket with structure: s3://bucket/job-id/output.mp4 35 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # AWS credentials (never commit these!) 132 | .aws/ 133 | aws-credentials.json 134 | credentials.json 135 | 136 | # Docker 137 | .dockerignore 138 | 139 | # IDE 140 | .vscode/ 141 | .idea/ 142 | *.swp 143 | *.swo 144 | *~ 145 | 146 | # OS 147 | .DS_Store 148 | .DS_Store? 149 | ._* 150 | .Spotlight-V100 151 | .Trashes 152 | ehthumbs.db 153 | Thumbs.db 154 | 155 | # Logs 156 | *.log 157 | logs/ 158 | 159 | # Temporary files 160 | tmp/ 161 | temp/ 162 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Amazon Nova Reel 1.1 MCP Server 2 | 3 | A Model Context Protocol (MCP) server for Amazon Nova Reel 1.1 video generation using AWS Bedrock. This server provides tools for asynchronous video generation with comprehensive prompting guidelines and both stdio and SSE transport support. 4 | 5 | <div align="center"> 6 | 7 | [](https://www.buymeacoffee.com/mirecekdg) 8 | 9 | </div> 10 | 11 | 12 | ## Features 13 | 14 | - **Asynchronous Video Generation**: Start, monitor, and retrieve video generation jobs 15 | - **Multiple Transport Methods**: Support for stdio, Server-Sent Events (SSE), and HTTP Streaming 16 | - **Comprehensive Prompting Guide**: Built-in guidelines based on AWS documentation 17 | - **Docker Support**: Ready-to-use Docker containers for all transport methods 18 | - **AWS Integration**: Full integration with AWS Bedrock and S3 19 | 20 | ## Available Tools 21 | 22 | ### 1. `start_async_invoke` 23 | Start a new video generation job. 24 | 25 | **Parameters:** 26 | - `prompt` (required): Text description for video generation 27 | - `duration_seconds` (optional): Video duration (12-120 seconds, multiples of 6, default: 12) 28 | - `fps` (optional): Frames per second (default: 24) 29 | - `dimension` (optional): Video dimensions (default: "1280x720") 30 | - `seed` (optional): Random seed for reproducible results 31 | - `task_type` (optional): Task type (default: "MULTI_SHOT_AUTOMATED") 32 | 33 | **Returns:** Job details including `job_id`, `invocation_arn`, and estimated video URL. 34 | 35 | ### 2. `list_async_invokes` 36 | List all tracked video generation jobs with their current status. 37 | 38 | **Returns:** Summary of all jobs with status counts and individual job details. 39 | 40 | ### 3. `get_async_invoke` 41 | Get detailed information about a specific video generation job. 42 | 43 | **Parameters:** 44 | - `identifier` (required): Either `job_id` or `invocation_arn` 45 | 46 | **Returns:** Detailed job information including video URL when completed. 47 | 48 | ### 4. `get_prompting_guide` 49 | Get comprehensive prompting guidelines for effective video generation. 50 | 51 | **Returns:** Detailed prompting best practices, examples, and templates. 52 | 53 | ## Installation 54 | 55 | ### Prerequisites 56 | 57 | - Python 3.8+ 58 | - AWS Account with Bedrock access 59 | - S3 bucket for video output 60 | - AWS credentials with appropriate permissions 61 | 62 | ### Local Installation 63 | 64 | 1. Clone or download the server files 65 | 2. Install dependencies: 66 | ```bash 67 | pip install -e . 68 | ``` 69 | 70 | ### Docker Installation 71 | 72 | #### Using Pre-built Images (Recommended) 73 | 74 | Pull multi-architecture images from GitHub Container Registry: 75 | 76 | ```bash 77 | # STDIO version 78 | docker pull ghcr.io/mirecekd/novareel-mcp:latest-stdio 79 | 80 | # SSE version 81 | docker pull ghcr.io/mirecekd/novareel-mcp:latest-sse 82 | 83 | # HTTP Streaming version 84 | docker pull ghcr.io/mirecekd/novareel-mcp:latest-http 85 | ``` 86 | 87 | #### Building Locally 88 | 89 | 1. Build containers using provided scripts: 90 | ```bash 91 | # Build all versions 92 | ./build-all.sh 93 | 94 | # Or build individual versions 95 | ./build-stdio.sh # STDIO version 96 | ./build-sse.sh # SSE version 97 | ./build-http.sh # HTTP Streaming version 98 | ``` 99 | 100 | 2. Or use docker-compose: 101 | ```bash 102 | docker-compose up -d 103 | ``` 104 | 105 | 3. Or use the quick start script: 106 | ```bash 107 | # Build all images 108 | ./start.sh build 109 | 110 | # Build specific version 111 | ./start.sh build-stdio 112 | ./start.sh build-sse 113 | ./start.sh build-http 114 | ``` 115 | 116 | ## Configuration 117 | 118 | ### Environment Variables 119 | 120 | - `AWS_ACCESS_KEY_ID`: Your AWS access key ID 121 | - `AWS_SECRET_ACCESS_KEY`: Your AWS secret access key 122 | - `AWS_REGION`: AWS region (default: us-east-1) 123 | - `S3_BUCKET`: S3 bucket name for video output 124 | 125 | ### .env File Example 126 | 127 | Create a `.env` file for docker-compose: 128 | 129 | ```env 130 | AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE 131 | AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 132 | AWS_REGION=us-east-1 133 | S3_BUCKET=my-video-generation-bucket 134 | ``` 135 | 136 | ## Usage 137 | 138 | ### MCP Client Integration (Cline/Claude Desktop) 139 | 140 | Add the server to your MCP client configuration: 141 | 142 | #### Cline Configuration 143 | Add to your Cline MCP settings: 144 | 145 | ```json 146 | { 147 | "mcpServers": { 148 | "Nova Reel Video MCP": { 149 | "disabled": false, 150 | "timeout": 60, 151 | "type": "stdio", 152 | "command": "docker", 153 | "args": [ 154 | "run", 155 | "-i", 156 | "--rm", 157 | "ghcr.io/mirecekd/novareel-mcp:latest-stdio", 158 | "--aws-access-key-id", 159 | "YOUR_AWS_ACCESS_KEY_ID", 160 | "--aws-secret-access-key", 161 | "YOUR_AWS_SECRET_ACCESS_KEY", 162 | "--s3-bucket", 163 | "YOUR_S3_BUCKET_NAME" 164 | ] 165 | } 166 | } 167 | } 168 | ``` 169 | 170 | #### Claude Desktop Configuration 171 | Add to your Claude Desktop `claude_desktop_config.json`: 172 | 173 | ```json 174 | { 175 | "mcpServers": { 176 | "novareel-mcp": { 177 | "command": "docker", 178 | "args": [ 179 | "run", 180 | "-i", 181 | "--rm", 182 | "ghcr.io/mirecekd/novareel-mcp:latest-stdio", 183 | "--aws-access-key-id", 184 | "YOUR_AWS_ACCESS_KEY_ID", 185 | "--aws-secret-access-key", 186 | "YOUR_AWS_SECRET_ACCESS_KEY", 187 | "--s3-bucket", 188 | "YOUR_S3_BUCKET_NAME" 189 | ] 190 | } 191 | } 192 | } 193 | ``` 194 | 195 | #### Alternative: Local Python Installation 196 | If you prefer running without Docker: 197 | 198 | ```json 199 | { 200 | "mcpServers": { 201 | "novareel-mcp": { 202 | "command": "uvx", 203 | "args": [ 204 | "--from", "git+https://github.com/mirecekd/novareel-mcp.git", 205 | "novareel-mcp-server", 206 | "--aws-access-key-id", "YOUR_AWS_ACCESS_KEY_ID", 207 | "--aws-secret-access-key", "YOUR_AWS_SECRET_ACCESS_KEY", 208 | "--s3-bucket", "YOUR_S3_BUCKET_NAME" 209 | ] 210 | } 211 | } 212 | } 213 | ``` 214 | 215 | **Important**: Replace the placeholder values with your actual AWS credentials and S3 bucket name. 216 | 217 | ### Running with uvx (Recommended) 218 | 219 | ```bash 220 | # First build the package 221 | ./build.sh 222 | 223 | # Then run from wheel file 224 | uvx --from ./dist/novareel_mcp-1.0.0-py3-none-any.whl novareel-mcp-server --aws-access-key-id YOUR_KEY --aws-secret-access-key YOUR_SECRET --s3-bucket YOUR_BUCKET 225 | 226 | # Or from current directory during development (without build) 227 | uvx --from . novareel-mcp-server --aws-access-key-id YOUR_KEY --aws-secret-access-key YOUR_SECRET --s3-bucket YOUR_BUCKET 228 | 229 | # Or using start script 230 | ./start.sh build-package # Build wheel 231 | ``` 232 | 233 | ### Stdio Version (Direct MCP Client) 234 | 235 | ```bash 236 | # Local execution 237 | python main.py --aws-access-key-id YOUR_KEY --aws-secret-access-key YOUR_SECRET --s3-bucket YOUR_BUCKET 238 | 239 | # Docker execution 240 | docker run --rm -i mirecekd/novareel-mcp-server:stdio --aws-access-key-id YOUR_KEY --aws-secret-access-key YOUR_SECRET --s3-bucket YOUR_BUCKET 241 | ``` 242 | 243 | ### SSE Version (Web Interface) 244 | 245 | ```bash 246 | # Local execution 247 | python -m novareel_mcp_server.server_sse --aws-access-key-id YOUR_KEY --aws-secret-access-key YOUR_SECRET --s3-bucket YOUR_BUCKET --host 0.0.0.0 --port 8000 248 | 249 | # Docker execution 250 | docker run -p 8000:8000 -e AWS_ACCESS_KEY_ID=YOUR_KEY -e AWS_SECRET_ACCESS_KEY=YOUR_SECRET -e S3_BUCKET=YOUR_BUCKET mirecekd/novareel-mcp-server:sse 251 | ``` 252 | 253 | Then access `http://localhost:8000/sse/` for the SSE endpoint. 254 | 255 | ### HTTP Streaming Version (Bidirectional Transport) 256 | 257 | ```bash 258 | # Local execution 259 | python -m novareel_mcp_server.server_http --aws-access-key-id YOUR_KEY --aws-secret-access-key YOUR_SECRET --s3-bucket YOUR_BUCKET --host 0.0.0.0 --port 8001 260 | 261 | # Docker execution 262 | docker run -p 8001:8001 -e AWS_ACCESS_KEY_ID=YOUR_KEY -e AWS_SECRET_ACCESS_KEY=YOUR_SECRET -e S3_BUCKET=YOUR_BUCKET ghcr.io/mirecekd/novareel-mcp:latest-http 263 | ``` 264 | 265 | Then access `http://localhost:8001` for the HTTP streaming transport. 266 | 267 | ### Package Build 268 | 269 | To create a distribution package: 270 | 271 | ```bash 272 | # Install build tools 273 | pip install build 274 | 275 | # Create package 276 | python3 -m build 277 | 278 | # Output files will be in dist/ 279 | ``` 280 | 281 | ## Example Usage 282 | 283 | ### Basic Video Generation 284 | 285 | ```python 286 | # Start a video generation job 287 | result = start_async_invoke( 288 | prompt="A majestic eagle soars over a mountain valley, camera tracking its flight as it circles above a pristine lake", 289 | duration_seconds=24, 290 | fps=24, 291 | dimension="1920x1080" 292 | ) 293 | 294 | job_id = result["job_id"] 295 | print(f"Started job: {job_id}") 296 | 297 | # Check job status 298 | status = get_async_invoke(job_id) 299 | print(f"Status: {status['status']}") 300 | 301 | # When completed, get video URL 302 | if status["status"] == "Completed": 303 | print(f"Video URL: {status['video_url']}") 304 | ``` 305 | 306 | ### List All Jobs 307 | 308 | ```python 309 | # Get overview of all jobs 310 | jobs = list_async_invokes() 311 | print(f"Total jobs: {jobs['total_invocations']}") 312 | print(f"Completed: {jobs['summary']['completed']}") 313 | print(f"In progress: {jobs['summary']['in_progress']}") 314 | ``` 315 | 316 | ## Prompting Guidelines 317 | 318 | The server includes comprehensive prompting guidelines based on AWS documentation. Access them using: 319 | 320 | ```python 321 | guide = get_prompting_guide() 322 | ``` 323 | 324 | ### Key Prompting Tips 325 | 326 | 1. **Be Specific**: Use detailed, descriptive language 327 | - Good: "A red cardinal perched on a snow-covered pine branch, morning sunlight filtering through the trees" 328 | - Bad: "A bird on a tree" 329 | 330 | 2. **Use Camera Terminology**: Control shot composition 331 | - "Close-up shot of hands carving wood" 332 | - "Wide shot establishing the mountain landscape" 333 | - "Camera pans left across the valley" 334 | 335 | 3. **Include Lighting Details**: Specify atmosphere 336 | - "Golden hour lighting casting long shadows" 337 | - "Soft blue hour twilight" 338 | - "Dramatic storm clouds overhead" 339 | 340 | 4. **Structure for Duration**: Match complexity to video length 341 | - 12-24 seconds: Single action or moment 342 | - 30-60 seconds: 2-3 distinct actions 343 | - 60-120 seconds: Full narrative with multiple scenes 344 | 345 | ### Example Prompts by Category 346 | 347 | **Nature (Short - 12s):** 348 | ``` 349 | Close-up of morning dew drops on a spider web, with soft sunrise lighting creating rainbow reflections 350 | ``` 351 | 352 | **Urban (Medium - 30s):** 353 | ``` 354 | A street musician plays violin in a subway station, commuters pause to listen, coins drop into his case, camera slowly pulls back to reveal the bustling underground scene 355 | ``` 356 | 357 | **Portrait (Long - 60s):** 358 | ``` 359 | Portrait of a chef preparing a signature dish: selecting fresh ingredients at market, returning to kitchen, methodically preparing each component, plating with artistic precision, and presenting the finished masterpiece 360 | ``` 361 | 362 | ## AWS Permissions 363 | 364 | Your AWS credentials need the following permissions: 365 | 366 | ```json 367 | { 368 | "Version": "2012-10-17", 369 | "Statement": [ 370 | { 371 | "Effect": "Allow", 372 | "Action": [ 373 | "bedrock:InvokeModel", 374 | "bedrock:StartAsyncInvoke", 375 | "bedrock:GetAsyncInvoke", 376 | "bedrock:ListFoundationModels" 377 | ], 378 | "Resource": "*" 379 | }, 380 | { 381 | "Effect": "Allow", 382 | "Action": [ 383 | "s3:PutObject", 384 | "s3:GetObject", 385 | "s3:ListBucket" 386 | ], 387 | "Resource": [ 388 | "arn:aws:s3:::your-bucket-name", 389 | "arn:aws:s3:::your-bucket-name/*" 390 | ] 391 | } 392 | ] 393 | } 394 | ``` 395 | 396 | ## Video Output 397 | 398 | Generated videos are stored in your S3 bucket with the following structure: 399 | ``` 400 | s3://your-bucket/ 401 | ├── job-id-1/ 402 | │ └── output.mp4 403 | ├── job-id-2/ 404 | │ └── output.mp4 405 | └── ... 406 | ``` 407 | 408 | Videos are accessible via HTTPS URLs: 409 | ``` 410 | https://your-bucket.s3.region.amazonaws.com/job-id/output.mp4 411 | ``` 412 | 413 | ## Supported Video Specifications 414 | 415 | - **Duration**: 12-120 seconds (must be multiples of 6) 416 | - **Frame Rate**: 24 fps (recommended) 417 | - **Dimensions**: 418 | - 1280x720 (HD) 419 | - **Format**: MP4 420 | - **Model**: amazon.nova-reel-v1:1 421 | 422 | ## Troubleshooting 423 | 424 | ### Common Issues 425 | 426 | 1. **AWS Credentials Error** 427 | - Verify your AWS credentials are correct 428 | - Ensure your account has Bedrock access enabled 429 | - Check IAM permissions 430 | 431 | 2. **S3 Bucket Access** 432 | - Verify bucket exists and is accessible 433 | - Check bucket permissions 434 | - Ensure bucket is in the same region as Bedrock 435 | 436 | 3. **Duration Validation** 437 | - Duration must be 12-120 seconds 438 | - Must be a multiple of 6 439 | - Valid values: 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120 440 | 441 | 4. **Job Not Found** 442 | - Use `list_async_invokes` to see all tracked jobs 443 | - Jobs are stored in memory and lost on server restart 444 | - For production, implement persistent storage 445 | 446 | ### Debug Mode 447 | 448 | Enable debug logging by setting environment variable: 449 | ```bash 450 | export PYTHONUNBUFFERED=1 451 | ``` 452 | 453 | ## Development 454 | 455 | ### Project Structure 456 | 457 | ``` 458 | novareel-mcp-server/ 459 | ├── main.py # Main MCP server (stdio) 460 | ├── main_sse.py # SSE version of MCP server 461 | ├── main_http.py # HTTP Streaming version of MCP server 462 | ├── prompting_guide.py # AWS prompting guidelines 463 | ├── pyproject.toml # Python dependencies 464 | ├── Dockerfile.stdio # Docker for stdio version 465 | ├── Dockerfile.sse # Docker for SSE version 466 | ├── Dockerfile.http # Docker for HTTP streaming version 467 | ├── docker-compose.yml # Container orchestration 468 | └── README.md # This documentation 469 | ``` 470 | 471 | ### Contributing 472 | 473 | 1. Fork the repository 474 | 2. Create a feature branch 475 | 3. Make your changes 476 | 4. Test with all transport versions (stdio, SSE, HTTP streaming) 477 | 5. Submit a pull request 478 | 479 | ## License 480 | 481 | This project is licensed under the MIT License - see the LICENSE file for details. 482 | 483 | ## Support 484 | 485 | For issues and questions: 486 | 1. Check the troubleshooting section 487 | 2. Review AWS Bedrock documentation 488 | 3. Open an issue in the repository 489 | 490 | ## Related Links 491 | 492 | - [AWS Nova Reel Documentation](https://docs.aws.amazon.com/nova/latest/userguide/) 493 | - [Video Generation Prompting Guide](https://docs.aws.amazon.com/nova/latest/userguide/prompting-video-generation.html) 494 | - [Camera Control Prompting](https://docs.aws.amazon.com/nova/latest/userguide/prompting-video-camera-control.html) 495 | - [Model Context Protocol](https://modelcontextprotocol.io/) 496 | - [FastMCP Framework](https://github.com/jlowin/fastmcp) 497 | ``` -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- ```yaml 1 | buy_me_a_coffee: mirecekdg 2 | ``` -------------------------------------------------------------------------------- /src/novareel_mcp_server/__init__.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Nova Reel MCP Server 3 | MCP Server pro Amazon Nova Reel 1.1 video generation 4 | """ 5 | 6 | __version__ = "1.0.0" 7 | ``` -------------------------------------------------------------------------------- /main_http.py: -------------------------------------------------------------------------------- ```python 1 | #!/usr/bin/env python3 2 | """ 3 | Amazon Nova Reel 1.1 MCP Server - HTTP Streaming Entry Point 4 | """ 5 | 6 | from src.novareel_mcp_server.server_http import main 7 | 8 | if __name__ == "__main__": 9 | main() 10 | ``` -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- ```python 1 | #!/usr/bin/env python3 2 | """ 3 | Nova Reel MCP Server - Main Entry Point 4 | Wrapper pro zpětnou kompatibilitu 5 | """ 6 | 7 | from src.novareel_mcp_server.server import main 8 | 9 | if __name__ == "__main__": 10 | main() 11 | ``` -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- ```bash 1 | #!/bin/bash 2 | 3 | echo "Building Nova Reel MCP Server Python Package" 4 | echo "============================================" 5 | 6 | # Check if build module is installed 7 | if ! python3 -c "import build" 2>/dev/null; then 8 | echo "Installing build module..." 9 | pip install build 10 | fi 11 | 12 | # Clean previous builds 13 | echo "Cleaning previous builds..." 14 | rm -rf dist/ build/ *.egg-info/ 15 | 16 | # Build the package 17 | echo "Building package..." 18 | python3 -m build 19 | 20 | echo "" 21 | echo "✅ Build completed!" 22 | echo "Generated files:" 23 | ls -la dist/ 24 | 25 | echo "" 26 | echo "Usage with uvx:" 27 | echo " uvx --from ./dist/novareel_mcp-1.0.0-py3-none-any.whl novareel-mcp-server --help" 28 | echo "" 29 | echo "Or install locally:" 30 | echo " pip install ./dist/novareel_mcp-1.0.0-py3-none-any.whl" 31 | ``` -------------------------------------------------------------------------------- /build-stdio.sh: -------------------------------------------------------------------------------- ```bash 1 | #!/bin/bash 2 | 3 | echo "Building Nova Reel MCP Server - STDIO Version (stdio transport)" 4 | echo "================================================================" 5 | 6 | docker build -f Dockerfile.stdio -t mirecekd/novareel-mcp-server:stdio -t mirecekd/novareel-mcp-server:latest . 7 | 8 | echo "" 9 | echo "Build completed!" 10 | echo "STDIO Version tags:" 11 | echo " - mirecekd/novareel-mcp-server:stdio" 12 | echo " - mirecekd/novareel-mcp-server:latest" 13 | echo "" 14 | echo "Usage examples:" 15 | echo "" 16 | echo " Explicit credentials:" 17 | echo " docker run --rm -i mirecekd/novareel-mcp-server:stdio --aws-access-key-id YOUR_KEY --aws-secret-access-key YOUR_SECRET --s3-bucket YOUR_BUCKET" 18 | echo "" 19 | echo " With session token (temporary credentials):" 20 | echo " docker run --rm -i mirecekd/novareel-mcp-server:stdio --aws-access-key-id YOUR_KEY --aws-secret-access-key YOUR_SECRET --aws-session-token YOUR_TOKEN --s3-bucket YOUR_BUCKET" 21 | echo "" 22 | echo " With AWS profile:" 23 | echo " docker run --rm -i -v ~/.aws:/root/.aws mirecekd/novareel-mcp-server:stdio --aws-profile my-profile --s3-bucket YOUR_BUCKET" 24 | ``` -------------------------------------------------------------------------------- /build-sse.sh: -------------------------------------------------------------------------------- ```bash 1 | #!/bin/bash 2 | 3 | echo "Building Nova Reel MCP Server - SSE Version (HTTP transport)" 4 | echo "============================================================" 5 | 6 | docker build -f Dockerfile.sse -t mirecekd/novareel-mcp-server:sse -t mirecekd/novareel-mcp-sse . 7 | 8 | echo "" 9 | echo "Build completed!" 10 | echo "SSE Version tags:" 11 | echo " - mirecekd/novareel-mcp-server:sse" 12 | echo " - mirecekd/novareel-mcp-sse" 13 | echo "" 14 | echo "Usage examples:" 15 | echo "" 16 | echo " Environment variables:" 17 | echo " docker run -e AWS_ACCESS_KEY_ID=YOUR_KEY -e AWS_SECRET_ACCESS_KEY=YOUR_SECRET -e S3_BUCKET=YOUR_BUCKET -p 8000:8000 mirecekd/novareel-mcp-server:sse" 18 | echo "" 19 | echo " With session token (temporary credentials):" 20 | echo " docker run -e AWS_ACCESS_KEY_ID=YOUR_KEY -e AWS_SECRET_ACCESS_KEY=YOUR_SECRET -e AWS_SESSION_TOKEN=YOUR_TOKEN -e S3_BUCKET=YOUR_BUCKET -p 8000:8000 mirecekd/novareel-mcp-server:sse" 21 | echo "" 22 | echo " With AWS profile:" 23 | echo " docker run -v ~/.aws:/root/.aws -e AWS_PROFILE=my-profile -e S3_BUCKET=YOUR_BUCKET -p 8000:8000 mirecekd/novareel-mcp-server:sse" 24 | echo "" 25 | echo "SSE server will be available at: http://localhost:8000" 26 | ``` -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- ```toml 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "novareel-mcp" 7 | version = "1.0.0" 8 | description = "MCP Server pro Amazon Nova Reel 1.1 video generation" 9 | authors = [ 10 | {name = "Miroslav Dvořák", email = "[email protected]"} 11 | ] 12 | readme = "README.md" 13 | license = {file = "LICENSE"} 14 | requires-python = ">=3.8" 15 | dependencies = [ 16 | "fastmcp>=0.2.0", 17 | "boto3>=1.35.0", 18 | "botocore>=1.35.0", 19 | ] 20 | classifiers = [ 21 | "Development Status :: 4 - Beta", 22 | "Intended Audience :: Developers", 23 | "License :: OSI Approved :: MIT License", 24 | "Programming Language :: Python :: 3", 25 | "Programming Language :: Python :: 3.8", 26 | "Programming Language :: Python :: 3.9", 27 | "Programming Language :: Python :: 3.10", 28 | "Programming Language :: Python :: 3.11", 29 | "Programming Language :: Python :: 3.12", 30 | ] 31 | 32 | [project.scripts] 33 | novareel-mcp-server = "novareel_mcp_server.server:main" 34 | 35 | [project.urls] 36 | Homepage = "https://github.com/mirecekd/novareel-mcp" 37 | Repository = "https://github.com/mirecekd/novareel-mcp" 38 | Issues = "https://github.com/mirecekd/novareel-mcp/issues" 39 | 40 | [tool.hatch.build.targets.wheel] 41 | packages = ["src/novareel_mcp_server"] 42 | 43 | [tool.hatch.build.targets.sdist] 44 | include = [ 45 | "/src", 46 | "/README.md", 47 | "/LICENSE", 48 | ] 49 | ``` -------------------------------------------------------------------------------- /build-http.sh: -------------------------------------------------------------------------------- ```bash 1 | #!/bin/bash 2 | 3 | # Build script for NovaReel MCP Server - HTTP Streaming version 4 | 5 | set -e 6 | 7 | echo "Building NovaReel MCP Server - HTTP Streaming version..." 8 | 9 | # Build Docker image 10 | docker build -f Dockerfile.http -t mirecekd/novareel-mcp-server:http . 11 | 12 | echo "Build completed successfully!" 13 | echo "Image: mirecekd/novareel-mcp-server:http" 14 | echo "" 15 | echo "Usage examples:" 16 | echo "" 17 | echo " Environment variables:" 18 | echo " docker run -p 8001:8001 -e AWS_ACCESS_KEY_ID=your_key -e AWS_SECRET_ACCESS_KEY=your_secret -e S3_BUCKET=your_bucket mirecekd/novareel-mcp-server:http" 19 | echo "" 20 | echo " With session token (temporary credentials):" 21 | echo " docker run -p 8001:8001 -e AWS_ACCESS_KEY_ID=your_key -e AWS_SECRET_ACCESS_KEY=your_secret -e AWS_SESSION_TOKEN=your_token -e S3_BUCKET=your_bucket mirecekd/novareel-mcp-server:http" 22 | echo "" 23 | echo " With AWS profile:" 24 | echo " docker run -p 8001:8001 -v ~/.aws:/root/.aws -e AWS_PROFILE=my-profile -e S3_BUCKET=your_bucket mirecekd/novareel-mcp-server:http" 25 | echo "" 26 | echo " Command line arguments:" 27 | echo " docker run -p 8001:8001 mirecekd/novareel-mcp-server:http --aws-access-key-id your_key --aws-secret-access-key your_secret --s3-bucket your_bucket" 28 | echo "" 29 | echo " Command line with session token:" 30 | echo " docker run -p 8001:8001 mirecekd/novareel-mcp-server:http --aws-access-key-id your_key --aws-secret-access-key your_secret --aws-session-token your_token --s3-bucket your_bucket" 31 | echo "" 32 | echo " Command line with AWS profile:" 33 | echo " docker run -p 8001:8001 -v ~/.aws:/root/.aws mirecekd/novareel-mcp-server:http --aws-profile my-profile --s3-bucket your_bucket" 34 | echo "" 35 | echo "HTTP streaming server will be available at: http://localhost:8001" 36 | ``` -------------------------------------------------------------------------------- /build-all.sh: -------------------------------------------------------------------------------- ```bash 1 | #!/bin/bash 2 | 3 | echo "Building All Nova Reel MCP Server Versions" 4 | echo "==========================================" 5 | echo "" 6 | 7 | echo "1. Building STDIO Version (stdio transport)..." 8 | echo "---------------------------------------------" 9 | docker build -f Dockerfile.stdio -t mirecekd/novareel-mcp-server:stdio -t mirecekd/novareel-mcp-server:latest . 10 | 11 | echo "" 12 | echo "2. Building SSE Version (Server-Sent Events transport)..." 13 | echo "--------------------------------------------------------" 14 | docker build -f Dockerfile.sse -t mirecekd/novareel-mcp-server:sse -t mirecekd/novareel-mcp-sse . 15 | 16 | echo "" 17 | echo "3. Building HTTP Version (HTTP Streaming transport)..." 18 | echo "-----------------------------------------------------" 19 | docker build -f Dockerfile.http -t mirecekd/novareel-mcp-server:http . 20 | 21 | echo "" 22 | echo "✅ All builds completed!" 23 | echo "========================" 24 | echo "" 25 | echo "Available images:" 26 | echo " STDIO Version:" 27 | echo " - mirecekd/novareel-mcp-server:stdio" 28 | echo " - mirecekd/novareel-mcp-server:latest" 29 | echo " SSE Version:" 30 | echo " - mirecekd/novareel-mcp-server:sse" 31 | echo " - mirecekd/novareel-mcp-sse" 32 | echo " HTTP Streaming Version:" 33 | echo " - mirecekd/novareel-mcp-server:http" 34 | echo "" 35 | echo "Usage examples:" 36 | echo "" 37 | echo " STDIO (Explicit credentials):" 38 | echo " docker run --rm -i mirecekd/novareel-mcp-server:stdio --aws-access-key-id KEY --aws-secret-access-key SECRET --s3-bucket BUCKET" 39 | echo "" 40 | echo " STDIO (With session token):" 41 | echo " docker run --rm -i mirecekd/novareel-mcp-server:stdio --aws-access-key-id KEY --aws-secret-access-key SECRET --aws-session-token TOKEN --s3-bucket BUCKET" 42 | echo "" 43 | echo " STDIO (With AWS profile):" 44 | echo " docker run --rm -i -v ~/.aws:/root/.aws mirecekd/novareel-mcp-server:stdio --aws-profile my-profile --s3-bucket BUCKET" 45 | echo "" 46 | echo " SSE (Environment variables):" 47 | echo " docker run -e AWS_ACCESS_KEY_ID=KEY -e AWS_SECRET_ACCESS_KEY=SECRET -e S3_BUCKET=BUCKET -p 8000:8000 mirecekd/novareel-mcp-server:sse" 48 | echo "" 49 | echo " SSE (With session token):" 50 | echo " docker run -e AWS_ACCESS_KEY_ID=KEY -e AWS_SECRET_ACCESS_KEY=SECRET -e AWS_SESSION_TOKEN=TOKEN -e S3_BUCKET=BUCKET -p 8000:8000 mirecekd/novareel-mcp-server:sse" 51 | echo "" 52 | echo " SSE (With AWS profile):" 53 | echo " docker run -v ~/.aws:/root/.aws -e AWS_PROFILE=my-profile -e S3_BUCKET=BUCKET -p 8000:8000 mirecekd/novareel-mcp-server:sse" 54 | echo "" 55 | echo " HTTP (Environment variables):" 56 | echo " docker run -e AWS_ACCESS_KEY_ID=KEY -e AWS_SECRET_ACCESS_KEY=SECRET -e S3_BUCKET=BUCKET -p 8001:8001 mirecekd/novareel-mcp-server:http" 57 | echo "" 58 | echo " HTTP (With session token):" 59 | echo " docker run -e AWS_ACCESS_KEY_ID=KEY -e AWS_SECRET_ACCESS_KEY=SECRET -e AWS_SESSION_TOKEN=TOKEN -e S3_BUCKET=BUCKET -p 8001:8001 mirecekd/novareel-mcp-server:http" 60 | echo "" 61 | echo " HTTP (With AWS profile):" 62 | echo " docker run -v ~/.aws:/root/.aws -e AWS_PROFILE=my-profile -e S3_BUCKET=BUCKET -p 8001:8001 mirecekd/novareel-mcp-server:http" 63 | ``` -------------------------------------------------------------------------------- /.github/workflows/docker-build.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Build and Push Multi-Arch Docker Images 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | tags: [ '*' ] 7 | pull_request: 8 | branches: [ main ] 9 | 10 | env: 11 | REGISTRY: ghcr.io 12 | IMAGE_NAME: ${{ github.repository }} 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | packages: write 20 | 21 | strategy: 22 | matrix: 23 | variant: [stdio, sse, http] 24 | 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v4 28 | 29 | - name: Set up QEMU 30 | uses: docker/setup-qemu-action@v3 31 | 32 | - name: Set up Docker Buildx 33 | uses: docker/setup-buildx-action@v3 34 | 35 | - name: Log in to Container Registry 36 | if: github.event_name != 'pull_request' 37 | uses: docker/login-action@v3 38 | with: 39 | registry: ${{ env.REGISTRY }} 40 | username: ${{ github.actor }} 41 | password: ${{ secrets.GITHUB_TOKEN }} 42 | 43 | - name: Extract metadata (tags, labels) for Docker 44 | id: meta 45 | uses: docker/metadata-action@v5 46 | with: 47 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 48 | tags: | 49 | type=ref,event=branch,suffix=-${{ matrix.variant }} 50 | type=ref,event=pr,suffix=-${{ matrix.variant }} 51 | type=semver,pattern={{version}},suffix=-${{ matrix.variant }} 52 | type=semver,pattern={{major}}.{{minor}},suffix=-${{ matrix.variant }} 53 | type=semver,pattern={{major}},suffix=-${{ matrix.variant }} 54 | type=raw,value=latest,suffix=-${{ matrix.variant }},enable={{is_default_branch}} 55 | 56 | - name: Build and push Docker image 57 | uses: docker/build-push-action@v5 58 | with: 59 | context: . 60 | file: ./Dockerfile.${{ matrix.variant }} 61 | platforms: linux/amd64,linux/arm64,linux/aarch64 62 | push: ${{ github.event_name != 'pull_request' }} 63 | tags: ${{ steps.meta.outputs.tags }} 64 | labels: ${{ steps.meta.outputs.labels }} 65 | cache-from: type=gha 66 | cache-to: type=gha,mode=max 67 | 68 | build-summary: 69 | runs-on: ubuntu-latest 70 | needs: build 71 | if: always() 72 | steps: 73 | - name: Build Summary 74 | run: | 75 | echo "## Build Results" >> $GITHUB_STEP_SUMMARY 76 | echo "| Variant | Status |" >> $GITHUB_STEP_SUMMARY 77 | echo "|---------|--------|" >> $GITHUB_STEP_SUMMARY 78 | echo "| stdio | ${{ needs.build.result }} |" >> $GITHUB_STEP_SUMMARY 79 | echo "| sse | ${{ needs.build.result }} |" >> $GITHUB_STEP_SUMMARY 80 | echo "| http | ${{ needs.build.result }} |" >> $GITHUB_STEP_SUMMARY 81 | echo "" >> $GITHUB_STEP_SUMMARY 82 | echo "Images built for platforms: linux/amd64, linux/arm64, linux/aarch64" >> $GITHUB_STEP_SUMMARY 83 | echo "Registry: ghcr.io/${{ github.repository }}" >> $GITHUB_STEP_SUMMARY 84 | echo "" >> $GITHUB_STEP_SUMMARY 85 | echo "Available images:" >> $GITHUB_STEP_SUMMARY 86 | echo "- ghcr.io/${{ github.repository }}:latest-stdio" >> $GITHUB_STEP_SUMMARY 87 | echo "- ghcr.io/${{ github.repository }}:latest-sse" >> $GITHUB_STEP_SUMMARY 88 | echo "- ghcr.io/${{ github.repository }}:latest-http" >> $GITHUB_STEP_SUMMARY 89 | ``` -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- ```yaml 1 | version: '3.8' 2 | 3 | services: 4 | novareel-stdio: 5 | image: ghcr.io/mirecekd/novareel-mcp:latest-stdio 6 | # Uncomment to build locally instead: 7 | # build: 8 | # context: . 9 | # dockerfile: Dockerfile.stdio 10 | container_name: novareel-mcp-stdio 11 | environment: 12 | - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} 13 | - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} 14 | - AWS_REGION=${AWS_REGION:-us-east-1} 15 | - S3_BUCKET=${S3_BUCKET} 16 | volumes: 17 | - novareel-data:/root 18 | stdin_open: true 19 | tty: true 20 | restart: unless-stopped 21 | networks: 22 | - novareel-network 23 | 24 | novareel-sse: 25 | image: ghcr.io/mirecekd/novareel-mcp:latest-sse 26 | # Uncomment to build locally instead: 27 | # build: 28 | # context: . 29 | # dockerfile: Dockerfile.sse 30 | container_name: novareel-mcp-sse 31 | environment: 32 | - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} 33 | - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} 34 | - AWS_REGION=${AWS_REGION:-us-east-1} 35 | - S3_BUCKET=${S3_BUCKET} 36 | volumes: 37 | - novareel-data:/root 38 | ports: 39 | - "8000:8000" 40 | restart: unless-stopped 41 | networks: 42 | - novareel-network 43 | healthcheck: 44 | test: ["CMD", "curl", "-f", "http://localhost:8000/health"] 45 | interval: 30s 46 | timeout: 10s 47 | retries: 3 48 | start_period: 40s 49 | 50 | novareel-http: 51 | image: ghcr.io/mirecekd/novareel-mcp:latest-http 52 | # Uncomment to build locally instead: 53 | # build: 54 | # context: . 55 | # dockerfile: Dockerfile.http 56 | container_name: novareel-mcp-http 57 | environment: 58 | - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} 59 | - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} 60 | - AWS_REGION=${AWS_REGION:-us-east-1} 61 | - S3_BUCKET=${S3_BUCKET} 62 | volumes: 63 | - novareel-data:/root 64 | ports: 65 | - "8001:8001" 66 | restart: unless-stopped 67 | networks: 68 | - novareel-network 69 | healthcheck: 70 | test: ["CMD", "curl", "-f", "http://localhost:8001/health"] 71 | interval: 30s 72 | timeout: 10s 73 | retries: 3 74 | start_period: 40s 75 | 76 | # Development version with live reloading 77 | novareel-dev: 78 | build: 79 | context: . 80 | dockerfile: Dockerfile.sse 81 | image: mirecekd/novareel-mcp-server:dev 82 | container_name: novareel-mcp-dev 83 | environment: 84 | - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} 85 | - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} 86 | - AWS_REGION=${AWS_REGION:-us-east-1} 87 | - S3_BUCKET=${S3_BUCKET} 88 | ports: 89 | - "8000:8000" 90 | volumes: 91 | - .:/app 92 | restart: unless-stopped 93 | networks: 94 | - novareel-network 95 | profiles: 96 | - dev 97 | 98 | volumes: 99 | novareel-data: 100 | driver: local 101 | 102 | networks: 103 | novareel-network: 104 | driver: bridge 105 | 106 | # Example usage: 107 | # 1. Create .env file with your AWS credentials: 108 | # AWS_ACCESS_KEY_ID=your_access_key 109 | # AWS_SECRET_ACCESS_KEY=your_secret_key 110 | # AWS_REGION=us-east-1 111 | # S3_BUCKET=your-bucket-name 112 | # 113 | # 2. Start both services: 114 | # docker-compose up -d 115 | # 116 | # 3. Use stdio version: 117 | # docker exec -it novareel-mcp-stdio python main.py 118 | # 119 | # 4. Use SSE version: 120 | # Access http://localhost:8000 for web interface 121 | # 122 | # 5. Use HTTP Streaming version: 123 | # Access http://localhost:8001 for HTTP streaming transport 124 | # 125 | # 6. Development with live reloading: 126 | # docker-compose --profile dev up novareel-dev 127 | # 128 | # 7. Stop services: 129 | # docker-compose down 130 | ``` -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- ```bash 1 | #!/bin/bash 2 | 3 | # Nova Reel MCP Server Quick Start Script 4 | 5 | set -e 6 | 7 | echo "🎬 Amazon Nova Reel MCP Server Quick Start" 8 | echo "==========================================" 9 | 10 | # Check if .env file exists 11 | if [ ! -f .env ]; then 12 | echo "⚠️ No .env file found!" 13 | echo "Please copy .env.example to .env and configure your AWS credentials:" 14 | echo "" 15 | echo " cp .env.example .env" 16 | echo " # Edit .env with your AWS credentials" 17 | echo "" 18 | exit 1 19 | fi 20 | 21 | # Source environment variables 22 | echo "📋 Loading environment variables..." 23 | export $(cat .env | grep -v '^#' | xargs) 24 | 25 | # Check required variables 26 | if [ -z "$S3_BUCKET" ]; then 27 | echo "❌ Missing required S3_BUCKET environment variable!" 28 | echo "Please ensure .env contains S3_BUCKET" 29 | exit 1 30 | fi 31 | 32 | # Check if we have valid credential configuration 33 | if [ -n "$AWS_PROFILE" ]; then 34 | echo "✅ Using AWS Profile: $AWS_PROFILE" 35 | elif [ -n "$AWS_ACCESS_KEY_ID" ] && [ -n "$AWS_SECRET_ACCESS_KEY" ]; then 36 | echo "✅ Using explicit AWS credentials" 37 | if [ -n "$AWS_SESSION_TOKEN" ]; then 38 | echo " (with session token for temporary credentials)" 39 | fi 40 | else 41 | echo "❌ Missing AWS credentials configuration!" 42 | echo "Please ensure .env contains either:" 43 | echo " Option 1: AWS_PROFILE=your-profile-name" 44 | echo " Option 2: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY" 45 | echo " (optionally with AWS_SESSION_TOKEN for temporary credentials)" 46 | exit 1 47 | fi 48 | 49 | echo "✅ Environment configured:" 50 | echo " AWS Region: ${AWS_REGION:-us-east-1}" 51 | echo " S3 Bucket: $S3_BUCKET" 52 | echo "" 53 | 54 | # Check if we should run stdio or sse version 55 | MODE=${1:-stdio} 56 | 57 | case $MODE in 58 | stdio) 59 | echo "🚀 Starting Nova Reel MCP Server (STDIO mode)..." 60 | echo " This mode is for direct MCP client connections." 61 | echo "" 62 | # Build command with conditional parameters 63 | CMD="python main.py --aws-region ${AWS_REGION:-us-east-1} --s3-bucket $S3_BUCKET" 64 | if [ -n "$AWS_PROFILE" ]; then 65 | CMD="$CMD --aws-profile $AWS_PROFILE" 66 | elif [ -n "$AWS_ACCESS_KEY_ID" ] && [ -n "$AWS_SECRET_ACCESS_KEY" ]; then 67 | CMD="$CMD --aws-access-key-id $AWS_ACCESS_KEY_ID --aws-secret-access-key $AWS_SECRET_ACCESS_KEY" 68 | if [ -n "$AWS_SESSION_TOKEN" ]; then 69 | CMD="$CMD --aws-session-token $AWS_SESSION_TOKEN" 70 | fi 71 | fi 72 | eval $CMD 73 | ;; 74 | sse) 75 | echo "🚀 Starting Nova Reel MCP Server (SSE mode)..." 76 | echo " This mode provides a web interface." 77 | echo " Access: http://localhost:8000" 78 | echo "" 79 | # Build command with conditional parameters 80 | CMD="python -m novareel_mcp_server.server_sse --aws-region ${AWS_REGION:-us-east-1} --s3-bucket $S3_BUCKET --host 0.0.0.0 --port 8000" 81 | if [ -n "$AWS_PROFILE" ]; then 82 | CMD="$CMD --aws-profile $AWS_PROFILE" 83 | elif [ -n "$AWS_ACCESS_KEY_ID" ] && [ -n "$AWS_SECRET_ACCESS_KEY" ]; then 84 | CMD="$CMD --aws-access-key-id $AWS_ACCESS_KEY_ID --aws-secret-access-key $AWS_SECRET_ACCESS_KEY" 85 | if [ -n "$AWS_SESSION_TOKEN" ]; then 86 | CMD="$CMD --aws-session-token $AWS_SESSION_TOKEN" 87 | fi 88 | fi 89 | eval $CMD 90 | ;; 91 | http) 92 | echo "🚀 Starting Nova Reel MCP Server (HTTP Streaming mode)..." 93 | echo " This mode provides HTTP streaming transport." 94 | echo " Access: http://localhost:8001" 95 | echo "" 96 | # Build command with conditional parameters 97 | CMD="python -m novareel_mcp_server.server_http --aws-region ${AWS_REGION:-us-east-1} --s3-bucket $S3_BUCKET --host 0.0.0.0 --port 8001" 98 | if [ -n "$AWS_PROFILE" ]; then 99 | CMD="$CMD --aws-profile $AWS_PROFILE" 100 | elif [ -n "$AWS_ACCESS_KEY_ID" ] && [ -n "$AWS_SECRET_ACCESS_KEY" ]; then 101 | CMD="$CMD --aws-access-key-id $AWS_ACCESS_KEY_ID --aws-secret-access-key $AWS_SECRET_ACCESS_KEY" 102 | if [ -n "$AWS_SESSION_TOKEN" ]; then 103 | CMD="$CMD --aws-session-token $AWS_SESSION_TOKEN" 104 | fi 105 | fi 106 | eval $CMD 107 | ;; 108 | docker-stdio) 109 | echo "🐳 Starting Nova Reel MCP Server (Docker STDIO)..." 110 | docker-compose up novareel-stdio 111 | ;; 112 | docker-sse) 113 | echo "🐳 Starting Nova Reel MCP Server (Docker SSE)..." 114 | echo " Access: http://localhost:8000" 115 | docker-compose up novareel-sse 116 | ;; 117 | docker-http) 118 | echo "🐳 Starting Nova Reel MCP Server (Docker HTTP Streaming)..." 119 | echo " Access: http://localhost:8001" 120 | docker-compose up novareel-http 121 | ;; 122 | docker-all) 123 | echo "🐳 Starting all Nova Reel MCP Servers (Docker)..." 124 | echo " SSE Access: http://localhost:8000" 125 | echo " HTTP Access: http://localhost:8001" 126 | docker-compose up -d 127 | echo "✅ All servers started in background" 128 | echo " Use 'docker-compose logs -f' to view logs" 129 | echo " Use 'docker-compose down' to stop" 130 | ;; 131 | docker-both) 132 | echo "🐳 Starting both Nova Reel MCP Servers (Docker - legacy)..." 133 | echo " SSE Access: http://localhost:8000" 134 | docker-compose up -d novareel-stdio novareel-sse 135 | echo "✅ Both servers started in background" 136 | echo " Use 'docker-compose logs -f' to view logs" 137 | echo " Use 'docker-compose down' to stop" 138 | ;; 139 | build) 140 | echo "🔨 Building all Docker images..." 141 | ./build-all.sh 142 | ;; 143 | build-stdio) 144 | echo "🔨 Building STDIO Docker image..." 145 | ./build-stdio.sh 146 | ;; 147 | build-sse) 148 | echo "🔨 Building SSE Docker image..." 149 | ./build-sse.sh 150 | ;; 151 | build-http) 152 | echo "🔨 Building HTTP Streaming Docker image..." 153 | ./build-http.sh 154 | ;; 155 | build-package) 156 | echo "🔨 Building Python package..." 157 | ./build.sh 158 | ;; 159 | *) 160 | echo "❌ Invalid mode: $MODE" 161 | echo "" 162 | echo "Usage: $0 [mode]" 163 | echo "" 164 | echo "Available modes:" 165 | echo " stdio - Run STDIO version locally (default)" 166 | echo " sse - Run SSE version locally" 167 | echo " http - Run HTTP Streaming version locally" 168 | echo " docker-stdio - Run STDIO version in Docker" 169 | echo " docker-sse - Run SSE version in Docker" 170 | echo " docker-http - Run HTTP Streaming version in Docker" 171 | echo " docker-both - Run STDIO + SSE versions in Docker (legacy)" 172 | echo " docker-all - Run all three versions in Docker" 173 | echo " build - Build all Docker images" 174 | echo " build-stdio - Build STDIO Docker image" 175 | echo " build-sse - Build SSE Docker image" 176 | echo " build-http - Build HTTP Streaming Docker image" 177 | echo " build-package - Build Python package (wheel)" 178 | echo "" 179 | echo "Examples:" 180 | echo " $0 # Run STDIO version locally" 181 | echo " $0 sse # Run SSE version locally" 182 | echo " $0 http # Run HTTP Streaming version locally" 183 | echo " $0 build-package # Build Python wheel for uvx" 184 | echo " $0 build # Build all Docker images" 185 | echo " $0 docker-all # Run all three versions in Docker" 186 | exit 1 187 | ;; 188 | esac 189 | ``` -------------------------------------------------------------------------------- /examples/basic_usage.py: -------------------------------------------------------------------------------- ```python 1 | #!/usr/bin/env python3 2 | """ 3 | Basic usage example for Nova Reel MCP Server 4 | This example demonstrates how to generate a simple video using the MCP server. 5 | """ 6 | 7 | import asyncio 8 | import json 9 | import sys 10 | import os 11 | 12 | # Add parent directory to path to import the server modules 13 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 14 | 15 | from main import start_async_invoke, get_async_invoke, list_async_invokes, get_prompting_guide 16 | 17 | async def basic_video_generation(): 18 | """ 19 | Example of basic video generation workflow 20 | """ 21 | print("🎬 Nova Reel MCP Server - Basic Usage Example") 22 | print("=" * 50) 23 | 24 | # Example prompt for a nature scene 25 | prompt = """ 26 | A majestic eagle soars over a mountain valley at golden hour, 27 | camera tracking its flight as it circles above a pristine lake, 28 | then dives gracefully toward the water surface 29 | """ 30 | 31 | print(f"📝 Prompt: {prompt.strip()}") 32 | print() 33 | 34 | try: 35 | # Start video generation 36 | print("🚀 Starting video generation...") 37 | result = await start_async_invoke( 38 | prompt=prompt, 39 | duration_seconds=24, # 24 second video 40 | fps=24, 41 | dimension="1920x1080" # Full HD 42 | ) 43 | 44 | if "error" in result: 45 | print(f"❌ Error: {result['error']}") 46 | return 47 | 48 | job_id = result["job_id"] 49 | print(f"✅ Job started successfully!") 50 | print(f" Job ID: {job_id}") 51 | print(f" Status: {result['status']}") 52 | print(f" Estimated URL: {result['estimated_video_url']}") 53 | print() 54 | 55 | # Monitor progress 56 | print("⏳ Monitoring progress...") 57 | max_attempts = 60 # Wait up to 5 minutes (60 * 5 seconds) 58 | attempt = 0 59 | 60 | while attempt < max_attempts: 61 | status_result = await get_async_invoke(job_id) 62 | 63 | if "error" in status_result: 64 | print(f"❌ Error checking status: {status_result['error']}") 65 | break 66 | 67 | current_status = status_result["status"] 68 | print(f" Status: {current_status} (attempt {attempt + 1}/{max_attempts})") 69 | 70 | if current_status == "Completed": 71 | print("🎉 Video generation completed!") 72 | print(f" Video URL: {status_result['video_url']}") 73 | print(f" Duration: {status_result['config']['duration_seconds']} seconds") 74 | print(f" Dimensions: {status_result['config']['dimension']}") 75 | break 76 | elif current_status in ["Failed", "Cancelled"]: 77 | print(f"❌ Video generation {current_status.lower()}") 78 | if "failure_message" in status_result: 79 | print(f" Reason: {status_result['failure_message']}") 80 | break 81 | 82 | # Wait 5 seconds before checking again 83 | await asyncio.sleep(5) 84 | attempt += 1 85 | 86 | if attempt >= max_attempts: 87 | print("⏰ Timeout reached. Video may still be processing.") 88 | print(" Use get_async_invoke() to check status later.") 89 | 90 | except Exception as e: 91 | print(f"❌ Unexpected error: {e}") 92 | 93 | async def list_all_jobs(): 94 | """ 95 | Example of listing all video generation jobs 96 | """ 97 | print("\n📋 Listing all jobs...") 98 | print("-" * 30) 99 | 100 | try: 101 | jobs_result = await list_async_invokes() 102 | 103 | if "error" in jobs_result: 104 | print(f"❌ Error: {jobs_result['error']}") 105 | return 106 | 107 | total = jobs_result["total_invocations"] 108 | summary = jobs_result["summary"] 109 | 110 | print(f"Total jobs: {total}") 111 | print(f" ✅ Completed: {summary['completed']}") 112 | print(f" ⏳ In Progress: {summary['in_progress']}") 113 | print(f" ❌ Failed: {summary['failed']}") 114 | print(f" ❓ Unknown: {summary['unknown']}") 115 | print() 116 | 117 | if total > 0: 118 | print("Recent jobs:") 119 | for job in jobs_result["invocations"][:5]: # Show last 5 jobs 120 | status_emoji = { 121 | "Completed": "✅", 122 | "InProgress": "⏳", 123 | "Failed": "❌", 124 | "Cancelled": "❌", 125 | "Unknown": "❓" 126 | }.get(job["status"], "❓") 127 | 128 | print(f" {status_emoji} {job['job_id'][:8]}... - {job['status']}") 129 | print(f" Prompt: {job['prompt'][:60]}...") 130 | if job.get("video_url"): 131 | print(f" URL: {job['video_url']}") 132 | print() 133 | 134 | except Exception as e: 135 | print(f"❌ Unexpected error: {e}") 136 | 137 | async def show_prompting_tips(): 138 | """ 139 | Example of getting prompting guidelines 140 | """ 141 | print("\n💡 Prompting Guidelines") 142 | print("=" * 30) 143 | 144 | try: 145 | guide = await get_prompting_guide() 146 | 147 | # Show basic principles 148 | print("Basic Principles:") 149 | for principle, details in guide["basic_principles"].items(): 150 | print(f"\n🔹 {principle.replace('_', ' ').title()}:") 151 | print(f" {details['description']}") 152 | print(f" ✅ Good: {details['good_example']}") 153 | print(f" ❌ Bad: {details['bad_example']}") 154 | 155 | # Show example prompts 156 | print("\n\n📝 Example Prompts:") 157 | examples = guide["example_prompts"] 158 | 159 | for category, prompts in examples.items(): 160 | print(f"\n🎯 {category.title()}:") 161 | for duration, prompt in prompts.items(): 162 | print(f" {duration.replace('_', ' ').title()}: {prompt}") 163 | 164 | # Show common mistakes 165 | print("\n\n⚠️ Common Mistakes to Avoid:") 166 | for mistake, details in guide["common_mistakes"].items(): 167 | print(f"\n❌ {details['problem']}") 168 | print(f" Example: {details['example']}") 169 | print(f" Solution: {details['solution']}") 170 | 171 | except Exception as e: 172 | print(f"❌ Unexpected error: {e}") 173 | 174 | async def main(): 175 | """ 176 | Main example function 177 | """ 178 | print("Welcome to Nova Reel MCP Server Examples!") 179 | print("This example will demonstrate basic video generation.") 180 | print() 181 | 182 | # Check if AWS credentials are configured 183 | if not all([ 184 | os.getenv("AWS_ACCESS_KEY_ID"), 185 | os.getenv("AWS_SECRET_ACCESS_KEY"), 186 | os.getenv("S3_BUCKET") 187 | ]): 188 | print("⚠️ AWS credentials not configured!") 189 | print("Please set the following environment variables:") 190 | print(" - AWS_ACCESS_KEY_ID") 191 | print(" - AWS_SECRET_ACCESS_KEY") 192 | print(" - S3_BUCKET") 193 | print(" - AWS_REGION (optional, defaults to us-east-1)") 194 | print() 195 | print("Example:") 196 | print(" export AWS_ACCESS_KEY_ID=your_access_key") 197 | print(" export AWS_SECRET_ACCESS_KEY=your_secret_key") 198 | print(" export S3_BUCKET=your-bucket-name") 199 | print(" python examples/basic_usage.py") 200 | return 201 | 202 | # Show prompting tips first 203 | await show_prompting_tips() 204 | 205 | # Generate a video 206 | await basic_video_generation() 207 | 208 | # List all jobs 209 | await list_all_jobs() 210 | 211 | print("\n🎉 Example completed!") 212 | print("Check your S3 bucket for the generated video.") 213 | 214 | if __name__ == "__main__": 215 | # Note: This example assumes the MCP server functions are available 216 | # In a real scenario, you would interact with the MCP server via the protocol 217 | print("📝 Note: This is a conceptual example.") 218 | print("In practice, you would interact with the MCP server through an MCP client.") 219 | print("This example shows the expected workflow and API usage.") 220 | 221 | # Run the example 222 | asyncio.run(main()) 223 | ``` -------------------------------------------------------------------------------- /src/novareel_mcp_server/prompting_guide.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Amazon Nova Reel Prompting Guidelines 3 | Based on AWS documentation for video generation and camera control. 4 | """ 5 | 6 | def get_prompting_guidelines(): 7 | """ 8 | Returns comprehensive prompting guidelines for Amazon Nova Reel video generation. 9 | Based on AWS documentation: 10 | - https://docs.aws.amazon.com/nova/latest/userguide/prompting-video-generation.html 11 | - https://docs.aws.amazon.com/nova/latest/userguide/prompting-video-camera-control.html 12 | """ 13 | 14 | return { 15 | "overview": { 16 | "title": "Amazon Nova Reel Video Generation Prompting Guide", 17 | "description": "Best practices for creating effective prompts for video generation with Amazon Nova Reel", 18 | "model": "amazon.nova-reel-v1:1", 19 | "supported_durations": "12-120 seconds (multiples of 6)", 20 | "supported_dimensions": ["1280x720", "1920x1080", "1024x1024"] 21 | }, 22 | 23 | "basic_principles": { 24 | "be_specific": { 25 | "description": "Use specific, descriptive language rather than vague terms", 26 | "good_example": "A red cardinal perched on a snow-covered pine branch, morning sunlight filtering through the trees", 27 | "bad_example": "A bird on a tree" 28 | }, 29 | "use_active_language": { 30 | "description": "Use active voice and present tense for dynamic scenes", 31 | "good_example": "The waves crash against the rocky shore as seagulls soar overhead", 32 | "bad_example": "Waves were crashing and birds were flying" 33 | }, 34 | "include_context": { 35 | "description": "Provide environmental and atmospheric details", 36 | "good_example": "In a bustling Tokyo street at night, neon signs reflect on wet pavement as people hurry past", 37 | "bad_example": "People walking in a city" 38 | } 39 | }, 40 | 41 | "video_structure": { 42 | "beginning_middle_end": { 43 | "description": "Structure your prompt with a clear progression", 44 | "example": "A butterfly lands on a flower (beginning), slowly opens and closes its wings (middle), then flies away into the sunset (end)", 45 | "tip": "For longer videos, describe multiple scenes or actions in sequence" 46 | }, 47 | "pacing": { 48 | "description": "Consider the pacing of actions for your video duration", 49 | "short_videos": "Focus on single actions or moments (12-24 seconds)", 50 | "medium_videos": "Include 2-3 distinct actions or scene changes (30-60 seconds)", 51 | "long_videos": "Develop a narrative with multiple scenes (60-120 seconds)" 52 | } 53 | }, 54 | 55 | "camera_control": { 56 | "overview": "Use specific camera terminology to control shot composition and movement", 57 | 58 | "shot_types": { 59 | "close_up": "Close-up shot of a person's face showing detailed expressions", 60 | "medium_shot": "Medium shot showing a person from waist up", 61 | "wide_shot": "Wide shot establishing the entire scene and environment", 62 | "extreme_close_up": "Extreme close-up focusing on eyes or hands", 63 | "establishing_shot": "Establishing shot revealing the location and setting" 64 | }, 65 | 66 | "camera_movements": { 67 | "pan": "Camera pans left/right across the landscape", 68 | "tilt": "Camera tilts up to reveal the towering mountain", 69 | "zoom": "Camera slowly zooms in on the subject's face", 70 | "dolly": "Camera dollies forward through the forest path", 71 | "tracking": "Camera tracks alongside the running athlete", 72 | "crane": "Camera cranes up to show the aerial view of the city" 73 | }, 74 | 75 | "angles": { 76 | "low_angle": "Low angle shot looking up at the imposing building", 77 | "high_angle": "High angle shot looking down at the busy street", 78 | "bird_eye": "Bird's eye view of the circular plaza", 79 | "worm_eye": "Worm's eye view of the towering trees", 80 | "dutch_angle": "Dutch angle creating a sense of unease" 81 | }, 82 | 83 | "depth_of_field": { 84 | "shallow": "Shallow depth of field with blurred background", 85 | "deep": "Deep focus keeping both foreground and background sharp", 86 | "rack_focus": "Rack focus shifting from foreground to background" 87 | } 88 | }, 89 | 90 | "lighting_and_atmosphere": { 91 | "natural_lighting": { 92 | "golden_hour": "Warm golden hour lighting casting long shadows", 93 | "blue_hour": "Soft blue hour twilight with city lights beginning to glow", 94 | "harsh_sunlight": "Bright midday sun creating strong contrasts", 95 | "overcast": "Soft, diffused lighting from overcast sky" 96 | }, 97 | 98 | "artificial_lighting": { 99 | "neon": "Colorful neon lights reflecting on wet streets", 100 | "candlelight": "Warm, flickering candlelight creating intimate atmosphere", 101 | "spotlight": "Dramatic spotlight illuminating the performer", 102 | "backlighting": "Strong backlighting creating silhouettes" 103 | }, 104 | 105 | "weather_atmosphere": { 106 | "fog": "Mysterious fog rolling through the valley", 107 | "rain": "Heavy rain creating ripples in puddles", 108 | "snow": "Gentle snowfall in the quiet forest", 109 | "storm": "Dramatic storm clouds gathering overhead" 110 | } 111 | }, 112 | 113 | "subject_and_action": { 114 | "people": { 115 | "emotions": "Include specific emotions and expressions", 116 | "clothing": "Describe clothing style and colors", 117 | "age_appearance": "Specify age range and general appearance", 118 | "actions": "Use specific action verbs (strolling, sprinting, gesturing)" 119 | }, 120 | 121 | "animals": { 122 | "species": "Be specific about animal species and breeds", 123 | "behavior": "Describe natural behaviors and movements", 124 | "habitat": "Include appropriate natural habitat details" 125 | }, 126 | 127 | "objects": { 128 | "materials": "Specify materials (wooden, metallic, glass, fabric)", 129 | "condition": "Describe condition (new, weathered, antique, modern)", 130 | "interaction": "How objects interact with environment or subjects" 131 | } 132 | }, 133 | 134 | "style_and_genre": { 135 | "cinematic_styles": { 136 | "documentary": "Documentary style with natural, observational camera work", 137 | "commercial": "Polished commercial style with perfect lighting", 138 | "indie_film": "Indie film aesthetic with handheld camera movement", 139 | "music_video": "Dynamic music video style with quick cuts and effects" 140 | }, 141 | 142 | "visual_styles": { 143 | "realistic": "Photorealistic style with natural colors and lighting", 144 | "stylized": "Stylized with enhanced colors and dramatic lighting", 145 | "vintage": "Vintage film look with grain and muted colors", 146 | "modern": "Clean, modern aesthetic with sharp details" 147 | } 148 | }, 149 | 150 | "technical_considerations": { 151 | "frame_rate": { 152 | "24fps": "Standard cinematic frame rate for natural motion", 153 | "higher_fps": "Higher frame rates for smooth slow-motion effects" 154 | }, 155 | 156 | "resolution": { 157 | "1280x720": "HD resolution suitable for most applications", 158 | "1920x1080": "Full HD for higher quality output", 159 | "1024x1024": "Square format for social media" 160 | }, 161 | 162 | "duration_planning": { 163 | "12_seconds": "Perfect for single action or moment", 164 | "24_seconds": "Good for simple scene with beginning and end", 165 | "60_seconds": "Allows for multiple actions or scene progression", 166 | "120_seconds": "Full narrative with multiple scenes possible" 167 | } 168 | }, 169 | 170 | "example_prompts": { 171 | "nature": { 172 | "short": "Close-up of morning dew drops on a spider web, with soft sunrise lighting creating rainbow reflections", 173 | "medium": "A majestic eagle soars over a mountain valley, camera tracking its flight as it circles above a pristine lake, then dives toward the water", 174 | "long": "Time-lapse of a flower blooming in a meadow: starting as a bud at dawn, slowly opening petals as the sun rises, bees visiting throughout the day, and closing as sunset approaches" 175 | }, 176 | 177 | "urban": { 178 | "short": "Neon signs reflecting in rain puddles on a busy Tokyo street at night, with people's feet splashing through the colorful reflections", 179 | "medium": "A street musician plays violin in a subway station, commuters pause to listen, coins drop into his case, camera slowly pulls back to reveal the bustling underground scene", 180 | "long": "Morning rush hour in Manhattan: alarm clocks ring, people emerge from apartments, flood the sidewalks, enter subway stations, trains arrive and depart, finally arriving at office buildings as the city comes alive" 181 | }, 182 | 183 | "portrait": { 184 | "short": "Extreme close-up of an elderly craftsman's weathered hands carving intricate details into wood, with warm workshop lighting", 185 | "medium": "A young dancer practices alone in a sunlit studio, her movements flowing from gentle stretches to powerful leaps, shadows dancing on the wooden floor", 186 | "long": "Portrait of a chef preparing a signature dish: selecting fresh ingredients at market, returning to kitchen, methodically preparing each component, plating with artistic precision, and presenting the finished masterpiece" 187 | } 188 | }, 189 | 190 | "common_mistakes": { 191 | "too_vague": { 192 | "problem": "Prompts that are too general or vague", 193 | "example": "A person doing something", 194 | "solution": "Be specific about who, what, where, when, and how" 195 | }, 196 | 197 | "conflicting_elements": { 198 | "problem": "Including contradictory or impossible elements", 199 | "example": "Underwater scene with fire burning", 200 | "solution": "Ensure all elements are physically and logically consistent" 201 | }, 202 | 203 | "overcomplication": { 204 | "problem": "Trying to include too many elements or actions", 205 | "example": "A person cooking while dancing while painting while talking on phone", 206 | "solution": "Focus on 1-3 main elements or actions for clarity" 207 | }, 208 | 209 | "inappropriate_duration": { 210 | "problem": "Describing actions that don't match video duration", 211 | "example": "Describing a 5-minute cooking process for a 12-second video", 212 | "solution": "Match action complexity to video duration" 213 | } 214 | }, 215 | 216 | "optimization_tips": { 217 | "use_keywords": "Include relevant keywords for style, mood, and technical aspects", 218 | "specify_quality": "Add terms like 'high quality', 'detailed', 'professional' for better results", 219 | "mention_equipment": "Reference camera types or lenses for specific looks (e.g., 'shot with 85mm lens')", 220 | "include_mood": "Describe the emotional tone or atmosphere you want to convey", 221 | "test_variations": "Try different phrasings of the same concept to find what works best" 222 | }, 223 | 224 | "prompt_templates": { 225 | "basic_template": "[Subject] [Action] [Location] [Lighting] [Camera angle] [Style]", 226 | "narrative_template": "[Opening scene], [transition/development], [conclusion/resolution]", 227 | "technical_template": "[Shot type] of [subject] [action] in [environment], [lighting], [camera movement], [style]" 228 | } 229 | } 230 | ``` -------------------------------------------------------------------------------- /src/novareel_mcp_server/server_sse.py: -------------------------------------------------------------------------------- ```python 1 | #!/usr/bin/env python3 2 | """ 3 | Amazon Nova Reel 1.1 MCP Server - SSE Version 4 | Provides tools for video generation using AWS Bedrock Nova Reel model via Server-Sent Events. 5 | """ 6 | 7 | import argparse 8 | import asyncio 9 | import json 10 | import os 11 | import sys 12 | import random 13 | import time 14 | from datetime import datetime 15 | from typing import Optional, Dict, Any, List 16 | import boto3 17 | from botocore.exceptions import ClientError, NoCredentialsError 18 | 19 | from fastmcp import FastMCP 20 | from .prompting_guide import get_prompting_guidelines 21 | 22 | # Create MCP server with SSE transport 23 | mcp = FastMCP("Amazon Nova Reel 1.1 SSE") 24 | 25 | # Global variables for AWS configuration 26 | aws_access_key_id: Optional[str] = None 27 | aws_secret_access_key: Optional[str] = None 28 | aws_session_token: Optional[str] = None 29 | aws_profile: Optional[str] = None 30 | aws_region: Optional[str] = None 31 | s3_bucket: Optional[str] = None 32 | bedrock_client = None 33 | 34 | # Model configuration 35 | MODEL_ID = "amazon.nova-reel-v1:1" 36 | SLEEP_SECONDS = 5 # Interval for checking video generation progress 37 | 38 | # In-memory storage for tracking invocations (in production, use persistent storage) 39 | active_invocations = {} 40 | 41 | 42 | class NovaReelError(Exception): 43 | """Base exception for Nova Reel operations""" 44 | pass 45 | 46 | 47 | class AWSConfigError(NovaReelError): 48 | """AWS configuration error""" 49 | pass 50 | 51 | 52 | class VideoGenerationError(NovaReelError): 53 | """Video generation error""" 54 | pass 55 | 56 | 57 | def initialize_aws_client(): 58 | """Initialize AWS Bedrock client with provided credentials or profile""" 59 | global bedrock_client 60 | 61 | if not s3_bucket: 62 | raise AWSConfigError("Missing required S3_BUCKET configuration") 63 | 64 | try: 65 | # Option 1: Use AWS Profile 66 | if aws_profile: 67 | print(f"Using AWS profile: {aws_profile}", file=sys.stderr) 68 | session = boto3.Session(profile_name=aws_profile, region_name=aws_region) 69 | bedrock_client = session.client("bedrock-runtime") 70 | 71 | # Option 2: Use explicit credentials 72 | elif aws_access_key_id and aws_secret_access_key: 73 | print("Using explicit AWS credentials", file=sys.stderr) 74 | client_kwargs = { 75 | "service_name": "bedrock-runtime", 76 | "region_name": aws_region, 77 | "aws_access_key_id": aws_access_key_id, 78 | "aws_secret_access_key": aws_secret_access_key 79 | } 80 | 81 | # Add session token if provided (for temporary credentials) 82 | if aws_session_token: 83 | client_kwargs["aws_session_token"] = aws_session_token 84 | print("Using temporary credentials with session token", file=sys.stderr) 85 | 86 | bedrock_client = boto3.client(**client_kwargs) 87 | 88 | # Option 3: Use default credential chain 89 | else: 90 | print("Using default AWS credential chain", file=sys.stderr) 91 | bedrock_client = boto3.client("bedrock-runtime", region_name=aws_region) 92 | 93 | # Test the connection with a simple operation 94 | # Note: bedrock-runtime doesn't have list_foundation_models, that's in bedrock client 95 | # We'll just create the client and let the first actual call test the connection 96 | 97 | except NoCredentialsError: 98 | raise AWSConfigError("No valid AWS credentials found. Please provide explicit credentials, set AWS_PROFILE, or configure default credentials.") 99 | except ClientError as e: 100 | raise AWSConfigError(f"AWS client error: {e}") 101 | except Exception as e: 102 | raise AWSConfigError(f"Failed to initialize AWS client: {e}") 103 | 104 | 105 | @mcp.tool() 106 | async def start_async_invoke( 107 | prompt: str, 108 | duration_seconds: int = 12, 109 | fps: int = 24, 110 | dimension: str = "1280x720", 111 | seed: Optional[int] = None, 112 | task_type: str = "MULTI_SHOT_AUTOMATED" 113 | ) -> Dict[str, Any]: 114 | """ 115 | Start asynchronous video generation with Amazon Nova Reel. 116 | 117 | Args: 118 | prompt: Text description for video generation. See prompting guidelines for best practices. 119 | duration_seconds: Video duration in seconds (must be multiple of 6, range 12-120) 120 | fps: Frames per second (24 recommended) 121 | dimension: Video dimensions (1280x720, 1920x1080, etc.) 122 | seed: Random seed for reproducible results (optional) 123 | task_type: Task type (MULTI_SHOT_AUTOMATED recommended) 124 | 125 | Returns: 126 | Dict containing invocation details and job information 127 | """ 128 | try: 129 | if not bedrock_client: 130 | initialize_aws_client() 131 | 132 | # Validate duration 133 | if duration_seconds < 12 or duration_seconds > 120 or duration_seconds % 6 != 0: 134 | return { 135 | "error": "Duration must be a multiple of 6 in range [12, 120]", 136 | "valid_durations": [12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120] 137 | } 138 | 139 | # Generate seed if not provided 140 | if seed is None: 141 | seed = random.randint(0, 2147483648) 142 | 143 | # Prepare model input 144 | model_input = { 145 | "taskType": task_type, 146 | "multiShotAutomatedParams": {"text": prompt}, 147 | "videoGenerationConfig": { 148 | "durationSeconds": duration_seconds, 149 | "fps": fps, 150 | "dimension": dimension, 151 | "seed": seed, 152 | }, 153 | } 154 | 155 | # Start async invocation 156 | invocation = bedrock_client.start_async_invoke( 157 | modelId=MODEL_ID, 158 | modelInput=model_input, 159 | outputDataConfig={"s3OutputDataConfig": {"s3Uri": f"s3://{s3_bucket}"}}, 160 | ) 161 | 162 | invocation_arn = invocation["invocationArn"] 163 | job_id = invocation_arn.split("/")[-1] 164 | s3_location = f"s3://{s3_bucket}/{job_id}" 165 | 166 | # Store invocation details 167 | invocation_data = { 168 | "invocation_arn": invocation_arn, 169 | "job_id": job_id, 170 | "prompt": prompt, 171 | "duration_seconds": duration_seconds, 172 | "fps": fps, 173 | "dimension": dimension, 174 | "seed": seed, 175 | "task_type": task_type, 176 | "s3_location": s3_location, 177 | "status": "InProgress", 178 | "created_at": datetime.now().isoformat(), 179 | "video_url": None 180 | } 181 | 182 | active_invocations[job_id] = invocation_data 183 | 184 | return { 185 | "success": True, 186 | "invocation_arn": invocation_arn, 187 | "job_id": job_id, 188 | "status": "InProgress", 189 | "s3_location": s3_location, 190 | "estimated_video_url": f"https://{s3_bucket}.s3.{aws_region}.amazonaws.com/{job_id}/output.mp4", 191 | "prompt": prompt, 192 | "config": { 193 | "duration_seconds": duration_seconds, 194 | "fps": fps, 195 | "dimension": dimension, 196 | "seed": seed 197 | }, 198 | "message": "Video generation started. Use get_async_invoke to check progress." 199 | } 200 | 201 | except AWSConfigError as e: 202 | return {"error": f"AWS configuration error: {e}"} 203 | except ClientError as e: 204 | return {"error": f"AWS API error: {e}"} 205 | except Exception as e: 206 | return {"error": f"Unexpected error: {e}"} 207 | 208 | 209 | @mcp.tool() 210 | async def list_async_invokes() -> Dict[str, Any]: 211 | """ 212 | List all tracked async video generation invocations. 213 | 214 | Returns: 215 | Dict containing list of all invocations with their current status 216 | """ 217 | try: 218 | if not bedrock_client: 219 | initialize_aws_client() 220 | 221 | # Update status for all active invocations 222 | updated_invocations = [] 223 | 224 | for job_id, invocation_data in active_invocations.items(): 225 | try: 226 | # Get current status from AWS 227 | response = bedrock_client.get_async_invoke( 228 | invocationArn=invocation_data["invocation_arn"] 229 | ) 230 | 231 | # Update status 232 | current_status = response["status"] 233 | invocation_data["status"] = current_status 234 | 235 | if current_status == "Completed": 236 | invocation_data["video_url"] = f"https://{s3_bucket}.s3.{aws_region}.amazonaws.com/{job_id}/output.mp4" 237 | invocation_data["completed_at"] = datetime.now().isoformat() 238 | elif current_status in ["Failed", "Cancelled"]: 239 | invocation_data["failed_at"] = datetime.now().isoformat() 240 | if "failureMessage" in response: 241 | invocation_data["failure_message"] = response["failureMessage"] 242 | 243 | updated_invocations.append({ 244 | "job_id": job_id, 245 | "status": current_status, 246 | "prompt": invocation_data["prompt"][:100] + "..." if len(invocation_data["prompt"]) > 100 else invocation_data["prompt"], 247 | "created_at": invocation_data["created_at"], 248 | "video_url": invocation_data.get("video_url"), 249 | "duration_seconds": invocation_data["duration_seconds"] 250 | }) 251 | 252 | except ClientError as e: 253 | # If we can't get status, mark as unknown 254 | invocation_data["status"] = "Unknown" 255 | invocation_data["error"] = str(e) 256 | updated_invocations.append({ 257 | "job_id": job_id, 258 | "status": "Unknown", 259 | "prompt": invocation_data["prompt"][:100] + "..." if len(invocation_data["prompt"]) > 100 else invocation_data["prompt"], 260 | "created_at": invocation_data["created_at"], 261 | "error": str(e) 262 | }) 263 | 264 | return { 265 | "success": True, 266 | "total_invocations": len(updated_invocations), 267 | "invocations": updated_invocations, 268 | "summary": { 269 | "in_progress": len([inv for inv in updated_invocations if inv["status"] == "InProgress"]), 270 | "completed": len([inv for inv in updated_invocations if inv["status"] == "Completed"]), 271 | "failed": len([inv for inv in updated_invocations if inv["status"] in ["Failed", "Cancelled"]]), 272 | "unknown": len([inv for inv in updated_invocations if inv["status"] == "Unknown"]) 273 | } 274 | } 275 | 276 | except AWSConfigError as e: 277 | return {"error": f"AWS configuration error: {e}"} 278 | except Exception as e: 279 | return {"error": f"Unexpected error: {e}"} 280 | 281 | 282 | @mcp.tool() 283 | async def get_async_invoke(identifier: str) -> Dict[str, Any]: 284 | """ 285 | Get detailed information about a specific async video generation invocation. 286 | 287 | Args: 288 | identifier: Either job_id or invocation_arn 289 | 290 | Returns: 291 | Dict containing detailed invocation information and video URL if completed 292 | """ 293 | try: 294 | if not bedrock_client: 295 | initialize_aws_client() 296 | 297 | # Find invocation by job_id or invocation_arn 298 | invocation_data = None 299 | job_id = None 300 | 301 | if identifier in active_invocations: 302 | # Direct job_id lookup 303 | job_id = identifier 304 | invocation_data = active_invocations[identifier] 305 | else: 306 | # Search by invocation_arn 307 | for jid, data in active_invocations.items(): 308 | if data["invocation_arn"] == identifier: 309 | job_id = jid 310 | invocation_data = data 311 | break 312 | 313 | if not invocation_data: 314 | return { 315 | "error": f"Invocation not found: {identifier}", 316 | "suggestion": "Use list_async_invokes to see all tracked invocations" 317 | } 318 | 319 | # Get current status from AWS 320 | try: 321 | response = bedrock_client.get_async_invoke( 322 | invocationArn=invocation_data["invocation_arn"] 323 | ) 324 | 325 | current_status = response["status"] 326 | invocation_data["status"] = current_status 327 | 328 | # Prepare detailed response 329 | result = { 330 | "success": True, 331 | "job_id": job_id, 332 | "invocation_arn": invocation_data["invocation_arn"], 333 | "status": current_status, 334 | "prompt": invocation_data["prompt"], 335 | "config": { 336 | "duration_seconds": invocation_data["duration_seconds"], 337 | "fps": invocation_data["fps"], 338 | "dimension": invocation_data["dimension"], 339 | "seed": invocation_data["seed"], 340 | "task_type": invocation_data["task_type"] 341 | }, 342 | "s3_location": invocation_data["s3_location"], 343 | "created_at": invocation_data["created_at"] 344 | } 345 | 346 | if current_status == "Completed": 347 | video_url = f"https://{s3_bucket}.s3.{aws_region}.amazonaws.com/{job_id}/output.mp4" 348 | invocation_data["video_url"] = video_url 349 | invocation_data["completed_at"] = datetime.now().isoformat() 350 | 351 | result["video_url"] = video_url 352 | result["completed_at"] = invocation_data["completed_at"] 353 | result["message"] = "Video generation completed successfully!" 354 | 355 | elif current_status == "InProgress": 356 | result["message"] = "Video generation is still in progress. Check again in a few moments." 357 | 358 | elif current_status in ["Failed", "Cancelled"]: 359 | invocation_data["failed_at"] = datetime.now().isoformat() 360 | result["failed_at"] = invocation_data["failed_at"] 361 | result["message"] = f"Video generation {current_status.lower()}" 362 | 363 | if "failureMessage" in response: 364 | result["failure_message"] = response["failureMessage"] 365 | invocation_data["failure_message"] = response["failureMessage"] 366 | 367 | return result 368 | 369 | except ClientError as e: 370 | return { 371 | "error": f"Failed to get invocation status: {e}", 372 | "job_id": job_id, 373 | "last_known_status": invocation_data.get("status", "Unknown") 374 | } 375 | 376 | except AWSConfigError as e: 377 | return {"error": f"AWS configuration error: {e}"} 378 | except Exception as e: 379 | return {"error": f"Unexpected error: {e}"} 380 | 381 | 382 | @mcp.tool() 383 | async def get_prompting_guide() -> Dict[str, Any]: 384 | """ 385 | Get comprehensive prompting guidelines for Amazon Nova Reel video generation. 386 | 387 | Returns: 388 | Dict containing prompting best practices and examples 389 | """ 390 | return get_prompting_guidelines() 391 | 392 | 393 | def main(): 394 | """Main function to run the MCP server with SSE transport""" 395 | parser = argparse.ArgumentParser(description="Amazon Nova Reel 1.1 MCP Server - SSE Version") 396 | parser.add_argument("--aws-access-key-id", help="AWS Access Key ID") 397 | parser.add_argument("--aws-secret-access-key", help="AWS Secret Access Key") 398 | parser.add_argument("--aws-session-token", help="AWS Session Token (for temporary credentials)") 399 | parser.add_argument("--aws-profile", help="AWS Profile name (alternative to explicit credentials)") 400 | parser.add_argument("--aws-region", default="us-east-1", help="AWS Region") 401 | parser.add_argument("--s3-bucket", help="S3 bucket name for video output") 402 | parser.add_argument("--host", default="0.0.0.0", help="Host to bind to") 403 | parser.add_argument("--port", type=int, default=8000, help="Port to bind to") 404 | 405 | args = parser.parse_args() 406 | 407 | # Set global configuration from args or environment variables 408 | global aws_access_key_id, aws_secret_access_key, aws_session_token, aws_profile, aws_region, s3_bucket 409 | 410 | aws_access_key_id = args.aws_access_key_id or os.getenv("AWS_ACCESS_KEY_ID") 411 | aws_secret_access_key = args.aws_secret_access_key or os.getenv("AWS_SECRET_ACCESS_KEY") 412 | aws_session_token = args.aws_session_token or os.getenv("AWS_SESSION_TOKEN") 413 | aws_profile = args.aws_profile or os.getenv("AWS_PROFILE") 414 | aws_region = args.aws_region or os.getenv("AWS_REGION", "us-east-1") 415 | s3_bucket = args.s3_bucket or os.getenv("S3_BUCKET") 416 | 417 | # Validate configuration - need either profile OR explicit credentials + S3 bucket 418 | if not s3_bucket: 419 | print("Error: Missing required S3_BUCKET configuration.", file=sys.stderr) 420 | print("Please provide --s3-bucket or S3_BUCKET env var", file=sys.stderr) 421 | sys.exit(1) 422 | 423 | # Check if we have valid credential configuration 424 | has_explicit_creds = aws_access_key_id and aws_secret_access_key 425 | has_profile = aws_profile 426 | 427 | if not has_explicit_creds and not has_profile: 428 | print("Error: Missing AWS credentials configuration.", file=sys.stderr) 429 | print("Please provide either:", file=sys.stderr) 430 | print(" Option 1: --aws-access-key-id and --aws-secret-access-key (with optional --aws-session-token)", file=sys.stderr) 431 | print(" Option 2: --aws-profile", file=sys.stderr) 432 | print(" Option 3: Set corresponding environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_PROFILE)", file=sys.stderr) 433 | print(" Option 4: Configure default AWS credentials (e.g., via aws configure)", file=sys.stderr) 434 | sys.exit(1) 435 | 436 | if has_explicit_creds and has_profile: 437 | print("Warning: Both explicit credentials and AWS profile provided. Using explicit credentials.", file=sys.stderr) 438 | aws_profile = None # Clear profile to avoid confusion 439 | 440 | # Remove s3:// prefix if present 441 | if s3_bucket.startswith("s3://"): 442 | s3_bucket = s3_bucket[5:] 443 | 444 | # Initialize AWS client 445 | try: 446 | initialize_aws_client() 447 | print(f"Nova Reel MCP Server (SSE) initialized with region: {aws_region}, bucket: {s3_bucket}", file=sys.stderr) 448 | print(f"Starting server on {args.host}:{args.port}", file=sys.stderr) 449 | except AWSConfigError as e: 450 | print(f"AWS configuration error: {e}", file=sys.stderr) 451 | sys.exit(1) 452 | 453 | # Run MCP server with SSE transport 454 | mcp.run(transport="sse", host=args.host, port=args.port) 455 | 456 | 457 | if __name__ == "__main__": 458 | main() 459 | ``` -------------------------------------------------------------------------------- /src/novareel_mcp_server/server.py: -------------------------------------------------------------------------------- ```python 1 | #!/usr/bin/env python3 2 | """ 3 | Amazon Nova Reel 1.1 MCP Server 4 | Provides tools for video generation using AWS Bedrock Nova Reel model via Model Context Protocol. 5 | """ 6 | 7 | import argparse 8 | import asyncio 9 | import json 10 | import os 11 | import sys 12 | import random 13 | import time 14 | from datetime import datetime 15 | from typing import Optional, Dict, Any, List 16 | import boto3 17 | from botocore.exceptions import ClientError, NoCredentialsError 18 | 19 | from fastmcp import FastMCP 20 | from .prompting_guide import get_prompting_guidelines 21 | 22 | # Create MCP server 23 | mcp = FastMCP("Amazon Nova Reel 1.1") 24 | 25 | # Global variables for AWS configuration 26 | aws_access_key_id: Optional[str] = None 27 | aws_secret_access_key: Optional[str] = None 28 | aws_session_token: Optional[str] = None 29 | aws_profile: Optional[str] = None 30 | aws_region: Optional[str] = None 31 | s3_bucket: Optional[str] = None 32 | bedrock_client = None 33 | 34 | # Model configuration 35 | MODEL_ID = "amazon.nova-reel-v1:1" 36 | SLEEP_SECONDS = 5 # Interval for checking video generation progress 37 | 38 | # Persistent storage for tracking invocations 39 | INVOCATIONS_FILE = os.path.expanduser("~/.novareel_invocations.json") 40 | active_invocations = {} 41 | 42 | 43 | def load_invocations(): 44 | """Load invocations from persistent storage""" 45 | global active_invocations 46 | try: 47 | if os.path.exists(INVOCATIONS_FILE): 48 | with open(INVOCATIONS_FILE, 'r') as f: 49 | active_invocations = json.load(f) 50 | except Exception as e: 51 | print(f"Warning: Could not load invocations file: {e}", file=sys.stderr) 52 | active_invocations = {} 53 | 54 | 55 | def save_invocations(): 56 | """Save invocations to persistent storage""" 57 | try: 58 | with open(INVOCATIONS_FILE, 'w') as f: 59 | json.dump(active_invocations, f, indent=2) 60 | except Exception as e: 61 | print(f"Warning: Could not save invocations file: {e}", file=sys.stderr) 62 | 63 | 64 | class NovaReelError(Exception): 65 | """Base exception for Nova Reel operations""" 66 | pass 67 | 68 | 69 | class AWSConfigError(NovaReelError): 70 | """AWS configuration error""" 71 | pass 72 | 73 | 74 | class VideoGenerationError(NovaReelError): 75 | """Video generation error""" 76 | pass 77 | 78 | 79 | def initialize_aws_client(): 80 | """Initialize AWS Bedrock client with provided credentials or profile""" 81 | global bedrock_client 82 | 83 | if not s3_bucket: 84 | raise AWSConfigError("Missing required S3_BUCKET configuration") 85 | 86 | try: 87 | # Option 1: Use AWS Profile 88 | if aws_profile: 89 | print(f"Using AWS profile: {aws_profile}", file=sys.stderr) 90 | session = boto3.Session(profile_name=aws_profile, region_name=aws_region) 91 | bedrock_client = session.client("bedrock-runtime") 92 | 93 | # Option 2: Use explicit credentials 94 | elif aws_access_key_id and aws_secret_access_key: 95 | print("Using explicit AWS credentials", file=sys.stderr) 96 | client_kwargs = { 97 | "service_name": "bedrock-runtime", 98 | "region_name": aws_region, 99 | "aws_access_key_id": aws_access_key_id, 100 | "aws_secret_access_key": aws_secret_access_key 101 | } 102 | 103 | # Add session token if provided (for temporary credentials) 104 | if aws_session_token: 105 | client_kwargs["aws_session_token"] = aws_session_token 106 | print("Using temporary credentials with session token", file=sys.stderr) 107 | 108 | bedrock_client = boto3.client(**client_kwargs) 109 | 110 | # Option 3: Use default credential chain 111 | else: 112 | print("Using default AWS credential chain", file=sys.stderr) 113 | bedrock_client = boto3.client("bedrock-runtime", region_name=aws_region) 114 | 115 | # Test the connection with a simple operation 116 | # Note: bedrock-runtime doesn't have list_foundation_models, that's in bedrock client 117 | # We'll just create the client and let the first actual call test the connection 118 | 119 | except NoCredentialsError: 120 | raise AWSConfigError("No valid AWS credentials found. Please provide explicit credentials, set AWS_PROFILE, or configure default credentials.") 121 | except ClientError as e: 122 | raise AWSConfigError(f"AWS client error: {e}") 123 | except Exception as e: 124 | raise AWSConfigError(f"Failed to initialize AWS client: {e}") 125 | 126 | 127 | @mcp.tool() 128 | async def start_async_invoke( 129 | prompt: str, 130 | duration_seconds: int = 12, 131 | fps: int = 24, 132 | dimension: str = "1280x720", 133 | seed: Optional[int] = None, 134 | task_type: str = "MULTI_SHOT_AUTOMATED" 135 | ) -> Dict[str, Any]: 136 | """ 137 | Start asynchronous video generation with Amazon Nova Reel. 138 | 139 | Args: 140 | prompt: Text description for video generation. See prompting guidelines for best practices. 141 | duration_seconds: Video duration in seconds (must be multiple of 6, range 12-120) 142 | fps: Frames per second (24 recommended) 143 | dimension: Video dimensions (1280x720, 1920x1080, etc.) 144 | seed: Random seed for reproducible results (optional) 145 | task_type: Task type (MULTI_SHOT_AUTOMATED recommended) 146 | 147 | Returns: 148 | Dict containing invocation details and job information 149 | """ 150 | try: 151 | if not bedrock_client: 152 | initialize_aws_client() 153 | 154 | # Validate duration 155 | if duration_seconds < 12 or duration_seconds > 120 or duration_seconds % 6 != 0: 156 | return { 157 | "error": "Duration must be a multiple of 6 in range [12, 120]", 158 | "valid_durations": [12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120] 159 | } 160 | 161 | # Generate seed if not provided 162 | if seed is None: 163 | seed = random.randint(0, 2147483648) 164 | 165 | # Prepare model input 166 | model_input = { 167 | "taskType": task_type, 168 | "multiShotAutomatedParams": {"text": prompt}, 169 | "videoGenerationConfig": { 170 | "durationSeconds": duration_seconds, 171 | "fps": fps, 172 | "dimension": dimension, 173 | "seed": seed, 174 | }, 175 | } 176 | 177 | # Start async invocation 178 | invocation = bedrock_client.start_async_invoke( 179 | modelId=MODEL_ID, 180 | modelInput=model_input, 181 | outputDataConfig={"s3OutputDataConfig": {"s3Uri": f"s3://{s3_bucket}"}}, 182 | ) 183 | 184 | invocation_arn = invocation["invocationArn"] 185 | job_id = invocation_arn.split("/")[-1] 186 | s3_location = f"s3://{s3_bucket}/{job_id}" 187 | 188 | # Store invocation details 189 | invocation_data = { 190 | "invocation_arn": invocation_arn, 191 | "job_id": job_id, 192 | "prompt": prompt, 193 | "duration_seconds": duration_seconds, 194 | "fps": fps, 195 | "dimension": dimension, 196 | "seed": seed, 197 | "task_type": task_type, 198 | "s3_location": s3_location, 199 | "status": "InProgress", 200 | "created_at": datetime.now().isoformat(), 201 | "video_url": None 202 | } 203 | 204 | active_invocations[job_id] = invocation_data 205 | save_invocations() # Save to persistent storage 206 | 207 | return { 208 | "success": True, 209 | "invocation_arn": invocation_arn, 210 | "job_id": job_id, 211 | "status": "InProgress", 212 | "s3_location": s3_location, 213 | "estimated_video_url": f"https://{s3_bucket}.s3.{aws_region}.amazonaws.com/{job_id}/output.mp4", 214 | "prompt": prompt, 215 | "config": { 216 | "duration_seconds": duration_seconds, 217 | "fps": fps, 218 | "dimension": dimension, 219 | "seed": seed 220 | }, 221 | "message": "Video generation started. Use get_async_invoke to check progress." 222 | } 223 | 224 | except AWSConfigError as e: 225 | return {"error": f"AWS configuration error: {e}"} 226 | except ClientError as e: 227 | return {"error": f"AWS API error: {e}"} 228 | except Exception as e: 229 | return {"error": f"Unexpected error: {e}"} 230 | 231 | 232 | @mcp.tool() 233 | async def list_async_invokes() -> Dict[str, Any]: 234 | """ 235 | List all tracked async video generation invocations. 236 | 237 | Returns: 238 | Dict containing list of all invocations with their current status 239 | """ 240 | try: 241 | if not bedrock_client: 242 | initialize_aws_client() 243 | 244 | # Update status for all active invocations 245 | updated_invocations = [] 246 | 247 | for job_id, invocation_data in active_invocations.items(): 248 | try: 249 | # Get current status from AWS 250 | response = bedrock_client.get_async_invoke( 251 | invocationArn=invocation_data["invocation_arn"] 252 | ) 253 | 254 | # Update status 255 | current_status = response["status"] 256 | invocation_data["status"] = current_status 257 | 258 | if current_status == "Completed": 259 | invocation_data["video_url"] = f"https://{s3_bucket}.s3.{aws_region}.amazonaws.com/{job_id}/output.mp4" 260 | invocation_data["completed_at"] = datetime.now().isoformat() 261 | elif current_status in ["Failed", "Cancelled"]: 262 | invocation_data["failed_at"] = datetime.now().isoformat() 263 | if "failureMessage" in response: 264 | invocation_data["failure_message"] = response["failureMessage"] 265 | 266 | updated_invocations.append({ 267 | "job_id": job_id, 268 | "status": current_status, 269 | "prompt": invocation_data["prompt"][:100] + "..." if len(invocation_data["prompt"]) > 100 else invocation_data["prompt"], 270 | "created_at": invocation_data["created_at"], 271 | "video_url": invocation_data.get("video_url"), 272 | "duration_seconds": invocation_data["duration_seconds"] 273 | }) 274 | 275 | except ClientError as e: 276 | # If we can't get status, mark as unknown 277 | invocation_data["status"] = "Unknown" 278 | invocation_data["error"] = str(e) 279 | updated_invocations.append({ 280 | "job_id": job_id, 281 | "status": "Unknown", 282 | "prompt": invocation_data["prompt"][:100] + "..." if len(invocation_data["prompt"]) > 100 else invocation_data["prompt"], 283 | "created_at": invocation_data["created_at"], 284 | "error": str(e) 285 | }) 286 | 287 | return { 288 | "success": True, 289 | "total_invocations": len(updated_invocations), 290 | "invocations": updated_invocations, 291 | "summary": { 292 | "in_progress": len([inv for inv in updated_invocations if inv["status"] == "InProgress"]), 293 | "completed": len([inv for inv in updated_invocations if inv["status"] == "Completed"]), 294 | "failed": len([inv for inv in updated_invocations if inv["status"] in ["Failed", "Cancelled"]]), 295 | "unknown": len([inv for inv in updated_invocations if inv["status"] == "Unknown"]) 296 | } 297 | } 298 | 299 | except AWSConfigError as e: 300 | return {"error": f"AWS configuration error: {e}"} 301 | except Exception as e: 302 | return {"error": f"Unexpected error: {e}"} 303 | 304 | 305 | @mcp.tool() 306 | async def get_async_invoke(identifier: str) -> Dict[str, Any]: 307 | """ 308 | Get detailed information about a specific async video generation invocation. 309 | 310 | Args: 311 | identifier: Either job_id or invocation_arn 312 | 313 | Returns: 314 | Dict containing detailed invocation information and video URL if completed 315 | """ 316 | try: 317 | if not bedrock_client: 318 | initialize_aws_client() 319 | 320 | # Find invocation by job_id or invocation_arn 321 | invocation_data = None 322 | job_id = None 323 | 324 | if identifier in active_invocations: 325 | # Direct job_id lookup 326 | job_id = identifier 327 | invocation_data = active_invocations[identifier] 328 | else: 329 | # Search by invocation_arn 330 | for jid, data in active_invocations.items(): 331 | if data["invocation_arn"] == identifier: 332 | job_id = jid 333 | invocation_data = data 334 | break 335 | 336 | if not invocation_data: 337 | return { 338 | "error": f"Invocation not found: {identifier}", 339 | "suggestion": "Use list_async_invokes to see all tracked invocations" 340 | } 341 | 342 | # Get current status from AWS 343 | try: 344 | response = bedrock_client.get_async_invoke( 345 | invocationArn=invocation_data["invocation_arn"] 346 | ) 347 | 348 | current_status = response["status"] 349 | invocation_data["status"] = current_status 350 | 351 | # Prepare detailed response 352 | result = { 353 | "success": True, 354 | "job_id": job_id, 355 | "invocation_arn": invocation_data["invocation_arn"], 356 | "status": current_status, 357 | "prompt": invocation_data["prompt"], 358 | "config": { 359 | "duration_seconds": invocation_data["duration_seconds"], 360 | "fps": invocation_data["fps"], 361 | "dimension": invocation_data["dimension"], 362 | "seed": invocation_data["seed"], 363 | "task_type": invocation_data["task_type"] 364 | }, 365 | "s3_location": invocation_data["s3_location"], 366 | "created_at": invocation_data["created_at"] 367 | } 368 | 369 | if current_status == "Completed": 370 | video_url = f"https://{s3_bucket}.s3.{aws_region}.amazonaws.com/{job_id}/output.mp4" 371 | invocation_data["video_url"] = video_url 372 | invocation_data["completed_at"] = datetime.now().isoformat() 373 | 374 | result["video_url"] = video_url 375 | result["completed_at"] = invocation_data["completed_at"] 376 | result["message"] = "Video generation completed successfully!" 377 | 378 | elif current_status == "InProgress": 379 | result["message"] = "Video generation is still in progress. Check again in a few moments." 380 | 381 | elif current_status in ["Failed", "Cancelled"]: 382 | invocation_data["failed_at"] = datetime.now().isoformat() 383 | result["failed_at"] = invocation_data["failed_at"] 384 | result["message"] = f"Video generation {current_status.lower()}" 385 | 386 | if "failureMessage" in response: 387 | result["failure_message"] = response["failureMessage"] 388 | invocation_data["failure_message"] = response["failureMessage"] 389 | 390 | return result 391 | 392 | except ClientError as e: 393 | return { 394 | "error": f"Failed to get invocation status: {e}", 395 | "job_id": job_id, 396 | "last_known_status": invocation_data.get("status", "Unknown") 397 | } 398 | 399 | except AWSConfigError as e: 400 | return {"error": f"AWS configuration error: {e}"} 401 | except Exception as e: 402 | return {"error": f"Unexpected error: {e}"} 403 | 404 | 405 | @mcp.tool() 406 | async def get_prompting_guide() -> Dict[str, Any]: 407 | """ 408 | Get comprehensive prompting guidelines for Amazon Nova Reel video generation. 409 | 410 | Returns: 411 | Dict containing prompting best practices and examples 412 | """ 413 | return get_prompting_guidelines() 414 | 415 | 416 | def main(): 417 | """Main function to run the MCP server""" 418 | parser = argparse.ArgumentParser(description="Amazon Nova Reel 1.1 MCP Server") 419 | parser.add_argument("--aws-access-key-id", help="AWS Access Key ID") 420 | parser.add_argument("--aws-secret-access-key", help="AWS Secret Access Key") 421 | parser.add_argument("--aws-session-token", help="AWS Session Token (for temporary credentials)") 422 | parser.add_argument("--aws-profile", help="AWS Profile name (alternative to explicit credentials)") 423 | parser.add_argument("--aws-region", default="us-east-1", help="AWS Region") 424 | parser.add_argument("--s3-bucket", help="S3 bucket name for video output") 425 | 426 | args = parser.parse_args() 427 | 428 | # Set global configuration from args or environment variables 429 | global aws_access_key_id, aws_secret_access_key, aws_session_token, aws_profile, aws_region, s3_bucket 430 | 431 | aws_access_key_id = args.aws_access_key_id or os.getenv("AWS_ACCESS_KEY_ID") 432 | aws_secret_access_key = args.aws_secret_access_key or os.getenv("AWS_SECRET_ACCESS_KEY") 433 | aws_session_token = args.aws_session_token or os.getenv("AWS_SESSION_TOKEN") 434 | aws_profile = args.aws_profile or os.getenv("AWS_PROFILE") 435 | aws_region = args.aws_region or os.getenv("AWS_REGION", "us-east-1") 436 | s3_bucket = args.s3_bucket or os.getenv("S3_BUCKET") 437 | 438 | # Validate configuration - need either profile OR explicit credentials + S3 bucket 439 | if not s3_bucket: 440 | print("Error: Missing required S3_BUCKET configuration.", file=sys.stderr) 441 | print("Please provide --s3-bucket or S3_BUCKET env var", file=sys.stderr) 442 | sys.exit(1) 443 | 444 | # Check if we have valid credential configuration 445 | has_explicit_creds = aws_access_key_id and aws_secret_access_key 446 | has_profile = aws_profile 447 | 448 | if not has_explicit_creds and not has_profile: 449 | print("Error: Missing AWS credentials configuration.", file=sys.stderr) 450 | print("Please provide either:", file=sys.stderr) 451 | print(" Option 1: --aws-access-key-id and --aws-secret-access-key (with optional --aws-session-token)", file=sys.stderr) 452 | print(" Option 2: --aws-profile", file=sys.stderr) 453 | print(" Option 3: Set corresponding environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_PROFILE)", file=sys.stderr) 454 | print(" Option 4: Configure default AWS credentials (e.g., via aws configure)", file=sys.stderr) 455 | sys.exit(1) 456 | 457 | if has_explicit_creds and has_profile: 458 | print("Warning: Both explicit credentials and AWS profile provided. Using explicit credentials.", file=sys.stderr) 459 | aws_profile = None # Clear profile to avoid confusion 460 | 461 | # Remove s3:// prefix if present 462 | if s3_bucket.startswith("s3://"): 463 | s3_bucket = s3_bucket[5:] 464 | 465 | # Load existing invocations 466 | load_invocations() 467 | 468 | # Initialize AWS client 469 | try: 470 | initialize_aws_client() 471 | print(f"Nova Reel MCP Server initialized with region: {aws_region}, bucket: {s3_bucket}", file=sys.stderr) 472 | print(f"Loaded {len(active_invocations)} existing invocations", file=sys.stderr) 473 | except AWSConfigError as e: 474 | print(f"AWS configuration error: {e}", file=sys.stderr) 475 | sys.exit(1) 476 | 477 | # Run MCP server 478 | mcp.run(transport="stdio") 479 | 480 | 481 | if __name__ == "__main__": 482 | main() 483 | ``` -------------------------------------------------------------------------------- /src/novareel_mcp_server/server_http.py: -------------------------------------------------------------------------------- ```python 1 | #!/usr/bin/env python3 2 | """ 3 | Amazon Nova Reel 1.1 MCP Server - HTTP Streaming Version 4 | Provides tools for video generation using AWS Bedrock Nova Reel model via HTTP Streaming transport. 5 | """ 6 | 7 | import argparse 8 | import asyncio 9 | import json 10 | import os 11 | import sys 12 | import random 13 | import time 14 | from datetime import datetime 15 | from typing import Optional, Dict, Any, List 16 | import boto3 17 | from botocore.exceptions import ClientError, NoCredentialsError 18 | 19 | from fastmcp import FastMCP 20 | from .prompting_guide import get_prompting_guidelines 21 | 22 | # Create MCP server with HTTP transport 23 | mcp = FastMCP("Amazon Nova Reel 1.1 HTTP") 24 | 25 | # Global variables for AWS configuration 26 | aws_access_key_id: Optional[str] = None 27 | aws_secret_access_key: Optional[str] = None 28 | aws_session_token: Optional[str] = None 29 | aws_profile: Optional[str] = None 30 | aws_region: Optional[str] = None 31 | s3_bucket: Optional[str] = None 32 | bedrock_client = None 33 | 34 | # Model configuration 35 | MODEL_ID = "amazon.nova-reel-v1:1" 36 | SLEEP_SECONDS = 5 # Interval for checking video generation progress 37 | 38 | # Persistent storage for tracking invocations 39 | INVOCATIONS_FILE = os.path.expanduser("~/.novareel_invocations_http.json") 40 | active_invocations = {} 41 | 42 | 43 | def load_invocations(): 44 | """Load invocations from persistent storage""" 45 | global active_invocations 46 | try: 47 | if os.path.exists(INVOCATIONS_FILE): 48 | with open(INVOCATIONS_FILE, 'r') as f: 49 | active_invocations = json.load(f) 50 | except Exception as e: 51 | print(f"Warning: Could not load invocations file: {e}", file=sys.stderr) 52 | active_invocations = {} 53 | 54 | 55 | def save_invocations(): 56 | """Save invocations to persistent storage""" 57 | try: 58 | with open(INVOCATIONS_FILE, 'w') as f: 59 | json.dump(active_invocations, f, indent=2) 60 | except Exception as e: 61 | print(f"Warning: Could not save invocations file: {e}", file=sys.stderr) 62 | 63 | 64 | class NovaReelError(Exception): 65 | """Base exception for Nova Reel operations""" 66 | pass 67 | 68 | 69 | class AWSConfigError(NovaReelError): 70 | """AWS configuration error""" 71 | pass 72 | 73 | 74 | class VideoGenerationError(NovaReelError): 75 | """Video generation error""" 76 | pass 77 | 78 | 79 | def initialize_aws_client(): 80 | """Initialize AWS Bedrock client with provided credentials or profile""" 81 | global bedrock_client 82 | 83 | if not s3_bucket: 84 | raise AWSConfigError("Missing required S3_BUCKET configuration") 85 | 86 | try: 87 | # Option 1: Use AWS Profile 88 | if aws_profile: 89 | print(f"Using AWS profile: {aws_profile}", file=sys.stderr) 90 | session = boto3.Session(profile_name=aws_profile, region_name=aws_region) 91 | bedrock_client = session.client("bedrock-runtime") 92 | 93 | # Option 2: Use explicit credentials 94 | elif aws_access_key_id and aws_secret_access_key: 95 | print("Using explicit AWS credentials", file=sys.stderr) 96 | client_kwargs = { 97 | "service_name": "bedrock-runtime", 98 | "region_name": aws_region, 99 | "aws_access_key_id": aws_access_key_id, 100 | "aws_secret_access_key": aws_secret_access_key 101 | } 102 | 103 | # Add session token if provided (for temporary credentials) 104 | if aws_session_token: 105 | client_kwargs["aws_session_token"] = aws_session_token 106 | print("Using temporary credentials with session token", file=sys.stderr) 107 | 108 | bedrock_client = boto3.client(**client_kwargs) 109 | 110 | # Option 3: Use default credential chain 111 | else: 112 | print("Using default AWS credential chain", file=sys.stderr) 113 | bedrock_client = boto3.client("bedrock-runtime", region_name=aws_region) 114 | 115 | # Test the connection with a simple operation 116 | # Note: bedrock-runtime doesn't have list_foundation_models, that's in bedrock client 117 | # We'll just create the client and let the first actual call test the connection 118 | 119 | except NoCredentialsError: 120 | raise AWSConfigError("No valid AWS credentials found. Please provide explicit credentials, set AWS_PROFILE, or configure default credentials.") 121 | except ClientError as e: 122 | raise AWSConfigError(f"AWS client error: {e}") 123 | except Exception as e: 124 | raise AWSConfigError(f"Failed to initialize AWS client: {e}") 125 | 126 | 127 | @mcp.tool() 128 | async def start_async_invoke( 129 | prompt: str, 130 | duration_seconds: int = 12, 131 | fps: int = 24, 132 | dimension: str = "1280x720", 133 | seed: Optional[int] = None, 134 | task_type: str = "MULTI_SHOT_AUTOMATED" 135 | ) -> Dict[str, Any]: 136 | """ 137 | Start asynchronous video generation with Amazon Nova Reel. 138 | 139 | Args: 140 | prompt: Text description for video generation. See prompting guidelines for best practices. 141 | duration_seconds: Video duration in seconds (must be multiple of 6, range 12-120) 142 | fps: Frames per second (24 recommended) 143 | dimension: Video dimensions (1280x720, 1920x1080, etc.) 144 | seed: Random seed for reproducible results (optional) 145 | task_type: Task type (MULTI_SHOT_AUTOMATED recommended) 146 | 147 | Returns: 148 | Dict containing invocation details and job information 149 | """ 150 | try: 151 | if not bedrock_client: 152 | initialize_aws_client() 153 | 154 | # Validate duration 155 | if duration_seconds < 12 or duration_seconds > 120 or duration_seconds % 6 != 0: 156 | return { 157 | "error": "Duration must be a multiple of 6 in range [12, 120]", 158 | "valid_durations": [12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120] 159 | } 160 | 161 | # Generate seed if not provided 162 | if seed is None: 163 | seed = random.randint(0, 2147483648) 164 | 165 | # Prepare model input 166 | model_input = { 167 | "taskType": task_type, 168 | "multiShotAutomatedParams": {"text": prompt}, 169 | "videoGenerationConfig": { 170 | "durationSeconds": duration_seconds, 171 | "fps": fps, 172 | "dimension": dimension, 173 | "seed": seed, 174 | }, 175 | } 176 | 177 | # Start async invocation 178 | invocation = bedrock_client.start_async_invoke( 179 | modelId=MODEL_ID, 180 | modelInput=model_input, 181 | outputDataConfig={"s3OutputDataConfig": {"s3Uri": f"s3://{s3_bucket}"}}, 182 | ) 183 | 184 | invocation_arn = invocation["invocationArn"] 185 | job_id = invocation_arn.split("/")[-1] 186 | s3_location = f"s3://{s3_bucket}/{job_id}" 187 | 188 | # Store invocation details 189 | invocation_data = { 190 | "invocation_arn": invocation_arn, 191 | "job_id": job_id, 192 | "prompt": prompt, 193 | "duration_seconds": duration_seconds, 194 | "fps": fps, 195 | "dimension": dimension, 196 | "seed": seed, 197 | "task_type": task_type, 198 | "s3_location": s3_location, 199 | "status": "InProgress", 200 | "created_at": datetime.now().isoformat(), 201 | "video_url": None 202 | } 203 | 204 | active_invocations[job_id] = invocation_data 205 | save_invocations() # Save to persistent storage 206 | 207 | return { 208 | "success": True, 209 | "invocation_arn": invocation_arn, 210 | "job_id": job_id, 211 | "status": "InProgress", 212 | "s3_location": s3_location, 213 | "estimated_video_url": f"https://{s3_bucket}.s3.{aws_region}.amazonaws.com/{job_id}/output.mp4", 214 | "prompt": prompt, 215 | "config": { 216 | "duration_seconds": duration_seconds, 217 | "fps": fps, 218 | "dimension": dimension, 219 | "seed": seed 220 | }, 221 | "message": "Video generation started. Use get_async_invoke to check progress." 222 | } 223 | 224 | except AWSConfigError as e: 225 | return {"error": f"AWS configuration error: {e}"} 226 | except ClientError as e: 227 | return {"error": f"AWS API error: {e}"} 228 | except Exception as e: 229 | return {"error": f"Unexpected error: {e}"} 230 | 231 | 232 | @mcp.tool() 233 | async def list_async_invokes() -> Dict[str, Any]: 234 | """ 235 | List all tracked async video generation invocations. 236 | 237 | Returns: 238 | Dict containing list of all invocations with their current status 239 | """ 240 | try: 241 | if not bedrock_client: 242 | initialize_aws_client() 243 | 244 | # Update status for all active invocations 245 | updated_invocations = [] 246 | 247 | for job_id, invocation_data in active_invocations.items(): 248 | try: 249 | # Get current status from AWS 250 | response = bedrock_client.get_async_invoke( 251 | invocationArn=invocation_data["invocation_arn"] 252 | ) 253 | 254 | # Update status 255 | current_status = response["status"] 256 | invocation_data["status"] = current_status 257 | 258 | if current_status == "Completed": 259 | invocation_data["video_url"] = f"https://{s3_bucket}.s3.{aws_region}.amazonaws.com/{job_id}/output.mp4" 260 | invocation_data["completed_at"] = datetime.now().isoformat() 261 | elif current_status in ["Failed", "Cancelled"]: 262 | invocation_data["failed_at"] = datetime.now().isoformat() 263 | if "failureMessage" in response: 264 | invocation_data["failure_message"] = response["failureMessage"] 265 | 266 | updated_invocations.append({ 267 | "job_id": job_id, 268 | "status": current_status, 269 | "prompt": invocation_data["prompt"][:100] + "..." if len(invocation_data["prompt"]) > 100 else invocation_data["prompt"], 270 | "created_at": invocation_data["created_at"], 271 | "video_url": invocation_data.get("video_url"), 272 | "duration_seconds": invocation_data["duration_seconds"] 273 | }) 274 | 275 | except ClientError as e: 276 | # If we can't get status, mark as unknown 277 | invocation_data["status"] = "Unknown" 278 | invocation_data["error"] = str(e) 279 | updated_invocations.append({ 280 | "job_id": job_id, 281 | "status": "Unknown", 282 | "prompt": invocation_data["prompt"][:100] + "..." if len(invocation_data["prompt"]) > 100 else invocation_data["prompt"], 283 | "created_at": invocation_data["created_at"], 284 | "error": str(e) 285 | }) 286 | 287 | return { 288 | "success": True, 289 | "total_invocations": len(updated_invocations), 290 | "invocations": updated_invocations, 291 | "summary": { 292 | "in_progress": len([inv for inv in updated_invocations if inv["status"] == "InProgress"]), 293 | "completed": len([inv for inv in updated_invocations if inv["status"] == "Completed"]), 294 | "failed": len([inv for inv in updated_invocations if inv["status"] in ["Failed", "Cancelled"]]), 295 | "unknown": len([inv for inv in updated_invocations if inv["status"] == "Unknown"]) 296 | } 297 | } 298 | 299 | except AWSConfigError as e: 300 | return {"error": f"AWS configuration error: {e}"} 301 | except Exception as e: 302 | return {"error": f"Unexpected error: {e}"} 303 | 304 | 305 | @mcp.tool() 306 | async def get_async_invoke(identifier: str) -> Dict[str, Any]: 307 | """ 308 | Get detailed information about a specific async video generation invocation. 309 | 310 | Args: 311 | identifier: Either job_id or invocation_arn 312 | 313 | Returns: 314 | Dict containing detailed invocation information and video URL if completed 315 | """ 316 | try: 317 | if not bedrock_client: 318 | initialize_aws_client() 319 | 320 | # Find invocation by job_id or invocation_arn 321 | invocation_data = None 322 | job_id = None 323 | 324 | if identifier in active_invocations: 325 | # Direct job_id lookup 326 | job_id = identifier 327 | invocation_data = active_invocations[identifier] 328 | else: 329 | # Search by invocation_arn 330 | for jid, data in active_invocations.items(): 331 | if data["invocation_arn"] == identifier: 332 | job_id = jid 333 | invocation_data = data 334 | break 335 | 336 | if not invocation_data: 337 | return { 338 | "error": f"Invocation not found: {identifier}", 339 | "suggestion": "Use list_async_invokes to see all tracked invocations" 340 | } 341 | 342 | # Get current status from AWS 343 | try: 344 | response = bedrock_client.get_async_invoke( 345 | invocationArn=invocation_data["invocation_arn"] 346 | ) 347 | 348 | current_status = response["status"] 349 | invocation_data["status"] = current_status 350 | 351 | # Prepare detailed response 352 | result = { 353 | "success": True, 354 | "job_id": job_id, 355 | "invocation_arn": invocation_data["invocation_arn"], 356 | "status": current_status, 357 | "prompt": invocation_data["prompt"], 358 | "config": { 359 | "duration_seconds": invocation_data["duration_seconds"], 360 | "fps": invocation_data["fps"], 361 | "dimension": invocation_data["dimension"], 362 | "seed": invocation_data["seed"], 363 | "task_type": invocation_data["task_type"] 364 | }, 365 | "s3_location": invocation_data["s3_location"], 366 | "created_at": invocation_data["created_at"] 367 | } 368 | 369 | if current_status == "Completed": 370 | video_url = f"https://{s3_bucket}.s3.{aws_region}.amazonaws.com/{job_id}/output.mp4" 371 | invocation_data["video_url"] = video_url 372 | invocation_data["completed_at"] = datetime.now().isoformat() 373 | 374 | result["video_url"] = video_url 375 | result["completed_at"] = invocation_data["completed_at"] 376 | result["message"] = "Video generation completed successfully!" 377 | 378 | elif current_status == "InProgress": 379 | result["message"] = "Video generation is still in progress. Check again in a few moments." 380 | 381 | elif current_status in ["Failed", "Cancelled"]: 382 | invocation_data["failed_at"] = datetime.now().isoformat() 383 | result["failed_at"] = invocation_data["failed_at"] 384 | result["message"] = f"Video generation {current_status.lower()}" 385 | 386 | if "failureMessage" in response: 387 | result["failure_message"] = response["failureMessage"] 388 | invocation_data["failure_message"] = response["failureMessage"] 389 | 390 | return result 391 | 392 | except ClientError as e: 393 | return { 394 | "error": f"Failed to get invocation status: {e}", 395 | "job_id": job_id, 396 | "last_known_status": invocation_data.get("status", "Unknown") 397 | } 398 | 399 | except AWSConfigError as e: 400 | return {"error": f"AWS configuration error: {e}"} 401 | except Exception as e: 402 | return {"error": f"Unexpected error: {e}"} 403 | 404 | 405 | @mcp.tool() 406 | async def get_prompting_guide() -> Dict[str, Any]: 407 | """ 408 | Get comprehensive prompting guidelines for Amazon Nova Reel video generation. 409 | 410 | Returns: 411 | Dict containing prompting best practices and examples 412 | """ 413 | return get_prompting_guidelines() 414 | 415 | 416 | def main(): 417 | """Main function to run the MCP server with HTTP streaming transport""" 418 | parser = argparse.ArgumentParser(description="Amazon Nova Reel 1.1 MCP Server - HTTP Streaming Version") 419 | parser.add_argument("--aws-access-key-id", help="AWS Access Key ID") 420 | parser.add_argument("--aws-secret-access-key", help="AWS Secret Access Key") 421 | parser.add_argument("--aws-session-token", help="AWS Session Token (for temporary credentials)") 422 | parser.add_argument("--aws-profile", help="AWS Profile name (alternative to explicit credentials)") 423 | parser.add_argument("--aws-region", default="us-east-1", help="AWS Region") 424 | parser.add_argument("--s3-bucket", help="S3 bucket name for video output") 425 | parser.add_argument("--host", default="0.0.0.0", help="Host to bind to") 426 | parser.add_argument("--port", type=int, default=8001, help="Port to bind to") 427 | 428 | args = parser.parse_args() 429 | 430 | # Set global configuration from args or environment variables 431 | global aws_access_key_id, aws_secret_access_key, aws_session_token, aws_profile, aws_region, s3_bucket 432 | 433 | aws_access_key_id = args.aws_access_key_id or os.getenv("AWS_ACCESS_KEY_ID") 434 | aws_secret_access_key = args.aws_secret_access_key or os.getenv("AWS_SECRET_ACCESS_KEY") 435 | aws_session_token = args.aws_session_token or os.getenv("AWS_SESSION_TOKEN") 436 | aws_profile = args.aws_profile or os.getenv("AWS_PROFILE") 437 | aws_region = args.aws_region or os.getenv("AWS_REGION", "us-east-1") 438 | s3_bucket = args.s3_bucket or os.getenv("S3_BUCKET") 439 | 440 | # Validate configuration - need either profile OR explicit credentials + S3 bucket 441 | if not s3_bucket: 442 | print("Error: Missing required S3_BUCKET configuration.", file=sys.stderr) 443 | print("Please provide --s3-bucket or S3_BUCKET env var", file=sys.stderr) 444 | sys.exit(1) 445 | 446 | # Check if we have valid credential configuration 447 | has_explicit_creds = aws_access_key_id and aws_secret_access_key 448 | has_profile = aws_profile 449 | 450 | if not has_explicit_creds and not has_profile: 451 | print("Error: Missing AWS credentials configuration.", file=sys.stderr) 452 | print("Please provide either:", file=sys.stderr) 453 | print(" Option 1: --aws-access-key-id and --aws-secret-access-key (with optional --aws-session-token)", file=sys.stderr) 454 | print(" Option 2: --aws-profile", file=sys.stderr) 455 | print(" Option 3: Set corresponding environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_PROFILE)", file=sys.stderr) 456 | print(" Option 4: Configure default AWS credentials (e.g., via aws configure)", file=sys.stderr) 457 | sys.exit(1) 458 | 459 | if has_explicit_creds and has_profile: 460 | print("Warning: Both explicit credentials and AWS profile provided. Using explicit credentials.", file=sys.stderr) 461 | aws_profile = None # Clear profile to avoid confusion 462 | 463 | # Remove s3:// prefix if present 464 | if s3_bucket.startswith("s3://"): 465 | s3_bucket = s3_bucket[5:] 466 | 467 | # Load existing invocations 468 | load_invocations() 469 | 470 | # Initialize AWS client 471 | try: 472 | initialize_aws_client() 473 | print(f"Nova Reel MCP Server (HTTP Streaming) initialized with region: {aws_region}, bucket: {s3_bucket}", file=sys.stderr) 474 | print(f"Loaded {len(active_invocations)} existing invocations", file=sys.stderr) 475 | print(f"Starting server on {args.host}:{args.port}", file=sys.stderr) 476 | except AWSConfigError as e: 477 | print(f"AWS configuration error: {e}", file=sys.stderr) 478 | sys.exit(1) 479 | 480 | # Run MCP server with HTTP streaming transport 481 | mcp.run(transport="http", host=args.host, port=args.port) 482 | 483 | 484 | if __name__ == "__main__": 485 | main() 486 | ```