MCP Servers Develop eklendi

This commit is contained in:
Sedat ÖZTÜRK 2026-09-18 16:47:23 +03:00
parent 197b3775f8
commit d21e87c26f
39 changed files with 1373 additions and 154 deletions

View file

@ -363,7 +363,9 @@ AI workflow integration:
MCP (`/mcp`, `Sozsoft.Mcp` module) — expose ListForm data/queries to an external AI agent
(Claude, ChatGPT, or an OAuth-registered MCP client) without new code: configure a server row
on the **MCP Servers** screen (`App.DeveloperKit.McpServers`, §12.2 of
`lowcode-reference.instructions.md`) with its allowed tools, datasource and tenant scope. A
`lowcode-reference.instructions.md`) with its allowed tools and tenant scope; queries always run
on the calling tenant's own database. Tenant users are read-only over MCP (schema tools,
`query_list`, `run_select`); writing and development tools are host-only. A
request to "let an agent query/update X" is a Decision Flow §1 (ListForm) request first — MCP
only gates which of the existing screens/queries an already-authorized client may reach; it
never grants access beyond the calling user's own ListForm/query permissions. Ayrıntı:

View file

@ -1189,7 +1189,7 @@ soneklerini alır.
| **Developer Kit › Components** | `App.DeveloperKit.CustomComponents` · `/admin/list/App.DeveloperKit.CustomComponents` | `Sas_H_CustomComponent` | host | Custom component listesi (ListForm) ve satırdaki **Design** butonuyla **Visual Designer** girişi (§10). Kayıtlar `data/App.DeveloperKit.CustomComponents.json` dosyasına senkronlanır. |
| **Developer Kit › ListForm** | `App.Listforms.Listform` | `Sas_H_ListForm` · `Sas_H_ListFormField` · `Sas_H_ListFormWorkflow` · `Sas_H_ListFormCustomization` · `Sas_H_ListFormImport` | host | Ekran tanımlarının kendisi. Bu dosyada anlatılan bütün JSON kolonlarının canlı hâli; düzenleyicisi `/admin/listform/edit/{kod}`. |
| **Developer Kit › Wizard Manager** | menü kodu `App.Listforms.WizardManager` · yetki `App.Listforms.Wizard` · `/admin/listform/wizardManager` | — | host | Wizard seed dosyalarının listesi, düzenlemesi, silinmesi, export/import'u. Sihirbazın kendisi ayrı bir rotadadır: `/admin/listform/wizard`. |
| **Developer Kit › MCP Servers** | `App.DeveloperKit.McpServers` · `/admin/list/App.DeveloperKit.McpServers` | `Sas_H_McpServer` | host | `/mcp` ucunun kiracı bazlı sözleşmesi: `Name`, `Url`, `Tenants`, `DataSourceCode`, `AllowedTools`, `MaxRows`, `AllowWrite`. Araç listesi bu kayda göre süzülür, çağrı anında aynı kural yeniden uygulanır. Yetkiler: ekran için `App.DeveloperKit.McpServers`, araç çağırma için `.Invoke`, veri yazma için `.Write` (son ikisi kiracı kullanıcılarına da verilebilir). MCP yetki vermez: kullanıcı yalnızca uygulamada okuyabildiği listeleri görür ve sorgular; ham SQL `App.SqlQueryManager` ister. Kayıtlar `data/App.DeveloperKit.McpServers.json` dosyasına senkronlanır. Araçlar ve şema keşfinin kaynağı: `README.md` §13.6. |
| **Developer Kit › MCP Servers** | `App.DeveloperKit.McpServers` · `/admin/list/App.DeveloperKit.McpServers` | `Sas_H_McpServer` | host | `/mcp` ucunun kiracı bazlı sözleşmesi: `Name`, `Url`, `Tenants`, `AllowedTools` (boşsa tüm araçlar, doluysa yalnızca seçilenler açık), `MaxRows`. Araç listesi bu kayda göre süzülür, çağrı anında aynı kural yeniden uygulanır. Yetkiler: ekran için `App.DeveloperKit.McpServers`, araç çağırma için `.Invoke`, veri yazma için `.Write`, prompt ile tablo/ekran/bileşen geliştirme (`create_table`, wizard deploy, custom component kaydı) için `.Develop` (host; Wizard ve Custom Components yetkilerini ayrıca ister). Kiracı kullanıcısı yetkisi ne olursa olsun yalnızca okur (`McpToolNames.TenantTools`: şema araçları, `query_list`, `run_select`); yazma, `create_table`, wizard, component ve `run_query` kiracıya kapalıdır. Host için tek sunucu anahtarı `AllowedTools`. MCP yetki vermez: kullanıcı yalnızca uygulamada okuyabildiği listeleri görür ve sorgular; ham SQL `App.SqlQueryManager` ister. Kayıtlar `data/App.DeveloperKit.McpServers.json` dosyasına senkronlanır. Araçlar ve şema keşfinin kaynağı: `README.md` §13.6. |
| **Forum Management** | `App.ForumManagement` (grup menüsü) | `Sas_T_ForumCategory` · `Sas_T_ForumTopic` · `Sas_T_ForumPost` | tenant | Altında Categories / Topics / Posts ListForm ekranları; yayın tarafı Administration'daki `/admin/forum`. |
| **Forum Categories** | `App.ForumManagement.Categories` · `/admin/list/App.ForumManagement.Categories` | `Sas_T_ForumCategory` | tenant | Forum kategorilerinin ListForm ekranı; veri `configs/seeds/host/data/App.ForumManagement.Categories.json` dosyasından seed edilir. |
| **Forum Topics** | `App.ForumManagement.Topics` · `/admin/list/App.ForumManagement.Topics` | `Sas_T_ForumTopic` | tenant | Forum konuları; kategori lookup'ı, sayaçlar ve sabitle/kilitle/çözüldü bayrakları. |
@ -1326,7 +1326,8 @@ yazma, oradan yeni bir Custom Component üret. `Services` sayfası yoktur.
| "mesai dışında girilemesin" / "sadece ofis IP'si" | Administration › Restrictions | Work Hour · IP Restriction kaydı |
| "kim değiştirmiş görelim" | Administration › Identity › Audit Logs | Hazır ekran; kayıt notu için `ShowNote` |
| "menüye eklensin" | Saas › Menus › Manager | Menü kaydı + yetki + route eşlemesi |
| "yapay zekâ ajanı sorgulasın / veri çeksin" | Saas › DeveloperKit › MCP Servers | Kayıt aç, `AllowedTools`/`DataSourceCode`/tenant kapsamını ayarla; yeni endpoint/kod yok |
| "yapay zekâ ajanı sorgulasın / veri çeksin" | Saas › DeveloperKit › MCP Servers | Kayıt aç, `AllowedTools`/tenant kapsamını ayarla; yeni endpoint/kod yok |
| "yapay zekâ ajanı ekran/bileşen geliştirsin" | Saas › DeveloperKit › MCP Servers | Yalnızca host kullanıcısı: `App.DeveloperKit.McpServers.Develop` + Wizard / Custom Components yetkilerini ver; geliştirme araçları `AllowedTools` ile açılıp kapatılır. Kiracı MCP'de yalnızca veri okur |
| "ülke/il/ilçe, para birimi, birim, departman, ünvan" | Saas / Administration › Definitions | Var olan tabloya lookup bağla, yenisini üretme |
| "yapay zekâ cevaplasın" | Saas › Definitions › AiBot | Bot tanımı; `/admin/ai` bu tanımı kullanır |

View file

@ -64,7 +64,7 @@ Tenant kapsamı isteniyorsa `host/` yerine `tenants/{tenantId}/`; iç düzen ayn
| # | Adım | Dosya | Neden bu sırada |
| --- | --- | --- | --- |
| 1 | Tablo / view | `sql/object/…` | Ekranın bağlanacağı nesne önce var olmalı |
| 1 | Tablo / view | `sql/object/…` | Ekranın bağlanacağı nesne önce var olmalı. MCP'de yeni tablo `create_table` ile kurulur (script ve dosya tasarımcıyla aynı); view MCP'den kurulamaz |
| 2 | CRUD uçları (gerekiyorsa) | `crud/{Entity}.json` | Custom Component'in data source'ları buna bakar |
| 3 | Custom Component (gerekiyorsa) | `data/App.DeveloperKit.CustomComponents.json` | Wizard `Custom` yolunda bileşen adını arar |
| 4 | Wizard | `wizard/{Ad}.json` | Menü, yetki, dil anahtarı ve ListForm'u üretir |

View file

