ultimate-gemini-mcp 3.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- src/__init__.py +16 -0
- src/config/__init__.py +44 -0
- src/config/constants.py +68 -0
- src/config/settings.py +143 -0
- src/core/__init__.py +47 -0
- src/core/exceptions.py +62 -0
- src/core/validation.py +117 -0
- src/server.py +168 -0
- src/services/__init__.py +13 -0
- src/services/gemini_client.py +304 -0
- src/services/image_service.py +174 -0
- src/services/prompt_enhancer.py +137 -0
- src/tools/__init__.py +11 -0
- src/tools/batch_generate.py +181 -0
- src/tools/generate_image.py +240 -0
- ultimate_gemini_mcp-3.0.7.dist-info/METADATA +462 -0
- ultimate_gemini_mcp-3.0.7.dist-info/RECORD +20 -0
- ultimate_gemini_mcp-3.0.7.dist-info/WHEEL +4 -0
- ultimate_gemini_mcp-3.0.7.dist-info/entry_points.txt +2 -0
- ultimate_gemini_mcp-3.0.7.dist-info/licenses/LICENSE +31 -0
src/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ultimate Gemini MCP Server
|
|
3
|
+
|
|
4
|
+
A unified MCP server that combines the best features from:
|
|
5
|
+
- Gemini 2.5 Flash Image (with prompt enhancement)
|
|
6
|
+
- Imagen 3, 4, and 4-Ultra
|
|
7
|
+
- Advanced features: batch processing, editing, templates, and more
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "3.0.7"
|
|
11
|
+
__author__ = "Ultimate Gemini MCP"
|
|
12
|
+
|
|
13
|
+
from .config import get_settings
|
|
14
|
+
from .server import create_app, main
|
|
15
|
+
|
|
16
|
+
__all__ = ["create_app", "main", "get_settings", "__version__"]
|
src/config/__init__.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Configuration module for Ultimate Gemini MCP."""
|
|
2
|
+
|
|
3
|
+
from .constants import (
|
|
4
|
+
ALL_MODELS,
|
|
5
|
+
ASPECT_RATIOS,
|
|
6
|
+
DEFAULT_ENHANCEMENT_MODEL,
|
|
7
|
+
DEFAULT_IMAGE_SIZE,
|
|
8
|
+
DEFAULT_MODEL,
|
|
9
|
+
DEFAULT_OUTPUT_DIR,
|
|
10
|
+
DEFAULT_TIMEOUT,
|
|
11
|
+
GEMINI_MODELS,
|
|
12
|
+
IMAGE_FORMATS,
|
|
13
|
+
IMAGE_SIZES,
|
|
14
|
+
MAX_BATCH_SIZE,
|
|
15
|
+
MAX_HUMAN_IMAGES,
|
|
16
|
+
MAX_OBJECT_IMAGES,
|
|
17
|
+
MAX_PROMPT_LENGTH,
|
|
18
|
+
MAX_REFERENCE_IMAGES,
|
|
19
|
+
RESPONSE_MODALITIES,
|
|
20
|
+
)
|
|
21
|
+
from .settings import APIConfig, ServerConfig, Settings, get_settings
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"ALL_MODELS",
|
|
25
|
+
"ASPECT_RATIOS",
|
|
26
|
+
"DEFAULT_ENHANCEMENT_MODEL",
|
|
27
|
+
"DEFAULT_IMAGE_SIZE",
|
|
28
|
+
"DEFAULT_MODEL",
|
|
29
|
+
"DEFAULT_OUTPUT_DIR",
|
|
30
|
+
"DEFAULT_TIMEOUT",
|
|
31
|
+
"GEMINI_MODELS",
|
|
32
|
+
"IMAGE_FORMATS",
|
|
33
|
+
"IMAGE_SIZES",
|
|
34
|
+
"MAX_BATCH_SIZE",
|
|
35
|
+
"MAX_HUMAN_IMAGES",
|
|
36
|
+
"MAX_OBJECT_IMAGES",
|
|
37
|
+
"MAX_PROMPT_LENGTH",
|
|
38
|
+
"MAX_REFERENCE_IMAGES",
|
|
39
|
+
"RESPONSE_MODALITIES",
|
|
40
|
+
"APIConfig",
|
|
41
|
+
"ServerConfig",
|
|
42
|
+
"Settings",
|
|
43
|
+
"get_settings",
|
|
44
|
+
]
|
src/config/constants.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constants and model definitions for Gemini 3 Pro Image API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
# Gemini Models (using official Google GenAI SDK)
|
|
8
|
+
GEMINI_MODELS = {
|
|
9
|
+
"gemini-3-pro-image-preview": "gemini-3-pro-image-preview",
|
|
10
|
+
"gemini-flash-latest": "gemini-flash-latest", # For prompt enhancement (non-image)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
# All available models
|
|
14
|
+
ALL_MODELS = GEMINI_MODELS
|
|
15
|
+
|
|
16
|
+
# Default models
|
|
17
|
+
DEFAULT_MODEL = "gemini-3-pro-image-preview"
|
|
18
|
+
DEFAULT_ENHANCEMENT_MODEL = "gemini-flash-latest"
|
|
19
|
+
|
|
20
|
+
# Aspect ratios
|
|
21
|
+
ASPECT_RATIOS = [
|
|
22
|
+
"1:1", # Square
|
|
23
|
+
"2:3", # Portrait
|
|
24
|
+
"3:2", # Landscape
|
|
25
|
+
"3:4", # Portrait
|
|
26
|
+
"4:3", # Standard landscape
|
|
27
|
+
"4:5", # Portrait
|
|
28
|
+
"5:4", # Landscape
|
|
29
|
+
"9:16", # Vertical mobile
|
|
30
|
+
"16:9", # Widescreen
|
|
31
|
+
"21:9", # Ultrawide
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
# Image formats
|
|
35
|
+
IMAGE_FORMATS = {
|
|
36
|
+
"png": "image/png",
|
|
37
|
+
"jpeg": "image/jpeg",
|
|
38
|
+
"jpg": "image/jpeg",
|
|
39
|
+
"webp": "image/webp",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Image sizes (Gemini 3 Pro Image)
|
|
43
|
+
IMAGE_SIZES = ["1K", "2K", "4K"]
|
|
44
|
+
DEFAULT_IMAGE_SIZE = "2K"
|
|
45
|
+
|
|
46
|
+
# Reference images limits (Gemini 3 Pro Image)
|
|
47
|
+
MAX_REFERENCE_IMAGES = 14
|
|
48
|
+
MAX_OBJECT_IMAGES = 6
|
|
49
|
+
MAX_HUMAN_IMAGES = 5
|
|
50
|
+
|
|
51
|
+
# Response modalities
|
|
52
|
+
RESPONSE_MODALITIES = ["TEXT", "IMAGE"]
|
|
53
|
+
|
|
54
|
+
# Generation limits
|
|
55
|
+
MAX_BATCH_SIZE = 8
|
|
56
|
+
MAX_PROMPT_LENGTH = 8192
|
|
57
|
+
|
|
58
|
+
# File size limits
|
|
59
|
+
MAX_IMAGE_SIZE_MB = 20
|
|
60
|
+
MAX_IMAGE_SIZE_BYTES = MAX_IMAGE_SIZE_MB * 1024 * 1024
|
|
61
|
+
|
|
62
|
+
# Timeout settings (in seconds)
|
|
63
|
+
DEFAULT_TIMEOUT = 60
|
|
64
|
+
ENHANCEMENT_TIMEOUT = 30
|
|
65
|
+
BATCH_TIMEOUT = 120
|
|
66
|
+
|
|
67
|
+
# Output settings
|
|
68
|
+
DEFAULT_OUTPUT_DIR = str(Path.home() / "gemini_images")
|
src/config/settings.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration settings for the Ultimate Gemini MCP server.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from pydantic import Field
|
|
10
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
11
|
+
|
|
12
|
+
from .constants import (
|
|
13
|
+
DEFAULT_ENHANCEMENT_MODEL,
|
|
14
|
+
DEFAULT_IMAGE_SIZE,
|
|
15
|
+
DEFAULT_MODEL,
|
|
16
|
+
DEFAULT_OUTPUT_DIR,
|
|
17
|
+
DEFAULT_TIMEOUT,
|
|
18
|
+
MAX_BATCH_SIZE,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ServerConfig(BaseSettings):
|
|
23
|
+
"""Server configuration settings."""
|
|
24
|
+
|
|
25
|
+
model_config = SettingsConfigDict(
|
|
26
|
+
env_file=".env",
|
|
27
|
+
env_file_encoding="utf-8",
|
|
28
|
+
case_sensitive=False,
|
|
29
|
+
extra="ignore",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Server settings
|
|
33
|
+
transport: str = Field(default="stdio", description="Transport mode: stdio or http")
|
|
34
|
+
host: str = Field(default="localhost", description="Host for HTTP transport")
|
|
35
|
+
port: int = Field(default=8000, description="Port for HTTP transport")
|
|
36
|
+
|
|
37
|
+
# Logging settings
|
|
38
|
+
log_level: str = Field(default="INFO", description="Logging level")
|
|
39
|
+
log_format: str = Field(default="standard", description="Log format: standard, json, detailed")
|
|
40
|
+
|
|
41
|
+
# Output settings
|
|
42
|
+
output_dir: str = Field(
|
|
43
|
+
default=DEFAULT_OUTPUT_DIR, description="Directory for generated images"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def from_env(cls) -> "ServerConfig":
|
|
48
|
+
"""Load configuration from environment variables."""
|
|
49
|
+
return cls()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class APIConfig(BaseSettings):
|
|
53
|
+
"""API configuration for Gemini and Imagen."""
|
|
54
|
+
|
|
55
|
+
model_config = SettingsConfigDict(
|
|
56
|
+
env_file=".env",
|
|
57
|
+
env_file_encoding="utf-8",
|
|
58
|
+
case_sensitive=False,
|
|
59
|
+
extra="ignore",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# API Keys
|
|
63
|
+
gemini_api_key: str = Field(
|
|
64
|
+
default="",
|
|
65
|
+
alias="GEMINI_API_KEY",
|
|
66
|
+
description="Gemini API key (also accepts GOOGLE_API_KEY)",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Model settings
|
|
70
|
+
default_model: str = Field(
|
|
71
|
+
default=DEFAULT_MODEL, description="Default Gemini 3 Pro Image model"
|
|
72
|
+
)
|
|
73
|
+
enhancement_model: str = Field(
|
|
74
|
+
default=DEFAULT_ENHANCEMENT_MODEL, description="Model for prompt enhancement"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Feature flags
|
|
78
|
+
enable_prompt_enhancement: bool = Field(
|
|
79
|
+
default=True, description="Enable automatic prompt enhancement"
|
|
80
|
+
)
|
|
81
|
+
enable_batch_processing: bool = Field(default=True, description="Enable batch processing")
|
|
82
|
+
|
|
83
|
+
# Request settings
|
|
84
|
+
request_timeout: int = Field(default=DEFAULT_TIMEOUT, description="API request timeout")
|
|
85
|
+
max_batch_size: int = Field(
|
|
86
|
+
default=MAX_BATCH_SIZE, description="Maximum batch size for parallel requests"
|
|
87
|
+
)
|
|
88
|
+
max_retries: int = Field(default=3, description="Maximum number of retries for failed requests")
|
|
89
|
+
|
|
90
|
+
# Image settings
|
|
91
|
+
default_aspect_ratio: str = Field(default="1:1", description="Default aspect ratio")
|
|
92
|
+
default_image_size: str = Field(
|
|
93
|
+
default=DEFAULT_IMAGE_SIZE, description="Default image size (1K, 2K, 4K)"
|
|
94
|
+
)
|
|
95
|
+
default_output_format: str = Field(default="png", description="Default output format")
|
|
96
|
+
enable_google_search: bool = Field(default=False, description="Enable Google Search grounding")
|
|
97
|
+
response_modalities: list[str] = Field(
|
|
98
|
+
default=["TEXT", "IMAGE"], description="Response modalities"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
102
|
+
"""Initialize API configuration with fallback for API key."""
|
|
103
|
+
super().__init__(**kwargs)
|
|
104
|
+
# Fallback to GOOGLE_API_KEY if GEMINI_API_KEY not set
|
|
105
|
+
if not self.gemini_api_key:
|
|
106
|
+
self.gemini_api_key = os.getenv("GOOGLE_API_KEY", "")
|
|
107
|
+
|
|
108
|
+
if not self.gemini_api_key:
|
|
109
|
+
raise ValueError("GEMINI_API_KEY or GOOGLE_API_KEY environment variable is required")
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def from_env(cls) -> "APIConfig":
|
|
113
|
+
"""Load API configuration from environment variables."""
|
|
114
|
+
return cls()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class Settings:
|
|
118
|
+
"""Combined settings for the server."""
|
|
119
|
+
|
|
120
|
+
def __init__(self) -> None:
|
|
121
|
+
self.server = ServerConfig.from_env()
|
|
122
|
+
self.api = APIConfig.from_env()
|
|
123
|
+
|
|
124
|
+
# Ensure output directory exists
|
|
125
|
+
output_path = Path(self.server.output_dir)
|
|
126
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def output_dir(self) -> Path:
|
|
130
|
+
"""Get output directory as Path object."""
|
|
131
|
+
return Path(self.server.output_dir)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# Global settings instance (lazy initialization)
|
|
135
|
+
_settings: Settings | None = None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def get_settings() -> Settings:
|
|
139
|
+
"""Get or create global settings instance."""
|
|
140
|
+
global _settings
|
|
141
|
+
if _settings is None:
|
|
142
|
+
_settings = Settings()
|
|
143
|
+
return _settings
|
src/core/__init__.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Core modules for Ultimate Gemini MCP."""
|
|
2
|
+
|
|
3
|
+
from .exceptions import (
|
|
4
|
+
APIError,
|
|
5
|
+
AuthenticationError,
|
|
6
|
+
ConfigurationError,
|
|
7
|
+
ContentPolicyError,
|
|
8
|
+
FileOperationError,
|
|
9
|
+
ImageProcessingError,
|
|
10
|
+
RateLimitError,
|
|
11
|
+
UltimateGeminiError,
|
|
12
|
+
ValidationError,
|
|
13
|
+
)
|
|
14
|
+
from .validation import (
|
|
15
|
+
sanitize_filename,
|
|
16
|
+
validate_aspect_ratio,
|
|
17
|
+
validate_base64_image,
|
|
18
|
+
validate_batch_size,
|
|
19
|
+
validate_file_path,
|
|
20
|
+
validate_image_format,
|
|
21
|
+
validate_model,
|
|
22
|
+
validate_prompt,
|
|
23
|
+
validate_prompts_list,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
# Exceptions
|
|
28
|
+
"UltimateGeminiError",
|
|
29
|
+
"ConfigurationError",
|
|
30
|
+
"ValidationError",
|
|
31
|
+
"APIError",
|
|
32
|
+
"AuthenticationError",
|
|
33
|
+
"RateLimitError",
|
|
34
|
+
"ContentPolicyError",
|
|
35
|
+
"ImageProcessingError",
|
|
36
|
+
"FileOperationError",
|
|
37
|
+
# Validation
|
|
38
|
+
"validate_prompt",
|
|
39
|
+
"validate_model",
|
|
40
|
+
"validate_aspect_ratio",
|
|
41
|
+
"validate_image_format",
|
|
42
|
+
"validate_file_path",
|
|
43
|
+
"validate_base64_image",
|
|
44
|
+
"validate_prompts_list",
|
|
45
|
+
"validate_batch_size",
|
|
46
|
+
"sanitize_filename",
|
|
47
|
+
]
|
src/core/exceptions.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom exceptions for the Ultimate Gemini MCP server.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class UltimateGeminiError(Exception):
|
|
7
|
+
"""Base exception for all Ultimate Gemini MCP errors."""
|
|
8
|
+
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConfigurationError(UltimateGeminiError):
|
|
13
|
+
"""Raised when there's a configuration error."""
|
|
14
|
+
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ValidationError(UltimateGeminiError):
|
|
19
|
+
"""Raised when input validation fails."""
|
|
20
|
+
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class APIError(UltimateGeminiError):
|
|
25
|
+
"""Raised when an API request fails."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self, message: str, status_code: int | None = None, response_data: dict | None = None
|
|
29
|
+
):
|
|
30
|
+
super().__init__(message)
|
|
31
|
+
self.status_code = status_code
|
|
32
|
+
self.response_data = response_data
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AuthenticationError(APIError):
|
|
36
|
+
"""Raised when API authentication fails."""
|
|
37
|
+
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class RateLimitError(APIError):
|
|
42
|
+
"""Raised when API rate limit is exceeded."""
|
|
43
|
+
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ContentPolicyError(APIError):
|
|
48
|
+
"""Raised when content violates safety policies."""
|
|
49
|
+
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ImageProcessingError(UltimateGeminiError):
|
|
54
|
+
"""Raised when image processing fails."""
|
|
55
|
+
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class FileOperationError(UltimateGeminiError):
|
|
60
|
+
"""Raised when file operations fail."""
|
|
61
|
+
|
|
62
|
+
pass
|
src/core/validation.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Input validation utilities.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from ..config.constants import (
|
|
10
|
+
ALL_MODELS,
|
|
11
|
+
ASPECT_RATIOS,
|
|
12
|
+
IMAGE_FORMATS,
|
|
13
|
+
MAX_PROMPT_LENGTH,
|
|
14
|
+
)
|
|
15
|
+
from .exceptions import ValidationError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def validate_prompt(prompt: str) -> None:
|
|
19
|
+
"""Validate prompt text."""
|
|
20
|
+
if not prompt or not prompt.strip():
|
|
21
|
+
raise ValidationError("Prompt cannot be empty")
|
|
22
|
+
|
|
23
|
+
if len(prompt) > MAX_PROMPT_LENGTH:
|
|
24
|
+
raise ValidationError(
|
|
25
|
+
f"Prompt too long: {len(prompt)} characters (max {MAX_PROMPT_LENGTH})"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def validate_model(model: str) -> None:
|
|
30
|
+
"""Validate model name."""
|
|
31
|
+
if model not in ALL_MODELS:
|
|
32
|
+
available = ", ".join(ALL_MODELS.keys())
|
|
33
|
+
raise ValidationError(f"Invalid model '{model}'. Available models: {available}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def validate_aspect_ratio(aspect_ratio: str) -> None:
|
|
37
|
+
"""Validate aspect ratio."""
|
|
38
|
+
if aspect_ratio not in ASPECT_RATIOS:
|
|
39
|
+
available = ", ".join(ASPECT_RATIOS)
|
|
40
|
+
raise ValidationError(f"Invalid aspect ratio '{aspect_ratio}'. Available: {available}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def validate_image_format(format_str: str) -> None:
|
|
44
|
+
"""Validate image format."""
|
|
45
|
+
if format_str.lower() not in IMAGE_FORMATS:
|
|
46
|
+
available = ", ".join(IMAGE_FORMATS.keys())
|
|
47
|
+
raise ValidationError(f"Invalid image format '{format_str}'. Available: {available}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def validate_file_path(path: str) -> Path:
|
|
51
|
+
"""Validate and return file path."""
|
|
52
|
+
try:
|
|
53
|
+
file_path = Path(path).resolve()
|
|
54
|
+
except Exception as e:
|
|
55
|
+
raise ValidationError(f"Invalid file path '{path}': {e}") from e
|
|
56
|
+
|
|
57
|
+
if not file_path.exists():
|
|
58
|
+
raise ValidationError(f"File does not exist: {file_path}")
|
|
59
|
+
|
|
60
|
+
if not file_path.is_file():
|
|
61
|
+
raise ValidationError(f"Path is not a file: {file_path}")
|
|
62
|
+
|
|
63
|
+
return file_path
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def validate_base64_image(data: str) -> None:
|
|
67
|
+
"""Validate base64-encoded image data."""
|
|
68
|
+
if not data:
|
|
69
|
+
raise ValidationError("Base64 image data cannot be empty")
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
# Try to decode to verify it's valid base64
|
|
73
|
+
decoded = base64.b64decode(data, validate=True)
|
|
74
|
+
if len(decoded) == 0:
|
|
75
|
+
raise ValidationError("Decoded image data is empty")
|
|
76
|
+
except Exception as e:
|
|
77
|
+
raise ValidationError(f"Invalid base64 image data: {e}") from e
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def validate_prompts_list(prompts: list[str]) -> None:
|
|
81
|
+
"""Validate list of prompts for batch processing."""
|
|
82
|
+
if not isinstance(prompts, list):
|
|
83
|
+
raise ValidationError("Prompts must be a list")
|
|
84
|
+
|
|
85
|
+
if not prompts:
|
|
86
|
+
raise ValidationError("Prompts list cannot be empty")
|
|
87
|
+
|
|
88
|
+
for i, prompt in enumerate(prompts):
|
|
89
|
+
if not isinstance(prompt, str):
|
|
90
|
+
raise ValidationError(f"Prompt at index {i} must be a string")
|
|
91
|
+
try:
|
|
92
|
+
validate_prompt(prompt)
|
|
93
|
+
except ValidationError as e:
|
|
94
|
+
raise ValidationError(f"Invalid prompt at index {i}: {e}") from e
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def sanitize_filename(filename: str) -> str:
|
|
98
|
+
"""Sanitize filename to remove unsafe and special characters."""
|
|
99
|
+
# Replace all non-alphanumeric characters (except hyphens) with underscores
|
|
100
|
+
safe_name = re.sub(r"[^a-zA-Z0-9-]", "_", filename)
|
|
101
|
+
# Remove multiple consecutive underscores
|
|
102
|
+
safe_name = re.sub(r"_+", "_", safe_name)
|
|
103
|
+
# Remove leading/trailing underscores
|
|
104
|
+
safe_name = safe_name.strip("_")
|
|
105
|
+
# Ensure filename is not empty
|
|
106
|
+
if not safe_name:
|
|
107
|
+
safe_name = "image"
|
|
108
|
+
return safe_name
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def validate_batch_size(size: int, max_size: int) -> None:
|
|
112
|
+
"""Validate batch size."""
|
|
113
|
+
if not isinstance(size, int) or size < 1:
|
|
114
|
+
raise ValidationError(f"Batch size must be at least 1, got {size}")
|
|
115
|
+
|
|
116
|
+
if size > max_size:
|
|
117
|
+
raise ValidationError(f"Batch size exceeds maximum: {size} > {max_size}")
|
src/server.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Ultimate Gemini MCP Server - Main Entry Point
|
|
4
|
+
|
|
5
|
+
Unified MCP server supporting:
|
|
6
|
+
- Gemini 3 Pro Image (with 4K resolution, reasoning, prompt enhancement and editing)
|
|
7
|
+
- Imagen 4, 4-Fast, and 4-Ultra (with advanced controls)
|
|
8
|
+
- Batch processing, prompt templates, and comprehensive features
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
from fastmcp import FastMCP
|
|
15
|
+
|
|
16
|
+
from .config import ALL_MODELS, get_settings
|
|
17
|
+
from .tools import register_batch_generate_tool, register_generate_image_tool
|
|
18
|
+
|
|
19
|
+
# Set up logging
|
|
20
|
+
logging.basicConfig(
|
|
21
|
+
level=logging.INFO,
|
|
22
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
23
|
+
stream=sys.stderr, # Important: use stderr for logging in MCP
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def create_app() -> FastMCP:
|
|
30
|
+
"""
|
|
31
|
+
Create and configure the Ultimate Gemini MCP application.
|
|
32
|
+
|
|
33
|
+
This is the factory function used by FastMCP CLI.
|
|
34
|
+
"""
|
|
35
|
+
logger.info("Initializing Ultimate Gemini MCP Server...")
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
# Load settings (validates API key)
|
|
39
|
+
settings = get_settings()
|
|
40
|
+
|
|
41
|
+
logger.info(f"Output directory: {settings.output_dir}")
|
|
42
|
+
logger.info(f"Prompt enhancement: {settings.api.enable_prompt_enhancement}")
|
|
43
|
+
logger.info(f"Available models: {', '.join(ALL_MODELS.keys())}")
|
|
44
|
+
|
|
45
|
+
# Create FastMCP server
|
|
46
|
+
mcp = FastMCP(
|
|
47
|
+
"Ultimate Gemini MCP",
|
|
48
|
+
version="1.5.0",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Register tools
|
|
52
|
+
register_generate_image_tool(mcp)
|
|
53
|
+
register_batch_generate_tool(mcp)
|
|
54
|
+
|
|
55
|
+
# Add resources
|
|
56
|
+
@mcp.resource("models://list")
|
|
57
|
+
def list_models() -> str:
|
|
58
|
+
"""List all available image generation models."""
|
|
59
|
+
import json
|
|
60
|
+
|
|
61
|
+
models_info = {
|
|
62
|
+
"gemini": {
|
|
63
|
+
"gemini-3-pro-image-preview": {
|
|
64
|
+
"name": "Gemini 3 Pro Image",
|
|
65
|
+
"description": "Advanced image generation with 4K resolution, reasoning, and editing",
|
|
66
|
+
"features": [
|
|
67
|
+
"Native 4K resolution",
|
|
68
|
+
"Advanced reasoning capabilities",
|
|
69
|
+
"Prompt enhancement",
|
|
70
|
+
"Image editing",
|
|
71
|
+
"Character consistency",
|
|
72
|
+
"Multi-image blending",
|
|
73
|
+
"World knowledge integration",
|
|
74
|
+
"Grounded generation with Google Search",
|
|
75
|
+
],
|
|
76
|
+
"default": True,
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"imagen": {
|
|
80
|
+
"imagen-4": {
|
|
81
|
+
"name": "Imagen 4",
|
|
82
|
+
"description": "High-quality image generation with improved text rendering",
|
|
83
|
+
"features": [
|
|
84
|
+
"Enhanced quality",
|
|
85
|
+
"Better text rendering",
|
|
86
|
+
"Negative prompts",
|
|
87
|
+
"Seed-based reproducibility",
|
|
88
|
+
"Person generation controls",
|
|
89
|
+
"Advanced controls",
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
"imagen-4-fast": {
|
|
93
|
+
"name": "Imagen 4 Fast",
|
|
94
|
+
"description": "Optimized for faster generation while maintaining good quality",
|
|
95
|
+
"features": [
|
|
96
|
+
"Faster generation speed",
|
|
97
|
+
"Good quality output",
|
|
98
|
+
"Negative prompts",
|
|
99
|
+
"Seed-based reproducibility",
|
|
100
|
+
"Person generation controls",
|
|
101
|
+
"Cost-effective",
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
"imagen-4-ultra": {
|
|
105
|
+
"name": "Imagen 4 Ultra",
|
|
106
|
+
"description": "Highest quality with best prompt adherence",
|
|
107
|
+
"features": [
|
|
108
|
+
"Highest quality",
|
|
109
|
+
"Best prompt adherence",
|
|
110
|
+
"Professional results",
|
|
111
|
+
"Enhanced text rendering",
|
|
112
|
+
"Advanced controls",
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return json.dumps(models_info, indent=2)
|
|
119
|
+
|
|
120
|
+
@mcp.resource("settings://config")
|
|
121
|
+
def get_config() -> str:
|
|
122
|
+
"""Get current server configuration."""
|
|
123
|
+
import json
|
|
124
|
+
|
|
125
|
+
config = {
|
|
126
|
+
"output_directory": str(settings.output_dir),
|
|
127
|
+
"prompt_enhancement_enabled": settings.api.enable_prompt_enhancement,
|
|
128
|
+
"batch_processing_enabled": settings.api.enable_batch_processing,
|
|
129
|
+
"default_model": settings.api.default_model,
|
|
130
|
+
"default_image_size": settings.api.default_image_size,
|
|
131
|
+
"max_batch_size": settings.api.max_batch_size,
|
|
132
|
+
"request_timeout": settings.api.request_timeout,
|
|
133
|
+
"default_aspect_ratio": settings.api.default_aspect_ratio,
|
|
134
|
+
"default_output_format": settings.api.default_output_format,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return json.dumps(config, indent=2)
|
|
138
|
+
|
|
139
|
+
logger.info("Ultimate Gemini MCP Server initialized successfully")
|
|
140
|
+
return mcp
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.error(f"Failed to initialize server: {e}", exc_info=True)
|
|
144
|
+
raise
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def main() -> None:
|
|
148
|
+
"""Main entry point for direct execution."""
|
|
149
|
+
try:
|
|
150
|
+
logger.info("Starting Ultimate Gemini MCP Server...")
|
|
151
|
+
|
|
152
|
+
# Create application
|
|
153
|
+
app = create_app()
|
|
154
|
+
|
|
155
|
+
# Run the server (FastMCP handles stdio transport)
|
|
156
|
+
logger.info("Server is ready and listening for MCP requests")
|
|
157
|
+
app.run()
|
|
158
|
+
|
|
159
|
+
except KeyboardInterrupt:
|
|
160
|
+
logger.info("Server shutdown requested by user")
|
|
161
|
+
sys.exit(0)
|
|
162
|
+
except Exception as e:
|
|
163
|
+
logger.error(f"Server error: {e}", exc_info=True)
|
|
164
|
+
sys.exit(1)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
main()
|
src/services/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Services module for Ultimate Gemini MCP."""
|
|
2
|
+
|
|
3
|
+
from .gemini_client import GeminiClient
|
|
4
|
+
from .image_service import ImageResult, ImageService
|
|
5
|
+
from .prompt_enhancer import PromptEnhancer, create_prompt_enhancer
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"GeminiClient",
|
|
9
|
+
"ImageService",
|
|
10
|
+
"ImageResult",
|
|
11
|
+
"PromptEnhancer",
|
|
12
|
+
"create_prompt_enhancer",
|
|
13
|
+
]
|