From 61091702ad66d41cbac0a5d74f48b2ff1c853fb2 Mon Sep 17 00:00:00 2001
From: "coderabbitai[bot]"
<136622811+coderabbitai[bot]@users.noreply.github.com>
Date: Thu, 20 Mar 2025 05:41:28 +0000
Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`FileSys?=
=?UTF-8?q?temCapablity`?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Docstrings generation was requested by @shahzadgamedev.
* https://github.com/quazaai/UnityMCPIntegration/pull/6#issuecomment-2739200113
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`
---
Editor/UI/MCPDebugWindow.cs | 27 +++++
mcpServer/build/filesystemTools.js | 167 +++++++++++++++++++++++++++--
mcpServer/build/toolDefinitions.js | 13 +++
mcpServer/src/filesystemTools.ts | 142 ++++++++++++++++++++++--
mcpServer/src/toolDefinitions.ts | 12 +++
5 files changed, 349 insertions(+), 12 deletions(-)
diff --git a/Editor/UI/MCPDebugWindow.cs b/Editor/UI/MCPDebugWindow.cs
index 196fca6..0c01994 100644
--- a/Editor/UI/MCPDebugWindow.cs
+++ b/Editor/UI/MCPDebugWindow.cs
@@ -50,6 +50,16 @@ public static void ShowWindow()
wnd.minSize = new Vector2(400, 500);
}
+ ///
+ /// Initializes the debug window UI by loading visual assets, configuring UI elements, and binding event callbacks.
+ ///
+ ///
+ /// This method clones the UXML layout into the window's root element and applies styling from the USS asset.
+ /// It queries essential UI components such as connection status labels, buttons, toggles, and text fields, and sets
+ /// a default server port value ("5010") if the port field is empty. Additionally, it binds events for connection,
+ /// disconnection, and auto-reconnect functionalities, sets up logging toggles, updates the UI to reflect the current state,
+ /// and registers an editor update callback. If either the UXML or USS asset is missing, an error is logged for debugging purposes.
+ ///
public void CreateGUI()
{
VisualElement root = rootVisualElement;
@@ -106,6 +116,13 @@ public void CreateGUI()
EditorApplication.update += OnEditorUpdate;
}
+ ///
+ /// Creates a fallback user interface for the MCP Debug Window when the UXML layout is unavailable.
+ ///
+ ///
+ /// 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.
+ ///
+ /// The container to which the fallback UI elements are added.
private void CreateFallbackUI(VisualElement root)
{
// Create a simple fallback UI if UXML fails to load
@@ -232,6 +249,16 @@ private void OnLoggingToggleChanged(string componentName, bool enabled)
MCPLogger.SetComponentLoggingEnabled(componentName, enabled);
}
+ ///
+ /// Initiates a connection attempt when the connect button is clicked.
+ ///
+ ///
+ /// Retrieves and validates the server port from the input field, defaulting to "5010" if empty,
+ /// and constructs a WebSocket URI using localhost and the specified port. If a MCPConnectionManager
+ /// instance is found, its internal server URI is updated via reflection. Depending on the MCPManager’s
+ /// initialization state, the method either retries an existing connection or initializes a new connection,
+ /// updating the UI state accordingly. Displays a dialog for invalid port input or connection errors.
+ ///
private void OnConnectClicked()
{
// Always use localhost for the WebSocket URL
diff --git a/mcpServer/build/filesystemTools.js b/mcpServer/build/filesystemTools.js
index b5eb77d..5adbfda 100644
--- a/mcpServer/build/filesystemTools.js
+++ b/mcpServer/build/filesystemTools.js
@@ -4,7 +4,20 @@ import { createTwoFilesPatch } from 'diff';
import { minimatch } from 'minimatch';
import { ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, EditFileArgsSchema, ListDirectoryArgsSchema, DirectoryTreeArgsSchema, SearchFilesArgsSchema, GetFileInfoArgsSchema, FindAssetsByTypeArgsSchema, ListScriptsArgsSchema } from './toolDefinitions.js';
// Helper functions
-// Updated validatePath function to properly handle empty paths
+/**
+ * Validates and normalizes a file path, ensuring it remains within the specified asset root.
+ *
+ * The function first treats empty or quote-only paths as a request for the asset root. It then cleans the path
+ * by removing extraneous quotes and escape characters, normalizes it, and handles relative paths by joining them with
+ * the asset root. For absolute paths that do not start with the asset root, it attempts to resolve them as relative paths.
+ * If the final resolved path escapes the asset root directory, an error is thrown.
+ *
+ * @param {string} requestedPath - The user-provided file path, which may include extraneous characters or be empty.
+ * @param {string} assetRootPath - The base directory that the resolved path must remain within.
+ * @returns {Promise} A promise that resolves to the validated and normalized absolute file path.
+ *
+ * @throws {Error} If the resolved path is outside the asset root directory.
+ */
async function validatePath(requestedPath, assetRootPath) {
// If path is empty or just quotes, use the asset root path directly
if (!requestedPath || requestedPath.trim() === '' || requestedPath.trim() === '""' || requestedPath.trim() === "''") {
@@ -49,6 +62,25 @@ async function validatePath(requestedPath, assetRootPath) {
}
return resolvedPath;
}
+/**
+ * Retrieves metadata for the specified file.
+ *
+ * This asynchronous function obtains file statistics including size, creation,
+ * modification, and access times, as well as its permissions. It also indicates whether
+ * the provided path refers to a file or a directory.
+ *
+ * @param {string} filePath - The path to the file or directory.
+ * @returns {Promise} An object containing:
+ * - size {number}: The file size in bytes.
+ * - created {Date}: The file's creation time.
+ * - modified {Date}: The last modification time.
+ * - accessed {Date}: The last access time.
+ * - isDirectory {boolean}: True if the path is a directory.
+ * - isFile {boolean}: True if the path is a file.
+ * - permissions {string}: The file's permissions as the last three octal digits (e.g., "644").
+ *
+ * @throws {Error} If retrieving file statistics fails, such as when the file does not exist.
+ */
async function getFileStats(filePath) {
const stats = await fs.stat(filePath);
return {
@@ -61,6 +93,21 @@ async function getFileStats(filePath) {
permissions: stats.mode.toString(8).slice(-3),
};
}
+/**
+ * Recursively searches for files and directories whose names include the specified pattern,
+ * while excluding paths that match any provided glob patterns.
+ *
+ * Starting at the given root directory, this asynchronous function traverses the directory tree and:
+ * - Computes the relative path for each entry to check against the exclusion patterns.
+ * - Performs a case-insensitive check to see if the entry's name contains the specified search pattern.
+ * - Recursively explores directories that are not excluded.
+ * Any errors encountered during traversal are silently ignored to allow the search to continue.
+ *
+ * @param {string} rootPath - The directory to begin the search.
+ * @param {string} pattern - The substring to match within file and directory names (case-insensitive).
+ * @param {string[]} [excludePatterns=[]] - Optional array of glob patterns; paths matching these patterns are skipped.
+ * @returns {Promise} A promise that resolves to an array of paths for entries that match the search pattern.
+ */
async function searchFiles(rootPath, pattern, excludePatterns = []) {
const results = [];
async function search(currentPath) {
@@ -93,15 +140,55 @@ async function searchFiles(rootPath, pattern, excludePatterns = []) {
await search(rootPath);
return results;
}
+/**
+ * Normalizes Windows-style carriage return and newline sequences to Unix-style newlines.
+ *
+ * Replaces all occurrences of "\r\n" in the provided text with "\n" to ensure consistent line endings.
+ *
+ * @param {string} text - The text to normalize.
+ * @returns {string} The text with normalized Unix-style line endings.
+ */
function normalizeLineEndings(text) {
return text.replace(/\r\n/g, '\n');
}
+/**
+ * Generates a unified diff patch showing the differences between the original and new file content.
+ *
+ * This function first normalizes line endings in both inputs to guarantee a consistent diff format,
+ * then creates a unified diff patch using the provided file identifier for header annotations.
+ *
+ * @param {string} originalContent - The original file content.
+ * @param {string} newContent - The updated file content.
+ * @param {string} [filepath='file'] - The file identifier used in the diff header.
+ * @returns {string} A unified diff string representing the changes between the two versions of content.
+ */
function createUnifiedDiff(originalContent, newContent, filepath = 'file') {
// Ensure consistent line endings for diff
const normalizedOriginal = normalizeLineEndings(originalContent);
const normalizedNew = normalizeLineEndings(newContent);
return createTwoFilesPatch(filepath, filepath, normalizedOriginal, normalizedNew, 'original', 'modified');
}
+/**
+ * Applies a series of text edits to a file and returns a formatted unified diff of the changes.
+ *
+ * This asynchronous function reads the content from the specified file, normalizes its line endings,
+ * and sequentially applies each edit. Each edit specifies an "oldText" to search for and a "newText"
+ * to substitute. The function first attempts an exact match; if not found, it then performs a
+ * flexible, line-by-line replacement that preserves the file's indentation. If an edit's old text
+ * cannot be found, an error is thrown.
+ *
+ * After applying all edits, a unified diff is generated to represent the changes. The diff is
+ * formatted within a code block that adapts the number of backticks based on its content. When
+ * dryRun is false (the default), the modified content is written back to the file; otherwise, no
+ * file write occurs.
+ *
+ * @param {string} filePath - The path to the file to be edited.
+ * @param {Array<{oldText: string, newText: string}>} edits - An array of edits describing the text to replace and its replacement.
+ * @param {boolean} [dryRun=false] - If true, simulates the edits without saving changes to the file.
+ * @returns {Promise} A formatted unified diff of the changes applied.
+ *
+ * @throws {Error} If an edit's old text cannot be located in the file content.
+ */
async function applyFileEdits(filePath, edits, dryRun = false) {
// Read file content and normalize line endings
const content = normalizeLineEndings(await fs.readFile(filePath, 'utf-8'));
@@ -164,6 +251,20 @@ async function applyFileEdits(filePath, edits, dryRun = false) {
}
return formattedDiff;
}
+/**
+ * Recursively constructs a tree representation of the directory structure.
+ *
+ * This asynchronous function reads the contents of the directory at the specified
+ * path, validates it against the asset root, and recursively processes subdirectories
+ * up to the specified maximum depth. When the maximum depth is reached, it returns a
+ * stub entry to indicate that further subdirectories exist.
+ *
+ * @param {string} currentPath - The starting directory path to build the tree from, typically relative to the asset root.
+ * @param {string} assetRootPath - The root directory used to validate and resolve the current path.
+ * @param {number} [maxDepth=5] - The maximum depth the function will traverse.
+ * @param {number} [currentDepth=0] - The current depth level during recursion (used internally).
+ * @returns {Promise>} 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.
+ */
async function buildDirectoryTree(currentPath, assetRootPath, maxDepth = 5, currentDepth = 0) {
if (currentDepth >= maxDepth) {
return [{ name: "...", type: "directory" }];
@@ -184,7 +285,16 @@ async function buildDirectoryTree(currentPath, assetRootPath, maxDepth = 5, curr
}
return result;
}
-// Function to recognize Unity asset types based on file extension
+/**
+ * Determines the Unity asset type based on the file extension.
+ *
+ * Extracts the file extension from the provided file path, converts it to lower case,
+ * and returns a matching asset type according to predefined mapping. If the extension is not recognized,
+ * the function returns "Other".
+ *
+ * @param {string} filePath - The file path from which the asset type is derived.
+ * @returns {string} The Unity asset type (e.g., "Scene", "Prefab", "Texture") or "Other" if unrecognized.
+ */
function getUnityAssetType(filePath) {
const ext = path.extname(filePath).toLowerCase();
// Common Unity asset types
@@ -229,7 +339,24 @@ function getUnityAssetType(filePath) {
};
return assetTypes[ext] || 'Other';
}
-// Handler function to process filesystem tools
+/**
+ * Processes filesystem tool commands by validating input arguments, normalizing file paths,
+ * and executing the corresponding filesystem operation.
+ *
+ * This asynchronous function supports various commands such as reading files, writing files,
+ * editing file contents, listing directories, constructing directory trees, searching files,
+ * retrieving file information, finding assets by type, and listing C# scripts. It validates
+ * command-specific arguments using predefined schemas and ensures that file paths are confined
+ * within the project directory. When a command is unrecognized or arguments are invalid, it
+ * returns an error response.
+ *
+ * @param {string} name - Identifier of the filesystem tool command (e.g., "read_file", "write_file").
+ * @param {*} args - Command-specific arguments whose structure is validated with predefined schemas.
+ * @param {string} projectPath - Root directory used to resolve and validate file paths.
+ * @returns {Promise} A promise that resolves to an object containing:
+ * - content: An array of objects with 'type' and 'text' properties representing the response message.
+ * - isError: (Optional) A boolean flag indicating whether an error occurred.
+ */
export async function handleFilesystemTool(name, args, projectPath) {
switch (name) {
case "read_file": {
@@ -392,7 +519,19 @@ export async function handleFilesystemTool(name, args, projectPath) {
const validPath = await validatePath(parsed.data.searchPath, projectPath);
const results = [];
const targetType = parsed.data.assetType.toLowerCase();
- // Recursive function to search for assets
+ /**
+ * Recursively searches a directory for Unity assets matching a specific type.
+ *
+ * This asynchronous function traverses the directory tree starting at the given directory.
+ * For each file, it determines its Unity asset type using the external getUnityAssetType function.
+ * If the asset type (in lowercase) matches the externally defined targetType, the file path is added to
+ * the global results array.
+ *
+ * @param {string} dir - The directory path to search.
+ *
+ * @remark This function relies on external variables: targetType (a string representing the desired asset type)
+ * and results (an array where matching asset paths are collected).
+ */
async function searchAssets(dir) {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
@@ -428,7 +567,17 @@ export async function handleFilesystemTool(name, args, projectPath) {
}
const validPath = await validatePath(parsed.data.path, projectPath);
const scripts = [];
- // Recursive function to find C# scripts
+ /**
+ * Recursively finds all C# script files (.cs) within the specified directory.
+ *
+ * This asynchronous function traverses the given directory and its subdirectories.
+ * When it encounters a file with a ".cs" extension, it appends an object containing
+ * the file's full path and name to the global `scripts` array.
+ *
+ * @param {string} dir - The directory path to begin the search.
+ *
+ * @throws {Error} If reading the directory fails.
+ */
async function findScripts(dir) {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
@@ -464,7 +613,13 @@ export async function handleFilesystemTool(name, args, projectPath) {
}
// Register filesystem tools with the MCP server
// This function is now only a stub that doesn't actually do anything
-// since all tools are registered in toolDefinitions.ts
+/**
+ * Deprecated function for registering filesystem tools.
+ *
+ * This function now only logs a message indicating that filesystem tool registration has moved to toolDefinitions.ts.
+ *
+ * @deprecated Filesystem tools registration is now performed in toolDefinitions.ts.
+ */
export function registerFilesystemTools(server, wsHandler) {
// This function is now deprecated as tool registration has moved to toolDefinitions.ts
console.log("Filesystem tools are now registered in toolDefinitions.ts");
diff --git a/mcpServer/build/toolDefinitions.js b/mcpServer/build/toolDefinitions.js
index cd4177f..148e50d 100644
--- a/mcpServer/build/toolDefinitions.js
+++ b/mcpServer/build/toolDefinitions.js
@@ -47,6 +47,19 @@ export const FindAssetsByTypeArgsSchema = z.object({
export const ListScriptsArgsSchema = z.object({
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.'),
});
+/**
+ * Registers available Unity Editor and filesystem tools with the MCP server and configures tool request handling.
+ *
+ * This function determines the project root from the UNITY_PROJECT_PATH environment variable (or defaults
+ * to the current working directory) and registers a set of tools—each defined with its name, description,
+ * category, tags, and input schema—for both Unity Editor operations and filesystem interactions.
+ * It also sets up a request handler that routes tool invocation requests based on the tool name,
+ * handling special cases such as connection verification, filesystem operations (via a dedicated handler),
+ * and Unity-specific commands. If the Unity Editor is not connected when required, it throws an McpError.
+ *
+ * @remark Tools like "verify_connection" are processed even if the Unity Editor is not connected, while other
+ * Unity-specific tools require an active connection and perform additional error handling.
+ */
export function registerTools(server, wsHandler) {
// Determine project root path from environment variable or default to parent of Assets folder
const projectPath = process.env.UNITY_PROJECT_PATH || path.resolve(process.cwd());
diff --git a/mcpServer/src/filesystemTools.ts b/mcpServer/src/filesystemTools.ts
index b84b4cc..29006c0 100644
--- a/mcpServer/src/filesystemTools.ts
+++ b/mcpServer/src/filesystemTools.ts
@@ -36,7 +36,19 @@ interface TreeEntry {
}
// Helper functions
-// Updated validatePath function to properly handle empty paths
+/**
+ * Validates and normalizes a filesystem path to ensure it resides within the specified asset root.
+ *
+ * This function cleans the input by stripping extraneous quotes and escape characters, defaults empty
+ * or improperly formatted paths to the asset root, and resolves relative or absolute paths to a canonical form.
+ * If an absolute path is provided that initially falls outside the asset root but is accessible when treated
+ * as relative, the relative path is used instead. If the final resolved path escapes the asset root, an error is thrown.
+ *
+ * @param requestedPath - The input path to validate, which may be empty, quoted, or malformed.
+ * @param assetRootPath - The base directory representing the Unity project assets.
+ * @returns A Promise that resolves to the absolute, normalized path within the asset root.
+ * @throws {Error} If the resolved path is outside the asset root directory.
+ */
async function validatePath(requestedPath: string, assetRootPath: string): Promise {
// If path is empty or just quotes, use the asset root path directly
if (!requestedPath || requestedPath.trim() === '' || requestedPath.trim() === '""' || requestedPath.trim() === "''") {
@@ -88,6 +100,16 @@ async function validatePath(requestedPath: string, assetRootPath: string): Promi
return resolvedPath;
}
+/**
+ * Retrieves metadata for a given file.
+ *
+ * This asynchronous function obtains file statistics from the filesystem and returns details such as
+ * file size, creation, modification, and access times, as well as indicators for whether the path
+ * represents a file or directory. The file permissions are formatted as a three-digit octal string.
+ *
+ * @param filePath - The path to the file.
+ * @returns An object containing metadata about the file.
+ */
async function getFileStats(filePath: string): Promise {
const stats = await fs.stat(filePath);
return {
@@ -101,6 +123,19 @@ async function getFileStats(filePath: string): Promise {
};
}
+/**
+ * Recursively searches for files in the given workspace that include the specified substring in their name.
+ *
+ * The search starts at the provided root directory and descends into subdirectories. Files whose names contain the
+ * given pattern (case-insensitive) are added to the result list. Paths that match any of the provided exclude glob
+ * patterns are skipped during the search. If an exclude pattern does not include a wildcard, it is automatically
+ * converted to cover nested directory structures.
+ *
+ * @param rootPath - The base directory to begin the search.
+ * @param pattern - The substring to match within file names (case-insensitive).
+ * @param excludePatterns - Optional array of glob patterns for paths to exclude from the search.
+ * @returns A promise resolving to an array of file paths that match the specified criteria.
+ */
async function searchFiles(
rootPath: string,
pattern: string,
@@ -144,10 +179,27 @@ async function searchFiles(
return results;
}
+/**
+ * Normalizes line endings in the provided text by converting Windows-style CRLF sequences to Unix-style LF.
+ *
+ * @param text The input text that may contain CRLF line endings.
+ * @returns The text with all CRLF sequences replaced by LF.
+ */
function normalizeLineEndings(text: string): string {
return text.replace(/\r\n/g, '\n');
}
+/**
+ * Generates a unified diff string by comparing the original and modified content of a file.
+ *
+ * The function normalizes the line endings of both inputs to Unix-style before generating the diff,
+ * ensuring a consistent and portable diff output. The file path is used in the diff header.
+ *
+ * @param originalContent - The original content of the file.
+ * @param newContent - The modified content of the file.
+ * @param filepath - The file path used in the diff header. Defaults to "file".
+ * @returns The unified diff string representing the differences between the original and modified content.
+ */
function createUnifiedDiff(originalContent: string, newContent: string, filepath: string = 'file'): string {
// Ensure consistent line endings for diff
const normalizedOriginal = normalizeLineEndings(originalContent);
@@ -163,6 +215,26 @@ function createUnifiedDiff(originalContent: string, newContent: string, filepath
);
}
+/**
+ * Applies a series of text edits to a file and returns a unified diff of the modifications.
+ *
+ * The function reads the file at the specified path, normalizes its line endings to Unix-style, and then applies each edit sequentially.
+ * For each edit, it first attempts an exact substring replacement of the old text with the new text.
+ * If an exact match is not found, it performs a line-by-line, whitespace-tolerant search to locate and replace the target content,
+ * preserving the original indentation. If no matching text is found for an edit, the function throws an error.
+ *
+ * After processing all edits, a unified diff is generated to highlight the changes between the original and modified content.
+ * The diff output is formatted with a dynamic number of backticks to prevent markdown formatting conflicts.
+ *
+ * If the dryRun flag is false, the modified content is saved back to the file.
+ *
+ * @param filePath - The path of the file to be edited.
+ * @param edits - An array of edit operations, each containing an `oldText` to be replaced and the corresponding `newText`.
+ * @param dryRun - When true, applies the edits only in memory and returns the diff without writing changes to the file.
+ * @returns A unified diff string representing the changes made to the file.
+ *
+ * @throws {Error} If an edit's oldText cannot be found in the file content.
+ */
async function applyFileEdits(
filePath: string,
edits: Array<{oldText: string, newText: string}>,
@@ -241,6 +313,21 @@ async function applyFileEdits(
return formattedDiff;
}
+/**
+ * Recursively constructs a tree representation of a directory structure.
+ *
+ * The function validates and reads the specified directory, then recursively builds an array of entries representing
+ * files and directories. For directories, it includes their children up to a defined maximum depth. Once the maximum
+ * depth is reached, a placeholder entry with the name "..." is returned to indicate that additional contents exist.
+ *
+ * @param currentPath - The starting directory path from which to build the tree.
+ * @param assetRootPath - The root directory used to validate that paths do not escape the intended asset scope.
+ * @param maxDepth - The maximum depth for recursive traversal (default is 5).
+ * @param currentDepth - The current recursion depth (default is 0, intended for internal use).
+ * @returns A promise that resolves to an array of tree entries representing the directory structure.
+ *
+ * @throws {Error} If the provided path is invalid or if reading the directory fails.
+ */
async function buildDirectoryTree(currentPath: string, assetRootPath: string, maxDepth: number = 5, currentDepth: number = 0): Promise {
if (currentDepth >= maxDepth) {
return [{ name: "...", type: "directory" }];
@@ -267,7 +354,15 @@ async function buildDirectoryTree(currentPath: string, assetRootPath: string, ma
return result;
}
-// Function to recognize Unity asset types based on file extension
+/**
+ * Determines the Unity asset type based on the file extension.
+ *
+ * This function extracts the file extension from the provided file path, normalizes it to lowercase,
+ * and matches it against a set of predefined Unity asset types. If no match is found, it returns "Other".
+ *
+ * @param filePath - The path of the file to evaluate.
+ * @returns The Unity asset type corresponding to the file extension, or "Other" if unrecognized.
+ */
function getUnityAssetType(filePath: string): string {
const ext = path.extname(filePath).toLowerCase();
@@ -315,7 +410,19 @@ function getUnityAssetType(filePath: string): string {
return assetTypes[ext] || 'Other';
}
-// Handler function to process filesystem tools
+/**
+ * Processes a filesystem tool command based on the provided command name and arguments.
+ *
+ * This function acts as a dispatcher for various filesystem operations in a Unity project context. It validates input arguments with defined schemas and resolves file paths relative to the project directory to ensure secure access. Supported operations include reading files (single or multiple), writing files (with directory creation), editing file content (with diff generation), listing directory contents (with Unity asset type detection), building directory trees, searching files with exclusion patterns, retrieving file information, finding assets by type, and listing C# scripts.
+ *
+ * @param name - The command name indicating which filesystem operation to perform.
+ * @param args - The command-specific arguments expected to conform to a corresponding schema.
+ * @param projectPath - The root directory of the project used to validate and resolve file paths.
+ *
+ * @returns An object containing a `content` array with the result messages and an optional `isError` flag when an error occurs.
+ *
+ * @remarks Invalid arguments are handled gracefully by returning a structured error message rather than throwing an exception.
+ */
export async function handleFilesystemTool(name: string, args: any, projectPath: string) {
switch (name) {
case "read_file": {
@@ -502,7 +609,16 @@ export async function handleFilesystemTool(name: string, args: any, projectPath:
const results: string[] = [];
const targetType = parsed.data.assetType.toLowerCase();
- // Recursive function to search for assets
+ /**
+ * Recursively searches a directory for assets matching a specific Unity asset type.
+ *
+ * This asynchronous helper iterates over all entries in the given directory. It recurses into subdirectories and checks each file's Unity asset type using `getUnityAssetType`. If the file's asset type (after converting to lowercase) equals the target type defined in the outer scope, the file's path is added to an external `results` array.
+ *
+ * @param dir - The directory path to search.
+ *
+ * @remarks
+ * The `targetType` and `results` variables must be defined in the enclosing scope.
+ */
async function searchAssets(dir: string) {
const entries = await fs.readdir(dir, { withFileTypes: true });
@@ -545,7 +661,15 @@ export async function handleFilesystemTool(name: string, args: any, projectPath:
const scripts: Array<{path: string, name: string}> = [];
- // Recursive function to find C# scripts
+ /**
+ * Recursively finds and collects C# script files within the given directory.
+ *
+ * This function searches the specified directory and all its subdirectories for files
+ * with a ".cs" extension (case-insensitive). Each discovered C# script file is added to
+ * a global array with its full path and filename.
+ *
+ * @param dir - The directory path from which to start the search.
+ */
async function findScripts(dir: string) {
const entries = await fs.readdir(dir, { withFileTypes: true });
@@ -587,7 +711,13 @@ export async function handleFilesystemTool(name: string, args: any, projectPath:
// Register filesystem tools with the MCP server
// This function is now only a stub that doesn't actually do anything
-// since all tools are registered in toolDefinitions.ts
+/**
+ * Stub function for registering filesystem tools.
+ *
+ * This function is deprecated and only logs that tool registration has moved to toolDefinitions.ts.
+ *
+ * @deprecated Tool registration has been moved to toolDefinitions.ts.
+ */
export function registerFilesystemTools(server: Server, wsHandler: WebSocketHandler) {
// This function is now deprecated as tool registration has moved to toolDefinitions.ts
console.log("Filesystem tools are now registered in toolDefinitions.ts");
diff --git a/mcpServer/src/toolDefinitions.ts b/mcpServer/src/toolDefinitions.ts
index f8059e6..9d0c8e9 100644
--- a/mcpServer/src/toolDefinitions.ts
+++ b/mcpServer/src/toolDefinitions.ts
@@ -64,6 +64,18 @@ export const ListScriptsArgsSchema = z.object({
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.'),
});
+/**
+ * Registers request handlers for Unity Editor and filesystem tools on the server.
+ *
+ * This function sets up two primary request handlers:
+ * - One for listing all available tools, providing detailed schema definitions and metadata for each tool.
+ * - One for processing tool execution calls by dispatching requests based on the tool name. Filesystem operations are routed through a dedicated handler,
+ * while Unity-specific commands first verify an active WebSocket connection.
+ *
+ * The Unity project root path is determined from the UNITY_PROJECT_PATH environment variable or defaults to the current working directory.
+ *
+ * @throws {McpError} If a Unity-specific tool is executed without an active connection.
+ */
export function registerTools(server: Server, wsHandler: WebSocketHandler) {
// Determine project root path from environment variable or default to parent of Assets folder
const projectPath = process.env.UNITY_PROJECT_PATH || path.resolve(process.cwd());