@appaflytech/zeyrek-ai-mcp 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +46 -0
- package/README.md +260 -0
- package/dist/config.js +128 -0
- package/dist/index.js +88 -0
- package/dist/knowledge/componentCatalog.js +438 -0
- package/dist/knowledge/componentEvents.js +164 -0
- package/dist/knowledge/coverage.js +60 -0
- package/dist/knowledge/normalize.js +1309 -0
- package/dist/knowledge/outlines.js +167 -0
- package/dist/knowledge/policy.js +44 -0
- package/dist/knowledge/reservedTables.js +75 -0
- package/dist/knowledge/rules.js +1725 -0
- package/dist/observability/trace.js +337 -0
- package/dist/platform/errors.js +132 -0
- package/dist/platform/http.js +169 -0
- package/dist/platform/responses.js +94 -0
- package/dist/platform/token.js +79 -0
- package/dist/resources/examples/entity-pair-m2m.json +105 -0
- package/dist/resources/examples/flow-list-to-form.json +65 -0
- package/dist/resources/examples/flow-load-table.json +178 -0
- package/dist/resources/examples/microservice-table-list.json +49 -0
- package/dist/resources/examples/middleware-authenticate.json +194 -0
- package/dist/resources/examples/page-detail-profile.json +95 -0
- package/dist/resources/examples/page-dynamic-form.json +221 -0
- package/dist/resources/examples/page-form-upsert.json +279 -0
- package/dist/resources/examples/page-html-embed.json +106 -0
- package/dist/resources/examples/page-sign-in.json +194 -0
- package/dist/resources/examples/workflow-basic.json +94 -0
- package/dist/resources/guides/component.md +108 -0
- package/dist/resources/guides/entity.md +348 -0
- package/dist/resources/guides/expressions.md +139 -0
- package/dist/resources/guides/getting-started.md +109 -0
- package/dist/resources/guides/html.md +106 -0
- package/dist/resources/guides/microservice.md +313 -0
- package/dist/resources/guides/middleware.md +271 -0
- package/dist/resources/guides/modify-existing.md +35 -0
- package/dist/resources/guides/page.md +1574 -0
- package/dist/resources/guides/view.md +89 -0
- package/dist/resources/guides/workflow.md +359 -0
- package/dist/resources/knowledge.js +44 -0
- package/dist/resources/schemas/CreateDatasourceCommand.json +54 -0
- package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +133 -0
- package/dist/resources/schemas/CreateEntityCommand.json +387 -0
- package/dist/resources/schemas/CreateMicroserviceCommand.json +528 -0
- package/dist/resources/schemas/CreateQueryCommand.json +43 -0
- package/dist/resources/schemas/CreateViewCommand.json +158 -0
- package/dist/resources/schemas/CreateWorkflowCommand.json +1719 -0
- package/dist/resources/schemas/Filter.json +124 -0
- package/dist/resources/schemas/GetRowsQuery.json +122 -0
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +565 -0
- package/dist/resources/schemas/PreviewQueryCommand.json +74 -0
- package/dist/resources/schemas/PreviewWorkflowCommand.json +981 -0
- package/dist/resources/schemas/StepDto.json +900 -0
- package/dist/resources/schemas/UpdateDatasourceCommand.json +58 -0
- package/dist/resources/schemas/UpdateEntityCommand.json +679 -0
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +512 -0
- package/dist/resources/schemas/UpdateQueryCommand.json +86 -0
- package/dist/resources/schemas/UpdateViewCommand.json +153 -0
- package/dist/resources/schemas/UpdateWorkflowCommand.json +1719 -0
- package/dist/resources/schemas.js +40 -0
- package/dist/server.js +63 -0
- package/dist/tools/access.js +27 -0
- package/dist/tools/applications.js +59 -0
- package/dist/tools/automations.js +103 -0
- package/dist/tools/common.js +18 -0
- package/dist/tools/components.js +280 -0
- package/dist/tools/datasources.js +82 -0
- package/dist/tools/dependencies.js +17 -0
- package/dist/tools/deployments.js +17 -0
- package/dist/tools/dtos.js +54 -0
- package/dist/tools/entities.js +166 -0
- package/dist/tools/files.js +138 -0
- package/dist/tools/flows.js +226 -0
- package/dist/tools/folders.js +51 -0
- package/dist/tools/layouts.js +105 -0
- package/dist/tools/microservices.js +198 -0
- package/dist/tools/organizations.js +65 -0
- package/dist/tools/pages.js +597 -0
- package/dist/tools/platformInfo.js +44 -0
- package/dist/tools/projects.js +152 -0
- package/dist/tools/queries.js +48 -0
- package/dist/tools/rows.js +115 -0
- package/dist/tools/screens.js +97 -0
- package/dist/tools/templates.js +65 -0
- package/dist/tools/variables.js +94 -0
- package/dist/tools/views.js +58 -0
- package/dist/tools/workflows.js +196 -0
- package/package.json +49 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { fetchHost, flowPath, hostParam, hostUpdateBody, unwrap } from './screens.js';
|
|
2
|
+
import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive } from './common.js';
|
|
3
|
+
import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
|
|
4
|
+
import { normalizeFlowSteps, withNormalization } from '../knowledge/normalize.js';
|
|
5
|
+
import { flowBodyWarnings } from '../knowledge/coverage.js';
|
|
6
|
+
import { PlatformApiError } from '../platform/errors.js';
|
|
7
|
+
import { handle } from '../platform/responses.js';
|
|
8
|
+
import { policyRules } from '../knowledge/rules.js';
|
|
9
|
+
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
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 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.");
|
|
12
|
+
const policyOverrideParam = z.boolean().optional();
|
|
13
|
+
const flowIdParam = guid.describe("Akış id'si; şemadaki events[].value bu id'dir");
|
|
14
|
+
function flowSummary(flow) {
|
|
15
|
+
return {
|
|
16
|
+
id: flow.id,
|
|
17
|
+
name: flow.name,
|
|
18
|
+
slug: flow.slug,
|
|
19
|
+
steps: Array.isArray(flow.steps) ? flow.steps.length : 0,
|
|
20
|
+
errorSteps: Array.isArray(flow.errorSteps) ? flow.errorSteps.length : 0
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function hostEnvelope(platform, host, project, environment, applicationId, hostId) {
|
|
24
|
+
const record = await fetchHost(platform, host, project, environment, applicationId, hostId);
|
|
25
|
+
return { [host]: hostUpdateBody(host, { ...record, applicationId }) };
|
|
26
|
+
}
|
|
27
|
+
async function requiredParameterWarnings(platform, project, environment, applicationId, steps) {
|
|
28
|
+
const warnings = [];
|
|
29
|
+
const microserviceSteps = steps
|
|
30
|
+
.map(step => ({ step, args: (step.args ?? {}) }))
|
|
31
|
+
.filter(entry => entry.step.type === 'microservice' && typeof entry.args.microserviceId === 'string');
|
|
32
|
+
const requiredFiltersByMicroservice = new Map();
|
|
33
|
+
const paginationByMicroservice = new Map();
|
|
34
|
+
for (const microserviceId of new Set(microserviceSteps.map(entry => entry.args.microserviceId))) {
|
|
35
|
+
try {
|
|
36
|
+
const definition = unwrap(await platform.tenant('GET', project, `/microservices/${applicationId}/${microserviceId}`, { environment }));
|
|
37
|
+
const parameters = (definition.sourceParameters ?? {});
|
|
38
|
+
const flattenFilters = (filters) => filters
|
|
39
|
+
.filter((filter) => Boolean(filter) && typeof filter === 'object')
|
|
40
|
+
.flatMap(filter => [filter, ...flattenFilters(Array.isArray(filter.children) ? filter.children : [])]);
|
|
41
|
+
// Uç okunduğunda filtrede fieldSlug YOKTUR (TableFilter yalnız fieldId taşır) — parametre
|
|
42
|
+
// ADI, platformun ürettiği parameterScheme'de durur: HandleFilters her dış kaynaklı filtre
|
|
43
|
+
// için { id: <alan id>, name: <alan slug'ı ya da 'alias.slug'>, source } satırı yazar
|
|
44
|
+
// (Microservice.SourceEntityParameterScheme:1031). Eskiden fieldId GUID'i parametre adıyla
|
|
45
|
+
// karşılaştırılıyordu ve zorunlu filtreli her uçta uyarı HER ZAMAN çıkıyordu (crm koşumu B-13).
|
|
46
|
+
const parameterNameByFieldId = new Map((Array.isArray(definition.parameterScheme) ? definition.parameterScheme : [])
|
|
47
|
+
.filter((item) => Boolean(item) && typeof item === 'object')
|
|
48
|
+
.map(item => [String(item.id ?? ''), String(item.name ?? '')])
|
|
49
|
+
.filter(([id, name]) => id !== '' && name !== ''));
|
|
50
|
+
requiredFiltersByMicroservice.set(microserviceId, flattenFilters(Array.isArray(parameters.filters) ? parameters.filters : [])
|
|
51
|
+
.filter(filter => filter.isRequired === true && filter.source !== 'expression')
|
|
52
|
+
.map(filter => String(filter.fieldSlug ?? parameterNameByFieldId.get(String(filter.fieldId ?? '')) ?? '')));
|
|
53
|
+
const limit = (parameters.limit ?? null);
|
|
54
|
+
if (limit && typeof limit.name === 'string' && limit.name.length > 0) {
|
|
55
|
+
paginationByMicroservice.set(microserviceId, { limitName: limit.name, offsetName: String(limit.offset ?? '') });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
requiredFiltersByMicroservice.set(microserviceId, []);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const rowsConsumers = rowsTargetsByStepName(steps);
|
|
63
|
+
for (const entry of microserviceSteps) {
|
|
64
|
+
const providedParameterNames = (Array.isArray(entry.args.parameters) ? entry.args.parameters : [])
|
|
65
|
+
.filter((parameter) => Boolean(parameter) && typeof parameter === 'object')
|
|
66
|
+
.map(parameter => String(parameter.name ?? ''));
|
|
67
|
+
const missing = (requiredFiltersByMicroservice.get(entry.args.microserviceId) ?? [])
|
|
68
|
+
.filter(slug => slug && !providedParameterNames.includes(slug));
|
|
69
|
+
if (missing.length > 0) {
|
|
70
|
+
warnings.push(`FLOW-003: '${entry.step.stepId ?? entry.step.name}' adımının çağırdığı microservice'in zorunlu (isRequired) filtre parametreleri eksik: ${missing.join(', ')} — runtime'da MissingParameterException verir. args.parameters'a ekleyin; kimlik parametresiyse kalıcı çözüm middleware + user.id filtresidir (platform_guide('middleware')).`);
|
|
71
|
+
}
|
|
72
|
+
const pagination = paginationByMicroservice.get(entry.args.microserviceId);
|
|
73
|
+
const rowsTarget = rowsConsumers.get(String(entry.step.name ?? ''));
|
|
74
|
+
if (!pagination || !rowsTarget)
|
|
75
|
+
continue;
|
|
76
|
+
const missingPagination = [pagination.limitName, pagination.offsetName]
|
|
77
|
+
.filter(name => name.length > 0 && !providedParameterNames.includes(name));
|
|
78
|
+
if (missingPagination.length > 0) {
|
|
79
|
+
warnings.push(`FLOW-013: '${entry.step.stepId ?? entry.step.name}' adımının sonucu '${rowsTarget}' komponentinin rows prop'una yazılıyor ama uç query-string'ten ${missingPagination.join('/')} alırken adım bunları GEÇMİYOR — TableMicroserviceExecutor.cs:178 sayfalamayı yalnız limit doluysa uygular, yani sunucu her çağrıda TÜM kayıtları döndürür ve tablonun pagination olayı yeni sayfa çekemez. Her biri için args.parameters'a { name: '<ad>', type: 'text', source: 'query-string', value: { type: 'property', ref: { source: 'component', element: '${rowsTarget}', property: '<ad>' } } } ekleyin; tabloda ayrıca isPaginated: true ve pagination olayı bu akışa bağlı olmalı.`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return warnings;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Hangi microservice adımının sonucu bir komponentin `rows` prop'una yazılıyor: setter iteratee'sinin
|
|
86
|
+
* storage anahtarı (mapping.source doluysa o, değilse value.ref — setter.ts:161) adımın ADIdır.
|
|
87
|
+
* Liste akışını böyle tanıyoruz; dönen sözlük adım adı → hedef elementId.
|
|
88
|
+
*/
|
|
89
|
+
function rowsTargetsByStepName(steps) {
|
|
90
|
+
const targets = new Map();
|
|
91
|
+
for (const step of steps.filter(step => step.type === 'setter')) {
|
|
92
|
+
const iteratees = Array.isArray((step.args ?? {}).iteratees)
|
|
93
|
+
? (step.args ?? {}).iteratees
|
|
94
|
+
: [];
|
|
95
|
+
for (const iteratee of iteratees.filter((iteratee) => Boolean(iteratee) && typeof iteratee === 'object')) {
|
|
96
|
+
if (iteratee.property !== 'rows')
|
|
97
|
+
continue;
|
|
98
|
+
const mapping = (iteratee.mapping ?? {});
|
|
99
|
+
const value = (iteratee.value ?? {});
|
|
100
|
+
const reference = typeof mapping.source === 'string' && mapping.source.length > 0
|
|
101
|
+
? mapping.source
|
|
102
|
+
: typeof value.ref === 'string' ? value.ref : '';
|
|
103
|
+
const stepName = reference.split('.')[0] ?? '';
|
|
104
|
+
if (stepName.length > 0 && typeof iteratee.element === 'string')
|
|
105
|
+
targets.set(stepName, iteratee.element);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return targets;
|
|
109
|
+
}
|
|
110
|
+
export function registerFlowTools(server, platform) {
|
|
111
|
+
const config = platform.config;
|
|
112
|
+
server.registerTool('flow_list', {
|
|
113
|
+
title: 'Akışları listele',
|
|
114
|
+
description: "Barındırıcının olay akışlarını listeler. Sayfada ayrı bir liste ucu vardır; layout ve komponentte akışlar kaydın kendi gövdesinden okunur.",
|
|
115
|
+
inputSchema: { project: projectParam, environment: environmentParam, host: hostParam, applicationId: guid, hostId: guid },
|
|
116
|
+
annotations: readOnly
|
|
117
|
+
}, handle(config, async ({ project, environment, host, applicationId, hostId }) => {
|
|
118
|
+
if (host === 'page') {
|
|
119
|
+
const payload = await platform.tenant('GET', project, flowPath('page', applicationId, hostId), { environment });
|
|
120
|
+
return { flows: (payload?.data ?? []).map(flowSummary) };
|
|
121
|
+
}
|
|
122
|
+
const record = await fetchHost(platform, host, project, environment, applicationId, hostId);
|
|
123
|
+
return { flows: (Array.isArray(record.flows) ? record.flows : []).map(flowSummary) };
|
|
124
|
+
}));
|
|
125
|
+
server.registerTool('flow_get', {
|
|
126
|
+
title: 'Akış getir',
|
|
127
|
+
description: 'Tek akışı adımlarıyla getirir; yeni akış yazarken en iyi şablon budur.',
|
|
128
|
+
inputSchema: { project: projectParam, environment: environmentParam, host: hostParam, applicationId: guid, hostId: guid, flowId: flowIdParam },
|
|
129
|
+
annotations: readOnly
|
|
130
|
+
}, handle(config, ({ project, environment, host, applicationId, hostId, flowId }) => platform.tenant('GET', project, flowPath(host, applicationId, hostId, flowId), { environment })));
|
|
131
|
+
server.registerTool('flow_create', {
|
|
132
|
+
title: 'Akış oluştur',
|
|
133
|
+
description: "Sayfa/layout/komponent için olay akışı oluşturur. Platform bu uçta barındırıcının TAM gövdesini de ister (akış ile şemadaki olay bağı tek işlemde tutarlı kalsın diye); gateway barındırıcıyı okuyup gövdeye kendisi koyar. Akışı bir komponent olayına bağlamak ayrı adımdır: page_schema_patch ile set_event {target, event, eventValue: <flow id>}.",
|
|
134
|
+
inputSchema: {
|
|
135
|
+
project: projectParam,
|
|
136
|
+
environment: environmentParam,
|
|
137
|
+
host: hostParam,
|
|
138
|
+
applicationId: guid,
|
|
139
|
+
hostId: guid,
|
|
140
|
+
name: z.string().min(1),
|
|
141
|
+
slug: z.string().min(1),
|
|
142
|
+
steps: stepsParam,
|
|
143
|
+
errorSteps: stepsParam.optional(),
|
|
144
|
+
flowId: guid.optional().describe('Verilmezse platform üretir; şemaya önceden bağlamak için kendiniz üretebilirsiniz'),
|
|
145
|
+
policyOverride: policyOverrideParam
|
|
146
|
+
},
|
|
147
|
+
annotations: writes
|
|
148
|
+
}, handle(config, async ({ project, environment, host, applicationId, hostId, name, slug, steps, errorSteps, flowId, policyOverride }) => {
|
|
149
|
+
const envelope = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
|
|
150
|
+
const resolvedFlowId = flowId ?? randomUUID();
|
|
151
|
+
const flow = {
|
|
152
|
+
id: resolvedFlowId,
|
|
153
|
+
name,
|
|
154
|
+
slug,
|
|
155
|
+
steps,
|
|
156
|
+
errorSteps: errorSteps ?? [],
|
|
157
|
+
...(host === 'page' ? { pageId: hostId } : host === 'layout' ? { layoutId: hostId } : { componentId: hostId })
|
|
158
|
+
};
|
|
159
|
+
const normalized = [...normalizeFlowSteps(flow.steps), ...normalizeFlowSteps(flow.errorSteps)];
|
|
160
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'flow', flow, { operation: 'create' }, policyOverride));
|
|
161
|
+
const parameterWarnings = [
|
|
162
|
+
...await requiredParameterWarnings(platform, project, environment, applicationId, steps),
|
|
163
|
+
...flowBodyWarnings(steps)
|
|
164
|
+
];
|
|
165
|
+
await platform.tenant('POST', project, flowPath(host, applicationId, hostId), { environment, body: { flow, ...envelope } });
|
|
166
|
+
return attachFindings(withNormalization({
|
|
167
|
+
host,
|
|
168
|
+
hostId,
|
|
169
|
+
flow: { id: resolvedFlowId, name, slug, steps: steps.length },
|
|
170
|
+
...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
|
|
171
|
+
hint: "Akışı bir olaya bağlamak için: page_schema_patch ops:[{op:'set_event', target:<elementId>, event:'<component_get events listesindeki ad>', eventValue:<flow id>}]. 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."
|
|
172
|
+
}, normalized), findings);
|
|
173
|
+
}));
|
|
174
|
+
server.registerTool('flow_update', {
|
|
175
|
+
title: 'Akış güncelle',
|
|
176
|
+
description: 'Akışın adını/adımlarını değiştirir. Gövde replace\'tir: verilen steps listesi akışın yeni tam hâlidir. Barındırıcı gövdesini gateway ekler.',
|
|
177
|
+
inputSchema: {
|
|
178
|
+
project: projectParam,
|
|
179
|
+
environment: environmentParam,
|
|
180
|
+
host: hostParam,
|
|
181
|
+
applicationId: guid,
|
|
182
|
+
hostId: guid,
|
|
183
|
+
flowId: flowIdParam,
|
|
184
|
+
name: z.string().min(1).optional(),
|
|
185
|
+
slug: z.string().min(1).optional(),
|
|
186
|
+
steps: stepsParam.optional(),
|
|
187
|
+
errorSteps: stepsParam.optional(),
|
|
188
|
+
policyOverride: policyOverrideParam
|
|
189
|
+
},
|
|
190
|
+
annotations: updates
|
|
191
|
+
}, handle(config, async ({ project, environment, host, applicationId, hostId, flowId, name, slug, steps, errorSteps, policyOverride }) => {
|
|
192
|
+
const current = unwrap(await platform.tenant('GET', project, flowPath(host, applicationId, hostId, flowId), { environment }));
|
|
193
|
+
if (!current.id)
|
|
194
|
+
throw new PlatformApiError(`'${flowId}' id'li akış bulunamadı; flow_list ile doğrulayın.`);
|
|
195
|
+
const envelope = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
|
|
196
|
+
const flow = {
|
|
197
|
+
id: flowId,
|
|
198
|
+
name: name ?? current.name,
|
|
199
|
+
slug: slug ?? current.slug,
|
|
200
|
+
steps: steps ?? current.steps ?? [],
|
|
201
|
+
errorSteps: errorSteps ?? current.errorSteps ?? [],
|
|
202
|
+
...(host === 'page' ? { pageId: hostId } : host === 'layout' ? { layoutId: hostId } : { componentId: hostId })
|
|
203
|
+
};
|
|
204
|
+
const authored = { steps: steps ?? [], errorSteps: errorSteps ?? [] };
|
|
205
|
+
const normalized = [...normalizeFlowSteps(authored.steps), ...normalizeFlowSteps(authored.errorSteps)];
|
|
206
|
+
const findings = steps || errorSteps
|
|
207
|
+
? enforcePolicy(evaluatePolicy(config, policyRules, 'flow', authored, { operation: 'update' }, policyOverride))
|
|
208
|
+
: [];
|
|
209
|
+
const parameterWarnings = [
|
|
210
|
+
...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
|
|
211
|
+
...flowBodyWarnings(flow.steps)
|
|
212
|
+
];
|
|
213
|
+
await platform.tenant('PUT', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { flow, ...envelope } });
|
|
214
|
+
return attachFindings(withNormalization({ host, hostId, flow: flowSummary(flow), ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}) }, normalized), findings);
|
|
215
|
+
}));
|
|
216
|
+
server.registerTool('flow_delete', {
|
|
217
|
+
title: 'Akış sil',
|
|
218
|
+
description: "Akışı siler. Bu uç GÖVDE ister (barındırıcı komutu) — gövdesiz silme şemadaki olay bağını sarkıtır; gateway gövdeyi kendisi kurar. Olay bağını da temizlemek için ayrıca page_schema_patch set_event ile eventValue'yu boşaltın.",
|
|
219
|
+
inputSchema: { project: projectParam, environment: environmentParam, host: hostParam, applicationId: guid, hostId: guid, flowId: flowIdParam },
|
|
220
|
+
annotations: destructive
|
|
221
|
+
}, handle(config, async ({ project, environment, host, applicationId, hostId, flowId }) => {
|
|
222
|
+
const envelope = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
|
|
223
|
+
await platform.tenant('DELETE', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { id: flowId, ...envelope } });
|
|
224
|
+
return { deleted: flowId, host, hostId };
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { guid, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
|
|
2
|
+
import { handle } from '../platform/responses.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export function registerFolderTools(server, platform) {
|
|
5
|
+
const config = platform.config;
|
|
6
|
+
server.registerTool('folder_list', {
|
|
7
|
+
title: 'Klasörleri listele',
|
|
8
|
+
description: 'Proje klasör ağacını listeler (drive/dosya organizasyonu).',
|
|
9
|
+
inputSchema: { project: projectParam, environment: environmentParam, searchTerm: z.string().optional(), parentId: guid.optional(), ...pagingParams },
|
|
10
|
+
annotations: readOnly
|
|
11
|
+
}, handle(config, ({ project, environment, ...query }) => platform.tenant('GET', project, '/folders', { environment, query })));
|
|
12
|
+
server.registerTool('folder_get', {
|
|
13
|
+
title: 'Klasör getir',
|
|
14
|
+
description: 'Tek klasörü getirir; breadcrumb: true ile üst yol zinciri döner.',
|
|
15
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid, breadcrumb: z.boolean().optional() },
|
|
16
|
+
annotations: readOnly
|
|
17
|
+
}, handle(config, ({ project, environment, id, breadcrumb }) => platform.tenant('GET', project, breadcrumb ? `/folders/${id}/breadcrumb` : `/folders/${id}`, { environment })));
|
|
18
|
+
server.registerTool('folder_create', {
|
|
19
|
+
title: 'Klasör oluştur',
|
|
20
|
+
description: "Yeni klasör oluşturur (definition = CreateFolderCommand: { name, parentId? }). DİKKAT: fiziksel klasör adı ve depolama yolu ASCII'ye katlanır ('Donanım' → 'Donanim') ve aynı üst klasörde çakışırsa sonuna ' (1)' eklenir — kullanıcıya gösterilen displayName ham adı korur. Yanıttaki name/path katlanmış hâldir, dosya yollarını oradan okuyun.",
|
|
21
|
+
inputSchema: { project: projectParam, environment: environmentParam, definition: definitionParam },
|
|
22
|
+
annotations: writes
|
|
23
|
+
}, handle(config, ({ project, environment, definition }) => platform.tenant('POST', project, '/folders', { environment, body: definition })));
|
|
24
|
+
server.registerTool('folder_update', {
|
|
25
|
+
title: 'Klasör yetkilerini güncelle',
|
|
26
|
+
description: "Klasörün YETKİLENDİRMESİNİ günceller — `UpdateFolderCommand`'ın alanları yalnız `middlewareIds` ve `roleIds`'tir. Ad değiştirmek için folder_rename kullanın; **klasör TAŞIMA (parentId değiştirme) platformda desteklenmiyor** — gövdeye parentId/name koyarsanız sessizce yok sayılır ve istek yine 200 döner (etkinlik turu Ç3).",
|
|
27
|
+
inputSchema: {
|
|
28
|
+
project: projectParam,
|
|
29
|
+
environment: environmentParam,
|
|
30
|
+
id: guid,
|
|
31
|
+
middlewareIds: z.array(guid).optional().describe('Klasöre erişimden önce koşacak middleware workflow id\'leri'),
|
|
32
|
+
roleIds: z.array(guid).optional().describe('Klasörü görebilecek roller; boş = kısıt yok')
|
|
33
|
+
},
|
|
34
|
+
annotations: updates
|
|
35
|
+
}, handle(config, ({ project, environment, id, middlewareIds, roleIds }) => platform.tenant('PUT', project, `/folders/${id}`, {
|
|
36
|
+
environment,
|
|
37
|
+
body: { id, middlewareIds: middlewareIds ?? [], roleIds: roleIds ?? [] }
|
|
38
|
+
})));
|
|
39
|
+
server.registerTool('folder_rename', {
|
|
40
|
+
title: 'Klasörü yeniden adlandır',
|
|
41
|
+
description: "Klasörün görünen adını (displayName) değiştirir; fiziksel depolama yolu (`name`/`path`) sabit kalır — bu kasıtlıdır, yüklenmiş dosyaların yolu kırılmasın diye. Üst klasörü değiştirmez (taşıma desteklenmiyor).",
|
|
42
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid, name: z.string().min(1) },
|
|
43
|
+
annotations: updates
|
|
44
|
+
}, handle(config, ({ project, environment, id, name }) => platform.tenant('PATCH', project, `/folders/${id}/rename`, { environment, body: { name } })));
|
|
45
|
+
server.registerTool('folder_delete', {
|
|
46
|
+
title: 'Klasör sil',
|
|
47
|
+
description: 'Klasörü siler.',
|
|
48
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
49
|
+
annotations: destructive
|
|
50
|
+
}, handle(config, ({ project, environment, id }) => platform.tenant('DELETE', project, `/folders/${id}`, { environment })));
|
|
51
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
|
|
2
|
+
import { normalizeComponentDefaults, componentDefaultsNote, blockSizeNote, heightTypeNote, invalidScreenEventNames, invalidWidgetTypeNodes, verticalGapWarnings, withNormalization } from '../knowledge/normalize.js';
|
|
3
|
+
import { fetchHost, layoutUpdateBody, schemaParam, eventsParam } from './screens.js';
|
|
4
|
+
import { applyCatalogLayoutRules } from './components.js';
|
|
5
|
+
import { PlatformApiError } from '../platform/errors.js';
|
|
6
|
+
import { handle } from '../platform/responses.js';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
const viewsParam = schemaParam.describe("Layout'un komponent ağacı. DİKKAT: layout'ta şema alanının adı 'schema' değil 'views'tir ve sayfa micro-op hattı (page_schema_patch) layout'a uygulanmaz. İçinde MUTLAKA bir { type: 'slot' } komponenti olmalı — sayfa içeriği YALNIZ slot'un içine render edilir, slot'suz layout'a bağlanan sayfalar boş görünür.");
|
|
9
|
+
function slotWarning(views) {
|
|
10
|
+
const hasSlot = (nodes) => Array.isArray(nodes) && nodes.some(node => Boolean(node) && typeof node === 'object' &&
|
|
11
|
+
(node.type === 'slot' || hasSlot(node.children)));
|
|
12
|
+
return hasSlot(views)
|
|
13
|
+
? []
|
|
14
|
+
: ["LAY-002: layout'ta 'slot' tipli komponent YOK — Studio ve runtime, sayfa şemasını yalnız layout'taki slot'un içine yerleştirir (page.mappers: layout varsa render şeması layout views'üdür); bu layout'a bağlanan her sayfanın içeriği görünmez olur. views'e bir { type: 'slot' } komponenti ekleyin ve page_state.layoutSlots ile doğrulayın."];
|
|
15
|
+
}
|
|
16
|
+
export function registerLayoutTools(server, platform) {
|
|
17
|
+
const config = platform.config;
|
|
18
|
+
server.registerTool('layout_list', {
|
|
19
|
+
title: 'Layout listele',
|
|
20
|
+
description: "Uygulamanın layout'larını listeler; sayfalar layoutId ile bunlara bağlanır.",
|
|
21
|
+
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, searchTerm: z.string().optional(), ...pagingParams },
|
|
22
|
+
annotations: readOnly
|
|
23
|
+
}, handle(config, ({ project, environment, applicationId, ...query }) => platform.tenant('GET', project, `/layouts/${applicationId}`, { environment, query })));
|
|
24
|
+
server.registerTool('layout_get', {
|
|
25
|
+
title: 'Layout getir',
|
|
26
|
+
description: "Layout'u komponent ağacı (views), widget'ları, akışları ve olaylarıyla getirir.",
|
|
27
|
+
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid },
|
|
28
|
+
annotations: readOnly
|
|
29
|
+
}, handle(config, ({ project, environment, applicationId, id }) => platform.tenant('GET', project, `/layouts/${applicationId}/${id}`, { environment })));
|
|
30
|
+
server.registerTool('layout_create', {
|
|
31
|
+
title: 'Layout oluştur',
|
|
32
|
+
description: "Yeni layout oluşturur. Şablon için mevcut bir layout'u layout_get ile alın; slot kurgusunu page_state çıktısındaki layoutSlots ile doğrulayın.",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
project: projectParam,
|
|
35
|
+
environment: environmentParam,
|
|
36
|
+
applicationId: guid,
|
|
37
|
+
name: z.string().min(1),
|
|
38
|
+
views: viewsParam.optional(),
|
|
39
|
+
events: eventsParam.optional(),
|
|
40
|
+
parameters: z.record(z.unknown()).optional()
|
|
41
|
+
},
|
|
42
|
+
annotations: writes
|
|
43
|
+
}, handle(config, async ({ project, environment, applicationId, name, views, events, parameters }) => {
|
|
44
|
+
const badEvents = invalidScreenEventNames(events, 'LAY-004');
|
|
45
|
+
if (badEvents.length > 0)
|
|
46
|
+
throw new PlatformApiError(`Geçersiz layout olayı (istek gönderilmedi): ${badEvents.join(' · ')}`, 400);
|
|
47
|
+
const badWidgets = invalidWidgetTypeNodes(views, 'LAY-005');
|
|
48
|
+
if (badWidgets.length > 0)
|
|
49
|
+
throw new PlatformApiError(`Geçersiz widget düğümü (istek gönderilmedi): ${badWidgets.join(' · ')}`, 400);
|
|
50
|
+
const fixed = normalizeComponentDefaults(views);
|
|
51
|
+
const catalog = (views ?? []).length > 0
|
|
52
|
+
? await applyCatalogLayoutRules(platform, project, environment, applicationId, views)
|
|
53
|
+
: { clamped: [], heights: [] };
|
|
54
|
+
const gaps = verticalGapWarnings(views ?? [], 'LAY-003 (uyarı)');
|
|
55
|
+
const response = await platform.tenant('POST', project, `/layouts/${applicationId}`, {
|
|
56
|
+
environment,
|
|
57
|
+
body: { name, views: views ?? [], events: events ?? [], parameters: parameters ?? null }
|
|
58
|
+
});
|
|
59
|
+
const normalized = withNormalization(response, [...componentDefaultsNote(fixed), ...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights), ...slotWarning(views)]);
|
|
60
|
+
if (gaps.length === 0 || !normalized || typeof normalized !== 'object')
|
|
61
|
+
return normalized;
|
|
62
|
+
return { ...normalized, layoutWarnings: gaps };
|
|
63
|
+
}));
|
|
64
|
+
server.registerTool('layout_update', {
|
|
65
|
+
title: 'Layout güncelle',
|
|
66
|
+
description: "Layout'u günceller. PUT replace olduğu için gateway kaydı önce okur, yalnız verdiğiniz alanları değiştirip tam gövdeyi geri yazar — views göndermezseniz mevcut ağaç korunur.",
|
|
67
|
+
inputSchema: {
|
|
68
|
+
project: projectParam,
|
|
69
|
+
environment: environmentParam,
|
|
70
|
+
applicationId: guid,
|
|
71
|
+
id: guid,
|
|
72
|
+
name: z.string().min(1).optional(),
|
|
73
|
+
views: viewsParam.optional(),
|
|
74
|
+
events: eventsParam.optional(),
|
|
75
|
+
parameters: z.record(z.unknown()).optional()
|
|
76
|
+
},
|
|
77
|
+
annotations: updates
|
|
78
|
+
}, handle(config, async ({ project, environment, applicationId, id, ...changes }) => {
|
|
79
|
+
const overrides = Object.fromEntries(Object.entries(changes).filter(([, value]) => value !== undefined));
|
|
80
|
+
const badEvents = invalidScreenEventNames(overrides.events, 'LAY-004');
|
|
81
|
+
if (badEvents.length > 0)
|
|
82
|
+
throw new PlatformApiError(`Geçersiz layout olayı (istek gönderilmedi): ${badEvents.join(' · ')}`, 400);
|
|
83
|
+
const badWidgets = invalidWidgetTypeNodes(overrides.views, 'LAY-005');
|
|
84
|
+
if (badWidgets.length > 0)
|
|
85
|
+
throw new PlatformApiError(`Geçersiz widget düğümü (istek gönderilmedi): ${badWidgets.join(' · ')}`, 400);
|
|
86
|
+
const current = await fetchHost(platform, 'layout', project, environment, applicationId, id);
|
|
87
|
+
const fixed = normalizeComponentDefaults(overrides.views);
|
|
88
|
+
const catalog = overrides.views === undefined
|
|
89
|
+
? { clamped: [], heights: [] }
|
|
90
|
+
: await applyCatalogLayoutRules(platform, project, environment, applicationId, overrides.views);
|
|
91
|
+
const body = layoutUpdateBody({ ...current, applicationId }, overrides);
|
|
92
|
+
await platform.tenant('PUT', project, `/layouts/${applicationId}/${id}`, { environment, body });
|
|
93
|
+
const gaps = overrides.views === undefined ? [] : verticalGapWarnings(body.views, 'LAY-003 (uyarı)');
|
|
94
|
+
const summary = { id, changed: Object.keys(overrides), preserved: { viewNodes: body.views.length } };
|
|
95
|
+
if (gaps.length > 0)
|
|
96
|
+
summary.layoutWarnings = gaps;
|
|
97
|
+
return withNormalization(summary, [...componentDefaultsNote(fixed), ...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights), ...slotWarning(body.views)]);
|
|
98
|
+
}));
|
|
99
|
+
server.registerTool('layout_delete', {
|
|
100
|
+
title: 'Layout sil',
|
|
101
|
+
description: "Layout'u siler; bu layout'a bağlı sayfalar layoutsuz kalır. Önce dependency_graph module:'layout' ile kontrol edin.",
|
|
102
|
+
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid },
|
|
103
|
+
annotations: destructive
|
|
104
|
+
}, handle(config, ({ project, environment, applicationId, id }) => platform.tenant('DELETE', project, `/layouts/${applicationId}/${id}`, { environment })));
|
|
105
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { guid, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
|
|
2
|
+
import { normalizeMicroserviceDefinition, withNormalization } from '../knowledge/normalize.js';
|
|
3
|
+
import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
|
|
4
|
+
import { microserviceOutline, resolvePart, resolvePatchOperations } from '../knowledge/outlines.js';
|
|
5
|
+
import { policyRules } from '../knowledge/rules.js';
|
|
6
|
+
import { handle } from '../platform/responses.js';
|
|
7
|
+
import fastJsonPatch from 'fast-json-patch';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
const policyOverrideParam = z.boolean().optional()
|
|
10
|
+
.describe('true: politika block bulgularını warn\'a indirir (bilinçli istisna; yanıtta işaretlenir)');
|
|
11
|
+
const patchOperationsParam = z.array(z.record(z.unknown())).min(1)
|
|
12
|
+
.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.");
|
|
13
|
+
const executeInput = {
|
|
14
|
+
project: projectParam,
|
|
15
|
+
environment: environmentParam,
|
|
16
|
+
application: z.string().min(1).describe('Application slug\'ı'),
|
|
17
|
+
microservice: z.string().min(1).describe('Microservice slug\'ı'),
|
|
18
|
+
method: z.enum(['GET', 'POST', 'PUT', 'DELETE']).default('GET'),
|
|
19
|
+
routeParams: z.array(z.string()).optional(),
|
|
20
|
+
query: z.record(z.unknown()).optional(),
|
|
21
|
+
body: z.union([z.record(z.unknown()), z.array(z.record(z.unknown()))]).optional()
|
|
22
|
+
.describe('İstek gövdesi: tek nesne ya da nesne dizisi (bulk uçlar için)'),
|
|
23
|
+
headers: z.record(z.string()).optional().describe("İsteğe eklenecek header'lar. 'Authorization' verirseniz gateway KENDİ token'ını KOYMAZ — istek tamamen sizin kimliğinizle gider (middleware'li uçları üye token'ıyla test etmenin yolu budur)"),
|
|
24
|
+
anonymous: z.boolean().optional().describe("true: istek HİÇBİR Authorization header'ı olmadan gider — 'korumalı uç tokensız ne döner?' sorusunun cevabı budur (korumalı uçtan 401, anonim uçtan 200 beklenir). Gateway normalde her çağrıya kendi token'ını koyduğu için bu bayrak olmadan anonim erişim testi yapılamaz.")
|
|
25
|
+
};
|
|
26
|
+
function executePath(prefix, application, microservice, routeParams) {
|
|
27
|
+
const routeSuffix = routeParams && routeParams.length > 0
|
|
28
|
+
? `/${routeParams.map(encodeURIComponent).join('/')}`
|
|
29
|
+
: '';
|
|
30
|
+
return `/${prefix}/${application}/${microservice}${routeSuffix}`;
|
|
31
|
+
}
|
|
32
|
+
/** Slug application_update ile değişebildiği için önbellek kısa ömürlüdür (yalnız aynı turdaki tekrar okumaları ucuzlatır). */
|
|
33
|
+
const APPLICATION_SLUG_TTL_MS = 60_000;
|
|
34
|
+
const applicationSlugCache = new Map();
|
|
35
|
+
async function applicationSlug(platform, applicationId) {
|
|
36
|
+
const cached = applicationSlugCache.get(applicationId);
|
|
37
|
+
if (cached && cached.expiresAt > Date.now())
|
|
38
|
+
return cached.slug;
|
|
39
|
+
try {
|
|
40
|
+
const payload = await platform.org('GET', `/applications/${applicationId}`);
|
|
41
|
+
const application = (payload?.data ?? payload);
|
|
42
|
+
const slug = typeof application.slug === 'string' && application.slug !== '' ? application.slug : undefined;
|
|
43
|
+
if (slug)
|
|
44
|
+
applicationSlugCache.set(applicationId, { slug, expiresAt: Date.now() + APPLICATION_SLUG_TTL_MS });
|
|
45
|
+
return slug;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Çalıştırma rotası `[Route("{project}")] + m/{application}/{microservice}` ve arama
|
|
53
|
+
* `m.Slug == microserviceSlug` ile yapılır (MicroservicesController:91,
|
|
54
|
+
* MicroserviceRepository.FindBySlugAsync) — yani yolu kuran alan `url` DEĞİL **slug**'dır ve
|
|
55
|
+
* proje öneki zorunludur. Ham curl ile anonim test yapmak isteyen bu yolu tahmin etmek zorunda
|
|
56
|
+
* kalıyordu (crm koşumu B-8): artık kayıt okunduğunda hazır geliyor.
|
|
57
|
+
*/
|
|
58
|
+
async function resolveRuntimeUrl(platform, project, environment, applicationId, definition) {
|
|
59
|
+
const slug = String(definition.slug ?? '');
|
|
60
|
+
const method = String(definition.httpMethod ?? 'get').toUpperCase();
|
|
61
|
+
const resolvedProject = platform.resolveProject(project);
|
|
62
|
+
const resolvedEnvironment = platform.resolveEnvironment(environment);
|
|
63
|
+
const application = await applicationSlug(platform, applicationId);
|
|
64
|
+
const path = `/${resolvedProject}/m/${application ?? '<application slug>'}/${slug}`;
|
|
65
|
+
return {
|
|
66
|
+
method,
|
|
67
|
+
path,
|
|
68
|
+
absolute: `${platform.config.tenantApiBaseUrl}${path}`,
|
|
69
|
+
headers: {
|
|
70
|
+
Host: `${platform.config.organization}.${platform.config.hostSuffix}`,
|
|
71
|
+
'X-Environment': resolvedEnvironment
|
|
72
|
+
},
|
|
73
|
+
note: `Yolu SLUG kurar, 'url' alanı ('${String(definition.url ?? '')}') rotaya girmez. Route parametreleri slug'ın ardına eklenir. Anonim erişim testi için microservice_execute'u anonymous: true ile çağırın; korumalı uçlarda 401, anonim uçlarda 200 beklenir.`
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function registerMicroserviceTools(server, platform) {
|
|
77
|
+
const config = platform.config;
|
|
78
|
+
server.registerTool('microservice_list', {
|
|
79
|
+
title: 'Microservice listele',
|
|
80
|
+
description: 'Application altındaki microservice\'leri listeler.',
|
|
81
|
+
inputSchema: {
|
|
82
|
+
project: projectParam,
|
|
83
|
+
environment: environmentParam,
|
|
84
|
+
applicationId: guid,
|
|
85
|
+
searchTerm: z.string().optional(),
|
|
86
|
+
httpMethod: z.string().optional(),
|
|
87
|
+
entityId: guid.optional(),
|
|
88
|
+
queryId: guid.optional(),
|
|
89
|
+
type: z.string().optional(),
|
|
90
|
+
isDefault: z.boolean().optional(),
|
|
91
|
+
...pagingParams
|
|
92
|
+
},
|
|
93
|
+
annotations: readOnly
|
|
94
|
+
}, handle(config, ({ project, environment, applicationId, ...query }) => platform.tenant('GET', project, `/microservices/${applicationId}`, { environment, query })));
|
|
95
|
+
server.registerTool('microservice_get', {
|
|
96
|
+
title: 'Microservice getir',
|
|
97
|
+
description: "Microservice tanımını getirir. Büyük tanımda önce view:'outline' (kaynak/alan özeti), sonra view:'part' ile tek parça çekin.",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
project: projectParam,
|
|
100
|
+
environment: environmentParam,
|
|
101
|
+
applicationId: guid,
|
|
102
|
+
id: guid,
|
|
103
|
+
view: z.enum(['full', 'outline', 'part']).optional().describe('varsayılan full'),
|
|
104
|
+
part: z.string().optional().describe("view:'part' için yol, ör. sourceParameters.joins veya validations[0]")
|
|
105
|
+
},
|
|
106
|
+
annotations: readOnly
|
|
107
|
+
}, handle(config, async ({ project, environment, applicationId, id, view, part }) => {
|
|
108
|
+
const payload = await platform.tenant('GET', project, `/microservices/${applicationId}/${id}`, { environment });
|
|
109
|
+
const definition = (payload?.data ?? payload);
|
|
110
|
+
if (view === 'part') {
|
|
111
|
+
if (!part)
|
|
112
|
+
throw new Error("view:'part' için 'part' parametresi zorunlu.");
|
|
113
|
+
return { part, value: resolvePart(definition, part) };
|
|
114
|
+
}
|
|
115
|
+
const resolvedUrl = await resolveRuntimeUrl(platform, project, environment, applicationId, definition);
|
|
116
|
+
if (view === 'outline')
|
|
117
|
+
return { ...microserviceOutline(definition), resolvedUrl };
|
|
118
|
+
return { ...payload, resolvedUrl };
|
|
119
|
+
}));
|
|
120
|
+
server.registerTool('microservice_create', {
|
|
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.",
|
|
123
|
+
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, definition: definitionParam, policyOverride: policyOverrideParam },
|
|
124
|
+
annotations: writes
|
|
125
|
+
}, handle(config, async ({ project, environment, applicationId, definition, policyOverride }) => {
|
|
126
|
+
const normalized = normalizeMicroserviceDefinition(definition);
|
|
127
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'create' }, policyOverride));
|
|
128
|
+
const payload = await platform.tenant('POST', project, `/microservices/${applicationId}`, { environment, body: definition });
|
|
129
|
+
return attachFindings(withNormalization(payload, normalized), findings);
|
|
130
|
+
}));
|
|
131
|
+
server.registerTool('microservice_update', {
|
|
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.',
|
|
134
|
+
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid, definition: definitionParam, policyOverride: policyOverrideParam },
|
|
135
|
+
annotations: updates
|
|
136
|
+
}, handle(config, async ({ project, environment, applicationId, id, definition, policyOverride }) => {
|
|
137
|
+
const normalized = normalizeMicroserviceDefinition(definition);
|
|
138
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'update' }, policyOverride));
|
|
139
|
+
const payload = await platform.tenant('PUT', project, `/microservices/${applicationId}/${id}`, { environment, body: definition });
|
|
140
|
+
return attachFindings(withNormalization(payload, normalized), findings);
|
|
141
|
+
}));
|
|
142
|
+
server.registerTool('microservice_patch', {
|
|
143
|
+
title: 'Microservice\'e patch uygula',
|
|
144
|
+
description: 'Tam gövdeyi bağlama almadan hedefli değişiklik: gateway mevcut tanımı çeker, JSON Patch uygular, politika kontrolünden geçirir, geri yazar. Yanıt outline özetidir.',
|
|
145
|
+
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid, operations: patchOperationsParam, policyOverride: policyOverrideParam },
|
|
146
|
+
annotations: updates
|
|
147
|
+
}, handle(config, async ({ project, environment, applicationId, id, operations, policyOverride }) => {
|
|
148
|
+
const current = await platform.tenant('GET', project, `/microservices/${applicationId}/${id}`, { environment });
|
|
149
|
+
const definition = structuredClone((current?.data ?? current));
|
|
150
|
+
const resolved = resolvePatchOperations(definition, operations);
|
|
151
|
+
const patched = fastJsonPatch.applyPatch(definition, resolved.operations, true, false).newDocument;
|
|
152
|
+
const normalized = normalizeMicroserviceDefinition(patched);
|
|
153
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', patched, { operation: 'update' }, policyOverride));
|
|
154
|
+
await platform.tenant('PUT', project, `/microservices/${applicationId}/${id}`, { environment, body: patched });
|
|
155
|
+
return attachFindings(withNormalization({ applied: operations.length, outline: microserviceOutline(patched) }, [...normalized, ...resolved.notes.map(note => `patch yolu seçiciden indekse çözüldü: ${note}`)]), findings);
|
|
156
|
+
}));
|
|
157
|
+
server.registerTool('microservice_delete', {
|
|
158
|
+
title: 'Microservice sil',
|
|
159
|
+
description: 'Microservice tanımını siler.',
|
|
160
|
+
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid },
|
|
161
|
+
annotations: destructive
|
|
162
|
+
}, handle(config, ({ project, environment, applicationId, id }) => platform.tenant('DELETE', project, `/microservices/${applicationId}/${id}`, { environment })));
|
|
163
|
+
server.registerTool('microservice_clone', {
|
|
164
|
+
title: 'Microservice klonla',
|
|
165
|
+
description: 'Microservice\'in kopyasını oluşturur.',
|
|
166
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
167
|
+
annotations: writes
|
|
168
|
+
}, handle(config, ({ project, environment, id }) => platform.tenant('POST', project, `/microservices/${id}/clone`, { environment })));
|
|
169
|
+
server.registerTool('microservice_execute', {
|
|
170
|
+
title: 'Microservice çalıştır',
|
|
171
|
+
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.',
|
|
172
|
+
inputSchema: executeInput,
|
|
173
|
+
annotations: writes
|
|
174
|
+
}, handle(config, ({ project, environment, application, microservice, method, routeParams, query, body, headers, anonymous }) => platform.tenantRaw(method, project, executePath('m', application, microservice, routeParams), { environment, query, body, headers, anonymous })));
|
|
175
|
+
server.registerTool('microservice_debug', {
|
|
176
|
+
title: 'Microservice debug',
|
|
177
|
+
description: 'Microservice\'i debug modunda çalıştırır; yanıtta adım adım executionItems ağacı döner (her adımın girdi/çıktısı).',
|
|
178
|
+
inputSchema: executeInput,
|
|
179
|
+
annotations: writes
|
|
180
|
+
}, handle(config, ({ project, environment, application, microservice, method, routeParams, query, body, headers, anonymous }) => platform.tenantRaw(method, project, executePath('debug', application, microservice, routeParams), { environment, query, body, headers, anonymous })));
|
|
181
|
+
server.registerTool('microservice_preview', {
|
|
182
|
+
title: 'Microservice önizle',
|
|
183
|
+
description: 'Kaydedilmemiş microservice tanımını çalıştırır; kaydetmeden önce preview ile doğrulayın. definition = PreviewMicroserviceCommand gövdesi (previewParameters dahil).',
|
|
184
|
+
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, definition: definitionParam, policyOverride: policyOverrideParam },
|
|
185
|
+
annotations: writes
|
|
186
|
+
}, handle(config, async ({ project, environment, applicationId, definition, policyOverride }) => {
|
|
187
|
+
const normalized = normalizeMicroserviceDefinition(definition);
|
|
188
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'preview' }, policyOverride));
|
|
189
|
+
const payload = await platform.tenantRaw('POST', project, `/microservices/${applicationId}/preview`, { environment, body: definition });
|
|
190
|
+
return attachFindings(withNormalization(payload, normalized), findings);
|
|
191
|
+
}));
|
|
192
|
+
server.registerTool('microservice_job_history', {
|
|
193
|
+
title: 'Job geçmişi',
|
|
194
|
+
description: 'Zamanlanmış (job tipli) microservice\'in çalışma geçmişini listeler.',
|
|
195
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid, ...pagingParams },
|
|
196
|
+
annotations: readOnly
|
|
197
|
+
}, handle(config, ({ project, environment, id, ...query }) => platform.tenant('GET', project, `/microservices/${id}/job-history`, { environment, query })));
|
|
198
|
+
}
|