@appaflytech/zeyrek-ai-mcp 0.0.2 → 0.0.4

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.
Files changed (39) hide show
  1. package/.env.example +21 -7
  2. package/.env.production.example +42 -0
  3. package/README.md +14 -7
  4. package/dist/config.js +113 -46
  5. package/dist/knowledge/componentCatalog.js +476 -32
  6. package/dist/knowledge/componentEvents.js +154 -12
  7. package/dist/knowledge/errorPath.js +64 -0
  8. package/dist/knowledge/normalize.js +12 -1
  9. package/dist/knowledge/outlines.js +9 -2
  10. package/dist/knowledge/rules.js +184 -3
  11. package/dist/knowledge/styleResolve.js +485 -0
  12. package/dist/observability/trace.js +4 -0
  13. package/dist/platform/http.js +3 -0
  14. package/dist/platform/responses.js +80 -4
  15. package/dist/platform/screenshot.js +106 -0
  16. package/dist/resources/examples/layout-app-shell.json +368 -0
  17. package/dist/resources/examples/microservice-table-list.json +1 -0
  18. package/dist/resources/examples/middleware-authenticate.json +243 -47
  19. package/dist/resources/examples/workflow-basic.json +24 -2
  20. package/dist/resources/guides/component.md +6 -3
  21. package/dist/resources/guides/entity.md +33 -1
  22. package/dist/resources/guides/external-component.md +129 -0
  23. package/dist/resources/guides/layout.md +147 -0
  24. package/dist/resources/guides/microservice.md +35 -0
  25. package/dist/resources/guides/middleware.md +10 -0
  26. package/dist/resources/guides/page.md +137 -15
  27. package/dist/resources/guides/translation.md +80 -0
  28. package/dist/resources/guides/workflow.md +127 -2
  29. package/dist/server.js +4 -0
  30. package/dist/tools/externalComponents.js +285 -0
  31. package/dist/tools/layouts.js +48 -6
  32. package/dist/tools/microservices.js +2 -2
  33. package/dist/tools/pages.js +98 -30
  34. package/dist/tools/platformInfo.js +2 -2
  35. package/dist/tools/projects.js +7 -2
  36. package/dist/tools/screens.js +36 -0
  37. package/dist/tools/translations.js +37 -0
  38. package/dist/tools/workflows.js +3 -3
  39. package/package.json +4 -1
@@ -119,7 +119,7 @@ export function registerMicroserviceTools(server, platform) {
119
119
  }));
120
120
  server.registerTool('microservice_create', {
121
121
  title: 'Microservice oluştur',
122
- description: "Yeni microservice tanımlar (definition = CreateMicroserviceCommand). İlk kullanımdan önce platform_guide('microservice') okuyun: kaynak seçimi (table > query) ve selectFields disiplini. 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')). Gövde politika kontrolünden geçer.",
122
+ 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')). 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.",
123
123
  inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, definition: definitionParam, policyOverride: policyOverrideParam },
124
124
  annotations: writes
125
125
  }, handle(config, async ({ project, environment, applicationId, definition, policyOverride }) => {
@@ -130,7 +130,7 @@ export function registerMicroserviceTools(server, platform) {
130
130
  }));
