everypixel-cli 0.1.0__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.
- everypixel_cli/__init__.py +1 -0
- everypixel_cli/__main__.py +5 -0
- everypixel_cli/application/__init__.py +10 -0
- everypixel_cli/application/models.py +59 -0
- everypixel_cli/application/serialization.py +20 -0
- everypixel_cli/application/services.py +1376 -0
- everypixel_cli/cli.py +1520 -0
- everypixel_cli/client.py +159 -0
- everypixel_cli/config.py +221 -0
- everypixel_cli/errors.py +262 -0
- everypixel_cli/files.py +298 -0
- everypixel_cli/mcp_server.py +828 -0
- everypixel_cli/openapi.py +338 -0
- everypixel_cli/output.py +159 -0
- everypixel_cli/resources/__init__.py +1 -0
- everypixel_cli/resources/openapi.json +5402 -0
- everypixel_cli/schemas.py +846 -0
- everypixel_cli-0.1.0.dist-info/METADATA +454 -0
- everypixel_cli-0.1.0.dist-info/RECORD +22 -0
- everypixel_cli-0.1.0.dist-info/WHEEL +4 -0
- everypixel_cli-0.1.0.dist-info/entry_points.txt +3 -0
- everypixel_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
"""MCP tools backed by the transport-independent application services."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import Callable, Mapping
|
|
7
|
+
from contextvars import ContextVar
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from threading import Event
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import anyio
|
|
14
|
+
from mcp.server import Server
|
|
15
|
+
from mcp.server.stdio import stdio_server
|
|
16
|
+
from mcp.types import (
|
|
17
|
+
CallToolRequestParams,
|
|
18
|
+
CallToolResult,
|
|
19
|
+
ListToolsResult,
|
|
20
|
+
TextContent,
|
|
21
|
+
Tool,
|
|
22
|
+
ToolAnnotations,
|
|
23
|
+
)
|
|
24
|
+
from pydantic import BaseModel, ConfigDict, Field, ValidationError
|
|
25
|
+
|
|
26
|
+
from . import __version__
|
|
27
|
+
from .application import (
|
|
28
|
+
ApplicationServices,
|
|
29
|
+
ExecutionOptions,
|
|
30
|
+
OperationResult,
|
|
31
|
+
)
|
|
32
|
+
from .application.models import OperationCancelled
|
|
33
|
+
from .application.serialization import serialize_operation_result
|
|
34
|
+
from .client import APIClient
|
|
35
|
+
from .config import resolved_settings
|
|
36
|
+
from .errors import (
|
|
37
|
+
ValidationCLIError,
|
|
38
|
+
format_validation_errors,
|
|
39
|
+
normalize_exception,
|
|
40
|
+
)
|
|
41
|
+
from .schemas import (
|
|
42
|
+
ImageEditMedia,
|
|
43
|
+
ImageEditModel,
|
|
44
|
+
ImageGenerateModel,
|
|
45
|
+
ImageResolution,
|
|
46
|
+
ImageSize,
|
|
47
|
+
ImageStyle,
|
|
48
|
+
LipsyncImageModel,
|
|
49
|
+
LipsyncImageResolution,
|
|
50
|
+
LipsyncVideoResolution,
|
|
51
|
+
PublicFigureThreshold,
|
|
52
|
+
VideoEditAspectRatio,
|
|
53
|
+
VideoEditDuration,
|
|
54
|
+
VideoEditModel,
|
|
55
|
+
VideoEditResolution,
|
|
56
|
+
VideoGenerateAspectRatio,
|
|
57
|
+
VideoGenerateDuration,
|
|
58
|
+
VideoGenerateModel,
|
|
59
|
+
VideoGenerateResolution,
|
|
60
|
+
VideoUpscaleResolution,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
ServiceFactory = Callable[[], ApplicationServices]
|
|
65
|
+
_CURRENT_CANCEL_EVENT: ContextVar[Event | None] = ContextVar(
|
|
66
|
+
"everypixel_mcp_cancellation",
|
|
67
|
+
default=None,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
READ_ONLY = ToolAnnotations(
|
|
71
|
+
read_only_hint=True,
|
|
72
|
+
idempotent_hint=True,
|
|
73
|
+
open_world_hint=True,
|
|
74
|
+
)
|
|
75
|
+
LOCAL_WRITE = ToolAnnotations(
|
|
76
|
+
read_only_hint=False,
|
|
77
|
+
destructive_hint=True,
|
|
78
|
+
idempotent_hint=False,
|
|
79
|
+
open_world_hint=True,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class MCPExecutionOptions(BaseModel):
|
|
84
|
+
"""Execution controls shared by asynchronous Everypixel tools."""
|
|
85
|
+
|
|
86
|
+
model_config = ConfigDict(extra="forbid")
|
|
87
|
+
|
|
88
|
+
wait: bool = Field(
|
|
89
|
+
default=True,
|
|
90
|
+
description=(
|
|
91
|
+
"Poll until the asynchronous task reaches SUCCESS; set false to "
|
|
92
|
+
"return the created task immediately."
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
download_directory: str | None = Field(
|
|
96
|
+
default=None,
|
|
97
|
+
description="Local directory for completed result files; implies waiting.",
|
|
98
|
+
)
|
|
99
|
+
timeout: float = Field(
|
|
100
|
+
default=300.0,
|
|
101
|
+
gt=0,
|
|
102
|
+
description="Maximum task wait time in seconds.",
|
|
103
|
+
)
|
|
104
|
+
poll_interval: float = Field(
|
|
105
|
+
default=2.0,
|
|
106
|
+
gt=0,
|
|
107
|
+
description="Delay between task status requests in seconds.",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _execution(
|
|
112
|
+
value: MCPExecutionOptions | None,
|
|
113
|
+
*,
|
|
114
|
+
force_wait: bool | None = None,
|
|
115
|
+
) -> ExecutionOptions:
|
|
116
|
+
options = value or MCPExecutionOptions()
|
|
117
|
+
return ExecutionOptions(
|
|
118
|
+
wait=options.wait if force_wait is None else force_wait,
|
|
119
|
+
download_directory=(
|
|
120
|
+
Path(options.download_directory) if options.download_directory else None
|
|
121
|
+
),
|
|
122
|
+
timeout=options.timeout,
|
|
123
|
+
poll_interval=options.poll_interval,
|
|
124
|
+
cancel_event=_CURRENT_CANCEL_EVENT.get(),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _tool_result(payload: Any, *, is_error: bool = False) -> CallToolResult:
|
|
129
|
+
"""Build one text and structured MCP result from a JSON-compatible value."""
|
|
130
|
+
|
|
131
|
+
structured = dict(payload) if isinstance(payload, Mapping) else {"result": payload}
|
|
132
|
+
return CallToolResult(
|
|
133
|
+
content=[
|
|
134
|
+
TextContent(
|
|
135
|
+
text=json.dumps(payload, ensure_ascii=False, default=str),
|
|
136
|
+
)
|
|
137
|
+
],
|
|
138
|
+
structured_content=structured,
|
|
139
|
+
is_error=is_error,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _invoke(action: Callable[[], Any]) -> CallToolResult:
|
|
144
|
+
"""Execute a service action through the MCP success/error boundary."""
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
value = action()
|
|
148
|
+
if isinstance(value, OperationResult):
|
|
149
|
+
value = serialize_operation_result(value)
|
|
150
|
+
return _tool_result(value)
|
|
151
|
+
except OperationCancelled:
|
|
152
|
+
raise
|
|
153
|
+
except Exception as exc: # The MCP boundary must never leak tracebacks or secrets.
|
|
154
|
+
return _tool_result(normalize_exception(exc).to_payload(), is_error=True)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class MCPToolInput(BaseModel):
|
|
158
|
+
"""Strict base model shared by all public MCP tool inputs."""
|
|
159
|
+
|
|
160
|
+
model_config = ConfigDict(extra="forbid")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class ImageGenerateInput(MCPToolInput):
|
|
164
|
+
prompt: str
|
|
165
|
+
model: ImageGenerateModel = "zimage"
|
|
166
|
+
image_size: ImageSize = "square"
|
|
167
|
+
style: ImageStyle | None = None
|
|
168
|
+
image: str | None = None
|
|
169
|
+
resolution: ImageResolution | None = None
|
|
170
|
+
seed: int = -1
|
|
171
|
+
callback_url: str | None = None
|
|
172
|
+
execution: MCPExecutionOptions | None = None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class ImageEditInput(MCPToolInput):
|
|
176
|
+
prompt: str
|
|
177
|
+
images: ImageEditMedia
|
|
178
|
+
model: ImageEditModel = "flux2"
|
|
179
|
+
image_size: ImageSize | None = None
|
|
180
|
+
resolution: ImageResolution | None = None
|
|
181
|
+
seed: int = -1
|
|
182
|
+
callback_url: str | None = None
|
|
183
|
+
execution: MCPExecutionOptions | None = None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class ImageUpscaleInput(MCPToolInput):
|
|
187
|
+
image: str | None = None
|
|
188
|
+
task_id: str | None = None
|
|
189
|
+
model: str = "seedvr2"
|
|
190
|
+
callback_url: str | None = None
|
|
191
|
+
execution: MCPExecutionOptions | None = None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class ImageAnglesInput(MCPToolInput):
|
|
195
|
+
image: str
|
|
196
|
+
azimuth: str = "front"
|
|
197
|
+
elevation: str = "eye_level"
|
|
198
|
+
distance: str = "medium"
|
|
199
|
+
prompt: str | None = None
|
|
200
|
+
execution: MCPExecutionOptions | None = None
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class ImageColorsInput(MCPToolInput):
|
|
204
|
+
image: str
|
|
205
|
+
reference: str
|
|
206
|
+
execution: MCPExecutionOptions | None = None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class VideoGenerateInput(MCPToolInput):
|
|
210
|
+
prompt: str
|
|
211
|
+
model: VideoGenerateModel = "ltx23"
|
|
212
|
+
duration: VideoGenerateDuration | None = None
|
|
213
|
+
resolution: VideoGenerateResolution | None = None
|
|
214
|
+
aspect_ratio: VideoGenerateAspectRatio | None = "16:9"
|
|
215
|
+
lora_high_url: str | None = None
|
|
216
|
+
lora_low_url: str | None = None
|
|
217
|
+
reference_images: list[str] | None = None
|
|
218
|
+
reference_videos: list[str] | None = None
|
|
219
|
+
image: str | None = None
|
|
220
|
+
last_image: str | None = None
|
|
221
|
+
seed: int | None = None
|
|
222
|
+
generate_audio: bool | None = None
|
|
223
|
+
callback_url: str | None = None
|
|
224
|
+
execution: MCPExecutionOptions | None = None
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class VideoEditInput(MCPToolInput):
|
|
228
|
+
prompt: str | None = None
|
|
229
|
+
model: VideoEditModel = "seedance2"
|
|
230
|
+
images: list[str] | None = None
|
|
231
|
+
video: str | None = None
|
|
232
|
+
audio: str | None = None
|
|
233
|
+
duration: VideoEditDuration | None = None
|
|
234
|
+
resolution: VideoEditResolution | None = None
|
|
235
|
+
aspect_ratio: VideoEditAspectRatio | None = None
|
|
236
|
+
seed: int | None = None
|
|
237
|
+
generate_audio: bool | None = None
|
|
238
|
+
callback_url: str | None = None
|
|
239
|
+
keyframes: list[dict[str, Any]] | None = None
|
|
240
|
+
public_figure_threshold: PublicFigureThreshold | None = None
|
|
241
|
+
execution: MCPExecutionOptions | None = None
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class VideoUpscaleInput(MCPToolInput):
|
|
245
|
+
video: str | None = None
|
|
246
|
+
task_id: str | None = None
|
|
247
|
+
resolution: VideoUpscaleResolution = "1080p"
|
|
248
|
+
execution: MCPExecutionOptions | None = None
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class LipsyncVideoInput(MCPToolInput):
|
|
252
|
+
video: str
|
|
253
|
+
audio: str
|
|
254
|
+
resolution: LipsyncVideoResolution = "480p"
|
|
255
|
+
prompt: str | None = None
|
|
256
|
+
seed: int = -1
|
|
257
|
+
callback_url: str | None = None
|
|
258
|
+
execution: MCPExecutionOptions | None = None
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class LipsyncImageInput(MCPToolInput):
|
|
262
|
+
image: str
|
|
263
|
+
audio: str
|
|
264
|
+
model: LipsyncImageModel = "inftalk"
|
|
265
|
+
resolution: LipsyncImageResolution = "480p"
|
|
266
|
+
prompt: str | None = None
|
|
267
|
+
seed: int = -1
|
|
268
|
+
callback_url: str | None = None
|
|
269
|
+
execution: MCPExecutionOptions | None = None
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class AudioTranscribeInput(MCPToolInput):
|
|
273
|
+
audio: str
|
|
274
|
+
language: str = "auto"
|
|
275
|
+
hints: str = ""
|
|
276
|
+
denoise: bool = True
|
|
277
|
+
execution: MCPExecutionOptions | None = None
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class TTSCreateInput(MCPToolInput):
|
|
281
|
+
text: str | None = None
|
|
282
|
+
text_file: str | None = None
|
|
283
|
+
speaker: str = "Ryan"
|
|
284
|
+
style: str = "Auto"
|
|
285
|
+
language: str = "Auto"
|
|
286
|
+
prompt: str = ""
|
|
287
|
+
seed: int = -1
|
|
288
|
+
execution: MCPExecutionOptions | None = None
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class TTSCloneInput(MCPToolInput):
|
|
292
|
+
audio: str
|
|
293
|
+
text: str | None = None
|
|
294
|
+
text_file: str | None = None
|
|
295
|
+
language: str = "Auto"
|
|
296
|
+
seed: int = -1
|
|
297
|
+
execution: MCPExecutionOptions | None = None
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class TTSVoiceInput(MCPToolInput):
|
|
301
|
+
text: str | None = None
|
|
302
|
+
text_file: str | None = None
|
|
303
|
+
character: str = "Female"
|
|
304
|
+
style: str = "Auto"
|
|
305
|
+
language: str = "Auto"
|
|
306
|
+
prompt: str = ""
|
|
307
|
+
seed: int = -1
|
|
308
|
+
execution: MCPExecutionOptions | None = None
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class TaskStatusInput(MCPToolInput):
|
|
312
|
+
task_id: str
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class TaskWaitInput(MCPToolInput):
|
|
316
|
+
task_id: str
|
|
317
|
+
execution: MCPExecutionOptions | None = None
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class KeywordsInput(MCPToolInput):
|
|
321
|
+
image: str
|
|
322
|
+
lang: str = "en"
|
|
323
|
+
num_keywords: int | None = None
|
|
324
|
+
colors: bool = False
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class ImageInput(MCPToolInput):
|
|
328
|
+
image: str
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class VideoInput(MCPToolInput):
|
|
332
|
+
video: str
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class RunInput(MCPToolInput):
|
|
336
|
+
endpoint: str
|
|
337
|
+
payload: dict[str, Any]
|
|
338
|
+
method: str | None = None
|
|
339
|
+
dry_run: bool = False
|
|
340
|
+
help_schema: bool = False
|
|
341
|
+
execution: MCPExecutionOptions | None = None
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
class NoInput(MCPToolInput):
|
|
345
|
+
pass
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@dataclass(frozen=True)
|
|
349
|
+
class ToolSpec:
|
|
350
|
+
"""One MCP contract and its transport-independent application handler."""
|
|
351
|
+
|
|
352
|
+
name: str
|
|
353
|
+
description: str
|
|
354
|
+
input_model: type[MCPToolInput]
|
|
355
|
+
annotations: ToolAnnotations
|
|
356
|
+
handler: Callable[[Any], Any]
|
|
357
|
+
|
|
358
|
+
def describe(self) -> Tool:
|
|
359
|
+
return Tool(
|
|
360
|
+
name=self.name,
|
|
361
|
+
description=self.description,
|
|
362
|
+
input_schema=self.input_model.model_json_schema(by_alias=True),
|
|
363
|
+
annotations=self.annotations,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class ToolRegistry:
|
|
368
|
+
"""List, validate, and invoke MCP tools through one explicit boundary."""
|
|
369
|
+
|
|
370
|
+
def __init__(self) -> None:
|
|
371
|
+
self._tools: dict[str, ToolSpec] = {}
|
|
372
|
+
|
|
373
|
+
def tool(
|
|
374
|
+
self,
|
|
375
|
+
input_model: type[MCPToolInput],
|
|
376
|
+
*,
|
|
377
|
+
annotations: ToolAnnotations,
|
|
378
|
+
) -> Callable[[Callable[[Any], Any]], Callable[[Any], Any]]:
|
|
379
|
+
def register(handler: Callable[[Any], Any]) -> Callable[[Any], Any]:
|
|
380
|
+
self._tools[handler.__name__] = ToolSpec(
|
|
381
|
+
name=handler.__name__,
|
|
382
|
+
description=handler.__doc__ or "",
|
|
383
|
+
input_model=input_model,
|
|
384
|
+
annotations=annotations,
|
|
385
|
+
handler=handler,
|
|
386
|
+
)
|
|
387
|
+
return handler
|
|
388
|
+
|
|
389
|
+
return register
|
|
390
|
+
|
|
391
|
+
def list_tools(self) -> list[Tool]:
|
|
392
|
+
return [spec.describe() for spec in self._tools.values()]
|
|
393
|
+
|
|
394
|
+
async def call_tool(self, params: CallToolRequestParams) -> CallToolResult:
|
|
395
|
+
spec = self._tools.get(params.name)
|
|
396
|
+
if spec is None:
|
|
397
|
+
return self._validation_error()
|
|
398
|
+
try:
|
|
399
|
+
arguments = spec.input_model.model_validate(params.arguments or {})
|
|
400
|
+
except ValidationError as exc:
|
|
401
|
+
return self._validation_error(exc)
|
|
402
|
+
cancel_event = Event()
|
|
403
|
+
|
|
404
|
+
def invoke() -> CallToolResult:
|
|
405
|
+
context_token = _CURRENT_CANCEL_EVENT.set(cancel_event)
|
|
406
|
+
try:
|
|
407
|
+
if cancel_event.is_set():
|
|
408
|
+
raise OperationCancelled
|
|
409
|
+
result = _invoke(lambda: spec.handler(arguments))
|
|
410
|
+
if cancel_event.is_set():
|
|
411
|
+
raise OperationCancelled
|
|
412
|
+
return result
|
|
413
|
+
finally:
|
|
414
|
+
_CURRENT_CANCEL_EVENT.reset(context_token)
|
|
415
|
+
|
|
416
|
+
try:
|
|
417
|
+
return await anyio.to_thread.run_sync(
|
|
418
|
+
invoke,
|
|
419
|
+
abandon_on_cancel=True,
|
|
420
|
+
)
|
|
421
|
+
finally:
|
|
422
|
+
cancel_event.set()
|
|
423
|
+
|
|
424
|
+
@staticmethod
|
|
425
|
+
def _validation_error(exc: ValidationError | None = None) -> CallToolResult:
|
|
426
|
+
message = (
|
|
427
|
+
format_validation_errors(exc.errors())
|
|
428
|
+
if exc is not None
|
|
429
|
+
else "Invalid MCP tool arguments"
|
|
430
|
+
)
|
|
431
|
+
return _tool_result(
|
|
432
|
+
ValidationCLIError(message).to_payload(),
|
|
433
|
+
is_error=True,
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _configured_services_factory(
|
|
438
|
+
*,
|
|
439
|
+
profile: str | None = None,
|
|
440
|
+
dev: bool = False,
|
|
441
|
+
base_url: str | None = None,
|
|
442
|
+
) -> tuple[ServiceFactory, bool]:
|
|
443
|
+
settings = resolved_settings(profile=profile, dev=dev, base_url=base_url)
|
|
444
|
+
|
|
445
|
+
client = APIClient(
|
|
446
|
+
base_url=settings["base_url"],
|
|
447
|
+
client_id=settings["client_id"],
|
|
448
|
+
client_secret=settings["client_secret"],
|
|
449
|
+
)
|
|
450
|
+
configured_services = ApplicationServices.with_client(client)
|
|
451
|
+
|
|
452
|
+
def services() -> ApplicationServices:
|
|
453
|
+
return configured_services
|
|
454
|
+
|
|
455
|
+
return services, settings["client_id"] is not None
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def create_mcp_server(
|
|
459
|
+
services_factory: ServiceFactory | None = None,
|
|
460
|
+
*,
|
|
461
|
+
client_id_present: bool | None = None,
|
|
462
|
+
) -> Server[Any]:
|
|
463
|
+
"""Create an Everypixel MCP server with injectable application services."""
|
|
464
|
+
|
|
465
|
+
if services_factory is None:
|
|
466
|
+
services_factory, configured_client_id = _configured_services_factory()
|
|
467
|
+
if client_id_present is None:
|
|
468
|
+
client_id_present = configured_client_id
|
|
469
|
+
client_id_present = bool(client_id_present)
|
|
470
|
+
|
|
471
|
+
registry = ToolRegistry()
|
|
472
|
+
|
|
473
|
+
@registry.tool(ImageGenerateInput, annotations=LOCAL_WRITE)
|
|
474
|
+
def image_generate(arguments: ImageGenerateInput) -> Any:
|
|
475
|
+
"""Generate an image from a prompt, optionally using a source image."""
|
|
476
|
+
|
|
477
|
+
return services_factory().execute_image_generate(
|
|
478
|
+
prompt=arguments.prompt,
|
|
479
|
+
model=arguments.model,
|
|
480
|
+
image_size=arguments.image_size,
|
|
481
|
+
style=arguments.style,
|
|
482
|
+
image=arguments.image,
|
|
483
|
+
resolution=arguments.resolution,
|
|
484
|
+
seed=arguments.seed,
|
|
485
|
+
callback_url=arguments.callback_url,
|
|
486
|
+
execution=_execution(arguments.execution),
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
@registry.tool(ImageEditInput, annotations=LOCAL_WRITE)
|
|
490
|
+
def image_edit(arguments: ImageEditInput) -> Any:
|
|
491
|
+
"""Edit one or more images using a text instruction."""
|
|
492
|
+
|
|
493
|
+
return services_factory().execute_image_edit(
|
|
494
|
+
prompt=arguments.prompt,
|
|
495
|
+
images=arguments.images,
|
|
496
|
+
model=arguments.model,
|
|
497
|
+
image_size=arguments.image_size,
|
|
498
|
+
resolution=arguments.resolution,
|
|
499
|
+
seed=arguments.seed,
|
|
500
|
+
callback_url=arguments.callback_url,
|
|
501
|
+
execution=_execution(arguments.execution),
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
@registry.tool(ImageUpscaleInput, annotations=LOCAL_WRITE)
|
|
505
|
+
def image_upscale(arguments: ImageUpscaleInput) -> Any:
|
|
506
|
+
"""Upscale an image supplied directly or by a completed task ID."""
|
|
507
|
+
|
|
508
|
+
return services_factory().execute_image_upscale(
|
|
509
|
+
image=arguments.image,
|
|
510
|
+
task_id=arguments.task_id,
|
|
511
|
+
model=arguments.model,
|
|
512
|
+
callback_url=arguments.callback_url,
|
|
513
|
+
execution=_execution(arguments.execution),
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
@registry.tool(ImageAnglesInput, annotations=LOCAL_WRITE)
|
|
517
|
+
def image_angles(arguments: ImageAnglesInput) -> Any:
|
|
518
|
+
"""Render an image from a different camera angle and distance."""
|
|
519
|
+
|
|
520
|
+
return services_factory().execute_image_angles(
|
|
521
|
+
image=arguments.image,
|
|
522
|
+
azimuth=arguments.azimuth,
|
|
523
|
+
elevation=arguments.elevation,
|
|
524
|
+
distance=arguments.distance,
|
|
525
|
+
prompt=arguments.prompt,
|
|
526
|
+
execution=_execution(arguments.execution),
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
@registry.tool(ImageColorsInput, annotations=LOCAL_WRITE)
|
|
530
|
+
def image_colors(arguments: ImageColorsInput) -> Any:
|
|
531
|
+
"""Transfer colors from a reference image to a source image."""
|
|
532
|
+
|
|
533
|
+
return services_factory().execute_image_colors(
|
|
534
|
+
image=arguments.image,
|
|
535
|
+
reference=arguments.reference,
|
|
536
|
+
execution=_execution(arguments.execution),
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
@registry.tool(VideoGenerateInput, annotations=LOCAL_WRITE)
|
|
540
|
+
def video_generate(arguments: VideoGenerateInput) -> Any:
|
|
541
|
+
"""Generate video from text, an image, or first and last frame images."""
|
|
542
|
+
|
|
543
|
+
return services_factory().execute_video_generate(
|
|
544
|
+
prompt=arguments.prompt,
|
|
545
|
+
model=arguments.model,
|
|
546
|
+
duration=arguments.duration,
|
|
547
|
+
resolution=arguments.resolution,
|
|
548
|
+
aspect_ratio=arguments.aspect_ratio,
|
|
549
|
+
lora_high_url=arguments.lora_high_url,
|
|
550
|
+
lora_low_url=arguments.lora_low_url,
|
|
551
|
+
reference_images=arguments.reference_images,
|
|
552
|
+
reference_videos=arguments.reference_videos,
|
|
553
|
+
image=arguments.image,
|
|
554
|
+
last_image=arguments.last_image,
|
|
555
|
+
seed=arguments.seed,
|
|
556
|
+
generate_audio=arguments.generate_audio,
|
|
557
|
+
callback_url=arguments.callback_url,
|
|
558
|
+
execution=_execution(arguments.execution),
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
@registry.tool(VideoEditInput, annotations=LOCAL_WRITE)
|
|
562
|
+
def video_edit(arguments: VideoEditInput) -> Any:
|
|
563
|
+
"""Edit a video with model-specific image, video, audio, or keyframe inputs."""
|
|
564
|
+
|
|
565
|
+
encoded_keyframes = [
|
|
566
|
+
json.dumps(keyframe, ensure_ascii=False)
|
|
567
|
+
for keyframe in arguments.keyframes or []
|
|
568
|
+
]
|
|
569
|
+
return services_factory().execute_video_edit(
|
|
570
|
+
prompt=arguments.prompt,
|
|
571
|
+
model=arguments.model,
|
|
572
|
+
images=arguments.images or [],
|
|
573
|
+
video=arguments.video,
|
|
574
|
+
audio=arguments.audio,
|
|
575
|
+
duration=arguments.duration,
|
|
576
|
+
resolution=arguments.resolution,
|
|
577
|
+
aspect_ratio=arguments.aspect_ratio,
|
|
578
|
+
seed=arguments.seed,
|
|
579
|
+
generate_audio=arguments.generate_audio,
|
|
580
|
+
callback_url=arguments.callback_url,
|
|
581
|
+
keyframes=encoded_keyframes,
|
|
582
|
+
public_figure_threshold=arguments.public_figure_threshold,
|
|
583
|
+
execution=_execution(arguments.execution),
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
@registry.tool(VideoUpscaleInput, annotations=LOCAL_WRITE)
|
|
587
|
+
def video_upscale(arguments: VideoUpscaleInput) -> Any:
|
|
588
|
+
"""Upscale a video supplied directly or by a completed task ID."""
|
|
589
|
+
|
|
590
|
+
return services_factory().execute_video_upscale(
|
|
591
|
+
video=arguments.video,
|
|
592
|
+
task_id=arguments.task_id,
|
|
593
|
+
resolution=arguments.resolution,
|
|
594
|
+
execution=_execution(arguments.execution),
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
@registry.tool(LipsyncVideoInput, annotations=LOCAL_WRITE)
|
|
598
|
+
def lipsync_video(arguments: LipsyncVideoInput) -> Any:
|
|
599
|
+
"""Synchronize lips in a video to an audio track."""
|
|
600
|
+
|
|
601
|
+
return services_factory().execute_lipsync_video(
|
|
602
|
+
video=arguments.video,
|
|
603
|
+
audio=arguments.audio,
|
|
604
|
+
resolution=arguments.resolution,
|
|
605
|
+
prompt=arguments.prompt,
|
|
606
|
+
seed=arguments.seed,
|
|
607
|
+
callback_url=arguments.callback_url,
|
|
608
|
+
execution=_execution(arguments.execution),
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
@registry.tool(LipsyncImageInput, annotations=LOCAL_WRITE)
|
|
612
|
+
def lipsync_image(arguments: LipsyncImageInput) -> Any:
|
|
613
|
+
"""Create a lipsync video from a still image and audio track."""
|
|
614
|
+
|
|
615
|
+
return services_factory().execute_lipsync_image(
|
|
616
|
+
image=arguments.image,
|
|
617
|
+
audio=arguments.audio,
|
|
618
|
+
model=arguments.model,
|
|
619
|
+
resolution=arguments.resolution,
|
|
620
|
+
prompt=arguments.prompt,
|
|
621
|
+
seed=arguments.seed,
|
|
622
|
+
callback_url=arguments.callback_url,
|
|
623
|
+
execution=_execution(arguments.execution),
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
@registry.tool(AudioTranscribeInput, annotations=LOCAL_WRITE)
|
|
627
|
+
def audio_transcribe(arguments: AudioTranscribeInput) -> Any:
|
|
628
|
+
"""Transcribe speech from an audio URL, data URI, or local file."""
|
|
629
|
+
|
|
630
|
+
return services_factory().execute_audio_transcribe(
|
|
631
|
+
audio=arguments.audio,
|
|
632
|
+
language=arguments.language,
|
|
633
|
+
hints=arguments.hints,
|
|
634
|
+
denoise=arguments.denoise,
|
|
635
|
+
execution=_execution(arguments.execution),
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
@registry.tool(TTSCreateInput, annotations=LOCAL_WRITE)
|
|
639
|
+
def tts_create(arguments: TTSCreateInput) -> Any:
|
|
640
|
+
"""Create speech from text using a selected speaker."""
|
|
641
|
+
|
|
642
|
+
return services_factory().execute_tts_create(
|
|
643
|
+
text=arguments.text,
|
|
644
|
+
text_file=Path(arguments.text_file) if arguments.text_file else None,
|
|
645
|
+
speaker=arguments.speaker,
|
|
646
|
+
style=arguments.style,
|
|
647
|
+
language=arguments.language,
|
|
648
|
+
prompt=arguments.prompt,
|
|
649
|
+
seed=arguments.seed,
|
|
650
|
+
execution=_execution(arguments.execution),
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
@registry.tool(TTSCloneInput, annotations=LOCAL_WRITE)
|
|
654
|
+
def tts_clone(arguments: TTSCloneInput) -> Any:
|
|
655
|
+
"""Create speech using a cloned voice sample."""
|
|
656
|
+
|
|
657
|
+
return services_factory().execute_tts_clone(
|
|
658
|
+
audio=arguments.audio,
|
|
659
|
+
text=arguments.text,
|
|
660
|
+
text_file=Path(arguments.text_file) if arguments.text_file else None,
|
|
661
|
+
language=arguments.language,
|
|
662
|
+
seed=arguments.seed,
|
|
663
|
+
execution=_execution(arguments.execution),
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
@registry.tool(TTSVoiceInput, annotations=LOCAL_WRITE)
|
|
667
|
+
def tts_voice(arguments: TTSVoiceInput) -> Any:
|
|
668
|
+
"""Create speech from text using a character voice."""
|
|
669
|
+
|
|
670
|
+
return services_factory().execute_tts_voice(
|
|
671
|
+
text=arguments.text,
|
|
672
|
+
text_file=Path(arguments.text_file) if arguments.text_file else None,
|
|
673
|
+
character=arguments.character,
|
|
674
|
+
style=arguments.style,
|
|
675
|
+
language=arguments.language,
|
|
676
|
+
prompt=arguments.prompt,
|
|
677
|
+
seed=arguments.seed,
|
|
678
|
+
execution=_execution(arguments.execution),
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
@registry.tool(TaskStatusInput, annotations=READ_ONLY)
|
|
682
|
+
def task_status(arguments: TaskStatusInput) -> Any:
|
|
683
|
+
"""Fetch the current state of an asynchronous Everypixel task once."""
|
|
684
|
+
|
|
685
|
+
return services_factory().get_task_status(
|
|
686
|
+
task_id=arguments.task_id,
|
|
687
|
+
execution=ExecutionOptions(
|
|
688
|
+
cancel_event=_CURRENT_CANCEL_EVENT.get(),
|
|
689
|
+
),
|
|
690
|
+
)
|
|
691
|
+
|
|
692
|
+
@registry.tool(TaskWaitInput, annotations=LOCAL_WRITE)
|
|
693
|
+
def task_wait(arguments: TaskWaitInput) -> Any:
|
|
694
|
+
"""Poll a task until SUCCESS and optionally download its result files."""
|
|
695
|
+
|
|
696
|
+
return services_factory().wait_for_task(
|
|
697
|
+
task_id=arguments.task_id,
|
|
698
|
+
execution=_execution(
|
|
699
|
+
arguments.execution,
|
|
700
|
+
force_wait=True,
|
|
701
|
+
),
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
@registry.tool(KeywordsInput, annotations=READ_ONLY)
|
|
705
|
+
def keywords(arguments: KeywordsInput) -> Any:
|
|
706
|
+
"""Extract semantic keywords and optional colors from an image."""
|
|
707
|
+
|
|
708
|
+
return services_factory().execute_keywords(
|
|
709
|
+
image=arguments.image,
|
|
710
|
+
lang=arguments.lang,
|
|
711
|
+
num_keywords=arguments.num_keywords,
|
|
712
|
+
colors=arguments.colors,
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
@registry.tool(ImageInput, annotations=READ_ONLY)
|
|
716
|
+
def quality(arguments: ImageInput) -> Any:
|
|
717
|
+
"""Score the technical quality of an image."""
|
|
718
|
+
|
|
719
|
+
return services_factory().execute_quality(image=arguments.image)
|
|
720
|
+
|
|
721
|
+
@registry.tool(ImageInput, annotations=READ_ONLY)
|
|
722
|
+
def quality_ugc(arguments: ImageInput) -> Any:
|
|
723
|
+
"""Score the user-generated-content quality of an image."""
|
|
724
|
+
|
|
725
|
+
return services_factory().execute_quality_ugc(image=arguments.image)
|
|
726
|
+
|
|
727
|
+
@registry.tool(ImageInput, annotations=READ_ONLY)
|
|
728
|
+
def faces(arguments: ImageInput) -> Any:
|
|
729
|
+
"""Detect faces and facial attributes in an image."""
|
|
730
|
+
|
|
731
|
+
return services_factory().execute_faces(image=arguments.image)
|
|
732
|
+
|
|
733
|
+
@registry.tool(ImageInput, annotations=READ_ONLY)
|
|
734
|
+
def captioning(arguments: ImageInput) -> Any:
|
|
735
|
+
"""Generate a natural-language caption for an image."""
|
|
736
|
+
|
|
737
|
+
return services_factory().execute_captioning(image=arguments.image)
|
|
738
|
+
|
|
739
|
+
@registry.tool(VideoInput, annotations=READ_ONLY)
|
|
740
|
+
def video_keywords(arguments: VideoInput) -> Any:
|
|
741
|
+
"""Extract semantic keywords from a video."""
|
|
742
|
+
|
|
743
|
+
return services_factory().execute_video_keywords(video=arguments.video)
|
|
744
|
+
|
|
745
|
+
@registry.tool(RunInput, annotations=LOCAL_WRITE)
|
|
746
|
+
def run(arguments: RunInput) -> Any:
|
|
747
|
+
"""Run any Everypixel operation by OpenAPI name or direct /v1 path."""
|
|
748
|
+
|
|
749
|
+
return services_factory().execute_generic(
|
|
750
|
+
endpoint=arguments.endpoint,
|
|
751
|
+
payload=arguments.payload,
|
|
752
|
+
method=arguments.method,
|
|
753
|
+
execution=_execution(arguments.execution),
|
|
754
|
+
dry_run=arguments.dry_run,
|
|
755
|
+
help_schema=arguments.help_schema,
|
|
756
|
+
client_id_present=client_id_present,
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
@registry.tool(NoInput, annotations=READ_ONLY)
|
|
760
|
+
def auth_check(_arguments: NoInput) -> Any:
|
|
761
|
+
"""Verify that the configured Everypixel credentials are accepted."""
|
|
762
|
+
|
|
763
|
+
return services_factory().check_auth()
|
|
764
|
+
|
|
765
|
+
@registry.tool(NoInput, annotations=LOCAL_WRITE)
|
|
766
|
+
def openapi(_arguments: NoInput) -> Any:
|
|
767
|
+
"""Return the selected live, cached, or bundled Everypixel OpenAPI schema."""
|
|
768
|
+
|
|
769
|
+
return services_factory().openapi()
|
|
770
|
+
|
|
771
|
+
@registry.tool(NoInput, annotations=LOCAL_WRITE)
|
|
772
|
+
def openapi_refresh(_arguments: NoInput) -> Any:
|
|
773
|
+
"""Refresh the local Everypixel OpenAPI cache from the live API."""
|
|
774
|
+
|
|
775
|
+
result, path = services_factory().refresh_openapi()
|
|
776
|
+
return {
|
|
777
|
+
"schema": serialize_operation_result(result),
|
|
778
|
+
"cache_path": str(path),
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
async def list_tools(_ctx: Any, _params: Any) -> ListToolsResult:
|
|
782
|
+
return ListToolsResult(tools=registry.list_tools())
|
|
783
|
+
|
|
784
|
+
async def call_tool(_ctx: Any, params: CallToolRequestParams) -> CallToolResult:
|
|
785
|
+
return await registry.call_tool(params)
|
|
786
|
+
|
|
787
|
+
return Server(
|
|
788
|
+
"everypixel",
|
|
789
|
+
title="Everypixel API",
|
|
790
|
+
description="Generate and analyze media through the Everypixel API.",
|
|
791
|
+
instructions=(
|
|
792
|
+
"Generation tools wait for the completed result by default. Pass "
|
|
793
|
+
"execution.wait=false to return the created task immediately, then "
|
|
794
|
+
"use the task status or task wait tool. "
|
|
795
|
+
"Media arguments accept HTTP(S) URLs, data URIs, and local file paths. "
|
|
796
|
+
"Use English for natural-language prompts sent to generation tools; "
|
|
797
|
+
"translate prompts when needed while preserving the user's intent and "
|
|
798
|
+
"any text that must appear verbatim in the generated result."
|
|
799
|
+
),
|
|
800
|
+
version=__version__,
|
|
801
|
+
on_list_tools=list_tools,
|
|
802
|
+
on_call_tool=call_tool,
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
async def _serve_stdio(server: Server[Any]) -> None:
|
|
807
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
808
|
+
await server.run(
|
|
809
|
+
read_stream,
|
|
810
|
+
write_stream,
|
|
811
|
+
server.create_initialization_options(),
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
def run_mcp_server(server: Server[Any]) -> None:
|
|
816
|
+
"""Run an Everypixel MCP server using the official stdio transport."""
|
|
817
|
+
|
|
818
|
+
anyio.run(_serve_stdio, server)
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
def main() -> None:
|
|
822
|
+
"""Run the configured server over stdio without importing the CLI layer."""
|
|
823
|
+
|
|
824
|
+
run_mcp_server(create_mcp_server())
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
if __name__ == "__main__":
|
|
828
|
+
main()
|