#
tokens: 35170/50000 21/21 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](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 | 
```