@appaflytech/zeyrek-ai-mcp 0.0.23 → 0.0.24

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 (47) hide show
  1. package/.env.example +15 -0
  2. package/dist/config.js +9 -0
  3. package/dist/index.js +2 -0
  4. package/dist/knowledge/componentCatalog.js +1 -1
  5. package/dist/knowledge/mobileComponentCatalog.js +231 -58
  6. package/dist/knowledge/mobileComponentEvents.js +118 -3
  7. package/dist/knowledge/normalize.js +16 -6
  8. package/dist/knowledge/platform/lint.js +110 -3
  9. package/dist/knowledge/platform/mobileRules.js +64 -8
  10. package/dist/knowledge/rules.js +56 -7
  11. package/dist/knowledge/styleResolve.js +49 -3
  12. package/dist/knowledge/styleResolveNative.js +104 -4
  13. package/dist/observability/trace.js +1 -1
  14. package/dist/platform/argumentAliases.js +128 -0
  15. package/dist/platform/bodyFile.js +103 -0
  16. package/dist/platform/capture/capture.js +29 -5
  17. package/dist/platform/capture/console-log.js +80 -0
  18. package/dist/platform/capture/index.js +1 -0
  19. package/dist/platform/diagnostics.js +10 -4
  20. package/dist/platform/http.js +23 -7
  21. package/dist/platform/responses.js +6 -2
  22. package/dist/platform/schemaReadMemory.js +60 -0
  23. package/dist/platform/secrets.js +9 -0
  24. package/dist/platform/strictArguments.js +68 -12
  25. package/dist/platform/tokenVault.js +187 -4
  26. package/dist/resources/guides/expressions.md +1 -1
  27. package/dist/resources/guides/microservice.md +9 -2
  28. package/dist/resources/guides/middleware.md +5 -1
  29. package/dist/resources/guides/mobile.md +150 -14
  30. package/dist/resources/guides/page.md +146 -39
  31. package/dist/resources/guides/theme.md +4 -1
  32. package/dist/resources/guides/workflow.md +7 -1
  33. package/dist/resources/schema-fingerprint.json +2 -2
  34. package/dist/server.js +1 -1
  35. package/dist/tools/datasources.js +1 -1
  36. package/dist/tools/designSchemaPatch.js +4 -3
  37. package/dist/tools/externalComponents.js +1 -1
  38. package/dist/tools/flows.js +70 -4
  39. package/dist/tools/layouts.js +5 -5
  40. package/dist/tools/microservices.js +26 -10
  41. package/dist/tools/pages.js +192 -36
  42. package/dist/tools/platformInfo.js +4 -1
  43. package/dist/tools/rows.js +7 -1
  44. package/dist/tools/schemaOps.js +1 -1
  45. package/dist/tools/screenWrite.js +4 -3
  46. package/dist/tools/screens.js +81 -0
  47. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -84,7 +84,7 @@ const moduleRegistrations = {
84
84
  mobile: registerMobileTools,
85
85
  };
86
86
  export function createGatewayServer(platform) {
87
- const server = rejectUnknownArguments(instrumentServer(new McpServer({ name: "ngapps", version: "0.1.0" }), platform.config));
87
+ const server = rejectUnknownArguments(instrumentServer(new McpServer({ name: "ngapps", version: "0.1.0" }), platform.config), platform);
88
88
  for (const moduleName of platform.config.modules) {
89
89
  moduleRegistrations[moduleName](server, platform);
90
90
  }
@@ -118,7 +118,7 @@ export function registerDatasourceTools(server, platform) {
118
118
  }, handle(config, ({ project, environment }) => platform.tenant('GET', project, '/datasources/resources', { environment })));
