dcc-mcp-openscreen 0.1.1__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.
- dcc_mcp_openscreen/__init__.py +2 -0
- dcc_mcp_openscreen/server.py +20 -0
- dcc_mcp_openscreen/skills/openscreen/scripts/_client.py +47 -0
- dcc_mcp_openscreen/skills/openscreen/scripts/captions.py +4 -0
- dcc_mcp_openscreen/skills/openscreen/scripts/export.py +7 -0
- dcc_mcp_openscreen/skills/openscreen/scripts/info.py +4 -0
- dcc_mcp_openscreen/skills/openscreen/scripts/pack.py +4 -0
- dcc_mcp_openscreen/skills/openscreen/scripts/record.py +11 -0
- dcc_mcp_openscreen/skills/openscreen/scripts/sources.py +4 -0
- dcc_mcp_openscreen/skills/openscreen/tools.yaml +67 -0
- dcc_mcp_openscreen-0.1.1.dist-info/METADATA +28 -0
- dcc_mcp_openscreen-0.1.1.dist-info/RECORD +14 -0
- dcc_mcp_openscreen-0.1.1.dist-info/WHEEL +4 -0
- dcc_mcp_openscreen-0.1.1.dist-info/entry_points.txt +5 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Standalone OpenScreen MCP adapter."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import argparse, os, signal, threading
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from dcc_mcp_core import DccServerOptions
|
|
6
|
+
from dcc_mcp_core.server_base import DccServerBase
|
|
7
|
+
class OpenScreenMcpServer(DccServerBase):
|
|
8
|
+
def __init__(self, *, port: int | None = None):
|
|
9
|
+
options = DccServerOptions.from_env("openscreen", Path(__file__).parent / "skills", port=port, server_name="dcc-mcp-openscreen", instance_type="standalone", adapter_version="0.1.0", standalone_main_thread=False)
|
|
10
|
+
super().__init__(options=options)
|
|
11
|
+
def main(argv=None):
|
|
12
|
+
p=argparse.ArgumentParser(); p.add_argument("--mcp-port", type=int); a=p.parse_args(argv)
|
|
13
|
+
server=OpenScreenMcpServer(port=a.mcp_port); server.register_builtin_actions(); server.start()
|
|
14
|
+
stopped=threading.Event(); signal.signal(signal.SIGINT, lambda *_: stopped.set())
|
|
15
|
+
if hasattr(signal,"SIGTERM"): signal.signal(signal.SIGTERM, lambda *_: stopped.set())
|
|
16
|
+
try: stopped.wait()
|
|
17
|
+
finally: server.stop()
|
|
18
|
+
|
|
19
|
+
if __name__ == '__main__':
|
|
20
|
+
main()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import json, os, subprocess, time
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
def executable() -> str:
|
|
6
|
+
return os.getenv("DCC_MCP_OPENSCREEN_PATH", "openscreen")
|
|
7
|
+
|
|
8
|
+
def _events(stdout: str) -> list[dict]:
|
|
9
|
+
out=[]
|
|
10
|
+
for line in stdout.splitlines():
|
|
11
|
+
try: out.append(json.loads(line))
|
|
12
|
+
except json.JSONDecodeError: pass
|
|
13
|
+
return out
|
|
14
|
+
|
|
15
|
+
def run(args: list[str], *, timeout: float = 900.0) -> dict:
|
|
16
|
+
try:
|
|
17
|
+
proc=subprocess.run([executable(), *args, "--json"], capture_output=True, text=True, timeout=timeout, check=False)
|
|
18
|
+
except subprocess.TimeoutExpired as exc:
|
|
19
|
+
raise RuntimeError(f"OpenScreen CLI timed out after {timeout:.0f}s; verify permissions and headless build") from exc
|
|
20
|
+
return _finish(proc.returncode, proc.stdout, proc.stderr)
|
|
21
|
+
|
|
22
|
+
def record(args: list[str], *, duration: int, timeout: float) -> dict:
|
|
23
|
+
proc=subprocess.Popen([executable(), "record", *args, "--json"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
24
|
+
try:
|
|
25
|
+
time.sleep(duration)
|
|
26
|
+
if proc.poll() is None and proc.stdin:
|
|
27
|
+
proc.stdin.write("stop\n"); proc.stdin.flush()
|
|
28
|
+
stdout, stderr=proc.communicate(timeout=max(10.0, timeout-duration))
|
|
29
|
+
except subprocess.TimeoutExpired as exc:
|
|
30
|
+
if os.name == "nt":\n subprocess.run(["taskkill", "/PID", str(proc.pid), "/T", "/F"], capture_output=True, check=False)\n else:\n proc.kill()\n proc.communicate()
|
|
31
|
+
raise RuntimeError("OpenScreen recording did not stop after a cooperative stop request") from exc
|
|
32
|
+
return _finish(proc.returncode, stdout, stderr)
|
|
33
|
+
|
|
34
|
+
def _finish(code: int, stdout: str, stderr: str) -> dict:
|
|
35
|
+
events=_events(stdout)
|
|
36
|
+
done=next((e for e in reversed(events) if e.get("event")=="done"), None)
|
|
37
|
+
if code != 0 or not done or done.get("success") is False:
|
|
38
|
+
detail=(stderr or "OpenScreen command failed").strip()
|
|
39
|
+
if not events and not detail: detail="OpenScreen produced no JSON events; verify a headless-capable build"
|
|
40
|
+
raise RuntimeError(detail[-2000:])
|
|
41
|
+
return done
|
|
42
|
+
|
|
43
|
+
def path_arg(value: str) -> str:
|
|
44
|
+
path=Path(value).expanduser().resolve()
|
|
45
|
+
if len(str(path))>4096: raise ValueError("path too long")
|
|
46
|
+
return str(path)
|
|
47
|
+
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
from dcc_mcp_core.skill import skill_entry, skill_success
|
|
2
|
+
from ._client import run, path_arg
|
|
3
|
+
@skill_entry
|
|
4
|
+
def main(project, min_words=2, max_words=7, **_): return skill_success("OpenScreen captions generated.", **run(["captions", path_arg(project), "--min-words", str(min_words), "--max-words", str(max_words)], timeout=1800))
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from dcc_mcp_core.skill import skill_entry, skill_success
|
|
2
|
+
from ._client import run, path_arg
|
|
3
|
+
@skill_entry
|
|
4
|
+
def main(project, output=None, quality="source", **_):
|
|
5
|
+
args=["export",path_arg(project),"--quality",quality]
|
|
6
|
+
if output: args += ["--out",path_arg(output)]
|
|
7
|
+
return skill_success("OpenScreen export completed.", **run(args, timeout=1800))
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from dcc_mcp_core.skill import skill_entry, skill_success
|
|
2
|
+
from ._client import record, path_arg
|
|
3
|
+
@skill_entry
|
|
4
|
+
def main(duration=30, window=None, display=0, project=None, mic=False, system_audio=False, **_):
|
|
5
|
+
if not isinstance(duration,int) or duration<1 or duration>3600: raise ValueError("duration must be 1..3600 seconds")
|
|
6
|
+
args=["--display",str(display)]
|
|
7
|
+
if window: args += ["--window",str(window)]
|
|
8
|
+
if project: args += ["--project",path_arg(project)]
|
|
9
|
+
if mic: args += ["--mic"]
|
|
10
|
+
if system_audio: args += ["--system-audio"]
|
|
11
|
+
return skill_success("OpenScreen recording completed.", **record(args,duration=duration,timeout=duration+120))
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
skill: openscreen
|
|
2
|
+
version: "0.1.0"
|
|
3
|
+
description: Record and export OpenScreen projects through its typed headless CLI.
|
|
4
|
+
tools:
|
|
5
|
+
- name: sources
|
|
6
|
+
description: List available displays, windows, and microphones.
|
|
7
|
+
source_file: scripts/sources.py
|
|
8
|
+
input_schema: {type: object, additionalProperties: false}
|
|
9
|
+
execution: sync
|
|
10
|
+
affinity: any
|
|
11
|
+
annotations: {read_only_hint: true, destructive_hint: false, idempotent_hint: true, open_world_hint: false}
|
|
12
|
+
- name: record
|
|
13
|
+
description: Record a bounded display or window capture to an optional OpenScreen project.
|
|
14
|
+
source_file: scripts/record.py
|
|
15
|
+
input_schema:
|
|
16
|
+
type: object
|
|
17
|
+
additionalProperties: false
|
|
18
|
+
properties:
|
|
19
|
+
duration: {type: integer, minimum: 1, maximum: 3600, default: 30}
|
|
20
|
+
window: {type: string, minLength: 1, maxLength: 256}
|
|
21
|
+
display: {type: integer, minimum: 0, maximum: 32, default: 0}
|
|
22
|
+
project: {type: string, maxLength: 4096}
|
|
23
|
+
mic: {type: boolean, default: false}
|
|
24
|
+
system_audio: {type: boolean, default: false}
|
|
25
|
+
execution: sync
|
|
26
|
+
affinity: any
|
|
27
|
+
timeout_hint_secs: 3720
|
|
28
|
+
annotations: {read_only_hint: false, destructive_hint: true, idempotent_hint: false, open_world_hint: false}
|
|
29
|
+
- name: export
|
|
30
|
+
description: Render an OpenScreen project to MP4 or GIF.
|
|
31
|
+
source_file: scripts/export.py
|
|
32
|
+
input_schema:
|
|
33
|
+
type: object
|
|
34
|
+
required: [project]
|
|
35
|
+
additionalProperties: false
|
|
36
|
+
properties:
|
|
37
|
+
project: {type: string, minLength: 1, maxLength: 4096}
|
|
38
|
+
output: {type: string, maxLength: 4096}
|
|
39
|
+
quality: {enum: [medium, good, source], default: source}
|
|
40
|
+
execution: sync
|
|
41
|
+
affinity: any
|
|
42
|
+
timeout_hint_secs: 1800
|
|
43
|
+
annotations: {read_only_hint: false, destructive_hint: true, idempotent_hint: true, open_world_hint: false}
|
|
44
|
+
- name: info
|
|
45
|
+
description: Inspect a project and verify referenced media.
|
|
46
|
+
source_file: scripts/info.py
|
|
47
|
+
input_schema: {type: object, required: [project], additionalProperties: false, properties: {project: {type: string, minLength: 1, maxLength: 4096}}}
|
|
48
|
+
execution: sync
|
|
49
|
+
affinity: any
|
|
50
|
+
timeout_hint_secs: 60
|
|
51
|
+
annotations: {read_only_hint: true, destructive_hint: false, idempotent_hint: true, open_world_hint: false}
|
|
52
|
+
- name: pack
|
|
53
|
+
description: Pack a project and referenced media into a portable folder.
|
|
54
|
+
source_file: scripts/pack.py
|
|
55
|
+
input_schema: {type: object, required: [project, output], additionalProperties: false, properties: {project: {type: string, minLength: 1, maxLength: 4096}, output: {type: string, minLength: 1, maxLength: 4096}}}
|
|
56
|
+
execution: sync
|
|
57
|
+
affinity: any
|
|
58
|
+
timeout_hint_secs: 600
|
|
59
|
+
annotations: {read_only_hint: false, destructive_hint: true, idempotent_hint: true, open_world_hint: false}
|
|
60
|
+
- name: captions
|
|
61
|
+
description: Generate local Whisper captions for a project with bounded word grouping.
|
|
62
|
+
source_file: scripts/captions.py
|
|
63
|
+
input_schema: {type: object, required: [project], additionalProperties: false, properties: {project: {type: string, minLength: 1, maxLength: 4096}, min_words: {type: integer, minimum: 1, maximum: 20, default: 2}, max_words: {type: integer, minimum: 1, maximum: 20, default: 7}}}
|
|
64
|
+
execution: sync
|
|
65
|
+
affinity: any
|
|
66
|
+
timeout_hint_secs: 1800
|
|
67
|
+
annotations: {read_only_hint: false, destructive_hint: true, idempotent_hint: true, open_world_hint: false}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dcc-mcp-openscreen
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: OpenScreen recording and demo export adapter for DCC-MCP
|
|
5
|
+
Author-email: Long Hao <hal.long@outlook.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.7
|
|
8
|
+
Requires-Dist: dcc-mcp-core<1.0.0,>=0.20.14
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# dcc-mcp-openscreen
|
|
12
|
+
|
|
13
|
+
Standalone DCC-MCP adapter for [OpenScreen](https://github.com/getopenscreen/openscreen).
|
|
14
|
+
|
|
15
|
+
The adapter exposes only typed, bounded CLI operations. Set `DCC_MCP_OPENSCREEN_PATH` when the executable is not on `PATH`. It does not automate the desktop UI; DCC window selection and unsupported UI actions remain owned by `dcc-cua` / `ui-control`.
|
|
16
|
+
|
|
17
|
+
## Run
|
|
18
|
+
|
|
19
|
+
```powershell
|
|
20
|
+
python -m pip install -e .
|
|
21
|
+
dcc-mcp-openscreen
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Tools: `openscreen__sources`, `openscreen__record`, and `openscreen__export`.
|
|
25
|
+
|
|
26
|
+
## License
|
|
27
|
+
|
|
28
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
dcc_mcp_openscreen/__init__.py,sha256=GzgC6No-VRRRJbserj4W6nBdeaOYmTLHcxc0_1JnpG4,74
|
|
2
|
+
dcc_mcp_openscreen/server.py,sha256=_qZDFbrnm6JSlYFzdztm-BWt5TDRLx8ChB1mLla5M0M,1069
|
|
3
|
+
dcc_mcp_openscreen/skills/openscreen/tools.yaml,sha256=hd4_xBiU7f9D-kRJJURXaNF6HxXBhD59BChbeRsXROI,3376
|
|
4
|
+
dcc_mcp_openscreen/skills/openscreen/scripts/_client.py,sha256=1h2YjFIZNI0SPnq9T4270ESf8Bw2F_595HqMplNS4EU,2274
|
|
5
|
+
dcc_mcp_openscreen/skills/openscreen/scripts/captions.py,sha256=M8OsrwnadUD17IbMaw7KaFuR4szqhIMRdjiqCkahpBk,327
|
|
6
|
+
dcc_mcp_openscreen/skills/openscreen/scripts/export.py,sha256=SWltsgjgCtmeG0nnqTsKwtxHzb592XYm4ygIoUfqoKM,353
|
|
7
|
+
dcc_mcp_openscreen/skills/openscreen/scripts/info.py,sha256=3dDv4vnLmR6UZGoJAt8l-9Ua3_qaABOu_SflBh9rlH8,220
|
|
8
|
+
dcc_mcp_openscreen/skills/openscreen/scripts/pack.py,sha256=ddKtAs_uqHKYc3R_WqRyQjwt8g9p4gA-Y0jwrhYI6oo,265
|
|
9
|
+
dcc_mcp_openscreen/skills/openscreen/scripts/record.py,sha256=lACbFOb99jyczSr-vqjhBdnnV6ANB_SSCVnqfslyEQI,660
|
|
10
|
+
dcc_mcp_openscreen/skills/openscreen/scripts/sources.py,sha256=OqSKegAN-_6suUWLXK5tCmoBQ1ZARWho9PdrY7qibjY,186
|
|
11
|
+
dcc_mcp_openscreen-0.1.1.dist-info/METADATA,sha256=GdmyiFCWPL7YkeyczHWEnEvWHdnaymIjThxmN-rjzQA,828
|
|
12
|
+
dcc_mcp_openscreen-0.1.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
13
|
+
dcc_mcp_openscreen-0.1.1.dist-info/entry_points.txt,sha256=nx5HVrUKcDOko0CvJ42TKG2noBMLXemRK7FJJQ4yT6I,142
|
|
14
|
+
dcc_mcp_openscreen-0.1.1.dist-info/RECORD,,
|