131
131
  server.registerTool('microservice_update', {
132
132
  title: 'Microservice güncelle',
133
- description: 'Microservice tanımını günceller (tam gövde). Hedefli değişiklik için microservice_patch tercih edin. Gövde politika kontrolünden geçer.',
133
+ 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.',
134
134
  inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid, definition: definitionParam, policyOverride: policyOverrideParam },
135
135
  annotations: updates
136
136
  }, handle(config, async ({ project, environment, applicationId, id, definition, policyOverride }) => {
@@ -1,13 +1,15 @@
1
1
  import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
2
2
  import { orphanedFlowWarnings, normalizeComponentDefaults, normalizeSchemaOps, schemaOpValidationErrors, validationNote, componentDefaultsNote, replacePropNote, ensurePageContainer, retargetRootAdds, rootAddsNote, blockSizeNote, heightTypeNote, reorderSchemaBlocks, reorderNote, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, FLEX_LAYOUT_PROP_NAMES, resetPageContainerPosition, pageContainerPositionWarning, verticalGapWarnings, invalidScreenEventNames, invalidWidgetTypeNodes, withNormalization, PAGE_CONTAINER_ELEMENT_ID } from '../knowledge/normalize.js';
3
- import { fetchHost, pageUpdateBody, schemaParam, eventsParam, unwrap } from './screens.js';
3
+ import { fetchHost, pageUpdateBody, schemaParam, eventsParam, unwrap, modeParam, summarize, findNode } from './screens.js';
4
4
  import { applyCatalogLayoutRules, fetchComponentEventNames } from './components.js';
5
5
  import { formBindingWarnings } from '../knowledge/coverage.js';
6
6
  import { PlatformApiError } from '../platform/errors.js';
7
- import { handle } from '../platform/responses.js';
7
+ import { handle, toolResult, toolError } from '../platform/responses.js';
8
+ import { StudioScreenshotter } from '../platform/screenshot.js';
9
+ import { writeFile } from 'node:fs/promises';
10
+ import { resolve } from 'node:path';
8
11
  import { z } from 'zod';
9
12
  const opsParam = z.array(z.record(z.unknown())).describe("Micro-op listesi. Op sözlüğü: replace_prop {target, prop, value} | set_binding {target, prop, expression: '{{ Page.variables.x }}'} | add_component {parent (üst elementId ya da 'root'), index?, component} | remove_component {target} | move_component {target, parent, index?, position: {x, y}} | set_event {target, event, eventValue} | remove_event {target, event} (gateway emülasyonu: platformun micro-op'u olay SİLEMEZ, eventValue: null yalnız değersiz ölü kayıt bırakır — bu op şemayı okuyup olayı diziden çıkarır ve sayfayı tam gövdeyle geri yazar) | set_validations {target, validations: [{type, message, value, isActive}]} (komponentin validation listesini TAMAMEN değiştirir; boş dizi hepsini siler). target = elementId.");
10
- const modeParam = z.enum(['summary', 'subtree', 'full']).optional().describe("summary (varsayılan): yalnız id/elementId/name/type ağacı — hedef bulmak için bunu kullanın. subtree: componentId ile tek alt ağaç. full: tüm şema.");
11
13
  /**
12
14
  * PAGE-008 — `set_event` uydurma bir olay adıyla 200 döner ve bağ SESSİZCE ölü kalır: motor olayı
13
15
  * komponentin yaydığı adla eşleştirir, eşleşme yoksa akış hiç çalışmaz. Kontrol yalnız hedefin
@@ -46,28 +48,6 @@ function invalidComponentNames(nodes) {
46
48
  }
47
49
  return found;
48
50
  }
49
- function summarize(node) {
50
- const summary = {
51
- id: node.id,
52
- elementId: node.elementId,
53
- name: node.name,
54
- type: node.type
55
- };
56
- if (Array.isArray(node.children) && node.children.length > 0) {
57
- summary.children = node.children.map(summarize);
58
- }
59
- return summary;
60
- }
61
- function findNode(nodes, target) {
62
- for (const node of nodes ?? []) {
63
- if (node.elementId === target || String(node.id) === target)
64
- return node;
65
- const found = findNode(node.children, target);
66
- if (found)
67
- return found;
68
- }
69
- return undefined;
70
- }
71
51
  function removeNodeFromTree(nodes, target) {
72
52
  for (let index = 0; index < nodes.length; index++) {
73
53
  if (nodes[index].elementId === target)
@@ -235,10 +215,11 @@ export function registerPageTools(server, platform) {
235
215
  applicationSlug: z.string().min(1).describe("Uygulamanın slug'ı (application_list / project_overview)"),
236
216
  pageSlug: z.string().min(1).describe("Sayfanın slug'ı (page_list)"),
237
217
  view: z.enum(['summary', 'full']).optional().describe('summary (varsayılan): şema/layout ağaçları id/name/type özetine indirgenir. full: ham gövde — büyük olabilir'),
218
+ resolveStyles: z.boolean().optional().describe("summary görünümünde her düğüme EFEKTİF stilini ekler: tema componentStyles → preset → düğümün kendi stili kaskadı çözülür (aibox'ta tipli stil grupları da derlenir ve TABAN katman olur). styleLayers hangi katmanın konuştuğunu söyler — 'bu kutu neden böyle görünüyor' sorusunun cevabı budur."),
238
219
  anonymous: z.boolean().optional().describe("true: istek HİÇBİR Authorization header'ı olmadan gider — 'korumalı sayfa tokensız ne döner, şema sızıyor mu?' sorusunun cevabı budur. Korumalı sayfada middlewareBlocked bekleyin.")
239
220
  },
240
221
  annotations: readOnly
241
- }, handle(config, async ({ project, environment, applicationSlug, pageSlug, view, anonymous }) => {
222
+ }, handle(config, async ({ project, environment, applicationSlug, pageSlug, view, resolveStyles, anonymous }) => {
242
223
  let response;
243
224
  try {
244
225
  response = await platform.tenant('GET', project, `/${applicationSlug}/p/${pageSlug}`, { environment, anonymous });
@@ -258,20 +239,107 @@ export function registerPageTools(server, platform) {
258
239
  return response;
259
240
  const body = (response?.data ?? {});
260
241
  const layout = body.layout;
242
+ const theme = body.project?.theme;
243
+ // undefined: stil raporu hiç yazılmaz. null: tema yok ama kaskad yine çözülür.
244
+ const styleSource = resolveStyles ? (theme ?? null) : undefined;
261
245
  const asNodes = (value) => (Array.isArray(value) ? value : []);
262
246
  const named = (value) => (Array.isArray(value) ? value : []).map(entry => ({ id: entry.id, name: entry.name, slug: entry.slug }));
263
247
  return {
264
248
  page: { id: body.id, name: body.name, slug: body.slug, browserTitle: body.browserTitle, isHomepage: body.isHomepage },
265
249
  events: body.events ?? [],
266
- layout: layout ? { id: layout.id, name: layout.name, views: asNodes(layout.views).map(summarize) } : null,
267
- schema: asNodes(body.schema).map(summarize),
250
+ layout: layout ? { id: layout.id, name: layout.name, views: asNodes(layout.views).map(node => summarize(node, styleSource)) } : null,
251
+ schema: asNodes(body.schema).map(node => summarize(node, styleSource)),
268
252
  widgets: named(body.widgets),
269
253
  flows: named(body.flows),
270
254
  variableKeys: Object.keys((body.variables ?? {})),
271
255
  hasTheme: Boolean(body.project?.theme),
272
- note: "schema, layout'taki slot'un İÇİNE yerleşerek çizilir (Kural 1f); tam gövde gerekirse view:'full'."
256
+ note: resolveStyles
257
+ ? "schema, layout'taki slot'un İÇİNE yerleşerek çizilir (Kural 1f). resolvedStyle = componentStyles → preset → düğüm stili kaskadı (aibox'ta tipli grupların derlemesi TABAN katmandır); styleLayers hangi katmanların konuştuğunu söyler."
258
+ : "schema, layout'taki slot'un İÇİNE yerleşerek çizilir (Kural 1f); tam gövde gerekirse view:'full'. Bir düğümün ekranda GERÇEKTEN aldığı stili görmek için resolveStyles:true."
273
259
  };
274
260
  }));
261
+ const screenshotter = new StudioScreenshotter(config);
262
+ const maxInlinePngBytes = 5 * 1024 * 1024;
263
+ server.registerTool('page_screenshot', {
264
+ title: 'Sayfanın ekran görüntüsünü al',
265
+ description: "Studio arayüzünün runtime render rotasını (/dev ya da /view) headless Chromium'da açar ve sayfanın PNG ekran görüntüsünü inline görüntü olarak döner — kurduğun sayfayı GÖZLE doğrulamanın yolu budur (page_runtime_get paketi verir, bu tool ekranda ne ÇİZİLDİĞİNİ gösterir). Sayfaya bağlı middleware'ler gerçekten koşar; reddedilirse yanıtta middlewareBlocked işaretlenir ve hata/boş durum karesi yine döner — bu da bir doğrulamadır. STUDIO_BASE_URL tanımlı olmalı; ilk kullanım için `npx playwright-core install chromium` gerekir.",
266
+ inputSchema: {
267
+ project: projectParam,
268
+ environment: environmentParam,
269
+ applicationSlug: z.string().min(1).describe("Uygulamanın slug'ı (application_list / project_overview)"),
270
+ pageSlug: z.string().min(1).describe("Sayfanın slug'ı (page_list)"),
271
+ viewport: z.object({
272
+ width: z.number().int().min(320).max(3840),
273
+ height: z.number().int().min(240).max(2160)
274
+ }).optional().describe('Varsayılan 1280x800 (deviceScaleFactor 1)'),
275
+ fullPage: z.boolean().optional().describe('true: kaydırılan kısım dahil tüm sayfa; varsayılan yalnız viewport karesi'),
276
+ delayMs: z.number().int().min(0).max(10000).optional().describe('Animasyon/geç gelen veri için EK bekleme; 600 ms yerleşme beklemesi her zaman uygulanır'),
277
+ anonymous: z.boolean().optional().describe("true: NG_AccessToken çerezi enjekte edilmez — anonim ziyaretçinin gördüğü kare. Korumalı sayfada middlewareBlocked bekleyin."),
278
+ outPath: z.string().min(1).optional().describe('Verilirse PNG bu yerel yola yazılır ve inline görüntü DÖNMEZ; yalnız özet (yol, boyut) döner — büyük fullPage kareleri için')
279
+ },
280
+ annotations: readOnly
281
+ }, async ({ project, environment, applicationSlug, pageSlug, viewport, fullPage, delayMs, anonymous, outPath }) => {
282
+ try {
283
+ if (!config.studioBaseUrl) {
284
+ throw new PlatformApiError("page_screenshot için STUDIO_BASE_URL tanımlı değil. Studio arayüzünün kök adresini .env'e ekleyin (yerel geliştirmede http://localhost:3000, dağıtımda nginx adresi).");
285
+ }
286
+ const resolvedEnvironment = platform.resolveEnvironment(environment);
287
+ const lane = resolvedEnvironment === 'development' ? 'dev' : resolvedEnvironment === 'production' ? 'view' : undefined;
288
+ if (!lane) {
289
+ throw new PlatformApiError(`page_screenshot yalnız 'development' (/dev) ve 'production' (/view) ortamlarını görüntüleyebilir; '${resolvedEnvironment}' için Studio'da karşılık gelen bir rota yok.`);
290
+ }
291
+ const url = `${config.studioBaseUrl}/${lane}/${platform.resolveProject(project)}/${applicationSlug}/${pageSlug}`;
292
+ const size = viewport ?? { width: 1280, height: 800 };
293
+ const shot = await screenshotter.capture({
294
+ url,
295
+ apiPathMarker: `/${applicationSlug}/p/${pageSlug}`,
296
+ token: anonymous ? undefined : await platform.accessToken(),
297
+ cookieUrl: config.studioBaseUrl,
298
+ viewport: size,
299
+ fullPage: fullPage === true,
300
+ delayMs: delayMs ?? 0
301
+ });
302
+ const meta = {
303
+ url,
304
+ environment: resolvedEnvironment,
305
+ viewport: size,
306
+ fullPage: fullPage === true,
307
+ sizeBytes: shot.png.byteLength,
308
+ elapsedMs: shot.elapsedMs,
309
+ ...(shot.apiStatus === undefined ? {} : { apiStatus: shot.apiStatus }),
310
+ ...(shot.apiStatus !== undefined && shot.apiStatus >= 300
311
+ ? { middlewareBlocked: true, note: 'Sayfanın veri isteği reddedildi — karede hata/boş durum görünüyor olabilir. Korumalı sayfada kimliksiz/yetkisiz isteğin reddedilmesi BEKLENEN davranıştır (page_runtime_get ile karşılaştırın).' }
312
+ : {}),
313
+ ...(shot.renderWaitTimedOut
314
+ ? { renderWaitTimedOut: true, note: "Sayfanın veri isteği gözlenmedi — kare hata/boş durumu gösteriyor olabilir; STUDIO_BASE_URL ve slug'ları doğrulayın." }
315
+ : {})
316
+ };
317
+ if (outPath) {
318
+ const absolute = resolve(outPath);
319
+ try {
320
+ await writeFile(absolute, shot.png);
321
+ }
322
+ catch (error) {
323
+ const message = error instanceof Error ? error.message : String(error);
324
+ throw new PlatformApiError(`page_screenshot: dosya yazılamadı — ${absolute} (${message}). Yol gateway sürecinin çalıştığı makineye göre çözülür.`);
325
+ }
326
+ return toolResult({ path: absolute, ...meta }, config);
327
+ }
328
+ if (shot.png.byteLength > maxInlinePngBytes) {
329
+ throw new PlatformApiError(`Görüntü ${shot.png.byteLength} B — inline sınırı (${maxInlinePngBytes} B) aşıyor. outPath verin (dosyaya yazılır, model bağlamına girmez) ya da fullPage'i kapatıp viewport'u küçültün.`);
330
+ }
331
+ return {
332
+ content: [
333
+ { type: 'image', data: shot.png.toString('base64'), mimeType: 'image/png' },
334
+ { type: 'text', text: JSON.stringify(meta) }
335
+ ],
336
+ structuredContent: meta
337
+ };
338
+ }
339
+ catch (error) {
340
+ return toolError(error);
341
+ }
342
+ });
275
343
  server.registerTool('page_schema_get', {
276
344
  title: 'Sayfa şemasını oku',
277
345
  description: "Sayfanın komponent şemasını okur; yanıttaki updatedAt değeri page_schema_patch'in expectedUpdatedAt kontrolünde kullanılır.",
@@ -297,7 +365,7 @@ export function registerPageTools(server, platform) {
297
365
  throw new PlatformApiError(`Sayfada '${componentId}' id'li bir komponent yok; page_schema_get'i summary kipinde çağırıp hedefi doğrulayın.`);
298
366
  return { updatedAt, schema: subtree };
299
367
  }
300
- return { updatedAt, schema: (page.schema ?? []).map(summarize) };
368
+ return { updatedAt, schema: (page.schema ?? []).map(node => summarize(node)) };
301
369
  }));
302
370
  server.registerTool('page_state', {
303
371
  title: 'Sayfa durumunu getir',
@@ -13,7 +13,7 @@ export function registerPlatformTools(server, platform) {
13
13
  }, handle(config, () => platform.org('GET', '/connect/userinfo')));
14
14
  server.registerTool('platform_guide', {
15
15
  title: 'Platform rehberi oku',
16
- description: "Modülün iş kuralları rehberini döner (workflow, microservice, entity, page, component, html, middleware, view, expressions, getting-started, modify-existing). Bir modülde İLK yazma işleminden önce ilgili rehberi okuyun; sayfa/layout/flow işleri için page, komponent modeli/olay sözleşmeleri için component, hazır komponentler yetmediğinde HTML kaçış kapısı (Mustache, fireEvent, script kuralları) için html, auth/oturum/yetki için middleware, tam metin arama indeksi için view, ifade yazarken kullanılabilir fonksiyon/bağlam sözlüğü (system.*, file.*, Page.*, state.*, Steps…) için expressions rehberi. Örnek gövdeler için ngapps://examples kaynaklarına bakın.",
16
+ description: "Modülün iş kuralları rehberini döner (workflow, microservice, entity, page, layout, component, html, middleware, view, expressions, translation, getting-started, modify-existing). Bir modülde İLK yazma işleminden önce ilgili rehberi okuyun; sayfa/layout/flow işleri için page, yerleşim kabuğunun kendi kuralları (tek slot, views alanı, micro-op yokluğu, mobil yığılma) için layout, komponent modeli/olay sözleşmeleri için component, hazır komponentler yetmediğinde HTML kaçış kapısı (Mustache, fireEvent, script kuralları) için html, auth/oturum/yetki için middleware, tam metin arama indeksi için view, ifade yazarken kullanılabilir fonksiyon/bağlam sözlüğü (system.*, file.*, Page.*, state.*, Steps…) için expressions, sayfa/içerik çevirileri (çoklu dil) için translation rehberi. Örnek gövdeler için ngapps://examples kaynaklarına bakın.",
17
17
  inputSchema: {
18
18
  guide: z.string().describe('Rehber adı; bilinmeyen ad verilirse mevcut rehberler listelenir')
19
19
  },
@@ -27,7 +27,7 @@ export function registerPlatformTools(server, platform) {
27
27
  }));
28
28
  server.registerTool('platform_gap_report', {
29
29
  title: 'Kabiliyet boşluğu bildir',
30
- description: "Yapmak İSTEDİĞİNİZ ama platform desteklemediği için yapamadığınız her noktada BUNU ÇAĞIRIN — tasarımı sessizce küçültmeyin. Bu çağrı platforma gitmez, yalnızca günlüğe 'KABİLİYET BOŞLUĞU' bloğu yazar; hata günlüğünde görünmeyen bu kayıtlar ürün yol haritasının girdisidir. Önce rehbere bakıp (platform_guide, özellikle hazır komponent yetmiyorsa 'html' kaçış kapısı) gerçekten desteklenmediğinden emin olun: desteklenen bir şeyi 'yok' diye bildirmek yanlış yol haritası üretir.",
30
+ description: "Yapmak İSTEDİĞİNİZ ama platform desteklemediği için yapamadığınız her noktada BUNU ÇAĞIRIN — tasarımı sessizce küçültmeyin. Bu çağrı platforma gitmez, yalnızca günlüğe 'KABİLİYET BOŞLUĞU' bloğu yazar; hata günlüğünde görünmeyen bu kayıtlar ürün yol haritasının girdisidir. Önce rehbere bakıp (platform_guide; hazır komponent yetmiyorsa tek seferlik için 'html' kaçış kapısı, kalıcı bir ihtiyaç içinse 'external-component' — kendi komponentinizi yazıp yayınlayabilirsiniz) gerçekten desteklenmediğinden emin olun: desteklenen bir şeyi 'yok' diye bildirmek yanlış yol haritası üretir.",
31
31
  inputSchema: {
32
32
  goal: z.string().min(1).describe('Ne yapmak istediniz — kullanıcıya görünen sonuç olarak yazın ("randevu listesini hasta adına göre süzmek")'),
33
33
  blocker: z.string().min(1).describe('Neden yapamadınız — hangi kabiliyetin eksik olduğu ("uçta X yok", "komponentte Y prop\'u yok")'),
@@ -79,12 +79,14 @@ export function registerProjectTools(server, platform) {
79
79
  }));
80
80
  server.registerTool('project_update', {
81
81
  title: 'Proje güncelle',
82
- description: 'Proje adını, slug\'ını ve temasını günceller. LoaderImage bu tool\'da desteklenmez.',
82
+ description: 'Proje adını, slug\'ını, temasını ve dil ayarlarını günceller. languages/defaultLanguage verilmezse mevcut değerlerine dokunulmaz. LoaderImage bu tool\'da desteklenmez.',
83
83
  inputSchema: {
84
84
  id: guid,
85
85
  name: z.string().min(1),
86
86
  slug: z.string().optional(),
87
- theme: z.string().optional()
87
+ theme: z.string().optional(),
88
+ languages: z.array(z.string().min(1)).min(1).optional().describe('ISO 639-1 kod listesi (proje dilleri); sayfa çevirileri bu kodlarla doğrulanır'),
89
+ defaultLanguage: z.string().min(1).optional().describe("languages içinde olmalı — proje dilleri hiç kurulmadıysa (ilk kurulumda) tek başına verilebilir")
88
90
  },
89
91
  annotations: updates
90
92
  }, handle(config, args => {
@@ -93,6 +95,9 @@ export function registerProjectTools(server, platform) {
93
95
  form.set('Slug', args.slug);
94
96
  if (args.theme)
95
97
  form.set('Theme', args.theme);
98
+ args.languages?.forEach(language => form.append('Languages', language));
99
+ if (args.defaultLanguage)
100
+ form.set('DefaultLanguage', args.defaultLanguage);
96
101
  return platform.org('PUT', `/projects/${args.id}`, { form });
97
102
  }));
98
103
  server.registerTool('project_delete', {
@@ -1,4 +1,5 @@
1
1
  import { PlatformApiError } from '../platform/errors.js';
2
+ import { resolveNodeStyleReport } from '../knowledge/styleResolve.js';
2
3
  import { z } from 'zod';
3
4
  export const hostParam = z.enum(['page', 'layout', 'component'])
4
5
  .describe("Akışın barındırıcısı: page | layout | component (widget de component'tir)");
@@ -6,6 +7,41 @@ export const schemaParam = z.array(z.record(z.unknown()))
6
7
  .describe('Komponent ağacı (DesignComponentManagementSchema listesi); verilmezse mevcut şema korunur');
7
8
  export const eventsParam = z.array(z.record(z.unknown()))
8
9
  .describe("Sayfa/layout düzeyi olaylar: [{ name, value }] — value bir flow id'sidir");
10
+ export const modeParam = z.enum(['summary', 'subtree', 'full']).optional().describe("summary (varsayılan): yalnız id/elementId/name/type ağacı — hedef bulmak için bunu kullanın. subtree: componentId ile tek alt ağaç. full: tüm şema.");
11
+ /**
12
+ * Ağacı yalnız kimlik alanlarına indirger; hedef aramak için okunur boyutta tutar.
13
+ * `styleSource` verilirse (undefined DEĞİLSE) her düğüme efektif stil raporu da
14
+ * yazılır: tema componentStyles → preset → düğümün kendi stili kaskadı.
15
+ */
16
+ export function summarize(node, styleSource) {
17
+ const summary = {
18
+ id: node.id,
19
+ elementId: node.elementId,
20
+ name: node.name,
21
+ type: node.type
22
+ };
23
+ if (styleSource !== undefined) {
24
+ const report = resolveNodeStyleReport(node, styleSource ?? undefined);
25
+ if (report.resolvedStyle !== undefined)
26
+ summary.resolvedStyle = report.resolvedStyle;
27
+ summary.styleLayers = report.styleLayers;
28
+ }
29
+ if (Array.isArray(node.children) && node.children.length > 0) {
30
+ summary.children = node.children.map(child => summarize(child, styleSource));
31
+ }
32
+ return summary;
33
+ }
34
+ /** elementId ya da id ile ağaçta düğüm bulur. */
35
+ export function findNode(nodes, target) {
36
+ for (const node of nodes ?? []) {
37
+ if (node.elementId === target || String(node.id) === target)
38
+ return node;
39
+ const found = findNode(node.children, target);
40
+ if (found)
41
+ return found;
42
+ }
43
+ return undefined;
44
+ }
9
45
  function asArray(value) {
10
46
  return Array.isArray(value) ? value : [];
11
47
  }
@@ -0,0 +1,37 @@
1
+ import { guid, projectParam, environmentParam, readOnly, updates } from './common.js';
2
+ import { handle } from '../platform/responses.js';
3
+ import { z } from 'zod';
4
+ const translationKeyInput = z.object({
5
+ key: z.string().min(1),
6
+ values: z.record(z.string()).describe("Dil kodu (ISO 639-1) → çeviri metni; kodlar projenin languages listesinde (project_get/project_update) tanımlı olmalı"),
7
+ componentId: guid.optional().describe("Çeviriyi belirli bir komponente bağlar; o komponent silinince kaydı da silinir")
8
+ });
9
+ export function registerTranslationTools(server, platform) {
10
+ const config = platform.config;
11
+ server.registerTool('translation_list', {
12
+ title: 'Çevirileri listele',
13
+ description: "Application'ın tüm çeviri kayıtlarını (dil × anahtar × değer) düz liste olarak döner. Proje dilleri/varsayılan dil için project_get; sayfada bir prop'u çeviriye bağlamak için platform_guide('translation').",
14
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid },
15
+ annotations: readOnly
16
+ }, handle(config, ({ project, environment, applicationId }) => platform.tenant('GET', project, `/translations/${applicationId}`, { environment })));
17
+ server.registerTool('translation_update', {
18
+ title: 'Çevirileri güncelle',
19
+ description: "Bir ya da daha çok çeviri anahtarını toplu ekler/günceller (keys) ve/veya siler (deletedKeys) — tek çağrıda ikisi de verilebilir. 'keys' üzerinden yeni bir anahtar da açılabilir (upsert). 'values'taki dil kodları projenin languages listesinde tanımlı olmalı, aksi hâlde platform reddeder.",
20
+ inputSchema: {
21
+ project: projectParam,
22
+ environment: environmentParam,
23
+ applicationId: guid,
24
+ keys: z.array(translationKeyInput).optional().describe('Eklenecek/güncellenecek anahtarlar'),
25
+ deletedKeys: z.array(z.string().min(1)).optional().describe('Silinecek anahtar adları')
26
+ },
27
+ annotations: updates
28
+ }, handle(config, ({ project, environment, applicationId, keys, deletedKeys }) => {
29
+ if (!keys?.length && !deletedKeys?.length) {
30
+ throw new Error("'keys' ya da 'deletedKeys' parametrelerinden en az biri dolu olmalı.");
31
+ }
32
+ return platform.tenant('PUT', project, `/translations/${applicationId}`, {
33
+ environment,
34
+ body: { keys: keys ?? [], deletedKeys: deletedKeys ?? [] }
35
+ });
36
+ }));
37
+ }
@@ -106,7 +106,7 @@ export function registerWorkflowTools(server, platform) {
106
106
  }));