119
119
  server.registerTool('datasource_configuration_options', {
120
120
  title: 'Yapılandırma seçenekleri',
121
- description: "Datasource'un alan tipi kataloğunu döner: commonType başına dbType listesi, varsayılan tip, tipin kabul ettiği seçenek anahtarları (length/precision/collation/...) ve dönüştürülebilir tipler; ayrıca ilişki kuralları (onUpdate/onDelete) ve identity generation tipleri. VARSAYILAN YANIT ÖZETTİR (~5 KB): collation DEĞER listesi (816 kayıt, tam yanıtın %79'u) dışarıdadır — tipin collation kabul edip etmediği zaten options'ta yazar. Collation değerleri gerçekten gerekiyorsa includeCollations: true ile tam yanıtı isteyin (~45 KB).",
121
+ description: "Datasource'un alan tipi kataloğunu döner: commonType başına dbType listesi, varsayılan tip, tipin kabul ettiği seçenek anahtarları (length/precision/collation/...) ve dönüştürülebilir tipler; ayrıca ilişki kuralları (onUpdate/onDelete) ve identity generation tipleri. İlişki alanına yazarken kuralı SAYI olarak verin (`None` 0 · `Cascade` 1 · `SetNull` 2 · `SetDefault` 3 — `Rule` sırası): her backend sürümünde çalışır. Katalog adı listeler; `onDelete` adı (`Cascade`) 2026-09-27'den sonraki backend'de de kabul edilir, `onUpdate` adı hâlâ reddedilir (500). VARSAYILAN YANIT ÖZETTİR (~5 KB): collation DEĞER listesi (816 kayıt, tam yanıtın %79'u) dışarıdadır — tipin collation kabul edip etmediği zaten options'ta yazar. Collation değerleri gerçekten gerekiyorsa includeCollations: true ile tam yanıtı isteyin (~45 KB).",
122
122
  inputSchema: {
123
123
  project: projectParam,
124
124
  environment: environmentParam,
@@ -87,7 +87,8 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
87
87
  if (deletedFlows.length > 0 && allowFlowDeletion !== true)
88
88
  throw new PlatformApiError(orphanedFlowMessage(deletedFlows), 400);
89
89
  const added = opRecords.filter(entry => entry.op === 'add_component').map(entry => entry.component);
90
- assertScreenWriteGuards(spec.kind, { schema: added });
90
+ const resolved = await resolveProfile(platform, applicationId);
91
+ assertScreenWriteGuards(spec.kind, { schema: added }, resolved.profile.platform);
91
92
  const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, added);
92
93
  const flowWarnings = opRecords.some(entry => entry.op === 'remove_component')
93
94
  ? [...orphanedFlowWarnings(nodes, events, opRecords), ...staleSetterWarnings(Array.isArray(record.flows) ? record.flows : [], opRecords, nodes)]
@@ -102,12 +103,12 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
102
103
  || (entry.op === 'replace_prop' && FLEX_LAYOUT_PROP_NAMES.has(String(entry.prop ?? ''))));
103
104
  const nested = simulatable ? nestedRepeaterNodes(simulated) : [];
104
105
  if (nested.length > 0)
105
- throw new PlatformApiError(nestedRepeaterMessage(nested), 400);
106
- const resolved = await resolveProfile(platform, applicationId);
106
+ throw new PlatformApiError(nestedRepeaterMessage(nested, resolved.profile.platform), 400);
107
107
  const reshaped = simulatable || opRecords.some(entry => entry.op === 'remove_component');
108
108
  const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, {
109
109
  kind: spec.kind,
110
110
  ops: [...opRecords, ...propTypeOps],
111
+ savedNodes: nodes,
111
112
  steps: await boundFlowSteps(platform, resolved, project, environment, host, applicationId, id, opRecords)
112
113
  });
113
114
  const layoutWarnings = reshaped
@@ -21,7 +21,7 @@ async function assertExternalComponentsAllowed(platform, applicationId, tool) {
21
21
  throw new PlatformApiError(`${tool}: ${findingLine({
22
22
  code: 'MOB-006',
23
23
  severity: 'warn',
24
- message: "harici bileşen mobil uygulamada desteklenmiyor — protokol v1 tarayıcıda koşan .mjs custom element'tir, native çizer onu yükleyemez (mobil protokol v2 Faz 3). Çağrı platforma gönderilmedi; mobil ekranı katalog bileşenleriyle kurun (component_list)."
24
+ message: "harici bileşen mobil uygulamada desteklenmiyor — protokol v1 tarayıcıda koşan .mjs custom element'tir, native çizer onu yükleyemez (mobil protokol v2 Faz 3). Çağrı platforma gönderilmedi; mobil ekranı katalog bileşenleriyle kurun (component_list). Katalogda karşılığı olmayan küçük görünüm için geçici yol 'html' bileşenidir: içerik korumalı belgede tema değişkenleriyle çizilir, fireEvent/ngApps.emit ile akışa bağlanır (component_get('html'))."
25
25
  })}`, 400);
26
26
  }
