MCP Servers Develop eklendi
This commit is contained in:
parent
197b3775f8
commit
d21e87c26f
39 changed files with 1373 additions and 154 deletions
4
.github/instructions/ai.instructions.md
vendored
4
.github/instructions/ai.instructions.md
vendored
|
|
@ -363,7 +363,9 @@ AI workflow integration:
|
||||||
MCP (`/mcp`, `Sozsoft.Mcp` module) — expose ListForm data/queries to an external AI agent
|
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
|
(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
|
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
|
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
|
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ı:
|
never grants access beyond the calling user's own ListForm/query permissions. Ayrıntı:
|
||||||
|
|
|
||||||
|
|
@ -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 › 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 › 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 › 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 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 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ı. |
|
| **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ı |
|
| "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` |
|
| "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 |
|
| "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 |
|
| "ü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 |
|
| "yapay zekâ cevaplasın" | Saas › Definitions › AiBot | Bot tanımı; `/admin/ai` bu tanımı kullanır |
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/instructions/lowcode.instructions.md
vendored
2
.github/instructions/lowcode.instructions.md
vendored
|
|
@ -64,7 +64,7 @@ Tenant kapsamı isteniyorsa `host/` yerine `tenants/{tenantId}/`; iç düzen ayn
|
||||||
|
|
||||||
| # | Adım | Dosya | Neden bu sırada |
|
| # | 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 |
|
| 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 |
|
| 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 |
|
| 4 | Wizard | `wizard/{Ad}.json` | Menü, yetki, dil anahtarı ve ListForm'u üretir |
|
||||||
|
|
|
||||||
45
README.md
45
README.md
|
|
@ -165,7 +165,7 @@ sozsoft-platform/
|
||||||
│ │ ├── Sozsoft.MailQueue/ # Şablonlu mail kuyruğu ve ekleri
|
│ │ ├── Sozsoft.MailQueue/ # Şablonlu mail kuyruğu ve ekleri
|
||||||
│ │ ├── Sozsoft.Sender/ # Mail (SES), SMS, WhatsApp, Rocket.Chat göndericileri
|
│ │ ├── Sozsoft.Sender/ # Mail (SES), SMS, WhatsApp, Rocket.Chat göndericileri
|
||||||
│ │ ├── Sozsoft.SqlQueryManager/ # SQL nesne yönetimi ve sorgu çalıştırma
|
│ │ ├── 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
|
│ └── test/ # Test projeleri
|
||||||
├── ui/ # React uygulaması
|
├── ui/ # React uygulaması
|
||||||
│ └── src/
|
│ └── 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.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.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.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`)
|
### 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.
|
arkasında oturum yapışkanlığı gerekmez.
|
||||||
|
|
||||||
**Kiracı bazlı sözleşme.** `Sas_H_McpServer` tablosundaki kayıt (ekran:
|
**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
|
`App.DeveloperKit.McpServers`) hangi araçların yayınlandığını ve sorguların hangi satır tavanıyla koşacağını
|
||||||
kaynağında ve hangi satır tavanıyla koşacağını belirler. `Tenants` boşsa kayıt tüm
|
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
|
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
|
(`AddListToolsFilter`) **ve** çağrı anında aynı kural yeniden uygulanır — süzme tek başına
|
||||||
bir yetki sınırı değildir.
|
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çlar.**
|
||||||
|
|
||||||
| Araç | İş |
|
| 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. |
|
| `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). |
|
| `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ı. |
|
| `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:
|
**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`
|
- Ş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.
|
filtreleri, kullanıcı özelleştirmeleri uygulamadaki gibidir.
|
||||||
- Ham SQL (`run_select`, veritabanı nesneleri) liste yetkilerini aşar; yalnızca uygulamada
|
- 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.
|
`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
|
- 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.
|
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,
|
**Ş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
|
`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
|
çö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.
|
Dördü de `https://` olmalıdır.
|
||||||
|
|
||||||
**Yetkiler.** `App.DeveloperKit.McpServers` (ekran), `.Invoke` (araç çağırma) ve `.Write`
|
**Yetkiler.** `App.DeveloperKit.McpServers` (ekran), `.Invoke` (araç çağırma), `.Write`
|
||||||
(veri yazma). Son ikisi `MultiTenancySide = Both`, yani kiracı kullanıcılarına da verilebilir.
|
(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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Dtos;
|
||||||
|
|
||||||
|
/// <summary>Gelistirme rehberinin bir basligi.</summary>
|
||||||
|
public class McpDevGuideHeadingDto
|
||||||
|
{
|
||||||
|
/// <summary><c>lowcode</c> (uretim protokolu, artefakt semalari) ya da <c>reference</c> (JSON kolon sozlesmeleri).</summary>
|
||||||
|
public string Document { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Bolum numarasi (<c>4.3</c>); get_dev_guide'a bu deger verilir.</summary>
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public int Level { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rehber cevabi. Bolum istenmediyse yalnizca <see cref="Sections"/> doludur; istendiyse
|
||||||
|
/// <see cref="Content"/> bolumun metnini, <see cref="Sections"/> alt basliklarini tasir.
|
||||||
|
/// </summary>
|
||||||
|
public class McpDevGuideDto
|
||||||
|
{
|
||||||
|
public McpDevGuideHeadingDto? Heading { get; set; }
|
||||||
|
|
||||||
|
public string? Content { get; set; }
|
||||||
|
|
||||||
|
public List<McpDevGuideHeadingDto> Sections { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Bir wizard seed dosyasi; <see cref="Seed"/> dosyanin diskteki bicimidir.</summary>
|
||||||
|
public class McpWizardFileDto
|
||||||
|
{
|
||||||
|
public string FileName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public JsonElement Seed { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Wizard'i ilk kez ya da yeniden deploy etme istegi.</summary>
|
||||||
|
public class McpWizardDeployRequestDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Seed dosyasi bicimindeki JSON (<c>{"Wizard": {...}}</c>) ya da duz wizard cevaplari.
|
||||||
|
/// </summary>
|
||||||
|
public string Wizard { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Doluysa guncellemedir: bu dosya ve urettigi kayitlar silinip yeniden uretilir.</summary>
|
||||||
|
public string? ReplaceFileName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Yeni tablo istegi. <c>Id</c>, <c>TenantId</c> ve audit kolonlari sablondan gelir; burada
|
||||||
|
/// yalnizca is kolonlari yazilir.
|
||||||
|
/// </summary>
|
||||||
|
public class McpCreateTableRequestDto
|
||||||
|
{
|
||||||
|
public string TableName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public List<McpTableColumnDto> Columns { get; set; } = [];
|
||||||
|
|
||||||
|
public bool FullAudited { get; set; } = true;
|
||||||
|
|
||||||
|
public bool MultiTenant { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class McpTableColumnDto
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Tablo tasarimcisinin tip listesinden biri (nvarchar, int, decimal, bit ...).</summary>
|
||||||
|
public string Type { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Yalnizca nvarchar icin.</summary>
|
||||||
|
public int? MaxLength { get; set; }
|
||||||
|
|
||||||
|
public bool IsNullable { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>Sayi, metin sabiti ('...') ya da NEWID()/GETDATE()/GETUTCDATE()/SYSUTCDATETIME().</summary>
|
||||||
|
public string? DefaultValue { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class McpCreateTableResultDto
|
||||||
|
{
|
||||||
|
public string TableName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Script { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Script'in yazildigi seed dosyasi, seed kokune gore goreli.</summary>
|
||||||
|
public string SeedFilePath { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Custom component ozeti; kod tasimaz.</summary>
|
||||||
|
public class McpComponentSummaryDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? RoutePath { get; set; }
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Kodu tasarimci belgesinden uretilen bilesen; MCP uzerinden kodu yazilamaz.</summary>
|
||||||
|
public bool IsVisualDesigner { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom component kaydetme istegi; ada gore varsa gunceller, yoksa ekler. Guncellemede null
|
||||||
|
/// birakilan alanlar mevcut degerini korur.
|
||||||
|
/// </summary>
|
||||||
|
public class McpComponentSaveRequestDto
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? Code { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Bos dize rotayi kaldirir (rotasiz bilesen); null mevcut rotayi korur.</summary>
|
||||||
|
public string? RoutePath { get; set; }
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
public string? Props { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Bagimli bilesen adlarinin JSON dizisi.</summary>
|
||||||
|
public string? Dependencies { get; set; }
|
||||||
|
|
||||||
|
public bool? IsActive { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -14,16 +14,11 @@ public class McpServerConfigDto
|
||||||
/// <summary>Ekranda gosterilen baglanti adresi; sunucu tarafinda yonlendirme icin kullanilmaz.</summary>
|
/// <summary>Ekranda gosterilen baglanti adresi; sunucu tarafinda yonlendirme icin kullanilmaz.</summary>
|
||||||
public string? Url { get; set; }
|
public string? Url { get; set; }
|
||||||
|
|
||||||
/// <summary>Bos ise istegin kapsamindaki varsayilan veri kaynagi kullanilir.</summary>
|
/// <summary>Bos liste "tum araclar acik" anlamina gelir.</summary>
|
||||||
public string? DataSourceCode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Bos liste "yazma disindaki tum araclar acik" anlamina gelir.</summary>
|
|
||||||
public List<string> AllowedTools { get; set; } = [];
|
public List<string> AllowedTools { get; set; } = [];
|
||||||
|
|
||||||
public int MaxRows { get; set; }
|
public int MaxRows { get; set; }
|
||||||
|
|
||||||
public bool AllowWrite { get; set; }
|
|
||||||
|
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
|
||||||
/// <summary>Istegin cozumlenen kiraci adi; host baglaminda bostur.</summary>
|
/// <summary>Istegin cozumlenen kiraci adi; host baglaminda bostur.</summary>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ namespace Sozsoft.Mcp;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MCP veri yazma yuzeyi. Yazma her zaman bir ListForm ekraninin insert/update/delete
|
/// MCP veri yazma yuzeyi. Yazma her zaman bir ListForm ekraninin insert/update/delete
|
||||||
/// sozlesmesinden gecer; ekran yetkisi ve tenant filtresi aynen uygulanir. Sunucu kaydinda
|
/// sozlesmesinden gecer; ekran yetkisi ve tenant filtresi aynen uygulanir. Sunucu kaydinda
|
||||||
/// <c>AllowWrite</c> kapali oldugunda bu araclar hic listelenmez.
|
/// <c>AllowedTools</c> dolu ve bu araclar secili degilse hic listelenmez.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IMcpDataAppService : IApplicationService
|
public interface IMcpDataAppService : IApplicationService
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
using Sozsoft.Platform.DeveloperKit;
|
||||||
|
using Sozsoft.Platform.ListForms;
|
||||||
|
using Volo.Abp.Application.Services;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MCP gelistirme yuzeyi: tablo, ekran (wizard) ve custom component uretimi. Her islem uygulamadaki
|
||||||
|
/// Wizard ve Custom Components servislerinden gecer; MCP yalnizca kapidir, ekranin
|
||||||
|
/// kendi yetkilerini, seed dosyasi yazimini ve bagimlilik kurallarini atlamaz.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpDevelopAppService : IApplicationService
|
||||||
|
{
|
||||||
|
Task<McpDevGuideDto> GetDevGuideAsync(string? document, string? section);
|
||||||
|
|
||||||
|
Task<McpCreateTableResultDto> CreateTableAsync(McpCreateTableRequestDto input);
|
||||||
|
|
||||||
|
Task<List<WizardFileInfoDto>> GetWizardsAsync(string? search);
|
||||||
|
|
||||||
|
Task<McpWizardFileDto> GetWizardAsync(string fileName);
|
||||||
|
|
||||||
|
/// <summary>Deploy edilen wizard'in ozeti.</summary>
|
||||||
|
Task<WizardFileInfoDto?> DeployWizardAsync(McpWizardDeployRequestDto input);
|
||||||
|
|
||||||
|
Task<WizardDeleteResultDto> DeleteWizardAsync(string fileName);
|
||||||
|
|
||||||
|
Task<List<McpComponentSummaryDto>> GetComponentsAsync(string? search);
|
||||||
|
|
||||||
|
Task<CustomComponentDto?> GetComponentAsync(string name);
|
||||||
|
|
||||||
|
Task<CustomComponentDto> SaveComponentAsync(McpComponentSaveRequestDto input);
|
||||||
|
|
||||||
|
Task DeleteComponentAsync(string name);
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,9 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Sozsoft.Mcp.Domain.Shared\Sozsoft.Mcp.Domain.Shared.csproj" />
|
<ProjectReference Include="..\Sozsoft.Mcp.Domain.Shared\Sozsoft.Mcp.Domain.Shared.csproj" />
|
||||||
|
<!-- Veri yazma ekranin insert/update/delete sozlesmesinden, gelistirme araclari Wizard ve
|
||||||
|
Custom Components sozlesmesinden gecer; araclar bu sozlesmelerin DTO'larini dondurur. -->
|
||||||
|
<ProjectReference Include="..\..\..\src\Sozsoft.Platform.Application.Contracts\Sozsoft.Platform.Application.Contracts.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Sozsoft.Mcp.Dtos;
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
|
@ -37,10 +38,8 @@ public abstract class McpAppService : ApplicationService
|
||||||
{
|
{
|
||||||
Name = server.Name,
|
Name = server.Name,
|
||||||
Url = server.Url,
|
Url = server.Url,
|
||||||
DataSourceCode = server.DataSourceCode,
|
|
||||||
AllowedTools = [.. ServerConfigProvider.SplitMultiValue(server.AllowedTools)],
|
AllowedTools = [.. ServerConfigProvider.SplitMultiValue(server.AllowedTools)],
|
||||||
MaxRows = server.MaxRows,
|
MaxRows = server.MaxRows,
|
||||||
AllowWrite = server.AllowWrite,
|
|
||||||
Description = server.Description,
|
Description = server.Description,
|
||||||
TenantName = CurrentTenant.Name
|
TenantName = CurrentTenant.Name
|
||||||
};
|
};
|
||||||
|
|
@ -63,7 +62,12 @@ public abstract class McpAppService : ApplicationService
|
||||||
{
|
{
|
||||||
var config = await RequireServerConfigAsync();
|
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]);
|
throw new UserFriendlyException(L[McpErrorCodes.ToolDisabled, toolName, config.Name]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,6 @@ public class McpDataAppService(
|
||||||
{
|
{
|
||||||
Check.NotNull(input, nameof(input));
|
Check.NotNull(input, nameof(input));
|
||||||
|
|
||||||
// AllowWrite kapaliysa yazma araclari McpToolPolicy tarafindan zaten reddedilir;
|
|
||||||
// burada ayri bir kontrol yok.
|
|
||||||
await EnsureToolAllowedAsync(toolName);
|
await EnsureToolAllowedAsync(toolName);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(input.ListFormCode))
|
if (string.IsNullOrWhiteSpace(input.ListFormCode))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,341 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
using Sozsoft.Mcp.Services;
|
||||||
|
using Sozsoft.Platform;
|
||||||
|
using Sozsoft.Platform.DeveloperKit;
|
||||||
|
using Sozsoft.Platform.ListForms;
|
||||||
|
using Volo.Abp;
|
||||||
|
using Volo.Abp.Application.Dtos;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ekran ve bilesen gelistirme yuzeyi. Uretim Wizard ve Custom Components servislerinden
|
||||||
|
/// gecer: menu, yetki, dil anahtari, seed dosyasi ve bagimlilik kurallari ekrandaki yolla
|
||||||
|
/// aynidir, yetki kontrolu de o servislerin kendi politikalarindan gelir. Develop yetkisi
|
||||||
|
/// yalnizca MCP kapisidir, arkadaki yetkinin yerine gecmez.
|
||||||
|
/// </summary>
|
||||||
|
[Authorize(PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop)]
|
||||||
|
public class McpDevelopAppService(
|
||||||
|
IMcpServerConfigProvider serverConfigProvider,
|
||||||
|
IMcpDevGuide devGuide,
|
||||||
|
IMcpTableCreator tableCreator,
|
||||||
|
IListFormWizardAppService wizardAppService,
|
||||||
|
ICustomComponentAppService componentAppService)
|
||||||
|
: McpAppService(serverConfigProvider), IMcpDevelopAppService
|
||||||
|
{
|
||||||
|
private const string DefaultGuideDocument = "lowcode";
|
||||||
|
|
||||||
|
/// <summary>Index cevabinda listelenen en derin baslik; daha derini bolum istenince gorunur.</summary>
|
||||||
|
private const int IndexHeadingLevel = 3;
|
||||||
|
|
||||||
|
/// <summary>Custom Components ekraninin tek sayfada dondurebildigi ust sinir.</summary>
|
||||||
|
private const int ComponentPageSize = 1000;
|
||||||
|
|
||||||
|
public async Task<McpDevGuideDto> GetDevGuideAsync(string? document, string? section)
|
||||||
|
{
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.GetDevGuide);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(section))
|
||||||
|
{
|
||||||
|
var headings = await devGuide.GetHeadingsAsync();
|
||||||
|
if (headings.Count == 0)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.DevGuideUnavailable, McpDevGuide.ConfigurationKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new McpDevGuideDto
|
||||||
|
{
|
||||||
|
Sections = [.. headings
|
||||||
|
.Where(h => h.Level <= IndexHeadingLevel)
|
||||||
|
.Where(h => string.IsNullOrWhiteSpace(document) || string.Equals(h.Document, document, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Select(ToDto)]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await devGuide.GetSectionAsync(
|
||||||
|
string.IsNullOrWhiteSpace(document) ? DefaultGuideDocument : document,
|
||||||
|
section)
|
||||||
|
?? throw new UserFriendlyException(L[McpErrorCodes.DevGuideSectionNotFound, section]);
|
||||||
|
|
||||||
|
return new McpDevGuideDto
|
||||||
|
{
|
||||||
|
Heading = ToDto(result.Heading),
|
||||||
|
Content = result.Content,
|
||||||
|
Sections = [.. result.Children.Select(ToDto)]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tablo tasarimcisiyla ayni yetkiyi ister: ham DDL liste yetkilerini asar, Develop yalnizca
|
||||||
|
/// MCP kapisidir.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<McpCreateTableResultDto> CreateTableAsync(McpCreateTableRequestDto input)
|
||||||
|
{
|
||||||
|
Check.NotNull(input, nameof(input));
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.CreateTable);
|
||||||
|
await AuthorizationService.CheckAsync(PlatformConsts.AppCodes.SqlQueryManagers.Default);
|
||||||
|
|
||||||
|
var created = await tableCreator.CreateAsync(new McpTableDefinition(
|
||||||
|
input.TableName,
|
||||||
|
[.. (input.Columns ?? []).Select(column => new McpTableColumn(
|
||||||
|
column.Name, column.Type, column.MaxLength, column.IsNullable, column.DefaultValue))],
|
||||||
|
input.FullAudited,
|
||||||
|
input.MultiTenant));
|
||||||
|
|
||||||
|
return new McpCreateTableResultDto
|
||||||
|
{
|
||||||
|
TableName = created.TableName,
|
||||||
|
Script = created.Script,
|
||||||
|
SeedFilePath = created.SeedFilePath
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<WizardFileInfoDto>> GetWizardsAsync(string? search)
|
||||||
|
{
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.ListWizards);
|
||||||
|
|
||||||
|
var files = await wizardAppService.GetFiles();
|
||||||
|
if (string.IsNullOrWhiteSpace(search))
|
||||||
|
{
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [.. files.Where(file =>
|
||||||
|
Contains(file.FileName, search) || Contains(file.WizardName, search) ||
|
||||||
|
Contains(file.ListFormCode, search) || Contains(file.CustomComponentName, search))];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<McpWizardFileDto> GetWizardAsync(string fileName)
|
||||||
|
{
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.GetWizard);
|
||||||
|
|
||||||
|
var seed = await wizardAppService.GetFile(fileName);
|
||||||
|
|
||||||
|
// Model dosyayi Claude Code'un depoda gordugu bicimde gorur; deploy_wizard ayni bicimi
|
||||||
|
// geri kabul eder.
|
||||||
|
using var document = JsonDocument.Parse(seed.ToSeedJson());
|
||||||
|
|
||||||
|
return new McpWizardFileDto { FileName = fileName, Seed = document.RootElement.Clone() };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<WizardFileInfoDto?> DeployWizardAsync(McpWizardDeployRequestDto input)
|
||||||
|
{
|
||||||
|
Check.NotNull(input, nameof(input));
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.DeployWizard);
|
||||||
|
|
||||||
|
var wizard = ParseWizard(input.Wizard);
|
||||||
|
wizard.EditFileName = string.IsNullOrWhiteSpace(input.ReplaceFileName) ? null : input.ReplaceFileName.Trim();
|
||||||
|
|
||||||
|
await wizardAppService.Create(wizard);
|
||||||
|
|
||||||
|
var files = await wizardAppService.GetFiles();
|
||||||
|
|
||||||
|
return files.FirstOrDefault(file => string.Equals(file.WizardName, wizard.WizardName?.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<WizardDeleteResultDto> DeleteWizardAsync(string fileName)
|
||||||
|
{
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.DeleteWizard);
|
||||||
|
|
||||||
|
return await wizardAppService.DeleteFile(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<McpComponentSummaryDto>> GetComponentsAsync(string? search)
|
||||||
|
{
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.ListComponents);
|
||||||
|
|
||||||
|
var components = await GetAllComponentsAsync();
|
||||||
|
|
||||||
|
return [.. components
|
||||||
|
.Where(component => string.IsNullOrWhiteSpace(search)
|
||||||
|
|| Contains(component.Name, search)
|
||||||
|
|| Contains(component.RoutePath, search)
|
||||||
|
|| Contains(component.Description, search))
|
||||||
|
.Select(component => new McpComponentSummaryDto
|
||||||
|
{
|
||||||
|
Id = component.Id,
|
||||||
|
Name = component.Name,
|
||||||
|
RoutePath = component.RoutePath,
|
||||||
|
Description = component.Description,
|
||||||
|
IsActive = component.IsActive,
|
||||||
|
IsVisualDesigner = IsVisualDesignerDocument(component.Props)
|
||||||
|
})];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CustomComponentDto?> GetComponentAsync(string name)
|
||||||
|
{
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.GetComponent);
|
||||||
|
|
||||||
|
return await FindComponentAsync(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CustomComponentDto> SaveComponentAsync(McpComponentSaveRequestDto input)
|
||||||
|
{
|
||||||
|
Check.NotNull(input, nameof(input));
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.SaveComponent);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(input.Name))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.ComponentNameRequired]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var existing = await FindComponentAsync(input.Name);
|
||||||
|
var props = input.Props ?? existing?.Props;
|
||||||
|
|
||||||
|
// Tasarimci belgesinde kod belgeden uretilir; buraya yazilan kod tasarimcidaki ilk
|
||||||
|
// kayitta sessizce ezilirdi. Belge MCP'de uretilemedigi icin bu bilesenler tasarimcida kalir.
|
||||||
|
if (IsVisualDesignerDocument(props))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.VisualDesignerComponent, input.Name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing is null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input.Code))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.ComponentCodeRequired]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await componentAppService.CreateAsync(new CreateUpdateCustomComponentDto
|
||||||
|
{
|
||||||
|
Name = input.Name.Trim(),
|
||||||
|
Code = input.Code,
|
||||||
|
RoutePath = input.RoutePath,
|
||||||
|
Description = input.Description,
|
||||||
|
Props = props,
|
||||||
|
Dependencies = input.Dependencies,
|
||||||
|
IsActive = input.IsActive ?? true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return await componentAppService.UpdateAsync(existing.Id, new CreateUpdateCustomComponentDto
|
||||||
|
{
|
||||||
|
Name = existing.Name,
|
||||||
|
Code = string.IsNullOrWhiteSpace(input.Code) ? existing.Code : input.Code,
|
||||||
|
RoutePath = input.RoutePath ?? existing.RoutePath,
|
||||||
|
Description = input.Description ?? existing.Description,
|
||||||
|
Props = props,
|
||||||
|
Dependencies = input.Dependencies ?? existing.Dependencies,
|
||||||
|
IsActive = input.IsActive ?? existing.IsActive
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteComponentAsync(string name)
|
||||||
|
{
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.DeleteComponent);
|
||||||
|
|
||||||
|
var component = await FindComponentAsync(name)
|
||||||
|
?? throw new UserFriendlyException(L[McpErrorCodes.ComponentNotFound, name]);
|
||||||
|
|
||||||
|
await componentAppService.DeleteAsync(component.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wizard'i seed dosyasi biciminden (<c>{"Wizard": {...}}</c>) ya da duz cevaplardan okur.
|
||||||
|
/// Iki bicim de dosyanin okuma kuralindan gecer; boylece Menu/Language bloklari ve
|
||||||
|
/// WizardCode/ModuleId adlari dosyadakiyle ayni anlama gelir.
|
||||||
|
/// </summary>
|
||||||
|
private ListFormWizardDto ParseWizard(string? json)
|
||||||
|
{
|
||||||
|
JsonObject? root;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
root = string.IsNullOrWhiteSpace(json) ? null : JsonNode.Parse(json) as JsonObject;
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
root = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root is null)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.InvalidWizard]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var seedJson = root.ContainsKey(nameof(WizardSeedFileDto.Wizard))
|
||||||
|
? root.ToJsonString()
|
||||||
|
: new JsonObject { [nameof(WizardSeedFileDto.Wizard)] = root }.ToJsonString();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return WizardSeedFileDto.FromSeedJson(seedJson)?.Wizard
|
||||||
|
?? throw new UserFriendlyException(L[McpErrorCodes.InvalidWizard]);
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.InvalidWizard]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IReadOnlyList<CustomComponentDto>> GetAllComponentsAsync()
|
||||||
|
{
|
||||||
|
var page = await componentAppService.GetListAsync(new PagedAndSortedResultRequestDto
|
||||||
|
{
|
||||||
|
MaxResultCount = ComponentPageSize,
|
||||||
|
Sorting = nameof(CustomComponentDto.Name)
|
||||||
|
});
|
||||||
|
|
||||||
|
return page.Items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<CustomComponentDto?> FindComponentAsync(string? name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var components = await GetAllComponentsAsync();
|
||||||
|
|
||||||
|
return components.FirstOrDefault(component => string.Equals(component.Name, name.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Props icinde tasarimci belgesi varsa ve kip acikca <c>code</c> degilse kod belgeden
|
||||||
|
/// uretilir; tasarimcinin varsayilan kipi <c>visual</c>'dir.
|
||||||
|
/// </summary>
|
||||||
|
private static bool IsVisualDesignerDocument(string? props)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(props))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var document = JsonDocument.Parse(props);
|
||||||
|
if (document.RootElement.ValueKind != JsonValueKind.Object ||
|
||||||
|
!document.RootElement.TryGetProperty("visualDesigner", out var designer) ||
|
||||||
|
designer.ValueKind != JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !designer.TryGetProperty("sourceMode", out var mode) ||
|
||||||
|
mode.ValueKind != JsonValueKind.String ||
|
||||||
|
!string.Equals(mode.GetString(), "code", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool Contains(string? value, string search)
|
||||||
|
=> value?.Contains(search.Trim(), StringComparison.OrdinalIgnoreCase) == true;
|
||||||
|
|
||||||
|
private static McpDevGuideHeadingDto ToDto(McpDevGuideHeading heading) => new()
|
||||||
|
{
|
||||||
|
Document = heading.Document,
|
||||||
|
Id = heading.Id,
|
||||||
|
Title = heading.Title,
|
||||||
|
Level = heading.Level
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -80,7 +80,7 @@ public class McpQueryAppService(
|
||||||
|
|
||||||
var maxRows = ResolveMaxRows(config, input.MaxRows);
|
var maxRows = ResolveMaxRows(config, input.MaxRows);
|
||||||
var parameters = sqlGuard.NormalizeParameters(input.Parameters);
|
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);
|
var limitedSql = sqlGuard.ApplyRowLimit(sql, maxRows, context.DataSourceType);
|
||||||
|
|
||||||
return ToResult(await queryExecutor.ExecuteAsync(context, limitedSql, parameters, maxRows));
|
return ToResult(await queryExecutor.ExecuteAsync(context, limitedSql, parameters, maxRows));
|
||||||
|
|
@ -88,12 +88,12 @@ public class McpQueryAppService(
|
||||||
|
|
||||||
public async Task<List<McpSavedQueryDto>> GetSavedQueriesAsync(string? search = null)
|
public async Task<List<McpSavedQueryDto>> GetSavedQueriesAsync(string? search = null)
|
||||||
{
|
{
|
||||||
var config = await EnsureToolAllowedAsync(McpToolNames.ListQueries);
|
await EnsureToolAllowedAsync(McpToolNames.ListQueries);
|
||||||
var results = new List<McpSavedQueryDto>();
|
var results = new List<McpSavedQueryDto>();
|
||||||
|
|
||||||
if (await AuthorizationService.IsGrantedAsync(SqlPermission))
|
if (await AuthorizationService.IsGrantedAsync(SqlPermission))
|
||||||
{
|
{
|
||||||
results.AddRange(await GetDatabaseObjectsAsync(config));
|
results.AddRange(await GetDatabaseObjectsAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await AuthorizationService.IsGrantedAsync(CustomEndpointPermission))
|
if (await AuthorizationService.IsGrantedAsync(CustomEndpointPermission))
|
||||||
|
|
@ -113,9 +113,9 @@ public class McpQueryAppService(
|
||||||
.ThenBy(item => item.Name, StringComparer.OrdinalIgnoreCase)];
|
.ThenBy(item => item.Name, StringComparer.OrdinalIgnoreCase)];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<McpSavedQueryDto>> GetDatabaseObjectsAsync(McpServerConfigDto config)
|
private async Task<List<McpSavedQueryDto>> GetDatabaseObjectsAsync()
|
||||||
{
|
{
|
||||||
var context = await queryExecutor.ResolveDataSourceAsync(config.DataSourceCode);
|
var context = await queryExecutor.ResolveDataSourceAsync();
|
||||||
var catalogSql = context.DataSourceType == DataSourceTypeEnum.Postgresql
|
var catalogSql = context.DataSourceType == DataSourceTypeEnum.Postgresql
|
||||||
? McpCatalogQueries.Postgres
|
? McpCatalogQueries.Postgres
|
||||||
: McpCatalogQueries.SqlServer;
|
: McpCatalogQueries.SqlServer;
|
||||||
|
|
@ -160,7 +160,7 @@ public class McpQueryAppService(
|
||||||
|
|
||||||
var maxRows = ResolveMaxRows(config, input.MaxRows);
|
var maxRows = ResolveMaxRows(config, input.MaxRows);
|
||||||
var parameters = sqlGuard.NormalizeParameters(input.Parameters);
|
var parameters = sqlGuard.NormalizeParameters(input.Parameters);
|
||||||
var context = await queryExecutor.ResolveDataSourceAsync(config.DataSourceCode);
|
var context = await queryExecutor.ResolveDataSourceAsync();
|
||||||
|
|
||||||
var schemaName = string.IsNullOrWhiteSpace(input.SchemaName)
|
var schemaName = string.IsNullOrWhiteSpace(input.SchemaName)
|
||||||
? sqlGuard.DefaultSchemaName(context.DataSourceType)
|
? sqlGuard.DefaultSchemaName(context.DataSourceType)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public class McpSchemaAppService(
|
||||||
var config = await ResolveServerConfigAsync();
|
var config = await ResolveServerConfigAsync();
|
||||||
var candidates = McpToolPolicy.VisibleTools(
|
var candidates = McpToolPolicy.VisibleTools(
|
||||||
config is not null,
|
config is not null,
|
||||||
config?.AllowWrite ?? false,
|
CurrentTenant.IsAvailable,
|
||||||
config?.AllowedTools ?? []);
|
config?.AllowedTools ?? []);
|
||||||
|
|
||||||
var required = candidates
|
var required = candidates
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Sozsoft.Mcp.Application.Contracts\Sozsoft.Mcp.Application.Contracts.csproj" />
|
<ProjectReference Include="..\Sozsoft.Mcp.Application.Contracts\Sozsoft.Mcp.Application.Contracts.csproj" />
|
||||||
<ProjectReference Include="..\Sozsoft.Mcp.Domain\Sozsoft.Mcp.Domain.csproj" />
|
<ProjectReference Include="..\Sozsoft.Mcp.Domain\Sozsoft.Mcp.Domain.csproj" />
|
||||||
<!-- Veri yazma ekranin insert/update/delete sozlesmesinden gecer. -->
|
|
||||||
<ProjectReference Include="..\..\..\src\Sozsoft.Platform.Application.Contracts\Sozsoft.Platform.Application.Contracts.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ public static class McpErrorCodes
|
||||||
|
|
||||||
public const string NoServerRecord = Prefix + "NoServerRecord";
|
public const string NoServerRecord = Prefix + "NoServerRecord";
|
||||||
public const string ToolDisabled = Prefix + "ToolDisabled";
|
public const string ToolDisabled = Prefix + "ToolDisabled";
|
||||||
|
public const string TenantReadOnly = Prefix + "TenantReadOnly";
|
||||||
public const string EmptyQuery = Prefix + "EmptyQuery";
|
public const string EmptyQuery = Prefix + "EmptyQuery";
|
||||||
public const string SingleStatementOnly = Prefix + "SingleStatementOnly";
|
public const string SingleStatementOnly = Prefix + "SingleStatementOnly";
|
||||||
public const string ReadOnlyStatementRequired = Prefix + "ReadOnlyStatementRequired";
|
public const string ReadOnlyStatementRequired = Prefix + "ReadOnlyStatementRequired";
|
||||||
|
|
@ -25,4 +26,18 @@ public static class McpErrorCodes
|
||||||
public const string DictionaryNotFound = Prefix + "DictionaryNotFound";
|
public const string DictionaryNotFound = Prefix + "DictionaryNotFound";
|
||||||
public const string RegistrationLimitReached = Prefix + "RegistrationLimitReached";
|
public const string RegistrationLimitReached = Prefix + "RegistrationLimitReached";
|
||||||
public const string RedirectUriNotAllowed = Prefix + "RedirectUriNotAllowed";
|
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";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,34 @@ public static class McpToolNames
|
||||||
public const string InsertRow = "insert_row";
|
public const string InsertRow = "insert_row";
|
||||||
public const string UpdateRow = "update_row";
|
public const string UpdateRow = "update_row";
|
||||||
public const string DeleteRow = "delete_row";
|
public const string DeleteRow = "delete_row";
|
||||||
|
public const string GetDevGuide = "get_dev_guide";
|
||||||
|
public const string CreateTable = "create_table";
|
||||||
|
public const string ListWizards = "list_wizards";
|
||||||
|
public const string GetWizard = "get_wizard";
|
||||||
|
public const string DeployWizard = "deploy_wizard";
|
||||||
|
public const string DeleteWizard = "delete_wizard";
|
||||||
|
public const string ListComponents = "list_components";
|
||||||
|
public const string GetComponent = "get_component";
|
||||||
|
public const string SaveComponent = "save_component";
|
||||||
|
public const string DeleteComponent = "delete_component";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Veri yazan araclar. Sunucu kaydinda <c>AllowWrite</c> kapaliysa bunlar arac listesinde
|
/// Ekran ve bilesen gelistirme araclari. Sunucu bazinda <c>AllowedTools</c> ile, kullanici
|
||||||
/// hic yer almaz.
|
/// bazinda <c>McpServers.Develop</c> yetkisiyle kapatilir.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly string[] WriteTools = [InsertRow, UpdateRow, DeleteRow];
|
public static readonly string[] DevelopTools =
|
||||||
|
[
|
||||||
|
GetDevGuide,
|
||||||
|
CreateTable,
|
||||||
|
ListWizards,
|
||||||
|
GetWizard,
|
||||||
|
DeployWizard,
|
||||||
|
DeleteWizard,
|
||||||
|
ListComponents,
|
||||||
|
GetComponent,
|
||||||
|
SaveComponent,
|
||||||
|
DeleteComponent
|
||||||
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sunucu kaydi olmasa da acik kalan araclar. Istemcinin baglantisinin neden bos
|
/// Sunucu kaydi olmasa da acik kalan araclar. Istemcinin baglantisinin neden bos
|
||||||
|
|
@ -35,10 +57,29 @@ public static class McpToolNames
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly string[] AlwaysAvailableTools = [ServerInfo];
|
public static readonly string[] AlwaysAvailableTools = [ServerInfo];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kiraci kullanicisinin gorebildigi araclarin tamami; kiraci baglaminda MCP yalnizca veri
|
||||||
|
/// okur. <c>AllowedTools</c> bu kumeyi yalnizca daraltabilir, genisletemez. <see cref="RunQuery"/>
|
||||||
|
/// ve <see cref="ListQueries"/> disarida kalir: procedure ve Custom Endpoint cagrisi veri
|
||||||
|
/// yazabilir, okuma oldugu garanti edilemez. <see cref="RunSelect"/> ise tek SELECT disindaki
|
||||||
|
/// her ifadeyi reddettigi icin okumadir.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string[] TenantTools =
|
||||||
|
[
|
||||||
|
ServerInfo,
|
||||||
|
ListLists,
|
||||||
|
DescribeList,
|
||||||
|
SearchSchema,
|
||||||
|
QueryList,
|
||||||
|
RunSelect
|
||||||
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Kullanicinin uygulamada da sahip olmasi gereken yetki. MCP yetki vermez: ham SQL'i
|
/// 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
|
/// uygulamada SQL Query Manager'i kullanabilen, veri yazmayi yazma yetkisi olan kullanici
|
||||||
/// gorur. Listede olmayan araclar kendi icinde liste bazli yetkiyle suzulur.
|
/// gorur. Listede olmayan araclar kendi icinde liste bazli yetkiyle suzulur. Gelistirme
|
||||||
|
/// araclarinin arkasindaki Wizard ve Custom Components servisleri kendi yetkilerini ayrica
|
||||||
|
/// ister; Develop yalnizca MCP kapisidir.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly IReadOnlyDictionary<string, string> RequiredPermissions = new Dictionary<string, string>
|
public static readonly IReadOnlyDictionary<string, string> RequiredPermissions = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
|
|
@ -46,6 +87,16 @@ public static class McpToolNames
|
||||||
[InsertRow] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
[InsertRow] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
||||||
[UpdateRow] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
[UpdateRow] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
||||||
[DeleteRow] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
[DeleteRow] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
||||||
|
[GetDevGuide] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
|
||||||
|
[CreateTable] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
|
||||||
|
[ListWizards] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
|
||||||
|
[GetWizard] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
|
||||||
|
[DeployWizard] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
|
||||||
|
[DeleteWizard] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
|
||||||
|
[ListComponents] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
|
||||||
|
[GetComponent] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
|
||||||
|
[SaveComponent] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
|
||||||
|
[DeleteComponent] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Develop,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>Yayinlanan tum araclar, arac listesindeki siralarina gore.</summary>
|
/// <summary>Yayinlanan tum araclar, arac listesindeki siralarina gore.</summary>
|
||||||
|
|
@ -61,7 +112,8 @@ public static class McpToolNames
|
||||||
RunQuery,
|
RunQuery,
|
||||||
InsertRow,
|
InsertRow,
|
||||||
UpdateRow,
|
UpdateRow,
|
||||||
DeleteRow
|
DeleteRow,
|
||||||
|
.. DevelopTools
|
||||||
];
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@ namespace Sozsoft.Mcp;
|
||||||
public static class McpToolPolicy
|
public static class McpToolPolicy
|
||||||
{
|
{
|
||||||
/// <param name="hasServerRecord">Istegin kiracisina dusen aktif bir sunucu kaydi var mi.</param>
|
/// <param name="hasServerRecord">Istegin kiracisina dusen aktif bir sunucu kaydi var mi.</param>
|
||||||
/// <param name="allowWrite">Sunucu kaydinda veri yazma acik mi.</param>
|
/// <param name="isTenant">Istek bir kiraci kullanicisindan mi geliyor; o zaman yalnizca okuma araclari acilir.</param>
|
||||||
/// <param name="allowedTools">Acik arac adlari; bos liste "yazma disindaki tumu acik" demektir.</param>
|
/// <param name="allowedTools">Acik arac adlari; bos liste "tumu acik" demektir.</param>
|
||||||
public static bool IsAllowed(
|
public static bool IsAllowed(
|
||||||
bool hasServerRecord,
|
bool hasServerRecord,
|
||||||
bool allowWrite,
|
bool isTenant,
|
||||||
IReadOnlyCollection<string> allowedTools,
|
IReadOnlyCollection<string> allowedTools,
|
||||||
string toolName)
|
string toolName)
|
||||||
{
|
{
|
||||||
|
|
@ -25,12 +25,12 @@ public static class McpToolPolicy
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasServerRecord)
|
if (isTenant && !McpToolNames.TenantTools.Contains(toolName, StringComparer.Ordinal))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!allowWrite && McpToolNames.WriteTools.Contains(toolName, StringComparer.Ordinal))
|
if (!hasServerRecord)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -45,7 +45,7 @@ public static class McpToolPolicy
|
||||||
|
|
||||||
public static IReadOnlyList<string> VisibleTools(
|
public static IReadOnlyList<string> VisibleTools(
|
||||||
bool hasServerRecord,
|
bool hasServerRecord,
|
||||||
bool allowWrite,
|
bool isTenant,
|
||||||
IReadOnlyCollection<string> allowedTools)
|
IReadOnlyCollection<string> allowedTools)
|
||||||
=> [.. McpToolNames.All.Where(name => IsAllowed(hasServerRecord, allowWrite, allowedTools, name))];
|
=> [.. McpToolNames.All.Where(name => IsAllowed(hasServerRecord, isTenant, allowedTools, name))];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ namespace Sozsoft.Mcp.Entities;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MCP (Model Context Protocol) sunucu tanimi. Kayit host kapsamindadir; hangi kiracilarin
|
/// MCP (Model Context Protocol) sunucu tanimi. Kayit host kapsamindadir; hangi kiracilarin
|
||||||
/// kullanabilecegi <see cref="Tenants"/> ile belirlenir. Istegin kiracisina dusen kayit, /mcp
|
/// kullanabilecegi <see cref="Tenants"/> ile belirlenir. Istegin kiracisina dusen kayit, /mcp
|
||||||
/// ucunda hangi araclarin gorunecegini ve sorgularin hangi veri kaynaginda hangi satir
|
/// ucunda hangi araclarin gorunecegini ve sorgularin hangi satir tavaniyla kosacagini belirler.
|
||||||
/// tavaniyla kosacagini belirler.
|
/// Sorgular her zaman istegin kiracisinin veritabaninda kosar.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class McpServer : FullAuditedEntity<Guid>
|
public class McpServer : FullAuditedEntity<Guid>
|
||||||
{
|
{
|
||||||
|
|
@ -28,22 +28,13 @@ public class McpServer : FullAuditedEntity<Guid>
|
||||||
public string? Tenants { get; set; }
|
public string? Tenants { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sorgu araclarinin kosacagi DataSource kodu. Bos ise istegin kapsamindaki varsayilan
|
/// Acik arac adlari, <c>PlatformConsts.MultiValueDelimiter</c> ile ayrilir. Bos ise tum
|
||||||
/// veri kaynagi kullanilir.
|
/// araclar aciktir.
|
||||||
/// </summary>
|
|
||||||
public string? DataSourceCode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Acik arac adlari, <c>PlatformConsts.MultiValueDelimiter</c> ile ayrilir. Bos ise yazma
|
|
||||||
/// disindaki tum araclar aciktir.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? AllowedTools { get; set; }
|
public string? AllowedTools { get; set; }
|
||||||
|
|
||||||
/// <summary>Tek sorgunun dondurebilecegi en fazla satir sayisi.</summary>
|
/// <summary>Tek sorgunun dondurebilecegi en fazla satir sayisi.</summary>
|
||||||
public int MaxRows { get; set; } = McpConsts.DefaultMaxRows;
|
public int MaxRows { get; set; } = McpConsts.DefaultMaxRows;
|
||||||
|
|
||||||
/// <summary>Kapali oldugunda veri yazma araclari arac listesinde hic yer almaz.</summary>
|
|
||||||
public bool AllowWrite { get; set; }
|
|
||||||
|
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Volo.Abp.Domain.Services;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Modelin ekran ve bilesen uretirken okudugu sozlesme: depodaki
|
||||||
|
/// <c>.github/instructions/lowcode*.instructions.md</c> dosyalari. Claude Code ayni dosyalari
|
||||||
|
/// depodan okur; MCP istemcisi ise bu servisten bolum bolum alir, boylece iki yolun bildigi
|
||||||
|
/// sozlesme ayrisamaz.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpDevGuide
|
||||||
|
{
|
||||||
|
/// <summary>Okunabilen belgelerin baslik agaci; klasor bulunamazsa bos.</summary>
|
||||||
|
Task<IReadOnlyList<McpDevGuideHeading>> GetHeadingsAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bir bolumun metni. <paramref name="section"/> bolum numarasi (<c>4.3</c>) ya da baslik
|
||||||
|
/// metninin bir parcasidir; bulunamazsa null.
|
||||||
|
/// </summary>
|
||||||
|
Task<McpDevGuideSection?> GetSectionAsync(string document, string section);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="Document">Belgenin kisa adi (<see cref="McpDevGuide.Documents"/>).</param>
|
||||||
|
/// <param name="Id">Bolum numarasi; numarasiz basliklarda baslik metni.</param>
|
||||||
|
public sealed record McpDevGuideHeading(string Document, string Id, string Title, int Level);
|
||||||
|
|
||||||
|
public sealed record McpDevGuideSection(McpDevGuideHeading Heading, string Content, IReadOnlyList<McpDevGuideHeading> Children);
|
||||||
|
|
||||||
|
public partial class McpDevGuide(IConfiguration configuration) : DomainService, IMcpDevGuide
|
||||||
|
{
|
||||||
|
/// <summary>Konteynerde mount noktasi; bos ise depo klasoru calisma dizininden yukari aranir.</summary>
|
||||||
|
public const string ConfigurationKey = "App:DevGuidePath";
|
||||||
|
|
||||||
|
/// <summary>Kisa ad → dosya. Yalnizca uretim sozlesmesi sunulur; kod standardi ve karar kurallari degil.</summary>
|
||||||
|
public static readonly IReadOnlyDictionary<string, string> Documents = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["lowcode"] = "lowcode.instructions.md",
|
||||||
|
["reference"] = "lowcode-reference.instructions.md",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tek bir yanitta donen metnin tavani. Ust seviye bolumlerin bazilari istemcilerin tek
|
||||||
|
/// arac cevabi icin kabul ettigi boyutu asar; bu durumda alt basliklar ayrica istenir.
|
||||||
|
/// </summary>
|
||||||
|
private const int MaxSectionLength = 60_000;
|
||||||
|
|
||||||
|
[GeneratedRegex(@"^(#{2,4})\s+(?:(\d+(?:\.\d+)*)\.?\s+)?(.+?)\s*$")]
|
||||||
|
private static partial Regex HeadingPattern();
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<McpDevGuideHeading>> GetHeadingsAsync()
|
||||||
|
{
|
||||||
|
var headings = new List<McpDevGuideHeading>();
|
||||||
|
foreach (var document in Documents.Keys)
|
||||||
|
{
|
||||||
|
var lines = await ReadLinesAsync(document);
|
||||||
|
headings.AddRange(ParseHeadings(document, lines).Select(heading => heading.Heading));
|
||||||
|
}
|
||||||
|
|
||||||
|
return headings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<McpDevGuideSection?> GetSectionAsync(string document, string section)
|
||||||
|
{
|
||||||
|
var lines = await ReadLinesAsync(document);
|
||||||
|
var headings = ParseHeadings(document, lines);
|
||||||
|
var wanted = section.Trim().TrimEnd('.');
|
||||||
|
|
||||||
|
var index = headings.FindIndex(h => string.Equals(h.Heading.Id, wanted, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = headings.FindIndex(h => h.Heading.Title.Contains(wanted, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var match = headings[index];
|
||||||
|
var next = headings.FindIndex(index + 1, h => h.Heading.Level <= match.Heading.Level);
|
||||||
|
var end = next < 0 ? lines.Length : headings[next].Line;
|
||||||
|
var children = headings
|
||||||
|
.Skip(index + 1)
|
||||||
|
.TakeWhile(h => h.Line < end)
|
||||||
|
.Where(h => h.Heading.Level == match.Heading.Level + 1)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var content = string.Join('\n', lines[match.Line..end]);
|
||||||
|
if (content.Length > MaxSectionLength && children.Count > 0)
|
||||||
|
{
|
||||||
|
// Bolumun giris metni tek basina anlamlidir; geri kalani alt baslik olarak istenir.
|
||||||
|
content = string.Join('\n', lines[match.Line..children[0].Line]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new McpDevGuideSection(match.Heading, content, [.. children.Select(child => child.Heading)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string[]> ReadLinesAsync(string document)
|
||||||
|
{
|
||||||
|
if (!Documents.TryGetValue(document, out var fileName))
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var directory = ResolveDirectory();
|
||||||
|
var path = directory is null ? null : Path.Combine(directory, fileName);
|
||||||
|
if (path is null || !File.Exists(path))
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await File.ReadAllTextAsync(path)).Replace("\r\n", "\n").Split('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? ResolveDirectory()
|
||||||
|
{
|
||||||
|
var configured = configuration[ConfigurationKey];
|
||||||
|
if (!string.IsNullOrWhiteSpace(configured))
|
||||||
|
{
|
||||||
|
return Path.GetFullPath(configured);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var dir = new DirectoryInfo(Directory.GetCurrentDirectory()); dir != null; dir = dir.Parent)
|
||||||
|
{
|
||||||
|
var candidate = Path.Combine(dir.FullName, ".github", "instructions");
|
||||||
|
if (Directory.Exists(candidate))
|
||||||
|
{
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kod bloklari icindeki <c>##</c> ile baslayan satirlar (ornegin SQL yorumlari) baslik
|
||||||
|
/// sayilmaz; aksi halde bolum sinirlari ornek kodun ortasinda kesilirdi.
|
||||||
|
/// </summary>
|
||||||
|
private static List<(McpDevGuideHeading Heading, int Line)> ParseHeadings(string document, string[] lines)
|
||||||
|
{
|
||||||
|
var headings = new List<(McpDevGuideHeading, int)>();
|
||||||
|
var inFence = false;
|
||||||
|
|
||||||
|
for (var i = 0; i < lines.Length; i++)
|
||||||
|
{
|
||||||
|
if (lines[i].TrimStart().StartsWith("```", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
inFence = !inFence;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inFence)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var match = HeadingPattern().Match(lines[i]);
|
||||||
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var title = match.Groups[3].Value;
|
||||||
|
var id = match.Groups[2].Success ? match.Groups[2].Value : title;
|
||||||
|
headings.Add((new McpDevGuideHeading(document, id, title, match.Groups[1].Length), i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return headings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,10 +16,10 @@ namespace Sozsoft.Mcp.Services;
|
||||||
public interface IMcpQueryExecutor
|
public interface IMcpQueryExecutor
|
||||||
{
|
{
|
||||||
/// <param name="dataSourceCode">
|
/// <param name="dataSourceCode">
|
||||||
/// Sunucu kaydindaki kod. Bos ya da <c>!Tenant</c> ise veri kaynagi istegin kiracisindan
|
/// Custom Endpoint'in kendi veri kaynagi kodu. Bos ya da <c>!Tenant</c> ise veri kaynagi
|
||||||
/// cozulur; host baglaminda varsayilan kaynaga duser.
|
/// istegin kiracisindan cozulur; host baglaminda varsayilan kaynaga duser.
|
||||||
/// </param>
|
/// </param>
|
||||||
Task<McpDataSourceContext> ResolveDataSourceAsync(string? dataSourceCode);
|
Task<McpDataSourceContext> ResolveDataSourceAsync(string? dataSourceCode = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sorguyu kosturur. Sonuc kumesi <paramref name="maxRows"/> satirda kesilir; kesilme
|
/// Sorguyu kosturur. Sonuc kumesi <paramref name="maxRows"/> satirda kesilir; kesilme
|
||||||
|
|
@ -34,7 +34,7 @@ public interface IMcpQueryExecutor
|
||||||
|
|
||||||
public class McpQueryExecutor(IDynamicDataManager dynamicDataManager) : DomainService, IMcpQueryExecutor
|
public class McpQueryExecutor(IDynamicDataManager dynamicDataManager) : DomainService, IMcpQueryExecutor
|
||||||
{
|
{
|
||||||
public async Task<McpDataSourceContext> ResolveDataSourceAsync(string? dataSourceCode)
|
public async Task<McpDataSourceContext> ResolveDataSourceAsync(string? dataSourceCode = null)
|
||||||
{
|
{
|
||||||
var useTenantScope = string.IsNullOrWhiteSpace(dataSourceCode)
|
var useTenantScope = string.IsNullOrWhiteSpace(dataSourceCode)
|
||||||
|| dataSourceCode == McpConsts.TenantDataSourceMarker;
|
|| dataSourceCode == McpConsts.TenantDataSourceMarker;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,262 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
using Sozsoft.Platform.Data.Seeds;
|
||||||
|
using Sozsoft.Platform.Enums;
|
||||||
|
using Sozsoft.Platform.Localization;
|
||||||
|
using Volo.Abp;
|
||||||
|
using Volo.Abp.Domain.Services;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Yeni tabloyu SQL Query Manager'daki tablo tasarimcisiyla ayni sozlesmeyle kurar: script
|
||||||
|
/// tasarimcinin <c>generateCreateTableSql</c> ciktisiyla ayni bicimdedir ve tasarimcinin deploy
|
||||||
|
/// sonrasi yazdigi yere (<c>{kapsam}/sql/object/{Tablo}.sql</c>) yazilir; boylece tablo
|
||||||
|
/// tasarimcida acilip duzenlenebilir ve veritabani sifirlandiginda seed ile geri gelir.
|
||||||
|
/// <para>
|
||||||
|
/// Yalnizca olusturma yapar. Var olan tabloya dokunulmaz — kolon ekleme/degistirme tasarimcinin
|
||||||
|
/// isidir ve sema degisikligi kurallarina (kayipsiz ekleme, degistirme/dusurme yok) tabidir.
|
||||||
|
/// Kolon tipi ve varsayilan deger serbest metin degil, beyaz listeden gecer; istemci SQL yazamaz.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpTableCreator
|
||||||
|
{
|
||||||
|
Task<McpCreatedTable> CreateAsync(McpTableDefinition definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class McpTableCreator(
|
||||||
|
IMcpQueryExecutor queryExecutor,
|
||||||
|
IConfiguration configuration,
|
||||||
|
IStringLocalizer<PlatformResource> localizer) : DomainService, IMcpTableCreator
|
||||||
|
{
|
||||||
|
private const string SchemaName = "dbo";
|
||||||
|
private const int DefaultTextLength = 100;
|
||||||
|
private const int MaxTextLength = 4000;
|
||||||
|
|
||||||
|
private static readonly Regex SafeIdentifierRegex = new(
|
||||||
|
@"^[A-Za-z_][A-Za-z0-9_]{0,127}$",
|
||||||
|
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Varsayilan deger olarak kabul edilenler: sayi, metin sabiti ve tasarimcinin kendi
|
||||||
|
/// sablonlarinda kullandigi fonksiyonlar. Baska bir ifade script'e hic girmez.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Regex SafeDefaultRegex = new(
|
||||||
|
@"^(?:-?\d{1,18}(?:\.\d{1,18})?|N?'(?:[^']|'')*'|NEWID\(\)|GETDATE\(\)|GETUTCDATE\(\)|SYSUTCDATETIME\(\))$",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
|
/// <summary>Tasarimcinin tip listesi (<c>DATA_TYPES</c>) ile aynidir.</summary>
|
||||||
|
private static readonly string[] SupportedTypes =
|
||||||
|
[
|
||||||
|
"nvarchar", "nvarchar(MAX)", "int", "bigint", "decimal", "float", "bit",
|
||||||
|
"datetime", "datetime2", "date", "uniqueidentifier", "money"
|
||||||
|
];
|
||||||
|
|
||||||
|
// Sablon kolonlari tasarimcinin FULL_AUDIT_COLUMNS / TENANT_COLUMN tanimlariyla aynidir.
|
||||||
|
private static readonly McpTableColumn IdColumn = new("Id", "uniqueidentifier", null, false, "NEWID()");
|
||||||
|
|
||||||
|
private static readonly McpTableColumn TenantColumn = new("TenantId", "uniqueidentifier", null, true, null);
|
||||||
|
|
||||||
|
private static readonly McpTableColumn[] AuditColumns =
|
||||||
|
[
|
||||||
|
new("CreationTime", "datetime2", null, false, "GETUTCDATE()"),
|
||||||
|
new("CreatorId", "uniqueidentifier", null, true, null),
|
||||||
|
new("LastModificationTime", "datetime2", null, true, null),
|
||||||
|
new("LastModifierId", "uniqueidentifier", null, true, null),
|
||||||
|
new("IsDeleted", "bit", null, false, "0"),
|
||||||
|
new("DeletionTime", "datetime2", null, true, null),
|
||||||
|
new("DeleterId", "uniqueidentifier", null, true, null)
|
||||||
|
];
|
||||||
|
|
||||||
|
public async Task<McpCreatedTable> CreateAsync(McpTableDefinition definition)
|
||||||
|
{
|
||||||
|
Check.NotNull(definition, nameof(definition));
|
||||||
|
|
||||||
|
var tableName = (definition.TableName ?? string.Empty).Trim();
|
||||||
|
EnsureSafeIdentifier(tableName);
|
||||||
|
|
||||||
|
var columns = BuildColumns(definition);
|
||||||
|
|
||||||
|
var context = await queryExecutor.ResolveDataSourceAsync();
|
||||||
|
if (context.DataSourceType != DataSourceTypeEnum.Mssql)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(localizer[McpErrorCodes.TableProviderNotSupported, context.DataSourceType.ToString()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var existing = await context.Repository.ExecuteScalarAsync<int?>(
|
||||||
|
"SELECT OBJECT_ID(@name, 'U')",
|
||||||
|
context.ConnectionString,
|
||||||
|
new Dictionary<string, object> { ["name"] = $"{SchemaName}.{tableName}" });
|
||||||
|
|
||||||
|
if (existing is not null)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(localizer[McpErrorCodes.TableAlreadyExists, tableName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var script = BuildScript(tableName, columns);
|
||||||
|
|
||||||
|
// GO yalnizca SSMS ayiricidir; tek batch oldugu icin calistirirken atilir, dosyada kalir.
|
||||||
|
await context.Repository.ExecuteAsync(
|
||||||
|
string.Join('\n', script.Split('\n').Where(line => line.Trim() != "GO")),
|
||||||
|
context.ConnectionString);
|
||||||
|
|
||||||
|
var seedFilePath = SaveSeedScript(tableName, script);
|
||||||
|
|
||||||
|
return new McpCreatedTable(tableName, script, seedFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<McpTableColumn> BuildColumns(McpTableDefinition definition)
|
||||||
|
{
|
||||||
|
if (definition.Columns.Count == 0)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(localizer[McpErrorCodes.TableColumnsRequired]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var userColumns = definition.Columns.Select(NormalizeColumn).ToList();
|
||||||
|
|
||||||
|
var columns = new List<McpTableColumn> { IdColumn };
|
||||||
|
if (definition.MultiTenant)
|
||||||
|
{
|
||||||
|
columns.Add(TenantColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
columns.AddRange(userColumns);
|
||||||
|
|
||||||
|
if (definition.FullAudited)
|
||||||
|
{
|
||||||
|
columns.AddRange(AuditColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
var duplicate = columns
|
||||||
|
.GroupBy(column => column.Name, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.FirstOrDefault(group => group.Count() > 1);
|
||||||
|
|
||||||
|
if (duplicate is not null)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(localizer[McpErrorCodes.DuplicateColumn, duplicate.Key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
private McpTableColumn NormalizeColumn(McpTableColumn column)
|
||||||
|
{
|
||||||
|
var name = (column.Name ?? string.Empty).Trim();
|
||||||
|
EnsureSafeIdentifier(name);
|
||||||
|
|
||||||
|
var type = SupportedTypes.FirstOrDefault(candidate =>
|
||||||
|
string.Equals(candidate, column.Type?.Trim(), StringComparison.OrdinalIgnoreCase))
|
||||||
|
?? throw new UserFriendlyException(
|
||||||
|
localizer[McpErrorCodes.UnsupportedColumnType, column.Type ?? string.Empty, string.Join(", ", SupportedTypes)]);
|
||||||
|
|
||||||
|
int? maxLength = null;
|
||||||
|
if (type == "nvarchar")
|
||||||
|
{
|
||||||
|
maxLength = column.MaxLength ?? DefaultTextLength;
|
||||||
|
if (maxLength is < 1 or > MaxTextLength)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(localizer[McpErrorCodes.InvalidColumnLength, name, MaxTextLength]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultValue = string.IsNullOrWhiteSpace(column.DefaultValue) ? null : column.DefaultValue.Trim();
|
||||||
|
if (defaultValue is not null && !SafeDefaultRegex.IsMatch(defaultValue))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(localizer[McpErrorCodes.InvalidDefaultValue, name, defaultValue]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new McpTableColumn(name, type, maxLength, column.IsNullable, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildScript(string tableName, List<McpTableColumn> columns)
|
||||||
|
{
|
||||||
|
var fullTableName = $"[{SchemaName}].[{tableName}]";
|
||||||
|
|
||||||
|
var lines = new List<string>
|
||||||
|
{
|
||||||
|
$"IF OBJECT_ID(N'{fullTableName}', 'U') IS NULL",
|
||||||
|
"BEGIN",
|
||||||
|
$" CREATE TABLE {fullTableName}",
|
||||||
|
" ("
|
||||||
|
};
|
||||||
|
|
||||||
|
lines.AddRange(columns.Select(column => ToColumnLine(column) + ","));
|
||||||
|
lines.Add($" CONSTRAINT [PK_{tableName}] PRIMARY KEY NONCLUSTERED");
|
||||||
|
lines.Add(" (");
|
||||||
|
lines.Add(" [Id] ASC");
|
||||||
|
lines.Add(" )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]");
|
||||||
|
lines.Add(" ) ON [PRIMARY]");
|
||||||
|
lines.Add("END");
|
||||||
|
lines.Add("GO");
|
||||||
|
|
||||||
|
return string.Join('\n', lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ToColumnLine(McpTableColumn column)
|
||||||
|
{
|
||||||
|
var type = column.Type switch
|
||||||
|
{
|
||||||
|
"nvarchar" => $"nvarchar({column.MaxLength})",
|
||||||
|
"decimal" => "decimal(18, 4)",
|
||||||
|
_ => column.Type
|
||||||
|
};
|
||||||
|
|
||||||
|
var nullPart = column.IsNullable ? "NULL" : "NOT NULL";
|
||||||
|
var defaultPart = column.DefaultValue is null ? string.Empty : $" DEFAULT {column.DefaultValue}";
|
||||||
|
|
||||||
|
return $" [{column.Name}] {type} {nullPart}{defaultPart}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tasarimcinin <c>SaveTableScriptAsync</c> ile yazdigi yol: kapsam istegin kiracisini izler.
|
||||||
|
/// Dosyasiz tablo veritabani sifirlandiginda geri gelmezdi; yazilamazsa islem hata verir.
|
||||||
|
/// </summary>
|
||||||
|
private string SaveSeedScript(string tableName, string script)
|
||||||
|
{
|
||||||
|
var outputPath = SeedPathResolver.GetScopePath(
|
||||||
|
configuration,
|
||||||
|
CurrentTenant.Id,
|
||||||
|
SeedPathResolver.GetProviderFolderName(isPostgreSql: false),
|
||||||
|
SeedPathResolver.ObjectFolder);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(outputPath);
|
||||||
|
|
||||||
|
var filePath = Path.Combine(outputPath, $"{tableName}.sql");
|
||||||
|
File.WriteAllText(filePath, script);
|
||||||
|
|
||||||
|
return $"{SeedPathResolver.GetScopeFolderName(CurrentTenant.Id)}/{SeedPathResolver.SqlFolder}/{SeedPathResolver.ObjectFolder}/{tableName}.sql";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureSafeIdentifier(string identifier)
|
||||||
|
{
|
||||||
|
if (!SafeIdentifierRegex.IsMatch(identifier))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(localizer[McpErrorCodes.InvalidIdentifier, identifier]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="FullAudited">Tam audit kolonlari (olusturma/degistirme/silme izi) eklensin mi.</param>
|
||||||
|
/// <param name="MultiTenant"><c>TenantId</c> kolonu eklensin mi.</param>
|
||||||
|
public sealed record McpTableDefinition(
|
||||||
|
string TableName,
|
||||||
|
IReadOnlyList<McpTableColumn> Columns,
|
||||||
|
bool FullAudited,
|
||||||
|
bool MultiTenant);
|
||||||
|
|
||||||
|
/// <param name="MaxLength">Yalnizca <c>nvarchar</c> icin; bos ise tasarimcinin varsayilani (100).</param>
|
||||||
|
public sealed record McpTableColumn(
|
||||||
|
string Name,
|
||||||
|
string Type,
|
||||||
|
int? MaxLength,
|
||||||
|
bool IsNullable,
|
||||||
|
string? DefaultValue);
|
||||||
|
|
||||||
|
/// <param name="SeedFilePath">Seed kokune gore goreli yol.</param>
|
||||||
|
public sealed record McpCreatedTable(string TableName, string Script, string SeedFilePath);
|
||||||
|
|
@ -26,9 +26,7 @@ public static class McpDbContextModelCreatingExtensions
|
||||||
// dxTagBox ile doldurulur.
|
// dxTagBox ile doldurulur.
|
||||||
b.Property(x => x.Tenants).HasMaxLength(2048);
|
b.Property(x => x.Tenants).HasMaxLength(2048);
|
||||||
b.Property(x => x.AllowedTools).HasMaxLength(1024);
|
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.MaxRows).HasDefaultValue(McpConsts.DefaultMaxRows);
|
||||||
b.Property(x => x.AllowWrite).HasDefaultValue(false);
|
|
||||||
b.Property(x => x.Description).HasMaxLength(1024);
|
b.Property(x => x.Description).HasMaxLength(1024);
|
||||||
|
|
||||||
b.HasIndex(x => new { x.Name }).IsUnique().HasFilter("[IsDeleted] = 0");
|
b.HasIndex(x => new { x.Name }).IsUnique().HasFilter("[IsDeleted] = 0");
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ public class McpHttpApiModule : AbpModule
|
||||||
.WithTools<McpSchemaTools>()
|
.WithTools<McpSchemaTools>()
|
||||||
.WithTools<McpQueryTools>()
|
.WithTools<McpQueryTools>()
|
||||||
.WithTools<McpDataTools>()
|
.WithTools<McpDataTools>()
|
||||||
|
.WithTools<McpDevelopTools>()
|
||||||
.WithRequestFilters(filters => filters.AddListToolsFilter(next => async (requestContext, cancellationToken) =>
|
.WithRequestFilters(filters => filters.AddListToolsFilter(next => async (requestContext, cancellationToken) =>
|
||||||
{
|
{
|
||||||
var result = await next(requestContext, cancellationToken);
|
var result = await next(requestContext, cancellationToken);
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,9 @@ internal static class McpServerInfoDefaults
|
||||||
"Sozsoft Platform data access. Do not guess table or column names: first find the list with " +
|
"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 " +
|
"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 " +
|
"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.";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ namespace Sozsoft.Mcp.Tools;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Veri yazma araclari. Yazma bir ListForm ekraninin sozlesmesinden gecer; ekran yetkisi ve
|
/// Veri yazma araclari. Yazma bir ListForm ekraninin sozlesmesinden gecer; ekran yetkisi ve
|
||||||
/// tenant filtresi aynen uygulanir. Sunucu kaydinda <c>AllowWrite</c> kapaliysa bu araclar
|
/// tenant filtresi aynen uygulanir. Sunucu kaydinda <c>AllowedTools</c> dolu ve bu araclar
|
||||||
/// arac listesinde hic gorunmez.
|
/// secili degilse arac listesinde hic gorunmez.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[McpServerToolType]
|
[McpServerToolType]
|
||||||
public sealed class McpDataTools(IMcpDataAppService dataAppService)
|
public sealed class McpDataTools(IMcpDataAppService dataAppService)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
using Sozsoft.Platform.DeveloperKit;
|
||||||
|
using Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Tools;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ekran (wizard) ve custom component gelistirme araclari. Uretim uygulamadaki Wizard ve
|
||||||
|
/// Custom Components servislerinden gecer; menu, yetki, dil anahtari ve seed dosyasi ekrandaki
|
||||||
|
/// yolla ayni sekilde olusur.
|
||||||
|
/// </summary>
|
||||||
|
[McpServerToolType]
|
||||||
|
public sealed class McpDevelopTools(IMcpDevelopAppService developAppService)
|
||||||
|
{
|
||||||
|
[McpServerTool(Name = McpToolNames.GetDevGuide, ReadOnly = true)]
|
||||||
|
[Description("Ekran ve bilesen uretim sozlesmesini okur: wizard alanlari, enum degerleri, editor/lookup/script sozlesmesi, custom component kurallari. section bos ise baslik listesi doner; once listeyi al, sonra ihtiyac duydugun bolumu numarasiyla iste (ornegin 4.2). Rehberde dosya yazmak anlatilan yerlerde dosya yerine bu sunucunun araclarini kullan.")]
|
||||||
|
public async Task<McpDevGuideDto> GetDevGuideAsync(
|
||||||
|
[Description("'lowcode' (uretim protokolu ve artefakt semalari, varsayilan) ya da 'reference' (JSON kolon sozlesmeleri, tasarimci envanteri).")]
|
||||||
|
string? document = null,
|
||||||
|
[Description("Bolum numarasi (4.2) ya da baslik metninin bir parcasi. Bos birakilirsa baslik listesi doner.")]
|
||||||
|
string? section = null)
|
||||||
|
=> await developAppService.GetDevGuideAsync(document, section);
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.CreateTable, Destructive = false, Idempotent = false)]
|
||||||
|
[Description("Ekranin baglanacagi yeni tabloyu SQL Query Manager tablo tasarimcisiyla ayni bicimde olusturur ve script'i seed dosyasina (sql/object) yazar. Id (uniqueidentifier, PK), TenantId ve tam audit kolonlari (CreationTime, CreatorId, LastModificationTime, LastModifierId, IsDeleted, DeletionTime, DeleterId) otomatik eklenir; columns icine yalnizca is kolonlarini yaz. Yalnizca yeni tablo olusturur: tablo varsa reddeder, kolon ekleme/degistirme tablo tasarimcisindan yapilir. SQL Server veri kaynaginda calisir. Ardindan deploy_wizard ile ekrani kur.")]
|
||||||
|
public async Task<McpCreateTableResultDto> CreateTableAsync(
|
||||||
|
[Description("Tablo adi; harf/rakam/alt cizgi. Adlandirma icin get_dev_guide'daki tablo onek kuralina uy (ornegin Adm_T_...).")]
|
||||||
|
string tableName,
|
||||||
|
[Description("Is kolonlari. type: nvarchar, nvarchar(MAX), int, bigint, decimal (18,4), float, bit, datetime, datetime2, date, uniqueidentifier, money. maxLength yalnizca nvarchar icin (varsayilan 100). defaultValue: sayi, 'metin' ya da NEWID()/GETDATE()/GETUTCDATE()/SYSUTCDATETIME().")]
|
||||||
|
List<McpTableColumnDto> columns,
|
||||||
|
[Description("Tam audit kolonlari eklensin mi. Kullanici acikca istemedikce true birak.")]
|
||||||
|
bool fullAudited = true,
|
||||||
|
[Description("TenantId kolonu eklensin mi. Kullanici acikca istemedikce true birak.")]
|
||||||
|
bool multiTenant = true)
|
||||||
|
=> await developAppService.CreateTableAsync(new McpCreateTableRequestDto
|
||||||
|
{
|
||||||
|
TableName = tableName,
|
||||||
|
Columns = columns,
|
||||||
|
FullAudited = fullAudited,
|
||||||
|
MultiTenant = multiTenant
|
||||||
|
});
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.ListWizards, ReadOnly = true)]
|
||||||
|
[Description("Wizard File Manager'daki wizard dosyalarini listeler: dosya adi, ekran kodu, yol (List/Custom), menu yolu ve bagimli wizard'lar.")]
|
||||||
|
public async Task<List<WizardFileInfoDto>> ListWizardsAsync(
|
||||||
|
[Description("Dosya adi, wizard adi, ekran kodu ya da bilesen adi icinde aranacak metin. Bos birakilabilir.")]
|
||||||
|
string? search = null)
|
||||||
|
=> await developAppService.GetWizardsAsync(search);
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.GetWizard, ReadOnly = true)]
|
||||||
|
[Description("Bir wizard seed dosyasini diskteki bicimiyle dondurur (Wizard / ListForm / Fields bolumleri). Var olan bir ekrani degistirmeden once oku.")]
|
||||||
|
public async Task<McpWizardFileDto> GetWizardAsync(
|
||||||
|
[Description("list_wizards cikisindaki fileName degeri, ornegin 'Tags.json'.")]
|
||||||
|
string fileName)
|
||||||
|
=> await developAppService.GetWizardAsync(fileName);
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.DeployWizard, Destructive = true, Idempotent = false)]
|
||||||
|
[Description("Wizard'i deploy eder: ListForm ekrani (ComponentKind 0) ya da bir custom component'i menuye baglayan kayit (ComponentKind 1), yetkileri, dil anahtarlari, menusu ve seed dosyasi birlikte uretilir. Hedef tablo onceden var olmalidir; yoksa once create_table ile olustur. replaceFileName verilirse o wizard ve urettigi kayitlar silinip yeniden uretilir; ekran wizard cevaplarindan yeniden turetildigi icin tasarimcida yapilmis ve wizard'da karsiligi olmayan ayarlar kaybolur. Ne yazacagini get_dev_guide 4. bolumunden ogren.")]
|
||||||
|
public async Task<WizardFileInfoDto?> DeployWizardAsync(
|
||||||
|
[Description("Wizard JSON'u: seed dosyasi bicimi ({\"Wizard\": {...}}, get_wizard cikisi gibi) ya da duz wizard cevaplari. Enum alanlari sayi olarak yazilir.")]
|
||||||
|
string wizard,
|
||||||
|
[Description("Guncellemede yerine gecilecek dosyanin adi (list_wizards cikisindaki fileName). Yeni wizard icin bos birak.")]
|
||||||
|
string? replaceFileName = null)
|
||||||
|
=> await developAppService.DeployWizardAsync(new McpWizardDeployRequestDto { Wizard = wizard, ReplaceFileName = replaceFileName });
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.DeleteWizard, Destructive = true, Idempotent = true)]
|
||||||
|
[Description("Bir wizard'i ve yalnizca onun urettigi kayitlari siler; baska yerde kullanilan kayitlar korunur ve raporda gosterilir. Bagimli wizard'i olan bir wizard silinemez, once bagimlilar silinir.")]
|
||||||
|
public async Task<WizardDeleteResultDto> DeleteWizardAsync(
|
||||||
|
[Description("Silinecek wizard dosyasinin adi (list_wizards cikisindaki fileName).")]
|
||||||
|
string fileName)
|
||||||
|
=> await developAppService.DeleteWizardAsync(fileName);
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.ListComponents, ReadOnly = true)]
|
||||||
|
[Description("Custom component'leri ad, rota ve aciklamasiyla listeler; kod dondurmez. isVisualDesigner true olanlarin kodu tasarimci belgesinden uretilir ve MCP ile degistirilemez.")]
|
||||||
|
public async Task<List<McpComponentSummaryDto>> ListComponentsAsync(
|
||||||
|
[Description("Ad, rota ya da aciklama icinde aranacak metin. Bos birakilabilir.")]
|
||||||
|
string? search = null)
|
||||||
|
=> await developAppService.GetComponentsAsync(search);
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.GetComponent, ReadOnly = true)]
|
||||||
|
[Description("Bir custom component'in kodunu, props'unu ve bagimliliklarini dondurur. Degistirmeden once oku.")]
|
||||||
|
public async Task<CustomComponentDto?> GetComponentAsync(
|
||||||
|
[Description("Bilesenin adi.")]
|
||||||
|
string name)
|
||||||
|
=> await developAppService.GetComponentAsync(name);
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.SaveComponent, Destructive = true, Idempotent = true)]
|
||||||
|
[Description("Custom component'i ada gore kaydeder: yoksa ekler, varsa gunceller. Guncellemede verilmeyen alanlar korunur. Kod kurallari ve kullanilabilir kutuphaneler icin get_dev_guide 7. bolumu. Bilesen menude gorunecekse ardindan ComponentKind 1 ile deploy_wizard cagir.")]
|
||||||
|
public async Task<CustomComponentDto> SaveComponentAsync(
|
||||||
|
[Description("Bilesenin adi; kimligidir.")]
|
||||||
|
string name,
|
||||||
|
[Description("Bilesenin React kodu. Yeni bilesende zorunlu, guncellemede bos birakilirsa mevcut kod korunur.")]
|
||||||
|
string? code = null,
|
||||||
|
[Description("'/' ile baslayan rota. '/admin/' ile baslamayan rota public sayfa olur. Bos dize rotayi kaldirir, verilmezse mevcut rota korunur.")]
|
||||||
|
string? routePath = null,
|
||||||
|
[Description("Aciklama.")]
|
||||||
|
string? description = null,
|
||||||
|
[Description("Props JSON metni. Tasarimci belgesi (visualDesigner) iceremez.")]
|
||||||
|
string? props = null,
|
||||||
|
[Description("Bagimli bilesen adlarinin JSON dizisi, ornegin [\"PublicHeader\"].")]
|
||||||
|
string? dependencies = null,
|
||||||
|
[Description("Bilesen etkin mi. Yeni bilesende varsayilan true.")]
|
||||||
|
bool? isActive = null)
|
||||||
|
=> await developAppService.SaveComponentAsync(new McpComponentSaveRequestDto
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Code = code,
|
||||||
|
RoutePath = routePath,
|
||||||
|
Description = description,
|
||||||
|
Props = props,
|
||||||
|
Dependencies = dependencies,
|
||||||
|
IsActive = isActive
|
||||||
|
});
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.DeleteComponent, Destructive = true, Idempotent = false)]
|
||||||
|
[Description("Custom component'i siler. Bilesene bagli bir Custom wizard varsa once delete_wizard ile o silinir.")]
|
||||||
|
public async Task DeleteComponentAsync(
|
||||||
|
[Description("Bilesenin adi.")]
|
||||||
|
string name)
|
||||||
|
=> await developAppService.DeleteComponentAsync(name);
|
||||||
|
}
|
||||||
|
|
@ -28455,20 +28455,26 @@
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.DeveloperKit.McpServers.Invoke",
|
"key": "App.DeveloperKit.McpServers.Invoke",
|
||||||
"en": "Call MCP tools",
|
"en": "Invoke",
|
||||||
"tr": "MCP araçlarını çağır"
|
"tr": "Çağır"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.DeveloperKit.McpServers.Write",
|
"key": "App.DeveloperKit.McpServers.Write",
|
||||||
"en": "Write data over MCP",
|
"en": "Write Data",
|
||||||
"tr": "MCP üzerinden veri yaz"
|
"tr": "Veri Yaz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.DeveloperKit.McpServers.Develop",
|
||||||
|
"en": "Develop",
|
||||||
|
"tr": "Geliştir"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.DeveloperKit.McpServers.Description",
|
"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.",
|
"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 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."
|
"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",
|
"resourceName": "Platform",
|
||||||
|
|
@ -28488,12 +28494,6 @@
|
||||||
"en": "Max Rows",
|
"en": "Max Rows",
|
||||||
"tr": "En Fazla Satır"
|
"tr": "En Fazla Satır"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.Listform.ListformField.AllowWrite",
|
|
||||||
"en": "Allow Write",
|
|
||||||
"tr": "Yazmaya İzin Ver"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.Mcp.Error.NoServerRecord",
|
"key": "App.Mcp.Error.NoServerRecord",
|
||||||
|
|
@ -28506,6 +28506,12 @@
|
||||||
"en": "The '{0}' tool is disabled on the '{1}' MCP server.",
|
"en": "The '{0}' tool is disabled on the '{1}' MCP server.",
|
||||||
"tr": "'{0}' aracı '{1}' MCP sunucusunda kapalı."
|
"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",
|
"resourceName": "Platform",
|
||||||
"key": "App.Mcp.Error.EmptyQuery",
|
"key": "App.Mcp.Error.EmptyQuery",
|
||||||
|
|
@ -28578,6 +28584,90 @@
|
||||||
"en": "No key field value was given.",
|
"en": "No key field value was given.",
|
||||||
"tr": "Anahtar alan değeri verilmedi."
|
"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",
|
"resourceName": "Platform",
|
||||||
"key": "App.Mcp.Error.DictionaryNotFound",
|
"key": "App.Mcp.Error.DictionaryNotFound",
|
||||||
|
|
|
||||||
|
|
@ -7969,7 +7969,6 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
}),
|
}),
|
||||||
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
||||||
new() { FieldName = "IsActive", FieldDbType = DbType.Boolean, Value = "true", CustomValueType = FieldCustomValueTypeEnum.Value },
|
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 },
|
new() { FieldName = "MaxRows", FieldDbType = DbType.Int32, Value = McpConsts.DefaultMaxRows.ToString(), CustomValueType = FieldCustomValueTypeEnum.Value },
|
||||||
}),
|
}),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.McpServer)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.McpServer)),
|
||||||
|
|
@ -8081,40 +8080,19 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
PivotSettingsJson = DefaultPivotSettingsJson
|
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() {
|
new() {
|
||||||
ListFormCode = listForm.ListFormCode,
|
ListFormCode = listForm.ListFormCode,
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.Int32,
|
SourceDbType = DbType.Int32,
|
||||||
FieldName = "MaxRows",
|
FieldName = "MaxRows",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 6,
|
EditOrderNo = 5,
|
||||||
EditorType2 = EditorTypes.dxNumberBox,
|
EditorType2 = EditorTypes.dxNumberBox,
|
||||||
EditorOptions = EditorOptions.Number(0),
|
EditorOptions = EditorOptions.Number(0),
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
CaptionName = "App.Listform.ListformField.MaxRows",
|
CaptionName = "App.Listform.ListformField.MaxRows",
|
||||||
Width = 0,
|
Width = 0,
|
||||||
ListOrderNo = 7,
|
ListOrderNo = 6,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
|
||||||
|
|
@ -8130,12 +8108,12 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "AllowedTools",
|
FieldName = "AllowedTools",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 7,
|
EditOrderNo = 6,
|
||||||
EditorType2 = EditorTypes.dxTagBox,
|
EditorType2 = EditorTypes.dxTagBox,
|
||||||
ColSpan = 2,
|
ColSpan = 2,
|
||||||
CaptionName = "App.Listform.ListformField.AllowedTools",
|
CaptionName = "App.Listform.ListformField.AllowedTools",
|
||||||
Width = 0,
|
Width = 0,
|
||||||
ListOrderNo = 8,
|
ListOrderNo = 7,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
|
||||||
|
|
@ -8153,39 +8131,19 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
PivotSettingsJson = DefaultPivotSettingsJson
|
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() {
|
new() {
|
||||||
ListFormCode = listForm.ListFormCode,
|
ListFormCode = listForm.ListFormCode,
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Description",
|
FieldName = "Description",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 9,
|
EditOrderNo = 7,
|
||||||
EditorType2 = EditorTypes.dxTextArea,
|
EditorType2 = EditorTypes.dxTextArea,
|
||||||
EditorOptions = EditorOptions.Multiline(60),
|
EditorOptions = EditorOptions.Multiline(60),
|
||||||
ColSpan = 2,
|
ColSpan = 2,
|
||||||
CaptionName = "App.Listform.ListformField.Description",
|
CaptionName = "App.Listform.ListformField.Description",
|
||||||
Width = 0,
|
Width = 0,
|
||||||
ListOrderNo = 10,
|
ListOrderNo = 8,
|
||||||
Visible = false,
|
Visible = false,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -517,7 +517,9 @@ public static class PlatformConsts
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MCP sunucu tanimlarinin yetkileri. <see cref="McpServers.McpServer"/> ekrani gorme
|
/// MCP sunucu tanimlarinin yetkileri. <see cref="McpServers.McpServer"/> ekrani gorme
|
||||||
/// (okuma) yetkisidir; <see cref="McpServers.Invoke"/> ise /mcp ucundan arac
|
/// (okuma) yetkisidir; <see cref="McpServers.Invoke"/> ise /mcp ucundan arac
|
||||||
/// cagirma yetkisidir ve kiraci kullanicilarina da verilebilir.
|
/// cagirma yetkisidir ve kiraci kullanicilarina da verilebilir. <see cref="McpServers.Develop"/>
|
||||||
|
/// /mcp ucundan ekran ve bilesen gelistirme araclarini acar; gelistirme islemi yine
|
||||||
|
/// Wizard ve Custom Components yetkilerinden gecer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class McpServers
|
public static class McpServers
|
||||||
{
|
{
|
||||||
|
|
@ -528,6 +530,7 @@ public static class PlatformConsts
|
||||||
public const string Delete = McpServer + ".Delete";
|
public const string Delete = McpServer + ".Delete";
|
||||||
public const string Invoke = McpServer + ".Invoke";
|
public const string Invoke = McpServer + ".Invoke";
|
||||||
public const string Write = McpServer + ".Write";
|
public const string Write = McpServer + ".Write";
|
||||||
|
public const string Develop = McpServer + ".Develop";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Sozsoft.Platform.Migrations
|
namespace Sozsoft.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20260910132559_Initial")]
|
[Migration("20260918122519_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -443,11 +443,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uniqueidentifier");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<bool>("AllowWrite")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("bit")
|
|
||||||
.HasDefaultValue(false);
|
|
||||||
|
|
||||||
b.Property<string>("AllowedTools")
|
b.Property<string>("AllowedTools")
|
||||||
.HasMaxLength(1024)
|
.HasMaxLength(1024)
|
||||||
.HasColumnType("nvarchar(1024)");
|
.HasColumnType("nvarchar(1024)");
|
||||||
|
|
@ -460,10 +455,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.HasColumnType("uniqueidentifier")
|
.HasColumnType("uniqueidentifier")
|
||||||
.HasColumnName("CreatorId");
|
.HasColumnName("CreatorId");
|
||||||
|
|
||||||
b.Property<string>("DataSourceCode")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("DeleterId")
|
b.Property<Guid?>("DeleterId")
|
||||||
.HasColumnType("uniqueidentifier")
|
.HasColumnType("uniqueidentifier")
|
||||||
.HasColumnName("DeleterId");
|
.HasColumnName("DeleterId");
|
||||||
|
|
@ -1500,10 +1500,8 @@ namespace Sozsoft.Platform.Migrations
|
||||||
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||||
Url = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
Url = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||||
Tenants = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
|
Tenants = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
|
||||||
DataSourceCode = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
|
||||||
AllowedTools = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
|
AllowedTools = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
|
||||||
MaxRows = table.Column<int>(type: "int", nullable: false, defaultValue: 200),
|
MaxRows = table.Column<int>(type: "int", nullable: false, defaultValue: 200),
|
||||||
AllowWrite = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
|
||||||
Description = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
|
Description = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
|
||||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
|
@ -440,11 +440,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uniqueidentifier");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<bool>("AllowWrite")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("bit")
|
|
||||||
.HasDefaultValue(false);
|
|
||||||
|
|
||||||
b.Property<string>("AllowedTools")
|
b.Property<string>("AllowedTools")
|
||||||
.HasMaxLength(1024)
|
.HasMaxLength(1024)
|
||||||
.HasColumnType("nvarchar(1024)");
|
.HasColumnType("nvarchar(1024)");
|
||||||
|
|
@ -457,10 +452,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.HasColumnType("uniqueidentifier")
|
.HasColumnType("uniqueidentifier")
|
||||||
.HasColumnName("CreatorId");
|
.HasColumnName("CreatorId");
|
||||||
|
|
||||||
b.Property<string>("DataSourceCode")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("DeleterId")
|
b.Property<Guid?>("DeleterId")
|
||||||
.HasColumnType("uniqueidentifier")
|
.HasColumnType("uniqueidentifier")
|
||||||
.HasColumnName("DeleterId");
|
.HasColumnName("DeleterId");
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
"CdnUrl": "https://dev-cdn.sozsoft.com",
|
"CdnUrl": "https://dev-cdn.sozsoft.com",
|
||||||
"CdnPath": "/etc/api/cdn",
|
"CdnPath": "/etc/api/cdn",
|
||||||
"SeedsPath": "/etc/api/seeds",
|
"SeedsPath": "/etc/api/seeds",
|
||||||
|
"DevGuidePath": "/etc/api/dev-guide",
|
||||||
"BaseDomain": "sozsoft.com",
|
"BaseDomain": "sozsoft.com",
|
||||||
"BackupPath": "/var/opt/mssql/backup"
|
"BackupPath": "/var/opt/mssql/backup"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
"CdnUrl": "https://cdn.sozsoft.com",
|
"CdnUrl": "https://cdn.sozsoft.com",
|
||||||
"CdnPath": "/etc/api/cdn",
|
"CdnPath": "/etc/api/cdn",
|
||||||
"SeedsPath": "/etc/api/seeds",
|
"SeedsPath": "/etc/api/seeds",
|
||||||
|
"DevGuidePath": "/etc/api/dev-guide",
|
||||||
"BaseDomain": "sozsoft.com",
|
"BaseDomain": "sozsoft.com",
|
||||||
"BackupPath": "/var/opt/mssql/backup"
|
"BackupPath": "/var/opt/mssql/backup"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
14
claude.md
14
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
|
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
|
(`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
|
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ı
|
`SelectableTools`'dan üretildiği için kendiliğinden güncellenir. Sunucu kaydında tek anahtar
|
||||||
`WriteTools` listesine de eklenir, yoksa `AllowWrite` kapalıyken de yayınlanırlar.
|
`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`
|
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.
|
ç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`
|
- **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
|
- **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
|
ekranın sorgusundan (`IListFormSelectAppService`/`IListFormDataAppService`) geçer; liste
|
||||||
yetkisini aşan bir yetenek (ham SQL gibi) `McpToolNames.RequiredPermissions`'a uygulamadaki
|
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
|
- **Yeni bir dil anahtarı** → `LanguagesData.json`; eklemeden önce anahtarın zaten var olup
|
||||||
olmadığını kontrol et.
|
olmadığını kontrol et.
|
||||||
- **Custom component sınıfları** → bileşen kodu veritabanında yaşar, Tailwind derlemede yalnızca
|
- **Custom component sınıfları** → bileşen kodu veritabanında yaşar, Tailwind derlemede yalnızca
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,9 @@ services:
|
||||||
# buradan okunur (ChangeLogStartupService). Boylece yeni tag API imajini
|
# buradan okunur (ChangeLogStartupService). Boylece yeni tag API imajini
|
||||||
# yeniden derletmez.
|
# yeniden derletmez.
|
||||||
- ../change-logs:/etc/api/change-logs:ro
|
- ../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:
|
networks:
|
||||||
- db
|
- db
|
||||||
- default
|
- default
|
||||||
|
|
|
||||||
|
|
@ -2569,6 +2569,15 @@
|
||||||
"MultiTenancySide": 3,
|
"MultiTenancySide": 3,
|
||||||
"Modules": "Sas|Adm"
|
"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",
|
"GroupName": "App.Saas",
|
||||||
"Name": "App.Listforms.Listform",
|
"Name": "App.Listforms.Listform",
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,9 @@
|
||||||
"IsActive": true,
|
"IsActive": true,
|
||||||
"Url": "https://api.sozsoft.com/mcp",
|
"Url": "https://api.sozsoft.com/mcp",
|
||||||
"Tenants": null,
|
"Tenants": null,
|
||||||
"DataSourceCode": null,
|
|
||||||
"AllowedTools": null,
|
"AllowedTools": null,
|
||||||
"MaxRows": 200,
|
"MaxRows": 200,
|
||||||
"AllowWrite": false,
|
"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)."
|
||||||
"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."
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue