diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs index e398a7747a..a409614712 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs @@ -17,12 +17,6 @@ public class CommandSelector : ICommandSelector, ITransientDependency public Type Select(CommandLineArgs commandLineArgs) { - // Don't fall back to HelpCommand for MCP command to avoid corrupting stdout JSON-RPC stream - if (commandLineArgs.IsMcpCommand()) - { - return Options.Commands.GetOrDefault("mcp") ?? typeof(HelpCommand); - } - if (commandLineArgs.Command.IsNullOrWhiteSpace()) { return typeof(HelpCommand); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs index b6c1cfba11..c051f6d455 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs @@ -32,14 +32,6 @@ public class HelpCommand : IConsoleCommand, ITransientDependency public Task ExecuteAsync(CommandLineArgs commandLineArgs) { - // Don't output help text for MCP command to avoid corrupting stdout JSON-RPC stream - // If MCP command is being used, it should have been handled directly, not through HelpCommand - if (commandLineArgs.IsMcpCommand()) - { - // Silently return - MCP server should handle its own errors - return Task.CompletedTask; - } - if (string.IsNullOrWhiteSpace(commandLineArgs.Target)) { Logger.LogInformation(GetUsageInfo()); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpHttpClientService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpHttpClientService.cs index b27c3740d6..c15a6c06b6 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpHttpClientService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpHttpClientService.cs @@ -19,13 +19,6 @@ public class McpHttpClientService : ISingletonDependency { private static readonly JsonSerializerOptions JsonSerializerOptionsWeb = new(JsonSerializerDefaults.Web); - private static class ErrorMessages - { - public const string NetworkConnectivity = "The tool execution failed due to a network connectivity issue. Please check your internet connection and try again."; - public const string Timeout = "The tool execution timed out. The operation took too long to complete. Please try again."; - public const string Unexpected = "The tool execution failed due to an unexpected error. Please try again later."; - } - private const string LogSource = nameof(McpHttpClientService); private readonly CliHttpClientFactory _httpClientFactory; @@ -46,40 +39,6 @@ public class McpHttpClientService : ISingletonDependency _cachedServerUrlLazy = new Lazy>(GetMcpServerUrlInternalAsync); } - private async Task GetMcpServerUrlAsync() - { - return await _cachedServerUrlLazy.Value; - } - - private async Task GetMcpServerUrlInternalAsync() - { - // Check config file - if (File.Exists(CliPaths.McpConfig)) - { - try - { - var json = await FileHelper.ReadAllTextAsync(CliPaths.McpConfig); - var config = JsonSerializer.Deserialize(json, JsonSerializerOptionsWeb); - if (!string.IsNullOrWhiteSpace(config?.ServerUrl)) - { - return config.ServerUrl.TrimEnd('/'); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to read MCP config file"); - } - } - - // Return default - return CliConsts.DefaultMcpServerUrl; - } - - private class McpConfig - { - public string ServerUrl { get; set; } - } - public void InitializeToolNames(List tools) { _validToolNames = tools.Select(t => t.Name).ToList(); @@ -152,37 +111,6 @@ public class McpHttpClientService : ISingletonDependency } } - private string CreateErrorResponse(string errorMessage) - { - return JsonSerializer.Serialize(new - { - content = new[] - { - new - { - type = "text", - text = errorMessage - } - }, - isError = true - }, JsonSerializerOptionsWeb); - } - - private string GetSanitizedHttpErrorMessage(HttpStatusCode statusCode) - { - return statusCode switch - { - HttpStatusCode.Unauthorized => "Authentication failed. Please ensure you are logged in with a valid account.", - HttpStatusCode.Forbidden => "Access denied. You do not have permission to use this tool.", - HttpStatusCode.NotFound => "The requested tool could not be found. It may have been removed or is temporarily unavailable.", - HttpStatusCode.BadRequest => "The tool request was invalid. Please check your input parameters and try again.", - (HttpStatusCode)429 => "Rate limit exceeded. Please wait a moment before trying again.", // TooManyRequests not available in .NET Standard 2.0 - HttpStatusCode.ServiceUnavailable => "The service is temporarily unavailable. Please try again later.", - HttpStatusCode.InternalServerError => "The tool execution encountered an internal error. Please try again later.", - _ => "The tool execution failed. Please try again later." - }; - } - public async Task CheckServerHealthAsync() { var baseUrl = await GetMcpServerUrlAsync(); @@ -258,6 +186,66 @@ public class McpHttpClientService : ISingletonDependency } } + private async Task GetMcpServerUrlAsync() + { + return await _cachedServerUrlLazy.Value; + } + + private async Task GetMcpServerUrlInternalAsync() + { + // Check config file + if (File.Exists(CliPaths.McpConfig)) + { + try + { + var json = await FileHelper.ReadAllTextAsync(CliPaths.McpConfig); + var config = JsonSerializer.Deserialize(json, JsonSerializerOptionsWeb); + if (!string.IsNullOrWhiteSpace(config?.ServerUrl)) + { + return config.ServerUrl.TrimEnd('/'); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to read MCP config file"); + } + } + + // Return default + return CliConsts.DefaultMcpServerUrl; + } + + private string CreateErrorResponse(string errorMessage) + { + return JsonSerializer.Serialize(new + { + content = new[] + { + new + { + type = "text", + text = errorMessage + } + }, + isError = true + }, JsonSerializerOptionsWeb); + } + + private string GetSanitizedHttpErrorMessage(HttpStatusCode statusCode) + { + return statusCode switch + { + HttpStatusCode.Unauthorized => "Authentication failed. Please ensure you are logged in with a valid account.", + HttpStatusCode.Forbidden => "Access denied. You do not have permission to use this tool.", + HttpStatusCode.NotFound => "The requested tool could not be found. It may have been removed or is temporarily unavailable.", + HttpStatusCode.BadRequest => "The tool request was invalid. Please check your input parameters and try again.", + (HttpStatusCode)429 => "Rate limit exceeded. Please wait a moment before trying again.", // TooManyRequests not available in .NET Standard 2.0 + HttpStatusCode.ServiceUnavailable => "The service is temporarily unavailable. Please try again later.", + HttpStatusCode.InternalServerError => "The tool execution encountered an internal error. Please try again later.", + _ => "The tool execution failed. Please try again later." + }; + } + private CliUsageException CreateHttpExceptionWithInner(Exception ex, string context) { _mcpLogger.Error(LogSource, context, ex); @@ -273,9 +261,20 @@ public class McpHttpClientService : ISingletonDependency return new CliUsageException($"Failed to fetch tool definitions: {userMessage}", ex); } + private static class ErrorMessages + { + public const string NetworkConnectivity = "The tool execution failed due to a network connectivity issue. Please check your internet connection and try again."; + public const string Timeout = "The tool execution timed out. The operation took too long to complete. Please try again."; + public const string Unexpected = "The tool execution failed due to an unexpected error. Please try again later."; + } + + private class McpConfig + { + public string ServerUrl { get; set; } + } + private class McpToolsResponse { public List Tools { get; set; } } } - diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpServerService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpServerService.cs index 7be6af6688..80fdcd3b7d 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpServerService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpServerService.cs @@ -74,7 +74,7 @@ public class McpServerService : ITransientDependency private void RegisterToolFromDefinition(McpServerOptions options, McpToolDefinition toolDef) { - // Convert McpToolDefinition to the input schema format expected by MCP + // Build input schema with lowercase keys as required by MCP JSON Schema format var inputSchemaObject = new Dictionary { ["type"] = "object", @@ -85,7 +85,7 @@ public class McpServerService : ITransientDependency RegisterTool(options, toolDef.Name, toolDef.Description, inputSchemaObject, toolDef.OutputSchema); } - private Dictionary ConvertProperties(Dictionary properties) + private static Dictionary ConvertProperties(Dictionary properties) { if (properties == null) {