27
27
  /** Platformun köprü protokolü; scaffold ve publish bu sürüme göre üretir/doğrular (backend ExternalComponentRules.CurrentProtocolVersion) */
@@ -275,7 +275,7 @@ async function functionWarnings(platform, host, project, environment, applicatio
275
275
  }
276
276
  const missing = fn.parameters.filter(parameter => parameter.required && !given.includes(parameter.name)).map(parameter => parameter.name);
277
277
  if (missing.length > 0) {
278
- warnings.add(`FLOW-024: '${label(step)}' adımında '${fn.name}' fonksiyonunun zorunlu parametresi eksik: ${missing.join(', ')} — çağrı 'Functions.${fn.name}: "${missing[0]}" is required' ile düşer, Steps.${String(step.name ?? label(step))}.error dolar ve akış hata gövdesiyle 'always' dalından devam eder.`);
278
+ warnings.add(`FLOW-024: '${label(step)}' adımında '${fn.name}' fonksiyonunun zorunlu parametresi eksik: ${missing.join(', ')} — çağrı 'Functions.${fn.name}: "${missing[0]}" is required' ile düşer, Steps.${String(step.name ?? label(step))}.error.message dolar ve .data null'dur; errorSteps yoksa akış 'always' dalından devam eder.`);
279
279
  }
280
280
  }
281
281
  for (const { step, origin, ref } of callSites) {
@@ -296,6 +296,60 @@ async function functionWarnings(platform, host, project, environment, applicatio
296
296
  }
297
297
  return [...warnings];
298
298
  }
