Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
578 changes: 578 additions & 0 deletions mcp-server/README.md

Large diffs are not rendered by default.

173 changes: 173 additions & 0 deletions mcp-server/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
"""Configuration management for OpenTryOn MCP Server."""

import os
from pathlib import Path
from typing import Optional
from dotenv import load_dotenv

# Load environment variables from parent directory
parent_dir = Path(__file__).parent.parent
env_path = parent_dir / ".env"
try:
if env_path.exists():
load_dotenv(env_path)
except Exception:
# Silently fail if .env cannot be loaded (e.g., permission issues)
pass


class Config:
"""Configuration for OpenTryOn MCP Server."""

# Server settings
SERVER_NAME = "opentryon-mcp"
SERVER_VERSION = "0.0.1"

# AWS/Amazon Nova Canvas
AWS_ACCESS_KEY_ID: Optional[str] = os.getenv("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY: Optional[str] = os.getenv("AWS_SECRET_ACCESS_KEY")
AMAZON_NOVA_REGION: str = os.getenv("AMAZON_NOVA_REGION", "us-east-1")
AMAZON_NOVA_MODEL_ID: str = os.getenv("AMAZON_NOVA_MODEL_ID", "amazon.nova-canvas-v1:0")

# Kling AI
KLING_AI_API_KEY: Optional[str] = os.getenv("KLING_AI_API_KEY")
KLING_AI_SECRET_KEY: Optional[str] = os.getenv("KLING_AI_SECRET_KEY")
KLING_AI_BASE_URL: str = os.getenv("KLING_AI_BASE_URL", "https://api-singapore.klingai.com")

# Segmind
SEGMIND_API_KEY: Optional[str] = os.getenv("SEGMIND_API_KEY")

# Google Gemini (Nano Banana)
GEMINI_API_KEY: Optional[str] = os.getenv("GEMINI_API_KEY")

# BFL API (FLUX.2)
BFL_API_KEY: Optional[str] = os.getenv("BFL_API_KEY")

# Luma AI
LUMA_AI_API_KEY: Optional[str] = os.getenv("LUMA_AI_API_KEY")

# U2Net Checkpoint
U2NET_CLOTH_SEG_CHECKPOINT_PATH: Optional[str] = os.getenv("U2NET_CLOTH_SEG_CHECKPOINT_PATH")

# File handling
MAX_FILE_SIZE_MB = 50
ALLOWED_IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp"}
ALLOWED_VIDEO_EXTENSIONS = {".mp4", ".mov", ".avi"}

# Temporary files
TEMP_DIR = Path("/tmp/opentryon_mcp")
TEMP_DIR.mkdir(exist_ok=True, parents=True)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Hardcoded Unix temp path causes Windows import failure

The TEMP_DIR is set to a hardcoded Unix path /tmp/opentryon_mcp and mkdir() is called at class definition time (during module import). This causes two problems: (1) the path is not portable - on Windows, /tmp doesn't exist as a standard temp directory, so the server will attempt to create an incorrect path like C:\tmp\opentryon_mcp; (2) executing mkdir() as a class-level statement means any permission errors or filesystem issues will crash the module import entirely, preventing the server from starting. The temp directory creation could be deferred to first use, and tempfile.gettempdir() provides a cross-platform solution.

Fix in Cursor Fix in Web


@classmethod
def validate(cls) -> dict[str, bool]:
"""Validate configuration and return status of each service."""
return {
"amazon_nova": bool(cls.AWS_ACCESS_KEY_ID and cls.AWS_SECRET_ACCESS_KEY),
"kling_ai": bool(cls.KLING_AI_API_KEY and cls.KLING_AI_SECRET_KEY),
"segmind": bool(cls.SEGMIND_API_KEY),
"gemini": bool(cls.GEMINI_API_KEY),
"flux2": bool(cls.BFL_API_KEY),
"luma_ai": bool(cls.LUMA_AI_API_KEY),
"u2net": bool(cls.U2NET_CLOTH_SEG_CHECKPOINT_PATH),
}

@classmethod
def get_status_message(cls) -> str:
"""Get a human-readable status message with helpful guidance."""
status = cls.validate()
lines = ["OpenTryOn MCP Server Configuration Status:"]

# Virtual Try-On Services
nova_status = "✓ Configured" if status['amazon_nova'] else "✗ Not configured (optional - requires AWS)"
kling_status = "✓ Configured" if status['kling_ai'] else "✗ Not configured (recommended)"
segmind_status = "✓ Configured" if status['segmind'] else "✗ Not configured (recommended)"

lines.append(f" Amazon Nova Canvas: {nova_status}")
lines.append(f" Kling AI: {kling_status}")
lines.append(f" Segmind: {segmind_status}")

# Image Generation Services
gemini_status = "✓ Configured" if status['gemini'] else "✗ Not configured (recommended)"
flux_status = "✓ Configured" if status['flux2'] else "✗ Not configured (recommended)"
luma_status = "✓ Configured" if status['luma_ai'] else "✗ Not configured (optional - for video)"

lines.append(f" Gemini (Nano Banana): {gemini_status}")
lines.append(f" FLUX.2: {flux_status}")
lines.append(f" Luma AI: {luma_status}")

# Preprocessing
u2net_status = "✓ Configured" if status['u2net'] else "✗ Not configured (optional - for local segmentation)"
lines.append(f" U2Net (Preprocessing): {u2net_status}")

# Add helpful message
vton_count = sum([status['amazon_nova'], status['kling_ai'], status['segmind']])
img_count = sum([status['gemini'], status['flux2'], status['luma_ai']])

lines.append("")
if vton_count == 0:
lines.append("⚠️ Warning: No virtual try-on service configured!")
lines.append(" Configure at least one: Kling AI (recommended) or Segmind")
if img_count == 0:
lines.append("⚠️ Warning: No image generation service configured!")
lines.append(" Configure at least one: Gemini (recommended) or FLUX.2")

if vton_count > 0 and img_count > 0:
lines.append("✅ Ready! At least one service from each category is configured.")
lines.append(f" Virtual Try-On: {vton_count}/3 services")
lines.append(f" Image Generation: {img_count}/3 services")

lines.append("")
lines.append("💡 Tip: Copy env.template to .env and add your API keys")
lines.append("📖 Setup guide: mcp-server/README.md")

return "\n".join(lines)

@classmethod
def get_missing_services(cls) -> dict[str, list[str]]:
"""Get list of missing services by category."""
status = cls.validate()

missing = {
"virtual_tryon": [],
"image_generation": [],
"optional": []
}

# Virtual Try-On (at least one required)
if not status['kling_ai']:
missing["virtual_tryon"].append("Kling AI")
if not status['segmind']:
missing["virtual_tryon"].append("Segmind")
if not status['amazon_nova']:
missing["optional"].append("Amazon Nova Canvas (requires AWS)")

# Image Generation (at least one required)
if not status['gemini']:
missing["image_generation"].append("Gemini (Nano Banana)")
if not status['flux2']:
missing["image_generation"].append("FLUX.2")

# Optional services
if not status['luma_ai']:
missing["optional"].append("Luma AI (for video generation)")
if not status['u2net']:
missing["optional"].append("U2Net (for local garment segmentation)")

return missing

@classmethod
def is_ready(cls) -> bool:
"""Check if minimum required services are configured."""
status = cls.validate()

# Need at least one virtual try-on service
has_vton = status['kling_ai'] or status['segmind'] or status['amazon_nova']

# Need at least one image generation service
has_img_gen = status['gemini'] or status['flux2'] or status['luma_ai']

return has_vton and has_img_gen


config = Config()

14 changes: 14 additions & 0 deletions mcp-server/examples/claude_desktop_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"mcpServers": {
"opentryon": {
"command": "python",
"args": [
"/absolute/path/to/opentryon/mcp-server/server.py"
],
"env": {
"PYTHONPATH": "/absolute/path/to/opentryon"
}
}
}
}

179 changes: 179 additions & 0 deletions mcp-server/examples/example_usage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
"""Example usage of OpenTryOn MCP Server tools."""

import json
from pathlib import Path
import sys

# Add parent directory to path to import tools
sys.path.insert(0, str(Path(__file__).parent.parent))

from tools import (
virtual_tryon_nova,
virtual_tryon_kling,
virtual_tryon_segmind,
generate_image_nano_banana,
generate_image_nano_banana_pro,
generate_image_flux2_pro,
generate_video_luma_ray,
segment_garment,
load_fashion_mnist,
)


def example_virtual_tryon():
"""Example: Virtual try-on with different providers."""
print("\n=== Virtual Try-On Examples ===\n")

# Example 1: Amazon Nova Canvas
print("1. Amazon Nova Canvas Virtual Try-On")
result = virtual_tryon_nova(
source_image="path/to/person.jpg",
reference_image="path/to/garment.jpg",
garment_class="UPPER_BODY",
output_dir="outputs/nova"
)
print(json.dumps(result, indent=2))

# Example 2: Kling AI
print("\n2. Kling AI Virtual Try-On")
result = virtual_tryon_kling(
source_image="path/to/person.jpg",
reference_image="path/to/garment.jpg",
output_dir="outputs/kling"
)
print(json.dumps(result, indent=2))

