@appaflytech/zeyrek-ai-mcp 0.0.15 → 0.0.17

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.
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ export const recordWriteViewParam = z.enum(['outline', 'full']).optional().describe("Yanıttaki kaydın biçimi. outline (VARSAYILAN): data yalnız kimlik alanlarını taşır (id, name, slug, version ve türüne göre url/httpMethod/source/type) — gönderdiğiniz gövdenin yankısı bağlama tekrar girmez; 'kaydedildi mi' kanıtı silentlyDropped/droppedCount'tadır. full: platformun döndürdüğü gövde aynen gelir.");
3
+ /**
4
+ * Create/update yanıtı platformun kaydettiği tanımın TAMAMINI döner; ajan aynı turda o gövdeyi
5
+ * kendisi göndermiştir. Kırpma yalnız bağımsız doğrulama yolu olan yerde yapılır: bu tool'larda
6
+ * `writeDiff` tam yanıt üzerinden ÖNCE hesaplanır, projeksiyon ondan sonra uygulanır.
7
+ */
8
+ export function outlineWrite(payload, view, keys) {
9
+ if (view === 'full' || !payload || typeof payload !== 'object' || Array.isArray(payload))
10
+ return payload;
11
+ const record = payload;
12
+ const data = record.data;
13
+ if (!data || typeof data !== 'object' || Array.isArray(data))
14
+ return payload;
15
+ const source = data;
16
+ return { ...record, data: Object.fromEntries(['id', 'name', 'slug', 'version', ...keys].filter(key => Object.hasOwn(source, key)).map(key => [key, source[key]])) };
17
+ }
@@ -105,14 +105,16 @@ globalThis["toplamHesapla"] = function (fiyat, adet) { // studio: Function
105
105
  "name": "toplamHesapla", "description": "fiyat × adet; adet verilmezse 1",
106
106
  "parameters": [ { "name": "fiyat", "type": "number", "required": true },
107
107
  { "name": "adet", "type": "number", "defaultValue": 1 } ],
108
- "body": "return fiyat * adet;" }
108
+ "body": "// fiyat × adet; adet verilmezse 1 sayılır
109
+ return fiyat * adet;" }
109
110
 
110
111
  // tarayıcı
111
112
  { "source": "studio", "applicationId": "<app id>", "scope": "application",
112
113
  "name": "toplamHesapla", "description": "fiyat × adet; adet verilmezse 1",
113
114
  "isAsync": false,
114
115
  "parameters": [ … aynı liste … ],
115
- "body": "return fiyat * adet;" }
116
+ "body": "// fiyat × adet; adet verilmezse 1 sayılır
117
+ return fiyat * adet;" }
116
118
  ```
117
119
 
118
120
  Gövde **düz deyimlerdir** — `function (…) { }` sarmalı ya da arrow yazılmaz; `return` serbesttir,
@@ -128,6 +130,33 @@ sözdizimi hatası `422 GlobalFunctionBodyInvalid` ile ve ayrıştırıcı mesaj
128
130
  | FN-004 | `source:'workflow'` + `isAsync:true` | **block** — motor senkron, 422 `GlobalFunctionAsyncNotAllowed` |
129
131
  | FN-004 | studio: `isAsync:true` ama `await` yok · `isAsync:false` ama `await` var | warn — ilki gereksiz async (inline `[object Promise]`), ikincisi **derlenmeyen gövde**: fonksiyon atlanır |
130
132
  | FN-005 | Kaynak/kapsam tutarsız: workflow'a `applicationId`/`scope`/`pageId`; studio'ya `applicationId` ya da `scope` yok; `page` kapsamına `pageId` yok; `application` kapsamına `pageId` var | **block** — platform 422 |
133
+ | FN-006 | 5+ dolu satırlı gövdede hiç yorum yok | warn — baş yorumu ekleyin (§Gövdede yorum) |
134
+
135
+ ### Gövdede yorum (FN-006)
136
+
137
+ Gövdeyi okuyan bir sonraki kişi (insan ya da ajan) fonksiyonu Studio'nun Functions drawer'ında
138
+ açar; yorumlar orada **yeşil italik** çizilir ve koddan ayrı okunur. Bu yüzden her gövde yorum taşır:
139
+
140
+ - **Baş yorumu** — ilk satırlar: fonksiyon ne yapar, parametrelerin anlamı ve birimi, ne döner.
141
+ Tek satırlık `return` gövdesinde bile bir satır yeterlidir.
142
+ - **Adım yorumu** — yalnız açık olmayan adımların üstüne: iş kuralı, sınır durumu, neden bu yol.
143
+ Kodu tekrar eden yorum yazılmaz (`// toplamı döndür` + `return toplam` gürültüdür).
144
+ - `//` kullanın; `/* */` çok satırlı açıklama için serbest. Gövde kendi satırına sarıldığı için
145
+ son satırdaki `//` kapanış parantezini yutmaz.
146
+
147
+ ```js
148
+ // KDV dahil sepet toplamı. satirlar: [{ fiyat, adet }], oran: 0.2 gibi ondalık.
149
+ // Boş sepette 0 döner; tutar kuruş hassasiyetinde yuvarlanır.
150
+ const araToplam = satirlar.reduce((toplam, satir) => toplam + satir.fiyat * satir.adet, 0)
151
+
152
+ // İndirim KDV'den ÖNCE uygulanır (muhasebe kuralı)
153
+ const indirimli = araToplam * (1 - (indirim ?? 0))
154
+
155
+ return Math.round(indirimli * (1 + oran) * 100) / 100
156
+ ```
157
+
158
+ 5 ya da daha fazla dolu satırlı ve hiç yorumu olmayan gövde **FN-006** uyarısı alır; kayıt yine
159
+ yapılır.
131
160
 
132
161
  Studio kaydı sonrası bağlı sayfaların çalışma paketi `Pages.Updated` ile yeniden kurulur (B-11):
133
162
  `/dev` yolu yeni gövdeyi hemen, `/view` yolu önbellek tazelenince gösterir. Workflow kaydı önbellek
@@ -153,6 +153,21 @@ koda baktığını gizlemek yerine raporlamak.
153
153
  (GW-005). Bir argüman kabul edildiyse gateway onu okuyor demektir.
154
154
 
