ultimate-unreal-engine-mcp 0.1.0

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.
Files changed (124) hide show
  1. package/README.md +729 -0
  2. package/dist/build/error-parser.js +51 -0
  3. package/dist/build/fix-suggester.js +84 -0
  4. package/dist/build/ubt-runner.js +146 -0
  5. package/dist/cli.js +13 -0
  6. package/dist/config.js +8 -0
  7. package/dist/docs/data/ue57-api.js +228 -0
  8. package/dist/docs/doc-index.js +110 -0
  9. package/dist/docs/types.js +4 -0
  10. package/dist/generators/class-generator.js +363 -0
  11. package/dist/generators/file-modifier.js +276 -0
  12. package/dist/generators/uht-validator.js +177 -0
  13. package/dist/index.js +89 -0
  14. package/dist/parsers/cpp-class-index.js +230 -0
  15. package/dist/parsers/cpp-parser.js +369 -0
  16. package/dist/parsers/ini-parser.js +216 -0
  17. package/dist/parsers/uproject-parser.js +130 -0
  18. package/dist/plugin-bridge/client.js +217 -0
  19. package/dist/plugin-bridge/protocol.js +6 -0
  20. package/dist/plugin-bridge/retry.js +23 -0
  21. package/dist/setup.js +209 -0
  22. package/dist/tools/ai-systems/index.js +247 -0
  23. package/dist/tools/ai-systems/types.js +4 -0
  24. package/dist/tools/animation/index.js +241 -0
  25. package/dist/tools/animation/types.js +4 -0
  26. package/dist/tools/audio/index.js +204 -0
  27. package/dist/tools/audio/types.js +4 -0
  28. package/dist/tools/blueprint/index.js +495 -0
  29. package/dist/tools/blueprint/types.js +4 -0
  30. package/dist/tools/build/index.js +163 -0
  31. package/dist/tools/chaos/index.js +230 -0
  32. package/dist/tools/chaos/types.js +4 -0
  33. package/dist/tools/collision-physics/index.js +211 -0
  34. package/dist/tools/config/index.js +288 -0
  35. package/dist/tools/cpp/index.js +305 -0
  36. package/dist/tools/docs/index.js +251 -0
  37. package/dist/tools/editor/index.js +242 -0
  38. package/dist/tools/gas/index.js +222 -0
  39. package/dist/tools/gas/types.js +5 -0
  40. package/dist/tools/import-export/index.js +218 -0
  41. package/dist/tools/input/index.js +146 -0
  42. package/dist/tools/known-issues/index.js +88 -0
  43. package/dist/tools/known-issues/middleware.js +55 -0
  44. package/dist/tools/known-issues/store.js +125 -0
  45. package/dist/tools/livelink/index.js +203 -0
  46. package/dist/tools/livelink/types.js +4 -0
  47. package/dist/tools/material/index.js +190 -0
  48. package/dist/tools/motion-design/index.js +251 -0
  49. package/dist/tools/motion-design/types.js +6 -0
  50. package/dist/tools/movie-render/index.js +220 -0
  51. package/dist/tools/networking/index.js +149 -0
  52. package/dist/tools/pcg/index.js +164 -0
  53. package/dist/tools/selection/index.js +180 -0
  54. package/dist/tools/sequencer/index.js +218 -0
  55. package/dist/tools/validation/index.js +183 -0
  56. package/dist/tools/validation/types.js +4 -0
  57. package/dist/tools/viewport/index.js +310 -0
  58. package/dist/tools/worldpartition/index.js +226 -0
  59. package/dist/tools/worldpartition/types.js +4 -0
  60. package/dist/utils/execFileNoThrow.js +40 -0
  61. package/dist/utils/logger.js +27 -0
  62. package/dist/utils/path-guard.js +26 -0
  63. package/package.json +40 -0
  64. package/unreal-plugin/MCPBridge/MCPBridge.uplugin +29 -0
  65. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/MCPBridgeEditor.Build.cs +68 -0
  66. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAICommands.cpp +919 -0
  67. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAICommands.h +23 -0
  68. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPActorCommands.cpp +415 -0
  69. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPActorCommands.h +16 -0
  70. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAnimationCommands.cpp +653 -0
  71. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAnimationCommands.h +24 -0
  72. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAssetCommands.cpp +290 -0
  73. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAssetCommands.h +17 -0
  74. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAudioCommands.cpp +624 -0
  75. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAudioCommands.h +22 -0
  76. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintHandlers.cpp +616 -0
  77. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintHandlers.h +25 -0
  78. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintWriteHandlers.cpp +744 -0
  79. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintWriteHandlers.h +24 -0
  80. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeEditor.cpp +23 -0
  81. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeSubsystem.cpp +149 -0
  82. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeSubsystem.h +38 -0
  83. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPChaosCommands.cpp +771 -0
  84. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPChaosCommands.h +22 -0
  85. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPCollisionPhysicsCommands.cpp +749 -0
  86. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPCollisionPhysicsCommands.h +22 -0
  87. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPEditorStateCommands.cpp +172 -0
  88. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPEditorStateCommands.h +16 -0
  89. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPGASCommands.cpp +715 -0
  90. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPGASCommands.h +22 -0
  91. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPImportExportCommands.cpp +679 -0
  92. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPImportExportCommands.h +22 -0
  93. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPInputHandlers.cpp +381 -0
  94. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPInputHandlers.h +24 -0
  95. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPLiveLinkCommands.cpp +504 -0
  96. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPLiveLinkCommands.h +22 -0
  97. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMaterialCommands.cpp +511 -0
  98. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMaterialCommands.h +22 -0
  99. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMotionDesignCommands.cpp +1110 -0
  100. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMotionDesignCommands.h +28 -0
  101. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMovieRenderCommands.cpp +590 -0
  102. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMovieRenderCommands.h +16 -0
  103. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPNetworkingCommands.cpp +482 -0
  104. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPNetworkingCommands.h +16 -0
  105. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPPieCommands.cpp +338 -0
  106. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPPieCommands.h +16 -0
  107. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSelectionCommands.cpp +677 -0
  108. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSelectionCommands.h +22 -0
  109. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSequencerCommands.cpp +721 -0
  110. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSequencerCommands.h +16 -0
  111. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPValidationCommands.cpp +368 -0
  112. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPValidationCommands.h +22 -0
  113. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPViewportCommands.cpp +1208 -0
  114. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPViewportCommands.h +29 -0
  115. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPWorldPartitionCommands.cpp +822 -0
  116. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPWorldPartitionCommands.h +23 -0
  117. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Public/MCPBridgeEditor.h +14 -0
  118. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/MCPBridgeRuntime.Build.cs +28 -0
  119. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPBridgeRuntime.cpp +22 -0
  120. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPCommandRouter.cpp +118 -0
  121. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPTcpServer.cpp +196 -0
  122. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPBridgeRuntime.h +15 -0
  123. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPCommandRouter.h +55 -0
  124. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPTcpServer.h +59 -0
