diff --git a/SampleCommandSet/Commands/Access/GetParametersCommand.cs b/SampleCommandSet/Commands/Access/GetParametersCommand.cs new file mode 100644 index 0000000..1102075 --- /dev/null +++ b/SampleCommandSet/Commands/Access/GetParametersCommand.cs @@ -0,0 +1,49 @@ +using Autodesk.Revit.UI; +using Newtonsoft.Json.Linq; +using revit_mcp_sdk.API.Base; +using System; +using System.Collections.Generic; + +namespace SampleCommandSet.Commands.Access +{ + /// + /// Batch read parameters for elements. + /// JSON-RPC method: get_parameters + /// params: { + /// elementIds: string[], // required, ElementId as string + /// paramNames?: string[] // optional, return all parameters by name when omitted + /// } + /// returns: array of { elementId, parameters: [{ name, value, storageType, isReadOnly, hasValue }] } + /// + public class GetParametersCommand : ExternalEventCommandBase + { + private GetParametersEventHandler _handler => (GetParametersEventHandler)Handler; + + public override string CommandName => "get_parameters"; + + public GetParametersCommand(UIApplication uiApp) + : base(new GetParametersEventHandler(), uiApp) + { + } + + public override object Execute(JObject parameters, string requestId) + { + if (parameters == null) + throw new ArgumentException("params cannot be null"); + + var elementIds = parameters["elementIds"]?.ToObject>(); + if (elementIds == null || elementIds.Count == 0) + throw new ArgumentException("elementIds is required and cannot be empty"); + + var paramNames = parameters["paramNames"]?.ToObject>(); + + _handler.ElementIds = elementIds; + _handler.ParamNames = paramNames; + + if (!RaiseAndWaitForCompletion(20000)) + throw new TimeoutException("get_parameters timeout"); + + return _handler.Result; + } + } +} diff --git a/SampleCommandSet/Commands/Access/GetParametersEventHandler.cs b/SampleCommandSet/Commands/Access/GetParametersEventHandler.cs new file mode 100644 index 0000000..f9d9861 --- /dev/null +++ b/SampleCommandSet/Commands/Access/GetParametersEventHandler.cs @@ -0,0 +1,127 @@ +using Autodesk.Revit.DB; +using Autodesk.Revit.UI; +using revit_mcp_sdk.API.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace SampleCommandSet.Commands.Access +{ + public class GetParametersEventHandler : IExternalEventHandler, IWaitableExternalEventHandler + { + public List ElementIds { get; set; } + public List ParamNames { get; set; } + + public object Result { get; private set; } + + private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false); + + public bool WaitForCompletion(int timeoutMilliseconds = 15000) + { + return _resetEvent.WaitOne(timeoutMilliseconds); + } + + public void Execute(UIApplication app) + { + try + { + var doc = app.ActiveUIDocument.Document; + var results = new List(); + + foreach (var idStr in ElementIds ?? Enumerable.Empty()) + { + if (!int.TryParse(idStr, out int idVal)) + continue; + + var element = doc.GetElement(new ElementId(idVal)); + if (element == null) + continue; + + var paramEntries = new List(); + + if (ParamNames != null && ParamNames.Any()) + { + foreach (var name in ParamNames) + { + var p = element.LookupParameter(name); + if (p != null) + paramEntries.Add(MakeParamEntry(p)); + } + } + else + { + foreach (Parameter p in element.Parameters) + { + if (p?.Definition == null) continue; + paramEntries.Add(MakeParamEntry(p)); + } + } + + results.Add(new + { + elementId = idStr, + parameters = paramEntries + }); + } + + Result = results; + } + catch (Exception ex) + { + Result = new { error = ex.Message }; + } + finally + { + _resetEvent.Set(); + } + } + + private object MakeParamEntry(Parameter p) + { + string name = p.Definition?.Name ?? ""; + string storageType = p.StorageType.ToString(); + bool isReadOnly = p.IsReadOnly; + bool hasValue = p.HasValue; + + object value = null; + try + { + switch (p.StorageType) + { + case StorageType.String: + value = p.AsString(); + break; + case StorageType.Double: + value = p.AsDouble(); + break; + case StorageType.Integer: + value = p.AsInteger(); + break; + case StorageType.ElementId: + value = p.AsElementId()?.IntegerValue; + break; + default: + value = null; + break; + } + } + catch { } + + return new + { + name, + value, + storageType, + isReadOnly, + hasValue + }; + } + + public string GetName() + { + return "get parameters"; + } + } +} + diff --git a/SampleCommandSet/Commands/Modify/SetParametersCommand.cs b/SampleCommandSet/Commands/Modify/SetParametersCommand.cs new file mode 100644 index 0000000..04b1529 --- /dev/null +++ b/SampleCommandSet/Commands/Modify/SetParametersCommand.cs @@ -0,0 +1,49 @@ +using Autodesk.Revit.UI; +using Newtonsoft.Json.Linq; +using revit_mcp_sdk.API.Base; +using System; +using System.Collections.Generic; + +namespace SampleCommandSet.Commands.Modify +{ + /// + /// Batch write element parameters. + /// JSON-RPC method: set_parameters + /// params: { + /// updates: [ { elementId: string, parameters: [ { name: string, value: any } ] } ], + /// dryRun?: bool + /// } + /// returns: summary and per-element results + /// + public class SetParametersCommand : ExternalEventCommandBase + { + private SetParametersEventHandler _handler => (SetParametersEventHandler)Handler; + + public override string CommandName => "set_parameters"; + + public SetParametersCommand(UIApplication uiApp) + : base(new SetParametersEventHandler(), uiApp) + { + } + + public override object Execute(JObject parameters, string requestId) + { + if (parameters == null) + throw new ArgumentException("params cannot be null"); + + var updates = parameters["updates"]?.ToObject>(); + if (updates == null || updates.Count == 0) + throw new ArgumentException("updates is required and cannot be empty"); + + bool dryRun = parameters["dryRun"]?.Value() ?? false; + + _handler.Updates = updates; + _handler.DryRun = dryRun; + + if (!RaiseAndWaitForCompletion(30000)) + throw new TimeoutException("set_parameters timeout"); + + return _handler.Result; + } + } +} diff --git a/SampleCommandSet/Commands/Modify/SetParametersEventHandler.cs b/SampleCommandSet/Commands/Modify/SetParametersEventHandler.cs new file mode 100644 index 0000000..f5055ea --- /dev/null +++ b/SampleCommandSet/Commands/Modify/SetParametersEventHandler.cs @@ -0,0 +1,205 @@ +using Autodesk.Revit.DB; +using Autodesk.Revit.UI; +using Newtonsoft.Json.Linq; +using revit_mcp_sdk.API.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace SampleCommandSet.Commands.Modify +{ + public class SetParametersEventHandler : IExternalEventHandler, IWaitableExternalEventHandler + { + public class ParamAssignment + { + public string Name { get; set; } + public JToken Value { get; set; } + } + + public class ElementUpdate + { + public string ElementId { get; set; } + public List Parameters { get; set; } = new List(); + } + + public class ElementResult + { + public string ElementId { get; set; } + public int UpdatedCount { get; set; } + public List Errors { get; set; } = new List(); + } + + public List Updates { get; set; } + public bool DryRun { get; set; } + + public object Result { get; private set; } + + private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false); + + public bool WaitForCompletion(int timeoutMilliseconds = 25000) + { + return _resetEvent.WaitOne(timeoutMilliseconds); + } + + public void Execute(UIApplication app) + { + try + { + var doc = app.ActiveUIDocument.Document; + var elementResults = new List(); + + using (var tx = new Transaction(doc, DryRun ? "[DryRun] Set Parameters" : "Set Parameters")) + { + if (!DryRun) + tx.Start(); + + foreach (var upd in Updates ?? Enumerable.Empty()) + { + var er = new ElementResult { ElementId = upd.ElementId }; + + if (!int.TryParse(upd.ElementId, out int idVal)) + { + er.Errors.Add($"Invalid ElementId: {upd.ElementId}"); + elementResults.Add(er); + continue; + } + + var element = doc.GetElement(new ElementId(idVal)); + if (element == null) + { + er.Errors.Add($"Element not found: {upd.ElementId}"); + elementResults.Add(er); + continue; + } + + foreach (var pa in upd.Parameters ?? Enumerable.Empty()) + { + try + { + var p = element.LookupParameter(pa.Name); + if (p == null) + { + er.Errors.Add($"Parameter not found: {pa.Name}"); + continue; + } + if (p.IsReadOnly) + { + er.Errors.Add($"Parameter read-only: {pa.Name}"); + continue; + } + + if (DryRun) + { + // Validate assignability without writing + if (!CanAssign(p, pa.Value, out var reason)) + er.Errors.Add($"{pa.Name} not assignable: {reason}"); + else + er.UpdatedCount++; + } + else + { + if (!Assign(p, pa.Value, out var reason)) + er.Errors.Add($"Failed {pa.Name}: {reason}"); + else + er.UpdatedCount++; + } + } + catch (Exception ex) + { + er.Errors.Add($"{pa.Name}: {ex.Message}"); + } + } + + elementResults.Add(er); + } + + if (!DryRun) + tx.Commit(); + } + + int total = elementResults.Count; + int updated = elementResults.Sum(x => x.UpdatedCount); + int errors = elementResults.Sum(x => x.Errors.Count); + + Result = new + { + summary = new { elements = total, assignments = updated, errors }, + results = elementResults + }; + } + catch (Exception ex) + { + Result = new { error = ex.Message }; + } + finally + { + _resetEvent.Set(); + } + } + + private bool CanAssign(Parameter p, JToken value, out string reason) + { + try + { + switch (p.StorageType) + { + case StorageType.String: + reason = null; return true; + case StorageType.Double: + if (value == null) { reason = "null"; return false; } + if (value.Type == JTokenType.Float || value.Type == JTokenType.Integer) { reason = null; return true; } + if (double.TryParse(value.ToString(), out _)) { reason = null; return true; } + reason = "expect number"; return false; + case StorageType.Integer: + if (value == null) { reason = "null"; return false; } + if (value.Type == JTokenType.Integer || value.Type == JTokenType.Boolean) { reason = null; return true; } + if (int.TryParse(value.ToString(), out _)) { reason = null; return true; } + reason = "expect integer"; return false; + case StorageType.ElementId: + // Simplified: ElementId assignment is not supported yet + reason = "ElementId not supported"; return false; + default: + reason = "Unsupported storage type"; return false; + } + } + catch (Exception ex) + { + reason = ex.Message; + return false; + } + } + + private bool Assign(Parameter p, JToken value, out string reason) + { + reason = null; + switch (p.StorageType) + { + case StorageType.String: + return p.Set(value?.ToString() ?? string.Empty); + case StorageType.Double: + if (value == null) { reason = "null"; return false; } + double d; + if (value.Type == JTokenType.Float || value.Type == JTokenType.Integer) d = value.Value(); + else if (!double.TryParse(value.ToString(), out d)) { reason = "parse double"; return false; } + return p.Set(d); + case StorageType.Integer: + if (value == null) { reason = "null"; return false; } + int i; + if (value.Type == JTokenType.Boolean) i = value.Value() ? 1 : 0; + else if (value.Type == JTokenType.Integer) i = value.Value(); + else if (!int.TryParse(value.ToString(), out i)) { reason = "parse int"; return false; } + return p.Set(i); + case StorageType.ElementId: + reason = "ElementId not supported"; return false; + default: + reason = "Unsupported storage type"; return false; + } + } + + public string GetName() + { + return "set parameters"; + } + } +} diff --git a/SampleCommandSet/Commands/Query/FindElementsCommand.cs b/SampleCommandSet/Commands/Query/FindElementsCommand.cs new file mode 100644 index 0000000..04c4e32 --- /dev/null +++ b/SampleCommandSet/Commands/Query/FindElementsCommand.cs @@ -0,0 +1,47 @@ +using Autodesk.Revit.UI; +using Newtonsoft.Json.Linq; +using revit_mcp_sdk.API.Base; +using System; + +namespace SampleCommandSet.Commands.Query +{ + /// + /// Find elements by filters. + /// JSON-RPC method: find_elements + /// params: { + /// categoryList?: string[], // BuiltInCategory names + /// classList?: string[], // e.g. "Wall", "FamilyInstance" + /// viewId?: long, // restrict to a view + /// onlyVisible?: bool, // default true + /// limit?: int, // default 200 + /// paramFilters?: [ { name, op, value } ] // op: equals|contains|gt|lt + /// } + /// returns: [{ id, uniqueId, name, category }] + /// + public class FindElementsCommand : ExternalEventCommandBase + { + private FindElementsEventHandler _handler => (FindElementsEventHandler)Handler; + + public override string CommandName => "find_elements"; + + public FindElementsCommand(UIApplication uiApp) + : base(new FindElementsEventHandler(), uiApp) + { + } + + public override object Execute(JObject parameters, string requestId) + { + _handler.CategoryList = parameters?["categoryList"]?.ToObject(); + _handler.ClassList = parameters?["classList"]?.ToObject(); + _handler.ViewId = parameters?["viewId"]?.Value(); + _handler.OnlyVisible = parameters?["onlyVisible"]?.Value() ?? true; + _handler.Limit = parameters?["limit"]?.Value() ?? 200; + _handler.ParamFilters = parameters?["paramFilters"]?.ToObject(); + + if (!RaiseAndWaitForCompletion(30000)) + throw new TimeoutException("find_elements timeout"); + + return _handler.Result; + } + } +} diff --git a/SampleCommandSet/Commands/Query/FindElementsEventHandler.cs b/SampleCommandSet/Commands/Query/FindElementsEventHandler.cs new file mode 100644 index 0000000..9b27470 --- /dev/null +++ b/SampleCommandSet/Commands/Query/FindElementsEventHandler.cs @@ -0,0 +1,164 @@ +using Autodesk.Revit.DB; +using Autodesk.Revit.UI; +using revit_mcp_sdk.API.Interfaces; +using SampleCommandSet.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace SampleCommandSet.Commands.Query +{ + public class FindElementsEventHandler : IExternalEventHandler, IWaitableExternalEventHandler + { + public class ParamFilter + { + public string Name { get; set; } + public string Op { get; set; } // equals|contains|gt|lt + public string Value { get; set; } + } + + public string[] CategoryList { get; set; } + public string[] ClassList { get; set; } + public long? ViewId { get; set; } + public bool OnlyVisible { get; set; } = true; + public int? Limit { get; set; } = 200; + public ParamFilter[] ParamFilters { get; set; } + + public object Result { get; private set; } + + private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false); + + public bool WaitForCompletion(int timeoutMilliseconds = 25000) + { + return _resetEvent.WaitOne(timeoutMilliseconds); + } + + public void Execute(UIApplication app) + { + try + { + var doc = app.ActiveUIDocument.Document; + + FilteredElementCollector collector; + if (ViewId.HasValue) + { + collector = new FilteredElementCollector(doc, new ElementId((int)ViewId.Value)); + } + else + { + collector = new FilteredElementCollector(doc); + } + + collector = collector.WhereElementIsNotElementType(); + + // Category filter + if (CategoryList != null && CategoryList.Length > 0) + { + var validCats = new List(); + foreach (var c in CategoryList) + { + if (Enum.TryParse(c, out BuiltInCategory bic)) + validCats.Add(bic); + } + if (validCats.Count > 0) + collector = collector.WherePasses(new ElementMulticategoryFilter(validCats)); + } + + // Class-name based filter + IEnumerable elems = collector.ToElements(); + if (ClassList != null && ClassList.Length > 0) + { + var set = new HashSet(ClassList); + elems = elems.Where(e => set.Contains(e.GetType().Name)); + } + + // Visibility filter + if (OnlyVisible && ViewId.HasValue) + { + var view = doc.GetElement(new ElementId((int)ViewId.Value)) as View; + if (view != null) + elems = elems.Where(e => !e.IsHidden(view)); + } + + // Parameter filters + if (ParamFilters != null && ParamFilters.Length > 0) + { + elems = elems.Where(e => MatchParamFilters(e, ParamFilters)); + } + + // Limit + int limit = Math.Max(1, Limit ?? 200); + elems = elems.Take(limit); + + var result = elems.Select(e => new + { + id = e.Id.GetIdValue(), + uniqueId = e.UniqueId, + name = e.Name, + category = e.Category?.Name + }).ToList(); + + Result = result; + } + catch (Exception ex) + { + Result = new { error = ex.Message }; + } + finally + { + _resetEvent.Set(); + } + } + + private bool MatchParamFilters(Element e, ParamFilter[] filters) + { + foreach (var f in filters) + { + var p = e.LookupParameter(f.Name); + if (p == null) return false; + + var op = (f.Op ?? "equals").ToLowerInvariant(); + var target = f.Value ?? string.Empty; + + switch (p.StorageType) + { + case StorageType.String: + var sv = p.AsString() ?? string.Empty; + if (op == "equals" && !string.Equals(sv, target, StringComparison.OrdinalIgnoreCase)) return false; + if (op == "contains" && sv.IndexOf(target, StringComparison.OrdinalIgnoreCase) < 0) return false; + if (op != "equals" && op != "contains") return false; + break; + case StorageType.Integer: + if (!int.TryParse(target, out int iv)) return false; + var pi = p.AsInteger(); + if (op == "equals" && pi != iv) return false; + if (op == "gt" && !(pi > iv)) return false; + if (op == "lt" && !(pi < iv)) return false; + if (op != "equals" && op != "gt" && op != "lt") return false; + break; + case StorageType.Double: + if (!double.TryParse(target, out double dv)) return false; + var pd = p.AsDouble(); + const double tol = 1e-9; + if (op == "equals" && Math.Abs(pd - dv) > tol) return false; + if (op == "gt" && !(pd > dv)) return false; + if (op == "lt" && !(pd < dv)) return false; + if (op != "equals" && op != "gt" && op != "lt") return false; + break; + case StorageType.ElementId: + // Simplified: ignore ElementId checks in filters + return false; + default: + return false; + } + } + return true; + } + + public string GetName() + { + return "find elements"; + } + } +} diff --git a/SampleCommandSet/command.json b/SampleCommandSet/command.json index c9ebbed..b5699d5 100644 --- a/SampleCommandSet/command.json +++ b/SampleCommandSet/command.json @@ -37,6 +37,21 @@ "commandName": "delete_element", "description": "Deletes elements using ElementId", "assemblyPath": "SampleCommandSet.dll" + }, + { + "commandName": "get_parameters", + "description": "Batch read parameters of elements", + "assemblyPath": "SampleCommandSet.dll" + }, + { + "commandName": "set_parameters", + "description": "Batch write parameters of elements (supports dryRun)", + "assemblyPath": "SampleCommandSet.dll" + }, + { + "commandName": "find_elements", + "description": "Find elements by category/class/parameter filters", + "assemblyPath": "SampleCommandSet.dll" } ] -} \ No newline at end of file +} diff --git a/revit-mcp-plugin/Core/SocketService.cs b/revit-mcp-plugin/Core/SocketService.cs index 9238bbe..94331ec 100644 --- a/revit-mcp-plugin/Core/SocketService.cs +++ b/revit-mcp-plugin/Core/SocketService.cs @@ -11,6 +11,7 @@ using RevitMCPSDK.API.Interfaces; using revit_mcp_plugin.Configuration; using revit_mcp_plugin.Utils; +using System.Collections.Generic; namespace revit_mcp_plugin.Core { @@ -239,6 +240,60 @@ private string ProcessJsonRPCRequest(string requestJson) ); } + // Built-in method: list registered commands + if (string.Equals(request.Method, "list_registered_commands", StringComparison.Ordinal)) + { + try + { + var commands = _commandRegistry.GetRegisteredCommands(); + return CreateSuccessResponse(request.Id, new { commands }); + } + catch (Exception ex) + { + return CreateErrorResponse(request.Id, JsonRPCErrorCodes.InternalError, ex.Message); + } + } + + // Built-in method: get command parameter JSON Schema + if (string.Equals(request.Method, "get_command_schema", StringComparison.Ordinal)) + { + try + { + var allSchemas = BuildCommandSchemas(); + var p = request.GetParamsObject(); + var methodName = p?["method"]?.Value(); + var methodsArray = p?["methods"]?.ToObject>(); + + Dictionary resultSchemas; + if (!string.IsNullOrEmpty(methodName)) + { + resultSchemas = new Dictionary(); + if (allSchemas.TryGetValue(methodName, out var schema)) + resultSchemas[methodName] = schema; + } + else if (methodsArray != null && methodsArray.Count > 0) + { + resultSchemas = new Dictionary(); + foreach (var m in methodsArray) + { + if (allSchemas.TryGetValue(m, out var schema)) + resultSchemas[m] = schema; + } + } + else + { + // No method specified: return all schemas + resultSchemas = allSchemas; + } + + return CreateSuccessResponse(request.Id, new { schemas = resultSchemas }); + } + catch (Exception ex) + { + return CreateErrorResponse(request.Id, JsonRPCErrorCodes.InternalError, ex.Message); + } + } + // 查找命令 // Search for the command in the registry. if (!_commandRegistry.TryGetCommand(request.Method, out var command)) @@ -308,5 +363,210 @@ private string CreateErrorResponse(string id, int code, string message, object d return response.ToJson(); } + + private Dictionary BuildCommandSchemas() + { + // JSON Schema (draft-07-like) for known methods + var schemas = new Dictionary(StringComparer.Ordinal) + { + ["list_registered_commands"] = new + { + title = "List Registered Commands", + type = "object", + properties = new { }, + additionalProperties = false, + required = new string[] { } + }, + ["get_command_schema"] = new + { + title = "Get Command Schema", + type = "object", + properties = new + { + method = new { type = "string", description = "Single method name" }, + methods = new { type = "array", items = new { type = "string" }, description = "Multiple method names" } + }, + additionalProperties = false + }, + ["say_hello"] = new + { + title = "Say Hello", + type = "object", + properties = new { }, + additionalProperties = false, + required = new string[] { } + }, + ["get_current_view_info"] = new + { + title = "Get Current View Info", + type = "object", + properties = new { }, + additionalProperties = false, + required = new string[] { } + }, + ["get_selected_elements"] = new + { + title = "Get Selected Elements", + type = "object", + properties = new + { + limit = new { type = "integer", minimum = 1, description = "Max elements to return" } + }, + additionalProperties = false + }, + ["get_available_family_types"] = new + { + title = "Get Available Family Types", + type = "object", + properties = new + { + categoryList = new { type = "array", items = new { type = "string" }, description = "BuiltInCategory names" }, + familyNameFilter = new { type = "string" }, + limit = new { type = "integer", minimum = 1 } + }, + additionalProperties = false + }, + ["get_current_view_elements"] = new + { + title = "Get Current View Elements", + type = "object", + properties = new + { + modelCategoryList = new { type = "array", items = new { type = "string" } }, + annotationCategoryList = new { type = "array", items = new { type = "string" } }, + includeHidden = new { type = "boolean", @default = false }, + limit = new { type = "integer", @default = 100, minimum = 1 } + }, + additionalProperties = false + }, + ["create_Wall"] = new + { + title = "Create Wall", + type = "object", + properties = new + { + startX = new { type = "number" }, + startY = new { type = "number" }, + endX = new { type = "number" }, + endY = new { type = "number" }, + height = new { type = "number" }, + thickness = new { type = "number" } + }, + required = new [] { "startX", "startY", "endX", "endY", "height", "thickness" }, + additionalProperties = false + }, + ["delete_element"] = new + { + title = "Delete Element", + type = "object", + properties = new + { + elementIds = new { type = "array", items = new { type = "string" } } + }, + required = new [] { "elementIds" }, + additionalProperties = false + }, + ["get_parameters"] = new + { + title = "Get Parameters", + type = "object", + properties = new + { + elementIds = new { type = "array", items = new { type = "string" }, description = "ElementId strings" }, + paramNames = new { type = "array", items = new { type = "string" }, description = "If omitted, returns all" } + }, + required = new [] { "elementIds" }, + additionalProperties = false + }, + ["set_parameters"] = new + { + title = "Set Parameters", + type = "object", + properties = new + { + dryRun = new { type = "boolean", @default = false }, + updates = new + { + type = "array", + items = new + { + type = "object", + properties = new + { + elementId = new { type = "string" }, + parameters = new + { + type = "array", + items = new + { + type = "object", + properties = new + { + name = new { type = "string" }, + value = new { } // any + }, + required = new [] { "name", "value" }, + additionalProperties = false + } + } + }, + required = new [] { "elementId", "parameters" }, + additionalProperties = false + } + } + }, + required = new [] { "updates" }, + additionalProperties = false + }, + ["find_elements"] = new + { + title = "Find Elements", + type = "object", + properties = new + { + categoryList = new { type = "array", items = new { type = "string" } }, + classList = new { type = "array", items = new { type = "string" } }, + viewId = new { type = "integer" }, + onlyVisible = new { type = "boolean", @default = true }, + limit = new { type = "integer", @default = 200, minimum = 1 }, + paramFilters = new + { + type = "array", + items = new + { + type = "object", + properties = new + { + name = new { type = "string" }, + op = new { type = "string", @enum = new [] { "equals", "contains", "gt", "lt" } }, + value = new { type = "string" } + }, + required = new [] { "name", "op", "value" }, + additionalProperties = false + } + } + }, + additionalProperties = false + } + }; + + // Return schemas only for currently registered commands (built-ins excepted) + var filtered = new Dictionary(StringComparer.Ordinal); + foreach (var kv in schemas) + { + if (kv.Key == "get_command_schema" || kv.Key == "list_registered_commands") + { + filtered[kv.Key] = kv.Value; + continue; + } + + if (_commandRegistry.TryGetCommand(kv.Key, out var _)) + { + filtered[kv.Key] = kv.Value; + } + } + + return filtered; + } } }