@ -165,7 +165,7 @@ sozsoft-platform/
│ │ ├── Sozsoft.MailQueue/ # Şablonlu mail kuyruğu ve ekleri
│ │ ├── Sozsoft.Sender/ # Mail (SES), SMS, WhatsApp, Rocket.Chat göndericileri
│ │ ├── Sozsoft.SqlQueryManager/ # SQL nesne yönetimi ve sorgu çalıştırma
│ │ └── Sozsoft.Mcp/ # MCP sunucusu: /mcp ucu, şema keşfi ve sorgu araçları
│ │ └── Sozsoft.Mcp/ # MCP sunucusu: /mcp ucu, şema keşfi, sorgu ve geliştirme araçları
│ └── test/ # Test projeleri
├── ui/ # React uygulaması
│ └── src/
@ -931,7 +931,7 @@ içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppSer
| **Sozsoft.MailQueue** | Şablon (Scriban) tabanlı gövde üretimi, ek dosya yaşam döngüsü, kuyruk çalıştırma ve günlükleme. |
| **Sozsoft.Sender** | Amazon SES (mail), Posta Güvercini (SMS), WhatsApp Cloud API, Rocket.Chat göndericileri. |
| **Sozsoft.SqlQueryManager** | SQL nesne yönetimi, sorgu çalıştırma, tablo/kolon metadata'sı. |
| **Sozsoft.Mcp** | MCP (Model Context Protocol) sunucusu: `/mcp` Streamable HTTP ucu, kiracı bazlı araç seti, şema keşfi ve salt-okunur sorgu. Bkz. §13.6. |
| **Sozsoft.Mcp** | MCP (Model Context Protocol) sunucusu: `/mcp` Streamable HTTP ucu, kiracı bazlı araç seti, şema keşfi, sorgu, veri yazma ve (host) tablo/ekran/bileşen geliştirme. Kiracı kullanıcısı yalnızca veri okur. Bkz. §13.6. |
### 10.2. Uygulama servisleri (`api/src/Sozsoft.Platform.Application`)
@ -1175,12 +1175,22 @@ sayfasına 302 ile değil, 401 ile döner); kiracı token talebinden ya da `__te
arkasında oturum yapışkanlığı gerekmez.
**Kiracı bazlı sözleşme.** `Sas_H_McpServer` tablosundaki kayıt (ekran:
`App.DeveloperKit.McpServers`) hangi araçların yayınlandığını, sorguların hangi veri
kaynağında ve hangi satır tavanıyla koşacağını belirler. `Tenants` boşsa kayıt tüm
`App.DeveloperKit.McpServers`) hangi araçların yayınlandığını ve sorguların hangi satır tavanıyla koşacağını
belirler; sorgular her zaman isteğin kiracısının veritabanında koşar. `Tenants` boşsa kayıt tüm
kiracılara açıktır (AiBot ile aynı sözleşme). Araç listesi bu kayda göre süzülür
(`AddListToolsFilter`) **ve** çağrı anında aynı kural yeniden uygulanır — süzme tek başına
bir yetki sınırı değildir.
**Kiracı kullanıcısı yalnızca okur.** Kiracı bağlamındaki istekte açık araçlar
`McpToolNames.TenantTools` kümesiyle sınırlıdır: `server_info`, `list_lists`, `describe_list`,
`search_schema`, `query_list`, `run_select`. `AllowedTools` bu kümeyi yalnızca daraltabilir;
kiracının `.Write`/`.Develop` ya da başka bir yetkisi olsa da veri yazma, `create_table`, wizard ve
component araçları listelenmez, çağrılırsa `App.Mcp.Error.TenantReadOnly` ile reddedilir.
`list_queries`/`run_query` da dışarıdadır: procedure ve Custom Endpoint çağrısı veri
yazabileceği için okuma olduğu garanti edilemez. Host kullanıcısı için tek sunucu anahtarı
`AllowedTools`'tur (boşsa tümü); sınırı yetkiler çizer. Karar `McpToolPolicy.IsAllowed`'dadır,
liste süzgeci ve çağrı kapısı aynı kuralı kullanır.
**Araçlar.**
| Araç | İş |
@ -1193,7 +1203,13 @@ bir yetki sınırı değildir.
| `run_select` | Parametreli tek SELECT; çoklu ifade, DDL ve DML reddedilir, satır tavanı sorguya yazılır. Yalnızca SQL Query Manager yetkisiyle. |
| `list_queries` | View/procedure/function nesneleri (SQL Query Manager yetkisiyle) + çağrılabilir Custom Endpoint'ler (`App.DeveloperKit.CustomEndpoints.Get` ile). |
| `run_query` | Kayıtlı bir nesneyi ya da Custom Endpoint'i parametreleriyle çağırır; yetkiler `list_queries` ile aynı. |
| `insert_row` / `update_row` / `delete_row` | ListForm ekranının insert/update/delete sözleşmesinden geçer; `AllowWrite` kapalıysa listelenmez. |
| `insert_row` / `update_row` / `delete_row` | ListForm ekranının insert/update/delete sözleşmesinden geçer. |
| `get_dev_guide` | Üretim sözleşmesi: `lowcode` / `reference` belgelerinin başlık listesi ya da istenen bölümü (numarayla, örn. `4.2`). Çok büyük bir üst bölüm yalnızca giriş metni + alt başlıklarıyla döner. |
| `create_table` | Yeni tabloyu SQL Query Manager tablo tasarımcısıyla aynı script biçimiyle kurar (`McpTableCreator`): `Id` + `TenantId` + tam audit kolonları şablondan gelir, istemci yalnızca iş kolonlarını verir; tip ve varsayılan değer beyaz listeden geçer, istemci SQL yazamaz. Script `{kapsam}/sql/object/{Tablo}.sql` dosyasına yazılır. Yalnızca oluşturur — tablo varsa reddeder; yalnızca SQL Server. `.Develop` + `App.SqlQueryManager` ister. |
| `list_wizards` / `get_wizard` | Wizard File Manager dosyaları; `get_wizard` seed dosyasını diskteki biçimiyle döndürür. |
| `deploy_wizard` | Wizard'ı Wizard ekranıyla aynı yoldan (`IListFormWizardAppService.Create`) deploy eder. Girdi seed dosyası biçimi (`{"Wizard": …}`) ya da düz cevaplardır; ikisi de `WizardSeedFileDto.FromSeedJson`'dan geçer. `replaceFileName` doluysa güncellemedir (eski dosya ve ürettiği kayıtlar silinip yeniden üretilir). |
| `delete_wizard` | Wizard Manager'daki silmeyle aynı: yalnızca `InsertedRecords`, bağımlı wizard varsa reddedilir. |
| `list_components` / `get_component` / `save_component` / `delete_component` | Custom component'ler `ICustomComponentAppService` üzerinden; seed senkronu ve `DataSources` çözümü aynen çalışır. `save_component` ada göre ekler/günceller, tasarımcı belgesi (`Props.visualDesigner`, `sourceMode ≠ code`) taşıyan bileşene kod yazmayı reddeder — kod belgeden üretildiği için tasarımcıdaki ilk kayıtta ezilirdi. |
**Yetki modeli: MCP yetki vermez.** Kullanıcı MCP'de uygulamadaki yetkisini taşır:
- Şema araçları yalnızca kullanıcının okuma yetkisi olan listeleri gösterir (`McpListFormAccessFilter`
@ -1202,9 +1218,21 @@ bir yetki sınırı değildir.
filtreleri, kullanıcı özelleştirmeleri uygulamadaki gibidir.
- Ham SQL (`run_select`, veritabanı nesneleri) liste yetkilerini aşar; yalnızca uygulamada
`App.SqlQueryManager` yetkisi olana açıktır. Yazma araçları `.Write` + ekranın C/U/D yetkisini ister.
- Geliştirme araçları (`McpToolNames.DevelopTools`) `.Develop` yetkisini ister; sunucu bazında
`AllowedTools` ile kapatılır. `create_table` ham DDL olduğu için tablo tasarımcısıyla aynı
`App.SqlQueryManager` yetkisini de ister. `.Develop` yalnızca MCP kapısıdır:
işlem Wizard (`App.Listforms.Wizard` + `.Create/.Update/.Delete`) ve Custom Components
(`App.DeveloperKit.CustomComponents` + `.Create/.Update/.Delete`) yetkilerinden ayrıca geçer.
- Araç listesi bu yetkilere göre süzülür (`GetAvailableToolsAsync`); çağrı anında aynı kontrol
tekrarlanır. Araç → yetki eşlemesi `McpToolNames.RequiredPermissions`'tadır.
**Geliştirme rehberi nereden okur.** `get_dev_guide` depodaki
`.github/instructions/lowcode*.instructions.md` dosyalarını runtime'da okur; Claude Code ile MCP
istemcisi aynı sözleşmeyi görür. Klasör `App:DevGuidePath` ayarından (konteynerde
`/etc/api/dev-guide`, compose'da `.github/instructions` salt okunur mount), boşsa çalışma
dizininden yukarı `.github/instructions` aranarak bulunur. İmaja gömülmez: API build bağlamı
`api/` olduğu için `.github` imaja girmez, rehber değiştiğinde imaj derlenmez.
**Şema keşfi nereden okur.** Tablo ve sütun bilgisi canlı katalogdan değil,
`Sas_H_DbTableColumn` tablosundaki JSON veri sözlüğünden gelir — sözlük dil anahtarlarından
çözülmüş başlıkları ve lookup ilişkilerini birlikte taşıdığı için modelin gördüğü adlar
@ -1286,8 +1314,11 @@ curl -s https://api.sozsoft.com/.well-known/oauth-authorization-server \
Dördü de `https://` olmalıdır.
**Yetkiler.** `App.DeveloperKit.McpServers` (ekran), `.Invoke` (araç çağırma) ve `.Write`
(veri yazma). Son ikisi `MultiTenancySide = Both`, yani kiracı kullanıcılarına da verilebilir.
**Yetkiler.** `App.DeveloperKit.McpServers` (ekran), `.Invoke` (araç çağırma), `.Write`
(veri yazma) ve `.Develop` (tablo/ekran/bileşen geliştirme). `.Invoke` `MultiTenancySide = Both`
olduğu için kiracı kullanıcısına da verilir; `.Write` da kiracıya tanımlanabilir ama MCP'de
kiracı salt okuma kümesiyle sınırlı olduğu için etkisi yoktur. `.Develop` host'tur —
arkasındaki Wizard ve Custom Components yetkileri de yalnızca host'ta tanımlıdır.
---

View file

@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
namespace Sozsoft.Mcp.Dtos;
/// <summary>Gelistirme rehberinin bir basligi.</summary>
public class McpDevGuideHeadingDto
{
/// <summary><c>lowcode</c> (uretim protokolu, artefakt semalari) ya da <c>reference</c> (JSON kolon sozlesmeleri).</summary>
public string Document { get; set; } = string.Empty;
/// <summary>Bolum numarasi (<c>4.3</c>); get_dev_guide'a bu deger verilir.</summary>
public string Id { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public int Level { get; set; }
}
/// <summary>
/// Rehber cevabi. Bolum istenmediyse yalnizca <see cref="Sections"/> doludur; istendiyse
/// <see cref="Content"/> bolumun metnini, <see cref="Sections"/> alt basliklarini tasir.
/// </summary>
public class McpDevGuideDto
{
public McpDevGuideHeadingDto? Heading { get; set; }
public string? Content { get; set; }
public List<McpDevGuideHeadingDto> Sections { get; set; } = [];
}
/// <summary>Bir wizard seed dosyasi; <see cref="Seed"/> dosyanin diskteki bicimidir.</summary>
public class McpWizardFileDto
{
public string FileName { get; set; } = string.Empty;
public JsonElement Seed { get; set; }
}
/// <summary>Wizard'i ilk kez ya da yeniden deploy etme istegi.</summary>
public class McpWizardDeployRequestDto
{
/// <summary>
/// Seed dosyasi bicimindeki JSON (<c>{"Wizard": {...}}</c>) ya da duz wizard cevaplari.
/// </summary>
public string Wizard { get; set; } = string.Empty;
/// <summary>Doluysa guncellemedir: bu dosya ve urettigi kayitlar silinip yeniden uretilir.</summary>
public string? ReplaceFileName { get; set; }
}
/// <summary>
/// Yeni tablo istegi. <c>Id</c>, <c>TenantId</c> ve audit kolonlari sablondan gelir; burada
/// yalnizca is kolonlari yazilir.
/// </summary>
public class McpCreateTableRequestDto
{
public string TableName { get; set; } = string.Empty;
public List<McpTableColumnDto> Columns { get; set; } = [];
public bool FullAudited { get; set; } = true;
public bool MultiTenant { get; set; } = true;
}
public class McpTableColumnDto
{
public string Name { get; set; } = string.Empty;
/// <summary>Tablo tasarimcisinin tip listesinden biri (nvarchar, int, decimal, bit ...).</summary>
public string Type { get; set; } = string.Empty;
/// <summary>Yalnizca nvarchar icin.</summary>
public int? MaxLength { get; set; }
public bool IsNullable { get; set; } = true;
/// <summary>Sayi, metin sabiti ('...') ya da NEWID()/GETDATE()/GETUTCDATE()/SYSUTCDATETIME().</summary>
public string? DefaultValue { get; set; }
}
public class McpCreateTableResultDto
{
public string TableName { get; set; } = string.Empty;
public string Script { get; set; } = string.Empty;
/// <summary>Script'in yazildigi seed dosyasi, seed kokune gore goreli.</summary>
public string SeedFilePath { get; set; } = string.Empty;
}
/// <summary>Custom component ozeti; kod tasimaz.</summary>
public class McpComponentSummaryDto
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? RoutePath { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; }
/// <summary>Kodu tasarimci belgesinden uretilen bilesen; MCP uzerinden kodu yazilamaz.</summary>
public bool IsVisualDesigner { get; set; }
}
/// <summary>
/// Custom component kaydetme istegi; ada gore varsa gunceller, yoksa ekler. Guncellemede null
/// birakilan alanlar mevcut degerini korur.
/// </summary>
public class McpComponentSaveRequestDto
{
public string Name { get; set; } = string.Empty;
public string? Code { get; set; }
/// <summary>Bos dize rotayi kaldirir (rotasiz bilesen); null mevcut rotayi korur.</summary>
public string? RoutePath { get; set; }
public string? Description { get; set; }
public string? Props { get; set; }
/// <summary>Bagimli bilesen adlarinin JSON dizisi.</summary>
public string? Dependencies { get; set; }
public bool? IsActive { get; set; }
}

View file