# Example 3: Segmind
print("\n3. Segmind Virtual Try-On")
result = virtual_tryon_segmind(
model_image="path/to/person.jpg",
cloth_image="path/to/garment.jpg",
category="Upper body",
num_inference_steps=35,
guidance_scale=2.5,
output_dir="outputs/segmind"
)
print(json.dumps(result, indent=2))


def example_image_generation():
"""Example: Image generation with different models."""
print("\n=== Image Generation Examples ===\n")

# Example 1: Nano Banana (Fast)
print("1. Nano Banana - Fast Image Generation")
result = generate_image_nano_banana(
prompt="A professional fashion model wearing elegant evening wear on a runway",
aspect_ratio="16:9",
output_dir="outputs/nano_banana"
)
print(json.dumps(result, indent=2))

# Example 2: Nano Banana Pro (4K)
print("\n2. Nano Banana Pro - 4K Image Generation")
result = generate_image_nano_banana_pro(
prompt="Professional fashion photography of elegant evening wear",
resolution="4K",
aspect_ratio="16:9",
use_search_grounding=True,
output_dir="outputs/nano_banana_pro"
)
print(json.dumps(result, indent=2))

# Example 3: FLUX.2 PRO
print("\n3. FLUX.2 PRO - High-Quality Image Generation")
result = generate_image_flux2_pro(
prompt="A stylish fashion model in modern casual wear",
width=1024,
height=1024,
seed=42,
output_dir="outputs/flux2_pro"
)
print(json.dumps(result, indent=2))


def example_video_generation():
"""Example: Video generation with Luma AI."""
print("\n=== Video Generation Examples ===\n")

# Example 1: Text-to-Video
print("1. Text-to-Video with Ray 2")
result = generate_video_luma_ray(
prompt="A fashion model walking on a runway in elegant evening wear",
model="ray-2",
mode="text_video",
resolution="720p",
duration="5s",
aspect_ratio="16:9",
output_dir="outputs/videos"
)
print(json.dumps(result, indent=2))

# Example 2: Image-to-Video with Keyframes
print("\n2. Image-to-Video with Keyframes")
result = generate_video_luma_ray(
prompt="Model walking gracefully",
model="ray-2",
mode="image_video",
start_image="path/to/start_frame.jpg",
end_image="path/to/end_frame.jpg",
resolution="720p",
duration="5s",
output_dir="outputs/videos"
)
print(json.dumps(result, indent=2))


def example_preprocessing():
"""Example: Preprocessing tools."""
print("\n=== Preprocessing Examples ===\n")

# Example 1: Segment Garment
print("1. Segment Garment")
result = segment_garment(
input_path="path/to/garment_images",
output_dir="outputs/segmented",
garment_class="upper"
)
print(json.dumps(result, indent=2))


def example_datasets():
"""Example: Dataset loading."""
print("\n=== Dataset Examples ===\n")

# Example 1: Fashion-MNIST
print("1. Load Fashion-MNIST Dataset")
result = load_fashion_mnist(
download=True,
normalize=True,
flatten=False
)
print(json.dumps(result, indent=2))


def main():
"""Run all examples."""
print("OpenTryOn MCP Server - Example Usage")
print("=" * 50)

# Note: These examples show the API structure
# Actual execution requires valid image paths and API keys

print("\nNote: These are example API calls.")
print("Replace paths and ensure API keys are configured in .env file.")
print("\nUncomment the examples you want to run:\n")

# Uncomment to run examples:
# example_virtual_tryon()
# example_image_generation()
# example_video_generation()
# example_preprocessing()
# example_datasets()

print("\nExamples complete!")


if __name__ == "__main__":
main()

Loading