dcc-mcp-renderdoc 0.2.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.
@@ -0,0 +1,6 @@
1
+ """RenderDoc adapter for DCC-MCP."""
2
+
3
+ from .__version__ import __version__
4
+ from .server import RenderDocMcpServer
5
+
6
+ __all__ = ["RenderDocMcpServer", "__version__"]
@@ -0,0 +1,3 @@
1
+ """Package version."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,255 @@
1
+ """Small, typed wrapper around RenderDoc's official command-line client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shutil
7
+ import subprocess
8
+ import tempfile
9
+ import xml.etree.ElementTree as ET
10
+ from collections import Counter
11
+ from pathlib import Path
12
+ from typing import Any, Iterable, Optional, Sequence
13
+
14
+
15
+ class RenderDocError(RuntimeError):
16
+ """Raised when a RenderDoc operation cannot satisfy its contract."""
17
+
18
+
19
+ def resolve_renderdoccmd(explicit: Optional[str] = None) -> Path:
20
+ """Resolve an executable RenderDoc CLI without invoking a shell."""
21
+ candidate = explicit or os.environ.get("DCC_MCP_RENDERDOC_CMD")
22
+ if candidate:
23
+ path = Path(candidate).expanduser().resolve()
24
+ if path.is_file():
25
+ return path
26
+ raise RenderDocError(f"RenderDoc command does not exist: {path}")
27
+
28
+ for name in ("renderdoccmd.exe", "renderdoccmd"):
29
+ found = shutil.which(name)
30
+ if found:
31
+ return Path(found).resolve()
32
+ raise RenderDocError(
33
+ "renderdoccmd was not found; install RenderDoc or set DCC_MCP_RENDERDOC_CMD"
34
+ )
35
+
36
+
37
+ def _run(
38
+ arguments: Sequence[str],
39
+ *,
40
+ timeout_secs: int,
41
+ command: Optional[str] = None,
42
+ ) -> subprocess.CompletedProcess[str]:
43
+ executable = resolve_renderdoccmd(command)
44
+ try:
45
+ result = subprocess.run(
46
+ [str(executable), *arguments],
47
+ check=False,
48
+ capture_output=True,
49
+ text=True,
50
+ timeout=timeout_secs,
51
+ shell=False,
52
+ )
53
+ except subprocess.TimeoutExpired as exc:
54
+ raise RenderDocError(f"RenderDoc command timed out after {timeout_secs}s") from exc
55
+ except OSError as exc:
56
+ raise RenderDocError(f"Could not start RenderDoc: {exc}") from exc
57
+ if result.returncode != 0:
58
+ detail = (result.stderr or result.stdout or "unknown error").strip()[-2000:]
59
+ raise RenderDocError(f"RenderDoc exited with code {result.returncode}: {detail}")
60
+ return result
61
+
62
+
63
+ def get_version(*, command: Optional[str] = None) -> dict[str, Any]:
64
+ result = _run(["version"], timeout_secs=30, command=command)
65
+ output = "\n".join(part.strip() for part in (result.stdout, result.stderr) if part.strip())
66
+ return {"command": str(resolve_renderdoccmd(command)), "version_output": output}
67
+
68
+
69
+ def capture_program(
70
+ executable: str,
71
+ output_template: str,
72
+ *,
73
+ arguments: Optional[Sequence[str]] = None,
74
+ working_directory: Optional[str] = None,
75
+ wait_for_exit: bool = True,
76
+ api_validation: bool = False,
77
+ hook_children: bool = False,
78
+ timeout_secs: int = 300,
79
+ command: Optional[str] = None,
80
+ ) -> dict[str, Any]:
81
+ """Launch one explicit executable under RenderDoc and return new captures."""
82
+ target = Path(executable).expanduser().resolve()
83
+ if not target.is_file():
84
+ raise RenderDocError(f"Target executable does not exist: {target}")
85
+ output = Path(output_template).expanduser().resolve()
86
+ output.parent.mkdir(parents=True, exist_ok=True)
87
+ cwd = Path(working_directory).expanduser().resolve() if working_directory else target.parent
88
+ if not cwd.is_dir():
89
+ raise RenderDocError(f"Working directory does not exist: {cwd}")
90
+
91
+ before = {path.resolve() for path in output.parent.glob("*.rdc")}
92
+ cli_args = ["capture", "--working-dir", str(cwd), "--capture-file", str(output)]
93
+ if wait_for_exit:
94
+ cli_args.append("--wait-for-exit")
95
+ if api_validation:
96
+ cli_args.append("--opt-api-validation")
97
+ if hook_children:
98
+ cli_args.append("--opt-hook-children")
99
+ cli_args.extend([str(target), *(str(value) for value in arguments or [])])
100
+ result = _run(cli_args, timeout_secs=timeout_secs, command=command)
101
+
102
+ captures = sorted(
103
+ (path.resolve() for path in output.parent.glob("*.rdc") if path.resolve() not in before),
104
+ key=lambda path: path.stat().st_mtime_ns,
105
+ )
106
+ if not captures:
107
+ output_detail = "\n".join(
108
+ part.strip() for part in (result.stdout, result.stderr) if part.strip()
109
+ )[-2000:]
110
+ raise RenderDocError(
111
+ "The target exited without creating a new .rdc capture; ensure it presents a frame "
112
+ f"or uses RenderDoc's in-application capture API. RenderDoc output: {output_detail}"
113
+ )
114
+ return {
115
+ "target": str(target),
116
+ "captures": [str(path) for path in captures],
117
+ "stdout": result.stdout.strip(),
118
+ }
119
+
120
+
121
+ def _require_capture(capture_file: str) -> Path:
122
+ path = Path(capture_file).expanduser().resolve()
123
+ if not path.is_file() or path.suffix.lower() != ".rdc":
124
+ raise RenderDocError(f"RenderDoc capture does not exist or is not .rdc: {path}")
125
+ return path
126
+
127
+
128
+ def _child_text(parent: ET.Element, name: str) -> Optional[str]:
129
+ child = parent.find(name)
130
+ return child.text.strip() if child is not None and child.text else None
131
+
132
+
133
+ def parse_capture_xml(xml_file: str, *, representative_limit: int = 20) -> dict[str, Any]:
134
+ """Parse the stable high-level fields emitted by RenderDoc's XML converter."""
135
+ path = Path(xml_file)
136
+ try:
137
+ root = ET.parse(path).getroot()
138
+ except (ET.ParseError, OSError) as exc:
139
+ raise RenderDocError(f"Could not parse RenderDoc XML: {exc}") from exc
140
+ if root.tag != "rdc":
141
+ raise RenderDocError(f"Unexpected RenderDoc XML root: {root.tag}")
142
+ header = root.find("header")
143
+ chunks = root.find("chunks")
144
+ if header is None or chunks is None:
145
+ raise RenderDocError("RenderDoc XML is missing header or chunks")
146
+
147
+ driver = header.find("driver")
148
+ thumbnail = header.find("thumbnail")
149
+ names = [chunk.get("name") or "unnamed" for chunk in chunks.findall("chunk")]
150
+ counts = Counter(names)
151
+ return {
152
+ "driver": {
153
+ "id": driver.get("id") if driver is not None else None,
154
+ "name": driver.text.strip() if driver is not None and driver.text else None,
155
+ },
156
+ "machine_ident": _child_text(header, "machineIdent"),
157
+ "thumbnail": {
158
+ "width": int(thumbnail.get("width", "0")) if thumbnail is not None else 0,
159
+ "height": int(thumbnail.get("height", "0")) if thumbnail is not None else 0,
160
+ },
161
+ "chunk_version": chunks.get("version"),
162
+ "chunk_count": len(names),
163
+ "chunk_frequencies": [
164
+ {"name": name, "count": count} for name, count in counts.most_common(20)
165
+ ],
166
+ "representative_chunks": names[:representative_limit],
167
+ }
168
+
169
+
170
+ def inspect_capture(
171
+ capture_file: str,
172
+ *,
173
+ representative_limit: int = 20,
174
+ command: Optional[str] = None,
175
+ ) -> dict[str, Any]:
176
+ capture = _require_capture(capture_file)
177
+ with tempfile.TemporaryDirectory(prefix="dcc-mcp-renderdoc-") as directory:
178
+ xml_file = Path(directory) / "capture.xml"
179
+ _run(
180
+ [
181
+ "convert",
182
+ "--filename",
183
+ str(capture),
184
+ "--output",
185
+ str(xml_file),
186
+ "--convert-format",
187
+ "xml",
188
+ ],
189
+ timeout_secs=180,
190
+ command=command,
191
+ )
192
+ details = parse_capture_xml(str(xml_file), representative_limit=representative_limit)
193
+ return {"capture_file": str(capture), "size_bytes": capture.stat().st_size, **details}
194
+
195
+
196
+ def _prepare_output(output_file: str, expected_suffixes: Iterable[str]) -> Path:
197
+ path = Path(output_file).expanduser().resolve()
198
+ if path.suffix.lower() not in set(expected_suffixes):
199
+ choices = ", ".join(sorted(expected_suffixes))
200
+ raise RenderDocError(f"Output must use one of these extensions: {choices}")
201
+ path.parent.mkdir(parents=True, exist_ok=True)
202
+ return path
203
+
204
+
205
+ def export_thumbnail(
206
+ capture_file: str,
207
+ output_file: str,
208
+ *,
209
+ max_size: int = 0,
210
+ command: Optional[str] = None,
211
+ ) -> dict[str, Any]:
212
+ capture = _require_capture(capture_file)
213
+ output = _prepare_output(output_file, {".bmp", ".jpg", ".png", ".tga"})
214
+ _run(
215
+ ["thumb", "--out", str(output), "--max-size", str(max_size), str(capture)],
216
+ timeout_secs=120,
217
+ command=command,
218
+ )
219
+ if not output.is_file():
220
+ raise RenderDocError("RenderDoc reported success but did not create the thumbnail")
221
+ return {
222
+ "capture_file": str(capture),
223
+ "output_file": str(output),
224
+ "size_bytes": output.stat().st_size,
225
+ }
226
+
227
+
228
+ def export_timeline(
229
+ capture_file: str,
230
+ output_file: str,
231
+ *,
232
+ command: Optional[str] = None,
233
+ ) -> dict[str, Any]:
234
+ capture = _require_capture(capture_file)
235
+ output = _prepare_output(output_file, {".json"})
236
+ _run(
237
+ [
238
+ "convert",
239
+ "--filename",
240
+ str(capture),
241
+ "--output",
242
+ str(output),
243
+ "--convert-format",
244
+ "chrome.json",
245
+ ],
246
+ timeout_secs=180,
247
+ command=command,
248
+ )
249
+ if not output.is_file():
250
+ raise RenderDocError("RenderDoc reported success but did not create the timeline")
251
+ return {
252
+ "capture_file": str(capture),
253
+ "output_file": str(output),
254
+ "size_bytes": output.stat().st_size,
255
+ }
@@ -0,0 +1,78 @@
1
+ """Standalone RenderDoc MCP server lifecycle."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import signal
7
+ import sys
8
+ import threading
9
+ from pathlib import Path
10
+ from typing import Optional
11
+
12
+ from dcc_mcp_core import DccServerOptions
13
+ from dcc_mcp_core.server_base import DccServerBase
14
+
15
+ from .__version__ import __version__
16
+ from .runtime import RenderDocError, get_version
17
+
18
+ DEFAULT_PORT = 8765
19
+ _server: Optional["RenderDocMcpServer"] = None
20
+
21
+
22
+ class RenderDocMcpServer(DccServerBase):
23
+ """Headless DCC-MCP adapter backed by renderdoccmd."""
24
+
25
+ def __init__(self, port: int = DEFAULT_PORT) -> None:
26
+ os.environ.setdefault("DCC_MCP_PYTHON_EXECUTABLE", sys.executable)
27
+ options = DccServerOptions.from_env(
28
+ "renderdoc",
29
+ Path(__file__).resolve().parent / "skills",
30
+ port=port,
31
+ server_name="dcc-mcp-renderdoc",
32
+ server_version=__version__,
33
+ )
34
+ super().__init__(options=options)
35
+
36
+ def _version_string(self) -> str:
37
+ try:
38
+ return get_version()["version_output"]
39
+ except RenderDocError:
40
+ return "RenderDoc CLI unavailable"
41
+
42
+
43
+ def start_server(port: Optional[int] = None) -> RenderDocMcpServer:
44
+ global _server
45
+ if _server is None or not _server.is_running:
46
+ selected_port = (
47
+ port
48
+ if port is not None
49
+ else int(os.environ.get("DCC_MCP_RENDERDOC_PORT", DEFAULT_PORT))
50
+ )
51
+ _server = RenderDocMcpServer(selected_port)
52
+ _server.register_builtin_actions()
53
+ _server.start()
54
+ return _server
55
+
56
+
57
+ def stop_server() -> None:
58
+ global _server
59
+ if _server is not None:
60
+ _server.stop()
61
+ _server = None
62
+
63
+
64
+ def main() -> None:
65
+ """Run until interrupted."""
66
+ stopped = threading.Event()
67
+ signal.signal(signal.SIGINT, lambda *_: stopped.set())
68
+ if hasattr(signal, "SIGTERM"):
69
+ signal.signal(signal.SIGTERM, lambda *_: stopped.set())
70
+ start_server()
71
+ try:
72
+ stopped.wait()
73
+ finally:
74
+ stop_server()
75
+
76
+
77
+ if __name__ == "__main__":
78
+ main()
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: renderdoc-analysis
3
+ description: >-
4
+ Domain skill — Inspect an existing RenderDoc capture and export its embedded thumbnail or Chrome
5
+ trace. Use for offline graphics triage and automation artifacts. Not for launching a capture —
6
+ use renderdoc-capture.
7
+ license: MIT
8
+ compatibility: "RenderDoc 1.45+; dcc-mcp-core 0.19+"
9
+ allowed-tools: "python"
10
+ metadata:
11
+ dcc-mcp:
12
+ dcc: renderdoc
13
+ layer: domain
14
+ version: "0.1.0"
15
+ search-hint: "RenderDoc inspect rdc chunks thumbnail Chrome trace graphics analysis"
16
+ tags: "renderdoc,analysis,thumbnail,timeline,graphics-debugging"
17
+ tools: tools.yaml
18
+ depends: "dcc-diagnostics"
19
+ ---
20
+
21
+ # RenderDoc Analysis
22
+
23
+ Inspect before exporting. These tools never modify the input `.rdc`; exports require an explicit
24
+ destination path and create its parent directory when needed.
25
+
@@ -0,0 +1,2 @@
1
+ - dcc-diagnostics
2
+
@@ -0,0 +1,15 @@
1
+ from dcc_mcp_core.skill import skill_entry, skill_success
2
+
3
+ from dcc_mcp_renderdoc.runtime import export_thumbnail
4
+
5
+
6
+ @skill_entry
7
+ def main(capture_file: str, output_file: str, max_size: int = 0, **_kwargs):
8
+ result = export_thumbnail(capture_file, output_file, max_size=max_size)
9
+ return skill_success("RenderDoc thumbnail exported.", **result)
10
+
11
+
12
+ if __name__ == "__main__":
13
+ from dcc_mcp_core.skill import run_main
14
+
15
+ run_main(main)
@@ -0,0 +1,15 @@
1
+ from dcc_mcp_core.skill import skill_entry, skill_success
2
+
3
+ from dcc_mcp_renderdoc.runtime import export_timeline
4
+
5
+
6
+ @skill_entry
7
+ def main(capture_file: str, output_file: str, **_kwargs):
8
+ result = export_timeline(capture_file, output_file)
9
+ return skill_success("RenderDoc Chrome timeline exported.", **result)
10
+
11
+
12
+ if __name__ == "__main__":
13
+ from dcc_mcp_core.skill import run_main
14
+
15
+ run_main(main)
@@ -0,0 +1,15 @@
1
+ from dcc_mcp_core.skill import skill_entry, skill_success
2
+
3
+ from dcc_mcp_renderdoc.runtime import inspect_capture
4
+
5
+
6
+ @skill_entry
7
+ def main(capture_file: str, representative_limit: int = 20, **_kwargs):
8
+ result = inspect_capture(capture_file, representative_limit=representative_limit)
9
+ return skill_success("RenderDoc capture inspected.", **result)
10
+
11
+
12
+ if __name__ == "__main__":
13
+ from dcc_mcp_core.skill import run_main
14
+
15
+ run_main(main)
@@ -0,0 +1,63 @@
1
+ tools:
2
+ - name: inspect_capture
3
+ description: Convert one .rdc capture to temporary XML and summarize stable header and chunk metadata. Use for offline triage. Not for GPU performance conclusions or high-level drawcall reconstruction.
4
+ input_schema:
5
+ type: object
6
+ required: [capture_file]
7
+ properties:
8
+ capture_file: {type: string, minLength: 1, maxLength: 1000}
9
+ representative_limit: {type: integer, minimum: 0, maximum: 100, default: 20}
10
+ additionalProperties: false
11
+ output_schema: {type: object}
12
+ read_only: true
13
+ destructive: false
14
+ idempotent: true
15
+ execution: async
16
+ affinity: any
17
+ timeout_hint_secs: 180
18
+ source_file: scripts/inspect_capture.py
19
+ next-tools:
20
+ on-success: [renderdoc_analysis__export_thumbnail, renderdoc_analysis__export_timeline]
21
+ on-failure: [renderdoc_capture__get_version, dcc_diagnostics__audit_log]
22
+ - name: export_thumbnail
23
+ description: Export the embedded capture thumbnail as PNG, JPG, BMP, or TGA. Use for visual review artifacts. Not for replaying or rendering arbitrary capture resources.
24
+ input_schema:
25
+ type: object
26
+ required: [capture_file, output_file]
27
+ properties:
28
+ capture_file: {type: string, minLength: 1, maxLength: 1000}
29
+ output_file: {type: string, minLength: 1, maxLength: 1000}
30
+ max_size: {type: integer, minimum: 0, maximum: 16384, default: 0}
31
+ additionalProperties: false
32
+ output_schema: {type: object}
33
+ read_only: false
34
+ destructive: false
35
+ idempotent: true
36
+ execution: async
37
+ affinity: any
38
+ timeout_hint_secs: 120
39
+ source_file: scripts/export_thumbnail.py
40
+ next-tools:
41
+ on-success: [renderdoc_analysis__inspect_capture]
42
+ on-failure: [renderdoc_capture__get_version, dcc_diagnostics__audit_log]
43
+ - name: export_timeline
44
+ description: Export capture events as Chrome profiler JSON. Use for timeline tooling and CI artifacts. Not for authoritative GPU duration measurement.
45
+ input_schema:
46
+ type: object
47
+ required: [capture_file, output_file]
48
+ properties:
49
+ capture_file: {type: string, minLength: 1, maxLength: 1000}
50
+ output_file: {type: string, minLength: 1, maxLength: 1000}
51
+ additionalProperties: false
52
+ output_schema: {type: object}
53
+ read_only: false
54
+ destructive: false
55
+ idempotent: true
56
+ execution: async
57
+ affinity: any
58
+ timeout_hint_secs: 180
59
+ source_file: scripts/export_timeline.py
60
+ next-tools:
61
+ on-success: [renderdoc_analysis__inspect_capture]
62
+ on-failure: [renderdoc_capture__get_version, dcc_diagnostics__audit_log]
63
+
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: renderdoc-capture
3
+ description: >-
4
+ Domain skill — Discover RenderDoc and launch an explicit game or graphics executable under
5
+ capture. Use for repeatable local or CI frame capture. Not for reading an existing capture —
6
+ use renderdoc-analysis.
7
+ license: MIT
8
+ compatibility: "RenderDoc 1.45+; dcc-mcp-core 0.19+"
9
+ allowed-tools: "python"
10
+ metadata:
11
+ dcc-mcp:
12
+ dcc: renderdoc
13
+ layer: domain
14
+ version: "0.1.0"
15
+ search-hint: "RenderDoc capture launch executable game graphics frame rdc"
16
+ tags: "renderdoc,capture,graphics-debugging,game-development"
17
+ tools: tools.yaml
18
+ depends: "dcc-diagnostics"
19
+ ---
20
+
21
+ # RenderDoc Capture
22
+
23
+ Call `get_version` before capture. `capture_program` starts only the explicit executable and
24
+ arguments, never a shell. The target must trigger a RenderDoc frame capture.
25
+
@@ -0,0 +1,2 @@
1
+ - dcc-diagnostics
2
+
@@ -0,0 +1,34 @@
1
+ from dcc_mcp_core.skill import skill_entry, skill_success
2
+
3
+ from dcc_mcp_renderdoc.runtime import capture_program
4
+
5
+
6
+ @skill_entry
7
+ def main(
8
+ executable: str,
9
+ output_template: str,
10
+ arguments=None,
11
+ working_directory=None,
12
+ wait_for_exit: bool = True,
13
+ api_validation: bool = False,
14
+ hook_children: bool = False,
15
+ timeout_secs: int = 300,
16
+ **_kwargs,
17
+ ):
18
+ result = capture_program(
19
+ executable,
20
+ output_template,
21
+ arguments=arguments,
22
+ working_directory=working_directory,
23
+ wait_for_exit=wait_for_exit,
24
+ api_validation=api_validation,
25
+ hook_children=hook_children,
26
+ timeout_secs=timeout_secs,
27
+ )
28
+ return skill_success(f"Created {len(result['captures'])} RenderDoc capture(s).", **result)
29
+
30
+
31
+ if __name__ == "__main__":
32
+ from dcc_mcp_core.skill import run_main
33
+
34
+ run_main(main)
@@ -0,0 +1,14 @@
1
+ from dcc_mcp_core.skill import skill_entry, skill_success
2
+
3
+ from dcc_mcp_renderdoc.runtime import get_version
4
+
5
+
6
+ @skill_entry
7
+ def main(**_kwargs):
8
+ return skill_success("RenderDoc CLI is available.", **get_version())
9
+
10
+
11
+ if __name__ == "__main__":
12
+ from dcc_mcp_core.skill import run_main
13
+
14
+ run_main(main)
@@ -0,0 +1,45 @@
1
+ tools:
2
+ - name: get_version
3
+ description: Resolve renderdoccmd and return its version output. Use before capture or analysis to verify the runtime. Not for capture-file inspection.
4
+ input_schema: {type: object, properties: {}, additionalProperties: false}
5
+ output_schema: {type: object}
6
+ read_only: true
7
+ destructive: false
8
+ idempotent: true
9
+ execution: sync
10
+ affinity: any
11
+ timeout_hint_secs: 30
12
+ source_file: scripts/get_version.py
13
+ next-tools:
14
+ on-success: [renderdoc_capture__capture_program]
15
+ on-failure: [dcc_diagnostics__audit_log]
16
+ - name: capture_program
17
+ description: Launch one explicit executable under RenderDoc and return newly created .rdc files. Use for controlled game or graphics test capture. Not for shell commands or attaching to an existing process.
18
+ input_schema:
19
+ type: object
20
+ required: [executable, output_template]
21
+ properties:
22
+ executable: {type: string, minLength: 1, maxLength: 1000}
23
+ output_template: {type: string, minLength: 1, maxLength: 1000}
24
+ arguments:
25
+ type: array
26
+ maxItems: 100
27
+ items: {type: string, maxLength: 2000}
28
+ working_directory: {type: string, maxLength: 1000}
29
+ wait_for_exit: {type: boolean, default: true}
30
+ api_validation: {type: boolean, default: false}
31
+ hook_children: {type: boolean, default: false}
32
+ timeout_secs: {type: integer, minimum: 1, maximum: 3600, default: 300}
33
+ additionalProperties: false
34
+ output_schema: {type: object}
35
+ read_only: false
36
+ destructive: false
37
+ idempotent: false
38
+ execution: async
39
+ affinity: any
40
+ timeout_hint_secs: 3600
41
+ source_file: scripts/capture_program.py
42
+ next-tools:
43
+ on-success: [renderdoc_analysis__inspect_capture, renderdoc_analysis__export_thumbnail]
44
+ on-failure: [dcc_diagnostics__audit_log]
45
+
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: dcc-mcp-renderdoc
3
+ Version: 0.2.0
4
+ Summary: RenderDoc capture and replay automation skills for DCC-MCP
5
+ Project-URL: Homepage, https://github.com/dcc-mcp/dcc-mcp-renderdoc
6
+ Project-URL: Repository, https://github.com/dcc-mcp/dcc-mcp-renderdoc
7
+ Project-URL: Issues, https://github.com/dcc-mcp/dcc-mcp-renderdoc/issues
8
+ Author-email: loonghao <hal.long@outlook.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: automation,game-development,graphics-debugging,mcp,renderdoc
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Games/Entertainment
20
+ Requires-Python: >=3.9
21
+ Requires-Dist: dcc-mcp-core<1.0.0,>=0.19.38
22
+ Provides-Extra: dev
23
+ Requires-Dist: build>=1.2; extra == 'dev'
24
+ Requires-Dist: pytest>=8; extra == 'dev'
25
+ Requires-Dist: ruff>=0.8; extra == 'dev'
26
+ Requires-Dist: twine>=6; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # dcc-mcp-renderdoc
30
+
31
+ RenderDoc capture and replay automation for the DCC Model Context Protocol ecosystem.
32
+
33
+ The adapter is headless-first: it reuses the official `renderdoccmd` executable for capture and
34
+ conversion, so agents can automate graphics regression triage without keeping the RenderDoc GUI
35
+ open or installing a second bridge.
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pip install dcc-mcp-renderdoc
41
+ ```
42
+
43
+ Install RenderDoc separately, then expose its command line tool with either `PATH` or:
44
+
45
+ ```bash
46
+ export DCC_MCP_RENDERDOC_CMD=/opt/renderdoc/bin/renderdoccmd
47
+ dcc-mcp-renderdoc
48
+ ```
49
+
50
+ On Windows, set the variable to `renderdoccmd.exe`.
51
+
52
+ ## Agent workflows
53
+
54
+ - Launch a game or test executable under RenderDoc and wait for a typed `.rdc` capture.
55
+ - Inspect capture driver, machine identity, chunk version, API-call counts, and representative calls.
56
+ - Export a capture thumbnail for visual review.
57
+ - Export Chrome trace JSON for timeline tooling.
58
+
59
+ The capture tool launches only the explicit executable and arguments supplied by the caller. It
60
+ never invokes a shell. Analysis tools are read-only with respect to the `.rdc` input.
61
+
62
+ ## Real CI
63
+
64
+ CI discovers the current stable RenderDoc build from the official downloads page. It compiles a
65
+ small OpenGL program, captures a real frame under Xvfb, calls the MCP analysis tool against the
66
+ resulting `.rdc`, and verifies thumbnail and timeline exports.
67
+
68
+ ## Development
69
+
70
+ ```bash
71
+ uv sync --extra dev
72
+ uv run python -m pytest
73
+ uv run ruff check src tests tools
74
+ uv run python tools/lint_skills.py
75
+ ```
76
+
77
+ RenderDoc is an MIT-licensed graphics debugger maintained independently at
78
+ [renderdoc.org](https://renderdoc.org/). This adapter is not affiliated with the RenderDoc project.
79
+
@@ -0,0 +1,20 @@
1
+ dcc_mcp_renderdoc/__init__.py,sha256=PtvTUY4_RsBHMB5Vlh8dc-JruF3l_jD1B0rhmVrDs8Y,163
2
+ dcc_mcp_renderdoc/__version__.py,sha256=VDsBswzfU7gNsBkIaxxV6tsi0ixlms3WjN7HYgZJZ2g,46
3
+ dcc_mcp_renderdoc/runtime.py,sha256=G4N-fJ4QaOYkbfmKcYJsSopOLPsMGAsk4tWvMs9sezk,9135
4
+ dcc_mcp_renderdoc/server.py,sha256=pts-cHn_wyr64cDticPOIfhs2Dgs5DAp62TSPwvO-YM,2075
5
+ dcc_mcp_renderdoc/skills/renderdoc-analysis/SKILL.md,sha256=isfpOP96bGnJbanUKVfsK-sYIGO14sLRNSOFz6mz_Po,837
6
+ dcc_mcp_renderdoc/skills/renderdoc-analysis/tools.yaml,sha256=9UvHc9_oPv7Vsgo9EUun50v_0Uw26-Hasxm-o8WjIqA,2623
7
+ dcc_mcp_renderdoc/skills/renderdoc-analysis/metadata/depends.md,sha256=oPXRkRwg5Q0bNsyQbM15Zo0kW0kf2HngdhKNVoM61f4,19
8
+ dcc_mcp_renderdoc/skills/renderdoc-analysis/scripts/export_thumbnail.py,sha256=j8Gm5QCZPpD-6kCuROkCnwwFuqmmF9-DxDon8ieVi_s,443
9
+ dcc_mcp_renderdoc/skills/renderdoc-analysis/scripts/export_timeline.py,sha256=RIgRXH3ZDt0TbM43bpq-pYZnqHPpxjuKRuI2jBh_L8E,409
10
+ dcc_mcp_renderdoc/skills/renderdoc-analysis/scripts/inspect_capture.py,sha256=-20dNoM7DoD0_B-CENLscipjYAdv30LGMfZEbdbtAHc,446
11
+ dcc_mcp_renderdoc/skills/renderdoc-capture/SKILL.md,sha256=W9u3CxjCXBNRH_JWLD0Roj9iRo_WR_z_zLD-AGsOD0E,826
12
+ dcc_mcp_renderdoc/skills/renderdoc-capture/tools.yaml,sha256=vYyif3bExXaG6foVsr_stUCW3OSmOay0hN0bAqKhxc8,1878
13
+ dcc_mcp_renderdoc/skills/renderdoc-capture/metadata/depends.md,sha256=oPXRkRwg5Q0bNsyQbM15Zo0kW0kf2HngdhKNVoM61f4,19
14
+ dcc_mcp_renderdoc/skills/renderdoc-capture/scripts/capture_program.py,sha256=ny4KUNouJD7fUYhh8PWDEoP_Npr4lezcZx8aZQ3Blpw,870
15
+ dcc_mcp_renderdoc/skills/renderdoc-capture/scripts/get_version.py,sha256=qog2c4GEykHZDTvWMGa7s8KxgZ1Skez9qek1TTytogs,311
16
+ dcc_mcp_renderdoc-0.2.0.dist-info/METADATA,sha256=5erLYp2CAce_dVmIJMV8O6W1McRxgdPyw4kIour8K3k,2873
17
+ dcc_mcp_renderdoc-0.2.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
18
+ dcc_mcp_renderdoc-0.2.0.dist-info/entry_points.txt,sha256=R0g0vPcVzarQHA-R65uVHu_R8xU0s4JRVaJ9r-aktIs,137
19
+ dcc_mcp_renderdoc-0.2.0.dist-info/licenses/LICENSE,sha256=tI9ZnJarjIkjC-Mje0HidFJgMMAhrK9qV-JdFSshBZw,1066
20
+ dcc_mcp_renderdoc-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ dcc-mcp-renderdoc = dcc_mcp_renderdoc.server:main
3
+
4
+ [dcc_mcp.adapters]
5
+ renderdoc = dcc_mcp_renderdoc:RenderDocMcpServer
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 loonghao
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+