@ -14,16 +14,11 @@ public class McpServerConfigDto
/// <summary>Ekranda gosterilen baglanti adresi; sunucu tarafinda yonlendirme icin kullanilmaz.</summary>
public string? Url { get; set; }
/// <summary>Bos ise istegin kapsamindaki varsayilan veri kaynagi kullanilir.</summary>
public string? DataSourceCode { get; set; }
/// <summary>Bos liste "yazma disindaki tum araclar acik" anlamina gelir.</summary>
/// <summary>Bos liste "tum araclar acik" anlamina gelir.</summary>
public List<string> AllowedTools { get; set; } = [];
public int MaxRows { get; set; }
public bool AllowWrite { get; set; }
public string? Description { get; set; }
/// <summary>Istegin cozumlenen kiraci adi; host baglaminda bostur.</summary>

View file

@ -7,7 +7,7 @@ namespace Sozsoft.Mcp;
/// <summary>
/// MCP veri yazma yuzeyi. Yazma her zaman bir ListForm ekraninin insert/update/delete
/// sozlesmesinden gecer; ekran yetkisi ve tenant filtresi aynen uygulanir. Sunucu kaydinda
/// <c>AllowWrite</c> kapali oldugunda bu araclar hic listelenmez.
/// <c>AllowedTools</c> dolu ve bu araclar secili degilse hic listelenmez.
/// </summary>
public interface IMcpDataAppService : IApplicationService
{

View file

@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Sozsoft.Mcp.Dtos;
using Sozsoft.Platform.DeveloperKit;
using Sozsoft.Platform.ListForms;
using Volo.Abp.Application.Services;
namespace Sozsoft.Mcp;
/// <summary>
/// MCP gelistirme yuzeyi: tablo, ekran (wizard) ve custom component uretimi. Her islem uygulamadaki
/// Wizard ve Custom Components servislerinden gecer; MCP yalnizca kapidir, ekranin
/// kendi yetkilerini, seed dosyasi yazimini ve bagimlilik kurallarini atlamaz.
/// </summary>
public interface IMcpDevelopAppService : IApplicationService
{
Task<McpDevGuideDto> GetDevGuideAsync(string? document, string? section);
Task<McpCreateTableResultDto> CreateTableAsync(McpCreateTableRequestDto input);
Task<List<WizardFileInfoDto>> GetWizardsAsync(string? search);
Task<McpWizardFileDto> GetWizardAsync(string fileName);
/// <summary>Deploy edilen wizard'in ozeti.</summary>
Task<WizardFileInfoDto?> DeployWizardAsync(McpWizardDeployRequestDto input);
Task<WizardDeleteResultDto> DeleteWizardAsync(string fileName);
Task<List<McpComponentSummaryDto>> GetComponentsAsync(string? search);
Task<CustomComponentDto?> GetComponentAsync(string name);
Task<CustomComponentDto> SaveComponentAsync(McpComponentSaveRequestDto input);
Task DeleteComponentAsync(string name);
}

View file

@ -15,6 +15,9 @@
<ItemGroup>
<ProjectReference Include="..\Sozsoft.Mcp.Domain.Shared\Sozsoft.Mcp.Domain.Shared.csproj" />
<!-- Veri yazma ekranin insert/update/delete sozlesmesinden, gelistirme araclari Wizard ve
Custom Components sozlesmesinden gecer; araclar bu sozlesmelerin DTO'larini dondurur. -->
<ProjectReference Include="..\..\..\src\Sozsoft.Platform.Application.Contracts\Sozsoft.Platform.Application.Contracts.csproj" />
</ItemGroup>
</Project>

View file

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Sozsoft.Mcp.Dtos;
@ -37,10 +38,8 @@ public abstract class McpAppService : ApplicationService
{
Name = server.Name,
Url = server.Url,
DataSourceCode = server.DataSourceCode,
AllowedTools = [.. ServerConfigProvider.SplitMultiValue(server.AllowedTools)],
MaxRows = server.MaxRows,
AllowWrite = server.AllowWrite,
Description = server.Description,
TenantName = CurrentTenant.Name
};
@ -63,7 +62,12 @@ public abstract class McpAppService : ApplicationService
{
var config = await RequireServerConfigAsync();
if (!McpToolPolicy.IsAllowed(true, config.AllowWrite, config.AllowedTools, toolName))
if (CurrentTenant.IsAvailable && !McpToolNames.TenantTools.Contains(toolName, StringComparer.Ordinal))
{
throw new UserFriendlyException(L[McpErrorCodes.TenantReadOnly, toolName]);
}
if (!McpToolPolicy.IsAllowed(true, CurrentTenant.IsAvailable, config.AllowedTools, toolName))
{
throw new UserFriendlyException(L[McpErrorCodes.ToolDisabled, toolName, config.Name]);
}

View file

@ -58,8 +58,6 @@ public class McpDataAppService(
{
Check.NotNull(input, nameof(input));
// AllowWrite kapaliysa yazma araclari McpToolPolicy tarafindan zaten reddedilir;
// burada ayri bir kontrol yok.
await EnsureToolAllowedAsync(toolName);
if (string.IsNullOrWhiteSpace(input.ListFormCode))

View file

@ -0,0 +1,341 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Sozsoft.Mcp.Dtos;
using Sozsoft.Mcp.Services;
using Sozsoft.Platform;
using Sozsoft.Platform.DeveloperKit;
using Sozsoft.Platform.ListForms;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
namespace Sozsoft.Mcp;
/// <summary>
/// Ekran ve bilesen gelistirme yuzeyi. Uretim Wizard ve Custom Components servislerinden
/// gecer: menu, yetki, dil anahtari, seed dosyasi ve bagimlilik kurallari ekrandaki yolla
/// aynidir, yetki kontrolu de o servislerin kendi politikalarindan gelir. Develop yetkisi
/// yalnizca MCP kapisidir, arkadaki yetkinin yerine gecmez.
/// </summary>
[Authorize(PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop)]
public class McpDevelopAppService(
IMcpServerConfigProvider serverConfigProvider,
IMcpDevGuide devGuide,
IMcpTableCreator tableCreator,
IListFormWizardAppService wizardAppService,
ICustomComponentAppService componentAppService)
: McpAppService(serverConfigProvider), IMcpDevelopAppService
{
private const string DefaultGuideDocument = "lowcode";
/// <summary>Index cevabinda listelenen en derin baslik; daha derini bolum istenince gorunur.</summary>
private const int IndexHeadingLevel = 3;
/// <summary>Custom Components ekraninin tek sayfada dondurebildigi ust sinir.</summary>
private const int ComponentPageSize = 1000;
public async Task<McpDevGuideDto> GetDevGuideAsync(string? document, string? section)
{
await EnsureToolAllowedAsync(McpToolNames.GetDevGuide);
if (string.IsNullOrWhiteSpace(section))
{
var headings = await devGuide.GetHeadingsAsync();
if (headings.Count == 0)
{
throw new UserFriendlyException(L[McpErrorCodes.DevGuideUnavailable, McpDevGuide.ConfigurationKey]);
}
return new McpDevGuideDto
{
Sections = [.. headings
.Where(h => h.Level <= IndexHeadingLevel)
.Where(h => string.IsNullOrWhiteSpace(document) || string.Equals(h.Document, document, StringComparison.OrdinalIgnoreCase))
.Select(ToDto)]
};
}
var result = await devGuide.GetSectionAsync(
string.IsNullOrWhiteSpace(document) ? DefaultGuideDocument : document,
section)
?? throw new UserFriendlyException(L[McpErrorCodes.DevGuideSectionNotFound, section]);
return new McpDevGuideDto
{
Heading = ToDto(result.Heading),
Content = result.Content,
Sections = [.. result.Children.Select(ToDto)]
};
}
/// <summary>
/// Tablo tasarimcisiyla ayni yetkiyi ister: ham DDL liste yetkilerini asar, Develop yalnizca
/// MCP kapisidir.
/// </summary>
public async Task<McpCreateTableResultDto> CreateTableAsync(McpCreateTableRequestDto input)
{
Check.NotNull(input, nameof(input));
await EnsureToolAllowedAsync(McpToolNames.CreateTable);
await AuthorizationService.CheckAsync(PlatformConsts.AppCodes.SqlQueryManagers.Default);
var created = await tableCreator.CreateAsync(new McpTableDefinition(
input.TableName,
[.. (input.Columns ?? []).Select(column => new McpTableColumn(
column.Name, column.Type, column.MaxLength, column.IsNullable, column.DefaultValue))],
input.FullAudited,
input.MultiTenant));
return new McpCreateTableResultDto
{
TableName = created.TableName,
Script = created.Script,
SeedFilePath = created.SeedFilePath
};
}
public async Task<List<WizardFileInfoDto>> GetWizardsAsync(string? search)
{
await EnsureToolAllowedAsync(McpToolNames.ListWizards);
var files = await wizardAppService.GetFiles();
if (string.IsNullOrWhiteSpace(search))
{
return files;
}
return [.. files.Where(file =>
Contains(file.FileName, search) || Contains(file.WizardName, search) ||
Contains(file.ListFormCode, search) || Contains(file.CustomComponentName, search))];
}
public async Task<McpWizardFileDto> GetWizardAsync(string fileName)
{
await EnsureToolAllowedAsync(McpToolNames.GetWizard);
var seed = await wizardAppService.GetFile(fileName);
// Model dosyayi Claude Code'un depoda gordugu bicimde gorur; deploy_wizard ayni bicimi
// geri kabul eder.
using var document = JsonDocument.Parse(seed.ToSeedJson());
return new McpWizardFileDto { FileName = fileName, Seed = document.RootElement.Clone() };
}
public async Task<WizardFileInfoDto?> DeployWizardAsync(McpWizardDeployRequestDto input)
{
Check.NotNull(input, nameof(input));
await EnsureToolAllowedAsync(McpToolNames.DeployWizard);
var wizard = ParseWizard(input.Wizard);
wizard.EditFileName = string.IsNullOrWhiteSpace(input.ReplaceFileName) ? null : input.ReplaceFileName.Trim();
await wizardAppService.Create(wizard);
var files = await wizardAppService.GetFiles();
return files.FirstOrDefault(file => string.Equals(file.WizardName, wizard.WizardName?.Trim(), StringComparison.OrdinalIgnoreCase));
}
public async Task<WizardDeleteResultDto> DeleteWizardAsync(string fileName)
{
await EnsureToolAllowedAsync(McpToolNames.DeleteWizard);
return await wizardAppService.DeleteFile(fileName);
}
public async Task<List<McpComponentSummaryDto>> GetComponentsAsync(string? search)
{
await EnsureToolAllowedAsync(McpToolNames.ListComponents);
var components = await GetAllComponentsAsync();
return [.. components
.Where(component => string.IsNullOrWhiteSpace(search)
|| Contains(component.Name, search)
|| Contains(component.RoutePath, search)
|| Contains(component.Description, search))
.Select(component => new McpComponentSummaryDto
{
Id = component.Id,
Name = component.Name,
RoutePath = component.RoutePath,
Description = component.Description,
IsActive = component.IsActive,
IsVisualDesigner = IsVisualDesignerDocument(component.Props)
})];
}
public async Task<CustomComponentDto?> GetComponentAsync(string name)
{
await EnsureToolAllowedAsync(McpToolNames.GetComponent);
return await FindComponentAsync(name);
}
public async Task<CustomComponentDto> SaveComponentAsync(McpComponentSaveRequestDto input)
{
Check.NotNull(input, nameof(input));
await EnsureToolAllowedAsync(McpToolNames.SaveComponent);
if (string.IsNullOrWhiteSpace(input.Name))
{
throw new UserFriendlyException(L[McpErrorCodes.ComponentNameRequired]);
}
var existing = await FindComponentAsync(input.Name);
var props = input.Props ?? existing?.Props;
// Tasarimci belgesinde kod belgeden uretilir; buraya yazilan kod tasarimcidaki ilk
// kayitta sessizce ezilirdi. Belge MCP'de uretilemedigi icin bu bilesenler tasarimcida kalir.
if (IsVisualDesignerDocument(props))
{
throw new UserFriendlyException(L[McpErrorCodes.VisualDesignerComponent, input.Name]);
}
if (existing is null)
{
if (string.IsNullOrWhiteSpace(input.Code))
{
throw new UserFriendlyException(L[McpErrorCodes.ComponentCodeRequired]);
}
return await componentAppService.CreateAsync(new CreateUpdateCustomComponentDto
{
Name = input.Name.Trim(),
Code = input.Code,
RoutePath = input.RoutePath,
Description = input.Description,
Props = props,
Dependencies = input.Dependencies,
IsActive = input.IsActive ?? true
});
}
return await componentAppService.UpdateAsync(existing.Id, new CreateUpdateCustomComponentDto
{
Name = existing.Name,
Code = string.IsNullOrWhiteSpace(input.Code) ? existing.Code : input.Code,
RoutePath = input.RoutePath ?? existing.RoutePath,
Description = input.Description ?? existing.Description,
Props = props,
Dependencies = input.Dependencies ?? existing.Dependencies,
IsActive = input.IsActive ?? existing.IsActive
});
}
public async Task DeleteComponentAsync(string name)
{
await EnsureToolAllowedAsync(McpToolNames.DeleteComponent);
var component = await FindComponentAsync(name)
?? throw new UserFriendlyException(L[McpErrorCodes.ComponentNotFound, name]);
await componentAppService.DeleteAsync(component.Id);
}
/// <summary>
/// Wizard'i seed dosyasi biciminden (<c>{"Wizard": {...}}</c>) ya da duz cevaplardan okur.
/// Iki bicim de dosyanin okuma kuralindan gecer; boylece Menu/Language bloklari ve
/// WizardCode/ModuleId adlari dosyadakiyle ayni anlama gelir.
/// </summary>
private ListFormWizardDto ParseWizard(string? json)
{
JsonObject? root;
try
{
root = string.IsNullOrWhiteSpace(json) ? null : JsonNode.Parse(json) as JsonObject;
}
catch (JsonException)
{
root = null;
}
if (root is null)
{
throw new UserFriendlyException(L[McpErrorCodes.InvalidWizard]);
}
var seedJson = root.ContainsKey(nameof(WizardSeedFileDto.Wizard))
? root.ToJsonString()
: new JsonObject { [nameof(WizardSeedFileDto.Wizard)] = root }.ToJsonString();
try
{
return WizardSeedFileDto.FromSeedJson(seedJson)?.Wizard
?? throw new UserFriendlyException(L[McpErrorCodes.InvalidWizard]);
}
catch (JsonException)
{
throw new UserFriendlyException(L[McpErrorCodes.InvalidWizard]);
}
}
private async Task<IReadOnlyList<CustomComponentDto>> GetAllComponentsAsync()
{
var page = await componentAppService.GetListAsync(new PagedAndSortedResultRequestDto
{
MaxResultCount = ComponentPageSize,
Sorting = nameof(CustomComponentDto.Name)
});
return page.Items;
}
private async Task<CustomComponentDto?> FindComponentAsync(string? name)
{
if (string.IsNullOrWhiteSpace(name))
{
return null;
}
var components = await GetAllComponentsAsync();
return components.FirstOrDefault(component => string.Equals(component.Name, name.Trim(), StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Props icinde tasarimci belgesi varsa ve kip acikca <c>code</c> degilse kod belgeden
/// uretilir; tasarimcinin varsayilan kipi <c>visual</c>'dir.
/// </summary>
private static bool IsVisualDesignerDocument(string? props)
{
if (string.IsNullOrWhiteSpace(props))
{
return false;
}
try
{
using var document = JsonDocument.Parse(props);
if (document.RootElement.ValueKind != JsonValueKind.Object ||
!document.RootElement.TryGetProperty("visualDesigner", out var designer) ||
designer.ValueKind != JsonValueKind.Object)
{
return false;
}
return !designer.TryGetProperty("sourceMode", out var mode) ||
mode.ValueKind != JsonValueKind.String ||
!string.Equals(mode.GetString(), "code", StringComparison.Ordinal);
}
catch (JsonException)
{
return false;
}
}
private static bool Contains(string? value, string search)
=> value?.Contains(search.Trim(), StringComparison.OrdinalIgnoreCase) == true;
private static McpDevGuideHeadingDto ToDto(McpDevGuideHeading heading) => new()
{
Document = heading.Document,
Id = heading.Id,
Title = heading.Title,
Level = heading.Level
};
}

View file

@ -80,7 +80,7 @@ public class McpQueryAppService(
var maxRows = ResolveMaxRows(config, input.MaxRows);
var parameters = sqlGuard.NormalizeParameters(input.Parameters);
var context = await queryExecutor.ResolveDataSourceAsync(config.DataSourceCode);
var context = await queryExecutor.ResolveDataSourceAsync();
var limitedSql = sqlGuard.ApplyRowLimit(sql, maxRows, context.DataSourceType);
return ToResult(await queryExecutor.ExecuteAsync(context, limitedSql, parameters, maxRows));
@ -88,12 +88,12 @@ public class McpQueryAppService(
public async Task<List<McpSavedQueryDto>> GetSavedQueriesAsync(string? search = null)
{
var config = await EnsureToolAllowedAsync(McpToolNames.ListQueries);
await EnsureToolAllowedAsync(McpToolNames.ListQueries);
var results = new List<McpSavedQueryDto>();
if (await AuthorizationService.IsGrantedAsync(SqlPermission))
{
results.AddRange(await GetDatabaseObjectsAsync(config));
results.AddRange(await GetDatabaseObjectsAsync());
}
if (await AuthorizationService.IsGrantedAsync(CustomEndpointPermission))
@ -113,9 +113,9 @@ public class McpQueryAppService(
.ThenBy(item => item.Name, StringComparer.OrdinalIgnoreCase)];
}
private async Task<List<McpSavedQueryDto>> GetDatabaseObjectsAsync(McpServerConfigDto config)
private async Task<List<McpSavedQueryDto>> GetDatabaseObjectsAsync()
{
var context = await queryExecutor.ResolveDataSourceAsync(config.DataSourceCode);
var context = await queryExecutor.ResolveDataSourceAsync();
var catalogSql = context.DataSourceType == DataSourceTypeEnum.Postgresql
? McpCatalogQueries.Postgres
: McpCatalogQueries.SqlServer;
@ -160,7 +160,7 @@ public class McpQueryAppService(
var maxRows = ResolveMaxRows(config, input.MaxRows);
var parameters = sqlGuard.NormalizeParameters(input.Parameters);
var context = await queryExecutor.ResolveDataSourceAsync(config.DataSourceCode);
var context = await queryExecutor.ResolveDataSourceAsync();
var schemaName = string.IsNullOrWhiteSpace(input.SchemaName)
? sqlGuard.DefaultSchemaName(context.DataSourceType)

View file

@ -31,7 +31,7 @@ public class McpSchemaAppService(
var config = await ResolveServerConfigAsync();
var candidates = McpToolPolicy.VisibleTools(
config is not null,
config?.AllowWrite ?? false,
CurrentTenant.IsAvailable,
config?.AllowedTools ?? []);
var required = candidates

View file

@ -15,8 +15,6 @@
<ItemGroup>
<ProjectReference Include="..\Sozsoft.Mcp.Application.Contracts\Sozsoft.Mcp.Application.Contracts.csproj" />
<ProjectReference Include="..\Sozsoft.Mcp.Domain\Sozsoft.Mcp.Domain.csproj" />
<!-- Veri yazma ekranin insert/update/delete sozlesmesinden gecer. -->
<ProjectReference Include="..\..\..\src\Sozsoft.Platform.Application.Contracts\Sozsoft.Platform.Application.Contracts.csproj" />
</ItemGroup>
</Project>

View file

@ -10,6 +10,7 @@ public static class McpErrorCodes
public const string NoServerRecord = Prefix + "NoServerRecord";
public const string ToolDisabled = Prefix + "ToolDisabled";
public const string TenantReadOnly = Prefix + "TenantReadOnly";
public const string EmptyQuery = Prefix + "EmptyQuery";
public const string SingleStatementOnly = Prefix + "SingleStatementOnly";
public const string ReadOnlyStatementRequired = Prefix + "ReadOnlyStatementRequired";
@ -25,4 +26,18 @@ public static class McpErrorCodes
public const string DictionaryNotFound = Prefix + "DictionaryNotFound";
public const string RegistrationLimitReached = Prefix + "RegistrationLimitReached";
public const string RedirectUriNotAllowed = Prefix + "RedirectUriNotAllowed";
public const string DevGuideUnavailable = Prefix + "DevGuideUnavailable";
public const string DevGuideSectionNotFound = Prefix + "DevGuideSectionNotFound";
public const string InvalidWizard = Prefix + "InvalidWizard";
public const string ComponentNameRequired = Prefix + "ComponentNameRequired";
public const string ComponentCodeRequired = Prefix + "ComponentCodeRequired";
public const string ComponentNotFound = Prefix + "ComponentNotFound";
public const string VisualDesignerComponent = Prefix + "VisualDesignerComponent";
public const string TableColumnsRequired = Prefix + "TableColumnsRequired";
public const string UnsupportedColumnType = Prefix + "UnsupportedColumnType";
public const string InvalidColumnLength = Prefix + "InvalidColumnLength";
public const string InvalidDefaultValue = Prefix + "InvalidDefaultValue";
public const string DuplicateColumn = Prefix + "DuplicateColumn";
public const string TableAlreadyExists = Prefix + "TableAlreadyExists";
public const string TableProviderNotSupported = Prefix + "TableProviderNotSupported";
}

View file

@ -22,12 +22,34 @@ public static class McpToolNames
public const string InsertRow = "insert_row";
public const string UpdateRow = "update_row";
public const string DeleteRow = "delete_row";
public const string GetDevGuide = "get_dev_guide";
public const string CreateTable = "create_table";
public const string ListWizards = "list_wizards";
public const string GetWizard = "get_wizard";
public const string DeployWizard = "deploy_wizard";
public const string DeleteWizard = "delete_wizard";
public const string ListComponents = "list_components";
public const string GetComponent = "get_component";
public const string SaveComponent = "save_component";
public const string DeleteComponent = "delete_component";
/// <summary>
/// Veri yazan araclar. Sunucu kaydinda <c>AllowWrite</c> kapaliysa bunlar arac listesinde
/// hic yer almaz.
/// Ekran ve bilesen gelistirme araclari. Sunucu bazinda <c>AllowedTools</c> ile, kullanici
/// bazinda <c>McpServers.Develop</c> yetkisiyle kapatilir.
/// </summary>
public static readonly string[] WriteTools = [InsertRow, UpdateRow, DeleteRow];
public static readonly string[] DevelopTools =
[
GetDevGuide,
CreateTable,
ListWizards,
GetWizard,
DeployWizard,
DeleteWizard,
ListComponents,
GetComponent,
SaveComponent,
DeleteComponent
];
/// <summary>
/// Sunucu kaydi olmasa da acik kalan araclar. Istemcinin baglantisinin neden bos
@ -35,10 +57,29 @@ public static class McpToolNames
/// </summary>
public static readonly string[] AlwaysAvailableTools = [ServerInfo];
/// <summary>
/// Kiraci kullanicisinin gorebildigi araclarin tamami; kiraci baglaminda MCP yalnizca veri
/// okur. <c>AllowedTools</c> bu kumeyi yalnizca daraltabilir, genisletemez. <see cref="RunQuery"/>
/// ve <see cref="ListQueries"/> disarida kalir: procedure ve Custom Endpoint cagrisi veri
/// yazabilir, okuma oldugu garanti edilemez. <see cref="RunSelect"/> ise tek SELECT disindaki
/// her ifadeyi reddettigi icin okumadir.
/// </summary>
public static readonly string[] TenantTools =
[
ServerInfo,
ListLists,
DescribeList,
SearchSchema,
QueryList,
RunSelect
];
/// <summary>
/// Kullanicinin uygulamada da sahip olmasi gereken yetki. MCP yetki vermez: ham SQL'i
/// uygulamada SQL Query Manager'i kullanabilen, veri yazmayi yazma yetkisi olan kullanici
/// gorur. Listede olmayan araclar kendi icinde liste bazli yetkiyle suzulur.
/// gorur. Listede olmayan araclar kendi icinde liste bazli yetkiyle suzulur. Gelistirme
/// araclarinin arkasindaki Wizard ve Custom Components servisleri kendi yetkilerini ayrica
/// ister; Develop yalnizca MCP kapisidir.
/// </summary>
public static readonly IReadOnlyDictionary<string, string> RequiredPermissions = new Dictionary<string, string>
{
@ -46,6 +87,16 @@ public static class McpToolNames
[InsertRow] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
[UpdateRow] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
[DeleteRow] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
[GetDevGuide] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
[CreateTable] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
[ListWizards] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
[GetWizard] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
[DeployWizard] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
[DeleteWizard] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
[ListComponents] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
[GetComponent] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
[SaveComponent] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
[DeleteComponent] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
};
/// <summary>Yayinlanan tum araclar, arac listesindeki siralarina gore.</summary>
@ -61,7 +112,8 @@ public static class McpToolNames
RunQuery,
InsertRow,
UpdateRow,
DeleteRow
DeleteRow,
.. DevelopTools
];
/// <summary>

View file

@ -12,11 +12,11 @@ namespace Sozsoft.Mcp;
public static class McpToolPolicy
{
/// <param name="hasServerRecord">Istegin kiracisina dusen aktif bir sunucu kaydi var mi.</param>
/// <param name="allowWrite">Sunucu kaydinda veri yazma acik mi.</param>
/// <param name="allowedTools">Acik arac adlari; bos liste "yazma disindaki tumu acik" demektir.</param>
/// <param name="isTenant">Istek bir kiraci kullanicisindan mi geliyor; o zaman yalnizca okuma araclari acilir.</param>
/// <param name="allowedTools">Acik arac adlari; bos liste "tumu acik" demektir.</param>
public static bool IsAllowed(
bool hasServerRecord,
bool allowWrite,
bool isTenant,
IReadOnlyCollection<string> allowedTools,
string toolName)
{
@ -25,12 +25,12 @@ public static class McpToolPolicy
return true;
}
if (!hasServerRecord)
if (isTenant && !McpToolNames.TenantTools.Contains(toolName, StringComparer.Ordinal))
{
return false;
}
if (!allowWrite && McpToolNames.WriteTools.Contains(toolName, StringComparer.Ordinal))
if (!hasServerRecord)
{
return false;
}
@ -45,7 +45,7 @@ public static class McpToolPolicy
public static IReadOnlyList<string> VisibleTools(
bool hasServerRecord,
bool allowWrite,
bool isTenant,
IReadOnlyCollection<string> allowedTools)
=> [.. McpToolNames.All.Where(name => IsAllowed(hasServerRecord, allowWrite, allowedTools, name))];
=> [.. McpToolNames.All.Where(name => IsAllowed(hasServerRecord, isTenant, allowedTools, name))];
}

View file

@ -6,8 +6,8 @@ namespace Sozsoft.Mcp.Entities;
/// <summary>
/// MCP (Model Context Protocol) sunucu tanimi. Kayit host kapsamindadir; hangi kiracilarin
/// kullanabilecegi <see cref="Tenants"/> ile belirlenir. Istegin kiracisina dusen kayit, /mcp
/// ucunda hangi araclarin gorunecegini ve sorgularin hangi veri kaynaginda hangi satir
/// tavaniyla kosacagini belirler.
/// ucunda hangi araclarin gorunecegini ve sorgularin hangi satir tavaniyla kosacagini belirler.
/// Sorgular her zaman istegin kiracisinin veritabaninda kosar.
/// </summary>
public class McpServer : FullAuditedEntity<Guid>
{
@ -28,22 +28,13 @@ public class McpServer : FullAuditedEntity<Guid>
public string? Tenants { get; set; }
/// <summary>
/// Sorgu araclarinin kosacagi DataSource kodu. Bos ise istegin kapsamindaki varsayilan
/// veri kaynagi kullanilir.
/// </summary>
public string? DataSourceCode { get; set; }
/// <summary>
/// Acik arac adlari, <c>PlatformConsts.MultiValueDelimiter</c> ile ayrilir. Bos ise yazma
/// disindaki tum araclar aciktir.
/// Acik arac adlari, <c>PlatformConsts.MultiValueDelimiter</c> ile ayrilir. Bos ise tum
/// araclar aciktir.
/// </summary>
public string? AllowedTools { get; set; }
/// <summary>Tek sorgunun dondurebilecegi en fazla satir sayisi.</summary>
public int MaxRows { get; set; } = McpConsts.DefaultMaxRows;
/// <summary>Kapali oldugunda veri yazma araclari arac listesinde hic yer almaz.</summary>
public bool AllowWrite { get; set; }
public string? Description { get; set; }
}

View file

@ -0,0 +1,177 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Volo.Abp.Domain.Services;
namespace Sozsoft.Mcp.Services;
/// <summary>
/// Modelin ekran ve bilesen uretirken okudugu sozlesme: depodaki
/// <c>.github/instructions/lowcode*.instructions.md</c> dosyalari. Claude Code ayni dosyalari
/// depodan okur; MCP istemcisi ise bu servisten bolum bolum alir, boylece iki yolun bildigi
/// sozlesme ayrisamaz.
/// </summary>
public interface IMcpDevGuide
{
/// <summary>Okunabilen belgelerin baslik agaci; klasor bulunamazsa bos.</summary>
Task<IReadOnlyList<McpDevGuideHeading>> GetHeadingsAsync();
/// <summary>
/// Bir bolumun metni. <paramref name="section"/> bolum numarasi (<c>4.3</c>) ya da baslik
/// metninin bir parcasidir; bulunamazsa null.
/// </summary>
Task<McpDevGuideSection?> GetSectionAsync(string document, string section);
}
/// <param name="Document">Belgenin kisa adi (<see cref="McpDevGuide.Documents"/>).</param>
/// <param name="Id">Bolum numarasi; numarasiz basliklarda baslik metni.</param>
public sealed record McpDevGuideHeading(string Document, string Id, string Title, int Level);
public sealed record McpDevGuideSection(McpDevGuideHeading Heading, string Content, IReadOnlyList<McpDevGuideHeading> Children);
public partial class McpDevGuide(IConfiguration configuration) : DomainService, IMcpDevGuide
{
/// <summary>Konteynerde mount noktasi; bos ise depo klasoru calisma dizininden yukari aranir.</summary>
public const string ConfigurationKey = "App:DevGuidePath";
/// <summary>Kisa ad → dosya. Yalnizca uretim sozlesmesi sunulur; kod standardi ve karar kurallari degil.</summary>
public static readonly IReadOnlyDictionary<string, string> Documents = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["lowcode"] = "lowcode.instructions.md",
["reference"] = "lowcode-reference.instructions.md",
};
/// <summary>
/// Tek bir yanitta donen metnin tavani. Ust seviye bolumlerin bazilari istemcilerin tek
/// arac cevabi icin kabul ettigi boyutu asar; bu durumda alt basliklar ayrica istenir.
/// </summary>
private const int MaxSectionLength = 60_000;
[GeneratedRegex(@"^(#{2,4})\s+(?:(\d+(?:\.\d+)*)\.?\s+)?(.+?)\s*$")]
private static partial Regex HeadingPattern();
public async Task<IReadOnlyList<McpDevGuideHeading>> GetHeadingsAsync()
{
var headings = new List<McpDevGuideHeading>();
foreach (var document in Documents.Keys)
{
var lines = await ReadLinesAsync(document);
headings.AddRange(ParseHeadings(document, lines).Select(heading => heading.Heading));
}
return headings;
}
public async Task<McpDevGuideSection?> GetSectionAsync(string document, string section)
{
var lines = await ReadLinesAsync(document);
var headings = ParseHeadings(document, lines);
var wanted = section.Trim().TrimEnd('.');
var index = headings.FindIndex(h => string.Equals(h.Heading.Id, wanted, StringComparison.OrdinalIgnoreCase));
if (index < 0)
{
index = headings.FindIndex(h => h.Heading.Title.Contains(wanted, StringComparison.OrdinalIgnoreCase));
}
if (index < 0)
{
return null;
}
var match = headings[index];
var next = headings.FindIndex(index + 1, h => h.Heading.Level <= match.Heading.Level);
var end = next < 0 ? lines.Length : headings[next].Line;
var children = headings
.Skip(index + 1)
.TakeWhile(h => h.Line < end)
.Where(h => h.Heading.Level == match.Heading.Level + 1)
.ToList();
var content = string.Join('\n', lines[match.Line..end]);
if (content.Length > MaxSectionLength && children.Count > 0)
{
// Bolumun giris metni tek basina anlamlidir; geri kalani alt baslik olarak istenir.
content = string.Join('\n', lines[match.Line..children[0].Line]);
}
return new McpDevGuideSection(match.Heading, content, [.. children.Select(child => child.Heading)]);
}
private async Task<string[]> ReadLinesAsync(string document)
{
if (!Documents.TryGetValue(document, out var fileName))
{
return [];
}
var directory = ResolveDirectory();
var path = directory is null ? null : Path.Combine(directory, fileName);
if (path is null || !File.Exists(path))
{
return [];
}
return (await File.ReadAllTextAsync(path)).Replace("\r\n", "\n").Split('\n');
}
private string? ResolveDirectory()
{
var configured = configuration[ConfigurationKey];
if (!string.IsNullOrWhiteSpace(configured))
{
return Path.GetFullPath(configured);
}
for (var dir = new DirectoryInfo(Directory.GetCurrentDirectory()); dir != null; dir = dir.Parent)
{
var candidate = Path.Combine(dir.FullName, ".github", "instructions");
if (Directory.Exists(candidate))
{
return candidate;
}
}
return null;
}
/// <summary>
/// Kod bloklari icindeki <c>##</c> ile baslayan satirlar (ornegin SQL yorumlari) baslik
/// sayilmaz; aksi halde bolum sinirlari ornek kodun ortasinda kesilirdi.
/// </summary>
private static List<(McpDevGuideHeading Heading, int Line)> ParseHeadings(string document, string[] lines)
{
var headings = new List<(McpDevGuideHeading, int)>();
var inFence = false;
for (var i = 0; i < lines.Length; i++)
{
if (lines[i].TrimStart().StartsWith("```", StringComparison.Ordinal))
{
inFence = !inFence;
continue;
}
if (inFence)
{
continue;
}
var match = HeadingPattern().Match(lines[i]);
if (!match.Success)
{
continue;
}
var title = match.Groups[3].Value;
var id = match.Groups[2].Success ? match.Groups[2].Value : title;
headings.Add((new McpDevGuideHeading(document, id, title, match.Groups[1].Length), i));
}
return headings;
}
}

View file

@ -16,10 +16,10 @@ namespace Sozsoft.Mcp.Services;
public interface IMcpQueryExecutor
{
/// <param name="dataSourceCode">
/// Sunucu kaydindaki kod. Bos ya da <c>!Tenant</c> ise veri kaynagi istegin kiracisindan
/// cozulur; host baglaminda varsayilan kaynaga duser.
/// Custom Endpoint'in kendi veri kaynagi kodu. Bos ya da <c>!Tenant</c> ise veri kaynagi
/// istegin kiracisindan cozulur; host baglaminda varsayilan kaynaga duser.
/// </param>
Task<McpDataSourceContext> ResolveDataSourceAsync(string? dataSourceCode);
Task<McpDataSourceContext> ResolveDataSourceAsync(string? dataSourceCode = null);
/// <summary>
/// Sorguyu kosturur. Sonuc kumesi <paramref name="maxRows"/> satirda kesilir; kesilme
@ -34,7 +34,7 @@ public interface IMcpQueryExecutor
public class McpQueryExecutor(IDynamicDataManager dynamicDataManager) : DomainService, IMcpQueryExecutor
{
public async Task<McpDataSourceContext> ResolveDataSourceAsync(string? dataSourceCode)
public async Task<McpDataSourceContext> ResolveDataSourceAsync(string? dataSourceCode = null)
{
var useTenantScope = string.IsNullOrWhiteSpace(dataSourceCode)
|| dataSourceCode == McpConsts.TenantDataSourceMarker;

View file

@ -0,0 +1,262 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Localization;
using Sozsoft.Platform.Data.Seeds;
using Sozsoft.Platform.Enums;
using Sozsoft.Platform.Localization;
using Volo.Abp;
using Volo.Abp.Domain.Services;
namespace Sozsoft.Mcp.Services;
/// <summary>
/// Yeni tabloyu SQL Query Manager'daki tablo tasarimcisiyla ayni sozlesmeyle kurar: script
/// tasarimcinin <c>generateCreateTableSql</c> ciktisiyla ayni bicimdedir ve tasarimcinin deploy
/// sonrasi yazdigi yere (<c>{kapsam}/sql/object/{Tablo}.sql</c>) yazilir; boylece tablo
/// tasarimcida acilip duzenlenebilir ve veritabani sifirlandiginda seed ile geri gelir.
/// <para>
/// Yalnizca olusturma yapar. Var olan tabloya dokunulmaz — kolon ekleme/degistirme tasarimcinin
/// isidir ve sema degisikligi kurallarina (kayipsiz ekleme, degistirme/dusurme yok) tabidir.
/// Kolon tipi ve varsayilan deger serbest metin degil, beyaz listeden gecer; istemci SQL yazamaz.
/// </para>
/// </summary>
public interface IMcpTableCreator
{
Task<McpCreatedTable> CreateAsync(McpTableDefinition definition);
}
public class McpTableCreator(
IMcpQueryExecutor queryExecutor,
IConfiguration configuration,
IStringLocalizer<PlatformResource> localizer) : DomainService, IMcpTableCreator
{
private const string SchemaName = "dbo";
private const int DefaultTextLength = 100;
private const int MaxTextLength = 4000;
private static readonly Regex SafeIdentifierRegex = new(
@"^[A-Za-z_][A-Za-z0-9_]{0,127}$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
/// <summary>
/// Varsayilan deger olarak kabul edilenler: sayi, metin sabiti ve tasarimcinin kendi
/// sablonlarinda kullandigi fonksiyonlar. Baska bir ifade script'e hic girmez.
/// </summary>
private static readonly Regex SafeDefaultRegex = new(
@"^(?:-?\d{1,18}(?:\.\d{1,18})?|N?'(?:[^']|'')*'|NEWID\(\)|GETDATE\(\)|GETUTCDATE\(\)|SYSUTCDATETIME\(\))$",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
/// <summary>Tasarimcinin tip listesi (<c>DATA_TYPES</c>) ile aynidir.</summary>
private static readonly string[] SupportedTypes =
[
"nvarchar", "nvarchar(MAX)", "int", "bigint", "decimal", "float", "bit",
"datetime", "datetime2", "date", "uniqueidentifier", "money"
];
// Sablon kolonlari tasarimcinin FULL_AUDIT_COLUMNS / TENANT_COLUMN tanimlariyla aynidir.
private static readonly McpTableColumn IdColumn = new("Id", "uniqueidentifier", null, false, "NEWID()");
private static readonly McpTableColumn TenantColumn = new("TenantId", "uniqueidentifier", null, true, null);
private static readonly McpTableColumn[] AuditColumns =
[
new("CreationTime", "datetime2", null, false, "GETUTCDATE()"),
new("CreatorId", "uniqueidentifier", null, true, null),
new("LastModificationTime", "datetime2", null, true, null),
new("LastModifierId", "uniqueidentifier", null, true, null),
new("IsDeleted", "bit", null, false, "0"),
new("DeletionTime", "datetime2", null, true, null),
new("DeleterId", "uniqueidentifier", null, true, null)
];
public async Task<McpCreatedTable> CreateAsync(McpTableDefinition definition)
{
Check.NotNull(definition, nameof(definition));
var tableName = (definition.TableName ?? string.Empty).Trim();
EnsureSafeIdentifier(tableName);
var columns = BuildColumns(definition);
var context = await queryExecutor.ResolveDataSourceAsync();
if (context.DataSourceType != DataSourceTypeEnum.Mssql)
{
throw new UserFriendlyException(localizer[McpErrorCodes.TableProviderNotSupported, context.DataSourceType.ToString()]);
}
var existing = await context.Repository.ExecuteScalarAsync<int?>(
"SELECT OBJECT_ID(@name, 'U')",
context.ConnectionString,
new Dictionary<string, object> { ["name"] = $"{SchemaName}.{tableName}" });
if (existing is not null)
{
throw new UserFriendlyException(localizer[McpErrorCodes.TableAlreadyExists, tableName]);
}
var script = BuildScript(tableName, columns);
// GO yalnizca SSMS ayiricidir; tek batch oldugu icin calistirirken atilir, dosyada kalir.
await context.Repository.ExecuteAsync(
string.Join('\n', script.Split('\n').Where(line => line.Trim() != "GO")),
context.ConnectionString);
var seedFilePath = SaveSeedScript(tableName, script);
return new McpCreatedTable(tableName, script, seedFilePath);
}
private List<McpTableColumn> BuildColumns(McpTableDefinition definition)
{
if (definition.Columns.Count == 0)
{
throw new UserFriendlyException(localizer[McpErrorCodes.TableColumnsRequired]);
}
var userColumns = definition.Columns.Select(NormalizeColumn).ToList();
var columns = new List<McpTableColumn> { IdColumn };
if (definition.MultiTenant)
{
columns.Add(TenantColumn);
}
columns.AddRange(userColumns);
if (definition.FullAudited)
{
columns.AddRange(AuditColumns);
}
var duplicate = columns
.GroupBy(column => column.Name, StringComparer.OrdinalIgnoreCase)
.FirstOrDefault(group => group.Count() > 1);
if (duplicate is not null)
{
throw new UserFriendlyException(localizer[McpErrorCodes.DuplicateColumn, duplicate.Key]);
}
return columns;
}
private McpTableColumn NormalizeColumn(McpTableColumn column)
{
var name = (column.Name ?? string.Empty).Trim();
EnsureSafeIdentifier(name);
var type = SupportedTypes.FirstOrDefault(candidate =>
string.Equals(candidate, column.Type?.Trim(), StringComparison.OrdinalIgnoreCase))
?? throw new UserFriendlyException(
localizer[McpErrorCodes.UnsupportedColumnType, column.Type ?? string.Empty, string.Join(", ", SupportedTypes)]);
int? maxLength = null;
if (type == "nvarchar")
{
maxLength = column.MaxLength ?? DefaultTextLength;
if (maxLength is < 1 or > MaxTextLength)
{
throw new UserFriendlyException(localizer[McpErrorCodes.InvalidColumnLength, name, MaxTextLength]);
}
}
var defaultValue = string.IsNullOrWhiteSpace(column.DefaultValue) ? null : column.DefaultValue.Trim();
if (defaultValue is not null && !SafeDefaultRegex.IsMatch(defaultValue))
{
throw new UserFriendlyException(localizer[McpErrorCodes.InvalidDefaultValue, name, defaultValue]);
}
return new McpTableColumn(name, type, maxLength, column.IsNullable, defaultValue);
}
private static string BuildScript(string tableName, List<McpTableColumn> columns)
{
var fullTableName = $"[{SchemaName}].[{tableName}]";
var lines = new List<string>
{
$"IF OBJECT_ID(N'{fullTableName}', 'U') IS NULL",
"BEGIN",
$" CREATE TABLE {fullTableName}",
" ("
};
lines.AddRange(columns.Select(column => ToColumnLine(column) + ","));
lines.Add($" CONSTRAINT [PK_{tableName}] PRIMARY KEY NONCLUSTERED");
lines.Add(" (");
lines.Add(" [Id] ASC");
lines.Add(" )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]");
lines.Add(" ) ON [PRIMARY]");
lines.Add("END");
lines.Add("GO");
return string.Join('\n', lines);
}
private static string ToColumnLine(McpTableColumn column)
{
var type = column.Type switch
{
"nvarchar" => $"nvarchar({column.MaxLength})",
"decimal" => "decimal(18, 4)",
_ => column.Type
};
var nullPart = column.IsNullable ? "NULL" : "NOT NULL";
var defaultPart = column.DefaultValue is null ? string.Empty : $" DEFAULT {column.DefaultValue}";
return $" [{column.Name}] {type} {nullPart}{defaultPart}";
}
/// <summary>
/// Tasarimcinin <c>SaveTableScriptAsync</c> ile yazdigi yol: kapsam istegin kiracisini izler.
/// Dosyasiz tablo veritabani sifirlandiginda geri gelmezdi; yazilamazsa islem hata verir.
/// </summary>
private string SaveSeedScript(string tableName, string script)
{
var outputPath = SeedPathResolver.GetScopePath(
configuration,
CurrentTenant.Id,
SeedPathResolver.GetProviderFolderName(isPostgreSql: false),
SeedPathResolver.ObjectFolder);
Directory.CreateDirectory(outputPath);
var filePath = Path.Combine(outputPath, $"{tableName}.sql");
File.WriteAllText(filePath, script);
return $"{SeedPathResolver.GetScopeFolderName(CurrentTenant.Id)}/{SeedPathResolver.SqlFolder}/{SeedPathResolver.ObjectFolder}/{tableName}.sql";
}
private void EnsureSafeIdentifier(string identifier)
{
if (!SafeIdentifierRegex.IsMatch(identifier))
{
throw new UserFriendlyException(localizer[McpErrorCodes.InvalidIdentifier, identifier]);
}
}
}
/// <param name="FullAudited">Tam audit kolonlari (olusturma/degistirme/silme izi) eklensin mi.</param>
/// <param name="MultiTenant"><c>TenantId</c> kolonu eklensin mi.</param>
public sealed record McpTableDefinition(
string TableName,
IReadOnlyList<McpTableColumn> Columns,
bool FullAudited,
bool MultiTenant);
/// <param name="MaxLength">Yalnizca <c>nvarchar</c> icin; bos ise tasarimcinin varsayilani (100).</param>
public sealed record McpTableColumn(
string Name,
string Type,
int? MaxLength,
bool IsNullable,
string? DefaultValue);
/// <param name="SeedFilePath">Seed kokune gore goreli yol.</param>
public sealed record McpCreatedTable(string TableName, string Script, string SeedFilePath);

View file

@ -26,9 +26,7 @@ public static class McpDbContextModelCreatingExtensions
// dxTagBox ile doldurulur.
b.Property(x => x.Tenants).HasMaxLength(2048);
b.Property(x => x.AllowedTools).HasMaxLength(1024);
b.Property(x => x.DataSourceCode).HasMaxLength(64);
b.Property(x => x.MaxRows).HasDefaultValue(McpConsts.DefaultMaxRows);
b.Property(x => x.AllowWrite).HasDefaultValue(false);
b.Property(x => x.Description).HasMaxLength(1024);
b.HasIndex(x => new { x.Name }).IsUnique().HasFilter("[IsDeleted] = 0");

View file

@ -45,6 +45,7 @@ public class McpHttpApiModule : AbpModule
.WithTools<McpSchemaTools>()
.WithTools<McpQueryTools>()
.WithTools<McpDataTools>()
.WithTools<McpDevelopTools>()
.WithRequestFilters(filters => filters.AddListToolsFilter(next => async (requestContext, cancellationToken) =>
{
var result = await next(requestContext, cancellationToken);

View file

@ -18,5 +18,9 @@ internal static class McpServerInfoDefaults
"Sozsoft Platform data access. Do not guess table or column names: first find the list with " +
"list_lists or search_schema, read its columns with describe_list, then query it with run_select " +
"using named @parameters. Never inline literal values into SQL. Columns marked isSensitive must " +
"not be selected. Call server_info first when a tool behaves unexpectedly.";
"not be selected. Call server_info first when a tool behaves unexpectedly. " +
"To build screens or components, read the contract with get_dev_guide first (index, then the " +
"section you need), inspect what exists with list_wizards/get_wizard and list_components/get_component, " +
"then produce them with deploy_wizard and save_component; the guide's file-writing steps map to these tools. " +
"Agree on screen type, data source, module and permissions with the user before deploying.";
}

View file

@ -8,8 +8,8 @@ namespace Sozsoft.Mcp.Tools;
/// <summary>
/// Veri yazma araclari. Yazma bir ListForm ekraninin sozlesmesinden gecer; ekran yetkisi ve
/// tenant filtresi aynen uygulanir. Sunucu kaydinda <c>AllowWrite</c> kapaliysa bu araclar
/// arac listesinde hic gorunmez.
/// tenant filtresi aynen uygulanir. Sunucu kaydinda <c>AllowedTools</c> dolu ve bu araclar
/// secili degilse arac listesinde hic gorunmez.
/// </summary>
[McpServerToolType]
public sealed class McpDataTools(IMcpDataAppService dataAppService)

View file

@ -0,0 +1,125 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
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)
{
[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. Hedef tablo onceden var olmalidir; yoksa once create_table ile olustur. 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. Bagimli wizard'i olan bir wizard silinemez, once bagimlilar silinir.")]
public async Task<WizardDeleteResultDto> DeleteWizardAsync(
[Description("Silinecek wizard dosyasinin adi (list_wizards cikisindaki fileName).")]
string fileName)
=> await developAppService.DeleteWizardAsync(fileName);
[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);
}

View file

@ -28455,20 +28455,26 @@
{
"resourceName": "Platform",
"key": "App.DeveloperKit.McpServers.Invoke",
"en": "Call MCP tools",
"tr": "MCP araçlarını çağır"
"en": "Invoke",
"tr": "Çağır"
},
{
"resourceName": "Platform",
"key": "App.DeveloperKit.McpServers.Write",
"en": "Write data over MCP",
"tr": "MCP üzerinden veri yaz"
"en": "Write Data",
"tr": "Veri Yaz"
},
{
"resourceName": "Platform",
"key": "App.DeveloperKit.McpServers.Develop",
"en": "Develop",
"tr": "Geliştir"
},
{
"resourceName": "Platform",
"key": "App.DeveloperKit.McpServers.Description",
"en": "MCP Servers — the Model Context Protocol endpoint definitions. Table: Sas_H_McpServer · host scope · list screen.\nUsage: an AI client (Claude Code, Claude Desktop, ChatGPT) connects to /mcp with the user's own access token; the record matching the request's tenant decides which tools are published, on which data source they run and with which row ceiling.\nFields:\n• Name → the server name shown in the tool list\n• Url → the address handed to the client (https://api.sozsoft.com/mcp in production, https://localhost:44344/mcp locally); the server does not route on this value\n• Tenants → which tenants may use it; when empty it is open to all\n• DataSourceCode → the data source the query tools run on; when empty the request's own scope is used\n• AllowedTools → the open tools; when empty every tool except the writing ones is open\n• MaxRows → the largest number of rows a single query may return\n• AllowWrite → while off, insert_row, update_row and delete_row are not even listed\n• IsActive → a passive record publishes no tools\nCaution: schema discovery reads the JSON data dictionary in Sas_H_DbTableColumn. When the dictionary is missing or stale the tools return no tables — run dbo.Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns with the tenant name.",
"tr": "MCP Sunucuları — Model Context Protocol uç tanımları. Tablo: Sas_H_McpServer · host kapsamlı · liste ekranı.\nKullanım: bir yapay zekâ istemcisi (Claude Code, Claude Desktop, ChatGPT) kullanıcının kendi erişim token'ı ile /mcp ucuna bağlanır; isteğin kiracısına düşen kayıt hangi araçların yayınlandığını, sorguların hangi veri kaynağında ve hangi satır tavanıyla koşacağını belirler.\nAlanlar:\n• Name → araç listesinde görünen sunucu adı\n• Url → istemciye verilen adres (sunucuda https://api.sozsoft.com/mcp, yerelde https://localhost:44344/mcp); sunucu bu değere göre yönlendirme yapmaz\n• Tenants → hangi kiracıların kullanabileceği; boş bırakılırsa tümüne açıktır\n• DataSourceCode → sorgu araçlarının koşacağı veri kaynağı; boş ise isteğin kendi kapsamı kullanılır\n• AllowedTools → açık araçlar; boş bırakılırsa yazma dışındaki tüm araçlar açıktır\n• MaxRows → tek sorgunun döndürebileceği en fazla satır sayısı\n• AllowWrite → kapalıyken insert_row, update_row ve delete_row araç listesinde hiç yer almaz\n• IsActive → pasif kayıt hiçbir araç yayınlamaz\nDikkat: şema keşfi Sas_H_DbTableColumn tablosundaki JSON veri sözlüğünü okur. Sözlük yok ya da bayatsa araçlar tablo döndürmez — dbo.Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns prosedürünü kiracı adıyla çalıştırın."
"en": "MCP Servers — the Model Context Protocol endpoint definitions. Table: Sas_H_McpServer · host scope · list screen.\nUsage: an AI client (Claude Code, Claude Desktop, ChatGPT) connects to /mcp with the user's own access token; the record matching the request's tenant decides which tools are published and with which row ceiling they run; queries always run on the requesting tenant's own database.\nFields:\n• Name → the server name shown in the tool list\n• Url → the address handed to the client (https://api.sozsoft.com/mcp in production, https://localhost:44344/mcp locally); the server does not route on this value\n• Tenants → which tenants may use it; when empty it is open to all\n• AllowedTools → the open tools; when empty every tool is open, when filled only the selected ones are. Tenant users only ever get the read tools (schema tools, query_list, run_select); this field can narrow that set but never widen it\n• MaxRows → the largest number of rows a single query may return\n• IsActive → a passive record publishes no tools\nCaution: schema discovery reads the JSON data dictionary in Sas_H_DbTableColumn. When the dictionary is missing or stale the tools return no tables — run dbo.Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns with the tenant name.",
"tr": "MCP Sunucuları — Model Context Protocol uç tanımları. Tablo: Sas_H_McpServer · host kapsamlı · liste ekranı.\nKullanım: bir yapay zekâ istemcisi (Claude Code, Claude Desktop, ChatGPT) kullanıcının kendi erişim token'ı ile /mcp ucuna bağlanır; isteğin kiracısına düşen kayıt hangi araçların yayınlandığını, sorguların hangi satır tavanıyla koşacağını belirler; sorgular her zaman isteğin kiracısının kendi veritabanında koşar.\nAlanlar:\n• Name → araç listesinde görünen sunucu adı\n• Url → istemciye verilen adres (sunucuda https://api.sozsoft.com/mcp, yerelde https://localhost:44344/mcp); sunucu bu değere göre yönlendirme yapmaz\n• Tenants → hangi kiracıların kullanabileceği; boş bırakılırsa tümüne açıktır\n• AllowedTools → açık araçlar; boş bırakılırsa tüm araçlar açıktır, doluysa yalnızca seçilenler açıktır. Kiracı kullanıcısı yalnızca okuma araçlarını alır (şema araçları, query_list, run_select); bu alan o kümeyi daraltabilir, genişletemez\n• MaxRows → tek sorgunun döndürebileceği en fazla satır sayısı\n• IsActive → pasif kayıt hiçbir araç yayınlamaz\nDikkat: şema keşfi Sas_H_DbTableColumn tablosundaki JSON veri sözlüğünü okur. Sözlük yok ya da bayatsa araçlar tablo döndürmez — dbo.Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns prosedürünü kiracı adıyla çalıştırın."
},
{
"resourceName": "Platform",
@ -28488,12 +28494,6 @@
"en": "Max Rows",
"tr": "En Fazla Satır"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.AllowWrite",
"en": "Allow Write",
"tr": "Yazmaya İzin Ver"
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.NoServerRecord",
@ -28506,6 +28506,12 @@
"en": "The '{0}' tool is disabled on the '{1}' MCP server.",
"tr": "'{0}' aracı '{1}' MCP sunucusunda kapalı."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.TenantReadOnly",
"en": "The '{0}' tool is not available to tenant users; over MCP a tenant can only read data.",
"tr": "'{0}' aracı kiracı kullanıcılarına açık değil; MCP üzerinden kiracı yalnızca veri okuyabilir."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.EmptyQuery",
@ -28578,6 +28584,90 @@
"en": "No key field value was given.",
"tr": "Anahtar alan değeri verilmedi."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.DevGuideUnavailable",
"en": "The development guide could not be found on the server. Check the {0} setting.",
"tr": "Geliştirme rehberi sunucuda bulunamadı. {0} ayarını kontrol edin."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.DevGuideSectionNotFound",
"en": "Section \"{0}\" was not found in the development guide. Call get_dev_guide without a section to see the headings.",
"tr": "Geliştirme rehberinde \"{0}\" bölümü bulunamadı. Başlıkları görmek için get_dev_guide aracını bölüm vermeden çağırın."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.InvalidWizard",
"en": "The wizard JSON could not be read. Send the seed file format ({\"Wizard\": {...}}) or the flat wizard answers.",
"tr": "Wizard JSON okunamadı. Seed dosyası biçimini ({\"Wizard\": {...}}) ya da düz wizard cevaplarını gönderin."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.ComponentNameRequired",
"en": "Component name is required.",
"tr": "Bileşen adı zorunludur."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.ComponentCodeRequired",
"en": "Code is required for a new component.",
"tr": "Yeni bileşen için kod zorunludur."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.ComponentNotFound",
"en": "Component \"{0}\" was not found.",
"tr": "\"{0}\" bileşeni bulunamadı."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.VisualDesignerComponent",
"en": "The code of component \"{0}\" is generated from its Visual Designer document and cannot be written over MCP. Edit it in the Visual Designer.",
"tr": "\"{0}\" bileşeninin kodu Visual Designer belgesinden üretiliyor, MCP üzerinden yazılamaz. Bileşeni Visual Designer'da düzenleyin."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.TableColumnsRequired",
"en": "At least one column is required to create a table.",
"tr": "Tablo oluşturmak için en az bir kolon gerekir."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.UnsupportedColumnType",
"en": "Unsupported column type: {0}. Supported types: {1}",
"tr": "Desteklenmeyen kolon tipi: {0}. Desteklenen tipler: {1}"
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.InvalidColumnLength",
"en": "The length of column \"{0}\" must be between 1 and {1}.",
"tr": "\"{0}\" kolonunun uzunluğu 1 ile {1} arasında olmalıdır."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.InvalidDefaultValue",
"en": "Invalid default value for column \"{0}\": {1}. Use a number, a quoted text or NEWID()/GETDATE()/GETUTCDATE()/SYSUTCDATETIME().",
"tr": "\"{0}\" kolonu için geçersiz varsayılan değer: {1}. Sayı, tırnaklı metin ya da NEWID()/GETDATE()/GETUTCDATE()/SYSUTCDATETIME() kullanın."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.DuplicateColumn",
"en": "Column \"{0}\" is defined more than once. Id, TenantId and audit columns are added automatically.",
"tr": "\"{0}\" kolonu birden fazla kez tanımlanmış. Id, TenantId ve audit kolonları otomatik eklenir."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.TableAlreadyExists",
"en": "Table \"{0}\" already exists. Change existing tables in the SQL Query Manager table designer.",
"tr": "\"{0}\" tablosu zaten var. Var olan tabloları SQL Query Manager tablo tasarımcısından değiştirin."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.TableProviderNotSupported",
"en": "Creating tables over MCP is supported only on SQL Server data sources (current: {0}).",
"tr": "MCP üzerinden tablo oluşturma yalnızca SQL Server veri kaynaklarında desteklenir (mevcut: {0})."
},
{
"resourceName": "Platform",
"key": "App.Mcp.Error.DictionaryNotFound",

View file

@ -7969,7 +7969,6 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
}),
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
new() { FieldName = "IsActive", FieldDbType = DbType.Boolean, Value = "true", CustomValueType = FieldCustomValueTypeEnum.Value },
new() { FieldName = "AllowWrite", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value },
new() { FieldName = "MaxRows", FieldDbType = DbType.Int32, Value = McpConsts.DefaultMaxRows.ToString(), CustomValueType = FieldCustomValueTypeEnum.Value },
}),
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.McpServer)),
@ -8081,40 +8080,19 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "DataSourceCode",
EditGroupOrderNo = 1,
EditOrderNo = 5,
EditorType2 = EditorTypes.dxSelectBox,
ColSpan = 1,
CaptionName = "App.Listform.ListformField.DataSourceCode",
Width = 0,
ListOrderNo = 6,
Visible = true,
IsActive = true,
AllowSearch = true,
LookupJson = LookupQueryValues.DefaultLookupQueryJson(nameof(TableNameEnum.DataSource), "Code", "Code"),
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Int32,
FieldName = "MaxRows",
EditGroupOrderNo = 1,
EditOrderNo = 6,
EditOrderNo = 5,
EditorType2 = EditorTypes.dxNumberBox,
EditorOptions = EditorOptions.Number(0),
ColSpan = 1,
CaptionName = "App.Listform.ListformField.MaxRows",
Width = 0,
ListOrderNo = 7,
ListOrderNo = 6,
Visible = true,
IsActive = true,
@ -8130,12 +8108,12 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
SourceDbType = DbType.String,
FieldName = "AllowedTools",
EditGroupOrderNo = 1,
EditOrderNo = 7,
EditOrderNo = 6,
EditorType2 = EditorTypes.dxTagBox,
ColSpan = 2,
CaptionName = "App.Listform.ListformField.AllowedTools",
Width = 0,
ListOrderNo = 8,
ListOrderNo = 7,
Visible = true,
IsActive = true,
@ -8153,39 +8131,19 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Boolean,
FieldName = "AllowWrite",
EditGroupOrderNo = 1,
EditOrderNo = 8,
EditorType2 = EditorTypes.dxCheckBox,
ColSpan = 1,
CaptionName = "App.Listform.ListformField.AllowWrite",
Width = 0,
ListOrderNo = 9,
Visible = true,
IsActive = true,
AllowSearch = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Description",
EditGroupOrderNo = 1,
EditOrderNo = 9,
EditOrderNo = 7,
EditorType2 = EditorTypes.dxTextArea,
EditorOptions = EditorOptions.Multiline(60),
ColSpan = 2,
CaptionName = "App.Listform.ListformField.Description",
Width = 0,
ListOrderNo = 10,
ListOrderNo = 8,
Visible = false,
IsActive = true,

View file

@ -517,7 +517,9 @@ public static class PlatformConsts
/// <summary>
/// MCP sunucu tanimlarinin yetkileri. <see cref="McpServers.McpServer"/> ekrani gorme
/// (okuma) yetkisidir; <see cref="McpServers.Invoke"/> ise /mcp ucundan arac
/// cagirma yetkisidir ve kiraci kullanicilarina da verilebilir.
/// cagirma yetkisidir ve kiraci kullanicilarina da verilebilir. <see cref="McpServers.Develop"/>
/// /mcp ucundan ekran ve bilesen gelistirme araclarini acar; gelistirme islemi yine
/// Wizard ve Custom Components yetkilerinden gecer.
/// </summary>
public static class McpServers
{
@ -528,6 +530,7 @@ public static class PlatformConsts
public const string Delete = McpServer + ".Delete";
public const string Invoke = McpServer + ".Invoke";
public const string Write = McpServer + ".Write";
public const string Develop = McpServer + ".Develop";
}
}

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20260910132559_Initial")]
[Migration("20260918122519_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -443,11 +443,6 @@ namespace Sozsoft.Platform.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<bool>("AllowWrite")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false);
b.Property<string>("AllowedTools")
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
@ -460,10 +455,6 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<string>("DataSourceCode")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");

View file

@ -1500,10 +1500,8 @@ namespace Sozsoft.Platform.Migrations
IsActive = table.Column<bool>(type: "bit", nullable: false),
Url = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
Tenants = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
DataSourceCode = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
AllowedTools = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
MaxRows = table.Column<int>(type: "int", nullable: false, defaultValue: 200),
AllowWrite = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
Description = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),

View file

@ -440,11 +440,6 @@ namespace Sozsoft.Platform.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<bool>("AllowWrite")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false);
b.Property<string>("AllowedTools")
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
@ -457,10 +452,6 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<string>("DataSourceCode")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");

View file

@ -8,6 +8,7 @@
"CdnUrl": "https://dev-cdn.sozsoft.com",
"CdnPath": "/etc/api/cdn",
"SeedsPath": "/etc/api/seeds",
"DevGuidePath": "/etc/api/dev-guide",
"BaseDomain": "sozsoft.com",
"BackupPath": "/var/opt/mssql/backup"
},

View file

@ -8,6 +8,7 @@
"CdnUrl": "https://cdn.sozsoft.com",
"CdnPath": "/etc/api/cdn",
"SeedsPath": "/etc/api/seeds",
"DevGuidePath": "/etc/api/dev-guide",
"BaseDomain": "sozsoft.com",
"BackupPath": "/var/opt/mssql/backup"
},

View file

@ -186,8 +186,14 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
ilgili `Mcp*Tools` sınıfı (`Sozsoft.Mcp.HttpApi`) ve arkasındaki AppService sözleşmesi
(`Sozsoft.Mcp.Application.Contracts`). Arac adı `McpToolNames.All` içinde yoksa araç listesi
süzgecinden geçmez ve hiç görünmez; MCP Servers ekranındaki `AllowedTools` seçenekleri de
`SelectableTools`'dan üretildiği için kendiliğinden güncellenir. Yazma araçları
`WriteTools` listesine de eklenir, yoksa `AllowWrite` kapalıyken de yayınlanırlar.
`SelectableTools`'dan üretildiği için kendiliğinden güncellenir. Sunucu kaydında tek anahtar
`AllowedTools`'tur: boşsa tüm araçlar, doluysa yalnızca seçilenler açıktır; araç tipine göre ayrı
bir bayrak yoktur, sınırı kullanıcı yetkisi çizer (`RequiredPermissions`). **Kiracı kullanıcısı
yalnızca okur**: kiracı bağlamında açık araçlar `McpToolNames.TenantTools` ile sınırlıdır ve
`AllowedTools` bu kümeyi yalnızca daraltır. Yeni bir araç veri yazıyor ya da okuma olduğu
garanti değilse (procedure, Custom Endpoint) `TenantTools`'a eklenmez. Veri yazan araçlar
`.Write`, ekran/bileşen geliştirme araçları `DevelopTools`'a eklenip `.Develop` yetkisine bağlanır;
işlem yine Wizard / Custom Components AppService'inden geçer.
Kapı iki yerdedir: `AddListToolsFilter` listeyi süzer, `McpAppService.EnsureToolAllowedAsync`
çağrıyı reddeder — süzme tek başına yetki sınırı değildir, ikisi birlikte yazılır.
- **MCP şema araçları** → tablo/sütun bilgisi canlı katalogdan değil `Sas_H_DbTableColumn`
@ -204,7 +210,9 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
- **MCP yetki vermez** → yeni bir MCP aracı kullanıcının uygulamadaki yetkisini aşamaz: liste verisi
ekranın sorgusundan (`IListFormSelectAppService`/`IListFormDataAppService`) geçer; liste
yetkisini aşan bir yetenek (ham SQL gibi) `McpToolNames.RequiredPermissions`'a uygulamadaki
karşılık gelen yetkiyle eklenir.
karşılık gelen yetkiyle eklenir. `create_table` bu yüzden `.Develop`'a ek olarak `App.SqlQueryManager`
ister; tablo scriptini tasarımcının `generateCreateTableSql` çıktısıyla aynı biçimde üretir
(`McpTableCreator`) — tasarımcının şablon kolonları ya da script biçimi değişirse o da değişir.
- **Yeni bir dil anahtarı** → `LanguagesData.json`; eklemeden önce anahtarın zaten var olup
olmadığını kontrol et.
- **Custom component sınıfları** → bileşen kodu veritabanında yaşar, Tailwind derlemede yalnızca

View file

@ -56,6 +56,9 @@ services:
# buradan okunur (ChangeLogStartupService). Boylece yeni tag API imajini
# yeniden derletmez.
- ../change-logs:/etc/api/change-logs:ro
# MCP get_dev_guide araci uretim sozlesmesini depodaki lowcode*.instructions.md
# dosyalarindan okur (App:DevGuidePath); rehber guncellendiginde imaj derlenmez.
- ../../.github/instructions:/etc/api/dev-guide:ro
networks:
- db
- default

View file

@ -2569,6 +2569,15 @@
"MultiTenancySide": 3,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Saas",
"Name": "App.DeveloperKit.McpServers.Develop",
"ParentName": "App.DeveloperKit.McpServers",
"DisplayName": "App.DeveloperKit.McpServers.Develop",
"IsEnabled": true,
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Saas",
"Name": "App.Listforms.Listform",

View file

@ -10,11 +10,9 @@
"IsActive": true,
"Url": "https://api.sozsoft.com/mcp",
"Tenants": null,
"DataSourceCode": null,
"AllowedTools": null,
"MaxRows": 200,
"AllowWrite": false,
"Description": "Default read-only MCP endpoint. Tools are gated by the App.DeveloperKit.McpServers.Invoke permission; turn AllowWrite on only for tenants that should write data through MCP."
"Description": "Default MCP endpoint. Every tool is published while AllowedTools is empty; each call still needs the user's own permission (App.DeveloperKit.McpServers.Invoke, .Write for writing data, .Develop for screen development)."
}
]
}