155
155
 
156
+ ## Aynı çağrıyı iki kez yapmak kopya açmaz: `ifExists`
157
+
158
+ Platform aynı adla ikinci kaydı hata vermeden `"<ad> (1)"` olarak açar. `flow_create`,
159
+ `microservice_create`, `workflow_create` ve `page_create` bu yüzden yazmadan önce aynı ad ya da
160
+ slug'ı (harf duyarsız) arar:
161
+
162
+ | `ifExists` | Kayıt zaten varsa |
163
+ |---|---|
164
+ | `error` (varsayılan) | Yazmaz; hata mevcut id'yi ve varsa `(n)` sayaçlı kopyaları söyler |
165
+ | `update` | Mevcut kaydı günceller, yanıtta `outcome: 'updated'`. `page_create`'te yalnız verilen alanlar yazılır, şema korunur. `workflow_create`'te mevcut kaydın `type`'ı farklıysa (middleware ↔ automation) yazmaz — tür değişirdi |
166
+ | `duplicate` | Platform davranışı: ikinci kayıt açılır. Platformun eklediği sayaç (ad `' (1)'`, slug `-1`; akışta ikisi de `' (1)'`) kayıp değildir, yanıtta `renamedByPlatform`'dadır |
167
+
168
+ Betiği yeniden koşturacaksan `ifExists: 'update'` ver; kopyaları elle ayıklayan yardımcı kod
169
+ yazma. Mevcut liste okunamazsa çağrı yazmadan düşer — tekrar dene.
170
+
156
171
  ## Yazdıktan sonra `silentlyDropped` alanına bak
157
172
 
158
173
  Platformun bir alanı **hatasız yutması** bu depodaki en pahalı hata sınıfı: çağrı 200 döner,
@@ -169,6 +184,26 @@ Gateway yazma tool'larında gönderdiğin gövdeyi kaydedilenle karşılaştır
169
184
  ]
170
185
  ```
171
186
 
187
+ Yazma yanıtında bu liste ve `gatewayNormalized` **kısaltılmış** gelir: aynı yolun düşmeleri tek
188
+ satırda sayıyla toplanır (`selectFields[*].path · kaydedilmedi: 25 öğe — ör. …`). Kayıp sayısı
189
+ `droppedCount`'tadır; sayım 1000 farkta durursa `droppedCountCapped: true` gelir ve sayı "en az"
190
+ demektir. Liste dışı bilgiler (20 satır kesmesi, sıra tabanlı karşılaştırma uyarısı)
191
+ `silentlyDroppedNotes`'tadır ve sayıya girmez. Tam liste gerekiyorsa yazmayı tekrarlama — kaydı `*_get` ile oku,
192
+ sonraki yazmalarda `diagnostics: 'full'` ver. `microservice_*` ve `workflow_*` create/update
193
+ yanıtında `data` yalnız kimlik alanlarını taşır; gövdenin tamamı için `view: 'full'`.
194
+
195
+ **Betikten toplu yazarken yanıtı olduğu gibi basma.** Otuz yazmanın tam yanıtı çıktı sınırını aşar
196
+ ve dosyaya düşer. Her çağrı için tek satır bas; ayrıntıyı yalnız sorunlu çağrıda bas:
197
+
198
+ ```js
199
+ const r = JSON.parse(result.content[0].text)
200
+ if (result.isError || r.droppedCount > 0) console.log(JSON.stringify({ slug, error: result.isError ? r : undefined, droppedCount: r.droppedCount, silentlyDropped: r.silentlyDropped }))
201
+ else console.log(JSON.stringify({ slug, id: r.data?.id, outcome: r.outcome }))
202
+ ```
203
+
204
+ Yalnız `isError`'a bakma: platform alanı hatasız yutunca çağrı başarılıdır ve kayıp yalnız
205
+ `droppedCount`'ta görünür.
206
+
172
207
  Diff dizi öğelerini indeksle değil KİMLİKLE eşler (`stepId`, `elementId`, `name`, `id`), çünkü
173
208
  platform diziyi kendi sırasında geri döndürür. Diff üretilemeyen tek durum, yanıtın kayıt değil
174
209
  kabul gövdesi olmasıdır; o zaman alan hiç görünmez ve doğrulamayı `*_get` ile kendin yaparsın.
@@ -1316,7 +1316,7 @@ budur — bir preset ya da `componentStyles` sizin değerinizi eziyorsa `styleLa
1316
1316
  | Değişiklik | Tool | Neden |
1317
1317
  |---|---|---|
1318
1318
  | Komponent ekle/sil/taşı, prop, binding, event | `page_schema_patch` | Doğrulama duvarı + açık Studio canvas'ına SignalR canlı yayını + eşzamanlı yazar kilidi yalnız bu yolda çalışır |
1319
- | Ad, slug, browserTitle, `browserTitleKey`, layout, roller, middleware | `page_update` | Üstbilgi alanları micro-op sözlüğünde yok. `browserTitleKey` sekme başlığını çeviri anahtarına bağlar: verilirse başlık sözlükten çözülür, boşsa `browserTitle` kullanılır. Başlık sayfa yüklenirken çözülür ve canlı dil değişimini izlemez (`platform_guide('translation')`) |
1319
+ | Ad, slug, browserTitle, `browserTitleKey`, layout, roller, middleware | `page_update` | Üstbilgi alanları micro-op sözlüğünde yok. `browserTitleKey` sekme başlığını çeviri anahtarına bağlar: verilirse başlık sözlükten çözülür, boşsa `browserTitle` kullanılır. Başlık canlı dil değişimini izler; anahtarın o dilde değeri yoksa varsayılan dile düşer (`platform_guide('translation')`) |
1320
1320
 
1321
1321
  Platformun `PUT /pages/...` ucu **replace**'tir: gönderilmeyen `schema`/`events`/`roleIds`
1322
1322
  alanları silinir. Gateway bu yüzden `page_update`'te sayfayı önce okur, yalnız verdiğin
