Mcp güncellemesi
This commit is contained in:
parent
42d3c55ae3
commit
6dee1c898b
79 changed files with 4559 additions and 8 deletions
|
|
@ -1189,6 +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. |
|
||||||
| **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ı. |
|
||||||
|
|
|
||||||
109
README.md
109
README.md
|
|
@ -164,7 +164,8 @@ sozsoft-platform/
|
||||||
│ │ ├── Sozsoft.Notifications/ # Bildirim tipleri, kuralları, kullanıcı bildirimleri
|
│ │ ├── Sozsoft.Notifications/ # Bildirim tipleri, kuralları, kullanıcı bildirimleri
|
||||||
│ │ ├── 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ı
|
||||||
│ └── test/ # Test projeleri
|
│ └── test/ # Test projeleri
|
||||||
├── ui/ # React uygulaması
|
├── ui/ # React uygulaması
|
||||||
│ └── src/
|
│ └── src/
|
||||||
|
|
@ -930,6 +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. |
|
||||||
|
|
||||||
### 10.2. Uygulama servisleri (`api/src/Sozsoft.Platform.Application`)
|
### 10.2. Uygulama servisleri (`api/src/Sozsoft.Platform.Application`)
|
||||||
|
|
||||||
|
|
@ -1150,6 +1152,111 @@ kuyruğu arka plan işçisiyle çalıştırır ve sonuçları günlükler.
|
||||||
Gemini sohbet modeli bağlanır (`configs/ai/Chat.json`). UI tarafında `/admin/ai` ve header'daki
|
Gemini sohbet modeli bağlanır (`configs/ai/Chat.json`). UI tarafında `/admin/ai` ve header'daki
|
||||||
asistan bileşeni bu uç noktayı kullanır.
|
asistan bileşeni bu uç noktayı kullanır.
|
||||||
|
|
||||||
|
### 13.6. MCP sunucusu
|
||||||
|
|
||||||
|
Yapay zekâ istemcileri (Claude Code, Claude Desktop, ChatGPT) platforma **`/mcp`** ucundan
|
||||||
|
bağlanır. Uç, API imajının içinde aynı portta (`8080`) yayınlanır; ayrı bir container yoktur.
|
||||||
|
Modül `api/modules/Sozsoft.Mcp` altında standart ABP katmanlarıyla durur.
|
||||||
|
|
||||||
|
**Kimlik ve kiracı.** Kimlik doğrulama mevcut OpenIddict bearer token'ıdır. Uç,
|
||||||
|
`UseAuthentication` → `UseAbpOpenIddictValidation` → `UseMultiTenancy` → `UseUnitOfWork`
|
||||||
|
ardışık düzeninden sonra bağlanır ve açıkça bearer şemasına bağlıdır (token'sız istek giriş
|
||||||
|
sayfasına 302 ile değil, 401 ile döner); kiracı token talebinden ya da `__tenant` başlığından
|
||||||
|
çözülür. Taşıyıcı **durumsuz** kiptedir
|
||||||
|
(`HttpServerSessionMode.Stateless`): her istek kendi ABP kapsamında koşar, ters vekil
|
||||||
|
arkasında oturum yapışkanlığı gerekmez.
|
||||||
|
|
||||||
|
**Kiracı bazlı sözleşme.** `Sas_H_McpServer` tablosundaki kayıt (ekran:
|
||||||
|
`App.DeveloperKit.McpServers`) hangi araçların yayınlandığını, sorguların hangi veri
|
||||||
|
kaynağında ve hangi satır tavanıyla koşacağını belirler. `Tenants` boşsa kayıt tüm
|
||||||
|
kiracılara açıktır (AiBot ile aynı sözleşme). Araç listesi bu kayda göre süzülür
|
||||||
|
(`AddListToolsFilter`) **ve** çağrı anında aynı kural yeniden uygulanır — süzme tek başına
|
||||||
|
bir yetki sınırı değildir.
|
||||||
|
|
||||||
|
**Araçlar.**
|
||||||
|
|
||||||
|
| Araç | İş |
|
||||||
|
| --- | --- |
|
||||||
|
| `server_info` | Bağlantının kiracısı, açık araçlar, veri sözlüğünün tazelik bilgisi. Her zaman açıktır. |
|
||||||
|
| `list_lists` | Listeler: kod, başlık, açıklama, arkasındaki tablo. |
|
||||||
|
| `describe_list` | Bir listenin sütunları: alan adı, görünen başlık, SQL tipi, lookup ilişkisi, `isSensitive`. |
|
||||||
|
| `search_schema` | Liste ve sütun adları/başlıkları üzerinde aksan ve büyük/küçük harf duyarsız arama. |
|
||||||
|
| `query_list` | Listenin verisini ekranın kendi sorgusuyla (`GetSelectAsync`) okur: filtre, sıralama, gruplama, özet (DevExtreme biçimi). Normal kullanıcının veri okuma yolu. |
|
||||||
|
| `run_select` | Parametreli tek SELECT; çoklu ifade, DDL ve DML reddedilir, satır tavanı sorguya yazılır. Yalnızca SQL Query Manager yetkisiyle. |
|
||||||
|
| `list_queries` | View/procedure/function nesneleri (SQL Query Manager yetkisiyle) + çağrılabilir Custom Endpoint'ler (`App.DeveloperKit.CustomEndpoints.Get` ile). |
|
||||||
|
| `run_query` | Kayıtlı bir nesneyi ya da Custom Endpoint'i parametreleriyle çağırır; yetkiler `list_queries` ile aynı. |
|
||||||
|
| `insert_row` / `update_row` / `delete_row` | ListForm ekranının insert/update/delete sözleşmesinden geçer; `AllowWrite` kapalıysa listelenmez. |
|
||||||
|
|
||||||
|
**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`
|
||||||
|
— ListForm `PermissionJson.R`, ekranla aynı karar; tek sorgu + toplu yetki kontrolü).
|
||||||
|
- `query_list` ekranın sorgusundan geçer: liste ve alan (`CanRead`) yetkileri, kiracı/şube
|
||||||
|
filtreleri, kullanıcı özelleştirmeleri uygulamadaki gibidir.
|
||||||
|
- Ham SQL (`run_select`, veritabanı nesneleri) liste yetkilerini aşar; yalnızca uygulamada
|
||||||
|
`App.SqlQueryManager` yetkisi olana açıktır. Yazma araçları `.Write` + ekranın C/U/D yetkisini ister.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
**Şema keşfi nereden okur.** Tablo ve sütun bilgisi canlı katalogdan değil,
|
||||||
|
`Sas_H_DbTableColumn` tablosundaki JSON veri sözlüğünden gelir — sözlük dil anahtarlarından
|
||||||
|
çözülmüş başlıkları ve lookup ilişkilerini birlikte taşıdığı için modelin gördüğü adlar
|
||||||
|
kullanıcının ekranda gördüğü adlarla aynı olur. Sözlük
|
||||||
|
`dbo.Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns` prosedürüyle kiracı adı başına
|
||||||
|
üretilir; sözlük yoksa araçlar tablo döndürmez ve `server_info` bunu bildirir.
|
||||||
|
|
||||||
|
**OAuth (Claude / ChatGPT connector'ları).** Connector formuna yalnızca ad ve URL girilir;
|
||||||
|
kimlik doğrulamayı istemci MCP OAuth akışıyla kendisi yürütür:
|
||||||
|
|
||||||
|
1. `/mcp` token'sız isteğe 401 + `WWW-Authenticate: Bearer resource_metadata="…"` döner
|
||||||
|
(`McpAuthenticationChallengeMiddleware`).
|
||||||
|
2. İstemci `/.well-known/oauth-protected-resource/mcp` (RFC 9728) belgesinden yetkilendirme
|
||||||
|
sunucusunu (`AuthServer:Authority`) öğrenir, `/.well-known/oauth-authorization-server`
|
||||||
|
(OpenIddict keşif belgesi, `registration_endpoint` eklenmiş) belgesini okur.
|
||||||
|
3. `/connect/register` (RFC 7591, `McpOAuthClientManager`):
|
||||||
|
- İstenen geri dönüş adresleri **bilinen istemcide** (`Platform_Mcp`; Claude, ChatGPT —
|
||||||
|
`DbMigrator` ayarı `OpenIddict:Applications:Platform_Mcp:RedirectUris`) kayıtlıysa onun
|
||||||
|
kimliği döner; onay ekranı çıkmaz.
|
||||||
|
- Değilse (Cursor, VS Code, Claude Code, başka bir AI istemcisi) **dinamik istemci** üretilir:
|
||||||
|
`mcp_` önekli, PKCE zorunlu, **açık onaylı** OpenIddict uygulaması. Aynı adres kümesi aynı
|
||||||
|
uygulamaya bağlanır; tavan `McpConsts.MaxDynamicClients`. Kabul edilen adresler: HTTPS,
|
||||||
|
localhost HTTP (RFC 8252), uygulama şemaları (`cursor://…`); `javascript:` vb. reddedilir.
|
||||||
|
Onay ekranındaki ad istemcinin beyanı + geri dönüş adresinin sahibidir (ad doğrulanamaz).
|
||||||
|
4. Kullanıcı platformun giriş ekranına (`/Account/Login`, kiracı seçimi dahil) yönlenir;
|
||||||
|
authorization code + PKCE ile token alınır. Kiracı ve yetki giriş yapan kullanıcıdan gelir.
|
||||||
|
|
||||||
|
Üç ayrıntı bilinçlidir:
|
||||||
|
- Yayıncı (`issuer`) `AuthServer:Authority`'den sabitlenir; ters vekil arkasında istekten
|
||||||
|
türetilirse `http://` olur ve istemci keşif belgesini reddeder.
|
||||||
|
- MCP istemcisinin gönderdiği RFC 8707 `resource` parametresi yok sayılır
|
||||||
|
(`McpOAuthServerConfiguration`): OpenIddict kayıtsız kaynağı reddeder ya da token kitlesini
|
||||||
|
`/mcp` adresine daraltır, API ise `Platform` kitlesini doğrular.
|
||||||
|
- MCP istemcilerinin (`McpConsts.IsMcpClient`) token'ları kullanıcı oturumuna bağlanmaz
|
||||||
|
(`PlatformTokenController`). Tek oturum kuralı her girişte diğer oturumları sildiği için,
|
||||||
|
bağlansaydı connector her token yenilemesinde kullanıcının web oturumunu düşürürdü.
|
||||||
|
- MCP istemcilerinin yenileme token ömrü uygulama bazında 30 gündür
|
||||||
|
(`McpConsts.RefreshTokenLifetime`, OpenIddict uygulama ayarı) ve her kullanımda yenilenir;
|
||||||
|
web istemcisinin dakikalık ömrü etkilenmez. Kullanıcı 30 gün hiç soru sormazsa bağlantıyı
|
||||||
|
yeniden kurar.
|
||||||
|
|
||||||
|
Bağlantı sunucu gerektirir: connector formu bağlantıyı istemci sağlayıcının sunucularından
|
||||||
|
kurar, `localhost` erişilemez. Forma `https://api.sozsoft.com/mcp` yazılır.
|
||||||
|
|
||||||
|
**Bağlanma (token ile, CLI).**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# sunucu
|
||||||
|
claude mcp add sozsoft --transport http https://api.sozsoft.com/mcp \n --header "Authorization: Bearer <access_token>"
|
||||||
|
|
||||||
|
# localhost (dotnet run / IIS Express)
|
||||||
|
claude mcp add sozsoft-local --transport http https://localhost:44344/mcp \n --header "Authorization: Bearer <access_token>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Ters vekilde `/mcp` ayrı bir blokta karşılanır (`configs/deployment/configs/nginx.conf`):
|
||||||
|
Streamable HTTP yanıtları SSE akışıdır, `location /` altındaki tamponlama akışı bekletir.
|
||||||
|
|
||||||
|
**Yetkiler.** `App.DeveloperKit.McpServers` (ekran), `.Invoke` (araç çağırma) ve `.Write`
|
||||||
|
(veri yazma). Son ikisi `MultiTenancySide = Both`, yani kiracı kullanıcılarına da verilebilir.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 14. Arka Plan İşleri
|
## 14. Arka Plan İşleri
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ WORKDIR /app
|
||||||
COPY global.json ./
|
COPY global.json ./
|
||||||
COPY common.props ./
|
COPY common.props ./
|
||||||
COPY "modules/Sozsoft.Languages/common.props" "modules/Sozsoft.Languages/"
|
COPY "modules/Sozsoft.Languages/common.props" "modules/Sozsoft.Languages/"
|
||||||
|
COPY "modules/Sozsoft.Mcp/common.props" "modules/Sozsoft.Mcp/"
|
||||||
COPY "modules/Sozsoft.Notifications/common.props" "modules/Sozsoft.Notifications/"
|
COPY "modules/Sozsoft.Notifications/common.props" "modules/Sozsoft.Notifications/"
|
||||||
COPY "modules/Sozsoft.Settings/common.props" "modules/Sozsoft.Settings/"
|
COPY "modules/Sozsoft.Settings/common.props" "modules/Sozsoft.Settings/"
|
||||||
COPY "modules/Sozsoft.SqlQueryManager/common.props" "modules/Sozsoft.SqlQueryManager/"
|
COPY "modules/Sozsoft.SqlQueryManager/common.props" "modules/Sozsoft.SqlQueryManager/"
|
||||||
|
|
@ -13,6 +14,12 @@ COPY "modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Sozsoft.Languages.Domai
|
||||||
COPY "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/Sozsoft.Languages.Domain.Shared.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/"
|
COPY "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/Sozsoft.Languages.Domain.Shared.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/"
|
||||||
COPY "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/Sozsoft.Languages.EntityFrameworkCore.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/"
|
COPY "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/Sozsoft.Languages.EntityFrameworkCore.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/"
|
||||||
COPY "modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj" "modules/Sozsoft.MailQueue/"
|
COPY "modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj" "modules/Sozsoft.MailQueue/"
|
||||||
|
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/Sozsoft.Mcp.Application.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/"
|
||||||
|
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Sozsoft.Mcp.Application.Contracts.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/"
|
||||||
|
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Sozsoft.Mcp.Domain.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/"
|
||||||
|
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/Sozsoft.Mcp.Domain.Shared.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/"
|
||||||
|
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.EntityFrameworkCore/Sozsoft.Mcp.EntityFrameworkCore.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.EntityFrameworkCore/"
|
||||||
|
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Sozsoft.Mcp.HttpApi.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/"
|
||||||
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/Sozsoft.Notifications.Application.Contracts.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/"
|
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/Sozsoft.Notifications.Application.Contracts.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/"
|
||||||
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/Sozsoft.Notifications.Domain.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/"
|
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/Sozsoft.Notifications.Domain.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/"
|
||||||
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/Sozsoft.Notifications.Domain.Shared.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/"
|
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/Sozsoft.Notifications.Domain.Shared.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/"
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ RUN apk add nodejs~=24 npm~=11
|
||||||
COPY global.json ./
|
COPY global.json ./
|
||||||
COPY common.props ./
|
COPY common.props ./
|
||||||
COPY "modules/Sozsoft.Languages/common.props" "modules/Sozsoft.Languages/"
|
COPY "modules/Sozsoft.Languages/common.props" "modules/Sozsoft.Languages/"
|
||||||
|
COPY "modules/Sozsoft.Mcp/common.props" "modules/Sozsoft.Mcp/"
|
||||||
COPY "modules/Sozsoft.Notifications/common.props" "modules/Sozsoft.Notifications/"
|
COPY "modules/Sozsoft.Notifications/common.props" "modules/Sozsoft.Notifications/"
|
||||||
COPY "modules/Sozsoft.Settings/common.props" "modules/Sozsoft.Settings/"
|
COPY "modules/Sozsoft.Settings/common.props" "modules/Sozsoft.Settings/"
|
||||||
COPY "modules/Sozsoft.SqlQueryManager/common.props" "modules/Sozsoft.SqlQueryManager/"
|
COPY "modules/Sozsoft.SqlQueryManager/common.props" "modules/Sozsoft.SqlQueryManager/"
|
||||||
|
|
@ -26,6 +27,12 @@ COPY "modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Sozsoft.Languages.Domai
|
||||||
COPY "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/Sozsoft.Languages.Domain.Shared.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/"
|
COPY "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/Sozsoft.Languages.Domain.Shared.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/"
|
||||||
COPY "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/Sozsoft.Languages.EntityFrameworkCore.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/"
|
COPY "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/Sozsoft.Languages.EntityFrameworkCore.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/"
|
||||||
COPY "modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj" "modules/Sozsoft.MailQueue/"
|
COPY "modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj" "modules/Sozsoft.MailQueue/"
|
||||||
|
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/Sozsoft.Mcp.Application.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/"
|
||||||
|
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Sozsoft.Mcp.Application.Contracts.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/"
|
||||||
|
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Sozsoft.Mcp.Domain.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/"
|
||||||
|
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/Sozsoft.Mcp.Domain.Shared.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/"
|
||||||
|
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.EntityFrameworkCore/Sozsoft.Mcp.EntityFrameworkCore.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.EntityFrameworkCore/"
|
||||||
|
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Sozsoft.Mcp.HttpApi.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/"
|
||||||
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/Sozsoft.Notifications.Application.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/"
|
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/Sozsoft.Notifications.Application.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/"
|
||||||
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/Sozsoft.Notifications.Application.Contracts.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/"
|
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/Sozsoft.Notifications.Application.Contracts.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/"
|
||||||
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/Sozsoft.Notifications.Domain.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/"
|
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/Sozsoft.Notifications.Domain.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/"
|
||||||
|
|
|
||||||
|
|
@ -92,144 +92,496 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.SqlQueryManager.App
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.SqlQueryManager.EntityFrameworkCore", "modules\Sozsoft.SqlQueryManager\Sozsoft.SqlQueryManager.EntityFrameworkCore\Sozsoft.SqlQueryManager.EntityFrameworkCore.csproj", "{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.SqlQueryManager.EntityFrameworkCore", "modules\Sozsoft.SqlQueryManager\Sozsoft.SqlQueryManager.EntityFrameworkCore\Sozsoft.SqlQueryManager.EntityFrameworkCore.csproj", "{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.Mcp.Domain.Shared", "modules\Sozsoft.Mcp\Sozsoft.Mcp.Domain.Shared\Sozsoft.Mcp.Domain.Shared.csproj", "{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.Mcp.Domain", "modules\Sozsoft.Mcp\Sozsoft.Mcp.Domain\Sozsoft.Mcp.Domain.csproj", "{E99A894F-1731-4A48-B345-1D14F70EA23F}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.Mcp.Application.Contracts", "modules\Sozsoft.Mcp\Sozsoft.Mcp.Application.Contracts\Sozsoft.Mcp.Application.Contracts.csproj", "{30F4D3DF-D777-40C2-B434-49173AC06A4B}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.Mcp.Application", "modules\Sozsoft.Mcp\Sozsoft.Mcp.Application\Sozsoft.Mcp.Application.csproj", "{A4BC1880-5492-41FF-81BA-963AC4C60A02}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.Mcp.EntityFrameworkCore", "modules\Sozsoft.Mcp\Sozsoft.Mcp.EntityFrameworkCore\Sozsoft.Mcp.EntityFrameworkCore.csproj", "{BE7FDBA3-58CD-41C0-A748-E636DD11F137}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.Mcp.HttpApi", "modules\Sozsoft.Mcp\Sozsoft.Mcp.HttpApi\Sozsoft.Mcp.HttpApi.csproj", "{594F56D4-CDDD-451A-B487-D49EEB9F5B98}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|Any CPU.Build.0 = Release|Any CPU
|
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|Any CPU.Build.0 = Release|Any CPU
|
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|Any CPU.Build.0 = Release|Any CPU
|
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|Any CPU.Build.0 = Release|Any CPU
|
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|Any CPU.Build.0 = Release|Any CPU
|
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|Any CPU.Build.0 = Release|Any CPU
|
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|Any CPU.Build.0 = Release|Any CPU
|
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|Any CPU.Build.0 = Release|Any CPU
|
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|Any CPU.Build.0 = Release|Any CPU
|
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|Any CPU.Build.0 = Release|Any CPU
|
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{9020ECD8-81D6-4604-A379-260796918C28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{9020ECD8-81D6-4604-A379-260796918C28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{9020ECD8-81D6-4604-A379-260796918C28}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{9020ECD8-81D6-4604-A379-260796918C28}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{9020ECD8-81D6-4604-A379-260796918C28}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{9020ECD8-81D6-4604-A379-260796918C28}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{9020ECD8-81D6-4604-A379-260796918C28}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{9020ECD8-81D6-4604-A379-260796918C28}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{9020ECD8-81D6-4604-A379-260796918C28}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{9020ECD8-81D6-4604-A379-260796918C28}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{9020ECD8-81D6-4604-A379-260796918C28}.Release|Any CPU.Build.0 = Release|Any CPU
|
{9020ECD8-81D6-4604-A379-260796918C28}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{9020ECD8-81D6-4604-A379-260796918C28}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{9020ECD8-81D6-4604-A379-260796918C28}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{9020ECD8-81D6-4604-A379-260796918C28}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{9020ECD8-81D6-4604-A379-260796918C28}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{A3933E06-9DFE-480D-85C4-66F28F076164}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{A3933E06-9DFE-480D-85C4-66F28F076164}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{A3933E06-9DFE-480D-85C4-66F28F076164}.Release|Any CPU.Build.0 = Release|Any CPU
|
{A3933E06-9DFE-480D-85C4-66F28F076164}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A3933E06-9DFE-480D-85C4-66F28F076164}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{A3933E06-9DFE-480D-85C4-66F28F076164}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{A3933E06-9DFE-480D-85C4-66F28F076164}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{A3933E06-9DFE-480D-85C4-66F28F076164}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|Any CPU.Build.0 = Release|Any CPU
|
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|Any CPU.Build.0 = Release|Any CPU
|
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{A926F04F-631B-4A05-853C-6C76555BF26A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{A926F04F-631B-4A05-853C-6C76555BF26A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{A926F04F-631B-4A05-853C-6C76555BF26A}.Release|Any CPU.Build.0 = Release|Any CPU
|
{A926F04F-631B-4A05-853C-6C76555BF26A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A926F04F-631B-4A05-853C-6C76555BF26A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{A926F04F-631B-4A05-853C-6C76555BF26A}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{A926F04F-631B-4A05-853C-6C76555BF26A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{A926F04F-631B-4A05-853C-6C76555BF26A}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{E14E0128-D4FF-462D-B684-7238EA876D69}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{E14E0128-D4FF-462D-B684-7238EA876D69}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{E14E0128-D4FF-462D-B684-7238EA876D69}.Release|Any CPU.Build.0 = Release|Any CPU
|
{E14E0128-D4FF-462D-B684-7238EA876D69}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E14E0128-D4FF-462D-B684-7238EA876D69}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{E14E0128-D4FF-462D-B684-7238EA876D69}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{E14E0128-D4FF-462D-B684-7238EA876D69}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{E14E0128-D4FF-462D-B684-7238EA876D69}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|Any CPU.Build.0 = Release|Any CPU
|
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{3E13749E-82D1-434B-B867-F7B094B66065}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{3E13749E-82D1-434B-B867-F7B094B66065}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{3E13749E-82D1-434B-B867-F7B094B66065}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{3E13749E-82D1-434B-B867-F7B094B66065}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3E13749E-82D1-434B-B867-F7B094B66065}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{3E13749E-82D1-434B-B867-F7B094B66065}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{3E13749E-82D1-434B-B867-F7B094B66065}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{3E13749E-82D1-434B-B867-F7B094B66065}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{3E13749E-82D1-434B-B867-F7B094B66065}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3E13749E-82D1-434B-B867-F7B094B66065}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{3E13749E-82D1-434B-B867-F7B094B66065}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3E13749E-82D1-434B-B867-F7B094B66065}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3E13749E-82D1-434B-B867-F7B094B66065}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{3E13749E-82D1-434B-B867-F7B094B66065}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{3E13749E-82D1-434B-B867-F7B094B66065}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{3E13749E-82D1-434B-B867-F7B094B66065}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{620D9F08-EB9F-469F-806A-64E915437154}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{620D9F08-EB9F-469F-806A-64E915437154}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{620D9F08-EB9F-469F-806A-64E915437154}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{620D9F08-EB9F-469F-806A-64E915437154}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{620D9F08-EB9F-469F-806A-64E915437154}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{620D9F08-EB9F-469F-806A-64E915437154}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{620D9F08-EB9F-469F-806A-64E915437154}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{620D9F08-EB9F-469F-806A-64E915437154}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{620D9F08-EB9F-469F-806A-64E915437154}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{620D9F08-EB9F-469F-806A-64E915437154}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{620D9F08-EB9F-469F-806A-64E915437154}.Release|Any CPU.Build.0 = Release|Any CPU
|
{620D9F08-EB9F-469F-806A-64E915437154}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{620D9F08-EB9F-469F-806A-64E915437154}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{620D9F08-EB9F-469F-806A-64E915437154}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{620D9F08-EB9F-469F-806A-64E915437154}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{620D9F08-EB9F-469F-806A-64E915437154}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|Any CPU.Build.0 = Release|Any CPU
|
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|Any CPU.Build.0 = Release|Any CPU
|
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|Any CPU.Build.0 = Release|Any CPU
|
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|Any CPU.Build.0 = Release|Any CPU
|
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|Any CPU.Build.0 = Release|Any CPU
|
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|Any CPU.Build.0 = Release|Any CPU
|
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|Any CPU.Build.0 = Release|Any CPU
|
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{8730045F-91F5-4438-8772-C6E31E89AACC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{8730045F-91F5-4438-8772-C6E31E89AACC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{8730045F-91F5-4438-8772-C6E31E89AACC}.Release|Any CPU.Build.0 = Release|Any CPU
|
{8730045F-91F5-4438-8772-C6E31E89AACC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8730045F-91F5-4438-8772-C6E31E89AACC}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{8730045F-91F5-4438-8772-C6E31E89AACC}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{8730045F-91F5-4438-8772-C6E31E89AACC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{8730045F-91F5-4438-8772-C6E31E89AACC}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|Any CPU.Build.0 = Release|Any CPU
|
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|Any CPU.Build.0 = Release|Any CPU
|
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|Any CPU.Build.0 = Release|Any CPU
|
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -274,6 +626,12 @@ Global
|
||||||
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4} = {2889482E-64CA-4A25-91D8-5B963D83681B}
|
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4} = {2889482E-64CA-4A25-91D8-5B963D83681B}
|
||||||
{ED9C639A-A706-4ECB-9638-A15B3681BDEC} = {2889482E-64CA-4A25-91D8-5B963D83681B}
|
{ED9C639A-A706-4ECB-9638-A15B3681BDEC} = {2889482E-64CA-4A25-91D8-5B963D83681B}
|
||||||
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6} = {2889482E-64CA-4A25-91D8-5B963D83681B}
|
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6} = {2889482E-64CA-4A25-91D8-5B963D83681B}
|
||||||
|
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36} = {03E1C8DA-035E-4882-AF81-F392139FCF38}
|
||||||
|
{E99A894F-1731-4A48-B345-1D14F70EA23F} = {03E1C8DA-035E-4882-AF81-F392139FCF38}
|
||||||
|
{30F4D3DF-D777-40C2-B434-49173AC06A4B} = {03E1C8DA-035E-4882-AF81-F392139FCF38}
|
||||||
|
{A4BC1880-5492-41FF-81BA-963AC4C60A02} = {03E1C8DA-035E-4882-AF81-F392139FCF38}
|
||||||
|
{BE7FDBA3-58CD-41C0-A748-E636DD11F137} = {03E1C8DA-035E-4882-AF81-F392139FCF38}
|
||||||
|
{594F56D4-CDDD-451A-B487-D49EEB9F5B98} = {03E1C8DA-035E-4882-AF81-F392139FCF38}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F}
|
SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Dtos;
|
||||||
|
|
||||||
|
/// <summary>Dinamik istemci kaydi (RFC 7591) istegi.</summary>
|
||||||
|
public class McpClientRegistrationRequestDto
|
||||||
|
{
|
||||||
|
public string? ClientName { get; set; }
|
||||||
|
|
||||||
|
public List<string> RedirectUris { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dinamik istemci kaydinin sonucu. Kayit basarisizsa <see cref="Error"/> RFC 7591 hata kodunu
|
||||||
|
/// tasir ve diger alanlar bos kalir.
|
||||||
|
/// </summary>
|
||||||
|
public class McpClientRegistrationResultDto
|
||||||
|
{
|
||||||
|
public string? ClientId { get; set; }
|
||||||
|
|
||||||
|
public string? ClientName { get; set; }
|
||||||
|
|
||||||
|
public List<string> RedirectUris { get; set; } = [];
|
||||||
|
|
||||||
|
public List<string> GrantTypes { get; set; } = [];
|
||||||
|
|
||||||
|
public List<string> ResponseTypes { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>Genel istemci: token ucunda gizli anahtar kullanilmaz, PKCE zorunludur.</summary>
|
||||||
|
public string TokenEndpointAuthMethod { get; set; } = "none";
|
||||||
|
|
||||||
|
public string? Scope { get; set; }
|
||||||
|
|
||||||
|
public long ClientIdIssuedAt { get; set; }
|
||||||
|
|
||||||
|
public string? Error { get; set; }
|
||||||
|
|
||||||
|
public string? ErrorDescription { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Dtos;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bir listenin verisini ekranin kendi sorgusuyla okuma istegi. Filtre, siralama ve gruplama
|
||||||
|
/// DevExtreme veri yukleme bicimindedir; ekranin grid'i de ayni bicimi kullanir.
|
||||||
|
/// </summary>
|
||||||
|
public class McpListQueryRequestDto
|
||||||
|
{
|
||||||
|
public string ListFormCode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Ornek: <c>["Status","=","Open"]</c> ya da <c>[["A",">",1],"and",["B","contains","x"]]</c>.</summary>
|
||||||
|
public string? Filter { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Ornek: <c>[{"selector":"CreationTime","desc":true}]</c>.</summary>
|
||||||
|
public string? Sort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Ornek: <c>[{"selector":"CustomerId","desc":false,"isExpanded":false}]</c>.</summary>
|
||||||
|
public string? Group { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Ornek: <c>[{"selector":"Id","summaryType":"count"}]</c>.</summary>
|
||||||
|
public string? GroupSummary { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Ornek: <c>[{"selector":"Amount","summaryType":"sum"}]</c>.</summary>
|
||||||
|
public string? TotalSummary { get; set; }
|
||||||
|
|
||||||
|
public int Skip { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Istenen satir sayisi; sunucu kaydindaki tavana kirpilir.</summary>
|
||||||
|
public int Take { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Liste sorgusunun sonucu.</summary>
|
||||||
|
public class McpListQueryResultDto
|
||||||
|
{
|
||||||
|
public object? Data { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Filtreye uyan toplam kayit sayisi (sayfalamadan once).</summary>
|
||||||
|
public int TotalCount { get; set; }
|
||||||
|
|
||||||
|
public int GroupCount { get; set; }
|
||||||
|
|
||||||
|
public object[]? Summary { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Uygulanan satir tavani; <see cref="TotalCount"/> bundan buyukse sonuc sayfalanmistir.</summary>
|
||||||
|
public int Take { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Parametreli tek SELECT istegi.</summary>
|
||||||
|
public class McpSelectRequestDto
|
||||||
|
{
|
||||||
|
public string Sql { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Sorgudaki adli parametreler.</summary>
|
||||||
|
public Dictionary<string, object?> Parameters { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>Istenen satir sayisi; sunucu kaydindaki tavana kirpilir.</summary>
|
||||||
|
public int MaxRows { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kayitli bir veritabani nesnesi (view, stored procedure, function) ya da Custom Endpoint.
|
||||||
|
/// </summary>
|
||||||
|
public class McpSavedQueryDto
|
||||||
|
{
|
||||||
|
/// <summary><c>McpConsts.SavedQueryKinds</c> degerlerinden biri.</summary>
|
||||||
|
public string Kind { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string? SchemaName { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Custom Endpoint'te beklenen parametre adlari; veritabani nesnelerinde bostur.</summary>
|
||||||
|
public List<string> ParameterNames { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Kayitli bir nesnenin ya da Custom Endpoint'in cagrilmasi.</summary>
|
||||||
|
public class McpSavedQueryRequestDto
|
||||||
|
{
|
||||||
|
/// <summary><see cref="McpSavedQueryDto.Kind"/> ile ayni degerler.</summary>
|
||||||
|
public string Kind { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Bos birakilirsa saglayicinin varsayilan semasi kullanilir.</summary>
|
||||||
|
public string? SchemaName { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, object?> Parameters { get; set; } = [];
|
||||||
|
|
||||||
|
public int MaxRows { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Sorgu sonucu.</summary>
|
||||||
|
public class McpQueryResultDto
|
||||||
|
{
|
||||||
|
public List<string> Columns { get; set; } = [];
|
||||||
|
public List<Dictionary<string, object?>> Rows { get; set; } = [];
|
||||||
|
public int RowCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Satir sayisi tavana dayandiginda true; sonuc kesilmis demektir.</summary>
|
||||||
|
public bool Truncated { get; set; }
|
||||||
|
|
||||||
|
public long ExecutionTimeMs { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Bir ListForm ekrani uzerinden kayit yazma/silme istegi.</summary>
|
||||||
|
public class McpRowRequestDto
|
||||||
|
{
|
||||||
|
public string ListFormCode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Alan adi → deger. Silme isleminde bos birakilir.</summary>
|
||||||
|
public Dictionary<string, object?> Values { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>Guncelleme ve silmede anahtar alan degerleri.</summary>
|
||||||
|
public List<object?> Keys { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Yazma isleminin sonucu.</summary>
|
||||||
|
public class McpWriteResultDto
|
||||||
|
{
|
||||||
|
/// <summary>Insert'te uretilen anahtar; guncelleme ve silmede bostur.</summary>
|
||||||
|
public string? Key { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Guncelleme ve silmede etkilenen satir sayisi.</summary>
|
||||||
|
public int AffectedRows { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Dtos;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Istegin kiracisina dusen MCP sunucu kaydinin etkin hali. /mcp ucu hangi araclari
|
||||||
|
/// listeleyecegine ve sorgularin hangi satir tavaniyla kosacagina bu kayda bakarak karar verir.
|
||||||
|
/// </summary>
|
||||||
|
public class McpServerConfigDto
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Ekranda gosterilen baglanti adresi; sunucu tarafinda yonlendirme icin kullanilmaz.</summary>
|
||||||
|
public string? Url { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Bos ise istegin kapsamindaki varsayilan veri kaynagi kullanilir.</summary>
|
||||||
|
public string? DataSourceCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Bos liste "yazma disindaki tum araclar acik" anlamina gelir.</summary>
|
||||||
|
public List<string> AllowedTools { get; set; } = [];
|
||||||
|
|
||||||
|
public int MaxRows { get; set; }
|
||||||
|
|
||||||
|
public bool AllowWrite { get; set; }
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Istegin cozumlenen kiraci adi; host baglaminda bostur.</summary>
|
||||||
|
public string? TenantName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Veri sozlugunun tazelik bilgisi. Sozluk <c>Sas_H_DbTableColumn</c> tablosunda, sozluk
|
||||||
|
/// ureten prosedur tarafindan yazilmis JSON olarak durur.
|
||||||
|
/// </summary>
|
||||||
|
public class McpSchemaInfoDto
|
||||||
|
{
|
||||||
|
public bool Available { get; set; }
|
||||||
|
public string? CacheKey { get; set; }
|
||||||
|
public DateTime? RefreshedAt { get; set; }
|
||||||
|
public int FormCount { get; set; }
|
||||||
|
public int FieldCount { get; set; }
|
||||||
|
public string? TenantName { get; set; }
|
||||||
|
public string? DefinitionCultureName { get; set; }
|
||||||
|
public string? UiCultureName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Sozluk bulunamadiginda ne yapilacagini anlatan mesaj.</summary>
|
||||||
|
public string? Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Bir listenin (ListForm) ozet tanimi.</summary>
|
||||||
|
public class McpListSummaryDto
|
||||||
|
{
|
||||||
|
public string ListFormCode { get; set; } = string.Empty;
|
||||||
|
public string? DisplayName { get; set; }
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Listenin arkasindaki tablo adi; SELECT tabanli listelerde bostur.</summary>
|
||||||
|
public string? TableName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Tablo adi ya da SELECT metni — listenin gercek SQL kaynagi.</summary>
|
||||||
|
public string? SqlSourceName { get; set; }
|
||||||
|
|
||||||
|
/// <summary><c>table</c>, <c>selectSql</c> ya da <c>unknown</c>.</summary>
|
||||||
|
public string? SourceKind { get; set; }
|
||||||
|
|
||||||
|
public string? KeyFieldName { get; set; }
|
||||||
|
public int FieldCount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Bir listenin sutun tanimi.</summary>
|
||||||
|
public class McpColumnDto
|
||||||
|
{
|
||||||
|
public string Field { get; set; } = string.Empty;
|
||||||
|
public string? DisplayName { get; set; }
|
||||||
|
public string? SqlDataType { get; set; }
|
||||||
|
public bool IsVisible { get; set; }
|
||||||
|
public bool IsBooleanColumn { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Lookup tasiyan sutunda hedef tablo; yoksa bostur.</summary>
|
||||||
|
public string? LookupTableName { get; set; }
|
||||||
|
public string? LookupValueColumn { get; set; }
|
||||||
|
public string? LookupDisplayColumn { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Parola/token/anahtar gibi sutunlarda true; sorgu araclari bunlari onermez.</summary>
|
||||||
|
public bool IsSensitive { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Varsayilan SELECT listesine girmesi uygun olan sutunlarda true.</summary>
|
||||||
|
public bool IsDefaultSelectColumn { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Bir listenin sutunlariyla birlikte tam tanimi.</summary>
|
||||||
|
public class McpListDetailDto : McpListSummaryDto
|
||||||
|
{
|
||||||
|
public List<McpColumnDto> Columns { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Serbest metin aramasinin tek sonucu.</summary>
|
||||||
|
public class McpSchemaMatchDto
|
||||||
|
{
|
||||||
|
public string ListFormCode { get; set; } = string.Empty;
|
||||||
|
public string? DisplayName { get; set; }
|
||||||
|
public string? TableName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Eslesme yalnizca liste duzeyindeyse bos; sutun duzeyindeyse eslesen sutunlar.</summary>
|
||||||
|
public List<McpColumnDto> MatchedColumns { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
using Volo.Abp.Application.Services;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MCP veri yazma yuzeyi. Yazma her zaman bir ListForm ekraninin insert/update/delete
|
||||||
|
/// sozlesmesinden gecer; ekran yetkisi ve tenant filtresi aynen uygulanir. Sunucu kaydinda
|
||||||
|
/// <c>AllowWrite</c> kapali oldugunda bu araclar hic listelenmez.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpDataAppService : IApplicationService
|
||||||
|
{
|
||||||
|
Task<McpWriteResultDto> InsertRowAsync(McpRowRequestDto input);
|
||||||
|
|
||||||
|
Task<McpWriteResultDto> UpdateRowAsync(McpRowRequestDto input);
|
||||||
|
|
||||||
|
Task<McpWriteResultDto> DeleteRowAsync(McpRowRequestDto input);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
using Volo.Abp.Application.Services;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uzak MCP istemcilerinin (Claude, ChatGPT connector'lari) OAuth kaydi. Kayit yeni bir
|
||||||
|
/// OpenIddict uygulamasi uretmez: istek, seed edilmis tek MCP istemcisinin izinli geri donus
|
||||||
|
/// adresleriyle dogrulanir ve o istemcinin kimligi dondurulur. Boylece herkese acik kayit ucu
|
||||||
|
/// veritabaninda denetimsiz uygulama biriktiremez.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpOAuthAppService : IApplicationService
|
||||||
|
{
|
||||||
|
Task<McpClientRegistrationResultDto> RegisterClientAsync(McpClientRegistrationRequestDto input);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
using Volo.Abp.Application.Services;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MCP salt-okunur sorgu yuzeyi. Her sorgu istegin kapsamindaki veri kaynaginda, sunucu
|
||||||
|
/// kaydindaki satir tavaniyla ve parametreli olarak kosar.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpQueryAppService : IApplicationService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Bir listenin verisini ekranin kendi sorgusuyla okur: liste okuma yetkisi, alan bazli
|
||||||
|
/// okuma yetkisi, kiraci/sube filtreleri ve kullanici ozellestirmeleri uygulamadaki gibi
|
||||||
|
/// gecerlidir. Normal kullanicinin veri okuma yolu budur.
|
||||||
|
/// </summary>
|
||||||
|
Task<McpListQueryResultDto> QueryListAsync(McpListQueryRequestDto input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tek bir SELECT ifadesi kosturur. Coklu ifade, DDL ve DML reddedilir; satir sayisi
|
||||||
|
/// sunucu kaydindaki tavana kirpilir.
|
||||||
|
/// </summary>
|
||||||
|
Task<McpQueryResultDto> RunSelectAsync(McpSelectRequestDto input);
|
||||||
|
|
||||||
|
/// <summary>Veri kaynagindaki view/procedure/function nesneleri ve Custom Endpoint'ler.</summary>
|
||||||
|
Task<List<McpSavedQueryDto>> GetSavedQueriesAsync(string? search = null);
|
||||||
|
|
||||||
|
/// <summary>Kayitli bir nesneyi ya da Custom Endpoint'i parametreleriyle cagirir.</summary>
|
||||||
|
Task<McpQueryResultDto> RunSavedQueryAsync(McpSavedQueryRequestDto input);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
using Volo.Abp.Application.Services;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MCP sema kesfi. Tablo ve sutun bilgisi canli katalogdan degil, platformun ekran
|
||||||
|
/// tanimlarindan uretilen JSON veri sozlugunden gelir; boylece modelin gordugu adlar
|
||||||
|
/// kullanicinin ekranda gordugu adlarla ayni olur.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpSchemaAppService : IApplicationService
|
||||||
|
{
|
||||||
|
/// <summary>Istegin kiracisina dusen etkin MCP sunucu kaydi; kayit yoksa null.</summary>
|
||||||
|
Task<McpServerConfigDto?> GetServerConfigAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bu kullanicinin bu baglantida cagirabilecegi araclar: sunucu kaydinin actiklari ile
|
||||||
|
/// kullanicinin uygulamadaki yetkilerinin kesisimi.
|
||||||
|
/// </summary>
|
||||||
|
Task<List<string>> GetAvailableToolsAsync();
|
||||||
|
|
||||||
|
/// <summary>Veri sozlugunun bulunup bulunmadigi ve ne zaman tazelendigi.</summary>
|
||||||
|
Task<McpSchemaInfoDto> GetSchemaInfoAsync();
|
||||||
|
|
||||||
|
/// <param name="search">Bos birakilirsa tum listeler doner.</param>
|
||||||
|
/// <param name="maxResults">Donecek en fazla liste sayisi.</param>
|
||||||
|
Task<List<McpListSummaryDto>> GetListsAsync(string? search = null, int maxResults = 200);
|
||||||
|
|
||||||
|
/// <summary>Tek bir listenin sutunlariyla birlikte tanimi; bulunamazsa null.</summary>
|
||||||
|
Task<McpListDetailDto?> GetListAsync(string listFormCode);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Liste ve sutun adlari ile basliklari uzerinde aksan ve buyuk/kucuk harf duyarsiz arama.
|
||||||
|
/// </summary>
|
||||||
|
Task<List<McpSchemaMatchDto>> SearchAsync(string term, int maxResults = 50);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Volo.Abp.Application;
|
||||||
|
using Volo.Abp.Authorization;
|
||||||
|
using Volo.Abp.Modularity;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
[DependsOn(
|
||||||
|
typeof(McpDomainSharedModule),
|
||||||
|
typeof(AbpDddApplicationContractsModule),
|
||||||
|
typeof(AbpAuthorizationModule)
|
||||||
|
)]
|
||||||
|
public class McpApplicationContractsModule : AbpModule
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<Import Project="..\common.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>Sozsoft.Mcp</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="10.0.0" />
|
||||||
|
<PackageReference Include="Volo.Abp.Authorization" Version="10.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Sozsoft.Mcp.Domain.Shared\Sozsoft.Mcp.Domain.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
using Sozsoft.Mcp.Services;
|
||||||
|
using Sozsoft.Platform.Localization;
|
||||||
|
using Volo.Abp;
|
||||||
|
using Volo.Abp.Application.Services;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MCP uygulama servislerinin ortak tabani. Kullaniciya gorunen metinler platformun
|
||||||
|
/// <c>PlatformResource</c> kaynagindan okunur; sunucu kaydini cozme ve arac kapisi her
|
||||||
|
/// serviste ayni davranir.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class McpAppService : ApplicationService
|
||||||
|
{
|
||||||
|
protected IMcpServerConfigProvider ServerConfigProvider { get; }
|
||||||
|
|
||||||
|
protected McpAppService(IMcpServerConfigProvider serverConfigProvider)
|
||||||
|
{
|
||||||
|
ServerConfigProvider = serverConfigProvider;
|
||||||
|
LocalizationResource = typeof(PlatformResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Etkin sunucu kaydinin DTO hali; kayit yoksa null.</summary>
|
||||||
|
protected async Task<McpServerConfigDto?> ResolveServerConfigAsync()
|
||||||
|
{
|
||||||
|
var server = await ServerConfigProvider.GetEffectiveServerAsync();
|
||||||
|
if (server is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new McpServerConfigDto
|
||||||
|
{
|
||||||
|
Name = server.Name,
|
||||||
|
Url = server.Url,
|
||||||
|
DataSourceCode = server.DataSourceCode,
|
||||||
|
AllowedTools = [.. ServerConfigProvider.SplitMultiValue(server.AllowedTools)],
|
||||||
|
MaxRows = server.MaxRows,
|
||||||
|
AllowWrite = server.AllowWrite,
|
||||||
|
Description = server.Description,
|
||||||
|
TenantName = CurrentTenant.Name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kaydi zorunlu kilan islemler icin. Kayit yoksa istemcinin nedenini gorebilecegi bir
|
||||||
|
/// hata atar.
|
||||||
|
/// </summary>
|
||||||
|
protected async Task<McpServerConfigDto> RequireServerConfigAsync()
|
||||||
|
=> await ResolveServerConfigAsync()
|
||||||
|
?? throw new UserFriendlyException(L[McpErrorCodes.NoServerRecord]);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kapali bir araca yapilan cagriyi reddeder: arac sunucu kaydinda kapaliysa ya da
|
||||||
|
/// kullanicinin uygulamada karsilik gelen yetkisi yoksa. Arac listesini suzmek tek basina
|
||||||
|
/// yeterli degildir — istemci listede olmayan bir araci da cagirabilir.
|
||||||
|
/// </summary>
|
||||||
|
protected async Task<McpServerConfigDto> EnsureToolAllowedAsync(string toolName)
|
||||||
|
{
|
||||||
|
var config = await RequireServerConfigAsync();
|
||||||
|
|
||||||
|
if (!McpToolPolicy.IsAllowed(true, config.AllowWrite, config.AllowedTools, toolName))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.ToolDisabled, toolName, config.Name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (McpToolNames.RequiredPermissions.TryGetValue(toolName, out var permission))
|
||||||
|
{
|
||||||
|
await AuthorizationService.CheckAsync(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Istenen satir sayisini sunucu kaydindaki tavana kirpar.</summary>
|
||||||
|
protected static int ResolveMaxRows(McpServerConfigDto config, int requested)
|
||||||
|
{
|
||||||
|
var ceiling = config.MaxRows > 0 ? config.MaxRows : McpConsts.DefaultMaxRows;
|
||||||
|
|
||||||
|
return requested > 0 ? Math.Min(requested, ceiling) : ceiling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
using Volo.Abp.Application;
|
||||||
|
using Volo.Abp.Modularity;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
[DependsOn(
|
||||||
|
typeof(McpDomainModule),
|
||||||
|
typeof(McpApplicationContractsModule),
|
||||||
|
typeof(AbpDddApplicationModule)
|
||||||
|
)]
|
||||||
|
public class McpApplicationModule : AbpModule
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kayitli nesneleri listelemek icin kullanilan katalog sorgulari. Parametre almazlar;
|
||||||
|
/// sabit metin oldugu icin string birlestirme riski yoktur.
|
||||||
|
/// </summary>
|
||||||
|
internal static class McpCatalogQueries
|
||||||
|
{
|
||||||
|
public const string SqlServer = """
|
||||||
|
SELECT
|
||||||
|
SCHEMA_NAME(o.schema_id) AS SchemaName,
|
||||||
|
o.name AS ObjectName,
|
||||||
|
CASE o.type
|
||||||
|
WHEN 'V' THEN 'VIEW'
|
||||||
|
WHEN 'P' THEN 'PROCEDURE'
|
||||||
|
WHEN 'IF' THEN 'TABLE_FUNCTION'
|
||||||
|
WHEN 'TF' THEN 'TABLE_FUNCTION'
|
||||||
|
WHEN 'FN' THEN 'SCALAR_FUNCTION'
|
||||||
|
END AS ObjectKind
|
||||||
|
FROM sys.objects o
|
||||||
|
WHERE o.is_ms_shipped = 0
|
||||||
|
AND o.type IN ('V', 'P', 'IF', 'TF', 'FN')
|
||||||
|
ORDER BY SchemaName, ObjectName
|
||||||
|
""";
|
||||||
|
|
||||||
|
public const string Postgres = """
|
||||||
|
SELECT n.nspname AS "SchemaName",
|
||||||
|
c.relname AS "ObjectName",
|
||||||
|
'VIEW' AS "ObjectKind"
|
||||||
|
FROM pg_class c
|
||||||
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||||
|
WHERE c.relkind IN ('v', 'm')
|
||||||
|
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
||||||
|
UNION ALL
|
||||||
|
SELECT n.nspname AS "SchemaName",
|
||||||
|
p.proname AS "ObjectName",
|
||||||
|
CASE
|
||||||
|
WHEN p.prokind = 'p' THEN 'PROCEDURE'
|
||||||
|
WHEN p.proretset THEN 'TABLE_FUNCTION'
|
||||||
|
ELSE 'SCALAR_FUNCTION'
|
||||||
|
END AS "ObjectKind"
|
||||||
|
FROM pg_proc p
|
||||||
|
JOIN pg_namespace n ON n.oid = p.pronamespace
|
||||||
|
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
||||||
|
ORDER BY 1, 2
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
using Sozsoft.Mcp.Services;
|
||||||
|
using Sozsoft.Platform;
|
||||||
|
using Sozsoft.Platform.ListForms.Select;
|
||||||
|
using Volo.Abp;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Veri yazma yuzeyi. Yazma her zaman bir ListForm ekraninin insert/update/delete
|
||||||
|
/// sozlesmesinden gecer; ekran yetkisi, tenant filtresi ve seed senkronizasyonu oradaki
|
||||||
|
/// yoldan aynen uygulanir.
|
||||||
|
/// </summary>
|
||||||
|
[Authorize(PlatformConsts.AppCodes.DeveloperKits.McpServers.Write)]
|
||||||
|
public class McpDataAppService(
|
||||||
|
IMcpServerConfigProvider serverConfigProvider,
|
||||||
|
IListFormDataAppService listFormDataAppService)
|
||||||
|
: McpAppService(serverConfigProvider), IMcpDataAppService
|
||||||
|
{
|
||||||
|
public async Task<McpWriteResultDto> InsertRowAsync(McpRowRequestDto input)
|
||||||
|
{
|
||||||
|
var request = await BuildRequestAsync(input, McpToolNames.InsertRow, requireKeys: false, requireValues: true);
|
||||||
|
var key = await listFormDataAppService.PostInsertAsync(request);
|
||||||
|
|
||||||
|
return new McpWriteResultDto { Key = ((object?)key)?.ToString() };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<McpWriteResultDto> UpdateRowAsync(McpRowRequestDto input)
|
||||||
|
{
|
||||||
|
var request = await BuildRequestAsync(input, McpToolNames.UpdateRow, requireKeys: true, requireValues: true);
|
||||||
|
var affected = await listFormDataAppService.PostUpdateAsync(request);
|
||||||
|
|
||||||
|
return new McpWriteResultDto { AffectedRows = affected };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<McpWriteResultDto> DeleteRowAsync(McpRowRequestDto input)
|
||||||
|
{
|
||||||
|
var request = await BuildRequestAsync(input, McpToolNames.DeleteRow, requireKeys: true, requireValues: false);
|
||||||
|
var affected = await listFormDataAppService.PostDeleteAsync(request);
|
||||||
|
|
||||||
|
return new McpWriteResultDto { AffectedRows = affected };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Istegi ekranin bekledigi bicime cevirir. <c>Data</c> JSON metni olarak verilir; ayni
|
||||||
|
/// bicim ekranin kopyalama yolunda da kullanilir.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<DataRequestDto> BuildRequestAsync(
|
||||||
|
McpRowRequestDto input,
|
||||||
|
string toolName,
|
||||||
|
bool requireKeys,
|
||||||
|
bool requireValues)
|
||||||
|
{
|
||||||
|
Check.NotNull(input, nameof(input));
|
||||||
|
|
||||||
|
// AllowWrite kapaliysa yazma araclari McpToolPolicy tarafindan zaten reddedilir;
|
||||||
|
// burada ayri bir kontrol yok.
|
||||||
|
await EnsureToolAllowedAsync(toolName);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(input.ListFormCode))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.ListFormCodeRequired]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requireValues && (input.Values is null || input.Values.Count == 0))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.ValuesRequired]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requireKeys && (input.Keys is null || input.Keys.Count == 0))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.KeysRequired]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DataRequestDto
|
||||||
|
{
|
||||||
|
ListFormCode = input.ListFormCode,
|
||||||
|
Data = JsonSerializer.Serialize(input.Values ?? new Dictionary<string, object?>()),
|
||||||
|
Keys = [.. (input.Keys ?? []).Select(key => key!)]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
using Sozsoft.Mcp.Services;
|
||||||
|
using Sozsoft.Platform.Localization;
|
||||||
|
using Volo.Abp;
|
||||||
|
using Volo.Abp.Application.Services;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dinamik istemci kaydi (RFC 7591). Uc kimlik dogrulamasizdir; istemci ne alirsa alsin token
|
||||||
|
/// kullanicinin kendi girisiyle ve kendi yetkileriyle uretilir. Kural ve kayit
|
||||||
|
/// <see cref="IMcpOAuthClientManager"/> icindedir.
|
||||||
|
/// </summary>
|
||||||
|
[AllowAnonymous]
|
||||||
|
public class McpOAuthAppService : ApplicationService, IMcpOAuthAppService
|
||||||
|
{
|
||||||
|
private const string AuthorizationCodeGrant = "authorization_code";
|
||||||
|
private const string RefreshTokenGrant = "refresh_token";
|
||||||
|
|
||||||
|
private readonly IMcpOAuthClientManager clientManager;
|
||||||
|
|
||||||
|
public McpOAuthAppService(IMcpOAuthClientManager clientManager)
|
||||||
|
{
|
||||||
|
this.clientManager = clientManager;
|
||||||
|
LocalizationResource = typeof(PlatformResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<McpClientRegistrationResultDto> RegisterClientAsync(McpClientRegistrationRequestDto input)
|
||||||
|
{
|
||||||
|
Check.NotNull(input, nameof(input));
|
||||||
|
|
||||||
|
var redirectUris = (input.RedirectUris ?? []).Distinct(StringComparer.Ordinal).ToList();
|
||||||
|
var registration = await clientManager.RegisterAsync(input.ClientName, redirectUris);
|
||||||
|
|
||||||
|
return registration.Error switch
|
||||||
|
{
|
||||||
|
McpClientRegistrationError.InvalidRedirectUri => new McpClientRegistrationResultDto
|
||||||
|
{
|
||||||
|
Error = "invalid_redirect_uri",
|
||||||
|
ErrorDescription = L[McpErrorCodes.RedirectUriNotAllowed, registration.ErrorValue ?? string.Empty]
|
||||||
|
},
|
||||||
|
McpClientRegistrationError.LimitReached => new McpClientRegistrationResultDto
|
||||||
|
{
|
||||||
|
Error = "invalid_client_metadata",
|
||||||
|
ErrorDescription = L[McpErrorCodes.RegistrationLimitReached, registration.ErrorValue ?? string.Empty]
|
||||||
|
},
|
||||||
|
_ => new McpClientRegistrationResultDto
|
||||||
|
{
|
||||||
|
ClientId = registration.ClientId,
|
||||||
|
ClientName = input.ClientName,
|
||||||
|
RedirectUris = redirectUris,
|
||||||
|
// Istemci hangi akislari isterse istesin gecerli olanlar bunlardir; RFC 7591
|
||||||
|
// sunucunun istenen degerleri daraltmasina izin verir.
|
||||||
|
GrantTypes = [AuthorizationCodeGrant, RefreshTokenGrant],
|
||||||
|
ResponseTypes = ["code"],
|
||||||
|
Scope = string.Join(' ', McpConsts.OAuthScopes),
|
||||||
|
ClientIdIssuedAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,291 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
using Sozsoft.Mcp.Services;
|
||||||
|
using Sozsoft.Platform;
|
||||||
|
using Sozsoft.Platform.Entities;
|
||||||
|
using Sozsoft.Platform.Enums;
|
||||||
|
using Sozsoft.Platform.ListForms.Select;
|
||||||
|
using Volo.Abp;
|
||||||
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Salt-okunur sorgu yuzeyi. MCP kullaniciya yetki vermez, uygulamadaki yetkisini tasir:
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item><c>query_list</c> ekranin kendi sorgusundan gecer; liste ve alan yetkileri, kiraci ve
|
||||||
|
/// sube filtreleri aynen uygulanir.</item>
|
||||||
|
/// <item>Ham SQL ve veritabani nesneleri (view/procedure/function) liste yetkilerini asar; bu
|
||||||
|
/// yuzden yalnizca uygulamada SQL Query Manager yetkisi olan kullaniciya aciktir.</item>
|
||||||
|
/// <item>Custom Endpoint'ler endpoint dispatcher'inin yetkisini ve endpoint'in kendi
|
||||||
|
/// kullanici/rol listesini ister.</item>
|
||||||
|
/// </list>
|
||||||
|
/// SQL kurallari <see cref="IMcpSqlGuard"/>, veri kaynagi cozumu ve calistirma
|
||||||
|
/// <see cref="IMcpQueryExecutor"/> icindedir.
|
||||||
|
/// </summary>
|
||||||
|
[Authorize(PlatformConsts.AppCodes.DeveloperKits.McpServers.Invoke)]
|
||||||
|
public class McpQueryAppService(
|
||||||
|
IMcpServerConfigProvider serverConfigProvider,
|
||||||
|
IMcpSqlGuard sqlGuard,
|
||||||
|
IMcpQueryExecutor queryExecutor,
|
||||||
|
IRepository<CustomEndpoint, Guid> customEndpointRepository,
|
||||||
|
IListFormSelectAppService listFormSelectAppService)
|
||||||
|
: McpAppService(serverConfigProvider), IMcpQueryAppService
|
||||||
|
{
|
||||||
|
private const string SqlPermission = PlatformConsts.AppCodes.SqlQueryManagers.Default;
|
||||||
|
private const string CustomEndpointPermission = PlatformConsts.AppCodes.DeveloperKits.Get;
|
||||||
|
|
||||||
|
public async Task<McpListQueryResultDto> QueryListAsync(McpListQueryRequestDto input)
|
||||||
|
{
|
||||||
|
Check.NotNull(input, nameof(input));
|
||||||
|
|
||||||
|
var config = await EnsureToolAllowedAsync(McpToolNames.QueryList);
|
||||||
|
var take = ResolveMaxRows(config, input.Take);
|
||||||
|
|
||||||
|
var result = await listFormSelectAppService.GetSelectAsync(new SelectRequestDto
|
||||||
|
{
|
||||||
|
ListFormCode = input.ListFormCode,
|
||||||
|
Filter = NullIfEmpty(input.Filter),
|
||||||
|
Sort = NullIfEmpty(input.Sort),
|
||||||
|
Group = NullIfEmpty(input.Group),
|
||||||
|
GroupSummary = NullIfEmpty(input.GroupSummary),
|
||||||
|
TotalSummary = NullIfEmpty(input.TotalSummary),
|
||||||
|
Skip = Math.Max(input.Skip, 0),
|
||||||
|
Take = take,
|
||||||
|
RequireTotalCount = true,
|
||||||
|
RequireGroupCount = !string.IsNullOrWhiteSpace(input.Group)
|
||||||
|
});
|
||||||
|
|
||||||
|
return new McpListQueryResultDto
|
||||||
|
{
|
||||||
|
Data = result.Data,
|
||||||
|
TotalCount = result.TotalCount,
|
||||||
|
GroupCount = result.GroupCount,
|
||||||
|
Summary = result.Summary,
|
||||||
|
Take = take
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public async Task<McpQueryResultDto> RunSelectAsync(McpSelectRequestDto input)
|
||||||
|
{
|
||||||
|
Check.NotNull(input, nameof(input));
|
||||||
|
|
||||||
|
var config = await EnsureToolAllowedAsync(McpToolNames.RunSelect);
|
||||||
|
|
||||||
|
var sql = (input.Sql ?? string.Empty).Trim().TrimEnd(';').Trim();
|
||||||
|
sqlGuard.EnsureReadOnlyStatement(sql);
|
||||||
|
|
||||||
|
var maxRows = ResolveMaxRows(config, input.MaxRows);
|
||||||
|
var parameters = sqlGuard.NormalizeParameters(input.Parameters);
|
||||||
|
var context = await queryExecutor.ResolveDataSourceAsync(config.DataSourceCode);
|
||||||
|
var limitedSql = sqlGuard.ApplyRowLimit(sql, maxRows, context.DataSourceType);
|
||||||
|
|
||||||
|
return ToResult(await queryExecutor.ExecuteAsync(context, limitedSql, parameters, maxRows));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<McpSavedQueryDto>> GetSavedQueriesAsync(string? search = null)
|
||||||
|
{
|
||||||
|
var config = await EnsureToolAllowedAsync(McpToolNames.ListQueries);
|
||||||
|
var results = new List<McpSavedQueryDto>();
|
||||||
|
|
||||||
|
if (await AuthorizationService.IsGrantedAsync(SqlPermission))
|
||||||
|
{
|
||||||
|
results.AddRange(await GetDatabaseObjectsAsync(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await AuthorizationService.IsGrantedAsync(CustomEndpointPermission))
|
||||||
|
{
|
||||||
|
results.AddRange(await GetCallableCustomEndpointsAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
|
{
|
||||||
|
results = [.. results.Where(item =>
|
||||||
|
item.Name.Contains(search, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| (item.Description?.Contains(search, StringComparison.OrdinalIgnoreCase) ?? false))];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [.. results
|
||||||
|
.OrderBy(item => item.Kind, StringComparer.Ordinal)
|
||||||
|
.ThenBy(item => item.Name, StringComparer.OrdinalIgnoreCase)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<McpSavedQueryDto>> GetDatabaseObjectsAsync(McpServerConfigDto config)
|
||||||
|
{
|
||||||
|
var context = await queryExecutor.ResolveDataSourceAsync(config.DataSourceCode);
|
||||||
|
var catalogSql = context.DataSourceType == DataSourceTypeEnum.Postgresql
|
||||||
|
? McpCatalogQueries.Postgres
|
||||||
|
: McpCatalogQueries.SqlServer;
|
||||||
|
|
||||||
|
// Katalog sorgusunun kendi satir siniri yoktur; nesne sayisi tavani asmaz.
|
||||||
|
var catalog = await queryExecutor.ExecuteAsync(context, catalogSql, new Dictionary<string, object>(), int.MaxValue);
|
||||||
|
|
||||||
|
var results = new List<McpSavedQueryDto>();
|
||||||
|
|
||||||
|
foreach (var row in catalog.Rows)
|
||||||
|
{
|
||||||
|
var kind = MapCatalogKind(ReadString(row, "ObjectKind"));
|
||||||
|
if (kind is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.Add(new McpSavedQueryDto
|
||||||
|
{
|
||||||
|
Kind = kind,
|
||||||
|
SchemaName = ReadString(row, "SchemaName"),
|
||||||
|
Name = ReadString(row, "ObjectName") ?? string.Empty
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<McpQueryResultDto> RunSavedQueryAsync(McpSavedQueryRequestDto input)
|
||||||
|
{
|
||||||
|
Check.NotNull(input, nameof(input));
|
||||||
|
|
||||||
|
var config = await EnsureToolAllowedAsync(McpToolNames.RunQuery);
|
||||||
|
|
||||||
|
if (IsKind(input.Kind, McpConsts.SavedQueryKinds.CustomEndpoint))
|
||||||
|
{
|
||||||
|
await AuthorizationService.CheckAsync(CustomEndpointPermission);
|
||||||
|
return await RunCustomEndpointAsync(input, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
await AuthorizationService.CheckAsync(SqlPermission);
|
||||||
|
|
||||||
|
var maxRows = ResolveMaxRows(config, input.MaxRows);
|
||||||
|
var parameters = sqlGuard.NormalizeParameters(input.Parameters);
|
||||||
|
var context = await queryExecutor.ResolveDataSourceAsync(config.DataSourceCode);
|
||||||
|
|
||||||
|
var schemaName = string.IsNullOrWhiteSpace(input.SchemaName)
|
||||||
|
? sqlGuard.DefaultSchemaName(context.DataSourceType)
|
||||||
|
: input.SchemaName;
|
||||||
|
|
||||||
|
var qualifiedName = sqlGuard.QuoteQualifiedName(schemaName, input.Name, context.DataSourceType);
|
||||||
|
var sql = BuildSavedQuerySql(input.Kind, qualifiedName, parameters, maxRows, context.DataSourceType);
|
||||||
|
|
||||||
|
return ToResult(await queryExecutor.ExecuteAsync(context, sql, parameters, maxRows));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<McpQueryResultDto> RunCustomEndpointAsync(
|
||||||
|
McpSavedQueryRequestDto input,
|
||||||
|
McpServerConfigDto config)
|
||||||
|
{
|
||||||
|
var endpoints = await GetCallableEndpointEntitiesAsync();
|
||||||
|
var endpoint = endpoints.Find(candidate => string.Equals(candidate.Name, input.Name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
?? throw new UserFriendlyException(L[McpErrorCodes.CustomEndpointNotFound, input.Name]);
|
||||||
|
|
||||||
|
var maxRows = ResolveMaxRows(config, input.MaxRows);
|
||||||
|
var parameters = sqlGuard.NormalizeParameters(input.Parameters);
|
||||||
|
|
||||||
|
// Eksik parametre Dapper tarafinda belirsiz bir hataya donusmesin diye burada yakalanir.
|
||||||
|
var missing = endpoint.Parameters
|
||||||
|
.Where(parameter => parameter.IsRequired && !parameters.ContainsKey(parameter.Name))
|
||||||
|
.Select(parameter => parameter.Name)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (missing.Count > 0)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.MissingParameters, string.Join(", ", missing)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var context = await queryExecutor.ResolveDataSourceAsync(endpoint.DataSourceCode);
|
||||||
|
|
||||||
|
return ToResult(await queryExecutor.ExecuteAsync(context, endpoint.Sql, parameters, maxRows));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<McpSavedQueryDto>> GetCallableCustomEndpointsAsync()
|
||||||
|
{
|
||||||
|
var endpoints = await GetCallableEndpointEntitiesAsync();
|
||||||
|
|
||||||
|
return [.. endpoints.Select(endpoint => new McpSavedQueryDto
|
||||||
|
{
|
||||||
|
Kind = McpConsts.SavedQueryKinds.CustomEndpoint,
|
||||||
|
Name = endpoint.Name,
|
||||||
|
Description = endpoint.Description,
|
||||||
|
ParameterNames = [.. endpoint.Parameters.Select(parameter => parameter.Name)]
|
||||||
|
})];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Yalnizca okuma amacli (GET) ve istegi yapan kullaniciya acik Custom Endpoint kayitlari.
|
||||||
|
/// Yetki sozlesmesi endpoint dispatcher'indaki ile birebir aynidir.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<List<CustomEndpoint>> GetCallableEndpointEntitiesAsync()
|
||||||
|
{
|
||||||
|
var query = await customEndpointRepository.GetQueryableAsync();
|
||||||
|
var candidates = query.Where(endpoint => endpoint.Method == "GET").ToList();
|
||||||
|
|
||||||
|
return [.. candidates.Where(endpoint => endpoint.Permissions.Any(permission =>
|
||||||
|
permission.ResourceType == "Global"
|
||||||
|
|| (permission.ResourceType == "User" && permission.ResourceId == CurrentUser.UserName)
|
||||||
|
|| (permission.ResourceType == "Role" && CurrentUser.Roles.Contains(permission.ResourceId))))];
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildSavedQuerySql(
|
||||||
|
string? kind,
|
||||||
|
string qualifiedName,
|
||||||
|
Dictionary<string, object> parameters,
|
||||||
|
int maxRows,
|
||||||
|
DataSourceTypeEnum dataSourceType)
|
||||||
|
{
|
||||||
|
var parameterList = string.Join(", ", parameters.Keys.Select(name => $"@{name}"));
|
||||||
|
|
||||||
|
if (IsKind(kind, McpConsts.SavedQueryKinds.View))
|
||||||
|
{
|
||||||
|
return sqlGuard.ApplyRowLimit($"SELECT * FROM {qualifiedName}", maxRows, dataSourceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKind(kind, McpConsts.SavedQueryKinds.TableFunction))
|
||||||
|
{
|
||||||
|
return sqlGuard.ApplyRowLimit($"SELECT * FROM {qualifiedName}({parameterList})", maxRows, dataSourceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKind(kind, McpConsts.SavedQueryKinds.ScalarFunction))
|
||||||
|
{
|
||||||
|
return $"SELECT {qualifiedName}({parameterList}) AS Value";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKind(kind, McpConsts.SavedQueryKinds.Procedure))
|
||||||
|
{
|
||||||
|
// Prosedur cagrisina satir tavani yazilamaz; sonuc kumesi okunurken kesilir.
|
||||||
|
return dataSourceType == DataSourceTypeEnum.Postgresql
|
||||||
|
? $"CALL {qualifiedName}({parameterList})"
|
||||||
|
: $"EXEC {qualifiedName} {string.Join(", ", parameters.Keys.Select(name => $"@{name} = @{name}"))}".TrimEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.UnsupportedObjectKind, kind ?? string.Empty]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? NullIfEmpty(string? value)
|
||||||
|
=> string.IsNullOrWhiteSpace(value) ? null : value;
|
||||||
|
|
||||||
|
private static McpQueryResultDto ToResult(McpQueryRows rows) => new()
|
||||||
|
{
|
||||||
|
Columns = rows.Columns,
|
||||||
|
Rows = rows.Rows,
|
||||||
|
RowCount = rows.Rows.Count,
|
||||||
|
Truncated = rows.Truncated,
|
||||||
|
ExecutionTimeMs = rows.ExecutionTimeMs
|
||||||
|
};
|
||||||
|
|
||||||
|
private static bool IsKind(string? kind, string expected)
|
||||||
|
=> string.Equals(kind, expected, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private static string? MapCatalogKind(string? catalogKind) => catalogKind switch
|
||||||
|
{
|
||||||
|
McpConsts.CatalogKinds.View => McpConsts.SavedQueryKinds.View,
|
||||||
|
McpConsts.CatalogKinds.Procedure => McpConsts.SavedQueryKinds.Procedure,
|
||||||
|
McpConsts.CatalogKinds.TableFunction => McpConsts.SavedQueryKinds.TableFunction,
|
||||||
|
McpConsts.CatalogKinds.ScalarFunction => McpConsts.SavedQueryKinds.ScalarFunction,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string? ReadString(Dictionary<string, object?> row, string key)
|
||||||
|
=> row.TryGetValue(key, out var value) ? value?.ToString() : null;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,214 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
using Sozsoft.Mcp.Services;
|
||||||
|
using Sozsoft.Platform;
|
||||||
|
using Volo.Abp.Authorization.Permissions;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sema kesfini JSON veri sozlugu uzerinden karsilar; sozlugu okuma ve arama normalizasyonu
|
||||||
|
/// domain servisindedir (<see cref="IMcpSchemaDictionary"/>). Kullanici yalnizca uygulamada
|
||||||
|
/// okuma yetkisi olan listeleri gorur (<see cref="IMcpListFormAccessFilter"/>).
|
||||||
|
/// </summary>
|
||||||
|
[Authorize(PlatformConsts.AppCodes.DeveloperKits.McpServers.Invoke)]
|
||||||
|
public class McpSchemaAppService(
|
||||||
|
IMcpServerConfigProvider serverConfigProvider,
|
||||||
|
IMcpSchemaDictionary dictionary,
|
||||||
|
IMcpListFormAccessFilter accessFilter,
|
||||||
|
IPermissionChecker permissionChecker)
|
||||||
|
: McpAppService(serverConfigProvider), IMcpSchemaAppService
|
||||||
|
{
|
||||||
|
public async Task<McpServerConfigDto?> GetServerConfigAsync()
|
||||||
|
=> await ResolveServerConfigAsync();
|
||||||
|
|
||||||
|
public async Task<List<string>> GetAvailableToolsAsync()
|
||||||
|
{
|
||||||
|
var config = await ResolveServerConfigAsync();
|
||||||
|
var candidates = McpToolPolicy.VisibleTools(
|
||||||
|
config is not null,
|
||||||
|
config?.AllowWrite ?? false,
|
||||||
|
config?.AllowedTools ?? []);
|
||||||
|
|
||||||
|
var required = candidates
|
||||||
|
.Where(McpToolNames.RequiredPermissions.ContainsKey)
|
||||||
|
.Select(tool => McpToolNames.RequiredPermissions[tool])
|
||||||
|
.Distinct(StringComparer.Ordinal)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var granted = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
if (required.Length > 0)
|
||||||
|
{
|
||||||
|
var grants = await permissionChecker.IsGrantedAsync(required);
|
||||||
|
granted.UnionWith(grants.Result
|
||||||
|
.Where(result => result.Value == PermissionGrantResult.Granted)
|
||||||
|
.Select(result => result.Key));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [.. candidates.Where(tool =>
|
||||||
|
!McpToolNames.RequiredPermissions.TryGetValue(tool, out var permission) || granted.Contains(permission))];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<McpSchemaInfoDto> GetSchemaInfoAsync()
|
||||||
|
{
|
||||||
|
var snapshot = await dictionary.GetSnapshotAsync();
|
||||||
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
return new McpSchemaInfoDto
|
||||||
|
{
|
||||||
|
Available = false,
|
||||||
|
TenantName = dictionary.ResolveTenantName(),
|
||||||
|
Message = L[McpErrorCodes.DictionaryNotFound, McpConsts.DictionaryProcedureName]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new McpSchemaInfoDto
|
||||||
|
{
|
||||||
|
Available = true,
|
||||||
|
CacheKey = snapshot.CacheKey,
|
||||||
|
RefreshedAt = snapshot.RefreshedAt,
|
||||||
|
FormCount = snapshot.FormCount,
|
||||||
|
FieldCount = snapshot.FieldCount,
|
||||||
|
TenantName = snapshot.TenantName,
|
||||||
|
DefinitionCultureName = snapshot.DefinitionCultureName,
|
||||||
|
UiCultureName = snapshot.UiCultureName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<McpListSummaryDto>> GetListsAsync(string? search = null, int maxResults = 200)
|
||||||
|
{
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.ListLists);
|
||||||
|
|
||||||
|
var forms = await GetReadableListFormsAsync();
|
||||||
|
var normalizedSearch = dictionary.Normalize(search);
|
||||||
|
|
||||||
|
var matches = forms.Where(form => normalizedSearch.Length == 0
|
||||||
|
|| dictionary.Normalize(form.SearchText).Contains(normalizedSearch, StringComparison.Ordinal));
|
||||||
|
|
||||||
|
return [.. matches.Take(NormalizeLimit(maxResults, 200)).Select(ToSummary)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<McpListDetailDto?> GetListAsync(string listFormCode)
|
||||||
|
{
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.DescribeList);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(listFormCode))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var forms = await GetReadableListFormsAsync();
|
||||||
|
var form = forms.FirstOrDefault(candidate =>
|
||||||
|
string.Equals(candidate.ListFormCode, listFormCode, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (form is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var detail = new McpListDetailDto();
|
||||||
|
CopySummary(form, detail);
|
||||||
|
detail.Columns = [.. (form.Fields ?? []).Select(ToColumn)];
|
||||||
|
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<McpSchemaMatchDto>> SearchAsync(string term, int maxResults = 50)
|
||||||
|
{
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.SearchSchema);
|
||||||
|
|
||||||
|
var normalized = dictionary.Normalize(term);
|
||||||
|
if (normalized.Length == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var limit = NormalizeLimit(maxResults, 50);
|
||||||
|
var forms = await GetReadableListFormsAsync();
|
||||||
|
var results = new List<McpSchemaMatchDto>();
|
||||||
|
|
||||||
|
foreach (var form in forms)
|
||||||
|
{
|
||||||
|
var formMatches = dictionary.Normalize(form.SearchText).Contains(normalized, StringComparison.Ordinal);
|
||||||
|
|
||||||
|
var columnMatches = (form.Fields ?? [])
|
||||||
|
.Where(field => dictionary.Normalize(field.Field).Contains(normalized, StringComparison.Ordinal)
|
||||||
|
|| dictionary.Normalize(field.DisplayName).Contains(normalized, StringComparison.Ordinal))
|
||||||
|
.Select(ToColumn)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (!formMatches && columnMatches.Count == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.Add(new McpSchemaMatchDto
|
||||||
|
{
|
||||||
|
ListFormCode = form.ListFormCode ?? string.Empty,
|
||||||
|
DisplayName = form.DisplayName,
|
||||||
|
TableName = NullIfEmpty(form.TableName),
|
||||||
|
MatchedColumns = columnMatches
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.Count >= limit)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<McpDictionaryListForm>> GetReadableListFormsAsync()
|
||||||
|
{
|
||||||
|
var forms = await dictionary.GetListFormsAsync();
|
||||||
|
var readable = await accessFilter.GetReadableListFormCodesAsync();
|
||||||
|
|
||||||
|
return [.. forms.Where(form => form.ListFormCode is not null && readable.Contains(form.ListFormCode))];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static McpListSummaryDto ToSummary(McpDictionaryListForm form)
|
||||||
|
{
|
||||||
|
var summary = new McpListSummaryDto();
|
||||||
|
CopySummary(form, summary);
|
||||||
|
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CopySummary(McpDictionaryListForm form, McpListSummaryDto target)
|
||||||
|
{
|
||||||
|
target.ListFormCode = form.ListFormCode ?? string.Empty;
|
||||||
|
target.DisplayName = NullIfEmpty(form.DisplayName);
|
||||||
|
target.Title = NullIfEmpty(form.Title);
|
||||||
|
target.Description = NullIfEmpty(form.Description);
|
||||||
|
target.TableName = NullIfEmpty(form.TableName);
|
||||||
|
target.SqlSourceName = NullIfEmpty(form.SqlSourceName);
|
||||||
|
target.SourceKind = NullIfEmpty(form.SourceKind);
|
||||||
|
target.KeyFieldName = NullIfEmpty(form.KeyFieldName);
|
||||||
|
target.FieldCount = form.Fields?.Count ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static McpColumnDto ToColumn(McpDictionaryField field) => new()
|
||||||
|
{
|
||||||
|
Field = field.Field ?? string.Empty,
|
||||||
|
DisplayName = NullIfEmpty(field.DisplayName),
|
||||||
|
SqlDataType = NullIfEmpty(field.SqlDataType),
|
||||||
|
IsVisible = field.IsVisible,
|
||||||
|
IsBooleanColumn = field.IsBooleanColumn,
|
||||||
|
LookupTableName = NullIfEmpty(field.LookupTableName),
|
||||||
|
LookupValueColumn = NullIfEmpty(field.LookupValueColumn),
|
||||||
|
LookupDisplayColumn = NullIfEmpty(field.LookupDisplayColumn),
|
||||||
|
IsSensitive = field.IsSensitive,
|
||||||
|
IsDefaultSelectColumn = field.IsDefaultSelectColumn
|
||||||
|
};
|
||||||
|
|
||||||
|
private static int NormalizeLimit(int requested, int fallback)
|
||||||
|
=> requested <= 0 ? fallback : Math.Min(requested, fallback * 5);
|
||||||
|
|
||||||
|
private static string? NullIfEmpty(string? value)
|
||||||
|
=> string.IsNullOrWhiteSpace(value) ? null : value;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<Import Project="..\common.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>Sozsoft.Mcp</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Volo.Abp.Ddd.Application" Version="10.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Sozsoft.Mcp.Application.Contracts\Sozsoft.Mcp.Application.Contracts.csproj" />
|
||||||
|
<ProjectReference Include="..\Sozsoft.Mcp.Domain\Sozsoft.Mcp.Domain.csproj" />
|
||||||
|
<!-- Veri yazma ekranin insert/update/delete sozlesmesinden gecer. -->
|
||||||
|
<ProjectReference Include="..\..\..\src\Sozsoft.Platform.Application.Contracts\Sozsoft.Platform.Application.Contracts.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
104
api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpConsts.cs
Normal file
104
api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpConsts.cs
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>MCP modulunun sabit sozlesme degerleri.</summary>
|
||||||
|
public static class McpConsts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Veri sozlugu satirlarinin turu. <c>Sas_H_DbTableColumn</c> tablosunda ayni deger
|
||||||
|
/// <c>DictionaryType</c> kolonunda durur.
|
||||||
|
/// </summary>
|
||||||
|
public const string DictionaryType = "ListFormJson";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sozluk ureten prosedurun varsayilan <c>@TenantName</c> degeri. Host baglaminda sozluk
|
||||||
|
/// bu adla yazilir.
|
||||||
|
/// </summary>
|
||||||
|
public const string DefaultDictionaryTenantName = "Sozsoft";
|
||||||
|
|
||||||
|
/// <summary>Sunucu kaydinda satir tavani verilmediginde (ya da sifir oldugunda) uygulanan deger.</summary>
|
||||||
|
public const int DefaultMaxRows = 200;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <c>CustomEndpoint.DataSourceCode</c> bu degeri tasidiginda istegi yapan kullanicinin
|
||||||
|
/// kiraci veritabani kullanilir.
|
||||||
|
/// </summary>
|
||||||
|
public const string TenantDataSourceMarker = "!Tenant";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sozluk ureten prosedurun adi. Sozluk bulunamadiginda istemciye ne yapacagi bu adla
|
||||||
|
/// bildirilir.
|
||||||
|
/// </summary>
|
||||||
|
public const string DictionaryProcedureName = "dbo.Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uzak MCP istemcilerinin (Claude, ChatGPT connector'lari) OAuth istemci kimligi. OpenIddict
|
||||||
|
/// uygulamasi DbMigrator'da <c>OpenIddict:Applications:Platform_Mcp</c> ayarindan seed edilir;
|
||||||
|
/// dinamik istemci kaydi yeni uygulama uretmez, bu kimligi dondurur.
|
||||||
|
/// </summary>
|
||||||
|
public const string OAuthClientId = "Platform_Mcp";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dinamik kayitla (RFC 7591) uretilen istemcilerin kimlik oneki. Bilinen saglayicilarin
|
||||||
|
/// (Claude, ChatGPT) disindaki MCP istemcileri bu onekle kendi OpenIddict uygulamasini alir.
|
||||||
|
/// </summary>
|
||||||
|
public const string DynamicClientIdPrefix = "mcp_";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dinamik istemci sayisinin tavani. Kayit ucu kimlik dogrulamasizdir; tavan, ucun
|
||||||
|
/// veritabaninda sinirsiz uygulama biriktirmesini onler.
|
||||||
|
/// </summary>
|
||||||
|
public const int MaxDynamicClients = 500;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MCP istemcilerinin yenileme token omru. Web istemcisinin kisa omru (dakikalar) connector
|
||||||
|
/// icin uygun degil: kullanici birkac saat soru sormadiginda baglanti kopardi. Yenileme
|
||||||
|
/// token'i her kullanimda yenilenir; omur, son kullanimdan itibaren sayilir.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly System.TimeSpan RefreshTokenLifetime = System.TimeSpan.FromDays(30);
|
||||||
|
|
||||||
|
/// <summary>Istemci kimligi bir MCP istemcisine (bilinen ya da dinamik) mi ait.</summary>
|
||||||
|
public static bool IsMcpClient(string? clientId)
|
||||||
|
=> clientId is not null
|
||||||
|
&& (string.Equals(clientId, OAuthClientId, System.StringComparison.Ordinal)
|
||||||
|
|| clientId.StartsWith(DynamicClientIdPrefix, System.StringComparison.Ordinal));
|
||||||
|
|
||||||
|
/// <summary>MCP ucunun sunucu adresine gore yolu.</summary>
|
||||||
|
public const string EndpointPath = "/mcp";
|
||||||
|
|
||||||
|
/// <summary>Dinamik istemci kaydi (RFC 7591) ucunun yolu.</summary>
|
||||||
|
public const string RegistrationEndpointPath = "/connect/register";
|
||||||
|
|
||||||
|
/// <summary>Korumali kaynak meta verisi (RFC 9728) ucunun yolu.</summary>
|
||||||
|
public const string ProtectedResourceMetadataPath = "/.well-known/oauth-protected-resource";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Istemcinin istemesi gereken kapsamlar. <c>offline_access</c> yenileme token'i icindir;
|
||||||
|
/// olmadan baglanti her saat yeniden giris ister.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string[] OAuthScopes = [Sozsoft.Platform.PlatformConsts.AppName, "offline_access"];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <c>run_query</c> araciyla cagrilabilen nesne turleri. Degerler istemciye oldugu gibi
|
||||||
|
/// gosterilir; degistirilirse arac sozlesmesi kirilir.
|
||||||
|
/// </summary>
|
||||||
|
public static class SavedQueryKinds
|
||||||
|
{
|
||||||
|
public const string View = "view";
|
||||||
|
public const string Procedure = "procedure";
|
||||||
|
public const string TableFunction = "tableFunction";
|
||||||
|
public const string ScalarFunction = "scalarFunction";
|
||||||
|
public const string CustomEndpoint = "customEndpoint";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Veritabanı katalog sorgusunun dondurdugu nesne turu etiketleri.
|
||||||
|
/// <see cref="SavedQueryKinds"/> degerlerine eslenir.
|
||||||
|
/// </summary>
|
||||||
|
public static class CatalogKinds
|
||||||
|
{
|
||||||
|
public const string View = "VIEW";
|
||||||
|
public const string Procedure = "PROCEDURE";
|
||||||
|
public const string TableFunction = "TABLE_FUNCTION";
|
||||||
|
public const string ScalarFunction = "SCALAR_FUNCTION";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
using Volo.Abp.Domain;
|
||||||
|
using Volo.Abp.Modularity;
|
||||||
|
using Volo.Abp.Validation;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Modulun paylasilan sozlesmesi. Kullaniciya gorunen metinler platformun kendi
|
||||||
|
/// <c>PlatformResource</c> kaynagindan (LanguagesData.json) okunur; modul ikinci bir dil
|
||||||
|
/// kaynagi acmaz.
|
||||||
|
/// </summary>
|
||||||
|
[DependsOn(
|
||||||
|
typeof(AbpValidationModule),
|
||||||
|
typeof(AbpDddDomainSharedModule)
|
||||||
|
)]
|
||||||
|
public class McpDomainSharedModule : AbpModule
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MCP hatalarinin dil anahtarlari. Karsiliklari platformun <c>LanguagesData.json</c>
|
||||||
|
/// dosyasinda EN + TR olarak durur; koda gomulu metin kullanilmaz.
|
||||||
|
/// </summary>
|
||||||
|
public static class McpErrorCodes
|
||||||
|
{
|
||||||
|
private const string Prefix = "App.Mcp.Error.";
|
||||||
|
|
||||||
|
public const string NoServerRecord = Prefix + "NoServerRecord";
|
||||||
|
public const string ToolDisabled = Prefix + "ToolDisabled";
|
||||||
|
public const string EmptyQuery = Prefix + "EmptyQuery";
|
||||||
|
public const string SingleStatementOnly = Prefix + "SingleStatementOnly";
|
||||||
|
public const string ReadOnlyStatementRequired = Prefix + "ReadOnlyStatementRequired";
|
||||||
|
public const string ForbiddenKeyword = Prefix + "ForbiddenKeyword";
|
||||||
|
public const string InvalidIdentifier = Prefix + "InvalidIdentifier";
|
||||||
|
public const string InvalidParameterName = Prefix + "InvalidParameterName";
|
||||||
|
public const string UnsupportedObjectKind = Prefix + "UnsupportedObjectKind";
|
||||||
|
public const string CustomEndpointNotFound = Prefix + "CustomEndpointNotFound";
|
||||||
|
public const string MissingParameters = Prefix + "MissingParameters";
|
||||||
|
public const string ListFormCodeRequired = Prefix + "ListFormCodeRequired";
|
||||||
|
public const string ValuesRequired = Prefix + "ValuesRequired";
|
||||||
|
public const string KeysRequired = Prefix + "KeysRequired";
|
||||||
|
public const string DictionaryNotFound = Prefix + "DictionaryNotFound";
|
||||||
|
public const string RegistrationLimitReached = Prefix + "RegistrationLimitReached";
|
||||||
|
public const string RedirectUriNotAllowed = Prefix + "RedirectUriNotAllowed";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Sozsoft.Platform;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// /mcp ucunda yayinlanan arac adlari. Ayni degerler MCP Servers ekranindaki
|
||||||
|
/// <c>AllowedTools</c> alaninin secenek listesini besler; buraya bir arac eklenirse ekranin
|
||||||
|
/// statik lookup listesi de guncellenir.
|
||||||
|
/// </summary>
|
||||||
|
public static class McpToolNames
|
||||||
|
{
|
||||||
|
public const string ServerInfo = "server_info";
|
||||||
|
public const string ListLists = "list_lists";
|
||||||
|
public const string DescribeList = "describe_list";
|
||||||
|
public const string SearchSchema = "search_schema";
|
||||||
|
public const string QueryList = "query_list";
|
||||||
|
public const string RunSelect = "run_select";
|
||||||
|
public const string ListQueries = "list_queries";
|
||||||
|
public const string RunQuery = "run_query";
|
||||||
|
public const string InsertRow = "insert_row";
|
||||||
|
public const string UpdateRow = "update_row";
|
||||||
|
public const string DeleteRow = "delete_row";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Veri yazan araclar. Sunucu kaydinda <c>AllowWrite</c> kapaliysa bunlar arac listesinde
|
||||||
|
/// hic yer almaz.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string[] WriteTools = [InsertRow, UpdateRow, DeleteRow];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sunucu kaydi olmasa da acik kalan araclar. Istemcinin baglantisinin neden bos
|
||||||
|
/// gorundugunu anlayabilmesi icin gereklidir.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string[] AlwaysAvailableTools = [ServerInfo];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kullanicinin uygulamada da sahip olmasi gereken yetki. MCP yetki vermez: ham SQL'i
|
||||||
|
/// uygulamada SQL Query Manager'i kullanabilen, veri yazmayi yazma yetkisi olan kullanici
|
||||||
|
/// gorur. Listede olmayan araclar kendi icinde liste bazli yetkiyle suzulur.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly IReadOnlyDictionary<string, string> RequiredPermissions = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
[RunSelect] = PlatformConsts.AppCodes.SqlQueryManagers.Default,
|
||||||
|
[InsertRow] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
||||||
|
[UpdateRow] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
||||||
|
[DeleteRow] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>Yayinlanan tum araclar, arac listesindeki siralarina gore.</summary>
|
||||||
|
public static readonly string[] All =
|
||||||
|
[
|
||||||
|
ServerInfo,
|
||||||
|
ListLists,
|
||||||
|
DescribeList,
|
||||||
|
SearchSchema,
|
||||||
|
QueryList,
|
||||||
|
RunSelect,
|
||||||
|
ListQueries,
|
||||||
|
RunQuery,
|
||||||
|
InsertRow,
|
||||||
|
UpdateRow,
|
||||||
|
DeleteRow
|
||||||
|
];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <c>AllowedTools</c> alaninda secilebilen araclar. Her zaman acik olanlar secenek
|
||||||
|
/// olarak sunulmaz; onlari kapatmak mumkun degildir.
|
||||||
|
/// </summary>
|
||||||
|
public static IEnumerable<string> SelectableTools
|
||||||
|
=> All.Where(name => !AlwaysAvailableTools.Contains(name));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bir aracin bir sunucu kaydina gore acik olup olmadigina karar veren kural. Karar iki yerde
|
||||||
|
/// kullanilir: arac listesi suzulurken ve arac cagrilirken. Listeyi suzmek tek basina bir
|
||||||
|
/// yetki siniri degildir — istemci listede olmayan bir araci da cagirabilir.
|
||||||
|
/// </summary>
|
||||||
|
public static class McpToolPolicy
|
||||||
|
{
|
||||||
|
/// <param name="hasServerRecord">Istegin kiracisina dusen aktif bir sunucu kaydi var mi.</param>
|
||||||
|
/// <param name="allowWrite">Sunucu kaydinda veri yazma acik mi.</param>
|
||||||
|
/// <param name="allowedTools">Acik arac adlari; bos liste "yazma disindaki tumu acik" demektir.</param>
|
||||||
|
public static bool IsAllowed(
|
||||||
|
bool hasServerRecord,
|
||||||
|
bool allowWrite,
|
||||||
|
IReadOnlyCollection<string> allowedTools,
|
||||||
|
string toolName)
|
||||||
|
{
|
||||||
|
if (McpToolNames.AlwaysAvailableTools.Contains(toolName, StringComparer.Ordinal))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasServerRecord)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allowWrite && McpToolNames.WriteTools.Contains(toolName, StringComparer.Ordinal))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowedTools.Count == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return allowedTools.Contains(toolName, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyList<string> VisibleTools(
|
||||||
|
bool hasServerRecord,
|
||||||
|
bool allowWrite,
|
||||||
|
IReadOnlyCollection<string> allowedTools)
|
||||||
|
=> [.. McpToolNames.All.Where(name => IsAllowed(hasServerRecord, allowWrite, allowedTools, name))];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<Import Project="..\common.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>Sozsoft.Mcp</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Volo.Abp.Ddd.Domain.Shared" Version="10.0.0" />
|
||||||
|
<PackageReference Include="Volo.Abp.Validation" Version="10.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- Yetki adlari ve tablo adi platformun tek kayit defterinde durur
|
||||||
|
(PlatformConsts.AppCodes, TableNameResolver); modul bunlari kopyalamaz. -->
|
||||||
|
<ProjectReference Include="..\..\..\src\Sozsoft.Platform.Domain.Shared\Sozsoft.Platform.Domain.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Sozsoft.Mcp.Services;
|
||||||
|
using Volo.Abp.Data;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bilinen MCP istemcisini (<see cref="McpConsts.OAuthClientId"/>) seed eder. Geri donus
|
||||||
|
/// adresleri <c>OpenIddict:Applications:Platform_Mcp:RedirectUris</c> ayarindadir; yeni bir
|
||||||
|
/// saglayici eklemek bu listeye callback eklemektir.
|
||||||
|
/// </summary>
|
||||||
|
public class McpOAuthClientDataSeedContributor(
|
||||||
|
IConfiguration configuration,
|
||||||
|
IMcpOAuthClientManager clientManager) : IDataSeedContributor, ITransientDependency
|
||||||
|
{
|
||||||
|
private const string RedirectUrisKey = "OpenIddict:Applications:Platform_Mcp:RedirectUris";
|
||||||
|
|
||||||
|
public async Task SeedAsync(DataSeedContext context)
|
||||||
|
{
|
||||||
|
// OpenIddict uygulamalari host kapsamindadir; kiraci seed turlarinda tekrarlanmaz.
|
||||||
|
if (context.TenantId is not null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var redirectUris = configuration.GetSection(RedirectUrisKey).Get<string[]>();
|
||||||
|
if (redirectUris is null || redirectUris.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await clientManager.EnsureKnownClientAsync(redirectUris);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
using System;
|
||||||
|
using Volo.Abp.Domain.Entities.Auditing;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MCP (Model Context Protocol) sunucu tanimi. Kayit host kapsamindadir; hangi kiracilarin
|
||||||
|
/// kullanabilecegi <see cref="Tenants"/> ile belirlenir. Istegin kiracisina dusen kayit, /mcp
|
||||||
|
/// ucunda hangi araclarin gorunecegini ve sorgularin hangi veri kaynaginda hangi satir
|
||||||
|
/// tavaniyla kosacagini belirler.
|
||||||
|
/// </summary>
|
||||||
|
public class McpServer : FullAuditedEntity<Guid>
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Istemciye verilen baglanti adresi (ornek: https://api.sozsoft.com/mcp). Yonlendirme icin
|
||||||
|
/// kullanilmaz; ekranin kopyalanabilir baglanti komutu uretebilmesi icin tutulur.
|
||||||
|
/// </summary>
|
||||||
|
public string? Url { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kaydin gecerli oldugu kiraci adlari, <c>PlatformConsts.MultiValueDelimiter</c> ile
|
||||||
|
/// ayrilir. Bos ise tum kiracilara aciktir.
|
||||||
|
/// </summary>
|
||||||
|
public string? Tenants { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sorgu araclarinin kosacagi DataSource kodu. Bos ise istegin kapsamindaki varsayilan
|
||||||
|
/// veri kaynagi kullanilir.
|
||||||
|
/// </summary>
|
||||||
|
public string? DataSourceCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Acik arac adlari, <c>PlatformConsts.MultiValueDelimiter</c> ile ayrilir. Bos ise yazma
|
||||||
|
/// disindaki tum araclar aciktir.
|
||||||
|
/// </summary>
|
||||||
|
public string? AllowedTools { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Tek sorgunun dondurebilecegi en fazla satir sayisi.</summary>
|
||||||
|
public int MaxRows { get; set; } = McpConsts.DefaultMaxRows;
|
||||||
|
|
||||||
|
/// <summary>Kapali oldugunda veri yazma araclari arac listesinde hic yer almaz.</summary>
|
||||||
|
public bool AllowWrite { get; set; }
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Volo.Abp.Domain;
|
||||||
|
using Volo.Abp.Modularity;
|
||||||
|
using Volo.Abp.OpenIddict;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
[DependsOn(
|
||||||
|
typeof(AbpDddDomainModule),
|
||||||
|
typeof(AbpOpenIddictDomainModule),
|
||||||
|
typeof(McpDomainSharedModule)
|
||||||
|
)]
|
||||||
|
public class McpDomainModule : AbpModule
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sozsoft.Platform.Entities;
|
||||||
|
using Sozsoft.Platform.Permissions;
|
||||||
|
using Volo.Abp.Authorization.Permissions;
|
||||||
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
using Volo.Abp.Domain.Services;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kullanicinin okuyabildigi listeleri belirler. Karar ekranin kendisiyle aynidir: ListForm'un
|
||||||
|
/// <c>PermissionJson.R</c> yetkisi (bkz. <c>ListFormAuthorizationManager</c>). MCP bu kurala
|
||||||
|
/// ek bir yetki tanimlamaz; uygulamada acamadigin listeyi MCP'de de goremezsin.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpListFormAccessFilter
|
||||||
|
{
|
||||||
|
Task<IReadOnlySet<string>> GetReadableListFormCodesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class McpListFormAccessFilter(
|
||||||
|
IRepository<ListForm, Guid> listFormRepository,
|
||||||
|
IPermissionChecker permissionChecker)
|
||||||
|
: DomainService, IMcpListFormAccessFilter
|
||||||
|
{
|
||||||
|
public async Task<IReadOnlySet<string>> GetReadableListFormCodesAsync()
|
||||||
|
{
|
||||||
|
// ListFormAuthorizationManager.CanAccess liste basina bir sorgu ve bir yetki kontrolu
|
||||||
|
// yapar; sozlukteki yuzlerce liste icin bu tek sorgu + tek toplu kontrole indirilir.
|
||||||
|
var query = await listFormRepository.GetQueryableAsync();
|
||||||
|
var definitions = query
|
||||||
|
.Select(listForm => new { listForm.ListFormCode, listForm.PermissionJson })
|
||||||
|
.ToList()
|
||||||
|
.GroupBy(definition => definition.ListFormCode, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(group => group.First())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var readPermissions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var definition in definitions)
|
||||||
|
{
|
||||||
|
var permission = ReadPermissionOf(definition.PermissionJson);
|
||||||
|
if (permission is not null)
|
||||||
|
{
|
||||||
|
readPermissions[definition.ListFormCode] = permission;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var distinctPermissions = readPermissions.Values.Distinct(StringComparer.Ordinal).ToArray();
|
||||||
|
if (distinctPermissions.Length == 0)
|
||||||
|
{
|
||||||
|
return new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
var grants = await permissionChecker.IsGrantedAsync(distinctPermissions);
|
||||||
|
var granted = grants.Result
|
||||||
|
.Where(result => result.Value == PermissionGrantResult.Granted)
|
||||||
|
.Select(result => result.Key)
|
||||||
|
.ToHashSet(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
return readPermissions
|
||||||
|
.Where(entry => granted.Contains(entry.Value))
|
||||||
|
.Select(entry => entry.Key)
|
||||||
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ReadPermissionOf(string? permissionJson)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(permissionJson))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var permissions = JsonSerializer.Deserialize<PermissionCrudValueObject>(permissionJson);
|
||||||
|
|
||||||
|
return string.IsNullOrWhiteSpace(permissions?.R) ? null : permissions.R;
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
// Bozuk yetki tanimi ekranda da erisimi kapatir (CanAccess false doner).
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,246 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using OpenIddict.Abstractions;
|
||||||
|
using Sozsoft.Platform;
|
||||||
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
using Volo.Abp.Domain.Services;
|
||||||
|
using Volo.Abp.MultiTenancy;
|
||||||
|
using Volo.Abp.OpenIddict.Applications;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uzak MCP istemcilerinin OpenIddict uygulamalari. Iki tur istemci vardir:
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item><b>Bilinen istemci</b> (<see cref="McpConsts.OAuthClientId"/>): geri donus adresleri
|
||||||
|
/// DbMigrator ayarinda sabit olan saglayicilar (Claude, ChatGPT). Onay ekrani gostermez.</item>
|
||||||
|
/// <item><b>Dinamik istemci</b> (<see cref="McpConsts.DynamicClientIdPrefix"/>): baska bir MCP
|
||||||
|
/// istemcisi (Cursor, VS Code, Claude Code, ...) kayit ucundan kendi uygulamasini alir. Kayit
|
||||||
|
/// herkese acik oldugu icin bu istemciler <b>acik onay</b> ister: kullanici giris yaptiktan sonra
|
||||||
|
/// hangi uygulamanin erisim istedigini gorup onaylar.</item>
|
||||||
|
/// </list>
|
||||||
|
/// Iki turde de token kullanicinin kendi yetkileriyle uretilir; istemci yetki kazanmaz.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpOAuthClientManager
|
||||||
|
{
|
||||||
|
/// <summary>Bilinen istemciyi olusturur ya da geri donus adreslerini gunceller.</summary>
|
||||||
|
Task EnsureKnownClientAsync(IReadOnlyCollection<string> redirectUris);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Istemci kaydi. Adresler bilinen istemcide kayitliysa onu, degilse ayni adres kumesine
|
||||||
|
/// sahip dinamik istemciyi dondurur; yoksa yenisini uretir.
|
||||||
|
/// </summary>
|
||||||
|
Task<McpClientRegistration> RegisterAsync(string? clientName, IReadOnlyCollection<string> redirectUris);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class McpOAuthClientManager(
|
||||||
|
IAbpApplicationManager applicationManager,
|
||||||
|
IRepository<OpenIddictApplication, Guid> applicationRepository,
|
||||||
|
ICurrentTenant currentTenant)
|
||||||
|
: DomainService, IMcpOAuthClientManager
|
||||||
|
{
|
||||||
|
private const int MaxClientNameLength = 100;
|
||||||
|
|
||||||
|
/// <summary>Tarayici disi calistirilabilir icerik tasiyan, geri donus adresi olamayacak semalar.</summary>
|
||||||
|
private static readonly string[] ForbiddenSchemes = ["javascript", "data", "file", "vbscript", "about", "blob"];
|
||||||
|
|
||||||
|
public async Task EnsureKnownClientAsync(IReadOnlyCollection<string> redirectUris)
|
||||||
|
{
|
||||||
|
// OpenIddict uygulamalari host kapsamindadir.
|
||||||
|
using (currentTenant.Change(null))
|
||||||
|
{
|
||||||
|
var existing = await applicationManager.FindByClientIdAsync(McpConsts.OAuthClientId);
|
||||||
|
var descriptor = BuildDescriptor(
|
||||||
|
McpConsts.OAuthClientId,
|
||||||
|
"MCP Connectors",
|
||||||
|
redirectUris,
|
||||||
|
OpenIddictConstants.ConsentTypes.Implicit);
|
||||||
|
|
||||||
|
if (existing is null)
|
||||||
|
{
|
||||||
|
await applicationManager.CreateAsync(descriptor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tanim tamamen dosyadan gelir; ayar degistiyse (yeni saglayici callback'i, token omru)
|
||||||
|
// kayit yeniden yazilir. Kimlik ve istemci turu degismez.
|
||||||
|
await applicationManager.PopulateAsync(existing, descriptor);
|
||||||
|
await applicationManager.UpdateAsync(existing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<McpClientRegistration> RegisterAsync(string? clientName, IReadOnlyCollection<string> redirectUris)
|
||||||
|
{
|
||||||
|
if (redirectUris.Count == 0)
|
||||||
|
{
|
||||||
|
return McpClientRegistration.Rejected(McpClientRegistrationError.InvalidRedirectUri, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
var invalid = redirectUris.FirstOrDefault(uri => !IsAcceptableRedirectUri(uri));
|
||||||
|
if (invalid is not null)
|
||||||
|
{
|
||||||
|
return McpClientRegistration.Rejected(McpClientRegistrationError.InvalidRedirectUri, invalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (currentTenant.Change(null))
|
||||||
|
{
|
||||||
|
var known = await applicationManager.FindByClientIdAsync(McpConsts.OAuthClientId);
|
||||||
|
if (known is not null)
|
||||||
|
{
|
||||||
|
var knownUris = await applicationManager.GetRedirectUrisAsync(known);
|
||||||
|
if (redirectUris.All(uri => knownUris.Any(registered => SameUri(registered, uri))))
|
||||||
|
{
|
||||||
|
return McpClientRegistration.Accepted(McpConsts.OAuthClientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dynamicClients = await GetDynamicClientsAsync();
|
||||||
|
|
||||||
|
// Ayni istemci her baglantida yeniden kayit olabilir; ayni adres kumesi tek uygulamaya
|
||||||
|
// baglanir, tablo buyumez.
|
||||||
|
var match = dynamicClients.FirstOrDefault(client => SameUriSet(client.RedirectUris, redirectUris));
|
||||||
|
if (match.ClientId is not null)
|
||||||
|
{
|
||||||
|
return McpClientRegistration.Accepted(match.ClientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dynamicClients.Count >= McpConsts.MaxDynamicClients)
|
||||||
|
{
|
||||||
|
return McpClientRegistration.Rejected(McpClientRegistrationError.LimitReached, McpConsts.MaxDynamicClients.ToString(CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
|
||||||
|
var clientId = McpConsts.DynamicClientIdPrefix + GuidGenerator.Create().ToString("N");
|
||||||
|
await applicationManager.CreateAsync(BuildDescriptor(
|
||||||
|
clientId,
|
||||||
|
DisplayNameOf(clientName, redirectUris.First()),
|
||||||
|
redirectUris,
|
||||||
|
OpenIddictConstants.ConsentTypes.Explicit));
|
||||||
|
|
||||||
|
return McpClientRegistration.Accepted(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<(string? ClientId, IReadOnlyList<string> RedirectUris)>> GetDynamicClientsAsync()
|
||||||
|
{
|
||||||
|
var query = await applicationRepository.GetQueryableAsync();
|
||||||
|
var rows = query
|
||||||
|
.Where(application => application.ClientId!.StartsWith(McpConsts.DynamicClientIdPrefix))
|
||||||
|
.Select(application => new { application.ClientId, application.RedirectUris })
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return [.. rows.Select(row => ((string?)row.ClientId, ParseUris(row.RedirectUris)))];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AbpApplicationDescriptor BuildDescriptor(
|
||||||
|
string clientId,
|
||||||
|
string displayName,
|
||||||
|
IEnumerable<string> redirectUris,
|
||||||
|
string consentType)
|
||||||
|
{
|
||||||
|
var descriptor = new AbpApplicationDescriptor
|
||||||
|
{
|
||||||
|
ClientId = clientId,
|
||||||
|
ClientType = OpenIddictConstants.ClientTypes.Public,
|
||||||
|
ConsentType = consentType,
|
||||||
|
DisplayName = displayName
|
||||||
|
};
|
||||||
|
|
||||||
|
descriptor.Permissions.UnionWith(
|
||||||
|
[
|
||||||
|
OpenIddictConstants.Permissions.Endpoints.Authorization,
|
||||||
|
OpenIddictConstants.Permissions.Endpoints.Token,
|
||||||
|
OpenIddictConstants.Permissions.Endpoints.Revocation,
|
||||||
|
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode,
|
||||||
|
OpenIddictConstants.Permissions.GrantTypes.RefreshToken,
|
||||||
|
OpenIddictConstants.Permissions.ResponseTypes.Code,
|
||||||
|
OpenIddictConstants.Permissions.Prefixes.Scope + PlatformConsts.AppName
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Genel istemci gizli anahtar tasiyamaz; yetkilendirme kodunun calinmasina karsi PKCE
|
||||||
|
// zorunludur.
|
||||||
|
descriptor.Requirements.Add(OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange);
|
||||||
|
|
||||||
|
descriptor.Settings[OpenIddictConstants.Settings.TokenLifetimes.RefreshToken] =
|
||||||
|
McpConsts.RefreshTokenLifetime.ToString("c", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
foreach (var redirectUri in redirectUris)
|
||||||
|
{
|
||||||
|
descriptor.RedirectUris.Add(new Uri(redirectUri, UriKind.Absolute));
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kabul edilen geri donus adresleri: HTTPS, yerel makineye donen HTTP (RFC 8252 §7.3 —
|
||||||
|
/// masaustu istemciler rastgele portla dinler) ve masaustu uygulamalarin ozel semalari
|
||||||
|
/// (RFC 8252 §7.1, ornek: <c>cursor://...</c>). Yerel olmayan duz HTTP reddedilir.
|
||||||
|
/// </summary>
|
||||||
|
private static bool IsAcceptableRedirectUri(string value)
|
||||||
|
{
|
||||||
|
if (!Uri.TryCreate(value, UriKind.Absolute, out var uri) || !string.IsNullOrEmpty(uri.Fragment))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri.Scheme == Uri.UriSchemeHttps)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri.Scheme == Uri.UriSchemeHttp)
|
||||||
|
{
|
||||||
|
return uri.IsLoopback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !ForbiddenSchemes.Contains(uri.Scheme, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Onay ekraninda gorunen ad. Istemci adi kayit isteginden gelir ve dogrulanamaz (herkes
|
||||||
|
/// kendine "Claude" diyebilir); bu yuzden token'in gidecegi adresin sahibi de gosterilir.
|
||||||
|
/// </summary>
|
||||||
|
private static string DisplayNameOf(string? clientName, string redirectUri)
|
||||||
|
{
|
||||||
|
var name = string.IsNullOrWhiteSpace(clientName) ? "MCP Client" : clientName.Trim();
|
||||||
|
if (name.Length > MaxClientNameLength)
|
||||||
|
{
|
||||||
|
name = name[..MaxClientNameLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
var uri = new Uri(redirectUri, UriKind.Absolute);
|
||||||
|
var owner = uri.IsLoopback ? "localhost" : string.IsNullOrEmpty(uri.Host) ? uri.Scheme : uri.Host;
|
||||||
|
|
||||||
|
return $"{name} ({owner})";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<string> ParseUris(string? json)
|
||||||
|
=> string.IsNullOrWhiteSpace(json) ? [] : JsonSerializer.Deserialize<List<string>>(json) ?? [];
|
||||||
|
|
||||||
|
private static bool SameUriSet(IReadOnlyCollection<string> left, IReadOnlyCollection<string> right)
|
||||||
|
=> left.Count == right.Count && right.All(uri => left.Any(candidate => SameUri(candidate, uri)));
|
||||||
|
|
||||||
|
/// <summary>OpenIddict adresleri <see cref="Uri"/> bicimiyle saklar; karsilastirma ayni bicimle yapilir.</summary>
|
||||||
|
private static bool SameUri(string left, string right)
|
||||||
|
=> Uri.TryCreate(left, UriKind.Absolute, out var a)
|
||||||
|
&& Uri.TryCreate(right, UriKind.Absolute, out var b)
|
||||||
|
&& string.Equals(a.AbsoluteUri, b.AbsoluteUri, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum McpClientRegistrationError
|
||||||
|
{
|
||||||
|
InvalidRedirectUri,
|
||||||
|
LimitReached
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Kayit sonucu: basariliysa istemci kimligi, degilse hata ve hataya konu deger.</summary>
|
||||||
|
public sealed record McpClientRegistration(string? ClientId, McpClientRegistrationError? Error, string? ErrorValue)
|
||||||
|
{
|
||||||
|
public static McpClientRegistration Accepted(string clientId) => new(clientId, null, null);
|
||||||
|
|
||||||
|
public static McpClientRegistration Rejected(McpClientRegistrationError error, string value) => new(null, error, value);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sozsoft.Platform.Data.Seeds;
|
||||||
|
using Sozsoft.Platform.DynamicData;
|
||||||
|
using Sozsoft.Platform.Enums;
|
||||||
|
using Volo.Abp.Domain.Services;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sorguyu istegin kapsamindaki veri kaynaginda kosturur ve satir tavanini uygular.
|
||||||
|
/// Veri kaynagi cozumu tek yerde kalsin diye burada toplanmistir.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpQueryExecutor
|
||||||
|
{
|
||||||
|
/// <param name="dataSourceCode">
|
||||||
|
/// Sunucu kaydindaki kod. Bos ya da <c>!Tenant</c> ise veri kaynagi istegin kiracisindan
|
||||||
|
/// cozulur; host baglaminda varsayilan kaynaga duser.
|
||||||
|
/// </param>
|
||||||
|
Task<McpDataSourceContext> ResolveDataSourceAsync(string? dataSourceCode);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sorguyu kosturur. Sonuc kumesi <paramref name="maxRows"/> satirda kesilir; kesilme
|
||||||
|
/// olduysa sonuc bunu bildirir.
|
||||||
|
/// </summary>
|
||||||
|
Task<McpQueryRows> ExecuteAsync(
|
||||||
|
McpDataSourceContext context,
|
||||||
|
string sql,
|
||||||
|
IReadOnlyDictionary<string, object> parameters,
|
||||||
|
int maxRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class McpQueryExecutor(IDynamicDataManager dynamicDataManager) : DomainService, IMcpQueryExecutor
|
||||||
|
{
|
||||||
|
public async Task<McpDataSourceContext> ResolveDataSourceAsync(string? dataSourceCode)
|
||||||
|
{
|
||||||
|
var useTenantScope = string.IsNullOrWhiteSpace(dataSourceCode)
|
||||||
|
|| dataSourceCode == McpConsts.TenantDataSourceMarker;
|
||||||
|
|
||||||
|
var resolvedCode = useTenantScope
|
||||||
|
? SeedConsts.DataSources.DefaultCode
|
||||||
|
: dataSourceCode!;
|
||||||
|
|
||||||
|
var (repository, connectionString, dataSourceType) =
|
||||||
|
await dynamicDataManager.GetAsync(useTenantScope, resolvedCode);
|
||||||
|
|
||||||
|
return new McpDataSourceContext(repository, connectionString, dataSourceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<McpQueryRows> ExecuteAsync(
|
||||||
|
McpDataSourceContext context,
|
||||||
|
string sql,
|
||||||
|
IReadOnlyDictionary<string, object> parameters,
|
||||||
|
int maxRows)
|
||||||
|
{
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
var rows = await context.Repository.QueryAsync(
|
||||||
|
sql,
|
||||||
|
context.ConnectionString,
|
||||||
|
parameters.ToDictionary(entry => entry.Key, entry => entry.Value));
|
||||||
|
stopwatch.Stop();
|
||||||
|
|
||||||
|
var columns = new List<string>();
|
||||||
|
var materialized = new List<Dictionary<string, object?>>();
|
||||||
|
var truncated = false;
|
||||||
|
|
||||||
|
foreach (var row in rows)
|
||||||
|
{
|
||||||
|
if (materialized.Count >= maxRows)
|
||||||
|
{
|
||||||
|
truncated = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row is not IDictionary<string, object> values)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columns.Count == 0)
|
||||||
|
{
|
||||||
|
columns = [.. values.Keys];
|
||||||
|
}
|
||||||
|
|
||||||
|
materialized.Add(values.ToDictionary(entry => entry.Key, entry => (object?)entry.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new McpQueryRows(columns, materialized, truncated, stopwatch.ElapsedMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Cozumlenmis veri kaynagi: hangi depo, hangi baglanti, hangi saglayici.</summary>
|
||||||
|
public sealed record McpDataSourceContext(
|
||||||
|
IDynamicDataRepository Repository,
|
||||||
|
string ConnectionString,
|
||||||
|
DataSourceTypeEnum DataSourceType);
|
||||||
|
|
||||||
|
/// <summary>Sorgu sonucunun ham hali.</summary>
|
||||||
|
public sealed record McpQueryRows(
|
||||||
|
List<string> Columns,
|
||||||
|
List<Dictionary<string, object?>> Rows,
|
||||||
|
bool Truncated,
|
||||||
|
long ExecutionTimeMs);
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sozsoft.Platform.Entities;
|
||||||
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
using Volo.Abp.Domain.Services;
|
||||||
|
using Volo.Abp.MultiTenancy;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sema bilgisinin tek kaynagi: <c>Sas_H_DbTableColumn</c> tablosundaki JSON veri sozlugu.
|
||||||
|
/// Sozluk kullaniciya gorunen basliklari ve lookup iliskilerini birlikte tasidigi icin canli
|
||||||
|
/// katalog yerine bu kullanilir — modelin gordugu adlar ekranda gorunen adlarla ayni olur.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpSchemaDictionary
|
||||||
|
{
|
||||||
|
/// <summary>Sozlugun bulunup bulunmadigi ve ne zaman tazelendigi; yoksa null.</summary>
|
||||||
|
Task<McpDictionarySnapshot?> GetSnapshotAsync();
|
||||||
|
|
||||||
|
/// <summary>Sozlukteki listeler; sozluk yoksa bos liste.</summary>
|
||||||
|
Task<IReadOnlyList<McpDictionaryListForm>> GetListFormsAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Istegin kapsamina dusen sozluk kiraci adi. Sozluk ureten prosedurun
|
||||||
|
/// <c>@TenantName</c> parametresiyle ayni degeri tasimasi gerekir.
|
||||||
|
/// </summary>
|
||||||
|
string ResolveTenantName();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aksan ve buyuk/kucuk harf duyarsiz karsilastirma anahtari; "Musteri" aramasi
|
||||||
|
/// "Müşteri" basligini da bulur.
|
||||||
|
/// </summary>
|
||||||
|
string Normalize(string? value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class McpSchemaDictionary(
|
||||||
|
IRepository<DbTableColumn, Guid> dictionaryRepository,
|
||||||
|
ICurrentTenant currentTenant)
|
||||||
|
: DomainService, IMcpSchemaDictionary
|
||||||
|
{
|
||||||
|
private static readonly JsonSerializerOptions DictionaryJsonOptions = new()
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly CultureInfo TurkishCulture = CultureInfo.GetCultureInfo("tr-TR");
|
||||||
|
|
||||||
|
public string ResolveTenantName()
|
||||||
|
=> currentTenant.Name ?? McpConsts.DefaultDictionaryTenantName;
|
||||||
|
|
||||||
|
public async Task<McpDictionarySnapshot?> GetSnapshotAsync()
|
||||||
|
{
|
||||||
|
// Tazelik bilgisi icin JsonData (buyuk sozluklerde megabaytlar) okunmaz.
|
||||||
|
using (currentTenant.Change(null))
|
||||||
|
{
|
||||||
|
var query = await ActiveRowsAsync(ResolveTenantName());
|
||||||
|
|
||||||
|
return query
|
||||||
|
.Select(row => new McpDictionarySnapshot(
|
||||||
|
row.CacheKey,
|
||||||
|
row.RefreshedAt,
|
||||||
|
row.FormCount,
|
||||||
|
row.FieldCount,
|
||||||
|
row.TenantName,
|
||||||
|
row.DefinitionCultureName,
|
||||||
|
row.UiCultureName))
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<McpDictionaryListForm>> GetListFormsAsync()
|
||||||
|
{
|
||||||
|
var json = await GetJsonDataAsync();
|
||||||
|
if (string.IsNullOrWhiteSpace(json))
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var document = JsonSerializer.Deserialize<DictionaryDocument>(json, DictionaryJsonOptions);
|
||||||
|
|
||||||
|
return document?.ListForms ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Normalize(string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var decomposed = value.ToLower(TurkishCulture).Normalize(NormalizationForm.FormD);
|
||||||
|
var builder = new StringBuilder(decomposed.Length);
|
||||||
|
|
||||||
|
foreach (var character in decomposed)
|
||||||
|
{
|
||||||
|
if (CharUnicodeInfo.GetUnicodeCategory(character) == UnicodeCategory.NonSpacingMark)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append(character switch
|
||||||
|
{
|
||||||
|
'ı' => 'i',
|
||||||
|
'ğ' => 'g',
|
||||||
|
'ş' => 's',
|
||||||
|
_ => character
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string?> GetJsonDataAsync()
|
||||||
|
{
|
||||||
|
// Sozluk host kapsamli bir tabloda tutulur; kiraci ayrimi TenantName kolonundadir.
|
||||||
|
using (currentTenant.Change(null))
|
||||||
|
{
|
||||||
|
var query = await ActiveRowsAsync(ResolveTenantName());
|
||||||
|
|
||||||
|
return query.Select(row => row.JsonData).FirstOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IQueryable<DbTableColumn>> ActiveRowsAsync(string tenantName)
|
||||||
|
{
|
||||||
|
var query = await dictionaryRepository.GetQueryableAsync();
|
||||||
|
|
||||||
|
return query
|
||||||
|
.Where(row => row.DictionaryType == McpConsts.DictionaryType
|
||||||
|
&& row.IsActive
|
||||||
|
&& row.TenantName == tenantName)
|
||||||
|
.OrderByDescending(row => row.RefreshedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Sozluk JSON'unun kok nesnesi.</summary>
|
||||||
|
private sealed class DictionaryDocument
|
||||||
|
{
|
||||||
|
public List<McpDictionaryListForm>? ListForms { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Sozluk satirinin tazelik bilgisi.</summary>
|
||||||
|
public sealed record McpDictionarySnapshot(
|
||||||
|
string? CacheKey,
|
||||||
|
DateTime RefreshedAt,
|
||||||
|
int FormCount,
|
||||||
|
int FieldCount,
|
||||||
|
string? TenantName,
|
||||||
|
string? DefinitionCultureName,
|
||||||
|
string? UiCultureName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sozlukteki bir liste. Alan adlari <c>dbo.Sas_H_ListFormFieldsDictionaryJson</c> prosedurunun
|
||||||
|
/// urettigi JSON ile birebir ayni olmak zorundadir.
|
||||||
|
/// </summary>
|
||||||
|
public class McpDictionaryListForm
|
||||||
|
{
|
||||||
|
public string? ListFormCode { get; set; }
|
||||||
|
public string? DisplayName { get; set; }
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public string? TableName { get; set; }
|
||||||
|
public string? SqlSourceName { get; set; }
|
||||||
|
public string? SourceKind { get; set; }
|
||||||
|
public string? KeyFieldName { get; set; }
|
||||||
|
public string? SearchText { get; set; }
|
||||||
|
public List<McpDictionaryField>? Fields { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Sozlukteki bir sutun.</summary>
|
||||||
|
public class McpDictionaryField
|
||||||
|
{
|
||||||
|
public string? Field { get; set; }
|
||||||
|
public string? DisplayName { get; set; }
|
||||||
|
public string? SqlDataType { get; set; }
|
||||||
|
public bool IsVisible { get; set; }
|
||||||
|
public bool IsBooleanColumn { get; set; }
|
||||||
|
public string? LookupTableName { get; set; }
|
||||||
|
public string? LookupValueColumn { get; set; }
|
||||||
|
public string? LookupDisplayColumn { get; set; }
|
||||||
|
public bool IsSensitive { get; set; }
|
||||||
|
public bool IsDefaultSelectColumn { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sozsoft.Mcp.Entities;
|
||||||
|
using Sozsoft.Platform;
|
||||||
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
using Volo.Abp.Domain.Services;
|
||||||
|
using Volo.Abp.MultiTenancy;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Istegin kiracisina dusen etkin sunucu kaydini cozer.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpServerConfigProvider
|
||||||
|
{
|
||||||
|
/// <summary>Etkin kayit; kiraciya acik aktif bir kayit yoksa null.</summary>
|
||||||
|
Task<McpServer?> GetEffectiveServerAsync();
|
||||||
|
|
||||||
|
/// <summary>Coklu deger tasiyan kolonu (kiraci/arac listesi) ogelerine ayirir.</summary>
|
||||||
|
IReadOnlyList<string> SplitMultiValue(string? value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class McpServerConfigProvider(
|
||||||
|
IRepository<McpServer, Guid> serverRepository,
|
||||||
|
ICurrentTenant currentTenant)
|
||||||
|
: DomainService, IMcpServerConfigProvider
|
||||||
|
{
|
||||||
|
public async Task<McpServer?> GetEffectiveServerAsync()
|
||||||
|
{
|
||||||
|
var tenantName = currentTenant.Name;
|
||||||
|
|
||||||
|
// Sunucu tanimlari host kapsaminda yasar; kiraci baglamindaki istek de ayni tabloyu
|
||||||
|
// okur, hangi kiracilara acik oldugu Tenants kolonunda durur.
|
||||||
|
List<McpServer> servers;
|
||||||
|
using (currentTenant.Change(null))
|
||||||
|
{
|
||||||
|
var query = await serverRepository.GetQueryableAsync();
|
||||||
|
servers = [.. query.Where(server => server.IsActive).OrderBy(server => server.Name)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers.Find(server => IsAvailableForTenant(server.Tenants, tenantName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<string> SplitMultiValue(string? value)
|
||||||
|
=> string.IsNullOrWhiteSpace(value)
|
||||||
|
? []
|
||||||
|
: [.. value.Split(
|
||||||
|
PlatformConsts.MultiValueDelimiter,
|
||||||
|
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kaydin kiraci listesi bos ise tum kiracilara aciktir; doluysa kiraci adi listede
|
||||||
|
/// gecmelidir (AiBot ekraniyla ayni sozlesme).
|
||||||
|
/// </summary>
|
||||||
|
private bool IsAvailableForTenant(string? tenants, string? tenantName)
|
||||||
|
{
|
||||||
|
var allowed = SplitMultiValue(tenants);
|
||||||
|
if (allowed.Count == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tenantName is not null
|
||||||
|
&& allowed.Any(name => string.Equals(name, tenantName, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
using Sozsoft.Platform.Enums;
|
||||||
|
using Sozsoft.Platform.Localization;
|
||||||
|
using Volo.Abp;
|
||||||
|
using Volo.Abp.Domain.Services;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Salt-okunur sorgu kurali. Hem serbest SELECT hem kayitli nesne cagrisi buradan gecer:
|
||||||
|
/// ifadenin gercekten okuma oldugu dogrulanir, tanimlayici ve parametre adlari kalibi gecer,
|
||||||
|
/// satir tavani sorguya yazilir.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpSqlGuard
|
||||||
|
{
|
||||||
|
/// <summary>Tek bir okuma ifadesi oldugunu dogrular; degilse anlasilir bir hata atar.</summary>
|
||||||
|
void EnsureReadOnlyStatement(string sql);
|
||||||
|
|
||||||
|
/// <summary>Sema/nesne adini dogrular ve saglayiciya gore tirnaklar.</summary>
|
||||||
|
string QuoteQualifiedName(string schemaName, string objectName, DataSourceTypeEnum dataSourceType);
|
||||||
|
|
||||||
|
/// <summary>Saglayicinin varsayilan semasi.</summary>
|
||||||
|
string DefaultSchemaName(DataSourceTypeEnum dataSourceType);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Satir tavanini sorgunun kendisine yazar: SQL Server'da <c>TOP (n)</c>, PostgreSQL'de
|
||||||
|
/// <c>LIMIT n</c>. Sorgu kendi sinirini zaten tasiyorsa dokunulmaz.
|
||||||
|
/// </summary>
|
||||||
|
string ApplyRowLimit(string sql, int maxRows, DataSourceTypeEnum dataSourceType);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Istemciden gelen parametre adlarini dogrular ve bastaki @ isaretini duserek Dapper'in
|
||||||
|
/// bekledigi bicime cevirir.
|
||||||
|
/// </summary>
|
||||||
|
Dictionary<string, object> NormalizeParameters(IReadOnlyDictionary<string, object?>? parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class McpSqlGuard(IStringLocalizer<PlatformResource> localizer) : DomainService, IMcpSqlGuard
|
||||||
|
{
|
||||||
|
/// <summary>Tek parcali SQL tanimlayicisi; sema ve nesne adlari bu kalibi gecmek zorundadir.</summary>
|
||||||
|
private static readonly Regex SafeIdentifierRegex = new(
|
||||||
|
@"^[A-Za-z_][A-Za-z0-9_]{0,127}$",
|
||||||
|
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
|
private static readonly Regex SafeParameterNameRegex = new(
|
||||||
|
@"^@?[A-Za-z_][A-Za-z0-9_]{0,127}$",
|
||||||
|
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
|
private static readonly Regex SqlCommentRegex = new(
|
||||||
|
@"--[^\r\n]*|/\*[\s\S]*?\*/",
|
||||||
|
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
|
private static readonly Regex SqlStringLiteralRegex = new(
|
||||||
|
@"'(?:[^']|'')*'",
|
||||||
|
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Okuma sozde kalmasi gereken ifadelerde yasakli anahtar kelimeler. Kontrol yorumlar ve
|
||||||
|
/// metin sabitleri cikarildiktan sonra yapilir; aksi halde bir yorum blogu ya da tirnak
|
||||||
|
/// icindeki metin yasakli bir kelimeyi gizleyebilir.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Regex ForbiddenStatementRegex = new(
|
||||||
|
@"\b(INSERT|UPDATE|DELETE|MERGE|UPSERT|CREATE|ALTER|DROP|TRUNCATE|GRANT|REVOKE|DENY|BACKUP|RESTORE|SHUTDOWN|RECONFIGURE|EXEC|EXECUTE|WAITFOR|OPENROWSET|OPENDATASOURCE|OPENQUERY|BULK|INTO)\b",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
|
private static readonly Regex SelectPrefixRegex = new(
|
||||||
|
@"^\s*SELECT\s+(?:(?:DISTINCT|ALL)\s+)?",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
|
private static readonly Regex ExistingTopRegex = new(
|
||||||
|
@"^\s*SELECT\s+(?:(?:DISTINCT|ALL)\s+)?TOP\s*[(\s]",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
|
private static readonly Regex ExistingLimitRegex = new(
|
||||||
|
@"\bLIMIT\s+\d+",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
|
public void EnsureReadOnlyStatement(string sql)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(sql))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(localizer[McpErrorCodes.EmptyQuery]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var stripped = SqlStringLiteralRegex.Replace(SqlCommentRegex.Replace(sql, " "), "''");
|
||||||
|
|
||||||
|
if (stripped.Contains(';', StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(localizer[McpErrorCodes.SingleStatementOnly]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var trimmed = stripped.TrimStart();
|
||||||
|
var startsWithRead = trimmed.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| trimmed.StartsWith("WITH", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (!startsWithRead)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(localizer[McpErrorCodes.ReadOnlyStatementRequired]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var forbidden = ForbiddenStatementRegex.Match(stripped);
|
||||||
|
if (forbidden.Success)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(
|
||||||
|
localizer[McpErrorCodes.ForbiddenKeyword, forbidden.Value.ToUpperInvariant()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string QuoteQualifiedName(string schemaName, string objectName, DataSourceTypeEnum dataSourceType)
|
||||||
|
{
|
||||||
|
EnsureSafeIdentifier(schemaName);
|
||||||
|
EnsureSafeIdentifier(objectName);
|
||||||
|
|
||||||
|
return dataSourceType == DataSourceTypeEnum.Postgresql
|
||||||
|
? $"\"{schemaName}\".\"{objectName}\""
|
||||||
|
: $"[{schemaName}].[{objectName}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DefaultSchemaName(DataSourceTypeEnum dataSourceType)
|
||||||
|
=> dataSourceType == DataSourceTypeEnum.Postgresql ? "public" : "dbo";
|
||||||
|
|
||||||
|
public string ApplyRowLimit(string sql, int maxRows, DataSourceTypeEnum dataSourceType)
|
||||||
|
{
|
||||||
|
if (dataSourceType == DataSourceTypeEnum.Postgresql)
|
||||||
|
{
|
||||||
|
return ExistingLimitRegex.IsMatch(sql) ? sql : $"{sql} LIMIT {maxRows}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ExistingTopRegex.IsMatch(sql))
|
||||||
|
{
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
var match = SelectPrefixRegex.Match(sql);
|
||||||
|
|
||||||
|
// WITH ile baslayan ya da beklenmeyen bicimdeki sorgularda sarmalama yapilmaz: SQL
|
||||||
|
// Server'da ORDER BY tasiyan bir alt sorgu gecersizdir. Sonuc kumesi okunurken yine
|
||||||
|
// maxRows satirda kesilir.
|
||||||
|
return match.Success
|
||||||
|
? string.Concat(sql.AsSpan(0, match.Length), $"TOP ({maxRows}) ", sql.AsSpan(match.Length))
|
||||||
|
: sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, object> NormalizeParameters(IReadOnlyDictionary<string, object?>? parameters)
|
||||||
|
{
|
||||||
|
var normalized = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
if (parameters is null)
|
||||||
|
{
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (key, value) in parameters)
|
||||||
|
{
|
||||||
|
if (key is null || !SafeParameterNameRegex.IsMatch(key))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(localizer[McpErrorCodes.InvalidParameterName, key ?? string.Empty]);
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized[key.TrimStart('@')] = value!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureSafeIdentifier(string identifier)
|
||||||
|
{
|
||||||
|
if (!SafeIdentifierRegex.IsMatch(identifier ?? string.Empty))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(localizer[McpErrorCodes.InvalidIdentifier, identifier ?? string.Empty]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<Import Project="..\common.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>Sozsoft.Mcp</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="10.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Sozsoft.Mcp.Domain.Shared\Sozsoft.Mcp.Domain.Shared.csproj" />
|
||||||
|
<!-- Veri sozlugu (DbTableColumn) ve dinamik veri erisimi platform domain'inde yasar. -->
|
||||||
|
<ProjectReference Include="..\..\..\src\Sozsoft.Platform.Domain\Sozsoft.Platform.Domain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Sozsoft.Mcp.Entities;
|
||||||
|
using Volo.Abp.Data;
|
||||||
|
using Volo.Abp.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.EntityFrameworkCore;
|
||||||
|
|
||||||
|
[ConnectionStringName(McpDbProperties.ConnectionStringName)]
|
||||||
|
public class McpDbContext(DbContextOptions<McpDbContext> options)
|
||||||
|
: AbpDbContext<McpDbContext>(options)
|
||||||
|
{
|
||||||
|
public DbSet<McpServer> McpServers { get; set; } = null!;
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
|
{
|
||||||
|
base.OnModelCreating(builder);
|
||||||
|
|
||||||
|
builder.ConfigureMcp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Sozsoft.Mcp.Entities;
|
||||||
|
using Sozsoft.Platform.Enums;
|
||||||
|
using Volo.Abp;
|
||||||
|
using Volo.Abp.EntityFrameworkCore.Modeling;
|
||||||
|
using static Sozsoft.Platform.PlatformConsts;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.EntityFrameworkCore;
|
||||||
|
|
||||||
|
public static class McpDbContextModelCreatingExtensions
|
||||||
|
{
|
||||||
|
public static void ConfigureMcp(this ModelBuilder builder)
|
||||||
|
{
|
||||||
|
Check.NotNull(builder, nameof(builder));
|
||||||
|
|
||||||
|
builder.Entity<McpServer>(b =>
|
||||||
|
{
|
||||||
|
// Tablo adi platformun tek kayit defterinden gelir (TableNameEnum.McpServer);
|
||||||
|
// modul ikinci bir adlandirma kurali tanimlamaz.
|
||||||
|
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.McpServer)), Prefix.DbSchema);
|
||||||
|
b.ConfigureByConvention();
|
||||||
|
|
||||||
|
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
|
||||||
|
b.Property(x => x.Url).HasMaxLength(512);
|
||||||
|
// Kiraci ve arac listeleri tek kolonda MultiValueDelimiter ile saklanir; ekranda
|
||||||
|
// dxTagBox ile doldurulur.
|
||||||
|
b.Property(x => x.Tenants).HasMaxLength(2048);
|
||||||
|
b.Property(x => x.AllowedTools).HasMaxLength(1024);
|
||||||
|
b.Property(x => x.DataSourceCode).HasMaxLength(64);
|
||||||
|
b.Property(x => x.MaxRows).HasDefaultValue(McpConsts.DefaultMaxRows);
|
||||||
|
b.Property(x => x.AllowWrite).HasDefaultValue(false);
|
||||||
|
b.Property(x => x.Description).HasMaxLength(1024);
|
||||||
|
|
||||||
|
b.HasIndex(x => new { x.Name }).IsUnique().HasFilter("[IsDeleted] = 0");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Sozsoft.Mcp.EntityFrameworkCore;
|
||||||
|
|
||||||
|
public static class McpDbProperties
|
||||||
|
{
|
||||||
|
public const string ConnectionStringName = "Mcp";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Volo.Abp.EntityFrameworkCore;
|
||||||
|
using Volo.Abp.Modularity;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.EntityFrameworkCore;
|
||||||
|
|
||||||
|
[DependsOn(
|
||||||
|
typeof(McpDomainModule),
|
||||||
|
typeof(AbpEntityFrameworkCoreModule)
|
||||||
|
)]
|
||||||
|
public class McpEntityFrameworkCoreModule : AbpModule
|
||||||
|
{
|
||||||
|
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||||
|
{
|
||||||
|
context.Services.AddAbpDbContext<McpDbContext>(options =>
|
||||||
|
{
|
||||||
|
options.AddDefaultRepositories(includeAllEntities: true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<Import Project="..\common.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>Sozsoft.Mcp</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Volo.Abp.EntityFrameworkCore" Version="10.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Sozsoft.Mcp.Domain\Sozsoft.Mcp.Domain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
using Volo.Abp.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Controllers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uzak MCP istemcilerinin baglanti kesfi. Istemci /mcp'den 401 alir, <c>WWW-Authenticate</c>
|
||||||
|
/// basligindaki meta veri adresinden yetkilendirme sunucusunu ogrenir, kayit ucundan istemci
|
||||||
|
/// kimligini alir ve kullaniciyi platformun giris ekranina yonlendirir.
|
||||||
|
/// Adresler ve yanit bicimi RFC'lerle sabittir; bu yuzden ABP'nin konvansiyonel uclari yerine
|
||||||
|
/// acik rotali bir controller kullanilir.
|
||||||
|
/// </summary>
|
||||||
|
[AllowAnonymous]
|
||||||
|
[IgnoreAntiforgeryToken]
|
||||||
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
|
public class McpOAuthController(
|
||||||
|
IOptions<McpOAuthOptions> options,
|
||||||
|
IMcpOAuthAppService oAuthAppService) : AbpControllerBase
|
||||||
|
{
|
||||||
|
/// <summary>Korumali kaynak meta verisi (RFC 9728).</summary>
|
||||||
|
[HttpGet(McpConsts.ProtectedResourceMetadataPath)]
|
||||||
|
[HttpGet(McpConsts.ProtectedResourceMetadataPath + McpConsts.EndpointPath)]
|
||||||
|
public IActionResult GetProtectedResourceMetadata()
|
||||||
|
{
|
||||||
|
var value = options.Value;
|
||||||
|
|
||||||
|
return new JsonResult(new ProtectedResourceMetadata(
|
||||||
|
value.ResourceUrl,
|
||||||
|
[value.AuthorizationServer],
|
||||||
|
McpConsts.OAuthScopes,
|
||||||
|
["header"],
|
||||||
|
McpServerInfoDefaults.DisplayName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Dinamik istemci kaydi (RFC 7591).</summary>
|
||||||
|
[HttpPost(McpConsts.RegistrationEndpointPath)]
|
||||||
|
public async Task<IActionResult> RegisterAsync([FromBody] ClientRegistrationRequest request)
|
||||||
|
{
|
||||||
|
var result = await oAuthAppService.RegisterClientAsync(new McpClientRegistrationRequestDto
|
||||||
|
{
|
||||||
|
ClientName = request.ClientName,
|
||||||
|
RedirectUris = request.RedirectUris ?? []
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.Error is not null)
|
||||||
|
{
|
||||||
|
return new JsonResult(new ClientRegistrationError(result.Error, result.ErrorDescription))
|
||||||
|
{
|
||||||
|
StatusCode = StatusCodes.Status400BadRequest
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonResult(new ClientRegistrationResponse(
|
||||||
|
result.ClientId!,
|
||||||
|
result.ClientIdIssuedAt,
|
||||||
|
result.ClientName,
|
||||||
|
result.RedirectUris,
|
||||||
|
result.GrantTypes,
|
||||||
|
result.ResponseTypes,
|
||||||
|
result.TokenEndpointAuthMethod,
|
||||||
|
result.Scope))
|
||||||
|
{
|
||||||
|
StatusCode = StatusCodes.Status201Created
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protokol sozlesmeleri snake_case alan adlariyla sabittir; ABP'nin camelCase JSON
|
||||||
|
// politikasina birakilmaz.
|
||||||
|
|
||||||
|
public sealed record ClientRegistrationRequest(
|
||||||
|
[property: JsonPropertyName("client_name")] string? ClientName,
|
||||||
|
[property: JsonPropertyName("redirect_uris")] List<string>? RedirectUris);
|
||||||
|
|
||||||
|
private sealed record ClientRegistrationResponse(
|
||||||
|
[property: JsonPropertyName("client_id")] string ClientId,
|
||||||
|
[property: JsonPropertyName("client_id_issued_at")] long ClientIdIssuedAt,
|
||||||
|
[property: JsonPropertyName("client_name")] string? ClientName,
|
||||||
|
[property: JsonPropertyName("redirect_uris")] IReadOnlyList<string> RedirectUris,
|
||||||
|
[property: JsonPropertyName("grant_types")] IReadOnlyList<string> GrantTypes,
|
||||||
|
[property: JsonPropertyName("response_types")] IReadOnlyList<string> ResponseTypes,
|
||||||
|
[property: JsonPropertyName("token_endpoint_auth_method")] string TokenEndpointAuthMethod,
|
||||||
|
[property: JsonPropertyName("scope")] string? Scope);
|
||||||
|
|
||||||
|
private sealed record ClientRegistrationError(
|
||||||
|
[property: JsonPropertyName("error")] string Error,
|
||||||
|
[property: JsonPropertyName("error_description")] string? ErrorDescription);
|
||||||
|
|
||||||
|
private sealed record ProtectedResourceMetadata(
|
||||||
|
[property: JsonPropertyName("resource")] string Resource,
|
||||||
|
[property: JsonPropertyName("authorization_servers")] IReadOnlyList<string> AuthorizationServers,
|
||||||
|
[property: JsonPropertyName("scopes_supported")] IReadOnlyList<string> ScopesSupported,
|
||||||
|
[property: JsonPropertyName("bearer_methods_supported")] IReadOnlyList<string> BearerMethodsSupported,
|
||||||
|
[property: JsonPropertyName("resource_name")] string ResourceName);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// /mcp'den donen 401 yanitina korumali kaynak meta verisinin adresini ekler (RFC 9728 §5.1).
|
||||||
|
/// Uzak MCP istemcisi OAuth akisini bu basliktan baslatir; baslik yoksa baglanti formu
|
||||||
|
/// "kimlik dogrulama desteklenmiyor" diye durur. Kimlik dogrulamadan once calismalidir:
|
||||||
|
/// basligi yanit baslamadan hemen once yazar, 401'i hangi katman uretmis olursa olsun.
|
||||||
|
/// </summary>
|
||||||
|
public class McpAuthenticationChallengeMiddleware(IOptions<McpOAuthOptions> options)
|
||||||
|
: IMiddleware, ITransientDependency
|
||||||
|
{
|
||||||
|
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||||
|
{
|
||||||
|
if (context.Request.Path.StartsWithSegments(McpConsts.EndpointPath))
|
||||||
|
{
|
||||||
|
context.Response.OnStarting(() =>
|
||||||
|
{
|
||||||
|
if (context.Response.StatusCode == StatusCodes.Status401Unauthorized)
|
||||||
|
{
|
||||||
|
context.Response.Headers[HeaderNames.WWWAuthenticate] =
|
||||||
|
$"Bearer resource_metadata=\"{options.Value.ProtectedResourceMetadataUrl}\", " +
|
||||||
|
$"scope=\"{string.Join(' ', McpConsts.OAuthScopes)}\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await next(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using ModelContextProtocol.AspNetCore;
|
||||||
|
using Sozsoft.Mcp.Tools;
|
||||||
|
using Volo.Abp.AspNetCore.Mvc;
|
||||||
|
using Volo.Abp.Modularity;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// /mcp ucunun sunum katmani. Araclar yalnizca uygulama sozlesmesini tanir; kimlik dogrulama,
|
||||||
|
/// kiraci cozumu, unit of work ve yetki kontrolu istegin gectigi ABP ardisik duzeninden gelir.
|
||||||
|
/// Ucun kendisi host tarafinda <c>MapMcp</c> ile baglanir.
|
||||||
|
/// </summary>
|
||||||
|
[DependsOn(
|
||||||
|
typeof(McpApplicationContractsModule),
|
||||||
|
typeof(AbpAspNetCoreMvcModule)
|
||||||
|
)]
|
||||||
|
public class McpHttpApiModule : AbpModule
|
||||||
|
{
|
||||||
|
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||||
|
{
|
||||||
|
var configuration = context.Services.GetConfiguration();
|
||||||
|
|
||||||
|
Configure<McpOAuthOptions>(options =>
|
||||||
|
{
|
||||||
|
options.BaseUrl = (configuration["App:SelfUrl"] ?? string.Empty).TrimEnd('/');
|
||||||
|
options.AuthorizationServer = (configuration["AuthServer:Authority"] ?? string.Empty).TrimEnd('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
context.Services
|
||||||
|
.AddMcpServer(options =>
|
||||||
|
{
|
||||||
|
options.ServerInfo = new() { Name = McpServerInfoDefaults.Name, Version = McpServerInfoDefaults.Version };
|
||||||
|
options.ServerInstructions = McpServerInfoDefaults.Instructions;
|
||||||
|
})
|
||||||
|
.WithHttpTransport(options =>
|
||||||
|
{
|
||||||
|
// Durumsuz kip: her istek kendi ABP kapsaminda kosar, boylece kimlik, kiraci
|
||||||
|
// cozumu ve unit of work istekle birlikte dogru kurulur. Durumlu kipte oturum
|
||||||
|
// kapsami initialize istegine baglanir ve sonraki cagrilar yanlis kiracinin
|
||||||
|
// baglaminda kalabilir; ayrica ters vekil arkasinda oturum yapisakligi gerekmez.
|
||||||
|
options.SessionMode = HttpServerSessionMode.Stateless;
|
||||||
|
})
|
||||||
|
.WithTools<McpSchemaTools>()
|
||||||
|
.WithTools<McpQueryTools>()
|
||||||
|
.WithTools<McpDataTools>()
|
||||||
|
.WithRequestFilters(filters => filters.AddListToolsFilter(next => async (requestContext, cancellationToken) =>
|
||||||
|
{
|
||||||
|
var result = await next(requestContext, cancellationToken);
|
||||||
|
|
||||||
|
// Arac listesi kiracinin sunucu kaydina ve kullanicinin uygulamadaki yetkilerine
|
||||||
|
// gore suzulur. Cagri aninda da ayni kural uygulanir
|
||||||
|
// (McpAppService.EnsureToolAllowedAsync): istemci listede olmayan bir araci da
|
||||||
|
// cagirabilir, suzme tek basina bir yetki siniri degildir.
|
||||||
|
var schemaAppService = requestContext.Services!.GetRequiredService<IMcpSchemaAppService>();
|
||||||
|
var available = (await schemaAppService.GetAvailableToolsAsync()).ToHashSet(System.StringComparer.Ordinal);
|
||||||
|
|
||||||
|
result.Tools = [.. result.Tools.Where(tool => available.Contains(tool.Name))];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// /mcp ucunun OAuth adresleri. Degerler ortam yapilandirmasindan gelir (<c>App:SelfUrl</c>,
|
||||||
|
/// <c>AuthServer:Authority</c>); localhost, dev ve production ayni kodla calisir.
|
||||||
|
/// </summary>
|
||||||
|
public class McpOAuthOptions
|
||||||
|
{
|
||||||
|
/// <summary>API'nin disaridan erisilen kok adresi, sonunda egik cizgi olmadan.</summary>
|
||||||
|
public string BaseUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Token'i veren yetkilendirme sunucusu (OpenIddict), sonunda egik cizgi olmadan.</summary>
|
||||||
|
public string AuthorizationServer { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Korumali kaynagin tanimlayicisi: istemcinin baglandigi /mcp adresi.</summary>
|
||||||
|
public string ResourceUrl => BaseUrl + McpConsts.EndpointPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kaynaga ozgu meta veri adresi (RFC 9728 §3.1: yol, well-known onekinin arkasina eklenir).
|
||||||
|
/// </summary>
|
||||||
|
public string ProtectedResourceMetadataUrl => BaseUrl + McpConsts.ProtectedResourceMetadataPath + McpConsts.EndpointPath;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Istemciye <c>initialize</c> yanitinda bildirilen sunucu kimligi ve yonerge metni.
|
||||||
|
/// Yonerge modele hitap eder, son kullaniciya degil; bu yuzden dil anahtari uzerinden
|
||||||
|
/// gecmez — protokol metnidir, arayuz metni degil.
|
||||||
|
/// </summary>
|
||||||
|
internal static class McpServerInfoDefaults
|
||||||
|
{
|
||||||
|
public const string Name = "sozsoft-platform";
|
||||||
|
|
||||||
|
/// <summary>Connector ekraninda gorunen kaynak adi.</summary>
|
||||||
|
public const string DisplayName = "Sozsoft Platform";
|
||||||
|
|
||||||
|
public const string Version = "1.0.0";
|
||||||
|
|
||||||
|
public const string Instructions =
|
||||||
|
"Sozsoft Platform data access. Do not guess table or column names: first find the list with " +
|
||||||
|
"list_lists or search_schema, read its columns with describe_list, then query it with run_select " +
|
||||||
|
"using named @parameters. Never inline literal values into SQL. Columns marked isSensitive must " +
|
||||||
|
"not be selected. Call server_info first when a tool behaves unexpectedly.";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<Import Project="..\common.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>Sozsoft.Mcp</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- /mcp ucunun Streamable HTTP tasiyicisi. -->
|
||||||
|
<PackageReference Include="ModelContextProtocol.AspNetCore" Version="2.2.0" />
|
||||||
|
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="10.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- Sunum katmani: araclar yalnizca uygulama sozlesmesini tanir, Domain'i gormez. -->
|
||||||
|
<ProjectReference Include="..\Sozsoft.Mcp.Application.Contracts\Sozsoft.Mcp.Application.Contracts.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Tools;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Veri yazma araclari. Yazma bir ListForm ekraninin sozlesmesinden gecer; ekran yetkisi ve
|
||||||
|
/// tenant filtresi aynen uygulanir. Sunucu kaydinda <c>AllowWrite</c> kapaliysa bu araclar
|
||||||
|
/// arac listesinde hic gorunmez.
|
||||||
|
/// </summary>
|
||||||
|
[McpServerToolType]
|
||||||
|
public sealed class McpDataTools(IMcpDataAppService dataAppService)
|
||||||
|
{
|
||||||
|
[McpServerTool(Name = McpToolNames.InsertRow, Destructive = false, Idempotent = false)]
|
||||||
|
[Description("Bir listeye yeni kayit ekler. Alan adlari describe_list cikisindaki field degerleridir.")]
|
||||||
|
public async Task<McpWriteResultDto> InsertRowAsync(
|
||||||
|
[Description("Kaydin eklenecegi listenin kodu.")]
|
||||||
|
string listFormCode,
|
||||||
|
[Description("Alan adi -> deger eslemesi.")]
|
||||||
|
Dictionary<string, object?> values)
|
||||||
|
=> await dataAppService.InsertRowAsync(new McpRowRequestDto
|
||||||
|
{
|
||||||
|
ListFormCode = listFormCode,
|
||||||
|
Values = values ?? []
|
||||||
|
});
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.UpdateRow, Destructive = true, Idempotent = true)]
|
||||||
|
[Description("Bir listedeki kaydi anahtar degerine gore gunceller. Yalnizca verilen alanlar yazilir.")]
|
||||||
|
public async Task<McpWriteResultDto> UpdateRowAsync(
|
||||||
|
[Description("Kaydin bulundugu listenin kodu.")]
|
||||||
|
string listFormCode,
|
||||||
|
[Description("Guncellenecek kaydin anahtar alan degeri; describe_list cikisindaki keyFieldName alanina karsilik gelir.")]
|
||||||
|
List<object?> keys,
|
||||||
|
[Description("Alan adi -> yeni deger eslemesi.")]
|
||||||
|
Dictionary<string, object?> values)
|
||||||
|
=> await dataAppService.UpdateRowAsync(new McpRowRequestDto
|
||||||
|
{
|
||||||
|
ListFormCode = listFormCode,
|
||||||
|
Keys = keys ?? [],
|
||||||
|
Values = values ?? []
|
||||||
|
});
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.DeleteRow, Destructive = true, Idempotent = true)]
|
||||||
|
[Description("Bir listedeki kaydi anahtar degerine gore siler. Ekranin kendi silme komutu kullanilir; soft delete tasiyan tablolarda kayit pasife cekilir.")]
|
||||||
|
public async Task<McpWriteResultDto> DeleteRowAsync(
|
||||||
|
[Description("Kaydin bulundugu listenin kodu.")]
|
||||||
|
string listFormCode,
|
||||||
|
[Description("Silinecek kaydin anahtar alan degeri.")]
|
||||||
|
List<object?> keys)
|
||||||
|
=> await dataAppService.DeleteRowAsync(new McpRowRequestDto
|
||||||
|
{
|
||||||
|
ListFormCode = listFormCode,
|
||||||
|
Keys = keys ?? []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Tools;
|
||||||
|
|
||||||
|
/// <summary>Salt-okunur sorgu araclari.</summary>
|
||||||
|
[McpServerToolType]
|
||||||
|
public sealed class McpQueryTools(IMcpQueryAppService queryAppService)
|
||||||
|
{
|
||||||
|
[McpServerTool(Name = McpToolNames.QueryList, ReadOnly = true)]
|
||||||
|
[Description("Bir listenin verisini uygulamadaki ekranla ayni sorguyla okur; kullanicinin liste ve alan yetkileri, kiraci ve sube filtreleri gecerlidir. Veri okumak icin once bunu kullan. Alan adlari describe_list cikisindaki field degerleridir. Filtre/siralama/gruplama DevExtreme bicimindedir.")]
|
||||||
|
public async Task<McpListQueryResultDto> QueryListAsync(
|
||||||
|
[Description("Listenin kodu; list_lists cikisindaki listFormCode degeri.")]
|
||||||
|
string listFormCode,
|
||||||
|
[Description("Filtre, JSON metni. Ornek: [\"Status\",\"=\",\"Open\"] ya da [[\"Amount\",\">\",100],\"and\",[\"Name\",\"contains\",\"abc\"]]. Operatorler: =, <>, >, >=, <, <=, contains, notcontains, startswith, endswith.")]
|
||||||
|
string? filter = null,
|
||||||
|
[Description("Siralama, JSON metni. Ornek: [{\"selector\":\"CreationTime\",\"desc\":true}].")]
|
||||||
|
string? sort = null,
|
||||||
|
[Description("Gruplama, JSON metni. Ornek: [{\"selector\":\"CustomerId\",\"desc\":false,\"isExpanded\":false}]. Gruplu sorguda satirlar yerine grup anahtarlari ve ozetleri doner.")]
|
||||||
|
string? group = null,
|
||||||
|
[Description("Grup ozetleri, JSON metni. Ornek: [{\"selector\":\"Id\",\"summaryType\":\"count\"},{\"selector\":\"Amount\",\"summaryType\":\"sum\"}]. Tipler: count, sum, min, max, avg.")]
|
||||||
|
string? groupSummary = null,
|
||||||
|
[Description("Tum sonuc icin ozet, JSON metni. Ornek: [{\"selector\":\"Amount\",\"summaryType\":\"sum\"}].")]
|
||||||
|
string? totalSummary = null,
|
||||||
|
[Description("Atlanacak kayit sayisi (sayfalama).")]
|
||||||
|
int skip = 0,
|
||||||
|
[Description("Istenen kayit sayisi; sunucu kaydindaki tavandan buyuk olamaz.")]
|
||||||
|
int take = 0)
|
||||||
|
=> await queryAppService.QueryListAsync(new McpListQueryRequestDto
|
||||||
|
{
|
||||||
|
ListFormCode = listFormCode,
|
||||||
|
Filter = filter,
|
||||||
|
Sort = sort,
|
||||||
|
Group = group,
|
||||||
|
GroupSummary = groupSummary,
|
||||||
|
TotalSummary = totalSummary,
|
||||||
|
Skip = skip,
|
||||||
|
Take = take
|
||||||
|
});
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.RunSelect, ReadOnly = true)]
|
||||||
|
[Description("Yalnizca uygulamada SQL Query Manager yetkisi olan kullanicilar icin: tek bir parametreli SELECT ifadesi kosturur. Liste verisi icin query_list tercih edilir. Coklu ifade, DDL ve DML reddedilir; satir sayisi sunucu kaydindaki tavana kirpilir. Sorguya deger gomme, @ad parametresi kullan.")]
|
||||||
|
public async Task<McpQueryResultDto> RunSelectAsync(
|
||||||
|
[Description("Tek bir SELECT (ya da WITH ... SELECT) ifadesi. Degerler @ad bicimindeki parametrelerle verilir.")]
|
||||||
|
string sql,
|
||||||
|
[Description("Sorgudaki adli parametreler: anahtar parametre adi, deger ise ham JSON degeri.")]
|
||||||
|
Dictionary<string, object?>? parameters = null,
|
||||||
|
[Description("Istenen en fazla satir sayisi; sunucu kaydindaki tavandan buyuk olamaz.")]
|
||||||
|
int maxRows = 0)
|
||||||
|
=> await queryAppService.RunSelectAsync(new McpSelectRequestDto
|
||||||
|
{
|
||||||
|
Sql = sql,
|
||||||
|
Parameters = parameters ?? [],
|
||||||
|
MaxRows = maxRows
|
||||||
|
});
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.ListQueries, ReadOnly = true)]
|
||||||
|
[Description("Veri kaynagindaki kayitli nesneleri (view, stored procedure, function) ve cagrilabilir Custom Endpoint'leri dondurur. run_query bunlardan birini cagirir.")]
|
||||||
|
public async Task<List<McpSavedQueryDto>> ListQueriesAsync(
|
||||||
|
[Description("Nesne adi ya da aciklamasi icinde aranacak metin. Bos birakilabilir.")]
|
||||||
|
string? search = null)
|
||||||
|
=> await queryAppService.GetSavedQueriesAsync(search);
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.RunQuery, ReadOnly = true)]
|
||||||
|
[Description("Kayitli bir nesneyi ya da Custom Endpoint'i parametreleriyle cagirir. Nesne turu ve adi list_queries cikisindan alinir.")]
|
||||||
|
public async Task<McpQueryResultDto> RunQueryAsync(
|
||||||
|
[Description("Nesne turu: view, procedure, tableFunction, scalarFunction ya da customEndpoint.")]
|
||||||
|
string kind,
|
||||||
|
[Description("Nesnenin ya da Custom Endpoint'in adi.")]
|
||||||
|
string name,
|
||||||
|
[Description("Nesnenin semasi. Bos birakilirsa SQL Server'da dbo, PostgreSQL'de public kullanilir.")]
|
||||||
|
string? schemaName = null,
|
||||||
|
[Description("Adli parametreler: anahtar parametre adi, deger ise ham JSON degeri.")]
|
||||||
|
Dictionary<string, object?>? parameters = null,
|
||||||
|
[Description("Istenen en fazla satir sayisi; sunucu kaydindaki tavandan buyuk olamaz.")]
|
||||||
|
int maxRows = 0)
|
||||||
|
=> await queryAppService.RunSavedQueryAsync(new McpSavedQueryRequestDto
|
||||||
|
{
|
||||||
|
Kind = kind,
|
||||||
|
Name = name,
|
||||||
|
SchemaName = schemaName,
|
||||||
|
Parameters = parameters ?? [],
|
||||||
|
MaxRows = maxRows
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Tools;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sema kesfi araclari. Tablo ve sutun bilgisi canli katalogdan degil, platformun ekran
|
||||||
|
/// tanimlarindan uretilen veri sozlugunden gelir; boylece modelin gordugu adlar kullanicinin
|
||||||
|
/// ekranda gordugu adlarla ayni olur.
|
||||||
|
/// </summary>
|
||||||
|
[McpServerToolType]
|
||||||
|
public sealed class McpSchemaTools(IMcpSchemaAppService schemaAppService)
|
||||||
|
{
|
||||||
|
[McpServerTool(Name = McpToolNames.ServerInfo, ReadOnly = true)]
|
||||||
|
[Description("Bu baglantinin kiracisini, acik araclari ve veri sozlugunun tazelik bilgisini dondurur. Baska bir arac beklendigi gibi calismiyorsa once bunu cagir.")]
|
||||||
|
public async Task<McpServerInfoResult> ServerInfoAsync()
|
||||||
|
{
|
||||||
|
var config = await schemaAppService.GetServerConfigAsync();
|
||||||
|
var schema = await schemaAppService.GetSchemaInfoAsync();
|
||||||
|
var availableTools = await schemaAppService.GetAvailableToolsAsync();
|
||||||
|
|
||||||
|
return new McpServerInfoResult(config, schema, availableTools);
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.ListLists, ReadOnly = true)]
|
||||||
|
[Description("Kiracinin listelerini (ekranlarini) ad, baslik, aciklama ve arkasindaki tablo ile dondurur. Arama metni verilmezse tumu doner.")]
|
||||||
|
public async Task<List<McpListSummaryDto>> ListListsAsync(
|
||||||
|
[Description("Liste adi, basligi, aciklamasi ya da tablo adi icinde aranacak metin. Bos birakilabilir.")]
|
||||||
|
string? search = null,
|
||||||
|
[Description("Donecek en fazla liste sayisi. Varsayilan 200.")]
|
||||||
|
int maxResults = 200)
|
||||||
|
=> await schemaAppService.GetListsAsync(search, maxResults);
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.DescribeList, ReadOnly = true)]
|
||||||
|
[Description("Tek bir listenin sutunlarini dondurur: alan adi, kullaniciya gorunen basligi, SQL tipi, lookup iliskisi ve sutunun hassas olup olmadigi.")]
|
||||||
|
public async Task<McpListDetailDto?> DescribeListAsync(
|
||||||
|
[Description("Listenin kodu; list_lists cikisindaki listFormCode degeri.")]
|
||||||
|
string listFormCode)
|
||||||
|
=> await schemaAppService.GetListAsync(listFormCode);
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.SearchSchema, ReadOnly = true)]
|
||||||
|
[Description("Liste ve sutun adlari ile basliklari uzerinde aksan ve buyuk/kucuk harf duyarsiz arama yapar; hangi listenin hangi sutununun arandigini bulmak icin kullanilir.")]
|
||||||
|
public async Task<List<McpSchemaMatchDto>> SearchSchemaAsync(
|
||||||
|
[Description("Aranacak metin. Turkce karakterler ve aksanlar onemsizdir.")]
|
||||||
|
string term,
|
||||||
|
[Description("Donecek en fazla eslesme sayisi. Varsayilan 50.")]
|
||||||
|
int maxResults = 50)
|
||||||
|
=> await schemaAppService.SearchAsync(term, maxResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="Server">Istegin kiracisina dusen sunucu kaydi; yoksa null.</param>
|
||||||
|
/// <param name="Schema">Veri sozlugunun tazelik bilgisi.</param>
|
||||||
|
/// <param name="AvailableTools">Bu baglantida cagrilabilen araclar.</param>
|
||||||
|
public sealed record McpServerInfoResult(
|
||||||
|
McpServerConfigDto? Server,
|
||||||
|
McpSchemaInfoDto Schema,
|
||||||
|
IReadOnlyList<string> AvailableTools);
|
||||||
8
api/modules/Sozsoft.Mcp/common.props
Normal file
8
api/modules/Sozsoft.Mcp/common.props
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<Version>0.1.0</Version>
|
||||||
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
|
<AbpProjectType>module</AbpProjectType>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
|
|
@ -1,11 +1,23 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Volo.Abp.Application.Services;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms.Select;
|
namespace Sozsoft.Platform.ListForms.Select;
|
||||||
|
|
||||||
public interface IListFormDataAppService
|
/// <summary>
|
||||||
|
/// Ekran uzerinden veri yazma sozlesmesi. Ekranin insert/update/delete komutlari ve yetki
|
||||||
|
/// kontrolu burada uygulanir; ayni yol hem grid'den hem /mcp araclarindan kullanilir.
|
||||||
|
/// </summary>
|
||||||
|
public interface IListFormDataAppService : IApplicationService
|
||||||
{
|
{
|
||||||
Task<int> PostInsertAsync(DataRequestDto input);
|
/// <returns>Uretilen anahtar degeri.</returns>
|
||||||
|
Task<dynamic> PostInsertAsync(DataRequestDto input);
|
||||||
|
|
||||||
|
/// <returns>Kopyalanan kaydin anahtar degeri.</returns>
|
||||||
|
Task<dynamic> PostDuplicateAsync(DataRequestDto input);
|
||||||
|
|
||||||
|
/// <returns>Etkilenen satir sayisi.</returns>
|
||||||
Task<int> PostUpdateAsync(DataRequestDto input);
|
Task<int> PostUpdateAsync(DataRequestDto input);
|
||||||
|
|
||||||
|
/// <returns>Etkilenen satir sayisi.</returns>
|
||||||
Task<int> PostDeleteAsync(DataRequestDto input);
|
Task<int> PostDeleteAsync(DataRequestDto input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ using static Sozsoft.Platform.PlatformConsts;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms.Select;
|
namespace Sozsoft.Platform.ListForms.Select;
|
||||||
|
|
||||||
public class ListFormDataAppService : PlatformAppService
|
public class ListFormDataAppService : PlatformAppService, IListFormDataAppService
|
||||||
{
|
{
|
||||||
private readonly IListFormAuthorizationManager authManager;
|
private readonly IListFormAuthorizationManager authManager;
|
||||||
private readonly IQueryManager qManager;
|
private readonly IQueryManager qManager;
|
||||||
|
|
|
||||||
|
|
@ -28295,6 +28295,156 @@
|
||||||
"key": "App.StaticLookup.Right",
|
"key": "App.StaticLookup.Right",
|
||||||
"en": "Right",
|
"en": "Right",
|
||||||
"tr": "Sağ"
|
"tr": "Sağ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.DeveloperKit.McpServers",
|
||||||
|
"en": "MCP Servers",
|
||||||
|
"tr": "MCP Sunucuları"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.DeveloperKit.McpServers.Invoke",
|
||||||
|
"en": "Call MCP tools",
|
||||||
|
"tr": "MCP araçlarını çağır"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.DeveloperKit.McpServers.Write",
|
||||||
|
"en": "Write data over MCP",
|
||||||
|
"tr": "MCP üzerinden veri yaz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.DeveloperKit.McpServers.Description",
|
||||||
|
"en": "MCP Servers — the Model Context Protocol endpoint definitions. Table: Sas_H_McpServer · host scope · list screen.\nUsage: an AI client (Claude Code, Claude Desktop, ChatGPT) connects to /mcp with the user's own access token; the record matching the request's tenant decides which tools are published, on which data source they run and with which row ceiling.\nFields:\n• Name → the server name shown in the tool list\n• Url → the address handed to the client (https://api.sozsoft.com/mcp in production, https://localhost:44344/mcp locally); the server does not route on this value\n• Tenants → which tenants may use it; when empty it is open to all\n• DataSourceCode → the data source the query tools run on; when empty the request's own scope is used\n• AllowedTools → the open tools; when empty every tool except the writing ones is open\n• MaxRows → the largest number of rows a single query may return\n• AllowWrite → while off, insert_row, update_row and delete_row are not even listed\n• IsActive → a passive record publishes no tools\nCaution: schema discovery reads the JSON data dictionary in Sas_H_DbTableColumn. When the dictionary is missing or stale the tools return no tables — run dbo.Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns with the tenant name.",
|
||||||
|
"tr": "MCP Sunucuları — Model Context Protocol uç tanımları. Tablo: Sas_H_McpServer · host kapsamlı · liste ekranı.\nKullanım: bir yapay zekâ istemcisi (Claude Code, Claude Desktop, ChatGPT) kullanıcının kendi erişim token'ı ile /mcp ucuna bağlanır; isteğin kiracısına düşen kayıt hangi araçların yayınlandığını, sorguların hangi veri kaynağında ve hangi satır tavanıyla koşacağını belirler.\nAlanlar:\n• Name → araç listesinde görünen sunucu adı\n• Url → istemciye verilen adres (sunucuda https://api.sozsoft.com/mcp, yerelde https://localhost:44344/mcp); sunucu bu değere göre yönlendirme yapmaz\n• Tenants → hangi kiracıların kullanabileceği; boş bırakılırsa tümüne açıktır\n• DataSourceCode → sorgu araçlarının koşacağı veri kaynağı; boş ise isteğin kendi kapsamı kullanılır\n• AllowedTools → açık araçlar; boş bırakılırsa yazma dışındaki tüm araçlar açıktır\n• MaxRows → tek sorgunun döndürebileceği en fazla satır sayısı\n• AllowWrite → kapalıyken insert_row, update_row ve delete_row araç listesinde hiç yer almaz\n• IsActive → pasif kayıt hiçbir araç yayınlamaz\nDikkat: şema keşfi Sas_H_DbTableColumn tablosundaki JSON veri sözlüğünü okur. Sözlük yok ya da bayatsa araçlar tablo döndürmez — dbo.Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns prosedürünü kiracı adıyla çalıştırın."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Listform.ListformField.Url",
|
||||||
|
"en": "Url",
|
||||||
|
"tr": "Adres"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Listform.ListformField.AllowedTools",
|
||||||
|
"en": "Allowed Tools",
|
||||||
|
"tr": "İzinli Araçlar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Listform.ListformField.MaxRows",
|
||||||
|
"en": "Max Rows",
|
||||||
|
"tr": "En Fazla Satır"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Listform.ListformField.AllowWrite",
|
||||||
|
"en": "Allow Write",
|
||||||
|
"tr": "Yazmaya İzin Ver"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.NoServerRecord",
|
||||||
|
"en": "No active MCP server record is defined for this tenant.",
|
||||||
|
"tr": "Bu kiracı için tanımlı ve aktif bir MCP sunucu kaydı yok."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.ToolDisabled",
|
||||||
|
"en": "The '{0}' tool is disabled on the '{1}' MCP server.",
|
||||||
|
"tr": "'{0}' aracı '{1}' MCP sunucusunda kapalı."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.EmptyQuery",
|
||||||
|
"en": "The query text is empty.",
|
||||||
|
"tr": "Sorgu metni boş."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.SingleStatementOnly",
|
||||||
|
"en": "Only a single SELECT statement may be run at a time.",
|
||||||
|
"tr": "Tek seferde yalnızca bir SELECT ifadesi koşturulabilir."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.ReadOnlyStatementRequired",
|
||||||
|
"en": "The query must start with SELECT or WITH.",
|
||||||
|
"tr": "Sorgu SELECT ya da WITH ile başlamak zorunda."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.ForbiddenKeyword",
|
||||||
|
"en": "Expression not allowed in a read-only query: {0}",
|
||||||
|
"tr": "Salt-okunur sorguda kullanılamayan ifade: {0}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.InvalidIdentifier",
|
||||||
|
"en": "Invalid object or schema name: {0}",
|
||||||
|
"tr": "Geçersiz nesne ya da şema adı: {0}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.InvalidParameterName",
|
||||||
|
"en": "Invalid parameter name: {0}",
|
||||||
|
"tr": "Geçersiz parametre adı: {0}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.UnsupportedObjectKind",
|
||||||
|
"en": "Unsupported object kind: {0}",
|
||||||
|
"tr": "Desteklenmeyen nesne türü: {0}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.CustomEndpointNotFound",
|
||||||
|
"en": "Custom Endpoint not found or not permitted: {0}",
|
||||||
|
"tr": "Custom Endpoint bulunamadı ya da yetki yok: {0}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.MissingParameters",
|
||||||
|
"en": "Required parameters are missing: {0}",
|
||||||
|
"tr": "Zorunlu parametreler eksik: {0}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.ListFormCodeRequired",
|
||||||
|
"en": "ListFormCode cannot be empty.",
|
||||||
|
"tr": "ListFormCode boş bırakılamaz."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.ValuesRequired",
|
||||||
|
"en": "No field value was given to write.",
|
||||||
|
"tr": "Yazılacak alan değeri verilmedi."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.KeysRequired",
|
||||||
|
"en": "No key field value was given.",
|
||||||
|
"tr": "Anahtar alan değeri verilmedi."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.DictionaryNotFound",
|
||||||
|
"en": "The data dictionary was not found. Run the {0} procedure with the tenant name.",
|
||||||
|
"tr": "Veri sözlüğü bulunamadı. {0} prosedürünü kiracı adıyla çalıştırın."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.RedirectUriNotAllowed",
|
||||||
|
"en": "The redirect address is not accepted. Use HTTPS, a localhost address or an application scheme: {0}",
|
||||||
|
"tr": "Geri dönüş adresi kabul edilmiyor. HTTPS, localhost ya da uygulama şeması kullanın: {0}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Mcp.Error.RegistrationLimitReached",
|
||||||
|
"en": "The limit of {0} dynamically registered MCP clients has been reached. Remove unused clients from the OpenIddict applications.",
|
||||||
|
"tr": "Dinamik kayıtlı MCP istemcisi tavanına ({0}) ulaşıldı. Kullanılmayan istemcileri OpenIddict uygulamalarından silin."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using Sozsoft.Languages.Languages;
|
using Sozsoft.Languages.Languages;
|
||||||
|
using Sozsoft.Mcp;
|
||||||
using Sozsoft.Platform.Editors;
|
using Sozsoft.Platform.Editors;
|
||||||
using Sozsoft.Platform.Entities;
|
using Sozsoft.Platform.Entities;
|
||||||
using Sozsoft.Platform.Enums;
|
using Sozsoft.Platform.Enums;
|
||||||
|
|
@ -7846,6 +7848,292 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region MCP Servers
|
||||||
|
listFormName = AppCodes.DeveloperKits.McpServers;
|
||||||
|
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
||||||
|
{
|
||||||
|
// Kayit /mcp ucunun kiraci bazli sozlesmesini tutar: hangi araclar acik, sorgular
|
||||||
|
// hangi veri kaynaginda hangi satir tavaniyla kosar.
|
||||||
|
var mcpServerTable = TableNameResolver.GetFullTableName(nameof(TableNameEnum.McpServer));
|
||||||
|
|
||||||
|
var listForm = await _listFormRepository.InsertAsync(
|
||||||
|
new ListForm()
|
||||||
|
{
|
||||||
|
ListFormType = ListFormTypeEnum.List,
|
||||||
|
PageSize = 10,
|
||||||
|
ExportJson = DefaultExportJson,
|
||||||
|
IsSubForm = false,
|
||||||
|
ShowNote = false,
|
||||||
|
LayoutJson = DefaultLayoutJson(),
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
ListFormCode = listFormName,
|
||||||
|
Name = listFormName,
|
||||||
|
Title = listFormName,
|
||||||
|
DataSourceCode = SeedConsts.DataSources.DefaultCode,
|
||||||
|
IsTenant = false,
|
||||||
|
IsBranch = false,
|
||||||
|
IsOrganizationUnit = false,
|
||||||
|
Description = $"{listFormName}.Description",
|
||||||
|
SelectCommandType = SelectCommandTypeEnum.Table,
|
||||||
|
SelectCommand = mcpServerTable,
|
||||||
|
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
|
||||||
|
SeedSyncInsert = true,
|
||||||
|
SeedSyncUpdate = true,
|
||||||
|
SeedSyncDelete = true,
|
||||||
|
KeyFieldName = "Id",
|
||||||
|
KeyFieldDbSourceType = DbType.Guid,
|
||||||
|
DefaultFilter = DefaultFilterJson,
|
||||||
|
SortMode = GridOptions.SortModeSingle,
|
||||||
|
FilterRowJson = DefaultFilterRowJson(),
|
||||||
|
HeaderFilterJson = DefaultHeaderFilterJson(),
|
||||||
|
SearchPanelJson = DefaultSearchPanelJson(),
|
||||||
|
GroupPanelJson = DefaultGroupPanelJson(),
|
||||||
|
SelectionJson = DefaultSelectionSingleJson,
|
||||||
|
ColumnOptionJson = DefaultColumnOptionJson(),
|
||||||
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 700, 600, true, true, true, true, false),
|
||||||
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>()
|
||||||
|
{
|
||||||
|
new() { Order = 1, ColCount = 2, ColSpan = 1, ItemType = "group" },
|
||||||
|
}),
|
||||||
|
InsertFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
||||||
|
new() { FieldName = "Id", FieldDbType = DbType.Guid, Value = "@NEWID", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
|
||||||
|
new() { FieldName = "CreationTime", FieldDbType = DbType.DateTimeOffset, Value = "@NOW", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
|
||||||
|
new() { FieldName = "CreatorId", FieldDbType = DbType.Guid, Value = "@USERID", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
|
||||||
|
}),
|
||||||
|
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
||||||
|
new() { FieldName = "IsActive", FieldDbType = DbType.Boolean, Value = "true", CustomValueType = FieldCustomValueTypeEnum.Value },
|
||||||
|
new() { FieldName = "AllowWrite", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value },
|
||||||
|
new() { FieldName = "MaxRows", FieldDbType = DbType.Int32, Value = McpConsts.DefaultMaxRows.ToString(), CustomValueType = FieldCustomValueTypeEnum.Value },
|
||||||
|
}),
|
||||||
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.McpServer)),
|
||||||
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#region MCP Servers Fields
|
||||||
|
await _listFormFieldRepository.InsertManyAsync([
|
||||||
|
new() {
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
SourceDbType = DbType.Guid,
|
||||||
|
FieldName = "Id",
|
||||||
|
CaptionName = "App.Listform.ListformField.Id",
|
||||||
|
Width = 0,
|
||||||
|
ListOrderNo = 1,
|
||||||
|
Visible = false,
|
||||||
|
IsActive = true,
|
||||||
|
|
||||||
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
SourceDbType = DbType.String,
|
||||||
|
FieldName = "Name",
|
||||||
|
EditGroupOrderNo = 1,
|
||||||
|
EditOrderNo = 1,
|
||||||
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
|
ColSpan = 1,
|
||||||
|
CaptionName = "App.Listform.ListformField.Name",
|
||||||
|
Width = 0,
|
||||||
|
ListOrderNo = 2,
|
||||||
|
Visible = true,
|
||||||
|
IsActive = true,
|
||||||
|
|
||||||
|
AllowSearch = true,
|
||||||
|
ValidationRuleJson = DefaultValidationRuleRequiredJson,
|
||||||
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
SourceDbType = DbType.Boolean,
|
||||||
|
FieldName = "IsActive",
|
||||||
|
EditGroupOrderNo = 1,
|
||||||
|
EditOrderNo = 2,
|
||||||
|
EditorType2 = EditorTypes.dxCheckBox,
|
||||||
|
ColSpan = 1,
|
||||||
|
CaptionName = "App.Listform.ListformField.IsActive",
|
||||||
|
Width = 0,
|
||||||
|
ListOrderNo = 3,
|
||||||
|
Visible = true,
|
||||||
|
IsActive = true,
|
||||||
|
|
||||||
|
AllowSearch = true,
|
||||||
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
SourceDbType = DbType.String,
|
||||||
|
FieldName = "Url",
|
||||||
|
EditGroupOrderNo = 1,
|
||||||
|
EditOrderNo = 3,
|
||||||
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
|
ColSpan = 2,
|
||||||
|
CaptionName = "App.Listform.ListformField.Url",
|
||||||
|
Width = 0,
|
||||||
|
ListOrderNo = 4,
|
||||||
|
Visible = true,
|
||||||
|
IsActive = true,
|
||||||
|
|
||||||
|
AllowSearch = true,
|
||||||
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
SourceDbType = DbType.String,
|
||||||
|
FieldName = "Tenants",
|
||||||
|
EditGroupOrderNo = 1,
|
||||||
|
EditOrderNo = 4,
|
||||||
|
EditorType2 = EditorTypes.dxTagBox,
|
||||||
|
ColSpan = 2,
|
||||||
|
CaptionName = "App.Listform.ListformField.Tenants",
|
||||||
|
Width = 0,
|
||||||
|
ListOrderNo = 5,
|
||||||
|
Visible = true,
|
||||||
|
IsActive = true,
|
||||||
|
|
||||||
|
AllowSearch = true,
|
||||||
|
LookupJson = JsonSerializer.Serialize(new LookupDto {
|
||||||
|
DataSourceType = UiLookupDataSourceTypeEnum.Query,
|
||||||
|
DisplayExpr = "Name",
|
||||||
|
ValueExpr = "Key",
|
||||||
|
LookupQuery = LookupQueryValues.TenantValues(true),
|
||||||
|
}),
|
||||||
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
SourceDbType = DbType.String,
|
||||||
|
FieldName = "DataSourceCode",
|
||||||
|
EditGroupOrderNo = 1,
|
||||||
|
EditOrderNo = 5,
|
||||||
|
EditorType2 = EditorTypes.dxSelectBox,
|
||||||
|
ColSpan = 1,
|
||||||
|
CaptionName = "App.Listform.ListformField.DataSourceCode",
|
||||||
|
Width = 0,
|
||||||
|
ListOrderNo = 6,
|
||||||
|
Visible = true,
|
||||||
|
IsActive = true,
|
||||||
|
|
||||||
|
AllowSearch = true,
|
||||||
|
LookupJson = LookupQueryValues.DefaultLookupQueryJson(nameof(TableNameEnum.DataSource), "Code", "Code"),
|
||||||
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
SourceDbType = DbType.Int32,
|
||||||
|
FieldName = "MaxRows",
|
||||||
|
EditGroupOrderNo = 1,
|
||||||
|
EditOrderNo = 6,
|
||||||
|
EditorType2 = EditorTypes.dxNumberBox,
|
||||||
|
EditorOptions = EditorOptions.Number(0),
|
||||||
|
ColSpan = 1,
|
||||||
|
CaptionName = "App.Listform.ListformField.MaxRows",
|
||||||
|
Width = 0,
|
||||||
|
ListOrderNo = 7,
|
||||||
|
Visible = true,
|
||||||
|
IsActive = true,
|
||||||
|
|
||||||
|
AllowSearch = true,
|
||||||
|
ValidationRuleJson = DefaultValidationRuleRequiredJson,
|
||||||
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
SourceDbType = DbType.String,
|
||||||
|
FieldName = "AllowedTools",
|
||||||
|
EditGroupOrderNo = 1,
|
||||||
|
EditOrderNo = 7,
|
||||||
|
EditorType2 = EditorTypes.dxTagBox,
|
||||||
|
ColSpan = 2,
|
||||||
|
CaptionName = "App.Listform.ListformField.AllowedTools",
|
||||||
|
Width = 0,
|
||||||
|
ListOrderNo = 8,
|
||||||
|
Visible = true,
|
||||||
|
IsActive = true,
|
||||||
|
|
||||||
|
AllowSearch = true,
|
||||||
|
// Secenek listesi modulun arac kayit defterinden uretilir; yeni bir arac
|
||||||
|
// eklendiginde ekran kendiliginden guncel olur.
|
||||||
|
LookupJson = JsonSerializer.Serialize(new LookupDto {
|
||||||
|
DataSourceType = UiLookupDataSourceTypeEnum.StaticData,
|
||||||
|
DisplayExpr = "name",
|
||||||
|
ValueExpr = "key",
|
||||||
|
LookupQuery = JsonSerializer.Serialize(
|
||||||
|
McpToolNames.SelectableTools.Select(tool => new LookupDataDto { Key = tool, Name = tool }).ToArray()),
|
||||||
|
}),
|
||||||
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
SourceDbType = DbType.Boolean,
|
||||||
|
FieldName = "AllowWrite",
|
||||||
|
EditGroupOrderNo = 1,
|
||||||
|
EditOrderNo = 8,
|
||||||
|
EditorType2 = EditorTypes.dxCheckBox,
|
||||||
|
ColSpan = 1,
|
||||||
|
CaptionName = "App.Listform.ListformField.AllowWrite",
|
||||||
|
Width = 0,
|
||||||
|
ListOrderNo = 9,
|
||||||
|
Visible = true,
|
||||||
|
IsActive = true,
|
||||||
|
|
||||||
|
AllowSearch = true,
|
||||||
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
SourceDbType = DbType.String,
|
||||||
|
FieldName = "Description",
|
||||||
|
EditGroupOrderNo = 1,
|
||||||
|
EditOrderNo = 9,
|
||||||
|
EditorType2 = EditorTypes.dxTextArea,
|
||||||
|
EditorOptions = EditorOptions.Multiline(60),
|
||||||
|
ColSpan = 2,
|
||||||
|
CaptionName = "App.Listform.ListformField.Description",
|
||||||
|
Width = 0,
|
||||||
|
ListOrderNo = 10,
|
||||||
|
Visible = false,
|
||||||
|
IsActive = true,
|
||||||
|
|
||||||
|
AllowSearch = true,
|
||||||
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Products
|
#region Products
|
||||||
listFormName = AppCodes.Orders.Products;
|
listFormName = AppCodes.Orders.Products;
|
||||||
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
||||||
|
|
|
||||||
|
|
@ -926,6 +926,17 @@
|
||||||
"IsDisabled": false,
|
"IsDisabled": false,
|
||||||
"ModuleId": "Sas"
|
"ModuleId": "Sas"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ParentCode": "App.DeveloperKit",
|
||||||
|
"Code": "App.DeveloperKit.McpServers",
|
||||||
|
"DisplayName": "App.DeveloperKit.McpServers",
|
||||||
|
"Order": 9,
|
||||||
|
"Url": "/admin/list/App.DeveloperKit.McpServers",
|
||||||
|
"Icon": "FcElectroDevices",
|
||||||
|
"RequiredPermissionName": "App.DeveloperKit.McpServers",
|
||||||
|
"IsDisabled": false,
|
||||||
|
"ModuleId": "Sas"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Saas",
|
"ParentCode": "App.Saas",
|
||||||
"Code": "App.ForumManagement",
|
"Code": "App.ForumManagement",
|
||||||
|
|
|
||||||
|
|
@ -2505,6 +2505,78 @@
|
||||||
"MultiTenancySide": 2,
|
"MultiTenancySide": 2,
|
||||||
"Modules": "Sas|Adm"
|
"Modules": "Sas|Adm"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"GroupName": "App.Saas",
|
||||||
|
"Name": "App.DeveloperKit.McpServers",
|
||||||
|
"ParentName": "App.DeveloperKit",
|
||||||
|
"DisplayName": "App.DeveloperKit.McpServers",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"MultiTenancySide": 2,
|
||||||
|
"Modules": "Sas|Adm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GroupName": "App.Saas",
|
||||||
|
"Name": "App.DeveloperKit.McpServers.Create",
|
||||||
|
"ParentName": "App.DeveloperKit.McpServers",
|
||||||
|
"DisplayName": "App.Platform.Create",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"MultiTenancySide": 2,
|
||||||
|
"Modules": "Sas|Adm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GroupName": "App.Saas",
|
||||||
|
"Name": "App.DeveloperKit.McpServers.Update",
|
||||||
|
"ParentName": "App.DeveloperKit.McpServers",
|
||||||
|
"DisplayName": "App.Platform.Update",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"MultiTenancySide": 2,
|
||||||
|
"Modules": "Sas|Adm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GroupName": "App.Saas",
|
||||||
|
"Name": "App.DeveloperKit.McpServers.Delete",
|
||||||
|
"ParentName": "App.DeveloperKit.McpServers",
|
||||||
|
"DisplayName": "App.Platform.Delete",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"MultiTenancySide": 2,
|
||||||
|
"Modules": "Sas|Adm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GroupName": "App.Saas",
|
||||||
|
"Name": "App.DeveloperKit.McpServers.Export",
|
||||||
|
"ParentName": "App.DeveloperKit.McpServers",
|
||||||
|
"DisplayName": "Export",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"MultiTenancySide": 2,
|
||||||
|
"Modules": "Sas|Adm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GroupName": "App.Saas",
|
||||||
|
"Name": "App.DeveloperKit.McpServers.Import",
|
||||||
|
"ParentName": "App.DeveloperKit.McpServers",
|
||||||
|
"DisplayName": "Import",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"MultiTenancySide": 2,
|
||||||
|
"Modules": "Sas|Adm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GroupName": "App.Saas",
|
||||||
|
"Name": "App.DeveloperKit.McpServers.Invoke",
|
||||||
|
"ParentName": "App.DeveloperKit.McpServers",
|
||||||
|
"DisplayName": "App.DeveloperKit.McpServers.Invoke",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"MultiTenancySide": 3,
|
||||||
|
"Modules": "Sas|Adm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GroupName": "App.Saas",
|
||||||
|
"Name": "App.DeveloperKit.McpServers.Write",
|
||||||
|
"ParentName": "App.DeveloperKit.McpServers",
|
||||||
|
"DisplayName": "App.DeveloperKit.McpServers.Write",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"MultiTenancySide": 3,
|
||||||
|
"Modules": "Sas|Adm"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"GroupName": "App.Saas",
|
"GroupName": "App.Saas",
|
||||||
"Name": "App.Listforms.Listform",
|
"Name": "App.Listforms.Listform",
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,13 @@
|
||||||
"Platform_PublicApi": {
|
"Platform_PublicApi": {
|
||||||
"ClientId": "Platform_PublicApi",
|
"ClientId": "Platform_PublicApi",
|
||||||
"RootUrl": "https://dev.sozsoft.com"
|
"RootUrl": "https://dev.sozsoft.com"
|
||||||
|
},
|
||||||
|
"Platform_Mcp": {
|
||||||
|
"RedirectUris": [
|
||||||
|
"https://claude.ai/api/mcp/auth_callback",
|
||||||
|
"https://claude.com/api/mcp/auth_callback",
|
||||||
|
"https://chatgpt.com/connector_platform_oauth_redirect"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,13 @@
|
||||||
"Platform_PublicApi": {
|
"Platform_PublicApi": {
|
||||||
"ClientId": "Platform_PublicApi",
|
"ClientId": "Platform_PublicApi",
|
||||||
"RootUrl": "https://sozsoft.com"
|
"RootUrl": "https://sozsoft.com"
|
||||||
|
},
|
||||||
|
"Platform_Mcp": {
|
||||||
|
"RedirectUris": [
|
||||||
|
"https://claude.ai/api/mcp/auth_callback",
|
||||||
|
"https://claude.com/api/mcp/auth_callback",
|
||||||
|
"https://chatgpt.com/connector_platform_oauth_redirect"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,13 @@
|
||||||
"Platform_PublicApi": {
|
"Platform_PublicApi": {
|
||||||
"ClientId": "Platform_PublicApi",
|
"ClientId": "Platform_PublicApi",
|
||||||
"RootUrl": "http://localhost:3000"
|
"RootUrl": "http://localhost:3000"
|
||||||
|
},
|
||||||
|
"Platform_Mcp": {
|
||||||
|
"RedirectUris": [
|
||||||
|
"https://claude.ai/api/mcp/auth_callback",
|
||||||
|
"https://claude.com/api/mcp/auth_callback",
|
||||||
|
"https://chatgpt.com/connector_platform_oauth_redirect"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ public enum TableNameEnum
|
||||||
CustomEndpoint,
|
CustomEndpoint,
|
||||||
CustomComponent,
|
CustomComponent,
|
||||||
DynamicService,
|
DynamicService,
|
||||||
|
McpServer,
|
||||||
ReportCategory,
|
ReportCategory,
|
||||||
ReportTemplate,
|
ReportTemplate,
|
||||||
IpRestriction,
|
IpRestriction,
|
||||||
|
|
|
||||||
|
|
@ -513,6 +513,22 @@ public static class PlatformConsts
|
||||||
public const string Publish = DynamicService + ".Publish";
|
public const string Publish = DynamicService + ".Publish";
|
||||||
public const string ViewCode = DynamicService + ".ViewCode";
|
public const string ViewCode = DynamicService + ".ViewCode";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MCP sunucu tanimlarinin yetkileri. <see cref="McpServers.McpServer"/> ekrani gorme
|
||||||
|
/// (okuma) yetkisidir; <see cref="McpServers.Invoke"/> ise /mcp ucundan arac
|
||||||
|
/// cagirma yetkisidir ve kiraci kullanicilarina da verilebilir.
|
||||||
|
/// </summary>
|
||||||
|
public static class McpServers
|
||||||
|
{
|
||||||
|
public const string McpServer = Default + ".McpServers";
|
||||||
|
|
||||||
|
public const string Create = McpServer + ".Create";
|
||||||
|
public const string Update = McpServer + ".Update";
|
||||||
|
public const string Delete = McpServer + ".Delete";
|
||||||
|
public const string Invoke = McpServer + ".Invoke";
|
||||||
|
public const string Write = McpServer + ".Write";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SqlQueryManagers
|
public static class SqlQueryManagers
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ public static class TableNameResolver
|
||||||
{ nameof(TableNameEnum.CustomEndpoint), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.CustomEndpoint), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
||||||
{ nameof(TableNameEnum.CustomComponent), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.CustomComponent), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
||||||
{ nameof(TableNameEnum.DynamicService), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.DynamicService), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
||||||
|
{ nameof(TableNameEnum.McpServer), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
||||||
{ nameof(TableNameEnum.DevRequest), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.DevRequest), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
||||||
|
|
||||||
// 🔹 ADMINISTRATION TABLOLARI
|
// 🔹 ADMINISTRATION TABLOLARI
|
||||||
|
|
|
||||||
|
|
@ -460,6 +460,7 @@ public static class SeedConsts
|
||||||
|
|
||||||
public const string DynamicServices = Default + ".DynamicServices";
|
public const string DynamicServices = Default + ".DynamicServices";
|
||||||
public const string CustomComponents = Default + ".CustomComponents";
|
public const string CustomComponents = Default + ".CustomComponents";
|
||||||
|
public const string McpServers = Default + ".McpServers";
|
||||||
}
|
}
|
||||||
|
|
||||||
public const string Forum = Prefix.App + ".Forum";
|
public const string Forum = Prefix.App + ".Forum";
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||||
using Sozsoft.Languages.EntityFrameworkCore;
|
using Sozsoft.Languages.EntityFrameworkCore;
|
||||||
using Sozsoft.MailQueue.EntityFrameworkCore;
|
using Sozsoft.MailQueue.EntityFrameworkCore;
|
||||||
|
using Sozsoft.Mcp.EntityFrameworkCore;
|
||||||
using Sozsoft.Notifications.EntityFrameworkCore;
|
using Sozsoft.Notifications.EntityFrameworkCore;
|
||||||
using Sozsoft.Platform.Entities;
|
using Sozsoft.Platform.Entities;
|
||||||
using Sozsoft.Platform.Enums;
|
using Sozsoft.Platform.Enums;
|
||||||
|
|
@ -185,6 +186,7 @@ public class PlatformDbContext :
|
||||||
builder.ConfigureMailQueue();
|
builder.ConfigureMailQueue();
|
||||||
builder.ConfigureNotification();
|
builder.ConfigureNotification();
|
||||||
builder.ConfigureSqlQueryManager();
|
builder.ConfigureSqlQueryManager();
|
||||||
|
builder.ConfigureMcp();
|
||||||
|
|
||||||
//Saas
|
//Saas
|
||||||
builder.Entity<AiBot>(b =>
|
builder.Entity<AiBot>(b =>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Sozsoft.Languages.EntityFrameworkCore;
|
using Sozsoft.Languages.EntityFrameworkCore;
|
||||||
|
using Sozsoft.Mcp.EntityFrameworkCore;
|
||||||
using Sozsoft.Notifications.EntityFrameworkCore;
|
using Sozsoft.Notifications.EntityFrameworkCore;
|
||||||
using Sozsoft.Platform.Domain.DeveloperKit;
|
using Sozsoft.Platform.Domain.DeveloperKit;
|
||||||
using Sozsoft.Settings.EntityFrameworkCore;
|
using Sozsoft.Settings.EntityFrameworkCore;
|
||||||
|
|
@ -36,7 +37,8 @@ namespace Sozsoft.Platform.EntityFrameworkCore;
|
||||||
typeof(LanguagesEntityFrameworkCoreModule),
|
typeof(LanguagesEntityFrameworkCoreModule),
|
||||||
typeof(SettingsEntityFrameworkCoreModule),
|
typeof(SettingsEntityFrameworkCoreModule),
|
||||||
typeof(NotificationEntityFrameworkCoreModule),
|
typeof(NotificationEntityFrameworkCoreModule),
|
||||||
typeof(SqlQueryManagerEntityFrameworkCoreModule)
|
typeof(SqlQueryManagerEntityFrameworkCoreModule),
|
||||||
|
typeof(McpEntityFrameworkCoreModule)
|
||||||
)]
|
)]
|
||||||
public class PlatformEntityFrameworkCoreModule : AbpModule
|
public class PlatformEntityFrameworkCoreModule : AbpModule
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Sozsoft.Platform.Migrations
|
namespace Sozsoft.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20260908125150_Initial")]
|
[Migration("20260910132559_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -437,6 +437,89 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Sas_H_BackgroundWorker_MailQueueTableFormat", (string)null);
|
b.ToTable("Sas_H_BackgroundWorker_MailQueueTableFormat", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Sozsoft.Mcp.Entities.McpServer", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<bool>("AllowWrite")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("AllowedTools")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("nvarchar(1024)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreationTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("CreationTime");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CreatorId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("CreatorId");
|
||||||
|
|
||||||
|
b.Property<string>("DataSourceCode")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("nvarchar(64)");
|
||||||
|
|
||||||
|
b.Property<Guid?>("DeleterId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("DeleterId");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletionTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("DeletionTime");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("nvarchar(1024)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("IsDeleted");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastModificationTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("LastModificationTime");
|
||||||
|
|
||||||
|
b.Property<Guid?>("LastModifierId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("LastModifierId");
|
||||||
|
|
||||||
|
b.Property<int>("MaxRows")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasDefaultValue(200);
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("nvarchar(128)");
|
||||||
|
|
||||||
|
b.Property<string>("Tenants")
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("nvarchar(2048)");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("nvarchar(512)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("[IsDeleted] = 0");
|
||||||
|
|
||||||
|
b.ToTable("Sas_H_McpServer", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Notifications.Entities.Notification", b =>
|
modelBuilder.Entity("Sozsoft.Notifications.Entities.Notification", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|
@ -1491,6 +1491,33 @@ namespace Sozsoft.Platform.Migrations
|
||||||
table.PrimaryKey("PK_Sas_H_LogEntry", x => x.Id);
|
table.PrimaryKey("PK_Sas_H_LogEntry", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Sas_H_McpServer",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||||
|
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
Url = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||||
|
Tenants = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
|
||||||
|
DataSourceCode = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||||
|
AllowedTools = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
|
||||||
|
MaxRows = table.Column<int>(type: "int", nullable: false, defaultValue: 200),
|
||||||
|
AllowWrite = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||||
|
Description = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
|
||||||
|
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
|
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||||
|
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Sas_H_McpServer", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Sas_H_Module",
|
name: "Sas_H_Module",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
|
@ -4156,6 +4183,13 @@ namespace Sozsoft.Platform.Migrations
|
||||||
table: "Sas_H_ListFormWorkflow",
|
table: "Sas_H_ListFormWorkflow",
|
||||||
column: "ListFormCode");
|
column: "ListFormCode");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Sas_H_McpServer_Name",
|
||||||
|
table: "Sas_H_McpServer",
|
||||||
|
column: "Name",
|
||||||
|
unique: true,
|
||||||
|
filter: "[IsDeleted] = 0");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sas_H_Menu_Code",
|
name: "IX_Sas_H_Menu_Code",
|
||||||
table: "Sas_H_Menu",
|
table: "Sas_H_Menu",
|
||||||
|
|
@ -4523,6 +4557,9 @@ namespace Sozsoft.Platform.Migrations
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_H_LogEntry");
|
name: "Sas_H_LogEntry");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Sas_H_McpServer");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_H_Menu");
|
name: "Sas_H_Menu");
|
||||||
|
|
||||||
|
|
@ -434,6 +434,89 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Sas_H_BackgroundWorker_MailQueueTableFormat", (string)null);
|
b.ToTable("Sas_H_BackgroundWorker_MailQueueTableFormat", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Sozsoft.Mcp.Entities.McpServer", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<bool>("AllowWrite")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("AllowedTools")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("nvarchar(1024)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreationTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("CreationTime");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CreatorId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("CreatorId");
|
||||||
|
|
||||||
|
b.Property<string>("DataSourceCode")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("nvarchar(64)");
|
||||||
|
|
||||||
|
b.Property<Guid?>("DeleterId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("DeleterId");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletionTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("DeletionTime");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("nvarchar(1024)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("IsDeleted");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastModificationTime")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("LastModificationTime");
|
||||||
|
|
||||||
|
b.Property<Guid?>("LastModifierId")
|
||||||
|
.HasColumnType("uniqueidentifier")
|
||||||
|
.HasColumnName("LastModifierId");
|
||||||
|
|
||||||
|
b.Property<int>("MaxRows")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasDefaultValue(200);
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("nvarchar(128)");
|
||||||
|
|
||||||
|
b.Property<string>("Tenants")
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("nvarchar(2048)");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("nvarchar(512)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("[IsDeleted] = 0");
|
||||||
|
|
||||||
|
b.ToTable("Sas_H_McpServer", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Notifications.Entities.Notification", b =>
|
modelBuilder.Entity("Sozsoft.Notifications.Entities.Notification", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,10 @@
|
||||||
<Folder Include="Dashboard\" />
|
<Folder Include="Dashboard\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\modules\Sozsoft.Mcp\Sozsoft.Mcp.EntityFrameworkCore\Sozsoft.Mcp.EntityFrameworkCore.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using OpenIddict.Abstractions;
|
||||||
|
using OpenIddict.Server;
|
||||||
|
using Sozsoft.Mcp;
|
||||||
|
using static OpenIddict.Server.OpenIddictServerEvents;
|
||||||
|
|
||||||
|
namespace Sozsoft.Platform.Identity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OpenIddict'i uzak MCP istemcilerinin (Claude, ChatGPT connector'lari) bekledigi OAuth
|
||||||
|
/// sozlesmesine uyarlar. Yalnizca MCP istemcisini ilgilendiren davranislar istemci kimligine
|
||||||
|
/// gore sinirlanir; web ve mobil istemcilerin akisina dokunulmaz.
|
||||||
|
/// </summary>
|
||||||
|
public static class McpOAuthServerConfiguration
|
||||||
|
{
|
||||||
|
public static void Configure(OpenIddictServerBuilder server, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var authority = configuration["AuthServer:Authority"];
|
||||||
|
|
||||||
|
// Ters vekil arkasinda istek HTTP olarak gelir; yayinci istekten turetilirse
|
||||||
|
// http://... olur ve istemci kesif belgesindeki issuer'i kaynak meta verisindeki
|
||||||
|
// yetkilendirme sunucusuyla eslestiremez. Yayinci ortam ayarindan sabitlenir.
|
||||||
|
if (!string.IsNullOrWhiteSpace(authority))
|
||||||
|
{
|
||||||
|
server.SetIssuer(new Uri(authority));
|
||||||
|
}
|
||||||
|
|
||||||
|
// MCP istemcileri once RFC 8414 adresini dener; ayni belge iki adresten de sunulur.
|
||||||
|
server.SetConfigurationEndpointUris(
|
||||||
|
".well-known/openid-configuration",
|
||||||
|
".well-known/oauth-authorization-server");
|
||||||
|
|
||||||
|
server.AddEventHandler<HandleConfigurationRequestContext>(builder =>
|
||||||
|
builder.UseInlineHandler(context =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(authority))
|
||||||
|
{
|
||||||
|
context.Metadata["registration_endpoint"] =
|
||||||
|
authority.TrimEnd('/') + McpConsts.RegistrationEndpointPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
})
|
||||||
|
.SetType(OpenIddictServerHandlerType.Custom));
|
||||||
|
|
||||||
|
// MCP istemcileri her istekte RFC 8707 "resource" parametresini (/mcp adresi) gonderir.
|
||||||
|
// OpenIddict kayitsiz kaynaklari reddeder ve kabul etse bile token'in hedef kitlesini o
|
||||||
|
// adrese daraltir; API ise "Platform" kitlesini dogrular. RFC 8707 sunucunun parametreyi
|
||||||
|
// yok saymasina izin verir: MCP istemcisinin istegindeki parametre cikarilir, token
|
||||||
|
// diger istemcilerle ayni kitleyle uretilir.
|
||||||
|
server.AddEventHandler<ExtractAuthorizationRequestContext>(builder =>
|
||||||
|
builder.UseInlineHandler(context =>
|
||||||
|
{
|
||||||
|
RemoveResourceParameter(context.Request);
|
||||||
|
return default;
|
||||||
|
})
|
||||||
|
.SetOrder(int.MaxValue - 100_000)
|
||||||
|
.SetType(OpenIddictServerHandlerType.Custom));
|
||||||
|
|
||||||
|
server.AddEventHandler<ExtractTokenRequestContext>(builder =>
|
||||||
|
builder.UseInlineHandler(context =>
|
||||||
|
{
|
||||||
|
RemoveResourceParameter(context.Request);
|
||||||
|
return default;
|
||||||
|
})
|
||||||
|
.SetOrder(int.MaxValue - 100_000)
|
||||||
|
.SetType(OpenIddictServerHandlerType.Custom));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveResourceParameter(OpenIddictRequest? request)
|
||||||
|
{
|
||||||
|
if (request is null || !McpConsts.IsMcpClient(request.ClientId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.RemoveParameter(OpenIddictConstants.Parameters.Resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Token uretiminde MCP istemcisinin kullanici oturumuna baglanmamasi gerektigini soyler.
|
||||||
|
/// Platform tek oturum kuralini uygular: her giris kullanicinin diger oturumlarini siler.
|
||||||
|
/// Connector token'i bir oturum acarsa her yenilemede kullanicinin web oturumunu duser,
|
||||||
|
/// kullanici web'e girdiginde de connector baglantisi kopar.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsSessionlessClient(string? clientId)
|
||||||
|
=> McpConsts.IsMcpClient(clientId);
|
||||||
|
}
|
||||||
|
|
@ -278,6 +278,11 @@ Your login code: {twoFactorToken}";
|
||||||
[UnitOfWork]
|
[UnitOfWork]
|
||||||
protected override async Task<IActionResult> HandleRefreshTokenAsync(OpenIddictRequest request)
|
protected override async Task<IActionResult> HandleRefreshTokenAsync(OpenIddictRequest request)
|
||||||
{
|
{
|
||||||
|
if (McpOAuthServerConfiguration.IsSessionlessClient(request.ClientId))
|
||||||
|
{
|
||||||
|
return await base.HandleRefreshTokenAsync(request);
|
||||||
|
}
|
||||||
|
|
||||||
var info = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
var info = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||||
var tenantId = await ResolveRefreshTenantIdAsync(info);
|
var tenantId = await ResolveRefreshTenantIdAsync(info);
|
||||||
var userGuid = await ResolveRefreshUserIdAsync(request, info, tenantId);
|
var userGuid = await ResolveRefreshUserIdAsync(request, info, tenantId);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using DevExpress.XtraReports.Web.Extensions;
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
using Hangfire.PostgreSql;
|
using Hangfire.PostgreSql;
|
||||||
using Hangfire.SqlServer;
|
using Hangfire.SqlServer;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Extensions.DependencyInjection;
|
using Microsoft.AspNetCore.Extensions.DependencyInjection;
|
||||||
|
|
@ -24,6 +25,7 @@ using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using OpenIddict.Server.AspNetCore;
|
using OpenIddict.Server.AspNetCore;
|
||||||
using OpenIddict.Validation.AspNetCore;
|
using OpenIddict.Validation.AspNetCore;
|
||||||
|
using Sozsoft.Mcp;
|
||||||
using Sozsoft.Languages;
|
using Sozsoft.Languages;
|
||||||
using Sozsoft.MailQueue;
|
using Sozsoft.MailQueue;
|
||||||
using Sozsoft.Notifications.Application;
|
using Sozsoft.Notifications.Application;
|
||||||
|
|
@ -77,7 +79,9 @@ namespace Sozsoft.Platform;
|
||||||
typeof(AbpAspNetCoreSerilogModule),
|
typeof(AbpAspNetCoreSerilogModule),
|
||||||
typeof(AbpSwashbuckleModule),
|
typeof(AbpSwashbuckleModule),
|
||||||
typeof(AbpBackgroundWorkersHangfireModule),
|
typeof(AbpBackgroundWorkersHangfireModule),
|
||||||
typeof(SqlQueryManagerApplicationModule)
|
typeof(SqlQueryManagerApplicationModule),
|
||||||
|
typeof(McpApplicationModule),
|
||||||
|
typeof(McpHttpApiModule)
|
||||||
)]
|
)]
|
||||||
public class PlatformHttpApiHostModule : AbpModule
|
public class PlatformHttpApiHostModule : AbpModule
|
||||||
{
|
{
|
||||||
|
|
@ -103,6 +107,10 @@ public class PlatformHttpApiHostModule : AbpModule
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Uzak MCP istemcilerinin OAuth sozlesmesi. Uc adresleri ABP'nin kendi sunucu
|
||||||
|
// ayarlarindan sonra uygulanmali; bu yuzden OpenIddictServerBuilder uzerinden verilir.
|
||||||
|
PreConfigure<OpenIddictServerBuilder>(server => McpOAuthServerConfiguration.Configure(server, configuration));
|
||||||
|
|
||||||
PreConfigure<IdentityBuilder>(builder =>
|
PreConfigure<IdentityBuilder>(builder =>
|
||||||
{
|
{
|
||||||
builder.AddClaimsPrincipalFactory<PlatformUserClaimsPrincipalFactory>();
|
builder.AddClaimsPrincipalFactory<PlatformUserClaimsPrincipalFactory>();
|
||||||
|
|
@ -509,6 +517,7 @@ public class PlatformHttpApiHostModule : AbpModule
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
});
|
});
|
||||||
|
app.UseMiddleware<McpAuthenticationChallengeMiddleware>();
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAbpOpenIddictValidation();
|
app.UseAbpOpenIddictValidation();
|
||||||
app.UseMiddleware<CaptchaMiddleware>();
|
app.UseMiddleware<CaptchaMiddleware>();
|
||||||
|
|
@ -552,6 +561,15 @@ public class PlatformHttpApiHostModule : AbpModule
|
||||||
app.UseConfiguredEndpoints(endpoints =>
|
app.UseConfiguredEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
endpoints.MapHub<MessengerHub>("/messengerhub");
|
endpoints.MapHub<MessengerHub>("/messengerhub");
|
||||||
|
|
||||||
|
// MCP (Model Context Protocol) ucu. Kimlik dogrulama, kiraci cozumu ve unit of work
|
||||||
|
// yukaridaki ardisik duzenden gelir; araclar kendi [Authorize] yetkilerini ister.
|
||||||
|
// Sema acikca bearer'a baglanir: varsayilan cerez semasi token'siz istegi giris
|
||||||
|
// sayfasina 302 ile yonlendirir, MCP istemcisi ise 401 bekler.
|
||||||
|
endpoints.MapMcp("/mcp").RequireAuthorization(new AuthorizeAttribute
|
||||||
|
{
|
||||||
|
AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@
|
||||||
<ProjectReference Include="..\Sozsoft.Platform.EntityFrameworkCore\Sozsoft.Platform.EntityFrameworkCore.csproj" />
|
<ProjectReference Include="..\Sozsoft.Platform.EntityFrameworkCore\Sozsoft.Platform.EntityFrameworkCore.csproj" />
|
||||||
<ProjectReference Include="..\Sozsoft.Platform.HttpApi\Sozsoft.Platform.HttpApi.csproj" />
|
<ProjectReference Include="..\Sozsoft.Platform.HttpApi\Sozsoft.Platform.HttpApi.csproj" />
|
||||||
<ProjectReference Include="..\..\modules\Sozsoft.SqlQueryManager\Sozsoft.SqlQueryManager.Application\Sozsoft.SqlQueryManager.Application.csproj" />
|
<ProjectReference Include="..\..\modules\Sozsoft.SqlQueryManager\Sozsoft.SqlQueryManager.Application\Sozsoft.SqlQueryManager.Application.csproj" />
|
||||||
|
<ProjectReference Include="..\..\modules\Sozsoft.Mcp\Sozsoft.Mcp.Application\Sozsoft.Mcp.Application.csproj" />
|
||||||
|
<ProjectReference Include="..\..\modules\Sozsoft.Mcp\Sozsoft.Mcp.HttpApi\Sozsoft.Mcp.HttpApi.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
23
claude.md
23
claude.md
|
|
@ -182,6 +182,29 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
|
||||||
Ayrıntı: `dotnet.instructions.md` §5.
|
Ayrıntı: `dotnet.instructions.md` §5.
|
||||||
- **Yeni bir yetki** → `PlatformConsts.AppCodes` + `PermissionsData.json` + (UI'da kullanılacaksa)
|
- **Yeni bir yetki** → `PlatformConsts.AppCodes` + `PermissionsData.json` + (UI'da kullanılacaksa)
|
||||||
`permission.constant.ts` + `LanguagesData.json`.
|
`permission.constant.ts` + `LanguagesData.json`.
|
||||||
|
- **`/mcp` ucuna yeni bir araç** → üç yer birlikte değişir: `McpToolNames` (ad kayıt defteri),
|
||||||
|
ilgili `Mcp*Tools` sınıfı (`Sozsoft.Mcp.HttpApi`) ve arkasındaki AppService sözleşmesi
|
||||||
|
(`Sozsoft.Mcp.Application.Contracts`). Arac adı `McpToolNames.All` içinde yoksa araç listesi
|
||||||
|
süzgecinden geçmez ve hiç görünmez; MCP Servers ekranındaki `AllowedTools` seçenekleri de
|
||||||
|
`SelectableTools`'dan üretildiği için kendiliğinden güncellenir. Yazma araçları
|
||||||
|
`WriteTools` listesine de eklenir, yoksa `AllowWrite` kapalıyken de yayınlanırlar.
|
||||||
|
Kapı iki yerdedir: `AddListToolsFilter` listeyi süzer, `McpAppService.EnsureToolAllowedAsync`
|
||||||
|
çağrıyı reddeder — süzme tek başına yetki sınırı değildir, ikisi birlikte yazılır.
|
||||||
|
- **MCP şema araçları** → tablo/sütun bilgisi canlı katalogdan değil `Sas_H_DbTableColumn`
|
||||||
|
sözlüğünden okunur (`IMcpSchemaDictionary`). Sözlüğün alan adları
|
||||||
|
`dbo.Sas_H_ListFormFieldsDictionaryJson` prosedürünün ürettiği JSON ile birebir aynıdır;
|
||||||
|
prosedür değişirse `McpDictionaryListForm`/`McpDictionaryField` de değişir.
|
||||||
|
- **MCP OAuth** → bilinen sağlayıcılar (Claude, ChatGPT) seed edilmiş `Platform_Mcp` istemcisini,
|
||||||
|
diğer MCP istemcileri `/connect/register` ile üretilen `mcp_` önekli açık onaylı istemcileri
|
||||||
|
kullanır (`McpOAuthClientManager`; seed `McpOAuthClientDataSeedContributor`). Bilinen sağlayıcı
|
||||||
|
eklemek = DbMigrator'daki `Platform_Mcp:RedirectUris` listesine callback eklemek. MCP
|
||||||
|
istemcisi kararı tek yerdedir (`McpConsts.IsMcpClient`): `resource` parametresi yok sayılır ve
|
||||||
|
token kullanıcı oturumuna bağlanmaz; token ucuna yeni bir oturum yolu eklenirse bu istisna
|
||||||
|
korunur, yoksa connector web oturumunu düşürür.
|
||||||
|
- **MCP yetki vermez** → yeni bir MCP aracı kullanıcının uygulamadaki yetkisini aşamaz: liste verisi
|
||||||
|
ekranın sorgusundan (`IListFormSelectAppService`/`IListFormDataAppService`) geçer; liste
|
||||||
|
yetkisini aşan bir yetenek (ham SQL gibi) `McpToolNames.RequiredPermissions`'a uygulamadaki
|
||||||
|
karşılık gelen yetkiyle eklenir.
|
||||||
- **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
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,22 @@ server {
|
||||||
proxy_send_timeout 3600;
|
proxy_send_timeout 3600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# MCP (Model Context Protocol) ucu. Streamable HTTP yanitlari SSE akisidir: location /
|
||||||
|
# altindaki tamponlama akisi bekletip istemcinin zaman asimina dusmesine yol acar,
|
||||||
|
# bu yuzden ayri bir blokta tamponlama kapatilir.
|
||||||
|
location /mcp {
|
||||||
|
proxy_pass http://127.0.0.1:8080/mcp;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header __tenant $http___tenant;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_cache off;
|
||||||
|
chunked_transfer_encoding on;
|
||||||
|
proxy_read_timeout 3600;
|
||||||
|
proxy_send_timeout 3600;
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://127.0.0.1:8080/;
|
proxy_pass http://127.0.0.1:8080/;
|
||||||
include /etc/nginx/proxy_params;
|
include /etc/nginx/proxy_params;
|
||||||
|
|
|
||||||
20
configs/seeds/host/data/App.DeveloperKit.McpServers.json
Normal file
20
configs/seeds/host/data/App.DeveloperKit.McpServers.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"ListFormCode": "App.DeveloperKit.McpServers",
|
||||||
|
"KeyFieldName": "Id",
|
||||||
|
"GeneratedAt": "2026-09-10T00:00:00Z",
|
||||||
|
"Order": 0,
|
||||||
|
"Rows": [
|
||||||
|
{
|
||||||
|
"Id": "8f3a41d2-6b1c-4e57-9a0d-2c7f5e84b310",
|
||||||
|
"Name": "Sozsoft Platform",
|
||||||
|
"IsActive": true,
|
||||||
|
"Url": "https://api.sozsoft.com/mcp",
|
||||||
|
"Tenants": null,
|
||||||
|
"DataSourceCode": null,
|
||||||
|
"AllowedTools": null,
|
||||||
|
"MaxRows": 200,
|
||||||
|
"AllowWrite": false,
|
||||||
|
"Description": "Default read-only MCP endpoint. Tools are gated by the App.DeveloperKit.McpServers.Invoke permission; turn AllowWrite on only for tenants that should write data through MCP."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue