diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs index 48d0f616..94855e12 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs @@ -248,7 +248,12 @@ public class McpDevelopAppService( return Problem(McpErrorCodes.InvalidIdentifier, entityName ?? string.Empty); } - var seedFile = CrudEndpointSeedFileDto.Generate(entity, operationTypes, objectType, Clock.Now.ToUniversalTime()); + // Model listeyi JSON dizisi olarak da gonderebilir (["GetList","Create"]); metne cevrilen dizi + // virgullu listeye indirilir. + var operations = operationTypes is null + ? null + : string.Join(',', operationTypes.Split(',').Select(item => item.Trim('[', ']', '"', ' '))); + var seedFile = CrudEndpointSeedFileDto.Generate(entity, operations, objectType, Clock.Now.ToUniversalTime()); seedFile.Write(configuration); await timesheetRecorder.RecordAsync(McpTimesheetTexts.GenerateCrudEndpoints, entity); @@ -319,6 +324,16 @@ public class McpDevelopAppService( var previous = wizard.EditFileName is null ? null : (await wizardAppService.GetFile(wizard.EditFileName)).Wizard; wizard.MenuOrder = previous?.MenuOrder ?? 0; + // deploy_wizard dosyayi tamamen degistirir. Duzenlemede WizardCode yazilmamissa kod sessizce + // App.Wizard.{Ad}'a duser: girdi kismidir ve deploy ekranin menusunu, yetkilerini ve alanlarini + // bosaltirdi. Kodu bilerek degistiren girdi WizardCode'u acikca yazar. + if (previous is not null && string.IsNullOrWhiteSpace(wizard.ListFormCode) && + !string.IsNullOrWhiteSpace(previous.ListFormCode) && + !string.Equals(previous.ListFormCode.Trim(), wizard.ResolveListFormCode(), StringComparison.OrdinalIgnoreCase)) + { + return Problem(McpErrorCodes.WizardCodeMissing, wizard.EditFileName!, previous.ListFormCode.Trim()); + } + // Alt form sekmesi ana kayit her degistiginde yeniden sorgulanmaz; MCP yeni baglanti // acarken yenilemeyi kapali yazar. Var olan baglantida kullanicinin ekrandan verdigi // deger korunur. diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs index fc610604..00c876b8 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs @@ -34,6 +34,7 @@ public static class McpErrorCodes public const string DevGuideSearchMatches = Prefix + "DevGuideSearchMatches"; public const string InvalidWizard = Prefix + "InvalidWizard"; public const string InvalidWizardValue = Prefix + "InvalidWizardValue"; + public const string WizardCodeMissing = Prefix + "WizardCodeMissing"; public const string SeedRowsInvalid = Prefix + "SeedRowsInvalid"; public const string SeedRowsNotAllowed = Prefix + "SeedRowsNotAllowed"; public const string SeedRowsUnknownFields = Prefix + "SeedRowsUnknownFields"; diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpHttpApiModule.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpHttpApiModule.cs index 2951be40..ade46bf9 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpHttpApiModule.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpHttpApiModule.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using ModelContextProtocol; using ModelContextProtocol.AspNetCore; using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; using Sozsoft.Mcp.Tools; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.Modularity; @@ -85,6 +86,8 @@ public class McpHttpApiModule : AbpModule // loglar. Hata sonucu (IsError) olarak donulur; model nedeni okuyup devam eder. filters.AddCallToolFilter(next => async (requestContext, cancellationToken) => { + McpToolArgumentNormalizer.Normalize(requestContext.Params, requestContext.MatchedPrimitive as McpServerTool); + try { return await next(requestContext, cancellationToken); diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpToolArgumentNormalizer.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpToolArgumentNormalizer.cs new file mode 100644 index 00000000..1fba379b --- /dev/null +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpToolArgumentNormalizer.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; + +namespace Sozsoft.Mcp; + +/// +/// Model metin parametresine tirnaksiz deger (section: 4.2) ya da JSON metni yerine dogrudan +/// dizi/nesne (filter: ["Status","=","Open"]) gonderebilir. SDK her parametreyi ayri ayri +/// string olarak bagladigi icin bu deger ("Cannot get the value of a +/// token type 'Number' / 'StartArray' as a string") ile duser. Deger cagri araca ulasmadan, aracin kendi +/// semasina bakilarak ham JSON metnine cevrilir — JSON bekleyen parametre ayni metni alir; string icin +/// ozel converter kullanilmaz, cunku arac semasindaki "type": "string" bilgisini siler. +/// +internal static class McpToolArgumentNormalizer +{ + public static void Normalize(CallToolRequestParams? parameters, McpServerTool? tool) + { + if (parameters?.Arguments is not { Count: > 0 } arguments + || tool is null + || !tool.ProtocolTool.InputSchema.TryGetProperty("properties", out var properties) + || properties.ValueKind != JsonValueKind.Object) + { + return; + } + + Dictionary? normalized = null; + + foreach (var (name, value) in arguments) + { + if (value.ValueKind is JsonValueKind.String or JsonValueKind.Null or JsonValueKind.Undefined + || !properties.TryGetProperty(name, out var schema) + || !IsStringOnly(schema)) + { + continue; + } + + normalized ??= new Dictionary(arguments); + normalized[name] = JsonSerializer.SerializeToElement(value.GetRawText()); + } + + if (normalized is not null) + { + parameters.Arguments = normalized; + } + } + + // Nullable metin parametresi semada ["string", "null"] olarak gelir; baska tip de kabul eden bir + // birlesim tipi donusturulmez, degeri SDK oldugu gibi baglar. + private static bool IsStringOnly(JsonElement schema) + { + if (!schema.TryGetProperty("type", out var type)) + { + return false; + } + + return type.ValueKind switch + { + JsonValueKind.String => type.GetString() == "string", + JsonValueKind.Array => type.EnumerateArray() + .Select(item => item.GetString()) + .Where(item => item != "null") + .SequenceEqual(["string"]), + _ => false + }; + } +} diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs index 90f5aed5..69439f62 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs @@ -101,7 +101,7 @@ public sealed class McpDevelopTools(IMcpDevelopAppService developAppService, IMc public async Task DeployWizardAsync( [Description("Wizard JSON'u: seed dosyasi bicimi ({\"Wizard\": {...}, \"ListForm\": {...}, \"Fields\": [...]}, get_wizard cikisi gibi) ya da duz wizard cevaplari. ListForm/Fields istege baglidir ve kismi olabilir. Enum alanlari sayi olarak yazilir.")] string wizard, - [Description("Guncellemede yerine gecilecek dosyanin adi (list_wizards cikisindaki fileName). Yeni wizard icin bos birak.")] + [Description("Guncellemede yerine gecilecek dosyanin adi (list_wizards cikisindaki fileName). Yeni wizard icin bos birak. Guncelleme dosyayi tamamen degistirir: wizard girdisi get_wizard ile okunan dosyanin degistirilmis tam halidir; Wizard bolumu (WizardCode, Menu, Language, Groups) eksiksiz gonderilir.")] string? replaceFileName = null, [Description("Demo satirlar: JSON nesne dizisi, anahtarlar ekranin alan adlari ([{\"Name\": \"...\"}]). Guid anahtar, TenantId ve audit alanlarini yazma; sunucu ve migrate doldurur. Yalnizca SeedData true olan liste ekraninda.")] string? seedRows = null) diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs index 351987ed..b4d6d201 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs @@ -1776,7 +1776,11 @@ public class ListFormWizardAppService( $"'{wizardName}' adında bir Wizard zaten var ({duplicateFile}). Farklı bir ad verin ya da mevcut kaydı Wizard Manager üzerinden düzenleyin."); } - if (previous?.InsertedRecords?.MenuCodes.Contains(code, StringComparer.OrdinalIgnoreCase) == true) + // Migrate bekleyen onceki surumun menusu (Pending.Replaced) de bu wizard'indir: veritabaninda + // durur ama yeni deploy onu geri alabilir; aksi halde kodunu degistiren deploy'u geri almak + // kendi menusuyle catisir. + var owned = (previous?.InsertedRecords ?? new WizardInsertedRecordsDto()).Union(previous?.Pending?.Replaced); + if (owned.MenuCodes.Contains(code, StringComparer.OrdinalIgnoreCase)) return; if (rows.Contains(WizardSeedRowStore.MenuFile, nameof(Menu.Code), code) || diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs index 44a16fdf..21af4334 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs @@ -278,7 +278,7 @@ public static class PlatformConsts public const string AdminNameDefaultValue = "Sedat"; public const string AdminSurNameDefaultValue = "ÖZTÜRK"; public const string AdminEmailDefaultValue = "system@sozsoft.com"; - public const string AdminPasswordDefaultValue = "1q2w3E*"; + public const string AdminPasswordDefaultValue = "K1r1Ut7Z2o@@"; public const string AdminPhoneNumberDefaultValue = "05449476346"; public const string AdminRocketUsernameDefaultValue = "sedat.ozturk"; public const string AdminWorkHourDefaultValue = "00:00-23:59";