194 lines
15 KiB
C#
194 lines
15 KiB
C#
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Threading.Tasks;
|
|
using ModelContextProtocol.Protocol;
|
|
using ModelContextProtocol.Server;
|
|
using Sozsoft.Mcp.Dtos;
|
|
using Sozsoft.Platform.DeveloperKit;
|
|
using Sozsoft.Platform.ListForms;
|
|
|
|
namespace Sozsoft.Mcp.Tools;
|
|
|
|
/// <summary>
|
|
/// Ekran (wizard) ve custom component gelistirme araclari. Uretim uygulamadaki Wizard ve
|
|
/// Custom Components servislerinden gecer; menu, yetki, dil anahtari ve seed dosyasi ekrandaki
|
|
/// yolla ayni sekilde olusur.
|
|
/// </summary>
|
|
[McpServerToolType]
|
|
public sealed class McpDevelopTools(IMcpDevelopAppService developAppService)
|
|
{
|
|
private static readonly JsonSerializerOptions TextOptions = new(JsonSerializerDefaults.Web);
|
|
|
|
[McpServerTool(Name = McpToolNames.GetDevGuide, ReadOnly = true)]
|
|
[Description("Ekran ve bilesen uretim sozlesmesini okur: wizard alanlari, enum degerleri, editor/lookup/script sozlesmesi, custom component kurallari. section bos ise baslik listesi doner; once listeyi al, sonra ihtiyac duydugun bolumu numarasiyla iste (ornegin 4.2). Rehberde dosya yazmak anlatilan yerlerde dosya yerine bu sunucunun araclarini kullan.")]
|
|
public async Task<McpDevGuideDto> GetDevGuideAsync(
|
|
[Description("'lowcode' (uretim protokolu ve artefakt semalari, varsayilan) ya da 'reference' (JSON kolon sozlesmeleri, tasarimci envanteri).")]
|
|
string? document = null,
|
|
[Description("Bolum numarasi (4.2) ya da baslik metninin bir parcasi. Bos birakilirsa baslik listesi doner.")]
|
|
string? section = null)
|
|
=> await developAppService.GetDevGuideAsync(document, section);
|
|
|
|
[McpServerTool(Name = McpToolNames.CreateTable, Destructive = false, Idempotent = false)]
|
|
[Description("Ekranin baglanacagi yeni tabloyu SQL Query Manager tablo tasarimcisiyla ayni bicimde olusturur ve script'i seed dosyasina (sql/object) yazar. Id (uniqueidentifier, PK), TenantId ve tam audit kolonlari (CreationTime, CreatorId, LastModificationTime, LastModifierId, IsDeleted, DeletionTime, DeleterId) otomatik eklenir; columns icine yalnizca is kolonlarini yaz. Yalnizca yeni tablo olusturur: tablo varsa reddeder, kolon ekleme/degistirme tablo tasarimcisindan yapilir. SQL Server veri kaynaginda calisir. Ardindan deploy_wizard ile ekrani kur.")]
|
|
public async Task<McpCreateTableResultDto> CreateTableAsync(
|
|
[Description("Tablo adi; harf/rakam/alt cizgi. Adlandirma icin get_dev_guide'daki tablo onek kuralina uy (ornegin Adm_T_...).")]
|
|
string tableName,
|
|
[Description("Is kolonlari. type: nvarchar, nvarchar(MAX), int, bigint, decimal (18,4), float, bit, datetime, datetime2, date, uniqueidentifier, money. maxLength yalnizca nvarchar icin (varsayilan 100). defaultValue: sayi, 'metin' ya da NEWID()/GETDATE()/GETUTCDATE()/SYSUTCDATETIME().")]
|
|
List<McpTableColumnDto> columns,
|
|
[Description("Tam audit kolonlari eklensin mi. Kullanici acikca istemedikce true birak.")]
|
|
bool fullAudited = true,
|
|
[Description("TenantId kolonu eklensin mi. Kullanici acikca istemedikce true birak.")]
|
|
bool multiTenant = true)
|
|
=> await developAppService.CreateTableAsync(new McpCreateTableRequestDto
|
|
{
|
|
TableName = tableName,
|
|
Columns = columns,
|
|
FullAudited = fullAudited,
|
|
MultiTenant = multiTenant
|
|
});
|
|
|
|
[McpServerTool(Name = McpToolNames.ListWizards, ReadOnly = true)]
|
|
[Description("Wizard File Manager'daki wizard dosyalarini listeler: dosya adi, ekran kodu, yol (List/Custom), menu yolu ve bagimli wizard'lar.")]
|
|
public async Task<List<WizardFileInfoDto>> ListWizardsAsync(
|
|
[Description("Dosya adi, wizard adi, ekran kodu ya da bilesen adi icinde aranacak metin. Bos birakilabilir.")]
|
|
string? search = null)
|
|
=> await developAppService.GetWizardsAsync(search);
|
|
|
|
[McpServerTool(Name = McpToolNames.GetWizard, ReadOnly = true)]
|
|
[Description("Bir wizard seed dosyasini diskteki bicimiyle dondurur (Wizard / ListForm / Fields bolumleri). Var olan bir ekrani degistirmeden once oku.")]
|
|
public async Task<McpWizardFileDto> GetWizardAsync(
|
|
[Description("list_wizards cikisindaki fileName degeri, ornegin 'Tags.json'.")]
|
|
string fileName)
|
|
=> await developAppService.GetWizardAsync(fileName);
|
|
|
|
[McpServerTool(Name = McpToolNames.DeployWizard, Destructive = true, Idempotent = false)]
|
|
[Description("Wizard'i deploy eder: ListForm ekrani (ComponentKind 0) ya da bir custom component'i menuye baglayan kayit (ComponentKind 1), yetkileri, dil anahtarlari, menusu ve seed dosyasi birlikte uretilir. Liste ekraninda Grid, Card, Pivot ve Chart gorunumleri varsayilan aciktir (DefaultLayout grid); kullanici bir gorunumu istemedigini soylemedikce bu bayraklari false yazma. Hedef tablo onceden var olmalidir; yoksa once create_table ile olustur. Istek ornek/demo veri tasiyorsa (prompt'taki tablo/liste ya da ekran goruntusunde gorunen satirlar) SeedData true yaz ve deploy'dan sonra her satiri insert_row ile ekle: satirlar seed klasorundeki data/{ListFormCode}.json dosyasina yazilir ve migrate ile ekranla birlikte geri yuklenir; SeedData olmadan eklenen satir yalnizca bu veritabaninda kalir. replaceFileName verilirse o wizard ve urettigi kayitlar silinip yeniden uretilir; ekran wizard cevaplarindan yeniden turetildigi icin tasarimcida yapilmis ve wizard'da karsiligi olmayan ayarlar kaybolur. Ne yazacagini get_dev_guide 4. bolumunden ogren.")]
|
|
public async Task<WizardFileInfoDto?> DeployWizardAsync(
|
|
[Description("Wizard JSON'u: seed dosyasi bicimi ({\"Wizard\": {...}}, get_wizard cikisi gibi) ya da duz wizard cevaplari. Enum alanlari sayi olarak yazilir.")]
|
|
string wizard,
|
|
[Description("Guncellemede yerine gecilecek dosyanin adi (list_wizards cikisindaki fileName). Yeni wizard icin bos birak.")]
|
|
string? replaceFileName = null)
|
|
=> await developAppService.DeployWizardAsync(new McpWizardDeployRequestDto { Wizard = wizard, ReplaceFileName = replaceFileName });
|
|
|
|
[McpServerTool(Name = McpToolNames.DeleteWizard, Destructive = true, Idempotent = true)]
|
|
[Description("Bir wizard'i ve yalnizca onun urettigi kayitlari siler; baska yerde kullanilan kayitlar korunur ve raporda gosterilir. Ekranin veri aynasi (data/{ListFormCode}.json) ekranla birlikte gider. Bagimli wizard'i olan bir wizard silinemez, once bagimlilar silinir. Ekranin tablosu varsayilan olarak kalir; dropTables true ise tablo DROP edilir, CRUD endpoint'leri ve sql/object + crud seed dosyalari da silinir. Tablo yalnizca create_table ya da tablo tasarimcisiyla olusturulmussa (sql/object scripti varsa) ve baska ekran, Custom Endpoint ya da bilesen kullanmiyorsa dusurulur; aksi halde raporda neden korundugu yazar. Tablo silmek geri alinamaz ve App.SqlQueryManager yetkisi ister: dropTables true gondermeden once kullaniciya tablonun ve icindeki verinin silinecegini soyle ve acik onay al.")]
|
|
public async Task<WizardDeleteResultDto> DeleteWizardAsync(
|
|
[Description("Silinecek wizard dosyasinin adi (list_wizards cikisindaki fileName).")]
|
|
string fileName,
|
|
[Description("Ekranin tablosu da dusurulsun mu. Yalnizca kullanici acikca onayladiysa true.")]
|
|
bool dropTables = false)
|
|
=> await developAppService.DeleteWizardAsync(fileName, dropTables);
|
|
|
|
[McpServerTool(Name = McpToolNames.VerifyWizard, ReadOnly = true)]
|
|
[Description("Deploy edilmis bir wizard'i veri yazmadan dogrular: menu kaydi (var mi, etkin mi, URL'si wizard'la ayni mi), menunun istedigi yetki tanimi, liste ekraninda ekranin sorgusu (cagiranin yetkisiyle tek satir), Custom yolunda bilesen (var mi, etkin mi, rotasi menuyle ayni mi) ve seed kapanisi (migrate ya da baska ortamda eksik kalacak bagimlilik). passed yalnizca blocking kontrollerden biri kalirsa false olur; seed_closure engellemez ama raporlanmalidir. deploy_wizard'dan sonra ve bir gelistirme talebini Staging durumuna almadan once cagir.")]
|
|
public async Task<McpVerifyResultDto> VerifyWizardAsync(
|
|
[Description("Dogrulanacak wizard dosyasinin adi (list_wizards ya da deploy_wizard cikisindaki fileName).")]
|
|
string fileName)
|
|
=> await developAppService.VerifyWizardAsync(fileName);
|
|
|
|
[McpServerTool(Name = McpToolNames.StartDevRequest, Destructive = false, Idempotent = false)]
|
|
[Description("App.DevRequests kuyrugundaki bir gelistirme talebini ustlenir: talep Ready to Start durumundaysa In Progress'e alir ve talebin metnini (baslik, HTML aciklama, oncelik, etiketler, SubTasks) dondurur; baska bir durumdaysa reddeder. Aciklamaya yapistirilmis gorseller ayri resim icerigi olarak gelir, aciklamada yerlerinde [image-N] isareti durur; gorseldeki ekran duzeni ve satirlar talebin parcasidir (demo veri icin deploy_wizard SeedData). Bir talep uzerinde calismaya bu aracla baslanir, Status'u update_row ile In Progress yapma. Sonraki adimlar icin get_dev_guide lowcode 0.11.")]
|
|
public async Task<IList<ContentBlock>> StartDevRequestAsync(
|
|
[Description("Talebin Id'si (GUID) ya da tam basligi. Ayni basligi tasiyan birden fazla talep varsa Id ver.")]
|
|
string request)
|
|
=> ToContent(await developAppService.StartDevRequestAsync(request));
|
|
|
|
[McpServerTool(Name = McpToolNames.GetDevRequest, ReadOnly = true)]
|
|
[Description("Bir gelistirme talebini durumuna dokunmadan okur; start_dev_request ile ayni cevabi (aciklama + ayri resim icerikleri) dondurur. Yarim kalmis (In Progress/Testing) bir talebe donerken ya da gorselleri yeniden gormek icin kullan; talebi ustlenmek icin degil.")]
|
|
public async Task<IList<ContentBlock>> GetDevRequestAsync(
|
|
[Description("Talebin Id'si (GUID) ya da tam basligi.")]
|
|
string request)
|
|
=> ToContent(await developAppService.GetDevRequestAsync(request));
|
|
|
|
[McpServerTool(Name = McpToolNames.AppendDevRequestNote, Destructive = false, Idempotent = false)]
|
|
[Description("Talebin HTML aciklamasinin sonuna ekleme yapar; var olan metne ve gorsellere dokunmaz. Aciklama gomulu gorsel tasidigi icin Description'i update_row ile yeniden yazma, sonuc notunu (<h4>MCP</h4> + verify_wizard ozeti) bu aracla ekle.")]
|
|
public async Task<string> AppendDevRequestNoteAsync(
|
|
[Description("Talebin Id'si (GUID) ya da tam basligi.")]
|
|
string request,
|
|
[Description("Eklenecek HTML parcasi.")]
|
|
string html)
|
|
{
|
|
await developAppService.AppendDevRequestNoteAsync(request, html);
|
|
return "ok";
|
|
}
|
|
|
|
[McpServerTool(Name = McpToolNames.AddDevRequestSubTasks, Destructive = false, Idempotent = false)]
|
|
[Description("Talebin Sub Tasks listesine uretilen her artefakt icin tamamlanmis bir satir ekler; atanan kisi baglanan kullanici, bitis tarihi bugun olarak sunucuda yazilir, var olan satirlar korunur. SubTasks alanini update_row ile yazma.")]
|
|
public async Task<string> AddDevRequestSubTasksAsync(
|
|
[Description("Talebin Id'si (GUID) ya da tam basligi.")]
|
|
string request,
|
|
[Description("Eklenecek satir basliklari, ornegin [\"create_table: Hr_T_Certificate\", \"deploy_wizard: Certificates.json\"].")]
|
|
List<string> titles)
|
|
{
|
|
await developAppService.AddDevRequestSubTasksAsync(request, titles);
|
|
return "ok";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Talep metni JSON olarak, gorseller ayri resim icerigi olarak doner; model resmi ancak bu
|
|
/// bicimde gorur, base64 metni olarak goremez.
|
|
/// </summary>
|
|
private static List<ContentBlock> ToContent(McpDevRequestDto devRequest)
|
|
{
|
|
List<ContentBlock> content = [new TextContentBlock { Text = JsonSerializer.Serialize(devRequest, TextOptions) }];
|
|
|
|
foreach (var image in devRequest.Images)
|
|
{
|
|
// Veri zaten base64; cozup yeniden kodlamak buyuk gorsellerde bosuna bellek harcar.
|
|
content.Add(new ImageContentBlock { Data = Encoding.UTF8.GetBytes(image.Data), MimeType = image.MimeType });
|
|
}
|
|
|
|
return content;
|
|
}
|
|
|
|
[McpServerTool(Name = McpToolNames.ListComponents, ReadOnly = true)]
|
|
[Description("Custom component'leri ad, rota ve aciklamasiyla listeler; kod dondurmez. isVisualDesigner true olanlarin kodu tasarimci belgesinden uretilir ve MCP ile degistirilemez.")]
|
|
public async Task<List<McpComponentSummaryDto>> ListComponentsAsync(
|
|
[Description("Ad, rota ya da aciklama icinde aranacak metin. Bos birakilabilir.")]
|
|
string? search = null)
|
|
=> await developAppService.GetComponentsAsync(search);
|
|
|
|
[McpServerTool(Name = McpToolNames.GetComponent, ReadOnly = true)]
|
|
[Description("Bir custom component'in kodunu, props'unu ve bagimliliklarini dondurur. Degistirmeden once oku.")]
|
|
public async Task<CustomComponentDto?> GetComponentAsync(
|
|
[Description("Bilesenin adi.")]
|
|
string name)
|
|
=> await developAppService.GetComponentAsync(name);
|
|
|
|
[McpServerTool(Name = McpToolNames.SaveComponent, Destructive = true, Idempotent = true)]
|
|
[Description("Custom component'i ada gore kaydeder: yoksa ekler, varsa gunceller. Guncellemede verilmeyen alanlar korunur. Kod kurallari ve kullanilabilir kutuphaneler icin get_dev_guide 7. bolumu. Bilesen menude gorunecekse ardindan ComponentKind 1 ile deploy_wizard cagir.")]
|
|
public async Task<CustomComponentDto> SaveComponentAsync(
|
|
[Description("Bilesenin adi; kimligidir.")]
|
|
string name,
|
|
[Description("Bilesenin React kodu. Yeni bilesende zorunlu, guncellemede bos birakilirsa mevcut kod korunur.")]
|
|
string? code = null,
|
|
[Description("'/' ile baslayan rota. '/admin/' ile baslamayan rota public sayfa olur. Bos dize rotayi kaldirir, verilmezse mevcut rota korunur.")]
|
|
string? routePath = null,
|
|
[Description("Aciklama.")]
|
|
string? description = null,
|
|
[Description("Props JSON metni. Tasarimci belgesi (visualDesigner) iceremez.")]
|
|
string? props = null,
|
|
[Description("Bagimli bilesen adlarinin JSON dizisi, ornegin [\"PublicHeader\"].")]
|
|
string? dependencies = null,
|
|
[Description("Bilesen etkin mi. Yeni bilesende varsayilan true.")]
|
|
bool? isActive = null)
|
|
=> await developAppService.SaveComponentAsync(new McpComponentSaveRequestDto
|
|
{
|
|
Name = name,
|
|
Code = code,
|
|
RoutePath = routePath,
|
|
Description = description,
|
|
Props = props,
|
|
Dependencies = dependencies,
|
|
IsActive = isActive
|
|
});
|
|
|
|
[McpServerTool(Name = McpToolNames.DeleteComponent, Destructive = true, Idempotent = false)]
|
|
[Description("Custom component'i siler. Bilesene bagli bir Custom wizard varsa once delete_wizard ile o silinir.")]
|
|
public async Task DeleteComponentAsync(
|
|
[Description("Bilesenin adi.")]
|
|
string name)
|
|
=> await developAppService.DeleteComponentAsync(name);
|
|
}
|