@appaflytech/zeyrek-ai-mcp 0.0.54 → 0.0.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/config.js CHANGED
@@ -32,6 +32,7 @@ export const moduleNames = [
32
32
  "dependency",
33
33
  "deployment",
34
34
  "environment",
35
+ "customDomain",
35
36
  "merge",
36
37
  "activity",
37
38
  "revision",
@@ -70,6 +71,7 @@ export const modulePresets = {
70
71
  "access",
71
72
  "dependency",
72
73
  "environment",
74
+ "customDomain",
73
75
  "merge",
74
76
  "activity",
75
77
  "revision",
@@ -106,7 +108,7 @@ export const modulePresets = {
106
108
  "mobile",
107
109
  "assistant",
108
110
  ],
109
- admin: ["platform", "template", "organization", "deployment", "environment", "merge", "activity"],
111
+ admin: ["platform", "template", "organization", "deployment", "environment", "customDomain", "merge", "activity"],
110
112
  };
111
113
  function expandModuleNames(entries) {
112
114
  const modules = [];
@@ -0,0 +1,76 @@
1
+ import { isIP } from 'node:net';
2
+ import { domainToASCII } from 'node:url';
3
+ import { getDomain } from 'tldts-icann';
4
+ const wildcardPrefix = '*.';
5
+ const hostPattern = /^(?=.{4,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]([a-z0-9-]{0,61}[a-z0-9])$/;
6
+ const pendingStates = new Set(['pending_dns', 'verifying']);
7
+ export function normalizeHost(host) {
8
+ const trimmed = host.trim().replace(/\.$/, '').toLowerCase();
9
+ const isWildcard = trimmed.startsWith(wildcardPrefix);
10
+ const name = isWildcard ? trimmed.slice(wildcardPrefix.length) : trimmed;
11
+ const ascii = domainToASCII(name) || name;
12
+ return isWildcard ? `${wildcardPrefix}${ascii}` : ascii;
13
+ }
14
+ export const isPattern = (host) => host.startsWith(wildcardPrefix);
15
+ // Studio'nun kuralıyla aynı (`apps/studio/src/features/custom-domain/custom-domain.functions.ts` `isApex`): kök public
16
+ // suffix listesinden gelir (kök `docs/adr/0027`), `firma.com.tr` apex'tir. Yalnız DNS yönergesinin kayıt tipini seçer, reddetmez.
17
+ export const isApex = (host) => {
18
+ const name = isPattern(host) ? host.slice(wildcardPrefix.length) : host;
19
+ return (getDomain(name) ?? name) === host;
20
+ };
21
+ /** Backend'in reddedeceği bir host için ne · neden · ne yapılacak; geçen host için null. */
22
+ export function hostProblem(host, kind, platformBaseDomains) {
23
+ const normalized = normalizeHost(host);
24
+ const name = isPattern(normalized) ? normalized.slice(wildcardPrefix.length) : normalized;
25
+ if (isPattern(normalized) && kind !== 'pattern') {
26
+ return `'${host}' bir dal kalıbı (joker '*.'): joker yalnız custom_domain_pattern_create ile açılır, bu araçta kullanılmaz.`;
27
+ }
28
+ if (!isPattern(normalized) && kind === 'pattern') {
29
+ return `'${host}' kalıp değil: dal kalıbı '*.' ile başlar (ör. *.ornek.com). Tek bir adres için custom_domain_create kullanın.`;
30
+ }
31
+ if (!name.includes('.') || isIP(name) !== 0 || platformBaseDomains.some(base => name === base || name.endsWith(`.${base}`))) {
32
+ return `'${host}': platform alan adı altına kayıt açılamaz — platform adresi (${platformBaseDomains.join(', ')}), noktasız ad ve IP özel alan adı olamaz. Müşterinin kendi alan adını verin; platform adresi uygulamayı zaten sunar.`;
33
+ }
34
+ if (!hostPattern.test(name)) {
35
+ return `'${host}' geçerli bir alan adı değil: harf/rakam/tire etiketleri, en az bir nokta, 4-253 karakter, son etiket harfle başlar.`;
36
+ }
37
+ return null;
38
+ }
39
+ /** Müşterinin DNS sağlayıcısına gireceği kayıtlar; hedef her zaman backend'in verdiği `cnameTarget`'tır. */
40
+ export function dnsRecords(domain) {
41
+ const records = [];
42
+ if (domain.cnameTarget) {
43
+ records.push({ type: isApex(domain.host) ? 'ALIAS / CNAME flattening' : 'CNAME', name: domain.host, value: domain.cnameTarget });
44
+ }
45
+ if (domain.ownershipTxtName && domain.ownershipTxtValue) {
46
+ records.push({ type: 'TXT', name: domain.ownershipTxtName, value: domain.ownershipTxtValue });
47
+ }
48
+ return records;
49
+ }
50
+ export const dnsTable = (records) => records.map(record => `${record.type} ${record.name} ${record.value}`).join('\n');
51
+ export const isPending = (state) => pendingStates.has(String(state));
52
+ // Yanıt bütçesi (41 §5): DNS yönergesi yalnız müşterinin hâlâ kayıt girmesi gereken satırda taşınır.
53
+ const needsRecords = (state) => isPending(state) || state === 'error';
54
+ export const withDns = (row) => needsRecords(row.state) ? { ...row, dns: dnsRecords(row) } : row;
55
+ /** Tek kayıt dönen yazma yanıtı: satır + DNS yönergesi (düz metin tablo da, ajan kullanıcıya iletebilsin) + sonraki adım. */
56
+ export function domainResponse(payload, note) {
57
+ const envelope = payload && typeof payload === 'object' ? payload : {};
58
+ const row = envelope.data;
59
+ if (!row || typeof row !== 'object')
60
+ return { ...envelope, note };
61
+ const records = dnsRecords(row);
62
+ return { ...envelope, data: { ...row, dns: records }, dnsTable: dnsTable(records), note };
63
+ }
64
+ export function listResponse(payload) {
65
+ const envelope = payload && typeof payload === 'object' ? payload : {};
66
+ const rows = Array.isArray(envelope.data) ? envelope.data : [];
67
+ return { ...envelope, data: rows.map(withDns), note: noteFor(rows) };
68
+ }
69
+ /** Listede ajanın bir sonraki adımı; bekleyen ve bağsız satır yoksa not yok. */
70
+ export function noteFor(rows) {
71
+ const notes = [
72
+ rows.some(row => isPending(row.state)) ? 'doğrulama sürüyor; 60 sn sonra custom_domain_list ile yeniden çağır, state active olunca adres yayında' : '',
73
+ rows.some(row => row.state === 'unbound') ? 'ortam silinmiş; custom_domain_bind ile bağla' : ''
74
+ ].filter(Boolean);
75
+ return notes.length > 0 ? notes.join(' · ') : undefined;
76
+ }
@@ -111,6 +111,30 @@ function templateSubscriptionHint(code) {
111
111
  return '';
112
112
  return ' Bu proje şablon güncellemelerini alıyor; dal açma, dal silme ve merge planının hiçbir adımı bu projede yapılamaz. Değişikliği şablonun kaynak projesinde yapın, template_create ile yeni sürüm yayımlayın, bu projede template_check_updates → template_receive_updates ile alın. Aynı çağrıyı tekrarlamak sonucu değiştirmez.';
113
113
  }
114
+ /**
115
+ * Özel alan adı retlerinin her biri başka bir araca ya da başka bir id'ye gönderir; kod olmadan
116
+ * "400" diye okunur ve ajan aynı host'u tekrar dener. Kodlar: backend `CustomDomainExceptions.cs`,
117
+ * durumlar `ExceptionStatusMap.cs` (HostTaken 409, Provider 502, kalanı 400).
118
+ */
119
+ function customDomainHint(code) {
120
+ if (code === 'CustomDomainHostTakenException')
121
+ return " Bu host başka bir kayıtta (bu ya da başka bir kuruluşta). custom_domain_list ile bakın: aynı projedeyse var olan kaydı custom_domain_bind ile istediğiniz ortama bağlayın, yeni kayıt açmayın.";
122
+ if (code === 'PlatformHostCantBeCustomDomainException')
123
+ return ' Platformun kendi alan adı ve altı, noktasız ad ve IP özel alan adı olamaz. Müşterinin kendi alan adını verin; platform adresi uygulamayı zaten sunar.';
124
+ if (code === 'InvalidCustomDomainHostException')
125
+ return " Host biçimi geçersiz: harf/rakam/tire etiketleri, en az bir nokta. Joker (*.) yalnız custom_domain_pattern_create'te; kalıp da yalnız jokerle açılır.";
126
+ if (code === 'CustomDomainBindingOutsideProjectException')
127
+ return " applicationId / environmentId / hedef bu projenin değil. application_list ve environment_list ile bu projenin id'lerini alın.";
128
+ if (code === 'CustomDomainRedirectTargetInvalidException')
129
+ return " Yönlendirme hedefi aynı projede, yayında (state active), kind primary bir kayıt olmalı. custom_domain_list'ten o satırın id'sini verin.";
130
+ if (code === 'InvalidCustomDomainStateException')
131
+ return " Kaydın durumu bu işlemi almıyor: removing kayıt bağlanmaz ve denetlenmez, unbound kayıt önce custom_domain_bind ister, kalıbın çocuğu ve yönlendirme bağlanmaz. custom_domain_list ile state ve kind'a bakın.";
132
+ if (code === 'InvalidCustomDomainBindingException')
133
+ return ' Bağlama türle uyuşmuyor: birincil uygulama + ortam ister, kalıp yalnız uygulama, yönlendirme yalnız hedef alan adı.';
134
+ if (code === 'CustomDomainProviderException')
135
+ return ' Sertifika sağlayıcısı isteği reddetti ya da ulaşılamadı; kayıt değişmedi. Bir dakika sonra custom_domain_verify ile yeniden deneyin; tekrarlarsa platform_gap_report.';
136
+ return '';
137
+ }
114
138
  /**
115
139
  * İçe aktarma zinciri dört çağrıdır ve her hata farklı bir adıma geri gönderir: plan süresi
116
140
  * dolduysa analizden, anahtar benzersiz değilse plan gövdesinden, hata oranı yüksekse dosyanın
@@ -401,7 +425,7 @@ export function mapPlatformError(status, payload, context) {
401
425
  if (validationMessages.length > 0) {
402
426
  return new PlatformApiError(`Doğrulama hatası (${location}): ${validationMessages.join(' ; ')}. Gövde şeması için ilgili ngapps://schemas kaynağına ya da mevcut bir kaydın *_get çıktısına bakın.`, status, code);
403
427
  }
404
- return new PlatformApiError(`Geçersiz istek (${location}): ${apiMessage ?? 'API gövdeyi reddetti.'}${mergeRevertHint(code)}${templateSubscriptionHint(code)}${importHint(code, context.path)}${accessHint(code, apiMessage)}`, status, code);
428
+ return new PlatformApiError(`Geçersiz istek (${location}): ${apiMessage ?? 'API gövdeyi reddetti.'}${mergeRevertHint(code)}${templateSubscriptionHint(code)}${importHint(code, context.path)}${accessHint(code, apiMessage)}${customDomainHint(code)}`, status, code);
405
429
  }
406
430
  if (status === 401) {
407
431
  // Uygulama oturumu 401'i gateway kimliğinin değil uygulama kullanıcısının sorunudur; USERNAME/PASSWORD
@@ -467,7 +491,7 @@ export function mapPlatformError(status, payload, context) {
467
491
  : '';
468
492
  const validationMessages = collectValidationMessages(body);
469
493
  const detail = validationMessages.length > 0 ? ` → ${validationMessages.join(' ; ')}` : '';
470
- const rejected = new PlatformApiError(`İş kuralı reddetti (${location}): ${apiMessage ?? 'çakışma'}${code ? ` [${code}]` : ''}${detail}${projectConflictHint}${pageSchemaHint(code, context.path)}${processHint(code, context.path)}${bulkRowHint(code)}${importHint(code, context.path)}${versionConflictHint(code, context.path)}${mergeHint(context.path)}${accessHint(code, apiMessage)}${microfunctionArchiveHint(code)}`, status, code);
494
+ const rejected = new PlatformApiError(`İş kuralı reddetti (${location}): ${apiMessage ?? 'çakışma'}${code ? ` [${code}]` : ''}${detail}${projectConflictHint}${pageSchemaHint(code, context.path)}${processHint(code, context.path)}${bulkRowHint(code)}${importHint(code, context.path)}${versionConflictHint(code, context.path)}${mergeHint(context.path)}${accessHint(code, apiMessage)}${microfunctionArchiveHint(code)}${customDomainHint(code)}`, status, code);
471
495
  if (typeof body.rowIndex === 'number')
472
496
  rejected.rowIndex = body.rowIndex;
473
497
  return rejected;
@@ -476,7 +500,7 @@ export function mapPlatformError(status, payload, context) {
476
500
  const fallbackDetail = fallbackValidationMessages.length > 0 ? ` → ${fallbackValidationMessages.join(' ; ')}` : '';
477
501
  const errorBody = status >= 500 ? describeErrorBody(body, { code, message: apiMessage }) : [];
478
502
  const errorBodyDetail = errorBody.length > 0 ? ` — ${errorBody.join(' · ')}` : '';
479
- return new PlatformApiError(`Platform hatası ${status} (${location}): ${apiMessage ?? 'beklenmeyen hata'}${code ? ` [${code}]` : ''}${errorBodyDetail}${fallbackDetail}${diagnoseKnownFailure(status, apiMessage ?? '', code, context)}`, status, code);
503
+ return new PlatformApiError(`Platform hatası ${status} (${location}): ${apiMessage ?? 'beklenmeyen hata'}${code ? ` [${code}]` : ''}${errorBodyDetail}${fallbackDetail}${diagnoseKnownFailure(status, apiMessage ?? '', code, context)}${customDomainHint(code)}`, status, code);
480
504
  }
481
505
  const CONNECTION_RESET_CODES = new Set(['ECONNRESET', 'UND_ERR_SOCKET', 'EPIPE', 'ECONNABORTED']);
482
506
  /**
@@ -0,0 +1,56 @@
1
+ # Özel Alan Adı Rehberi
2
+
3
+ Bir uygulamanın bir ortamını müşterinin kendi alan adında (`demo.ornek.com`) yayımlamak. Kayıt platformun
4
+ çekirdek tablosundadır; ortamın verisine dokunmaz. Studio'da proje › Ayarlar › **Alan adları** ekranının
5
+ yaptığı her şey `custom_domain_*` araçlarıyla yapılır.
6
+
7
+ ## Dört kural (değişmez)
8
+
9
+ 1. **Platform alan adı yasak.** Platformun kendi tabanı (kurulumda `zeyrek.app`, lokalde `zeyrek.local`) ve
10
+ altı, noktasız ad ve IP özel alan adı olamaz. Platform adresi uygulamayı zaten sunar; özel alan adı onun
11
+ **yanına** gelir, yerine geçmez.
12
+ 2. **Apex ALIAS ister.** Kökün kendisi (`ornek.com`, `firma.com.tr`; kök public suffix listesinden bulunur) çoğu DNS
13
+ sağlayıcısında CNAME alamaz: ALIAS /
14
+ ANAME / CNAME flattening kullanılır. Sağlayıcı bunu bilmiyorsa `www.ornek.com`'u birincil açıp `ornek.com`'u
15
+ ona yönlendirme (`custom_domain_redirect_create`) olarak açın.
16
+ 3. **Dal kalıbı kotadan düşer.** `*.onizleme.ornek.com` her açık dal için `<dal-slug>.onizleme.ornek.com`
17
+ çocuğunu kendisi açar ve dal silinince düşürür; her çocuk sağlayıcıda ayrı hostname'dir ve kurulumun
18
+ ücretsiz kotasından (100) düşer. 31 karakteri aşan ya da `www` / `zeyrek-check` olan dal slug'ı çocuk almaz.
19
+ 4. **Şablon abonesi serbest, alan adı şablon içeriği değil.** Şablondan kurulup güncelleme alan proje kendi
20
+ alan adını ekler, bağlar, kaldırır; şablon güncellemesi alan adını taşımaz ve bozmaz. Şablonun kaynak
21
+ projesindeki alan adı abonelere kopyalanmaz.
22
+
23
+ ## Sıra
24
+
25
+ 1. `application_list` + `environment_list` → uygulamanın ve ortamın id'si (production kabul edilir).
26
+ 2. `custom_domain_create { projectId, host, applicationId, environmentId }` → kayıt `pending_dns`; yanıt
27
+ `dns` satırlarını ve `dnsTable` düz metnini taşır. Bu kayıtları **müşteri** kendi DNS sağlayıcısına girer;
28
+ ajan DNS'e yazamaz.
29
+ 3. `custom_domain_list` ile 60 sn arayla okuyun: `pending_dns → verifying → active`. `active` olunca adres
30
+ yayındadır. Beklemeyi kısaltmak için `custom_domain_verify`.
31
+ 4. Başka ortama taşımak: `custom_domain_bind { id, applicationId, environmentId }` — host ve DNS değişmez.
32
+ 5. Kaldırmak: `custom_domain_delete` → `removing` → listeden çıkar. Müşterinin DNS kaydı kalır.
33
+
34
+ ## Durumlar
35
+
36
+ | state | Anlamı | Sonraki adım |
37
+ |---|---|---|
38
+ | `pending_dns` | DNS kaydı henüz platformu göstermiyor | Müşteri `dnsTable`'ı girer; bekle |
39
+ | `verifying` | DNS doğru, sertifika alınıyor | Bekle |
40
+ | `active` | Yayında | — |
41
+ | `error` | 72 saatte DNS gelmedi ya da sağlayıcı reddetti; `error` alanı sebebi söyler | Sebebi gider, `custom_domain_verify` (baştan başlar) |
42
+ | `unbound` | Bağlı ortam silindi ya da uygulama kaldırıldı; host ve DNS korunur | `custom_domain_bind` |
43
+ | `removing` | Sağlayıcıdan düşürülüyor | Bekle; bu kayıtta başka işlem yok |
44
+
45
+ ## Yönlendirme ve kalıp
46
+
47
+ - `custom_domain_redirect_create { host, targetDomainId }`: 301, yol ve sorgu korunur. Hedef aynı projede
48
+ `active` bir **birincil** kayıttır; hedef silinince yönlendirme de silinir.
49
+ - `custom_domain_pattern_create { host: '*.…', applicationId }`: dallar içindir; ortam seçilmez. Çocuklar
50
+ `custom_domain_list`'te `parentDomainId` ile görünür ve elle bağlanmaz.
51
+
52
+ ## Tuzaklar
53
+
54
+ - Özel alan adı kapalı kurulumda (`CustomDomains:Enabled=false`) uçlar 404 döner; araç değil kurulum ayarıdır.
55
+ - Bağlama ya da kaldırma adresin sunduğu ortamı en geç 30 sn içinde değiştirir (çözüm önbelleği).
56
+ - Aynı host ikinci kez açılamaz (409); aynı projedeyse var olan kaydı `custom_domain_bind` ile taşıyın.
@@ -31,7 +31,7 @@ tetikleyen satır `row` / `previousRow` / `user`.
31
31
 
32
32
  | Çağrı | Ne yapar | Gerçek örnek (qm) |
33
33
  |---|---|---|
34
- | `system.getPageUrl(path)` | Sayfanın environment'a göre doğru serve URL'ini üretir — e-posta/redirect linki için TEK doğru yol, sabit URL yazma | `system.getPageUrl(`/web/dif-kayit?id=${row.finding_record}`)` |
34
+ | `system.getPageUrl(path)` | Sayfanın environment'a göre doğru serve URL'ini üretir — e-posta/redirect linki için TEK doğru yol, sabit URL yazma. İstek uygulamanın özel alan adından geldiyse adres o alan adında döner (`https://demo.firma.com/dif-kayit?id=…`); istek yokken (zamanlanmış iş, kuyruk) sayfayı gösteren aktif alan adı varsa yine o, yoksa platform adresi | `system.getPageUrl(`/web/dif-kayit?id=${row.finding_record}`)` |
35
35
  | `system.generateToken({claims})` | Org secret'ıyla JWT üretir — sign-in ucunun kalbi | `{ access_token: system.generateToken({ userId: user.data.id, email: user.data.email }) }` |
36
36
  | `system.validateToken(token)` | Org secret'ıyla doğrular; dönüş `{ isValid, claims }` — **claim'ler bir seviye içeride** ve anahtarları `generateToken`'a verdiğin sözlüğün anahtarları (`r.claims.userId`); `r.id` YOK | `const r = system.validateToken(cookie.APP_Token)` → condition: `token.data.isValid && token.data.claims.userId` |
37
37
  | `file.readAsBase64(fileId)` | Drive dosyasını `data:<mime>;base64,…` döner | şablon okuma |
package/dist/server.js CHANGED
@@ -36,6 +36,7 @@ import { registerFileTools } from "./tools/files.js";
36
36
  import { registerRowTools } from "./tools/rows.js";
37
37
  import { registerImportTools } from "./tools/imports.js";
38
38
  import { registerEnvironmentTools } from "./tools/environments.js";
39
+ import { registerCustomDomainTools } from "./tools/customDomains.js";
39
40
  import { registerMergeTools } from "./tools/merges.js";
40
41
  import { registerRevisionTools } from "./tools/revisions.js";
41
42
  import { registerActivityTools } from "./tools/activities.js";
@@ -95,6 +96,7 @@ const moduleRegistrations = {
95
96
  dependency: registerDependencyTools,
96
97
  deployment: registerDeploymentTools,
97
98
  environment: registerEnvironmentTools,
99
+ customDomain: registerCustomDomainTools,
98
100
  merge: registerMergeTools,
99
101
  activity: registerActivityTools,
100
102
  revision: registerRevisionTools,
@@ -0,0 +1,73 @@
1
+ import { destructive, guid, readOnly, updates, writes } from './common.js';
2
+ import { domainResponse, hostProblem, listResponse, normalizeHost } from '../knowledge/customDomain.js';
3
+ import { PlatformApiError } from '../platform/errors.js';
4
+ import { handle } from '../platform/responses.js';
5
+ import { z } from 'zod';
6
+ const host = z.string().min(1).max(253)
7
+ .describe("Müşterinin alan adı (ör. demo.ornek.com). Gateway kırpar, küçültür, IDN'yi punycode'a çevirir; platformun kendi alan adı ve altı reddedilir.");
8
+ const pendingNote = 'DNS kayıtları (dnsTable) müşterinin DNS sağlayıcısına girilir; doğrulama asenkron — custom_domain_list ile 60 sn arayla okuyun, state active olunca adres yayında.';
9
+ // Platform tabanı kuruluşa göre değişir; yalnız backend bilir (`CustomDomains:BaseDomains`), uç `mounts` onu yayımlar.
10
+ async function assertHost(platform, tool, value, kind) {
11
+ const mounts = await platform.org('GET', '/custom-domains/mounts');
12
+ const problem = hostProblem(value, kind, mounts?.platformBaseDomains ?? []);
13
+ if (problem)
14
+ throw new PlatformApiError(`${tool}: ${problem}`, 400);
15
+ return normalizeHost(value);
16
+ }
17
+ export function registerCustomDomainTools(server, platform) {
18
+ const config = platform.config;
19
+ server.registerTool('custom_domain_list', {
20
+ title: 'Özel alan adları',
21
+ description: "Projenin özel alan adlarını döner (birincil · yönlendirme · dal kalıbı ve kalıbın çocukları, parentDomainId ile). Her satırda state: pending_dns | verifying | active | error | unbound | removing; yalnız active yayındadır. DNS'i bekleyen ya da hatalı satır dns yönergesini taşır. Bekleyen satır varsa note '60 sn sonra yeniden çağır' der; unbound satır ortamı silinmiş ya da uygulaması kaldırılmış kayıttır, custom_domain_bind ile bağlanır. Özel alan adı kapalı kurulumda uç 404 döner.",
22
+ inputSchema: { projectId: guid },
23
+ annotations: readOnly
24
+ }, handle(config, async ({ projectId }) => listResponse(await platform.org('GET', `/custom-domains/${projectId}`))));
25
+ server.registerTool('custom_domain_create', {
26
+ title: 'Özel alan adı ekle',
27
+ description: "Bir uygulamanın bir ortamını müşterinin alan adında yayımlar (definition = CreateCustomDomainCommand: { host, applicationId, environmentId }). Kayıt pending_dns doğar; yanıt müşterinin gireceği DNS kayıtlarını dns satırlarında ve dnsTable düz metninde verir. production ortamı kabul edilir: kayıt CORE tablosuna yazılır, ortamın verisine dokunmaz. Joker (*.) host bu araçta reddedilir — dal kalıbı custom_domain_pattern_create'tir; tek adresi başka adrese yönlendirmek custom_domain_redirect_create'tir. Şablon abonesi proje de alan adını kendisi yönetir (alan adı şablon içeriği değildir).",
28
+ inputSchema: { projectId: guid, host, applicationId: guid, environmentId: guid },
29
+ annotations: writes
30
+ }, handle(config, async ({ projectId, host: value, applicationId, environmentId }) => {
31
+ const normalized = await assertHost(platform, 'custom_domain_create', value, 'primary');
32
+ return domainResponse(await platform.org('POST', `/custom-domains/${projectId}`, { body: { host: normalized, applicationId, environmentId } }), pendingNote);
33
+ }));
34
+ server.registerTool('custom_domain_pattern_create', {
35
+ title: 'Dal kalıbı ekle',
36
+ description: "Bir uygulamanın dallarını tek kalıpla yayımlar: '*.onizleme.ornek.com' her açık dal için '<dal-slug>.onizleme.ornek.com' çocuğunu kendiliğinden açar, dal silinince düşürür (definition = CreateCustomDomainPatternCommand: { host, applicationId }). Müşteri tek joker CNAME girer. Her çocuk sağlayıcıda ayrı hostname'dir ve kotadan düşer; 31 karakteri aşan ya da 'www'/'zeyrek-check' olan dal slug'ı çocuk almaz. Kalıp joker olmayan host'u reddeder.",
37
+ inputSchema: { projectId: guid, host: host.describe("Joker host: '*.' ile başlar (ör. *.onizleme.ornek.com)."), applicationId: guid },
38
+ annotations: writes
39
+ }, handle(config, async ({ projectId, host: value, applicationId }) => {
40
+ const normalized = await assertHost(platform, 'custom_domain_pattern_create', value, 'pattern');
41
+ return domainResponse(await platform.org('POST', `/custom-domains/${projectId}/pattern`, { body: { host: normalized, applicationId } }), pendingNote);
42
+ }));
43
+ server.registerTool('custom_domain_redirect_create', {
44
+ title: 'Yönlendirme ekle',
45
+ description: "Bir alan adını aynı projedeki yayında (active) birincil alan adına 301 ile yönlendirir (ör. www.ornek.com → ornek.com; definition = CreateCustomDomainRedirectCommand: { host, targetDomainId }). Yol ve sorgu korunur. Hedef silinince yönlendirme de silinir. Hedef id'si custom_domain_list'te kind: 'primary', state: 'active' satırıdır.",
46
+ inputSchema: { projectId: guid, host, targetDomainId: guid.describe('Yönlendirmenin hedefi: aynı projede active, kind primary bir kaydın id\'si') },
47
+ annotations: writes
48
+ }, handle(config, async ({ projectId, host: value, targetDomainId }) => {
49
+ const normalized = await assertHost(platform, 'custom_domain_redirect_create', value, 'redirect');
50
+ return domainResponse(await platform.org('POST', `/custom-domains/${projectId}/redirect`, { body: { host: normalized, targetDomainId } }), pendingNote);
51
+ }));
52
+ server.registerTool('custom_domain_verify', {
53
+ title: 'Alan adını şimdi denetle',
54
+ description: "DNS'i ve sertifikayı sıradaki tur beklenmeden şimdi denetler; error durumundaki kayıt baştan başlar. Arka plan zaten dakikada bir denetler, bu yalnız beklemeyi kısaltır. unbound ve removing kayıt denetlenmez (400).",
55
+ inputSchema: { id: guid.describe('Alan adı kaydının id\'si (custom_domain_list)') },
56
+ annotations: updates
57
+ }, handle(config, async ({ id }) => domainResponse(await platform.org('POST', `/custom-domains/${id}/verify`), pendingNote)));
58
+ server.registerTool('custom_domain_bind', {
59
+ title: 'Alan adını başka ortama bağla',
60
+ description: "Birincil alan adını aynı projede başka bir uygulama ortamına bağlar (definition = BindCustomDomainCommand: { applicationId, environmentId }); unbound kayıt böyle yeniden yayına girer. Host değişmez, DNS'e dokunulmaz. Yalnız kind primary; kalıbın çocukları ve yönlendirmeler bağlanmaz.",
61
+ inputSchema: { id: guid.describe('Alan adı kaydının id\'si (custom_domain_list)'), applicationId: guid, environmentId: guid },
62
+ annotations: updates
63
+ }, handle(config, async ({ id, applicationId, environmentId }) => domainResponse(await platform.org('PUT', `/custom-domains/${id}/environment`, { body: { applicationId, environmentId } }), 'Bağlama kalıcıdır; adres en geç 30 sn içinde yeni ortamı sunar.')));
64
+ server.registerTool('custom_domain_delete', {
65
+ title: 'Alan adını kaldır',
66
+ description: "Alan adını kaldırır: kayıt removing olur, sağlayıcıdaki hostname arka planda düşürülür ve kayıt listeden çıkar. Kalıp silinirse çocukları, birincil silinirse ona yönlendiren kayıtlar da silinir. Müşterinin DNS kayıtlarına dokunulmaz.",
67
+ inputSchema: { id: guid.describe('Alan adı kaydının id\'si (custom_domain_list)') },
68
+ annotations: destructive
69
+ }, handle(config, async ({ id }) => ({
70
+ ...await platform.org('DELETE', `/custom-domains/${id}`),
71
+ note: 'kayıt removing; custom_domain_list ile listeden çıkmasını bekleyin'
72
+ })));
73
+ }
@@ -54,7 +54,7 @@ export function registerMobileStoreImportTools(server, platform) {
54
54
  }));
55
55
  server.registerTool('mobile_store_import_mapping', {
56
56
  title: 'İçe aktarma seçimlerini kaydet',
57
- description: "Raporun Zeyrek'e nasıl yazılacağını kaydeder (PUT mobile-store/{appId}/imports/{importId}/mapping; seçimler birleşmez, verilen sözlük kaydı değiştirir — önce mobile_store_import_report ile okuyun). Anahtarlar: scheme (ZORUNLU — URL şeması mağazadan okunamaz, eski uygulamanınki girilir; MOB-041/P-31) · choice.<satır> = apple | play | custom ve custom.<satır> (iki mağazada farklı metin, K6) · skip.<satır> / skip.language.<dil> = true · target.language.<mağaza>.<dil> = <Zeyrek dili> (eşlenemeyen dilin metnini o dile yazar; eşlenmiş satırı ezmez) · product.<i>.{key, entitlement, period, trial, price, skip} (i = rapordaki ürün sırası; anahtar ve hak ^[a-z][a-z0-9_]{1,63}$) · declaration.<anahtar> = true. Taslak kural ihlaliyle de saklanır: ihlaller yanıtın violations listesinde [{ row, field, code }] döner (choice_required, period_required, price_required, key_taken…) ve mobile_store_import_apply onlar düzelmeden 422 verir. İçe aktarılan ürünün MAĞAZA KİMLİĞİ seçilmez — mağazadan gelir ve kilitlidir (MOB-038).",
57
+ description: "Raporun Zeyrek'e nasıl yazılacağını kaydeder (PUT mobile-store/{appId}/imports/{importId}/mapping; seçimler birleşmez, verilen sözlük kaydı değiştirir — önce mobile_store_import_report ile okuyun). Anahtarlar: scheme (ZORUNLU — URL şeması mağazadan okunamaz, eski uygulamanınki girilir; MOB-041/P-31) · choice.<satır> = apple | play | custom ve custom.<satır> (iki mağazada farklı metin, K6) · skip.<satır> / skip.language.<dil> = true · replace.<satır> = true (ikon, adaptive ön plan ya da öne çıkan görsel yuvası doluysa üzerine yazar; verilmezse mevcut görsel korunur) · target.language.<mağaza>.<dil> = <Zeyrek dili> (eşlenemeyen dilin metnini o dile yazar; eşlenmiş satırı ezmez) · product.<i>.{key, entitlement, period, trial, price, skip} (i = rapordaki ürün sırası; anahtar ve hak ^[a-z][a-z0-9_]{1,63}$) · declaration.<anahtar> = true. Taslak kural ihlaliyle de saklanır: ihlaller yanıtın violations listesinde [{ row, field, code }] döner (choice_required, period_required, price_required, key_taken…) ve mobile_store_import_apply onlar düzelmeden 422 verir. İçe aktarılan ürünün MAĞAZA KİMLİĞİ seçilmez — mağazadan gelir ve kilitlidir (MOB-038).",
58
58
  inputSchema: {
59
59
  applicationId,
60
60
  importId,
@@ -81,6 +81,7 @@ const guideRouting = {
81
81
  translation: 'sayfa/içerik çevirileri (çoklu dil)',
82
82
  access: 'kompleks proje yetkisi: organizasyon ağacı, izin kataloğu, kapsam, grant/atama, alan koruması (maske · şifre · türetilmiş alan)',
83
83
  merge: "dal aç, dalda çalış, development'a merge et; çakışma çözümü ve apply kapıları",
84
+ 'custom-domain': 'uygulamayı müşterinin alan adında yayımlama: DNS yönergesi, durumlar, yönlendirme, dal kalıbı, dört kural',
84
85
  deployment: "development → production deploy'u neyi taşır: yalnız tanım; tablo satırları recordTransferMode ile, dosyalar yalnız id referansıyla; production'da eksik satır/dosya teşhisi",
85
86
  bpm: 'süreç motoru: tanım modeli, düğümler, atama sözlüğü, bölge olayları, kutu ve görev eylemleri, bağlayıcı/belge/imza/kanallar (süreç ≠ workflow)',
86
87
  decision: 'DMN karar tabloları: model, isabet politikaları, FEEL alt kümesi, DRD, süreçte decision düğümü, DMN XML'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appaflytech/zeyrek-ai-mcp",
3
- "version": "0.0.54",
3
+ "version": "0.0.56",
4
4
  "type": "module",
5
5
  "description": "Zeyrek (NgApps) platform APIs as MCP tools — data, logic and Studio design surface in one gateway",
6
6
  "license": "UNLICENSED",
@@ -50,6 +50,7 @@
50
50
  "fast-json-patch": "^3.1.1",
51
51
  "pino": "^9.5.0",
52
52
  "playwright-core": "^1.49.0",
53
+ "tldts-icann": "^7.4.18",
53
54
  "undici": "^7.3.0",
54
55
  "zod": "^3.24.1"
55
56
  },