107
107
  server.registerTool('workflow_create', {
108
108
  title: 'Workflow oluştur',
109
- description: "Yeni workflow tanımlar (definition = CreateWorkflowCommand). type: 'workflow' | 'automation' | 'middleware'. İlk kullanımdan önce platform_guide('workflow') okuyun: DTO-önce akışı, branch bağlama (branches[].nextStepId), tip×step matrisi. Middleware (istek öncesi koşan guard/setter, user değişkeni, alfabetik sıra) için platform_guide('middleware') + ngapps://examples/middleware-authenticate. Gövde politika kontrolünden geçer.",
109
+ description: "Yeni workflow tanımlar (definition = CreateWorkflowCommand). type: 'workflow' | 'automation' | 'middleware'. İlk kullanımdan önce platform_guide('workflow') okuyun: DTO-önce akışı, branch bağlama (branches[].nextStepId), tip×step matrisi. Her adım ve akışın kendisi 'description' taşımak ZORUNDADIR (WF-024, block): adımın NE İŞE YARADIĞINI 1-2 cümleyle yazın — Studio kanvasında ⓘ ikonu olarak görünür; mekanizmayı değil niyeti yazın. Middleware (istek öncesi koşan guard/setter, user değişkeni, alfabetik sıra) için platform_guide('middleware') + ngapps://examples/middleware-authenticate. Adımın gerçek bir tuzağı varsa (idempotent değil, kapsam adımda değil, dış bağımlılık, sessiz düşen dal) 'caution' alanına yazın — OPSİYONELDİR, tuzak yoksa boş bırakın; doldurma metin yazmayın. Gövde politika kontrolünden geçer.",
110
110
  inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, definition: definitionParam, policyOverride: policyOverrideParam },
111
111
  annotations: writes
112
112
  }, handle(config, async ({ project, environment, applicationId, definition, policyOverride }) => {
@@ -117,7 +117,7 @@ export function registerWorkflowTools(server, platform) {
117
117
  }));
