Skip to content

Commit 6109170

Browse files
📝 Add docstrings to FileSystemCapablity
Docstrings generation was requested by @shahzadgamedev. * #6 (comment) The following files were modified: * `Editor/UI/MCPDebugWindow.cs` * `mcpServer/build/filesystemTools.js` * `mcpServer/build/toolDefinitions.js` * `mcpServer/src/filesystemTools.ts` * `mcpServer/src/toolDefinitions.ts`
1 parent b86aef2 commit 6109170

5 files changed

Lines changed: 349 additions & 12 deletions

File tree

‎Editor/UI/MCPDebugWindow.cs‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ public static void ShowWindow()
5050
wnd.minSize = new Vector2(400, 500);
5151
}
5252

53+
/// <summary>
54+
/// Initializes the debug window UI by loading visual assets, configuring UI elements, and binding event callbacks.
55+
/// </summary>
56+
/// <remarks>
57+
/// This method clones the UXML layout into the window's root element and applies styling from the USS asset.
58+
/// It queries essential UI components such as connection status labels, buttons, toggles, and text fields, and sets
59+
/// a default server port value ("5010") if the port field is empty. Additionally, it binds events for connection,
60+
/// disconnection, and auto-reconnect functionalities, sets up logging toggles, updates the UI to reflect the current state,
61+
/// and registers an editor update callback. If either the UXML or USS asset is missing, an error is logged for debugging purposes.
62+
/// </remarks>
5363
public void CreateGUI()
5464
{
5565
VisualElement root = rootVisualElement;
@@ -106,6 +116,13 @@ public void CreateGUI()
106116
EditorApplication.update += OnEditorUpdate;
107117
}
108118

119+
/// <summary>
120+
/// Creates a fallback user interface for the MCP Debug Window when the UXML layout is unavailable.
121+
/// </summary>
122+
/// <remarks>
123+
/// This method builds a basic UI on the provided root element that includes a notification label about the missing UXML, a text field for entering the server port (default value "5010"), buttons to initiate connection and disconnection, a toggle for auto-reconnect functionality, and a label to display connection status.
124+
/// </remarks>
125+
/// <param name="root">The container to which the fallback UI elements are added.</param>
109126
private void CreateFallbackUI(VisualElement root)
110127
{
111128
// Create a simple fallback UI if UXML fails to load
@@ -232,6 +249,16 @@ private void OnLoggingToggleChanged(string componentName, bool enabled)
232249
MCPLogger.SetComponentLoggingEnabled(componentName, enabled);
233250
}
234251

252+
/// <summary>
253+
/// Initiates a connection attempt when the connect button is clicked.
254+
/// </summary>
255+
/// <remarks>
256+
/// Retrieves and validates the server port from the input field, defaulting to "5010" if empty,
257+
/// and constructs a WebSocket URI using localhost and the specified port. If a MCPConnectionManager
258+
/// instance is found, its internal server URI is updated via reflection. Depending on the MCPManager’s
259+
/// initialization state, the method either retries an existing connection or initializes a new connection,
260+
/// updating the UI state accordingly. Displays a dialog for invalid port input or connection errors.
261+
/// </remarks>
235262
private void OnConnectClicked()
236263
{
237264
// Always use localhost for the WebSocket URL

‎mcpServer/build/filesystemTools.js‎

Lines changed: 161 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,20 @@ import { createTwoFilesPatch } from 'diff';
44
import { minimatch } from 'minimatch';
55
import { ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, EditFileArgsSchema, ListDirectoryArgsSchema, DirectoryTreeArgsSchema, SearchFilesArgsSchema, GetFileInfoArgsSchema, FindAssetsByTypeArgsSchema, ListScriptsArgsSchema } from './toolDefinitions.js';
66
// Helper functions
7-
// Updated validatePath function to properly handle empty paths
7+
/**
8+
* Validates and normalizes a file path, ensuring it remains within the specified asset root.
9+
*
10+
* The function first treats empty or quote-only paths as a request for the asset root. It then cleans the path
11+
* by removing extraneous quotes and escape characters, normalizes it, and handles relative paths by joining them with
12+
* the asset root. For absolute paths that do not start with the asset root, it attempts to resolve them as relative paths.
13+
* If the final resolved path escapes the asset root directory, an error is thrown.
14+
*
15+
* @param {string} requestedPath - The user-provided file path, which may include extraneous characters or be empty.
16+
* @param {string} assetRootPath - The base directory that the resolved path must remain within.
17+
* @returns {Promise<string>} A promise that resolves to the validated and normalized absolute file path.
18+
*
19+
* @throws {Error} If the resolved path is outside the asset root directory.
20+
*/
821
async function validatePath(requestedPath, assetRootPath) {
922
// If path is empty or just quotes, use the asset root path directly
1023
if (!requestedPath || requestedPath.trim() === '' || requestedPath.trim() === '""' || requestedPath.trim() === "''") {
@@ -49,6 +62,25 @@ async function validatePath(requestedPath, assetRootPath) {
4962
}
5063
return resolvedPath;
5164
}
65+
/**
66+
* Retrieves metadata for the specified file.
67+
*
68+
* This asynchronous function obtains file statistics including size, creation,
69+
* modification, and access times, as well as its permissions. It also indicates whether
70+
* the provided path refers to a file or a directory.
71+
*
72+
* @param {string} filePath - The path to the file or directory.
73+
* @returns {Promise<Object>} An object containing:
74+
* - size {number}: The file size in bytes.
75+
* - created {Date}: The file's creation time.
76+
* - modified {Date}: The last modification time.
77+
* - accessed {Date}: The last access time.
78+
* - isDirectory {boolean}: True if the path is a directory.
79+
* - isFile {boolean}: True if the path is a file.
80+
* - permissions {string}: The file's permissions as the last three octal digits (e.g., "644").
81+
*
82+
* @throws {Error} If retrieving file statistics fails, such as when the file does not exist.
83+
*/
5284
async function getFileStats(filePath) {
5385
const stats = await fs.stat(filePath);
5486
return {
@@ -61,6 +93,21 @@ async function getFileStats(filePath) {
6193
permissions: stats.mode.toString(8).slice(-3),
6294
};
6395
}
96+
/**
97+
* Recursively searches for files and directories whose names include the specified pattern,
98+
* while excluding paths that match any provided glob patterns.
99+
*
100+
* Starting at the given root directory, this asynchronous function traverses the directory tree and:
101+
* - Computes the relative path for each entry to check against the exclusion patterns.
102+
* - Performs a case-insensitive check to see if the entry's name contains the specified search pattern.
103+
* - Recursively explores directories that are not excluded.
104+
* Any errors encountered during traversal are silently ignored to allow the search to continue.
105+
*
106+
* @param {string} rootPath - The directory to begin the search.
107+
* @param {string} pattern - The substring to match within file and directory names (case-insensitive).
108+
* @param {string[]} [excludePatterns=[]] - Optional array of glob patterns; paths matching these patterns are skipped.
109+
* @returns {Promise<string[]>} A promise that resolves to an array of paths for entries that match the search pattern.
110+
*/
64111
async function searchFiles(rootPath, pattern, excludePatterns = []) {
65112
const results = [];
66113
async function search(currentPath) {
@@ -93,15 +140,55 @@ async function searchFiles(rootPath, pattern, excludePatterns = []) {
93140
await search(rootPath);
94141
return results;
95142
}
143+
/**
144+
* Normalizes Windows-style carriage return and newline sequences to Unix-style newlines.
145+
*
146+
* Replaces all occurrences of "\r\n" in the provided text with "\n" to ensure consistent line endings.
147+
*
148+
* @param {string} text - The text to normalize.
149+
* @returns {string} The text with normalized Unix-style line endings.
150+
*/
96151
function normalizeLineEndings(text) {
97152
return text.replace(/\r\n/g, '\n');
98153
}
154+
/**
155+
* Generates a unified diff patch showing the differences between the original and new file content.
156+
*
157+
* This function first normalizes line endings in both inputs to guarantee a consistent diff format,
158+
* then creates a unified diff patch using the provided file identifier for header annotations.
159+
*
160+
* @param {string} originalContent - The original file content.
161+
* @param {string} newContent - The updated file content.
162+
* @param {string} [filepath='file'] - The file identifier used in the diff header.
163+
* @returns {string} A unified diff string representing the changes between the two versions of content.
164+
*/
99165
function createUnifiedDiff(originalContent, newContent, filepath = 'file') {
100166
// Ensure consistent line endings for diff
101167
const normalizedOriginal = normalizeLineEndings(originalContent);
102168
const normalizedNew = normalizeLineEndings(newContent);
103169
return createTwoFilesPatch(filepath, filepath, normalizedOriginal, normalizedNew, 'original', 'modified');
104170
}
171+
/**
172+
* Applies a series of text edits to a file and returns a formatted unified diff of the changes.
173+
*
174+
* This asynchronous function reads the content from the specified file, normalizes its line endings,
175+
* and sequentially applies each edit. Each edit specifies an "oldText" to search for and a "newText"
176+
* to substitute. The function first attempts an exact match; if not found, it then performs a
177+
* flexible, line-by-line replacement that preserves the file's indentation. If an edit's old text
178+
* cannot be found, an error is thrown.
179+
*
180+
* After applying all edits, a unified diff is generated to represent the changes. The diff is
181+
* formatted within a code block that adapts the number of backticks based on its content. When
182+
* dryRun is false (the default), the modified content is written back to the file; otherwise, no
183+
* file write occurs.
184+
*
185+
* @param {string} filePath - The path to the file to be edited.
186+
* @param {Array<{oldText: string, newText: string}>} edits - An array of edits describing the text to replace and its replacement.
187+
* @param {boolean} [dryRun=false] - If true, simulates the edits without saving changes to the file.
188+
* @returns {Promise<string>} A formatted unified diff of the changes applied.
189+
*
190+
* @throws {Error} If an edit's old text cannot be located in the file content.
191+
*/
105192
async function applyFileEdits(filePath, edits, dryRun = false) {
106193
// Read file content and normalize line endings
107194
const content = normalizeLineEndings(await fs.readFile(filePath, 'utf-8'));
@@ -164,6 +251,20 @@ async function applyFileEdits(filePath, edits, dryRun = false) {
164251
}
165252
return formattedDiff;
166253
}
254+
/**
255+
* Recursively constructs a tree representation of the directory structure.
256+
*
257+
* This asynchronous function reads the contents of the directory at the specified
258+
* path, validates it against the asset root, and recursively processes subdirectories
259+
* up to the specified maximum depth. When the maximum depth is reached, it returns a
260+
* stub entry to indicate that further subdirectories exist.
261+
*
262+
* @param {string} currentPath - The starting directory path to build the tree from, typically relative to the asset root.
263+
* @param {string} assetRootPath - The root directory used to validate and resolve the current path.
264+
* @param {number} [maxDepth=5] - The maximum depth the function will traverse.
265+
* @param {number} [currentDepth=0] - The current depth level during recursion (used internally).
266+
* @returns {Promise<Array<Object>>} A promise that resolves to an array representing the directory tree. Each object includes a "name" and a "type" (either "file" or "directory"), and directory objects may include a "children" property with nested entries.
267+
*/
167268
async function buildDirectoryTree(currentPath, assetRootPath, maxDepth = 5, currentDepth = 0) {
168269
if (currentDepth >= maxDepth) {
169270
return [{ name: "...", type: "directory" }];
@@ -184,7 +285,16 @@ async function buildDirectoryTree(currentPath, assetRootPath, maxDepth = 5, curr
184285
}
185286
return result;
186287
}
187-
// Function to recognize Unity asset types based on file extension
288+
/**
289+
* Determines the Unity asset type based on the file extension.
290+
*
291+
* Extracts the file extension from the provided file path, converts it to lower case,
292+
* and returns a matching asset type according to predefined mapping. If the extension is not recognized,
293+
* the function returns "Other".
294+
*
295+
* @param {string} filePath - The file path from which the asset type is derived.
296+
* @returns {string} The Unity asset type (e.g., "Scene", "Prefab", "Texture") or "Other" if unrecognized.
297+
*/
188298
function getUnityAssetType(filePath) {
189299
const ext = path.extname(filePath).toLowerCase();
190300
// Common Unity asset types
@@ -229,7 +339,24 @@ function getUnityAssetType(filePath) {
229339
};
230340
return assetTypes[ext] || 'Other';
231341
}
232-
// Handler function to process filesystem tools
342+
/**
343+
* Processes filesystem tool commands by validating input arguments, normalizing file paths,
344+
* and executing the corresponding filesystem operation.
345+
*
346+
* This asynchronous function supports various commands such as reading files, writing files,
347+
* editing file contents, listing directories, constructing directory trees, searching files,
348+
* retrieving file information, finding assets by type, and listing C# scripts. It validates
349+
* command-specific arguments using predefined schemas and ensures that file paths are confined
350+
* within the project directory. When a command is unrecognized or arguments are invalid, it
351+
* returns an error response.
352+
*
353+
* @param {string} name - Identifier of the filesystem tool command (e.g., "read_file", "write_file").
354+
* @param {*} args - Command-specific arguments whose structure is validated with predefined schemas.
355+
* @param {string} projectPath - Root directory used to resolve and validate file paths.
356+
* @returns {Promise<Object>} A promise that resolves to an object containing:
357+
* - content: An array of objects with 'type' and 'text' properties representing the response message.
358+
* - isError: (Optional) A boolean flag indicating whether an error occurred.
359+
*/
233360
export async function handleFilesystemTool(name, args, projectPath) {
234361
switch (name) {
235362
case "read_file": {
@@ -392,7 +519,19 @@ export async function handleFilesystemTool(name, args, projectPath) {
392519
const validPath = await validatePath(parsed.data.searchPath, projectPath);
393520
const results = [];
394521
const targetType = parsed.data.assetType.toLowerCase();
395-
// Recursive function to search for assets
522+
/**
523+
* Recursively searches a directory for Unity assets matching a specific type.
524+
*
525+
* This asynchronous function traverses the directory tree starting at the given directory.
526+
* For each file, it determines its Unity asset type using the external getUnityAssetType function.
527+
* If the asset type (in lowercase) matches the externally defined targetType, the file path is added to
528+
* the global results array.
529+
*
530+
* @param {string} dir - The directory path to search.
531+
*
532+
* @remark This function relies on external variables: targetType (a string representing the desired asset type)
533+
* and results (an array where matching asset paths are collected).
534+
*/
396535
async function searchAssets(dir) {
397536
const entries = await fs.readdir(dir, { withFileTypes: true });
398537
for (const entry of entries) {
@@ -428,7 +567,17 @@ export async function handleFilesystemTool(name, args, projectPath) {
428567
}
429568
const validPath = await validatePath(parsed.data.path, projectPath);
430569
const scripts = [];
431-
// Recursive function to find C# scripts
570+
/**
571+
* Recursively finds all C# script files (.cs) within the specified directory.
572+
*
573+
* This asynchronous function traverses the given directory and its subdirectories.
574+
* When it encounters a file with a ".cs" extension, it appends an object containing
575+
* the file's full path and name to the global `scripts` array.
576+
*
577+
* @param {string} dir - The directory path to begin the search.
578+
*
579+
* @throws {Error} If reading the directory fails.
580+
*/
432581
async function findScripts(dir) {
433582
const entries = await fs.readdir(dir, { withFileTypes: true });
434583
for (const entry of entries) {
@@ -464,7 +613,13 @@ export async function handleFilesystemTool(name, args, projectPath) {
464613
}
465614
// Register filesystem tools with the MCP server
466615
// This function is now only a stub that doesn't actually do anything
467-
// since all tools are registered in toolDefinitions.ts
616+
/**
617+
* Deprecated function for registering filesystem tools.
618+
*
619+
* This function now only logs a message indicating that filesystem tool registration has moved to toolDefinitions.ts.
620+
*
621+
* @deprecated Filesystem tools registration is now performed in toolDefinitions.ts.
622+
*/
468623
export function registerFilesystemTools(server, wsHandler) {
469624
// This function is now deprecated as tool registration has moved to toolDefinitions.ts
470625
console.log("Filesystem tools are now registered in toolDefinitions.ts");

‎mcpServer/build/toolDefinitions.js‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@ export const FindAssetsByTypeArgsSchema = z.object({
4747
export const ListScriptsArgsSchema = z.object({
4848
path: z.string().optional().default("Scripts").describe('Path to look for scripts in. Can be absolute or relative to Unity project Assets folder. If empty, defaults to the Assets/Scripts folder.'),
4949
});
50+
/**
51+
* Registers available Unity Editor and filesystem tools with the MCP server and configures tool request handling.
52+
*
53+
* This function determines the project root from the UNITY_PROJECT_PATH environment variable (or defaults
54+
* to the current working directory) and registers a set of tools—each defined with its name, description,
55+
* category, tags, and input schema—for both Unity Editor operations and filesystem interactions.
56+
* It also sets up a request handler that routes tool invocation requests based on the tool name,
57+
* handling special cases such as connection verification, filesystem operations (via a dedicated handler),
58+
* and Unity-specific commands. If the Unity Editor is not connected when required, it throws an McpError.
59+
*
60+
* @remark Tools like "verify_connection" are processed even if the Unity Editor is not connected, while other
61+
* Unity-specific tools require an active connection and perform additional error handling.
62+
*/
5063
export function registerTools(server, wsHandler) {
5164
// Determine project root path from environment variable or default to parent of Assets folder
5265
const projectPath = process.env.UNITY_PROJECT_PATH || path.resolve(process.cwd());

0 commit comments

Comments
 (0)