299
+ /**
300
+ * FLOW-028 (g-02, ADR 0004 — ZM-07). `microservice.ts` `.finally` üç yoldan birini dener: adımın
301
+ * kendi `error` dalı bağlıysa oradan sürer, yoksa flow `errorSteps`'i (`engine.onError`) devralır,
302
+ * ikisi de yoksa `always` koşar ve storage artık `{ data: null, headers: null, error }`'dır — hata
303
+ * gövdesi ARTIK `data`'da DEĞİL. İkisi de bağlı değilken bu adımın `.data`'sını okuyan bir
304
+ * setter/condition varsa hata anında NULL üzerinden koşar ve yazar hiç fark etmez. Aynı yürüyüş
305
+ * FLOW-024/025'in kullandığı `flowExpressionSites` taramasıdır; setter'ın STORAGE (kod olmayan)
306
+ * referansı ayrıca kendi düz alanından okunur (FLOW-008/009 deseni, `rowsTargetsByStepName`).
307
+ */
308
+ function microserviceAlwaysDataWarnings(steps, errorSteps) {
309
+ const mainSteps = (Array.isArray(steps) ? steps : []).filter(isRecord);
310
+ const hasErrorFlow = (Array.isArray(errorSteps) ? errorSteps : []).filter(isRecord).some(step => String(step.type ?? '') !== 'start');
311
+ const label = (step) => String(step.stepId ?? step.name ?? '?');
312
+ const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
313
+ const warnings = new Set();
314
+ for (const step of mainSteps) {
315
+ if (String(step.type ?? '') !== 'microservice')
316
+ continue;
317
+ const name = String(step.name ?? step.stepId ?? '');
318
+ if (name === '')
319
+ continue;
320
+ const hasErrorBranch = (Array.isArray(step.branches) ? step.branches : [])
321
+ .filter(isRecord)
322
+ .some(branch => String(branch.name ?? '') === 'error' && Boolean(branch.nextStepId));
323
+ if (hasErrorBranch || hasErrorFlow)
324
+ continue;
325
+ const pattern = new RegExp(`Steps\\s*(?:\\.\\s*${escapeRegExp(name)}\\b|\\[\\s*['"]${escapeRegExp(name)}['"]\\s*\\])\\s*\\??\\.\\s*data\\b`);
326
+ const message = (other) => `FLOW-028: '${label(other)}' adımı 'always' dalında '${name}.data' okuyor ama '${name}' adımının ne bağlı bir 'error' dalı ne akışın 'errorSteps'i var — uç hata dönerse bu adımın 'data'sı NULL olur (eskiden hata gövdesiydi, ADR 0004) ve '${label(other)}' boş/null veriyle koşar. '${name}' adımına 'error' dalı bağlayın ya da flow'a errorSteps verin.`;
327
+ for (const other of mainSteps) {
328
+ if (other === step)
329
+ continue;
330
+ if (String(other.type ?? '') === 'setter') {
331
+ const iteratees = Array.isArray(other.args?.iteratees)
332
+ ? other.args.iteratees.filter(isRecord)
333
+ : [];
334
+ for (const iteratee of iteratees) {
335
+ const mapping = (iteratee.mapping ?? {});
336
+ const value = (iteratee.value ?? {});
337
+ const reference = typeof mapping.source === 'string' && mapping.source.length > 0
338
+ ? mapping.source
339
+ : typeof value.ref === 'string' ? value.ref : '';
340
+ if (value.type === 'storage' && (reference === `${name}.data` || reference.startsWith(`${name}.data.`) || reference.startsWith(`${name}.data[`))) {
341
+ warnings.add(message(other));
342
+ }
343
+ }
344
+ }
345
+ for (const site of flowExpressionSites(other)) {
346
+ if (pattern.test(site.expression))
347
+ warnings.add(message(other));
348
+ }
349
+ }
350
+ }
351
+ return [...warnings];
352
+ }
299
353
  export function registerFlowTools(server, platform) {
300
354
  const config = platform.config;
301
355
  server.registerTool('flow_list', {
@@ -369,7 +423,8 @@ export function registerFlowTools(server, platform) {
369
423
  ...await requiredParameterWarnings(platform, project, environment, applicationId, steps),
370
424
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, steps),
371
425
  ...flowBodyWarnings(steps),
372
- ...unknownSetterTargetWarnings(steps, errorSteps ?? [], hostNodes(hostRecord))
426
+ ...unknownSetterTargetWarnings(steps, errorSteps ?? [], hostNodes(hostRecord)),
427
+ ...microserviceAlwaysDataWarnings(steps, errorSteps ?? [])
373
428
  ];
374
429
  // Gövde yalnız akıştır (designer-partial-update K1): barındırıcı zarfı platformda hâlâ isteğe bağlı
375
430
  // ama gönderilirse okunan tam kaydı yeniden yazar, açık Studio designer'ının kayıtlı tabanını
@@ -438,7 +493,8 @@ export function registerFlowTools(server, platform) {
438
493
  ...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
439
494
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, flow.steps),
440
495
  ...flowBodyWarnings(flow.steps),
441
- ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord))
496
+ ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord)),
497
+ ...microserviceAlwaysDataWarnings(flow.steps, flow.errorSteps)
442
498
  ];
443
499
  await platform.tenant('PUT', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { flow } });
444
500
  // Kısmi güncellemede yalnız GÖNDERİLEN alanlar karşılaştırılır; verilmeyen alan için
@@ -490,7 +546,8 @@ export function registerFlowTools(server, platform) {
490
546
  ...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
491
547
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, flow.steps),
492
548
  ...flowBodyWarnings(flow.steps),
493
- ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord))
549
+ ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord)),
550
+ ...microserviceAlwaysDataWarnings(flow.steps, flow.errorSteps)
494
551
  ];
495
552
  // Gövde değil FARK gönderilir; platform PATCH ucu akışı tek başına yazar. Net diff okunan kayıt ile son hâl arasından hesaplanır.
496
553
  const netOps = fastJsonPatch.compare(current, flow);
@@ -521,4 +578,13 @@ export function registerFlowTools(server, platform) {
521
578
  await platform.tenant('DELETE', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { id: flowId } });
522
579
  return { deleted: flowId, host, hostId };
523
580
  }));
581
+ server.registerTool('flow_restore', {
582
+ title: 'Silinen akışı geri yükle',
583
+ description: "Silinmiş bir akışı revizyon defterindeki son içeriğiyle (ad, slug, adımlar, barındırıcı) AYNI id ile yeniden kurar. Sayfa kaydı bağı kalmayan akışı kalıcı siler (FLOW-002, allowFlowDeletion) — Studio'nun eski sekmesi boş şema kaydettiğinde de (ZM-27). Akışı olaya YENİDEN BAĞLAMAZ: geri geldikten sonra set_event ile bağlayın. SINIR: adımları sayfanın bir komponentini hedefliyorsa (tetikleyen element, iterate/parametre komponenti) o komponent de şemada olmalıdır, yoksa geri yükleme reddedilir — sayfa boşaldıysa (ZM-27) page_restore kullanın: sayfayı ve akışlarını birlikte geri kurar. Akış hâlâ varsa 409, saklama süresi dolup revizyonu budanmışsa 404 döner. Silinen akışı revision_list(module: 'flow') satırlarında operation: delete olarak bulun; recordId akış id'sidir.",
584
+ inputSchema: { project: projectParam, environment: environmentParam, flowId: flowIdParam },
585
+ annotations: writes
586
+ }, handle(config, async ({ project, environment, flowId }) => {
587
+ const restored = unwrap(await platform.tenant('POST', project, `/flows/${flowId}/restore`, { environment }));
588
+ return { restored: flowId, flow: restored };
589
+ }));
524
590
  }
@@ -99,8 +99,8 @@ export function registerLayoutTools(server, platform) {
99
99
  },
100
100
  annotations: writes
