From d21e87c26f41641efb3f574fc9bd28518adf3831 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?=
<76204082+iamsedatozturk@users.noreply.github.com>
Date: Fri, 18 Sep 2026 16:47:23 +0300
Subject: [PATCH] MCP Servers Develop eklendi
---
.github/instructions/ai.instructions.md | 4 +-
.../lowcode-reference.instructions.md | 5 +-
.github/instructions/lowcode.instructions.md | 2 +-
README.md | 45 ++-
.../Dtos/McpDevelopDtos.cs | 133 +++++++
.../Dtos/McpSchemaDtos.cs | 7 +-
.../IMcpDataAppService.cs | 2 +-
.../IMcpDevelopAppService.cs | 37 ++
.../Sozsoft.Mcp.Application.Contracts.csproj | 3 +
.../Sozsoft.Mcp.Application/McpAppService.cs | 10 +-
.../McpDataAppService.cs | 2 -
.../McpDevelopAppService.cs | 341 ++++++++++++++++++
.../McpQueryAppService.cs | 12 +-
.../McpSchemaAppService.cs | 2 +-
.../Sozsoft.Mcp.Application.csproj | 2 -
.../McpErrorCodes.cs | 15 +
.../Sozsoft.Mcp.Domain.Shared/McpToolNames.cs | 62 +++-
.../McpToolPolicy.cs | 14 +-
.../Sozsoft.Mcp.Domain/Entities/McpServer.cs | 17 +-
.../Services/McpDevGuide.cs | 177 +++++++++
.../Services/McpQueryExecutor.cs | 8 +-
.../Services/McpTableCreator.cs | 262 ++++++++++++++
.../McpDbContextModelCreatingExtensions.cs | 2 -
.../Sozsoft.Mcp.HttpApi/McpHttpApiModule.cs | 1 +
.../McpServerInfoDefaults.cs | 6 +-
.../Sozsoft.Mcp.HttpApi/Tools/McpDataTools.cs | 4 +-
.../Tools/McpDevelopTools.cs | 125 +++++++
.../Migrations/LanguagesData.json | 114 +++++-
.../Migrations/ListFormSeeder_Saas.cs | 54 +--
.../PlatformConsts.cs | 5 +-
....cs => 20260918122519_Initial.Designer.cs} | 11 +-
...9_Initial.cs => 20260918122519_Initial.cs} | 2 -
.../PlatformDbContextModelSnapshot.cs | 9 -
.../appsettings.Dev.json | 1 +
.../appsettings.Production.json | 1 +
claude.md | 14 +-
configs/deployment/docker-compose-app.yml | 3 +
.../host/data/Abp.Identity.Permissions.json | 9 +
.../data/App.DeveloperKit.McpServers.json | 4 +-
39 files changed, 1373 insertions(+), 154 deletions(-)
create mode 100644 api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Dtos/McpDevelopDtos.cs
create mode 100644 api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDevelopAppService.cs
create mode 100644 api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs
create mode 100644 api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpDevGuide.cs
create mode 100644 api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs
create mode 100644 api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs
rename api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/{20260910132559_Initial.Designer.cs => 20260918122519_Initial.Designer.cs} (99%)
rename api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/{20260910132559_Initial.cs => 20260918122519_Initial.cs} (99%)
diff --git a/.github/instructions/ai.instructions.md b/.github/instructions/ai.instructions.md
index 3313614f..ef876035 100644
--- a/.github/instructions/ai.instructions.md
+++ b/.github/instructions/ai.instructions.md
@@ -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ı:
diff --git a/.github/instructions/lowcode-reference.instructions.md b/.github/instructions/lowcode-reference.instructions.md
index a78ea592..cf4ba750 100644
--- a/.github/instructions/lowcode-reference.instructions.md
+++ b/.github/instructions/lowcode-reference.instructions.md
@@ -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 |
diff --git a/.github/instructions/lowcode.instructions.md b/.github/instructions/lowcode.instructions.md
index d7984297..4c1a0f41 100644
--- a/.github/instructions/lowcode.instructions.md
+++ b/.github/instructions/lowcode.instructions.md
@@ -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 |
diff --git a/README.md b/README.md
index d271f329..b6a166df 100644
--- a/README.md
+++ b/README.md
@@ -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.
---
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Dtos/McpDevelopDtos.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Dtos/McpDevelopDtos.cs
new file mode 100644
index 00000000..a82e4412
--- /dev/null
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Dtos/McpDevelopDtos.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json;
+
+namespace Sozsoft.Mcp.Dtos;
+
+/// Gelistirme rehberinin bir basligi.
+public class McpDevGuideHeadingDto
+{
+ /// lowcode (uretim protokolu, artefakt semalari) ya da reference (JSON kolon sozlesmeleri).
+ public string Document { get; set; } = string.Empty;
+
+ /// Bolum numarasi (4.3); get_dev_guide'a bu deger verilir.
+ public string Id { get; set; } = string.Empty;
+
+ public string Title { get; set; } = string.Empty;
+
+ public int Level { get; set; }
+}
+
+///
+/// Rehber cevabi. Bolum istenmediyse yalnizca doludur; istendiyse
+/// bolumun metnini, alt basliklarini tasir.
+///
+public class McpDevGuideDto
+{
+ public McpDevGuideHeadingDto? Heading { get; set; }
+
+ public string? Content { get; set; }
+
+ public List Sections { get; set; } = [];
+}
+
+/// Bir wizard seed dosyasi; dosyanin diskteki bicimidir.
+public class McpWizardFileDto
+{
+ public string FileName { get; set; } = string.Empty;
+
+ public JsonElement Seed { get; set; }
+}
+
+/// Wizard'i ilk kez ya da yeniden deploy etme istegi.
+public class McpWizardDeployRequestDto
+{
+ ///
+ /// Seed dosyasi bicimindeki JSON ({"Wizard": {...}}) ya da duz wizard cevaplari.
+ ///
+ public string Wizard { get; set; } = string.Empty;
+
+ /// Doluysa guncellemedir: bu dosya ve urettigi kayitlar silinip yeniden uretilir.
+ public string? ReplaceFileName { get; set; }
+}
+
+///
+/// Yeni tablo istegi. Id, TenantId ve audit kolonlari sablondan gelir; burada
+/// yalnizca is kolonlari yazilir.
+///
+public class McpCreateTableRequestDto
+{
+ public string TableName { get; set; } = string.Empty;
+
+ public List 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;
+
+ /// Tablo tasarimcisinin tip listesinden biri (nvarchar, int, decimal, bit ...).
+ public string Type { get; set; } = string.Empty;
+
+ /// Yalnizca nvarchar icin.
+ public int? MaxLength { get; set; }
+
+ public bool IsNullable { get; set; } = true;
+
+ /// Sayi, metin sabiti ('...') ya da NEWID()/GETDATE()/GETUTCDATE()/SYSUTCDATETIME().
+ public string? DefaultValue { get; set; }
+}
+
+public class McpCreateTableResultDto
+{
+ public string TableName { get; set; } = string.Empty;
+
+ public string Script { get; set; } = string.Empty;
+
+ /// Script'in yazildigi seed dosyasi, seed kokune gore goreli.
+ public string SeedFilePath { get; set; } = string.Empty;
+}
+
+/// Custom component ozeti; kod tasimaz.
+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; }
+
+ /// Kodu tasarimci belgesinden uretilen bilesen; MCP uzerinden kodu yazilamaz.
+ public bool IsVisualDesigner { get; set; }
+}
+
+///
+/// Custom component kaydetme istegi; ada gore varsa gunceller, yoksa ekler. Guncellemede null
+/// birakilan alanlar mevcut degerini korur.
+///
+public class McpComponentSaveRequestDto
+{
+ public string Name { get; set; } = string.Empty;
+
+ public string? Code { get; set; }
+
+ /// Bos dize rotayi kaldirir (rotasiz bilesen); null mevcut rotayi korur.
+ public string? RoutePath { get; set; }
+
+ public string? Description { get; set; }
+
+ public string? Props { get; set; }
+
+ /// Bagimli bilesen adlarinin JSON dizisi.
+ public string? Dependencies { get; set; }
+
+ public bool? IsActive { get; set; }
+}
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Dtos/McpSchemaDtos.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Dtos/McpSchemaDtos.cs
index fc50faeb..5dd7884f 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Dtos/McpSchemaDtos.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Dtos/McpSchemaDtos.cs
@@ -14,16 +14,11 @@ public class McpServerConfigDto
/// Ekranda gosterilen baglanti adresi; sunucu tarafinda yonlendirme icin kullanilmaz.
public string? Url { get; set; }
- /// Bos ise istegin kapsamindaki varsayilan veri kaynagi kullanilir.
- public string? DataSourceCode { get; set; }
-
- /// Bos liste "yazma disindaki tum araclar acik" anlamina gelir.
+ /// Bos liste "tum araclar acik" anlamina gelir.
public List AllowedTools { get; set; } = [];
public int MaxRows { get; set; }
- public bool AllowWrite { get; set; }
-
public string? Description { get; set; }
/// Istegin cozumlenen kiraci adi; host baglaminda bostur.
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDataAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDataAppService.cs
index 40e0b496..541dcbb6 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDataAppService.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDataAppService.cs
@@ -7,7 +7,7 @@ namespace Sozsoft.Mcp;
///
/// MCP veri yazma yuzeyi. Yazma her zaman bir ListForm ekraninin insert/update/delete
/// sozlesmesinden gecer; ekran yetkisi ve tenant filtresi aynen uygulanir. Sunucu kaydinda
-/// AllowWrite kapali oldugunda bu araclar hic listelenmez.
+/// AllowedTools dolu ve bu araclar secili degilse hic listelenmez.
///
public interface IMcpDataAppService : IApplicationService
{
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDevelopAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDevelopAppService.cs
new file mode 100644
index 00000000..41e5133d
--- /dev/null
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDevelopAppService.cs
@@ -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;
+
+///
+/// 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.
+///
+public interface IMcpDevelopAppService : IApplicationService
+{
+ Task GetDevGuideAsync(string? document, string? section);
+
+ Task CreateTableAsync(McpCreateTableRequestDto input);
+
+ Task> GetWizardsAsync(string? search);
+
+ Task GetWizardAsync(string fileName);
+
+ /// Deploy edilen wizard'in ozeti.
+ Task DeployWizardAsync(McpWizardDeployRequestDto input);
+
+ Task DeleteWizardAsync(string fileName);
+
+ Task> GetComponentsAsync(string? search);
+
+ Task GetComponentAsync(string name);
+
+ Task SaveComponentAsync(McpComponentSaveRequestDto input);
+
+ Task DeleteComponentAsync(string name);
+}
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Sozsoft.Mcp.Application.Contracts.csproj b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Sozsoft.Mcp.Application.Contracts.csproj
index 5b219c2c..d49ddda1 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Sozsoft.Mcp.Application.Contracts.csproj
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Sozsoft.Mcp.Application.Contracts.csproj
@@ -15,6 +15,9 @@
+
+
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpAppService.cs
index 3f259ab7..1a92ffdf 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpAppService.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpAppService.cs
@@ -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]);
}
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDataAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDataAppService.cs
index 2ce3cbd3..5b5c6d6c 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDataAppService.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDataAppService.cs
@@ -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))
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs
new file mode 100644
index 00000000..d49aa779
--- /dev/null
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs
@@ -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;
+
+///
+/// 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.
+///
+[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";
+
+ /// Index cevabinda listelenen en derin baslik; daha derini bolum istenince gorunur.
+ private const int IndexHeadingLevel = 3;
+
+ /// Custom Components ekraninin tek sayfada dondurebildigi ust sinir.
+ private const int ComponentPageSize = 1000;
+
+ public async Task 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)]
+ };
+ }
+
+ ///
+ /// Tablo tasarimcisiyla ayni yetkiyi ister: ham DDL liste yetkilerini asar, Develop yalnizca
+ /// MCP kapisidir.
+ ///
+ public async Task 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> 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 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 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 DeleteWizardAsync(string fileName)
+ {
+ await EnsureToolAllowedAsync(McpToolNames.DeleteWizard);
+
+ return await wizardAppService.DeleteFile(fileName);
+ }
+
+ public async Task> 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 GetComponentAsync(string name)
+ {
+ await EnsureToolAllowedAsync(McpToolNames.GetComponent);
+
+ return await FindComponentAsync(name);
+ }
+
+ public async Task 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);
+ }
+
+ ///
+ /// Wizard'i seed dosyasi biciminden ({"Wizard": {...}}) ya da duz cevaplardan okur.
+ /// Iki bicim de dosyanin okuma kuralindan gecer; boylece Menu/Language bloklari ve
+ /// WizardCode/ModuleId adlari dosyadakiyle ayni anlama gelir.
+ ///
+ 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> GetAllComponentsAsync()
+ {
+ var page = await componentAppService.GetListAsync(new PagedAndSortedResultRequestDto
+ {
+ MaxResultCount = ComponentPageSize,
+ Sorting = nameof(CustomComponentDto.Name)
+ });
+
+ return page.Items;
+ }
+
+ private async Task 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));
+ }
+
+ ///
+ /// Props icinde tasarimci belgesi varsa ve kip acikca code degilse kod belgeden
+ /// uretilir; tasarimcinin varsayilan kipi visual'dir.
+ ///
+ 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
+ };
+}
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpQueryAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpQueryAppService.cs
index d6356d2e..0958e7d2 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpQueryAppService.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpQueryAppService.cs
@@ -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> GetSavedQueriesAsync(string? search = null)
{
- var config = await EnsureToolAllowedAsync(McpToolNames.ListQueries);
+ await EnsureToolAllowedAsync(McpToolNames.ListQueries);
var results = new List();
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> GetDatabaseObjectsAsync(McpServerConfigDto config)
+ private async Task> 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)
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpSchemaAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpSchemaAppService.cs
index fc1efea9..a0a88a9f 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpSchemaAppService.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpSchemaAppService.cs
@@ -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
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/Sozsoft.Mcp.Application.csproj b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/Sozsoft.Mcp.Application.csproj
index 9e6c0163..13eb9d28 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/Sozsoft.Mcp.Application.csproj
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/Sozsoft.Mcp.Application.csproj
@@ -15,8 +15,6 @@
-
-
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs
index 43f73334..30de78ff 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs
@@ -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";
}
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpToolNames.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpToolNames.cs
index 2c6f77e1..d0c25035 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpToolNames.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpToolNames.cs
@@ -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";
///
- /// Veri yazan araclar. Sunucu kaydinda AllowWrite kapaliysa bunlar arac listesinde
- /// hic yer almaz.
+ /// Ekran ve bilesen gelistirme araclari. Sunucu bazinda AllowedTools ile, kullanici
+ /// bazinda McpServers.Develop yetkisiyle kapatilir.
///
- public static readonly string[] WriteTools = [InsertRow, UpdateRow, DeleteRow];
+ public static readonly string[] DevelopTools =
+ [
+ GetDevGuide,
+ CreateTable,
+ ListWizards,
+ GetWizard,
+ DeployWizard,
+ DeleteWizard,
+ ListComponents,
+ GetComponent,
+ SaveComponent,
+ DeleteComponent
+ ];
///
/// Sunucu kaydi olmasa da acik kalan araclar. Istemcinin baglantisinin neden bos
@@ -35,10 +57,29 @@ public static class McpToolNames
///
public static readonly string[] AlwaysAvailableTools = [ServerInfo];
+ ///
+ /// Kiraci kullanicisinin gorebildigi araclarin tamami; kiraci baglaminda MCP yalnizca veri
+ /// okur. AllowedTools bu kumeyi yalnizca daraltabilir, genisletemez.
+ /// ve disarida kalir: procedure ve Custom Endpoint cagrisi veri
+ /// yazabilir, okuma oldugu garanti edilemez. ise tek SELECT disindaki
+ /// her ifadeyi reddettigi icin okumadir.
+ ///
+ public static readonly string[] TenantTools =
+ [
+ ServerInfo,
+ ListLists,
+ DescribeList,
+ SearchSchema,
+ QueryList,
+ RunSelect
+ ];
+
///
/// 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.
///
public static readonly IReadOnlyDictionary RequiredPermissions = new Dictionary
{
@@ -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,
};
/// Yayinlanan tum araclar, arac listesindeki siralarina gore.
@@ -61,7 +112,8 @@ public static class McpToolNames
RunQuery,
InsertRow,
UpdateRow,
- DeleteRow
+ DeleteRow,
+ .. DevelopTools
];
///
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpToolPolicy.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpToolPolicy.cs
index 73861c20..6d1fe8c8 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpToolPolicy.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpToolPolicy.cs
@@ -12,11 +12,11 @@ namespace Sozsoft.Mcp;
public static class McpToolPolicy
{
/// Istegin kiracisina dusen aktif bir sunucu kaydi var mi.
- /// Sunucu kaydinda veri yazma acik mi.
- /// Acik arac adlari; bos liste "yazma disindaki tumu acik" demektir.
+ /// Istek bir kiraci kullanicisindan mi geliyor; o zaman yalnizca okuma araclari acilir.
+ /// Acik arac adlari; bos liste "tumu acik" demektir.
public static bool IsAllowed(
bool hasServerRecord,
- bool allowWrite,
+ bool isTenant,
IReadOnlyCollection 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 VisibleTools(
bool hasServerRecord,
- bool allowWrite,
+ bool isTenant,
IReadOnlyCollection allowedTools)
- => [.. McpToolNames.All.Where(name => IsAllowed(hasServerRecord, allowWrite, allowedTools, name))];
+ => [.. McpToolNames.All.Where(name => IsAllowed(hasServerRecord, isTenant, allowedTools, name))];
}
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Entities/McpServer.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Entities/McpServer.cs
index be26c671..482273ca 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Entities/McpServer.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Entities/McpServer.cs
@@ -6,8 +6,8 @@ namespace Sozsoft.Mcp.Entities;
///
/// MCP (Model Context Protocol) sunucu tanimi. Kayit host kapsamindadir; hangi kiracilarin
/// kullanabilecegi 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.
///
public class McpServer : FullAuditedEntity
{
@@ -28,22 +28,13 @@ public class McpServer : FullAuditedEntity
public string? Tenants { get; set; }
///
- /// Sorgu araclarinin kosacagi DataSource kodu. Bos ise istegin kapsamindaki varsayilan
- /// veri kaynagi kullanilir.
- ///
- public string? DataSourceCode { get; set; }
-
- ///
- /// Acik arac adlari, PlatformConsts.MultiValueDelimiter ile ayrilir. Bos ise yazma
- /// disindaki tum araclar aciktir.
+ /// Acik arac adlari, PlatformConsts.MultiValueDelimiter ile ayrilir. Bos ise tum
+ /// araclar aciktir.
///
public string? AllowedTools { get; set; }
/// Tek sorgunun dondurebilecegi en fazla satir sayisi.
public int MaxRows { get; set; } = McpConsts.DefaultMaxRows;
- /// Kapali oldugunda veri yazma araclari arac listesinde hic yer almaz.
- public bool AllowWrite { get; set; }
-
public string? Description { get; set; }
}
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpDevGuide.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpDevGuide.cs
new file mode 100644
index 00000000..5feabc3f
--- /dev/null
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpDevGuide.cs
@@ -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;
+
+///
+/// Modelin ekran ve bilesen uretirken okudugu sozlesme: depodaki
+/// .github/instructions/lowcode*.instructions.md dosyalari. Claude Code ayni dosyalari
+/// depodan okur; MCP istemcisi ise bu servisten bolum bolum alir, boylece iki yolun bildigi
+/// sozlesme ayrisamaz.
+///
+public interface IMcpDevGuide
+{
+ /// Okunabilen belgelerin baslik agaci; klasor bulunamazsa bos.
+ Task> GetHeadingsAsync();
+
+ ///
+ /// Bir bolumun metni. bolum numarasi (4.3) ya da baslik
+ /// metninin bir parcasidir; bulunamazsa null.
+ ///
+ Task GetSectionAsync(string document, string section);
+}
+
+/// Belgenin kisa adi ().
+/// Bolum numarasi; numarasiz basliklarda baslik metni.
+public sealed record McpDevGuideHeading(string Document, string Id, string Title, int Level);
+
+public sealed record McpDevGuideSection(McpDevGuideHeading Heading, string Content, IReadOnlyList Children);
+
+public partial class McpDevGuide(IConfiguration configuration) : DomainService, IMcpDevGuide
+{
+ /// Konteynerde mount noktasi; bos ise depo klasoru calisma dizininden yukari aranir.
+ public const string ConfigurationKey = "App:DevGuidePath";
+
+ /// Kisa ad → dosya. Yalnizca uretim sozlesmesi sunulur; kod standardi ve karar kurallari degil.
+ public static readonly IReadOnlyDictionary Documents = new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ ["lowcode"] = "lowcode.instructions.md",
+ ["reference"] = "lowcode-reference.instructions.md",
+ };
+
+ ///
+ /// 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.
+ ///
+ private const int MaxSectionLength = 60_000;
+
+ [GeneratedRegex(@"^(#{2,4})\s+(?:(\d+(?:\.\d+)*)\.?\s+)?(.+?)\s*$")]
+ private static partial Regex HeadingPattern();
+
+ public async Task> GetHeadingsAsync()
+ {
+ var headings = new List();
+ 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 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 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;
+ }
+
+ ///
+ /// Kod bloklari icindeki ## ile baslayan satirlar (ornegin SQL yorumlari) baslik
+ /// sayilmaz; aksi halde bolum sinirlari ornek kodun ortasinda kesilirdi.
+ ///
+ 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;
+ }
+}
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpQueryExecutor.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpQueryExecutor.cs
index a4861b18..43adbac4 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpQueryExecutor.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpQueryExecutor.cs
@@ -16,10 +16,10 @@ namespace Sozsoft.Mcp.Services;
public interface IMcpQueryExecutor
{
///
- /// Sunucu kaydindaki kod. Bos ya da !Tenant ise veri kaynagi istegin kiracisindan
- /// cozulur; host baglaminda varsayilan kaynaga duser.
+ /// Custom Endpoint'in kendi veri kaynagi kodu. Bos ya da !Tenant ise veri kaynagi
+ /// istegin kiracisindan cozulur; host baglaminda varsayilan kaynaga duser.
///
- Task ResolveDataSourceAsync(string? dataSourceCode);
+ Task ResolveDataSourceAsync(string? dataSourceCode = null);
///
/// Sorguyu kosturur. Sonuc kumesi satirda kesilir; kesilme
@@ -34,7 +34,7 @@ public interface IMcpQueryExecutor
public class McpQueryExecutor(IDynamicDataManager dynamicDataManager) : DomainService, IMcpQueryExecutor
{
- public async Task ResolveDataSourceAsync(string? dataSourceCode)
+ public async Task ResolveDataSourceAsync(string? dataSourceCode = null)
{
var useTenantScope = string.IsNullOrWhiteSpace(dataSourceCode)
|| dataSourceCode == McpConsts.TenantDataSourceMarker;
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs
new file mode 100644
index 00000000..30839e55
--- /dev/null
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs
@@ -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;
+
+///
+/// Yeni tabloyu SQL Query Manager'daki tablo tasarimcisiyla ayni sozlesmeyle kurar: script
+/// tasarimcinin generateCreateTableSql ciktisiyla ayni bicimdedir ve tasarimcinin deploy
+/// sonrasi yazdigi yere ({kapsam}/sql/object/{Tablo}.sql) yazilir; boylece tablo
+/// tasarimcida acilip duzenlenebilir ve veritabani sifirlandiginda seed ile geri gelir.
+///
+/// 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.
+///
+///
+public interface IMcpTableCreator
+{
+ Task CreateAsync(McpTableDefinition definition);
+}
+
+public class McpTableCreator(
+ IMcpQueryExecutor queryExecutor,
+ IConfiguration configuration,
+ IStringLocalizer 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);
+
+ ///
+ /// Varsayilan deger olarak kabul edilenler: sayi, metin sabiti ve tasarimcinin kendi
+ /// sablonlarinda kullandigi fonksiyonlar. Baska bir ifade script'e hic girmez.
+ ///
+ private static readonly Regex SafeDefaultRegex = new(
+ @"^(?:-?\d{1,18}(?:\.\d{1,18})?|N?'(?:[^']|'')*'|NEWID\(\)|GETDATE\(\)|GETUTCDATE\(\)|SYSUTCDATETIME\(\))$",
+ RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
+
+ /// Tasarimcinin tip listesi (DATA_TYPES) ile aynidir.
+ 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 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(
+ "SELECT OBJECT_ID(@name, 'U')",
+ context.ConnectionString,
+ new Dictionary { ["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 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 { 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 columns)
+ {
+ var fullTableName = $"[{SchemaName}].[{tableName}]";
+
+ var lines = new List
+ {
+ $"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}";
+ }
+
+ ///
+ /// Tasarimcinin SaveTableScriptAsync ile yazdigi yol: kapsam istegin kiracisini izler.
+ /// Dosyasiz tablo veritabani sifirlandiginda geri gelmezdi; yazilamazsa islem hata verir.
+ ///
+ 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]);
+ }
+ }
+}
+
+/// Tam audit kolonlari (olusturma/degistirme/silme izi) eklensin mi.
+/// TenantId kolonu eklensin mi.
+public sealed record McpTableDefinition(
+ string TableName,
+ IReadOnlyList Columns,
+ bool FullAudited,
+ bool MultiTenant);
+
+/// Yalnizca nvarchar icin; bos ise tasarimcinin varsayilani (100).
+public sealed record McpTableColumn(
+ string Name,
+ string Type,
+ int? MaxLength,
+ bool IsNullable,
+ string? DefaultValue);
+
+/// Seed kokune gore goreli yol.
+public sealed record McpCreatedTable(string TableName, string Script, string SeedFilePath);
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.EntityFrameworkCore/EntityFrameworkCore/McpDbContextModelCreatingExtensions.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.EntityFrameworkCore/EntityFrameworkCore/McpDbContextModelCreatingExtensions.cs
index 40714282..fdd9c452 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.EntityFrameworkCore/EntityFrameworkCore/McpDbContextModelCreatingExtensions.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.EntityFrameworkCore/EntityFrameworkCore/McpDbContextModelCreatingExtensions.cs
@@ -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");
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpHttpApiModule.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpHttpApiModule.cs
index 69ef42d3..1d3163cf 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpHttpApiModule.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpHttpApiModule.cs
@@ -45,6 +45,7 @@ public class McpHttpApiModule : AbpModule
.WithTools()
.WithTools()
.WithTools()
+ .WithTools()
.WithRequestFilters(filters => filters.AddListToolsFilter(next => async (requestContext, cancellationToken) =>
{
var result = await next(requestContext, cancellationToken);
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpServerInfoDefaults.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpServerInfoDefaults.cs
index 6da4b77a..23786a43 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpServerInfoDefaults.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpServerInfoDefaults.cs
@@ -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.";
}
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDataTools.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDataTools.cs
index b60d95ef..559d440e 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDataTools.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDataTools.cs
@@ -8,8 +8,8 @@ namespace Sozsoft.Mcp.Tools;
///
/// Veri yazma araclari. Yazma bir ListForm ekraninin sozlesmesinden gecer; ekran yetkisi ve
-/// tenant filtresi aynen uygulanir. Sunucu kaydinda AllowWrite kapaliysa bu araclar
-/// arac listesinde hic gorunmez.
+/// tenant filtresi aynen uygulanir. Sunucu kaydinda AllowedTools dolu ve bu araclar
+/// secili degilse arac listesinde hic gorunmez.
///
[McpServerToolType]
public sealed class McpDataTools(IMcpDataAppService dataAppService)
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs
new file mode 100644
index 00000000..b8f713c5
--- /dev/null
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs
@@ -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;
+
+///
+/// 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.
+///
+[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 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 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 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> 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 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 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 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> 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 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 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);
+}
diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json
index ad99dfa5..7a330679 100644
--- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json
+++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json
@@ -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",
diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs
index 53dbd282..7790dca8 100644
--- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs
+++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs
@@ -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,
diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs
index 76103bc5..6887f2c5 100644
--- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs
+++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs
@@ -517,7 +517,9 @@ public static class PlatformConsts
///
/// MCP sunucu tanimlarinin yetkileri. ekrani gorme
/// (okuma) yetkisidir; ise /mcp ucundan arac
- /// cagirma yetkisidir ve kiraci kullanicilarina da verilebilir.
+ /// cagirma yetkisidir ve kiraci kullanicilarina da verilebilir.
+ /// /mcp ucundan ekran ve bilesen gelistirme araclarini acar; gelistirme islemi yine
+ /// Wizard ve Custom Components yetkilerinden gecer.
///
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";
}
}
diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260910132559_Initial.Designer.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260918122519_Initial.Designer.cs
similarity index 99%
rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260910132559_Initial.Designer.cs
rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260918122519_Initial.Designer.cs
index ecb9f20e..c1185765 100644
--- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260910132559_Initial.Designer.cs
+++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260918122519_Initial.Designer.cs
@@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
- [Migration("20260910132559_Initial")]
+ [Migration("20260918122519_Initial")]
partial class Initial
{
///
@@ -443,11 +443,6 @@ namespace Sozsoft.Platform.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
- b.Property("AllowWrite")
- .ValueGeneratedOnAdd()
- .HasColumnType("bit")
- .HasDefaultValue(false);
-
b.Property("AllowedTools")
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
@@ -460,10 +455,6 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
- b.Property("DataSourceCode")
- .HasMaxLength(64)
- .HasColumnType("nvarchar(64)");
-
b.Property("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260910132559_Initial.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260918122519_Initial.cs
similarity index 99%
rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260910132559_Initial.cs
rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260918122519_Initial.cs
index 2da1203b..f79e372c 100644
--- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260910132559_Initial.cs
+++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260918122519_Initial.cs
@@ -1500,10 +1500,8 @@ namespace Sozsoft.Platform.Migrations
IsActive = table.Column(type: "bit", nullable: false),
Url = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true),
Tenants = table.Column(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
- DataSourceCode = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true),
AllowedTools = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
MaxRows = table.Column(type: "int", nullable: false, defaultValue: 200),
- AllowWrite = table.Column(type: "bit", nullable: false, defaultValue: false),
Description = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
CreationTime = table.Column(type: "datetime2", nullable: false),
CreatorId = table.Column(type: "uniqueidentifier", nullable: true),
diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs
index 3dafbca9..468c4476 100644
--- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs
+++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs
@@ -440,11 +440,6 @@ namespace Sozsoft.Platform.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
- b.Property("AllowWrite")
- .ValueGeneratedOnAdd()
- .HasColumnType("bit")
- .HasDefaultValue(false);
-
b.Property("AllowedTools")
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
@@ -457,10 +452,6 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
- b.Property("DataSourceCode")
- .HasMaxLength(64)
- .HasColumnType("nvarchar(64)");
-
b.Property("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Dev.json b/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Dev.json
index 18bcb4ee..92f824e2 100644
--- a/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Dev.json
+++ b/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Dev.json
@@ -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"
},
diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Production.json b/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Production.json
index 83939862..b0f06cf5 100644
--- a/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Production.json
+++ b/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Production.json
@@ -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"
},
diff --git a/claude.md b/claude.md
index d30b0902..935aca81 100644
--- a/claude.md
+++ b/claude.md
@@ -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
diff --git a/configs/deployment/docker-compose-app.yml b/configs/deployment/docker-compose-app.yml
index 4f6e575f..ac2b8f3d 100644
--- a/configs/deployment/docker-compose-app.yml
+++ b/configs/deployment/docker-compose-app.yml
@@ -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
diff --git a/configs/seeds/host/data/Abp.Identity.Permissions.json b/configs/seeds/host/data/Abp.Identity.Permissions.json
index 2da15d2a..21e01f2c 100644
--- a/configs/seeds/host/data/Abp.Identity.Permissions.json
+++ b/configs/seeds/host/data/Abp.Identity.Permissions.json
@@ -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",
diff --git a/configs/seeds/host/data/App.DeveloperKit.McpServers.json b/configs/seeds/host/data/App.DeveloperKit.McpServers.json
index 5d7df705..9b8c657e 100644
--- a/configs/seeds/host/data/App.DeveloperKit.McpServers.json
+++ b/configs/seeds/host/data/App.DeveloperKit.McpServers.json
@@ -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)."
}
]
}