mirror of https://github.com/abpframework/abp.git
Browse Source
Introduces CommandLineArgsExtensions for MCP command detection, removes McpToolDefinitionValidator, and updates MCP tool definition fetching to require a successful server connection. Cleans up unused environment variables, adds mcp-config.json support, and simplifies tool validation and caching logic. These changes improve reliability and maintainability of MCP command execution and tool management.pull/24677/head
11 changed files with 77 additions and 336 deletions
@ -0,0 +1,11 @@ |
|||||
|
using Volo.Abp.Cli.Commands; |
||||
|
|
||||
|
namespace Volo.Abp.Cli.Args; |
||||
|
|
||||
|
public static class CommandLineArgsExtensions |
||||
|
{ |
||||
|
public static bool IsMcpCommand(this CommandLineArgs args) |
||||
|
{ |
||||
|
return args.IsCommand(McpCommand.Name); |
||||
|
} |
||||
|
} |
||||
@ -1,181 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Text.RegularExpressions; |
|
||||
using Microsoft.Extensions.Logging; |
|
||||
using Volo.Abp.Cli.Commands.Models; |
|
||||
using Volo.Abp.DependencyInjection; |
|
||||
|
|
||||
namespace Volo.Abp.Cli.Commands.Services; |
|
||||
|
|
||||
public class McpToolDefinitionValidator : ITransientDependency |
|
||||
{ |
|
||||
private const int MaxToolNameLength = 100; |
|
||||
private const int MaxDescriptionLength = 2000; |
|
||||
private const string UnknownToolName = "<unknown>"; |
|
||||
|
|
||||
private static readonly Regex ToolNameRegex = new Regex("^[a-zA-Z0-9_]+$", RegexOptions.Compiled); |
|
||||
private static readonly HashSet<string> ValidTypeValues = new HashSet<string> |
|
||||
{ "string", "number", "boolean", "object", "array" }; |
|
||||
|
|
||||
private readonly ILogger<McpToolDefinitionValidator> _logger; |
|
||||
|
|
||||
public McpToolDefinitionValidator(ILogger<McpToolDefinitionValidator> logger) |
|
||||
{ |
|
||||
_logger = logger; |
|
||||
} |
|
||||
|
|
||||
public List<McpToolDefinition> ValidateAndFilter(List<McpToolDefinition> tools) |
|
||||
{ |
|
||||
if (tools == null || tools.Count == 0) |
|
||||
{ |
|
||||
return new List<McpToolDefinition>(); |
|
||||
} |
|
||||
|
|
||||
var validTools = new List<McpToolDefinition>(); |
|
||||
|
|
||||
foreach (var tool in tools) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
if (!IsValidTool(tool)) |
|
||||
{ |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
validTools.Add(tool); |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
_logger.LogWarning($"Error validating tool '{tool?.Name ?? UnknownToolName}': {ex.Message}"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (validTools.Count < tools.Count) |
|
||||
{ |
|
||||
_logger.LogWarning($"Filtered out {tools.Count - validTools.Count} invalid tool(s). {validTools.Count} valid tool(s) remaining."); |
|
||||
} |
|
||||
|
|
||||
return validTools; |
|
||||
} |
|
||||
|
|
||||
private bool IsValidTool(McpToolDefinition tool) |
|
||||
{ |
|
||||
if (!IsValidToolName(tool)) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (!IsValidDescription(tool)) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (tool.InputSchema != null && !IsValidInputSchema(tool)) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
private bool IsValidToolName(McpToolDefinition tool) |
|
||||
{ |
|
||||
if (string.IsNullOrWhiteSpace(tool.Name)) |
|
||||
{ |
|
||||
_logger.LogWarning($"Skipping tool with empty name"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (tool.Name.Length > MaxToolNameLength) |
|
||||
{ |
|
||||
_logger.LogWarning($"Skipping tool '{tool.Name}' with name exceeding {MaxToolNameLength} characters"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (!ToolNameRegex.IsMatch(tool.Name)) |
|
||||
{ |
|
||||
_logger.LogWarning($"Skipping tool with invalid name format: {tool.Name}"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
private bool IsValidDescription(McpToolDefinition tool) |
|
||||
{ |
|
||||
if (string.IsNullOrWhiteSpace(tool.Description)) |
|
||||
{ |
|
||||
_logger.LogWarning($"Skipping tool '{tool.Name}' with empty description"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (tool.Description.Length > MaxDescriptionLength) |
|
||||
{ |
|
||||
_logger.LogWarning($"Skipping tool '{tool.Name}' with description exceeding {MaxDescriptionLength} characters"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
private bool IsValidInputSchema(McpToolDefinition tool) |
|
||||
{ |
|
||||
if (!ArePropertiesValid(tool)) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (!AreRequiredFieldsValid(tool)) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
private bool ArePropertiesValid(McpToolDefinition tool) |
|
||||
{ |
|
||||
if (tool.InputSchema.Properties == null) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
foreach (var property in tool.InputSchema.Properties) |
|
||||
{ |
|
||||
if (string.IsNullOrWhiteSpace(property.Value?.Type) || |
|
||||
!ValidTypeValues.Contains(property.Value.Type)) |
|
||||
{ |
|
||||
_logger.LogWarning($"Skipping tool '{tool.Name}' with invalid property type: {property.Value?.Type ?? UnknownToolName}"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (property.Value.Description != null && property.Value.Description.Length > MaxDescriptionLength) |
|
||||
{ |
|
||||
_logger.LogWarning($"Skipping tool '{tool.Name}' with property description exceeding {MaxDescriptionLength} characters"); |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
private bool AreRequiredFieldsValid(McpToolDefinition tool) |
|
||||
{ |
|
||||
if (tool.InputSchema.Required == null || tool.InputSchema.Properties == null) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
foreach (var required in tool.InputSchema.Required) |
|
||||
{ |
|
||||
if (!tool.InputSchema.Properties.ContainsKey(required)) |
|
||||
{ |
|
||||
_logger.LogWarning($"Skipping tool '{tool.Name}' with required field '{required}' not in properties"); |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Loading…
Reference in new issue