@@ -175,11 +175,10 @@ motor olmasıdır (tarayıcı JS'i ve Jint).
175
175
  verilmişse sekme başlığı çeviri sözlüğünden çözülür; anahtar boşsa `browserTitle` olduğu gibi
176
176
  kullanılır. İkisi de boşsa sayfa adı yazılır.
177
177
 
178
- > **Sekme başlığı SAYFA YÜKLENİRKEN çözülür, canlı dil değişimini İZLEMEZ.** Ölçüldü 2026-09-08
179
- > (yerel runtime): `language-selector` ile dil değiştirildiğinde sayfa içeriği ve layout başlığı
180
- > anında yeni dile geçiyor, sekme başlığı ise açılıştaki dilde kalıyor; aynı dille sayfa yeniden
181
- > yüklendiğinde başlık doğru geliyor. Sebep `Runtime.svelte`'te dilin bir `const` olarak bir kez
182
- > çözülmesi. Sekme başlığının dilini doğrulayacaksan **yeniden yükleyerek** doğrula.
178
+ > **Sekme başlığı canlı dil değişimini İZLER.** `language-selector` ile dil değiştirildiğinde
179
+ > sayfa içeriğiyle birlikte sekme başlığı da yeni dilde çözülür; yeniden yükleme gerekmez
180
+ > (ölçüldü 2026-09-11). Anahtarın seçilen dilde değeri yoksa projenin varsayılan diline düşer.
181
+ > 2026-09-08 ölçümündeki "açılıştaki dilde kalır" davranışı düzeltildi.
183
182
 
184
183
  ## Doğrulama mesajları: `t:` öneki
185
184
 
@@ -1,10 +1,10 @@
1
1
  {
2
- "hash": "05c7864d58e308973805f7becee1578ce9eddad196abcdf7eebb43b1e7677549",
3
- "generatedAt": "2026-09-16T15:53:10.903Z",
2
+ "hash": "f37265d598ea468deb2157165891629c2e88c7fd2238e82ec60cda7f58f7de4c",
3
+ "generatedAt": "2026-09-18T12:57:15.416Z",
4
4
  "types": 186,
5
5
  "files": 108,
6
6
  "note": "Şemaları üreten backend kaynaklarının parmak izi. Elle düzenlemeyin: 'npm run generate-schemas' yazar, 'npm run schema:check' denetler.",
7
7
  "missing": [
8
- "NgApps.Infrastructure.Models.IdNamePair`1[[System.Guid, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]"
8
+ "NgApps.Infrastructure.Models.IdNamePair`1[[System.Guid, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]"
9
9
  ]
10
10
  }
@@ -95,6 +95,20 @@
95
95
  "$ref": "#/components/schemas/NgApps.Domain.Contexts.Shared.Dependencies.DependencyNode"
96
96
  },
97
97
  "nullable": true
98
+ },
99
+ "bindings": {
100
+ "type": "array",
101
+ "items": {
102
+ "$ref": "#/components/schemas/NgApps.Domain.Contexts.Shared.Dependencies.DependencyNode"
103
+ },
104
+ "nullable": true
105
+ },
106
+ "functions": {
107
+ "type": "array",
108
+ "items": {
109
+ "$ref": "#/components/schemas/NgApps.Domain.Contexts.Shared.Dependencies.DependencyNode"
110
+ },
111
+ "nullable": true
98
112
  }
99
113
  },
100
114
  "additionalProperties": false
@@ -189,6 +203,10 @@
189
203
  },
190
204
  "stepKind": {
191
205
  "$ref": "#/components/schemas/NgApps.Domain.Contexts.Studio.Flows.StepKind"
206
+ },
207
+ "propertyName": {
208
+ "type": "string",
209
+ "nullable": true
192
210
  }
193
211
  },
194
212
  "additionalProperties": false
@@ -3,7 +3,7 @@ import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collec
3
3
  import { guid, projectParam, environmentParam, updates, microOps } from './common.js';
4
4
  import { fetchHost, hostPath } from './screens.js';
5
5
  import { assertScreenWriteGuards, screenCatalogNotes } from './screenWrite.js';
6
- import { schemaOpDrops, withWriteDiff } from '../platform/writeDiff.js';
6
+ import { emptyWriteDiff, schemaOpDrops, withWriteDiff } from '../platform/writeDiff.js';
7
7
  import { fetchComponentEventNames } from './components.js';
8
8
  import { PlatformApiError } from '../platform/errors.js';
9
9
  import { handle } from '../platform/responses.js';
@@ -136,14 +136,14 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
136
136
  if (layoutWarnings.length > 0)
137
137
  response.layoutWarnings = layoutWarnings;
138
138
  const contentOps = opRecords.filter(entry => entry.op === 'add_component' || entry.op === 'replace_prop');
139
- let opDrops = [];
139
+ let opDrops = emptyWriteDiff();
140
140
  if (contentOps.length > 0) {
141
141
  try {
142
142
  const saved = await fetchHost(platform, host, project, environment, applicationId, id);
143
143
  opDrops = schemaOpDrops(saved[spec.tree], contentOps);
144
144
  }
145
145
  catch {
146
- opDrops = [];
146
+ opDrops = emptyWriteDiff();
147
147
  }
148
148
  }