118
118
  server.registerTool('workflow_update', {
119
119
  title: 'Workflow güncelle',
120
- description: 'Workflow tanımını günceller (definition = UpdateWorkflowCommand, tam gövde). Hedefli değişiklik için workflow_patch tercih edin. Gövde politika kontrolünden geçer.',
120
+ description: 'Workflow tanımını günceller (definition = UpdateWorkflowCommand, tam gövde). Hedefli değişiklik için workflow_patch tercih edin. Her adım ve akışın kendisi \'description\' taşımak ZORUNDADIR (WF-024, block). Adımın gerçek bir tuzağı varsa (idempotent değil, kapsam adımda değil, dış bağımlılık, sessiz düşen dal) \'caution\' alanına yazın — OPSİYONELDİR, tuzak yoksa boş bırakın; doldurma metin yazmayın. Gövde politika kontrolünden geçer.',
121
121
  inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid, definition: definitionParam, policyOverride: policyOverrideParam },
122
122
  annotations: updates
123
123
  }, handle(config, async ({ project, environment, applicationId, id, definition, policyOverride }) => {
@@ -128,7 +128,7 @@ export function registerWorkflowTools(server, platform) {
128
128
  }));
129
129
  server.registerTool('workflow_patch', {
130
130
  title: 'Workflow\'a patch uygula',
131
- description: 'Tam gövdeyi bağlama almadan hedefli değişiklik: gateway mevcut workflow\'u çeker, JSON Patch işlemlerini uygular, politika kontrolünden geçirir, tam gövdeyi geri yazar. Yanıt outline özetidir.',
131
+ description: 'Tam gövdeyi bağlama almadan hedefli değişiklik: gateway mevcut workflow\'u çeker, JSON Patch işlemlerini uygular, politika kontrolünden geçirir, tam gövdeyi geri yazar. Değişiklik hedefli ama yazma değil: hata yolu olmayan bir workflow/middleware kaydına patch atmak o kaydın grafiğini de düzeltir — start adımına global-error dalı + 500 dönen response adımı eklenir ve yanıtta gatewayNormalized ile bildirilir. Yanıt outline özetidir.',
132
132
  inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid, operations: patchOperationsParam, policyOverride: policyOverrideParam },
133
133
  annotations: updates
134
134
  }, handle(config, async ({ project, environment, applicationId, id, operations, policyOverride }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appaflytech/zeyrek-ai-mcp",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "description": "MCP gateway exposing the NgApps platform APIs as tools",
6
6
  "license": "UNLICENSED",
@@ -13,6 +13,7 @@
13
13
  "files": [
14
14
  "dist",
15
15
  ".env.example",
16
+ ".env.production.example",
16
17
  "README.md"
17
18
  ],
18
19
  "publishConfig": {
@@ -26,6 +27,7 @@
26
27
  "prepack": "npm run build",
27
28
  "typecheck": "tsc --noEmit",
28
29
  "test": "vitest run",
30
+ "docs:lint": "tsx scripts/docs-lint.ts",
29
31
  "generate-schemas": "tsx scripts/generate-schemas.ts",
30
32
  "generate-reserved-tables": "tsx scripts/generate-reserved-tables.ts",
31
33
  "harvest-examples": "tsx scripts/harvest-examples.ts"
@@ -36,6 +38,7 @@
36
38
  "express": "^4.21.0",
37
39
  "fast-json-patch": "^3.1.1",
38
40
  "pino": "^9.5.0",
41
+ "playwright-core": "^1.49.0",
39
42
  "undici": "^7.3.0",
40
43
  "zod": "^3.24.1"
41
44
  },