101
101
  }, handle(config, async ({ project, environment, applicationId, name, views, events, parameters }) => {
102
- assertScreenWriteGuards('layout', { schema: views, events });
103
102
  const resolved = await resolveProfile(platform, applicationId);
103
+ assertScreenWriteGuards('layout', { schema: views, events }, resolved.profile.platform);
104
104
  const fixed = normalizeComponentDefaults(views);
105
105
  const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, views);
106
106
  const gaps = [
@@ -138,13 +138,13 @@ export function registerLayoutTools(server, platform) {
138
138
  annotations: updates
139
139
  }, handle(config, async ({ project, environment, applicationId, id, ...changes }) => {
140
140
  const overrides = Object.fromEntries(Object.entries(changes).filter(([, value]) => value !== undefined));
141
- assertScreenWriteGuards('layout', { schema: overrides.views, events: overrides.events });
141
+ const resolved = overrides.views === undefined ? undefined : await resolveProfile(platform, applicationId);
142
+ assertScreenWriteGuards('layout', { schema: overrides.views, events: overrides.events }, resolved?.profile.platform);
142
143
  const current = await fetchHost(platform, 'layout', project, environment, applicationId, id);
143
144
  const fixed = normalizeComponentDefaults(overrides.views);
144
145
  const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, overrides.views);
145
146
  const body = layoutUpdateBody({ ...current, applicationId }, overrides);
146
147
  await platform.tenant('PUT', project, `/layouts/${applicationId}/${id}`, { environment, body });
147
- const resolved = overrides.views === undefined ? undefined : await resolveProfile(platform, applicationId);
148
148
  const gaps = overrides.views === undefined || !resolved
149
149
  ? []
150
150
  : [
@@ -206,7 +206,8 @@ export function registerLayoutTools(server, platform) {
206
206
  const overrides = Object.fromEntries(['name', 'views', 'events', 'parameters']
207
207
  .filter(key => patched[key] !== undefined)
208
208
  .map(key => [key, patched[key]]));
209
- assertScreenWriteGuards('layout', { schema: overrides.views, events: overrides.events });
209
+ const profile = await resolveProfile(platform, applicationId);
210
+ assertScreenWriteGuards('layout', { schema: overrides.views, events: overrides.events }, profile.profile.platform);
210
211
  const fixed = normalizeComponentDefaults(overrides.views);
211
212
  const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, overrides.views);
212
213
  const body = layoutUpdateBody({ ...current, applicationId }, overrides);
@@ -219,7 +220,6 @@ export function registerLayoutTools(server, platform) {
219
220
  body: { baseVersion: baseVersionOf(document, 'layout_get'), ops: netOps }
220
221
  });
221
222
  const written = patchResult(saved, finalDocument);
222
- const profile = await resolveProfile(platform, applicationId);
223
223
  const gaps = [
224
224
  ...(profile.profile.layout.blocksMeaningful ? verticalGapWarnings(body.views, 'LAY-003 (uyarı)') : []),
225
225
  ...flexSizeWarnings(body.views, 'LAY-007 (uyarı)', 'LAY-008 (uyarı)'),
@@ -6,13 +6,14 @@ import { normalizeMicroserviceDefinition, normalizeMicroservicePatchOps, withNor
6
6
  import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
7
7
  import { microserviceOutline, workflowOutline, resolvePart, resolvePatchOperations } from '../knowledge/outlines.js';
8
8
  import { patchResult, baseVersionOf } from '../platform/patch.js';
9
- import { policyRules } from '../knowledge/rules.js';
9
+ import { filterOperatorError, policyRules } from '../knowledge/rules.js';
10
10
  import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
11
- import { identityHeaders } from '../platform/tokenVault.js';
11
+ import { identityHeaders, resolveIdentity } from '../platform/tokenVault.js';
12
12
  import { PlatformApiError } from '../platform/errors.js';
13
13
  import { existingMatch, ifExistsParam, listAll, platformRename, withOutcome, withRename } from '../platform/ifExists.js';
14
14
  import { outlineWrite, recordWriteViewParam } from '../platform/writeView.js';
15
15
  import { handle } from '../platform/responses.js';
16
+ import { headersForMode } from '../platform/http.js';
16
17
  import fastJsonPatch from 'fast-json-patch';
17
18
  import { z } from 'zod';
18
19
  import { constraintSummary } from '../knowledge/backendConstraints.js';
@@ -68,7 +69,7 @@ async function queryScopeFindings(platform, project, environment, definition) {
68
69
  }
69
70
  const patchOperationsParam = z.array(z.record(z.unknown())).min(1)
70
71
  .describe("RFC 6902 JSON Patch işlemleri; path'leri view:'outline'/'part' ile keşfedin. Dizi öğelerinde indeks yerine seçici kullanılabilir: '/sourceParameters/joins[alias=customers]/path' — seçici gövde platforma gitmeden indekse çevrilir, çözülemezse istek hiç gönderilmez.");
71
- const asUserParam = z.string().min(1).optional().describe("Uygulama üyesinin kimliği: istek gateway'in servis jetonu yerine bu kimlikle gider. Jeton HEM Authorization başlığında HEM NG_AccessToken çerezinde taşınır, böylece jetonu ÇEREZDEN okuyan middleware de geçer — uca middleware bağlandığı anda bu alan olmadan çağrı 401 döner (09-05 koşusu S-21). İKİ ŞEKİL kabul edilir: (1) ham JWT, (2) `tok_…` referansı (giriş ucunu çağırdığınızda yanıtın tokenRefs alanında döner, 15 dk geçerli). anonymous ile birlikte verilemez.");
72
+ const asUserParam = z.string().min(1).optional().describe("Uygulama üyesinin kimliği: istek gateway'in servis jetonu yerine bu kimlikle gider. Jeton HEM Authorization başlığında HEM NG_AccessToken çerezinde taşınır, böylece jetonu ÇEREZDEN okuyan middleware de geçer — uca middleware bağlandığı anda bu alan olmadan çağrı 401 döner (09-05 koşusu S-21). ÜÇ ŞEKİL kabul edilir: (1) TERCİH EDİLEN `test:<ad>` — gateway kök .env'deki MCP_TEST_USER_<AD> satırıyla giriş ucunu kendisi çağırır, jetonu kasadan yeniden kullanır (satırı insan yazar), (2) `tok_…` referansı (giriş ucunu çağırdığınızda yanıtın tokenRefs alanında döner; ömür MCP_TOKEN_TTL_MINUTES, MCP_STATE_DIR varsa gateway yeniden başlayınca da çözülür), (3) ham JWT. anonymous ile birlikte verilemez.");
72
73
  const cookiesParam = z.record(z.string()).optional().describe("İsteğe eklenecek çerezler (ad → değer): uygulamanın KENDİ oturum çerezi buraya verilir (ör. { CORE_Token: '<jwt>' }). asUser verildiğinde gateway NG_AccessToken çerezini zaten kendisi kurar; bu alan yalnız uygulama başka bir çerez adı okuyorsa gerekir. anonymous ile birlikte verilemez.");
73
74
  const executeInput = {
74
75
  project: projectParam,
@@ -189,6 +190,13 @@ function unboundQueryParameters(value, into = []) {
189
190
  unboundQueryParameters(entry, into);
190
191
  return into;
191
192
  }
193
+ /** Çağrının dinamik filtresi (`filters`, sorguda ya da gövdede) bilinen işleçlerle mi — değilse istek gitmez (g-03). */
194
+ function assertFilterOperators(tool, bound) {
195
+ const bodyFilters = bound.body && !Array.isArray(bound.body) ? bound.body.filters : undefined;
196
+ const error = filterOperatorError(tool, bound.query?.filters) ?? filterOperatorError(tool, bodyFilters);
197
+ if (error)
198
+ throw new PlatformApiError(error, 400);
199
+ }
192
200
  function executePath(prefix, application, microservice, routeParams) {
193
201
  const routeSuffix = routeParams && routeParams.length > 0
194
202
  ? `/${routeParams.map(encodeURIComponent).join('/')}`
@@ -406,14 +414,20 @@ export function registerMicroserviceTools(server, platform) {
406
414
  }, handle(config, ({ project, environment, id }) => platform.tenant('POST', project, `/microservices/${id}/clone`, { environment })));
407
415
  server.registerTool('microservice_execute', {
408
416
  title: 'Microservice çalıştır',
409
- description: "Kayıtlı microservice'i gerçek runtime yolundan çalıştırır (POST /{project}/m/{application}/{microservice SLUG'ı}). Dönen httpStatus microservice'in kendi sonucudur (hata da geçerli çıktıdır); beklenmedik sonuçta microservice_debug ile adım adım inceleyin. Anonim erişim testi için anonymous: true. Yanıt 'headers' alanında platformun döndüğü header'ları taşır (küçük harf; ör. headers['total-count'] — sayfalı tablo akışlarının okuduğu sözleşme; set-cookie/authorization elenir).",
410
- inputSchema: executeInput,
417
+ description: "Kayıtlı microservice'i gerçek runtime yolundan çalıştırır (POST /{project}/m/{application}/{microservice SLUG'ı}). Dönen httpStatus microservice'in kendi sonucudur (hata da geçerli çıktıdır); beklenmedik sonuçta microservice_debug ile adım adım inceleyin. Anonim erişim testi için anonymous: true. Yanıt 'headers' alanı VARSAYILAN OLARAK yalnız iş başlıklarını taşır (küçük harf): content-type, content-length, content-disposition, location, total-count (sayfalı tablo akışlarının okuduğu sözleşme), x-request-id, traceparent. Ucun response adımında kendi yazdığı başlıkları ya da sunucu başlıklarını görmek için includeHeaders:'all'; hiç başlık istemiyorsanız 'none'. set-cookie/authorization her kipte elenir.",
418
+ inputSchema: {
419
+ ...executeInput,
420
+ includeHeaders: z.enum(['none', 'essential', 'all']).optional().describe("Yanıttaki 'headers' alanı. essential (varsayılan): content-type, content-length, content-disposition, location, total-count, x-request-id, traceparent. all: platformun döndüğü bütün başlıklar (gizliler hariç) — ucun kendi yazdığı başlığı doğrulamak için. none: başlık yok")
421
+ },
411
422
  annotations: writes
412
- }, handle(config, async ({ project, environment, application, microservice, method, routeParams, query, body, parameters, headers, anonymous, asUser, cookies, language }) => {
423
+ }, handle(config, async ({ project, environment, application, microservice, method, routeParams, query, body, parameters, headers, anonymous, asUser, cookies, language, includeHeaders }) => {
413
424
  const notes = [];
414
- const requestHeaders = { ...runIdentity(anonymous, asUser, cookies, headers), ...languageHeader(language, notes) };
425
+ const member = anonymous === true ? asUser : await resolveIdentity(asUser, platform, project, environment);
426
+ const requestHeaders = { ...runIdentity(anonymous, member, cookies, headers), ...languageHeader(language, notes) };
415
427
  const bound = bindCallParameters(method, parameters, query, body, notes);
416
- return withNormalization(await platform.tenantRaw(method, project, executePath('m', application, microservice, routeParams), { environment, query: bound.query, body: bound.body, headers: requestHeaders, anonymous, mirrorAuthorizationCookie: true }), notes);
428
+ assertFilterOperators('microservice_execute', bound);
429
+ const raw = await platform.tenantRaw(method, project, executePath('m', application, microservice, routeParams), { environment, query: bound.query, body: bound.body, headers: requestHeaders, anonymous, mirrorAuthorizationCookie: true });
430
+ return withNormalization({ ...raw, headers: headersForMode(raw.headers, includeHeaders ?? 'essential') }, notes);
417
431
  }));
418
432
  server.registerTool('microservice_debug', {
419
433
  title: 'Microservice debug',
@@ -422,8 +436,10 @@ export function registerMicroserviceTools(server, platform) {
422
436
  annotations: writes
423
437
  }, handle(config, async ({ project, environment, application, microservice, method, routeParams, query, body, parameters, headers, anonymous, asUser, cookies, language }) => {
424
438
  const notes = [];
425
- const requestHeaders = { ...runIdentity(anonymous, asUser, cookies, headers), ...languageHeader(language, notes) };
439
+ const member = anonymous === true ? asUser : await resolveIdentity(asUser, platform, project, environment);
440
+ const requestHeaders = { ...runIdentity(anonymous, member, cookies, headers), ...languageHeader(language, notes) };
426
441
  const bound = bindCallParameters(method, parameters, query, body, notes);
442
+ assertFilterOperators('microservice_debug', bound);
427
443
  const payload = await platform.tenantRaw(method, project, executePath('debug', application, microservice, routeParams), { environment, query: bound.query, body: bound.body, headers: requestHeaders, anonymous, mirrorAuthorizationCookie: true });
428
444
  const unbound = unboundQueryParameters(payload.body);
429
445
  return withNormalization({
@@ -474,7 +490,7 @@ export function registerMicroserviceTools(server, platform) {
474
490
  const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'preview' }, policyOverride));
475
491
  // Önizleme de ucun middleware'lerini koşturur: kimlik verilmezse korumalı uç 401 döner ve kuru
476
492
  // koşu aracı tam gerektiği anda kullanılamaz hâle gelir (09-05 koşusu S-21).
477
- const payload = await platform.tenantRaw('POST', project, `/microservices/${resolvedApplicationId}/preview`, { environment, body: definition, headers: { ...identityHeaders(asUser, cookies, undefined), ...languageHeader(language, normalized) }, mirrorAuthorizationCookie: true });
493
+ const payload = await platform.tenantRaw('POST', project, `/microservices/${resolvedApplicationId}/preview`, { environment, body: definition, headers: { ...identityHeaders(await resolveIdentity(asUser, platform, project, environment), cookies, undefined), ...languageHeader(language, normalized) }, mirrorAuthorizationCookie: true });
478
494
  return attachFindings(withResolved(withNormalization(payload, normalized), [...resolver.resolved, ...hydrated]), findings);
479
495
  }));
480
496
  server.registerTool('microservice_job_history', {