149
149
  return withWriteDiff(withNormalization(response, [
@@ -5,12 +5,13 @@ import { flowBindings, normalizeFlowSteps, targetlessSetterWarnings, unknownSett
5
5
  import { flowBodyWarnings } from '../knowledge/coverage.js';
6
6
  import { PlatformApiError } from '../platform/errors.js';
7
7
  import { handle } from '../platform/responses.js';
8
- import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
8
+ import { emptyWriteDiff, writeDiff, withWriteDiff } from '../platform/writeDiff.js';
9
9
  import { resolvePatchOperations } from '../knowledge/outlines.js';
10
10
  import { patchResult, baseVersionOf } from '../platform/patch.js';
11
11
  import fastJsonPatch from 'fast-json-patch';
12
12
  import { policyRules, flowExpressionSites } from '../knowledge/rules.js';
13
13
  import { functionCallRefs } from '../knowledge/functionGuards.js';
14
+ import { existingMatch, ifExistsParam, platformRename, withRename } from '../platform/ifExists.js';
14
15
  import { randomUUID } from 'node:crypto';
15
16
  import { z } from 'zod';
16
17
  /**
@@ -19,14 +20,18 @@ import { z } from 'zod';
19
20
  * `flow_create` 200 döndü, `flow_get` adımı gösterdi ama `args` içinde kodu taşıyan alan yoktu —
20
21
  * teşhis ~10 dakika sürdü ve tek belirti filtrenin hiçbir şey yapmamasıydı. Okuma başarısız
21
22
  * olursa yazma yine başarılıdır: diff bir teşhis eklentisidir, yazmanın sonucu değil.
23
+ *
24
+ * Okunan kayıt da döner: `ifExists:'duplicate'` yazmasında platform ada/slug'a ' (1)' ekler ve yanıtın
25
+ * gönderilen adı değil KAYDEDİLEN adı göstermesi gerekir (flow-upsert-ve-baglar S-10).
22
26
  */
23
- async function flowWriteDrops(platform, host, project, environment, applicationId, hostId, flowId, sent) {
27
+ async function flowWriteDrops(platform, host, project, environment, applicationId, hostId, flowId, sent, allowRename = false) {
24
28
  try {
25
29
  const saved = unwrap(await platform.tenant('GET', project, flowPath(host, applicationId, hostId, flowId), { environment }));
26
- return writeDiff(sent, saved);
30
+ const rename = allowRename ? platformRename(sent, saved, 'paren') : { sent };
31
+ return { ...writeDiff(rename.sent, saved), saved, ...(rename.renamed ? { renamed: rename.renamed } : {}) };
27
32
  }
28
33
  catch {
29
- return { dropped: [], normalized: [] };
34
+ return emptyWriteDiff();
30
35
  }
31
36
  }
32
37
  const stepsParam = z.array(z.record(z.unknown())).describe("Adım listesi (StepManagementSchema: { stepId, name, type, kind, args, branches }). DİKKAT: branches[].nextStepId bir GUID değil, hedef adımın stepId STRING'idir; dal ADI motorun anahtarıdır ve start/setter/code/function adımlarında 'always' olmak zorundadır. Tam şablon: ngapps://examples/flow-load-table (ya da flow_get çıktısı); adım tipleri ve setter/parameters şekli platform_guide('page') Kural 1c. Kullanıcı fonksiyonu çağırmak için 'function' adımı: { type:'function', args:{ functionId:'<function_list id>', functionName:'<ad>', parameters:[{ name:'<bildirimdeki ad>', value:{ type:'static'|'property'|'storage'|'state'|'code', ref } }] } } → çıktı Steps.<adımAdı>.data (ngapps://examples/flow-call-function, platform_guide('functions')).");
@@ -69,8 +74,7 @@ function flowListResult(flows, record) {
69
74
  ...(danglingBindings.length > 0 ? { danglingBindings } : {})
70
75
  };
71
76
  }
72
- const ifExistsParam = z.enum(['error', 'update', 'duplicate']).default('error').describe("Barındırıcıda aynı ad ya da slug'da (büyük/küçük harf duyarsız) akış varsa ne olacağı. 'error' (varsayılan): yazmaz, mevcut akışın id'sini döner. 'update': mevcut akışı bu gövdeyle günceller (id'si ve olay bağları korunur). 'duplicate': platformun ürün davranışı — ikinci kayıt açılır, platform ada/slug'a ' (1)' sayacını ekler (Studio'daki 'New Flow (1)' ile aynı kural).");
73
- const upper = (value) => String(value ?? '').toUpperCase();
77
+ const flowIfExistsParam = ifExistsParam('akış', 'mevcut akışı bu gövdeyle günceller (id\'si ve olay bağları korunur)');
74
78
  /**
75
79
  * Barındırıcının akışları, önbelleksiz liste ucundan (`GET /pages|layouts/{id}/flows`). Layout ve
76
80
  * sayfa tasarım kaydı önbellekten gelir ve zarfsız akış yazmasından sonra akışı hemen taşımaz;
@@ -103,36 +107,21 @@ async function listHostFlows(platform, host, project, environment, applicationId
103
107
  * tam da zaman aşımında kopya üretirdi.
104
108
  */
105
109
  async function existingFlowMatch(platform, host, project, environment, applicationId, hostId, hostRecord, name, slug, flowId, ifExists) {
106
- if (ifExists === 'duplicate')
107
- return undefined;
108
- let flows;
109
- try {
110
- flows = await listHostFlows(platform, host, project, environment, applicationId, hostId, async () => hostRecord);
111
- }
112
- catch (error) {
113
- throw new PlatformApiError(`FLOW-026: barındırıcının mevcut akışları okunamadı, akış YAZILMADI (${error instanceof Error ? error.message : String(error)}). Liste okunmadan yazmak aynı adla ikinci akış açabilir; flow_list ile tekrar deneyin ya da bilerek ikinci akış istiyorsanız ifExists:'duplicate' verin.`);
114
- }
115
- const matches = flows.filter(flow => upper(flow.name) === upper(name) || upper(flow.slug) === upper(slug));
116
- const [match] = matches;
117
- if (match === undefined)
118
- return undefined;
119
- const describe = (flow) => `${String(flow.id)} ('${String(flow.name)}', slug '${String(flow.slug)}')`;
120
- const copyPattern = new RegExp(`^${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')} \\(\\d+\\)$`, 'i');
121
- const copies = flows.filter(flow => copyPattern.test(String(flow.name ?? '')));
122
- const copyNote = copies.length > 0
123
- ? ` Barındırıcıda bu adın sayaçlı eşleri de var: ${copies.map(describe).join(' · ')} — bağı olup olmadığını flow_list (bindings) ile kontrol edip gerekirse silin.`
124
- : '';
125
- if (ifExists === 'error') {
126
- const where = host === 'page' ? 'sayfada' : host === 'layout' ? "layout'ta" : "widget'ta";
127
- throw new PlatformApiError(`FLOW-026: bu ${where} aynı ad ya da slug'da akış zaten var: ${matches.map(describe).join(' · ')}. İstek gönderilmedi — gönderilseydi platform ürün kuralı gereği ikinci bir akış açardı (çakışan ad ya da slug'a ' (1)' sayacı eklenerek; yalnız harf farkı varsa sayaçsız). Mevcut akışı güncellemek için aynı çağrıyı ifExists:'update' ile tekrarlayın; bilerek ikinci akış istiyorsanız ifExists:'duplicate'.${copyNote}`, 409);
128
- }
129
- if (matches.length > 1) {
130
- throw new PlatformApiError(`FLOW-026: ad ve slug farklı akışlara çıkıyor (${matches.map(describe).join(' · ')}); hangisinin güncelleneceği belirsiz, istek gönderilmedi. flowId ile flow_update kullanın.${copyNote}`, 409);
131
- }
132
- if (flowId !== undefined && flowId.toLowerCase() !== String(match.id).toLowerCase()) {
133
- throw new PlatformApiError(`FLOW-026: verilen flowId (${flowId}) aynı adlı mevcut akışla (${describe(match)}) çelişiyor; istek gönderilmedi. Mevcut akışı güncellemek için flowId'yi vermeyin ya da ${String(match.id)} verin.`, 409);
134
- }
135
- return match;
110
+ return await existingMatch({
111
+ code: 'FLOW-026',
112
+ noun: 'akış',
113
+ where: host === 'page' ? 'bu sayfada' : host === 'layout' ? "bu layout'ta" : "bu widget'ta",
114
+ name,
115
+ slug,
116
+ id: flowId,
117
+ idParam: 'flowId',
118
+ ifExists,
119
+ list: () => listHostFlows(platform, host, project, environment, applicationId, hostId, async () => hostRecord),
120
+ listTool: 'flow_list',
121
+ copyCheck: 'bağı olup olmadığını flow_list (bindings) ile kontrol edip gerekirse silin',
122
+ updateTool: 'flowId ile flow_update',
123
+ slugSuffix: 'paren'
124
+ });
136
125
  }
137
126
  async function requiredParameterWarnings(platform, project, environment, applicationId, steps) {
138
127
  const warnings = [];
@@ -356,7 +345,7 @@ export function registerFlowTools(server, platform) {
356
345
  steps: stepsParam,
357
346
  errorSteps: stepsParam.optional(),
358
347
  flowId: guid.optional().describe('Verilmezse platform üretir; şemaya önceden bağlamak için kendiniz üretebilirsiniz'),
359
- ifExists: ifExistsParam,
348
+ ifExists: flowIfExistsParam,
360
349
  policyOverride: policyOverrideParam
361
350
  },
362
351
  annotations: writes
@@ -392,15 +381,20 @@ export function registerFlowTools(server, platform) {
392
381
  }
393
382
  // Diff YALNIZ çağıranın yazdığı alanları kapsar; `id` ve barındırıcı bağı (`pageId` vb.)
394
383
  // gövdeye gateway koyar, kullanıcının düzeltebileceği bir şey değildir.
395
- const drops = await flowWriteDrops(platform, host, project, environment, applicationId, hostId, writtenFlowId, { name: flow.name, slug: flow.slug, steps: flow.steps, errorSteps: flow.errorSteps });
396
- return attachFindings(withWriteDiff(withNormalization({
384
+ const drops = await flowWriteDrops(platform, host, project, environment, applicationId, hostId, writtenFlowId, { name: flow.name, slug: flow.slug, steps: flow.steps, errorSteps: flow.errorSteps }, ifExists === 'duplicate');
385
+ return attachFindings(withWriteDiff(withRename(withNormalization({
397
386
  host,
398
387
  hostId,
399
388
  outcome: existing === undefined ? 'created' : 'updated',
400
- flow: { id: writtenFlowId, name, slug, steps: steps.length },
389
+ flow: {
390
+ id: writtenFlowId,
391
+ name: typeof drops.saved?.name === 'string' ? drops.saved.name : name,
392
+ slug: typeof drops.saved?.slug === 'string' ? drops.saved.slug : slug,
393
+ steps: steps.length
394
+ },
401
395
  ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
402
396
  hint: "Akışı bir olaya bağlamak için: page_schema_patch (layout'ta layout_schema_patch, widget'ta component_schema_patch) ops:[{op:'set_event', target:<elementId>, event:'<component_get events listesindeki ad>', eventValue:<flow id>}]; olayda zaten akış bağlıysa ikinciyi add_event ile ekleyin (set_event bağlı akışı değiştirir, PAGE-034). Olay adları komponente özeldir ve 'on' ÖNEKİ ALMAZ (button → 'click', form → 'submit', table → 'mount'/'select'/'sort'…); uydurma ad 200 döner ama bağ sessizce ölü kalır — gateway PAGE-008 ile yakalar."
403
- }, normalized), drops), findings);
397
+ }, normalized), drops.renamed), drops), findings);
404
398
  }));
405
399
  server.registerTool('flow_update', {
406
400
  title: 'Akış güncelle',
@@ -16,7 +16,7 @@ import { z } from 'zod';
16
16
  const sourceParam = z.enum(['workflow', 'studio']).describe("Nerede çalışır: 'workflow' → SUNUCU (workflow/middleware/automation ifadeleri, Jint), tenant geneli, çıplak adla çağrılır (toplamHesapla(1, 2)), applicationId/scope/pageId VERİLMEZ, isAsync olamaz. 'studio' → TARAYICI ({{ }} bağlaması, UI flow code/function adımı), uygulamaya ait, Functions.toplamHesapla(1, 2) ile çağrılır, applicationId + scope zorunlu. Kaynak sonradan DEĞİŞMEZ");
17
17
  const scopeParam = z.enum(['application', 'page']).describe("Yalnız source:'studio'. application: uygulamanın her sayfasında görünür. page: yalnız o sayfanın tarayıcı bağlamında, pageId zorunlu (K2). Ad uygulama içinde kapsamlar arası benzersizdir (K3)");
18
18
  const nameParam = z.string().min(1).describe("Fonksiyon adı; studio'da Functions.<ad>(…), workflow'da <ad>(…) ile çağrılır. JS tanımlayıcısı olmalı, ayrılmış sözcük olamaz; studio'da 'Functions', workflow'da motor global adları (system, input, Now…) yasaktır (FN-001, istek gitmeden). Aynı ad iki kaynakta yan yana yaşayabilir (ayrı ad alanları); aynı kaynak içinde tekrarı platform 422 ile reddeder");
19
- const bodyParam = z.string().min(1).max(65536).describe("Fonksiyon GÖVDESİ, iki kaynakta aynı biçim: düz JavaScript deyimleri, 'return' içerebilir — 'function' sarmalı ya da arrow YAZILMAZ ('return fiyat * adet'). Gövde çağrıldığı yerin bağlamını kapanış olarak görür: tarayıcıda Page/state/Steps/komponent adları, sunucuda input/adım çıktıları/system.* (K7). Parametre adları gövdede doğrudan kullanılır. Platform kaydetmeden önce SÖZDİZİMİNİ denetler (422 GlobalFunctionBodyInvalid, ayrıştırıcı mesajıyla). En çok 64 KB");
19
+ const bodyParam = z.string().min(1).max(65536).describe("Fonksiyon GÖVDESİ, iki kaynakta aynı biçim: düz JavaScript deyimleri, 'return' içerebilir — 'function' sarmalı ya da arrow YAZILMAZ ('return fiyat * adet'). Gövde çağrıldığı yerin bağlamını kapanış olarak görür: tarayıcıda Page/state/Steps/komponent adları, sunucuda input/adım çıktıları/system.* (K7). Parametre adları gövdede doğrudan kullanılır. Gövde YORUMLU yazılır: başta ne yaptığını/parametreleri/dönüşü söyleyen // baş yorumu, açık olmayan adımlarda kısa satır yorumu; 5+ dolu satırlı yorumsuz gövde FN-006 uyarısı alır (Studio yorumu yeşil çizer). Platform kaydetmeden önce SÖZDİZİMİNİ denetler (422 GlobalFunctionBodyInvalid, ayrıştırıcı mesajıyla). En çok 64 KB");
20
20
  const parameterParam = z.object({
21
21
  name: z.string().min(1).describe('Parametre adı — JS tanımlayıcısı, fonksiyon içinde benzersiz; gövdede bu adla okunur'),
22
22
  type: z.enum(['text', 'number', 'boolean', 'date', 'object', 'array', 'any']).optional()
@@ -64,7 +64,10 @@ export function describeSignatureChange(current, next) {
64
64
  };
65
65
  }
66
66
  function signatureWarning(name, source, change, dependents) {
67
- const callers = [dependents.flows.length ? `flows: ${dependents.flows.join(', ')}` : '', dependents.workflows.length ? `workflows: ${dependents.workflows.join(', ')}` : ''].filter(Boolean).join('; ');
67
+ const callers = ['flows', 'workflows', 'bindings', 'functions']
68
+ .map(bucket => (dependents[bucket].length ? `${bucket}: ${dependents[bucket].join(', ')}` : ''))
69
+ .filter(Boolean)
70
+ .join('; ');
68
71
  const what = [
69
72
  change.name ? `ad '${change.name[0]}' → '${change.name[1]}'` : '',
70
73
  change.parameters ? `parametreler [${change.parameters[0].join(', ')}] → [${change.parameters[1].join(', ')}]` : ''
@@ -75,11 +78,12 @@ function signatureWarning(name, source, change, dependents) {
75
78
  return `İmza değişti (${what}) ve fonksiyonu ${dependents.total} kayıt kullanıyor (${callers}): ${effect}. Güncelleme yapıldı; çağıran ifadeleri gözden geçirin — dependency_graph(module:'global-function').`;
76
79
  }
77
80
  /**
78
- * `GET global-functions/{id}/dependencies` KOVALI grafiği döner (`{ flows, workflows }`); düz dizi
81
+ * `GET global-functions/{id}/dependencies` KOVALI grafiği döner (`{ flows, workflows, bindings, functions }`); düz dizi
79
82
  * şekli de düzleştirilir — yapısal (`args.functionId`) ve metinsel (`Functions.<ad>(` / çıplak `<ad>(`)
80
83
  * bağımlılıklar aynı listede gelir. Etiket Studio rozetleriyle aynıdır: komponent olayı `Sahip › Komponent · olay`,
81
84
  * sayfa/layout olayı `Sahip · olay`, bağı olmayan akış `Sahip › akış adı` (sahip sayfa/layout/widget; eski yanıtta
82
- * sahipsiz); automation düğümü `ad (automation)`.
85
+ * sahipsiz); automation düğümü `ad (automation)`. Kod tipli prop bağlaması `Sahip › Komponent · prop`, gövdesinde
86
+ * çağıran fonksiyon adıyla yazılır.
83
87
  */
84
88
  export function summarizeDependents(payload) {
85
89
  const data = (() => {
@@ -94,7 +98,7 @@ export function summarizeDependents(payload) {
94
98
  const name = String(entry.name ?? entry.slug ?? entry.id ?? '?');
95
99
  const host = text(entry.hostName);
96
100
  const component = text(entry.componentName);
97
- const event = text(entry.eventTitle) ?? text(entry.eventName);
101
+ const event = text(entry.eventTitle) ?? text(entry.eventName) ?? text(entry.propertyName);
98
102
  const label = component
99
103
  ? [host, [component, event].filter(Boolean).join(' · ')].filter(Boolean).join(' › ')
100
104
  : event
@@ -106,14 +110,20 @@ export function summarizeDependents(payload) {
106
110
  const entries = data.filter(isRecord);
107
111
  const moduleOf = (entry) => String(entry.module ?? entry.type ?? entry.kind ?? '').toLowerCase();
108
112
  const isWorkflow = (entry) => moduleOf(entry).includes('workflow') || moduleOf(entry) === 'automation';
113
+ const isFunction = (entry) => moduleOf(entry) === 'global-function';
114
+ const isBinding = (entry) => text(entry.propertyName) !== undefined;
109
115
  const workflows = names(entries.filter(isWorkflow));
110
- const flows = names(entries.filter(entry => !isWorkflow(entry)));
111
- return { flows, workflows, total: flows.length + workflows.length };
116
+ const functions = names(entries.filter(isFunction));
117
+ const bindings = names(entries.filter(entry => !isWorkflow(entry) && !isFunction(entry) && isBinding(entry)));
118
+ const flows = names(entries.filter(entry => !isWorkflow(entry) && !isFunction(entry) && !isBinding(entry)));
119
+ return { flows, workflows, bindings, functions, total: flows.length + workflows.length + bindings.length + functions.length };
112
120
  }
113
121
  const record = isRecord(data) ? data : {};
114
122
  const flows = names(record.flows);
115
123
  const workflows = names(record.workflows);
116
- return { flows, workflows, total: flows.length + workflows.length };
124
+ const bindings = names(record.bindings);
125
+ const functions = names(record.functions);
126
+ return { flows, workflows, bindings, functions, total: flows.length + workflows.length + bindings.length + functions.length };
117
127
  }
118
128
  function usageHint(name, source, scope) {
119
129
  if (source === 'workflow') {
@@ -268,7 +278,7 @@ export function registerFunctionTools(server, platform) {
268
278
  data: saved,
269
279
  signature: functionSignature(saved.name ? saved : definition),
270
280
  signatureChange,
271
- dependents: dependents ?? { flows: [], workflows: [], total: 0 },
281
+ dependents: dependents ?? { flows: [], workflows: [], bindings: [], functions: [], total: 0 },
272
282
  ...(dependents === undefined ? { dependentsNote: 'Bağımlılık ucu okunamadı; çağıran akış olup olmadığı bilinmiyor.' } : {}),
273
283
  ...(renamedReferences !== undefined ? { renamedReferences } : {}),
274
284
  ...(dependents !== undefined && dependents.total > 0 && callersBreak
@@ -321,7 +331,7 @@ export function registerFunctionTools(server, platform) {
321
331
  return {
322
332
  deleted: id,
323
333
  source,
324
- dependents: dependents ?? { flows: [], workflows: [], total: 0 },
334
+ dependents: dependents ?? { flows: [], workflows: [], bindings: [], functions: [], total: 0 },
325
335
  ...(dependents === undefined ? { dependentsNote: 'Bağımlılık ucu okunamadı; çağıran akış olup olmadığı bilinmiyor.' } : {}),
326
336
  ...(dependents !== undefined && dependents.total > 0
327
337
  ? { note: `${dependents.total} çağıran kırıldı (acknowledgeDependents): ${[...dependents.flows, ...dependents.workflows].join(', ')} — ifadeleri düzeltin ya da fonksiyonu aynı adla yeniden tanımlayın.` }
@@ -10,11 +10,15 @@ import { policyRules } from '../knowledge/rules.js';
10
10
  import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
11
11
  import { identityHeaders } from '../platform/tokenVault.js';
12
12
  import { PlatformApiError } from '../platform/errors.js';
13
+ import { existingMatch, ifExistsParam, listAll, platformRename, withOutcome, withRename } from '../platform/ifExists.js';
14
+ import { outlineWrite, recordWriteViewParam } from '../platform/writeView.js';
13
15
  import { handle } from '../platform/responses.js';
14
16
  import fastJsonPatch from 'fast-json-patch';
15
17
  import { z } from 'zod';
16
18
  import { constraintSummary } from '../knowledge/backendConstraints.js';
17
19
  import { unmappedMembers, introducedUnmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
20
+ const MICROSERVICE_OUTLINE_KEYS = ['url', 'httpMethod', 'source', 'type'];
21
+ const microserviceIfExistsParam = ifExistsParam('uç', 'mevcut ucu bu tanımla tam gövde günceller (id\'si korunur; microservice_update ile aynı yazma)');
18
22
  const policyOverrideParam = z.boolean().optional()
19
23
  .describe('true: politika block bulgularını warn\'a indirir (bilinçli istisna; yanıtta işaretlenir)');
20
24
  /**
@@ -287,28 +291,52 @@ export function registerMicroserviceTools(server, platform) {
287
291
  }));
288
292
  server.registerTool('microservice_create', {
289
293
  title: 'Microservice oluştur',
290
- description: "Yeni microservice tanımlar (definition = CreateMicroserviceCommand). İlk kullanımdan önce platform_guide('microservice') okuyun: kaynak seçimi (table > query) ve selectFields disiplini. 'description' ZORUNLUDUR (MS-014, block): çağıranın bu uçtan ne elde ettiğini yazın. Auth/oturum gerektiren uçlarda definition.middlewareIds ile middleware bağlayın; kimlik kapsaması filtrede source:'expression' + value:'user.id' ile yapılır (platform_guide('middleware')). BU YALNIZ source:'table' UÇLARDA VAR: query kaynaklı ucun parametreleri ÇAĞRIDAN gelir (body → query-string → header) ve middleware'in kurduğu user o yola girmez — kapsamı gövdeye bırakan uç sahtelenebilir (ölçüldü 2026-09-07). Kapsanmış agregasyon ucu sorguyu bir workflow query adımından çağırır: platform_guide('microservice') §'Kimlik kapsaması × uç kaynağı'. Ucun gerçek bir tuzağı varsa (auth istemiyor, sayfalama yok, atomik değil) 'caution' alanına yazın — OPSİYONELDİR, tuzak yoksa boş bırakın. Gövde politika kontrolünden geçer. " + constraintSummary('CreateMicroserviceCommand'),
291
- inputSchema: { project: projectParam, environment: environmentParam, applicationId: appRef, definition: definitionParam, policyOverride: policyOverrideParam },
294
+ description: "Yeni microservice tanımlar (definition = CreateMicroserviceCommand). Aynı ad ya da slug'da uç varsa varsayılan olarak YAZMAZ ve mevcut ucun id'sini döner (MS-023 — gönderilseydi platform '<ad> (1)' adıyla ikinci bir uç açardı); tekrar çalıştırılabilir yazma için ifExists:'update' verin, yanıttaki outcome 'created' | 'updated' olur. Mevcut uçlar okunamazsa da yazmaz. İlk kullanımdan önce platform_guide('microservice') okuyun: kaynak seçimi (table > query) ve selectFields disiplini. 'description' ZORUNLUDUR (MS-014, block): çağıranın bu uçtan ne elde ettiğini yazın. Auth/oturum gerektiren uçlarda definition.middlewareIds ile middleware bağlayın; kimlik kapsaması filtrede source:'expression' + value:'user.id' ile yapılır (platform_guide('middleware')). BU YALNIZ source:'table' UÇLARDA VAR: query kaynaklı ucun parametreleri ÇAĞRIDAN gelir (body → query-string → header) ve middleware'in kurduğu user o yola girmez — kapsamı gövdeye bırakan uç sahtelenebilir (ölçüldü 2026-09-07). Kapsanmış agregasyon ucu sorguyu bir workflow query adımından çağırır: platform_guide('microservice') §'Kimlik kapsaması × uç kaynağı'. Ucun gerçek bir tuzağı varsa (auth istemiyor, sayfalama yok, atomik değil) 'caution' alanına yazın — OPSİYONELDİR, tuzak yoksa boş bırakın. Gövde politika kontrolünden geçer. " + constraintSummary('CreateMicroserviceCommand'),
295
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: appRef, definition: definitionParam, ifExists: microserviceIfExistsParam, view: recordWriteViewParam, policyOverride: policyOverrideParam },
292
296
  annotations: writes
293
- }, handle(config, async ({ project, environment, applicationId, definition, policyOverride }) => {
297
+ }, handle(config, async ({ project, environment, applicationId, definition, ifExists, view, policyOverride }) => {
294
298
  const resolver = new RefResolver(platform, project, environment);
295
299
  const resolvedApplicationId = await resolver.applicationId(applicationId);
296
300
  const hydrated = await hydrateMicroserviceRefs(definition, resolver);
297
301
  const normalized = normalizeMicroserviceDefinition(definition);
302
+ // Politika ÖNCE koşar: block alan istek platforma hiç gitmez, var-mı listesi de okunmaz.
303
+ const createFindings = enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'create' }, policyOverride));
304
+ const scopeFindings = await queryScopeFindings(platform, project, environment, definition);
305
+ const existing = await existingMatch({
306
+ code: 'MS-023',
307
+ noun: 'uç',
308
+ where: 'bu uygulamada',
309
+ name: typeof definition.name === 'string' ? definition.name : undefined,
310
+ slug: typeof definition.slug === 'string' ? definition.slug : undefined,
311
+ id: typeof definition.id === 'string' ? definition.id : undefined,
312
+ idParam: 'definition.id',
313
+ ifExists,
314
+ list: () => listAll(platform, project, environment, `/microservices/${resolvedApplicationId}`),
315
+ listTool: 'microservice_list',
316
+ copyCheck: 'kullanılıp kullanılmadığını dependency_graph ile kontrol edip gerekirse silin',
317
+ updateTool: 'id ile microservice_update'
318
+ });
319
+ // Kayıt bulunduysa yazma bir UPDATE'tir: gövde kuralları microservice_update ile aynı kipte (R-1).
298
320
  const findings = [
299
- ...enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'create' }, policyOverride)),
300
- ...await queryScopeFindings(platform, project, environment, definition)
321
+ ...(existing === undefined
322
+ ? createFindings
323
+ : enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'update' }, policyOverride))),
324
+ ...scopeFindings
301
325
  ];
302
- const drift = unmappedMembers('CreateMicroserviceCommand', definition);
303
- const payload = await platform.tenant('POST', project, `/microservices/${resolvedApplicationId}`, { environment, body: definition });
304
- return attachFindings(withUnmappedMembers(withWriteDiff(withResolved(withNormalization(payload, normalized), [...resolver.resolved, ...hydrated]), writeDiff(definition, payload)), drift), findings);
326
+ const drift = unmappedMembers(existing === undefined ? 'CreateMicroserviceCommand' : 'UpdateMicroserviceCommand', definition);
327
+ const payload = existing === undefined
328
+ ? await platform.tenant('POST', project, `/microservices/${resolvedApplicationId}`, { environment, body: definition })
329
+ : await platform.tenant('PUT', project, `/microservices/${resolvedApplicationId}/${String(existing.id)}`, { environment, body: definition });
330
+ const saved = payload?.data;
331
+ const rename = ifExists === 'duplicate' ? platformRename(definition, saved, 'dash') : { sent: definition };
332
+ return attachFindings(withUnmappedMembers(withWriteDiff(withResolved(withRename(withNormalization(withOutcome(outlineWrite(payload, view, MICROSERVICE_OUTLINE_KEYS), existing), normalized), rename.renamed), [...resolver.resolved, ...hydrated]), writeDiff(rename.sent, payload)), drift), findings);
305
333
  }));
306
334
  server.registerTool('microservice_update', {
307
335
  title: 'Microservice güncelle',
308
336
  description: 'Microservice tanımını günceller (tam gövde). Hedefli değişiklik için microservice_patch tercih edin. \'description\' ZORUNLUDUR (MS-014, block). Ucun gerçek bir tuzağı varsa (auth istemiyor, sayfalama yok, atomik değil) \'caution\' alanına yazın — OPSİYONELDİR, tuzak yoksa boş bırakın. Gövde politika kontrolünden geçer. ' + constraintSummary('UpdateMicroserviceCommand'),
309
- inputSchema: { project: projectParam, environment: environmentParam, applicationId: appRef, id: guid, definition: definitionParam, policyOverride: policyOverrideParam },
337
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: appRef, id: guid, definition: definitionParam, view: recordWriteViewParam, policyOverride: policyOverrideParam },
310
338
  annotations: updates
311
- }, handle(config, async ({ project, environment, applicationId, id, definition, policyOverride }) => {
339
+ }, handle(config, async ({ project, environment, applicationId, id, definition, view, policyOverride }) => {
312
340
  const resolver = new RefResolver(platform, project, environment);
313
341
  const resolvedApplicationId = await resolver.applicationId(applicationId);
314
342
  const hydrated = await hydrateMicroserviceRefs(definition, resolver);
@@ -319,7 +347,7 @@ export function registerMicroserviceTools(server, platform) {
319
347
  ];
320
348
  const drift = unmappedMembers('UpdateMicroserviceCommand', definition);
321
349
  const payload = await platform.tenant('PUT', project, `/microservices/${resolvedApplicationId}/${id}`, { environment, body: definition });
322
- return attachFindings(withUnmappedMembers(withWriteDiff(withResolved(withNormalization(payload, normalized), [...resolver.resolved, ...hydrated]), writeDiff(definition, payload)), drift), findings);
350
+ return attachFindings(withUnmappedMembers(withWriteDiff(withResolved(withNormalization(outlineWrite(payload, view, MICROSERVICE_OUTLINE_KEYS), normalized), [...resolver.resolved, ...hydrated]), writeDiff(definition, payload)), drift), findings);
323
351
  }));
324
352
  server.registerTool('microservice_patch', {
325
353
  title: 'Microservice\'e patch uygula',