@@ -0,0 +1,55 @@
1
+ // src/tools/known-issues/middleware.ts
2
+ // Wraps tool handlers with a pre-execution KNOWN_ISSUES check.
3
+ // Matching issues are prepended as warnings to the tool response content array.
4
+ // Handler exceptions are caught and returned as isError responses — never propagated.
5
+ import { readKnownIssues } from './store.js';
6
+ // ---------------------------------------------------------------------------
7
+ // withKnownIssues
8
+ // ---------------------------------------------------------------------------
9
+ /**
10
+ * Wraps a tool handler with a KNOWN_ISSUES pre-execution check.
11
+ *
12
+ * Before invoking the real handler:
13
+ * 1. Reads KNOWN_ISSUES.md (cached by mtime — cheap on cache hit).
14
+ * 2. Filters issues where affectedTools includes toolName or '*' (wildcard).
15
+ * 3. If matching issues exist, prepends a single warning content item
16
+ * (all warnings joined by '\n') before the handler's content items.
17
+ *
18
+ * Exceptions thrown by the handler are caught and returned as:
19
+ * { isError: true, content: [{ type: 'text', text: 'Error: <message>' }] }
20
+ *
21
+ * @param toolName The MCP tool name (e.g., 'ue_read_cpp_class').
22
+ * @param handler The real tool handler to wrap.
23
+ * @returns A new handler with the same signature.
24
+ */
25
+ export function withKnownIssues(toolName, handler) {
26
+ return async (args) => {
27
+ const issues = await readKnownIssues();
28
+ const relevant = issues.filter((i) => i.affectedTools.includes(toolName) || i.affectedTools.includes('*'));
29
+ let result;
30
+ try {
31
+ result = await handler(args);
32
+ }
33
+ catch (err) {
34
+ return {
35
+ isError: true,
36
+ content: [
37
+ {
38
+ type: 'text',
39
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`,
40
+ },
41
+ ],
42
+ };
43
+ }
44
+ if (relevant.length === 0) {
45
+ return result;
46
+ }
47
+ const warnings = relevant
48
+ .map((i) => `[KNOWN ISSUE ${i.id}] ${i.description} — Resolution: ${i.resolution}`)
49
+ .join('\n');
50
+ return {
51
+ content: [{ type: 'text', text: warnings }, ...result.content],
52
+ isError: result.isError,
53
+ };
54
+ };
55
+ }
@@ -0,0 +1,125 @@
1
+ // src/tools/known-issues/store.ts
2
+ // Reads, caches, and writes KNOWN_ISSUES.md at the project root.
3
+ // Cache is keyed by file mtime — re-reads only when the file changes.
4
+ import * as fs from 'fs/promises';
5
+ import * as path from 'path';
6
+ import { PROJECT_ROOT } from '../../config.js';
7
+ const KNOWN_ISSUES_PATH = path.join(PROJECT_ROOT, 'KNOWN_ISSUES.md');
8
+ const cache = { issues: [], mtime: 0 };
9
+ // ---------------------------------------------------------------------------
10
+ // Helpers
11
+ // ---------------------------------------------------------------------------
12
+ /**
13
+ * Extract the text after a bold field label on a line.
14
+ * e.g., "**Description:** foo" → "foo"
15
+ */
16
+ function extractField(lines, label) {
17
+ for (const line of lines) {
18
+ const trimmed = line.trim();
19
+ if (trimmed.startsWith(label)) {
20
+ return trimmed.slice(label.length).trim();
21
+ }
22
+ }
23
+ return '';
24
+ }
25
+ /**
26
+ * Parse a single issue block (text after "## ISSUE-" prefix).
27
+ * The first line of the block is the issue number suffix (e.g., "001\n...").
28
+ */
29
+ function parseIssueBlock(block) {
30
+ const newlineIdx = block.indexOf('\n');
31
+ const idSuffix = newlineIdx === -1 ? block.trim() : block.slice(0, newlineIdx).trim();
32
+ if (!idSuffix)
33
+ return null;
34
+ const id = 'ISSUE-' + idSuffix;
35
+ const body = newlineIdx === -1 ? '' : block.slice(newlineIdx + 1);
36
+ const lines = body.split('\n');
37
+ const description = extractField(lines, '**Description:**');
38
+ const affectedToolsRaw = extractField(lines, '**Affected tools:**');
39
+ const rootCause = extractField(lines, '**Root cause:**');
40
+ const resolution = extractField(lines, '**Resolution:**');
41
+ const dateAdded = extractField(lines, '**Date added:**');
42
+ const statusRaw = extractField(lines, '**Status:**').toLowerCase();
43
+ const status = statusRaw === 'resolved' ? 'resolved' : 'active';
44
+ const affectedTools = affectedToolsRaw.trim() === ''
45
+ ? []
46
+ : affectedToolsRaw.split(',').map((s) => s.trim()).filter(Boolean);
47
+ return { id, description, affectedTools, rootCause, resolution, dateAdded, status };
48
+ }
49
+ // ---------------------------------------------------------------------------
50
+ // Public API
51
+ // ---------------------------------------------------------------------------
52
+ /**
53
+ * Read and parse all ## ISSUE-NNN sections from KNOWN_ISSUES.md.
54
+ * Results are cached by mtime; a second call without file changes returns the
55
+ * same array reference without performing a second fs.readFile.
56
+ *
57
+ * Returns [] if the file does not exist or contains no ## ISSUE- sections.
58
+ */
59
+ export async function readKnownIssues() {
60
+ let stat;
61
+ try {
62
+ stat = await fs.stat(KNOWN_ISSUES_PATH);
63
+ }
64
+ catch (err) {
65
+ if (err !== null &&
66
+ typeof err === 'object' &&
67
+ 'code' in err &&
68
+ err.code === 'ENOENT') {
69
+ return [];
70
+ }
71
+ throw err;
72
+ }
73
+ const currentMtime = stat.mtimeMs;
74
+ if (currentMtime === cache.mtime) {
75
+ return cache.issues;
76
+ }
77
+ const content = await fs.readFile(KNOWN_ISSUES_PATH, 'utf8');
78
+ // Split on '\n## ISSUE-' — the first segment is the header (discarded).
79
+ const segments = content.split('\n## ISSUE-');
80
+ const issueBlocks = segments.slice(1); // discard header segment
81
+ const issues = [];
82
+ for (const block of issueBlocks) {
83
+ const issue = parseIssueBlock(block);
84
+ if (issue !== null) {
85
+ issues.push(issue);
86
+ }
87
+ }
88
+ cache.issues = issues;
89
+ cache.mtime = currentMtime;
90
+ return issues;
91
+ }
92
+ /**
93
+ * Append a new ## ISSUE-NNN section to KNOWN_ISSUES.md.
94
+ * The issue number is auto-incremented from the highest existing issue number.
95
+ * Invalidates the mtime cache so the next readKnownIssues() call re-reads the file.
96
+ *
97
+ * Returns the full KnownIssue with the computed id.
98
+ */
99
+ export async function appendKnownIssue(issue) {
100
+ const existing = await readKnownIssues();
101
+ // Compute next ID
102
+ let maxN = 0;
103
+ for (const existing_issue of existing) {
104
+ const n = parseInt(existing_issue.id.replace('ISSUE-', ''), 10);
105
+ if (!isNaN(n) && n > maxN) {
106
+ maxN = n;
107
+ }
108
+ }
109
+ const nextN = maxN + 1;
110
+ const id = 'ISSUE-' + String(nextN).padStart(3, '0');
111
+ // Build the markdown block matching KNOWN_ISSUES.md format
112
+ const affectedToolsStr = issue.affectedTools.join(', ');
113
+ const block = `\n## ${id}\n` +
114
+ `**Description:** ${issue.description}\n` +
115
+ `**Affected tools:** ${affectedToolsStr}\n` +
116
+ `**Root cause:** ${issue.rootCause}\n` +
117
+ `**Resolution:** ${issue.resolution}\n` +
118
+ `**Date added:** ${issue.dateAdded}\n` +
119
+ `**Status:** ${issue.status}\n` +
120
+ `\n---`;
121
+ await fs.appendFile(KNOWN_ISSUES_PATH, block, 'utf8');
122
+ // Invalidate cache so next read picks up the newly appended content
123
+ cache.mtime = 0;
124
+ return { ...issue, id };
125
+ }
@@ -0,0 +1,203 @@
1
+ // src/tools/livelink/index.ts
2
+ // MCP tool implementations for Live Link source/subject management (Phase 27).
3
+ // All tools route commands through PluginBridgeClient to the C++ MCPBridge handlers.
4
+ // Returns structured plugin_not_connected errors when the plugin is absent.
5
+ //
6
+ // Tools registered (Phase 27 — LL-01 through LL-04):
7
+ // ue_list_livelink_sources — List all active Live Link sources with connection status
8
+ // ue_list_livelink_subjects — List all Live Link subjects with roles and enabled state
9
+ // ue_control_livelink_subject — Pause or resume an individual Live Link subject
10
+ // ue_preview_livelink_data — Inspect current frame data for a Live Link subject
11
+ //
12
+ // All tools require MCPBridge plugin (Phase 27 — LL-01 through LL-04).
13
+ import { z } from 'zod';
14
+ import { withKnownIssues } from '../known-issues/middleware.js';
15
+ import { PluginBridgeClient, PluginNotConnectedError } from '../../plugin-bridge/client.js';
16
+ // Module-level bridge instance — injected in tests via exported handler signatures.
17
+ const bridge = new PluginBridgeClient();
18
+ // ---------------------------------------------------------------------------
19
+ // sendOrDisconnect helper
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * Sends a command to the bridge and returns a ToolResult.
23
+ *
24
+ * - On success (response.success true): returns data as JSON text.
25
+ * - On command-level failure (response.success false): returns isError:true with error JSON.
26
+ * - On PluginNotConnectedError: returns isError:true with plugin_not_connected JSON.
27
+ * - On other errors: rethrows (withKnownIssues catches and formats).
28
+ *
29
+ * NOTE: sendCommand() overwrites correlationId with crypto.randomUUID() internally;
30
+ * passing an empty string is safe and correct (per STATE.md decision).
31
+ */
32
+ async function sendOrDisconnect(b, cmd) {
33
+ try {
34
+ const response = await b.sendCommand({ ...cmd, correlationId: '' });
35
+ if (!response.success) {
36
+ return {
37
+ isError: true,
38
+ content: [{ type: 'text', text: JSON.stringify({ error: response.error }) }],
39
+ };
40
+ }
41
+ return {
42
+ content: [{ type: 'text', text: JSON.stringify(response.data ?? {}) }],
43
+ };
44
+ }
45
+ catch (err) {
46
+ if (err instanceof PluginNotConnectedError) {
47
+ return {
48
+ isError: true,
49
+ content: [{ type: 'text', text: JSON.stringify(err.bridgeError) }],
50
+ };
51
+ }
52
+ throw err; // withKnownIssues catches unexpected errors
53
+ }
54
+ }
55
+ // ---------------------------------------------------------------------------
56
+ // Exported handler functions (for direct unit testing via bridge injection)
57
+ // ---------------------------------------------------------------------------
58
+ /**
59
+ * ue_list_livelink_sources handler — satisfies LL-01.
60
+ * Sends livelink.sources to the plugin; returns all active Live Link sources
61
+ * with connection status, source type, and machine name.
62
+ *
63
+ * @param args Tool arguments (no parameters required).
64
+ * @param b PluginBridgeClient instance (defaults to module-level singleton).
65
+ */
66
+ export async function handleListLiveLinkSources(args, b = bridge) {
67
+ // Response data shape: LiveLinkSourcesResult
68
+ void args; // no parameters
69
+ return sendOrDisconnect(b, {
70
+ type: 'livelink.sources',
71
+ payload: {},
72
+ });
73
+ }
74
+ /**
75
+ * ue_list_livelink_subjects handler — satisfies LL-02.
76
+ * Sends livelink.subjects to the plugin; returns all Live Link subjects
77
+ * with their roles (Animation, Transform, Camera, Light) and enabled state.
78
+ *
79
+ * @param args Tool arguments (no parameters required).
80
+ * @param b PluginBridgeClient instance (defaults to module-level singleton).
81
+ */
82
+ export async function handleListLiveLinkSubjects(args, b = bridge) {
83
+ // Response data shape: LiveLinkSubjectsResult
84
+ void args; // no parameters
85
+ return sendOrDisconnect(b, {
86
+ type: 'livelink.subjects',
87
+ payload: {},
88
+ });
89
+ }
90
+ /**
91
+ * ue_control_livelink_subject handler — satisfies LL-03.
92
+ * Sends livelink.control to the plugin; pauses or resumes an individual
93
+ * Live Link subject by toggling its enabled state.
94
+ *
95
+ * @param args Tool arguments: subject_name and enabled flag.
96
+ * @param b PluginBridgeClient instance (defaults to module-level singleton).
97
+ */
98
+ export async function handleControlLiveLinkSubject(args, b = bridge) {
99
+ // Response data shape: LiveLinkControlResult
100
+ return sendOrDisconnect(b, {
101
+ type: 'livelink.control',
102
+ payload: { subject_name: args.subject_name, enabled: args.enabled },
103
+ });
104
+ }
105
+ /**
106
+ * ue_preview_livelink_data handler — satisfies LL-04.
107
+ * Sends livelink.preview to the plugin; returns the current frame data for
108
+ * a Live Link subject (transform, camera, or animation bone data).
109
+ *
110
+ * @param args Tool arguments: subject_name.
111
+ * @param b PluginBridgeClient instance (defaults to module-level singleton).
112
+ */
113
+ export async function handlePreviewLiveLinkData(args, b = bridge) {
114
+ // Response data shape: LiveLinkPreviewResult
115
+ return sendOrDisconnect(b, {
116
+ type: 'livelink.preview',
117
+ payload: { subject_name: args.subject_name },
118
+ });
119
+ }
120
+ // ---------------------------------------------------------------------------
121
+ // registerLiveLinkTools
122
+ // ---------------------------------------------------------------------------
123
+ /**
124
+ * Register UE Live Link source/subject management tools on the MCP server.
125
+ *
126
+ * All tools in this domain require the MCPBridge editor plugin.
127
+ * When the plugin is not connected, each handler returns:
128
+ * { isError: true, content: [{ type: 'text', text: <plugin_not_connected JSON> }] }
129
+ *
130
+ * Tools registered (Phase 27):
131
+ * ue_list_livelink_sources — LL-01: List Live Link sources with status
132
+ * ue_list_livelink_subjects — LL-02: List subjects with roles and enabled state
133
+ * ue_control_livelink_subject — LL-03: Pause or resume a Live Link subject
134
+ * ue_preview_livelink_data — LL-04: Inspect current frame data for a subject
135
+ *
136
+ * @param server The McpServer instance to register tools on.
137
+ * @param _bridge Optional PluginBridgeClient for testing (not used directly — handlers
138
+ * accept bridge injection via their exported function signatures).
139
+ */
140
+ export function registerLiveLinkTools(server, _bridge) {
141
+ const b = _bridge ?? new PluginBridgeClient();
142
+ // --------------------------------------------------------------------------
143
+ // ue_list_livelink_sources (LL-01)
144
+ // --------------------------------------------------------------------------
145
+ server.registerTool('ue_list_livelink_sources', {
146
+ title: 'List Live Link Sources',
147
+ description: '[requires_plugin] List all active Live Link sources with connection status, source type, and machine name. Returns informative message if Live Link plugin is not enabled.',
148
+ inputSchema: z.object({}),
149
+ annotations: {
150
+ readOnlyHint: true,
151
+ destructiveHint: false,
152
+ },
153
+ }, withKnownIssues('ue_list_livelink_sources', async (args) => handleListLiveLinkSources(args, b)));
154
+ // --------------------------------------------------------------------------
155
+ // ue_list_livelink_subjects (LL-02)
156
+ // --------------------------------------------------------------------------
157
+ server.registerTool('ue_list_livelink_subjects', {
158
+ title: 'List Live Link Subjects',
159
+ description: '[requires_plugin] List all Live Link subjects with their roles (Animation, Transform, Camera, Light) and enabled/disabled state.',
160
+ inputSchema: z.object({}),
161
+ annotations: {
162
+ readOnlyHint: true,
163
+ destructiveHint: false,
164
+ },
165
+ }, withKnownIssues('ue_list_livelink_subjects', async (args) => handleListLiveLinkSubjects(args, b)));
166
+ // --------------------------------------------------------------------------
167
+ // ue_control_livelink_subject (LL-03)
168
+ // --------------------------------------------------------------------------
169
+ server.registerTool('ue_control_livelink_subject', {
170
+ title: 'Control Live Link Subject',
171
+ description: '[requires_plugin] Pause or resume an individual Live Link subject by toggling its enabled state.',
172
+ inputSchema: z.object({
173
+ subject_name: z
174
+ .string()
175
+ .min(1)
176
+ .describe("Name of the Live Link subject to control, e.g. 'MyMocapActor'"),
177
+ enabled: z
178
+ .boolean()
179
+ .describe('true to resume/enable the subject, false to pause/disable it'),
180
+ }),
181
+ annotations: {
182
+ readOnlyHint: false,
183
+ destructiveHint: false,
184
+ },
185
+ }, withKnownIssues('ue_control_livelink_subject', async (args) => handleControlLiveLinkSubject(args, b)));
186
+ // --------------------------------------------------------------------------
187
+ // ue_preview_livelink_data (LL-04)
188
+ // --------------------------------------------------------------------------
189
+ server.registerTool('ue_preview_livelink_data', {
190
+ title: 'Preview Live Link Data',
191
+ description: '[requires_plugin] Inspect the current frame data for a Live Link subject. Returns role-specific data: transform (location/rotation/scale), camera (FOV/aperture/focus), or animation (bone transforms, capped at 10 bones).',
192
+ inputSchema: z.object({
193
+ subject_name: z
194
+ .string()
195
+ .min(1)
196
+ .describe("Name of the Live Link subject to preview, e.g. 'MyCamera'"),
197
+ }),
198
+ annotations: {
199
+ readOnlyHint: true,
200
+ destructiveHint: false,
201
+ },
202
+ }, withKnownIssues('ue_preview_livelink_data', async (args) => handlePreviewLiveLinkData(args, b)));
203
+ }
@@ -0,0 +1,4 @@
1
+ // src/tools/livelink/types.ts
2
+ // TypeScript result interfaces for the four Live Link MCP tools (Phase 27).
3
+ // These interfaces mirror the JSON shapes returned by the C++ handlers in Plan 27-01.
4
+ export {};
@@ -0,0 +1,190 @@
1
+ // src/tools/material/index.ts
2
+ // MCP tool implementations for material parameter inspection and material instance creation (Phase 15).
3
+ // All tools route commands through PluginBridgeClient to the C++ MCPBridge plugin.
4
+ // Returns structured plugin_not_connected errors when the plugin is absent.
5
+ import { z } from 'zod';
6
+ import { withKnownIssues } from '../known-issues/middleware.js';
7
+ import { PluginBridgeClient, PluginNotConnectedError } from '../../plugin-bridge/client.js';
8
+ // ---------------------------------------------------------------------------
9
+ // sendOrDisconnect helper
10
+ // ---------------------------------------------------------------------------
11
+ /**
12
+ * Sends a command to the bridge and returns a ToolResult.
13
+ *
14
+ * - On success (response.success true): returns data as JSON text.
15
+ * - On command-level failure (response.success false): returns isError:true with error JSON.
16
+ * - On PluginNotConnectedError: returns isError:true with plugin_not_connected JSON.
17
+ * - On other errors: rethrows (withKnownIssues catches and formats).
18
+ *
19
+ * NOTE: sendCommand() overwrites correlationId with crypto.randomUUID() internally;
20
+ * passing an empty string is safe and correct (per STATE.md decision).
21
+ */
22
+ async function sendOrDisconnect(bridge, cmd) {
23
+ try {
24
+ const response = await bridge.sendCommand({ ...cmd, correlationId: '' });
25
+ if (!response.success) {
26
+ return {
27
+ isError: true,
28
+ content: [{ type: 'text', text: JSON.stringify({ error: response.error }) }],
29
+ };
30
+ }
31
+ return {
32
+ content: [{ type: 'text', text: JSON.stringify(response.data ?? {}) }],
33
+ };
34
+ }
35
+ catch (err) {
36
+ if (err instanceof PluginNotConnectedError) {
37
+ return {
38
+ isError: true,
39
+ content: [{ type: 'text', text: JSON.stringify(err.bridgeError) }],
40
+ };
41
+ }
42
+ throw err; // withKnownIssues catches unexpected errors
43
+ }
44
+ }
45
+ // ---------------------------------------------------------------------------
46
+ // registerMaterialTools
47
+ // ---------------------------------------------------------------------------
48
+ /**
49
+ * Register UE material parameter inspection and material instance tools on the MCP server.
50
+ *
51
+ * All tools in this domain require the MCPBridge editor plugin.
52
+ * When the plugin is not connected, each handler returns:
53
+ * { isError: true, content: [{ type: 'text', text: <plugin_not_connected JSON> }] }
54
+ *
55
+ * Tools registered (Phase 15):
56
+ * ue_material_params — Read all parameters from a material or material instance
57
+ * ue_create_material_instance — Create a new Material Instance Constant asset from a parent material
58
+ * ue_set_material_param — Set a scalar, vector, or texture parameter on a material instance
59
+ * ue_actor_materials — List all material asset paths used by an actor or static mesh
60
+ *
61
+ * @param server The McpServer instance to register tools on.
62
+ * @param bridge Optional PluginBridgeClient for testing (defaults to a new instance).
63
+ */
64
+ export function registerMaterialTools(server, bridge) {
65
+ const _bridge = bridge ?? new PluginBridgeClient();
66
+ // --------------------------------------------------------------------------
67
+ // ue_material_params
68
+ // --------------------------------------------------------------------------
69
+ server.registerTool('ue_material_params', {
70
+ title: 'Read Material Parameters',
71
+ description: '[requires_plugin] Read all scalar, vector, and texture parameters from a material asset or material instance. Returns parameter name, type, current value, and default value.',
72
+ inputSchema: z.object({
73
+ asset_path: z
74
+ .string()
75
+ .describe('UE asset path to the material or material instance, e.g. "/Game/Materials/M_Rock"'),
76
+ }),
77
+ annotations: {
78
+ readOnlyHint: true,
79
+ destructiveHint: false,
80
+ },
81
+ }, withKnownIssues('ue_material_params', async (args) => {
82
+ return sendOrDisconnect(_bridge, {
83
+ type: 'material.params',
84
+ payload: { asset_path: args.asset_path },
85
+ });
86
+ }));
87
+ // --------------------------------------------------------------------------
88
+ // ue_create_material_instance
89
+ // --------------------------------------------------------------------------
90
+ server.registerTool('ue_create_material_instance', {
91
+ title: 'Create Material Instance',
92
+ description: '[requires_plugin] Create a new Material Instance Constant asset from a parent material. The instance is saved to disk as a persistent UE asset.',
93
+ inputSchema: z.object({
94
+ parent_path: z
95
+ .string()
96
+ .describe('UE asset path of the parent material, e.g. "/Game/Materials/M_Rock"'),
97
+ instance_path: z
98
+ .string()
99
+ .describe('Package path (directory) for the new instance, e.g. "/Game/Materials". Must start with /Game/.'),
100
+ instance_name: z
101
+ .string()
102
+ .describe('Asset name for the new instance, e.g. "MI_Rock_Red"'),
103
+ }),
104
+ annotations: {
105
+ readOnlyHint: false,
106
+ destructiveHint: false,
107
+ },
108
+ }, withKnownIssues('ue_create_material_instance', async (args) => {
109
+ return sendOrDisconnect(_bridge, {
110
+ type: 'material.createInstance',
111
+ payload: {
112
+ parent_path: args.parent_path,
113
+ instance_path: args.instance_path,
114
+ instance_name: args.instance_name,
115
+ },
116
+ });
117
+ }));
118
+ // --------------------------------------------------------------------------
119
+ // ue_set_material_param
120
+ // --------------------------------------------------------------------------
121
+ server.registerTool('ue_set_material_param', {
122
+ title: 'Set Material Instance Parameter',
123
+ description: '[requires_plugin] Set a scalar, vector, or texture parameter override on a Material Instance Constant. The value type must match param_type.',
124
+ inputSchema: z.object({
125
+ asset_path: z
126
+ .string()
127
+ .describe('UE asset path to the Material Instance Constant'),
128
+ param_name: z
129
+ .string()
130
+ .describe('Parameter name as defined in the parent material'),
131
+ param_type: z
132
+ .enum(['scalar', 'vector', 'texture'])
133
+ .describe('Parameter type: scalar (float), vector (RGBA), or texture (asset path string)'),
134
+ value: z
135
+ .union([
136
+ z.number(),
137
+ z.object({
138
+ r: z.number(),
139
+ g: z.number(),
140
+ b: z.number(),
141
+ a: z.number().optional(),
142
+ }),
143
+ z.string(),
144
+ ])
145
+ .describe('Parameter value: number for scalar, {r,g,b,a} object for vector, asset path string for texture'),
146
+ }),
147
+ annotations: {
148
+ readOnlyHint: false,
149
+ destructiveHint: false,
150
+ },
151
+ }, withKnownIssues('ue_set_material_param', async (args) => {
152
+ return sendOrDisconnect(_bridge, {
153
+ type: 'material.setParam',
154
+ payload: {
155
+ asset_path: args.asset_path,
156
+ param_name: args.param_name,
157
+ param_type: args.param_type,
158
+ value: args.value,
159
+ },
160
+ });
161
+ }));
162
+ // --------------------------------------------------------------------------
163
+ // ue_actor_materials
164
+ // --------------------------------------------------------------------------
165
+ server.registerTool('ue_actor_materials', {
166
+ title: 'List Actor or Mesh Materials',
167
+ description: '[requires_plugin] List all material asset paths used by an actor in the open level or by a Static Mesh asset. Provide either actor_label or asset_path.',
168
+ inputSchema: z.object({
169
+ actor_label: z
170
+ .string()
171
+ .optional()
172
+ .describe('Label of the actor in the open level (e.g. "StaticMeshActor_0"). Provide this OR asset_path.'),
173
+ asset_path: z
174
+ .string()
175
+ .optional()
176
+ .describe('UE asset path to a Static Mesh asset (e.g. "/Game/Meshes/SM_Rock"). Provide this OR actor_label.'),
177
+ }),
178
+ annotations: {
179
+ readOnlyHint: true,
180
+ destructiveHint: false,
181
+ },
182
+ }, withKnownIssues('ue_actor_materials', async (args) => {
183
+ const payload = {};
184
+ if (args.actor_label)
185
+ payload['actor_label'] = args.actor_label;
186
+ if (args.asset_path)
187
+ payload['asset_path'] = args.asset_path;
188
+ return sendOrDisconnect(_bridge, { type: 'material.actorMaterials', payload });
189
+ }));
190
+ }