Files

111 lines
4.7 KiB
Python

"""Live (macOS) — tool schemas and handlers.
Extracted from server.py; see Engine/docs/03_SERVER_TOOLS.md for the tool catalog.
"""
from __future__ import annotations
from pathlib import Path
from typing import Sequence
from mcp.types import TextContent, Tool
from server_tools._shared import (
_text_result,
_validate_filepath,
_validate_output_path,
generate_output_path,
)
TOOLS = [
Tool(
name="push_to_fcp",
description="LIVE: send an FCPXML file into the running Final Cut Pro with zero clicks (official Open Document Apple event). Creates/targets a library via import-options. Launches FCP if needed. macOS-only; first use triggers an Automation permission prompt. For true zero-click, pass a library_location ending in .fcpbundle (a new path is auto-created); omitting it makes FCP show a modal library picker.",
inputSchema={
"type": "object",
"properties": {
"filepath": {"type": "string", "description": "Path to FCPXML file or .fcpxmld bundle to import"},
"library_location": {"type": "string", "description": "Target .fcpbundle library path (auto-created if it doesn't exist; the extension is normalized to .fcpbundle). Omit to import into the active library, but note FCP then shows a modal 'Open Library' picker that blocks until answered"},
"suppress_warnings": {"type": "boolean", "description": "Suppress non-fatal import warning dialogs", "default": True},
"copy_assets": {"type": "boolean", "description": "Copy media into the library (true) or link in place (false). Omit for FCP default"},
},
"required": ["filepath"]
}
),
Tool(
name="list_fcp_libraries",
description="LIVE: enumerate the running Final Cut Pro's open libraries, events, and projects via Apple's read-only scripting dictionary. Refuses to launch FCP unless allow_launch is true. macOS-only.",
inputSchema={
"type": "object",
"properties": {
"allow_launch": {"type": "boolean", "description": "Launch FCP if it isn't running", "default": False},
},
}
),
]
async def handle_push_to_fcp(arguments: dict) -> Sequence[TextContent]:
from fcpxml.live import push_to_fcp
filepath = _validate_filepath(arguments["filepath"], ('.fcpxml', '.fcpxmld'))
# Flat files get an options-injected sibling copy (never touch the
# original); the copy path goes through the same write sandbox as
# every other derived output.
import_copy = None
if Path(filepath).suffix.lower() == '.fcpxml':
anchor = str(Path(filepath).resolve().parent)
import_copy = _validate_output_path(
generate_output_path(filepath, "_import"), anchor_dir=anchor
)
result = push_to_fcp(
filepath,
library_location=arguments.get("library_location"),
suppress_warnings=arguments.get("suppress_warnings", True),
copy_assets=arguments.get("copy_assets"),
import_copy_path=import_copy,
)
lines = [
f"Sent to Final Cut Pro: {result['sent']}",
f"FCP {'was launched' if result['launched_fcp'] else 'was already running'} — "
f"import happens in-app (libraries/events are created or merged per import-options).",
]
if arguments.get("library_location"):
lines.append(f"Target library: {arguments['library_location']}")
lines.append(
"Note: Apple offers no programmatic export — to round-trip edits "
"back, use File > Export XML in FCP."
)
return _text_result("\n".join(lines))
async def handle_list_fcp_libraries(arguments: dict) -> Sequence[TextContent]:
from fcpxml.live import list_fcp_libraries
try:
libraries = list_fcp_libraries(
allow_launch=arguments.get("allow_launch", False)
)
except RuntimeError as exc:
return _text_result(str(exc))
if not libraries:
return _text_result("Final Cut Pro is running but reports no open libraries.")
lines = [f"Open libraries in Final Cut Pro ({len(libraries)}):", ""]
for lib in libraries:
lines.append(f"📚 {lib['name']}")
for event in lib["events"]:
lines.append(f" └─ {event['name']}")
for proj in event["projects"]:
lines.append(f" • {proj}")
return _text_result("\n".join(lines))
HANDLERS = {
"push_to_fcp": handle_push_to_fcp,
"list_fcp_libraries": handle_list_fcp_libraries,
}