@creative-ia/cortex 1.0.5

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 (64) hide show
  1. package/README.md +41 -0
  2. package/dist/config/cloud-proxy.d.ts +15 -0
  3. package/dist/config/cloud-proxy.js +63 -0
  4. package/dist/config/cloudwatch-store.d.ts +13 -0
  5. package/dist/config/cloudwatch-store.js +66 -0
  6. package/dist/config/license.d.ts +29 -0
  7. package/dist/config/license.js +165 -0
  8. package/dist/config/ssm-store.d.ts +2 -0
  9. package/dist/config/ssm-store.js +38 -0
  10. package/dist/config/telemetry.d.ts +17 -0
  11. package/dist/config/telemetry.js +93 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +460 -0
  14. package/dist/knowledge/dynamo-store.d.ts +17 -0
  15. package/dist/knowledge/dynamo-store.js +85 -0
  16. package/dist/knowledge/embeddings.d.ts +2 -0
  17. package/dist/knowledge/embeddings.js +36 -0
  18. package/dist/knowledge/loader.d.ts +8 -0
  19. package/dist/knowledge/loader.js +57 -0
  20. package/dist/lambda-package.zip +0 -0
  21. package/dist/lambda.d.ts +22 -0
  22. package/dist/lambda.js +496 -0
  23. package/dist/package.json +1 -0
  24. package/dist/tools/advance-process.d.ts +7 -0
  25. package/dist/tools/advance-process.js +128 -0
  26. package/dist/tools/analyze-code.d.ts +7 -0
  27. package/dist/tools/analyze-code.js +131 -0
  28. package/dist/tools/analyze-docs.d.ts +8 -0
  29. package/dist/tools/analyze-docs.js +147 -0
  30. package/dist/tools/config-registry.d.ts +3 -0
  31. package/dist/tools/config-registry.js +20 -0
  32. package/dist/tools/create-process.d.ts +6 -0
  33. package/dist/tools/create-process.js +257 -0
  34. package/dist/tools/decompose-epic.d.ts +7 -0
  35. package/dist/tools/decompose-epic.js +603 -0
  36. package/dist/tools/diagrams.d.ts +51 -0
  37. package/dist/tools/diagrams.js +304 -0
  38. package/dist/tools/generate-report.d.ts +9 -0
  39. package/dist/tools/generate-report.js +891 -0
  40. package/dist/tools/generate-wiki.d.ts +10 -0
  41. package/dist/tools/generate-wiki.js +700 -0
  42. package/dist/tools/get-architecture.d.ts +6 -0
  43. package/dist/tools/get-architecture.js +78 -0
  44. package/dist/tools/get-code-standards.d.ts +7 -0
  45. package/dist/tools/get-code-standards.js +52 -0
  46. package/dist/tools/init-process.d.ts +7 -0
  47. package/dist/tools/init-process.js +82 -0
  48. package/dist/tools/knowledge-crud.d.ts +26 -0
  49. package/dist/tools/knowledge-crud.js +142 -0
  50. package/dist/tools/logo-base64.d.ts +1 -0
  51. package/dist/tools/logo-base64.js +1 -0
  52. package/dist/tools/logs-query.d.ts +15 -0
  53. package/dist/tools/logs-query.js +46 -0
  54. package/dist/tools/reverse-engineer.d.ts +13 -0
  55. package/dist/tools/reverse-engineer.js +956 -0
  56. package/dist/tools/semantic-search.d.ts +7 -0
  57. package/dist/tools/semantic-search.js +68 -0
  58. package/dist/tools/update-process.d.ts +17 -0
  59. package/dist/tools/update-process.js +195 -0
  60. package/dist/tools/validate-idea.d.ts +7 -0
  61. package/dist/tools/validate-idea.js +339 -0
  62. package/dist/tools/validate-process.d.ts +6 -0
  63. package/dist/tools/validate-process.js +102 -0
  64. package/package.json +31 -0
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Knowledge Loader — Carrega skills e knowledge do S3 (LocalStack ou AWS).
3
+ * O conteúdo NUNCA é exposto diretamente ao cliente.
4
+ * As tools processam o conteúdo e retornam apenas respostas derivadas.
5
+ */
6
+ import { S3Client, GetObjectCommand, ListObjectsV2Command } from "@aws-sdk/client-s3";
7
+ const isLocal = process.env.USE_LOCALSTACK === "true";
8
+ const s3 = new S3Client({
9
+ region: process.env.AWS_REGION || "us-east-1",
10
+ ...(isLocal && {
11
+ endpoint: "http://localhost:4566",
12
+ forcePathStyle: true,
13
+ credentials: { accessKeyId: "test", secretAccessKey: "test" },
14
+ }),
15
+ });
16
+ const BUCKET = process.env.SKILLS_BUCKET || "creative-ia50-skills";
17
+ async function getObject(key) {
18
+ const cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key });
19
+ const res = await s3.send(cmd);
20
+ return (await res.Body?.transformToString()) || "";
21
+ }
22
+ async function listObjects(prefix) {
23
+ const cmd = new ListObjectsV2Command({ Bucket: BUCKET, Prefix: prefix });
24
+ const res = await s3.send(cmd);
25
+ return (res.Contents || []).map((o) => o.Key).filter(Boolean);
26
+ }
27
+ // --- Public API ---
28
+ export async function loadCodeStandards() {
29
+ const raw = await getObject("knowledge/code-standards.json");
30
+ return JSON.parse(raw);
31
+ }
32
+ export async function loadDecisions() {
33
+ const raw = await getObject("knowledge/decisions.json");
34
+ return JSON.parse(raw);
35
+ }
36
+ export async function loadGlossary() {
37
+ const raw = await getObject("knowledge/glossary.json");
38
+ return JSON.parse(raw);
39
+ }
40
+ export async function loadIntegrations() {
41
+ const raw = await getObject("knowledge/integrations.json");
42
+ return JSON.parse(raw);
43
+ }
44
+ export async function loadSkill(name) {
45
+ return getObject(`skills/${name}.md`);
46
+ }
47
+ export async function listSkills() {
48
+ const keys = await listObjects("skills/");
49
+ return keys.map((k) => k.replace("skills/", "").replace(".md", ""));
50
+ }
51
+ export async function loadIndex() {
52
+ const raw = await getObject("knowledge/index.json");
53
+ return JSON.parse(raw);
54
+ }
55
+ export async function loadTemplate(name) {
56
+ return getObject(`templates/${name}`);
57
+ }
Binary file
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ interface APIGatewayV2Event {
3
+ requestContext: {
4
+ http: {
5
+ method: string;
6
+ path: string;
7
+ sourceIp: string;
8
+ };
9
+ requestId: string;
10
+ };
11
+ headers: Record<string, string>;
12
+ body?: string;
13
+ isBase64Encoded?: boolean;
14
+ }
15
+ interface APIGatewayV2Result {
16
+ statusCode: number;
17
+ headers: Record<string, string>;
18
+ body: string;
19
+ isBase64Encoded?: boolean;
20
+ }
21
+ export declare function handler(event: APIGatewayV2Event): Promise<APIGatewayV2Result>;
22
+ export {};
package/dist/lambda.js ADDED
@@ -0,0 +1,496 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Creative IA 50 — Lambda Handler (Streamable HTTP)
4
+ *
5
+ * Recebe requests HTTP do API Gateway e processa via MCP Server.
6
+ * Stateless: cada request cria uma nova instância do transport.
7
+ *
8
+ * Usa WebStandardStreamableHTTPServerTransport (Web Standard Request/Response)
9
+ * que é a forma correta de integrar com Lambda/API Gateway.
10
+ *
11
+ * Fluxo:
12
+ * 1. API Gateway recebe POST /mcp com JSON-RPC body
13
+ * 2. Lambda converte evento para Web Standard Request
14
+ * 3. WebStandardStreamableHTTPServerTransport processa
15
+ * 4. Response volta como JSON-RPC via API Gateway
16
+ */
17
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
18
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
19
+ import { z } from "zod";
20
+ import { validateLicense, canAccessDomain } from "./config/license.js";
21
+ import { getCodeStandards } from "./tools/get-code-standards.js";
22
+ import { analyzeCode } from "./tools/analyze-code.js";
23
+ import { getArchitectureGuidance } from "./tools/get-architecture.js";
24
+ import { initProcess } from "./tools/init-process.js";
25
+ import { validateProcess } from "./tools/validate-process.js";
26
+ import { validateIdea } from "./tools/validate-idea.js";
27
+ import { generateReport } from "./tools/generate-report.js";
28
+ import { saveKnowledge, getKnowledge, searchKnowledge, deleteKnowledgeEntry, listDomains, } from "./tools/knowledge-crud.js";
29
+ import { getConfig } from "./tools/config-registry.js";
30
+ import { logEvent, searchLogs, insightsQuery } from "./tools/logs-query.js";
31
+ import { semanticSearch } from "./tools/semantic-search.js";
32
+ import { createProcess } from "./tools/create-process.js";
33
+ import { updateProcess } from "./tools/update-process.js";
34
+ import { decomposeEpic } from "./tools/decompose-epic.js";
35
+ import { advanceProcess } from "./tools/advance-process.js";
36
+ import { generateWiki } from "./tools/generate-wiki.js";
37
+ import { reverseEngineer } from "./tools/reverse-engineer.js";
38
+ // --- Helper: license gate ---
39
+ async function licenseGate(licenseKey, toolName) {
40
+ const check = await validateLicense(licenseKey, toolName);
41
+ if (!check.valid)
42
+ return { denied: `Access denied: ${check.reason}` };
43
+ return { license: check.license };
44
+ }
45
+ function createServer() {
46
+ const server = new McpServer({ name: "creative-ia50", version: "1.0.0" });
47
+ // --- Tool: get_code_standards ---
48
+ server.tool("get_code_standards", "Returns code standards rules (L1-L4) for the requested level, language, or specific rule ID.", {
49
+ licenseKey: z.string().describe("Client license key"),
50
+ level: z.string().optional().describe("L1, L2, L3, L4, or all"),
51
+ language: z.string().optional().describe("typescript, java, or all"),
52
+ ruleId: z.string().optional().describe("Specific rule ID (e.g. L1-001)"),
53
+ }, async ({ licenseKey, level, language, ruleId }) => {
54
+ const gate = await licenseGate(licenseKey, "get_code_standards");
55
+ if ("denied" in gate)
56
+ return { content: [{ type: "text", text: gate.denied }] };
57
+ const result = await getCodeStandards({ level, language, ruleId });
58
+ return { content: [{ type: "text", text: result }] };
59
+ });
60
+ // --- Tool: analyze_code ---
61
+ server.tool("analyze_code", "Analyzes code against L1-L4 standards. Returns violations with fix suggestions.", {
62
+ licenseKey: z.string().describe("Client license key"),
63
+ code: z.string().describe("Source code to analyze"),
64
+ language: z.string().optional().describe("typescript or java"),
65
+ filename: z.string().optional().describe("Filename for context"),
66
+ }, async ({ licenseKey, code, language, filename }) => {
67
+ const gate = await licenseGate(licenseKey, "analyze_code");
68
+ if ("denied" in gate)
69
+ return { content: [{ type: "text", text: gate.denied }] };
70
+ const result = await analyzeCode({ code, language, filename });
71
+ return { content: [{ type: "text", text: result }] };
72
+ });
73
+ // --- Tool: get_architecture_guidance ---
74
+ server.tool("get_architecture_guidance", "Returns architecture guidance for a specific stack (BFF, Backend, Frontend).", {
75
+ licenseKey: z.string().describe("Client license key"),
76
+ stack: z.string().describe("bff, backend, or frontend"),
77
+ topic: z.string().optional().describe("layers, folders, errors, api, di, or all"),
78
+ }, async ({ licenseKey, stack, topic }) => {
79
+ const gate = await licenseGate(licenseKey, "get_architecture_guidance");
80
+ if ("denied" in gate)
81
+ return { content: [{ type: "text", text: gate.denied }] };
82
+ const result = await getArchitectureGuidance({ stack, topic });
83
+ return { content: [{ type: "text", text: result }] };
84
+ });
85
+ // --- Tool: init_process ---
86
+ server.tool("init_process", "Initializes a new process with UUID, checklist, and mandatory workflow.", {
87
+ licenseKey: z.string().describe("Client license key"),
88
+ title: z.string().describe("Process title"),
89
+ stakeholder: z.string().optional().describe("Stakeholder name"),
90
+ description: z.string().optional().describe("Process description"),
91
+ }, async ({ licenseKey, title, stakeholder, description }) => {
92
+ const gate = await licenseGate(licenseKey, "init_process");
93
+ if ("denied" in gate)
94
+ return { content: [{ type: "text", text: gate.denied }] };
95
+ const result = await initProcess({ title, stakeholder, description });
96
+ return { content: [{ type: "text", text: result }] };
97
+ });
98
+ // --- Tool: validate_process ---
99
+ server.tool("validate_process", "Validates if a process follows the mandatory workflow and has all required artifacts.", {
100
+ licenseKey: z.string().describe("Client license key"),
101
+ processId: z.string().describe("Process UUID"),
102
+ artifacts: z.array(z.string()).describe("List of artifact filenames in the process"),
103
+ }, async ({ licenseKey, processId, artifacts }) => {
104
+ const gate = await licenseGate(licenseKey, "validate_process");
105
+ if ("denied" in gate)
106
+ return { content: [{ type: "text", text: gate.denied }] };
107
+ const result = await validateProcess({ processId, artifacts });
108
+ return { content: [{ type: "text", text: result }] };
109
+ });
110
+ // --- Tool: validate_idea ---
111
+ server.tool("validate_idea", "Analyzes a business idea against the company knowledge base.", {
112
+ licenseKey: z.string().describe("Client license key"),
113
+ idea: z.string().describe("Business idea in natural language"),
114
+ context: z.string().optional().describe("Additional context about the idea"),
115
+ processId: z.string().optional().describe("UUID do processo (se chamado dentro de um processo existente)"),
116
+ }, async ({ licenseKey, idea, context, processId }) => {
117
+ const gate = await licenseGate(licenseKey, "validate_idea");
118
+ if ("denied" in gate)
119
+ return { content: [{ type: "text", text: gate.denied }] };
120
+ const result = await validateIdea({ idea, context, processId });
121
+ return { content: [{ type: "text", text: result }] };
122
+ });
123
+ // --- Tool: generate_report ---
124
+ server.tool("generate_report", "Generates a branded HTML report from processed content.", {
125
+ licenseKey: z.string().describe("Client license key"),
126
+ title: z.string().describe("Report title"),
127
+ content: z.string().describe("Report content"),
128
+ reportType: z.string().describe("Report type: feasibility, analysis, architecture, process, epics"),
129
+ outputDir: z.string().optional().describe("Custom output directory"),
130
+ diagramsDir: z.string().optional().describe("Relative path to generated-diagrams folder (for epics reportType)"),
131
+ }, async ({ licenseKey, title, content, reportType, outputDir, diagramsDir }) => {
132
+ const gate = await licenseGate(licenseKey, "generate_report");
133
+ if ("denied" in gate)
134
+ return { content: [{ type: "text", text: gate.denied }] };
135
+ const result = await generateReport({ title, content, reportType, outputDir, diagramsDir });
136
+ return { content: [{ type: "text", text: result }] };
137
+ });
138
+ // =============================================================================
139
+ // Knowledge CRUD Tools
140
+ // =============================================================================
141
+ server.tool("save_knowledge", "Salva ou atualiza um registro de conhecimento organizacional no DynamoDB.", {
142
+ licenseKey: z.string().describe("Client license key"),
143
+ domain: z.string().describe("Knowledge domain"),
144
+ id: z.string().describe("Unique ID within domain"),
145
+ title: z.string().describe("Entry title"),
146
+ content: z.string().describe("Entry content (markdown)"),
147
+ tags: z.array(z.string()).optional().describe("Tags for search/filtering"),
148
+ metadata: z.record(z.unknown()).optional().describe("Additional structured metadata"),
149
+ }, async ({ licenseKey, domain, id, title, content, tags, metadata }) => {
150
+ const gate = await licenseGate(licenseKey, "save_knowledge");
151
+ if ("denied" in gate)
152
+ return { content: [{ type: "text", text: gate.denied }] };
153
+ if (!canAccessDomain(gate.license, domain))
154
+ return { content: [{ type: "text", text: `Access denied: domain '${domain}' not allowed` }] };
155
+ const result = await saveKnowledge({ domain, id, title, content, tags, metadata });
156
+ return { content: [{ type: "text", text: result }] };
157
+ });
158
+ server.tool("get_knowledge", "Recupera conhecimento organizacional.", {
159
+ licenseKey: z.string().describe("Client license key"),
160
+ domain: z.string().describe("Knowledge domain"),
161
+ id: z.string().optional().describe("Specific entry ID"),
162
+ limit: z.number().optional().describe("Max entries to return"),
163
+ }, async ({ licenseKey, domain, id, limit }) => {
164
+ const gate = await licenseGate(licenseKey, "get_knowledge");
165
+ if ("denied" in gate)
166
+ return { content: [{ type: "text", text: gate.denied }] };
167
+ if (!canAccessDomain(gate.license, domain))
168
+ return { content: [{ type: "text", text: `Access denied: domain '${domain}' not allowed` }] };
169
+ const result = await getKnowledge({ domain, id, limit });
170
+ return { content: [{ type: "text", text: result }] };
171
+ });
172
+ server.tool("search_knowledge", "Busca textual no conhecimento organizacional.", {
173
+ licenseKey: z.string().describe("Client license key"),
174
+ query: z.string().describe("Search text"),
175
+ domain: z.string().optional().describe("Limit search to specific domain"),
176
+ tag: z.string().optional().describe("Search by tag"),
177
+ limit: z.number().optional().describe("Max results"),
178
+ }, async ({ licenseKey, query, domain, tag, limit }) => {
179
+ const gate = await licenseGate(licenseKey, "search_knowledge");
180
+ if ("denied" in gate)
181
+ return { content: [{ type: "text", text: gate.denied }] };
182
+ if (domain && !canAccessDomain(gate.license, domain))
183
+ return { content: [{ type: "text", text: `Access denied: domain '${domain}' not allowed` }] };
184
+ const result = await searchKnowledge({ query, domain, tag, limit, license: gate.license });
185
+ return { content: [{ type: "text", text: result }] };
186
+ });
187
+ server.tool("delete_knowledge", "Remove um registro de conhecimento organizacional.", {
188
+ licenseKey: z.string().describe("Client license key"),
189
+ domain: z.string().describe("Knowledge domain"),
190
+ id: z.string().describe("Entry ID to delete"),
191
+ }, async ({ licenseKey, domain, id }) => {
192
+ const gate = await licenseGate(licenseKey, "delete_knowledge");
193
+ if ("denied" in gate)
194
+ return { content: [{ type: "text", text: gate.denied }] };
195
+ if (!canAccessDomain(gate.license, domain))
196
+ return { content: [{ type: "text", text: `Access denied: domain '${domain}' not allowed` }] };
197
+ const result = await deleteKnowledgeEntry({ domain, id });
198
+ return { content: [{ type: "text", text: result }] };
199
+ });
200
+ server.tool("list_knowledge_domains", "Lista todos os domains de conhecimento disponíveis.", { licenseKey: z.string().describe("Client license key") }, async ({ licenseKey }) => {
201
+ const gate = await licenseGate(licenseKey, "list_knowledge_domains");
202
+ if ("denied" in gate)
203
+ return { content: [{ type: "text", text: gate.denied }] };
204
+ const result = await listDomains(gate.license);
205
+ return { content: [{ type: "text", text: result }] };
206
+ });
207
+ // =============================================================================
208
+ // Config + Logs + Semantic Search
209
+ // =============================================================================
210
+ server.tool("get_config", "Lê configuração do MCP Server via SSM Parameter Store.", {
211
+ licenseKey: z.string().describe("Client license key"),
212
+ key: z.string().optional().describe("Config key"),
213
+ }, async ({ licenseKey, key }) => {
214
+ const gate = await licenseGate(licenseKey, "get_config");
215
+ if ("denied" in gate)
216
+ return { content: [{ type: "text", text: gate.denied }] };
217
+ const result = await getConfig({ key });
218
+ return { content: [{ type: "text", text: result }] };
219
+ });
220
+ server.tool("log_event", "Registra um evento de log no CloudWatch Logs.", {
221
+ licenseKey: z.string().describe("Client license key"),
222
+ level: z.string().describe("Log level: info, warn, error, debug"),
223
+ message: z.string().describe("Log message"),
224
+ meta: z.record(z.unknown()).optional().describe("Additional metadata"),
225
+ }, async ({ licenseKey, level, message, meta }) => {
226
+ const gate = await licenseGate(licenseKey, "log_event");
227
+ if ("denied" in gate)
228
+ return { content: [{ type: "text", text: gate.denied }] };
229
+ const result = await logEvent({ level, message, meta });
230
+ return { content: [{ type: "text", text: result }] };
231
+ });
232
+ server.tool("search_logs", "Busca logs no CloudWatch por pattern.", {
233
+ licenseKey: z.string().describe("Client license key"),
234
+ pattern: z.string().optional().describe("Filter pattern"),
235
+ hours: z.number().optional().describe("Hours to look back"),
236
+ limit: z.number().optional().describe("Max results"),
237
+ }, async ({ licenseKey, pattern, hours, limit }) => {
238
+ const gate = await licenseGate(licenseKey, "search_logs");
239
+ if ("denied" in gate)
240
+ return { content: [{ type: "text", text: gate.denied }] };
241
+ const result = await searchLogs({ pattern, hours, limit });
242
+ return { content: [{ type: "text", text: result }] };
243
+ });
244
+ server.tool("insights_query", "Executa uma query CloudWatch Insights nos logs.", {
245
+ licenseKey: z.string().describe("Client license key"),
246
+ query: z.string().describe("CloudWatch Insights query"),
247
+ hours: z.number().optional().describe("Hours to look back"),
248
+ limit: z.number().optional().describe("Max results"),
249
+ }, async ({ licenseKey, query, hours, limit }) => {
250
+ const gate = await licenseGate(licenseKey, "insights_query");
251
+ if ("denied" in gate)
252
+ return { content: [{ type: "text", text: gate.denied }] };
253
+ const result = await insightsQuery({ query, hours, limit });
254
+ return { content: [{ type: "text", text: result }] };
255
+ });
256
+ server.tool("semantic_search", "Busca semântica no knowledge base usando Bedrock Titan Embeddings.", {
257
+ licenseKey: z.string().describe("Client license key"),
258
+ query: z.string().describe("Natural language search query"),
259
+ domain: z.string().optional().describe("Limit to specific domain"),
260
+ limit: z.number().optional().describe("Max results"),
261
+ }, async ({ licenseKey, query, domain, limit }) => {
262
+ const gate = await licenseGate(licenseKey, "semantic_search");
263
+ if ("denied" in gate)
264
+ return { content: [{ type: "text", text: gate.denied }] };
265
+ if (domain && !canAccessDomain(gate.license, domain))
266
+ return { content: [{ type: "text", text: `Access denied: domain '${domain}' not allowed` }] };
267
+ const result = await semanticSearch({ query, domain, limit, license: gate.license });
268
+ return { content: [{ type: "text", text: result }] };
269
+ });
270
+ server.tool("create_process", "Cria esqueleto de processo: recebe frase em linguagem natural, inicializa no DynamoDB e retorna arquivos locais (README, 01-stakeholder, knowledge.json, orchestration.json). O parecer de viabilidade é gerado após o stakeholder estar completo.", {
271
+ licenseKey: z.string().describe("Client license key"),
272
+ prompt: z.string().describe("Frase em linguagem natural descrevendo o processo (ex: 'Criar processo de arquitetura para jornada PIX')"),
273
+ stakeholder: z.string().optional().describe("Nome do stakeholder ou squad"),
274
+ }, async ({ licenseKey, prompt, stakeholder }) => {
275
+ const gate = await licenseGate(licenseKey, "create_process");
276
+ if ("denied" in gate)
277
+ return { content: [{ type: "text", text: gate.denied }] };
278
+ const result = await createProcess({ prompt, stakeholder });
279
+ return { content: [{ type: "text", text: result }] };
280
+ });
281
+ server.tool("update_process", "Acrescenta informações a um processo existente (tech stack, decisões, status de fases, notas). Pode listar processos acessíveis.", {
282
+ licenseKey: z.string().describe("Client license key"),
283
+ processId: z.string().describe("UUID do processo (ex: 'ddb8715d')"),
284
+ updates: z.object({
285
+ tech_stack: z.record(z.string()).optional().describe("Mapa chave-valor do tech stack (ex: {frontend: 'React Native', backend: 'Java Spring Boot'})"),
286
+ decisions: z.array(z.object({
287
+ title: z.string().describe("Título da decisão"),
288
+ decision: z.string().describe("Decisão tomada"),
289
+ rationale: z.string().optional().describe("Justificativa"),
290
+ })).optional().describe("Novas decisões técnicas a adicionar"),
291
+ phase_status: z.record(z.string()).optional().describe("Atualização de status de fases (ex: {'01-stakeholder': 'done'})"),
292
+ notes: z.string().optional().describe("Nota ou observação a adicionar"),
293
+ stakeholder: z.string().optional().describe("Atualizar stakeholder"),
294
+ }).describe("Objeto com as atualizações a aplicar"),
295
+ listProcesses: z.boolean().optional().describe("Se true, lista processos acessíveis ao invés de atualizar"),
296
+ }, async ({ licenseKey, processId, updates, listProcesses }) => {
297
+ const gate = await licenseGate(licenseKey, "update_process");
298
+ if ("denied" in gate)
299
+ return { content: [{ type: "text", text: gate.denied }] };
300
+ const result = await updateProcess({ processId, updates, listProcesses });
301
+ return { content: [{ type: "text", text: result }] };
302
+ });
303
+ server.tool("decompose_epic", "Decompõe um épico (EP-XX) em artefatos completos: EPIC.md + Features + User Stories + Tasks. Lê o markdown de épicos do processo e gera toda a hierarquia.", {
304
+ licenseKey: z.string().describe("Client license key"),
305
+ processId: z.string().describe("UUID do processo (ex: '5b650020')"),
306
+ epicId: z.string().describe("ID do épico a decompor (ex: 'EP-01', 'EP-02')"),
307
+ processDir: z.string().describe("Caminho absoluto do diretório do processo (ex: '/Users/.../process/5b650020')"),
308
+ }, async ({ licenseKey, processId, epicId, processDir }) => {
309
+ const gate = await licenseGate(licenseKey, "decompose_epic");
310
+ if ("denied" in gate)
311
+ return { content: [{ type: "text", text: gate.denied }] };
312
+ const result = await decomposeEpic({ processId, epicId, processDir });
313
+ return { content: [{ type: "text", text: result }] };
314
+ });
315
+ server.tool("advance_process", "Orquestrador inteligente: detecta a fase atual do processo e avança para o próximo artefato. Se há épicos pendentes, decompõe o próximo automaticamente. Pode receber epicId específico.", {
316
+ licenseKey: z.string().describe("Client license key"),
317
+ processId: z.string().describe("UUID do processo (ex: '5b650020')"),
318
+ processDir: z.string().describe("Caminho absoluto do diretório do processo"),
319
+ epicId: z.string().optional().describe("ID do épico específico (ex: 'EP-01'). Se omitido, avança o próximo pendente."),
320
+ }, async ({ licenseKey, processId, processDir, epicId }) => {
321
+ const gate = await licenseGate(licenseKey, "advance_process");
322
+ if ("denied" in gate)
323
+ return { content: [{ type: "text", text: gate.denied }] };
324
+ const result = await advanceProcess({ processId, processDir, epicId });
325
+ return { content: [{ type: "text", text: result }] };
326
+ });
327
+ server.tool("generate_wiki", "Gera/atualiza wiki navegável do processo: index.html (home com cards), pages/ (cada artefato .md como .html com sidebar TOC + breadcrumb), styles.css e loader.js compartilhados. Formato dark theme com layout de referência.", {
328
+ licenseKey: z.string().describe("Client license key"),
329
+ processId: z.string().describe("UUID do processo (ex: '5b650020')"),
330
+ processDir: z.string().describe("Caminho absoluto do diretório do processo"),
331
+ }, async ({ licenseKey, processId, processDir }) => {
332
+ const gate = await licenseGate(licenseKey, "generate_wiki");
333
+ if ("denied" in gate)
334
+ return { content: [{ type: "text", text: gate.denied }] };
335
+ const result = await generateWiki({ processId, processDir });
336
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
337
+ });
338
+ server.tool("reverse_engineer", "Analisa repositório local ou remoto (Git URL) e gera artefato .md de engenharia reversa: stack, módulos, dependências, APIs, padrões de arquitetura e métricas. Para repos remotos, configure tokens nas env vars do mcp.json (GITHUB_TOKEN, GITLAB_TOKEN, BITBUCKET_TOKEN, AZURE_DEVOPS_TOKEN).", {
339
+ licenseKey: z.string().describe("Client license key"),
340
+ repoDir: z.string().describe("Caminho absoluto do repositório local OU URL Git remota (https://github.com/org/repo.git)"),
341
+ processId: z.string().optional().describe("UUID do processo (se vinculado a um processo existente)"),
342
+ processDir: z.string().optional().describe("Caminho absoluto do diretório do processo (para salvar o artefato dentro do processo)"),
343
+ outputFilename: z.string().optional().describe("Nome do arquivo de saída (default: engenharia-reversa-{repo}.md)"),
344
+ }, async ({ licenseKey, repoDir, processId, processDir, outputFilename }) => {
345
+ const gate = await licenseGate(licenseKey, "reverse_engineer");
346
+ if ("denied" in gate)
347
+ return { content: [{ type: "text", text: gate.denied }] };
348
+ const result = await reverseEngineer({ repoDir, processId, processDir, outputFilename });
349
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
350
+ });
351
+ return server;
352
+ }
353
+ /**
354
+ * Extrai X-License-Key do header (case-insensitive, API Gateway normaliza pra lowercase).
355
+ */
356
+ function extractLicenseKey(headers) {
357
+ // API Gateway HTTP API v2 normaliza headers pra lowercase
358
+ return headers["x-license-key"] || headers["X-License-Key"];
359
+ }
360
+ /**
361
+ * Injeta licenseKey nos argumentos de tools/call se não estiver presente.
362
+ * Assim o cliente não precisa passar licenseKey em cada chamada — vem do header.
363
+ */
364
+ function injectLicenseKey(body, licenseKey) {
365
+ if (!body || typeof body !== "object")
366
+ return body;
367
+ const rpc = body;
368
+ // Só injeta em tools/call
369
+ if (rpc.method !== "tools/call")
370
+ return body;
371
+ const params = rpc.params;
372
+ if (!params?.arguments)
373
+ return body;
374
+ const args = params.arguments;
375
+ // Só injeta se não veio no body (header tem prioridade como fallback)
376
+ if (!args.licenseKey) {
377
+ args.licenseKey = licenseKey;
378
+ }
379
+ return body;
380
+ }
381
+ export async function handler(event) {
382
+ const method = event.requestContext.http.method;
383
+ // Health check
384
+ if (method === "GET") {
385
+ return {
386
+ statusCode: 200,
387
+ headers: { "content-type": "application/json" },
388
+ body: JSON.stringify({ status: "ok", service: "creative-ia50-mcp", version: "1.0.0" }),
389
+ };
390
+ }
391
+ // Only POST allowed for MCP
392
+ if (method !== "POST") {
393
+ return {
394
+ statusCode: 405,
395
+ headers: { "content-type": "application/json" },
396
+ body: JSON.stringify({ error: "Method not allowed" }),
397
+ };
398
+ }
399
+ // --- License validation at HTTP level ---
400
+ const licenseKey = extractLicenseKey(event.headers);
401
+ if (!licenseKey) {
402
+ return {
403
+ statusCode: 401,
404
+ headers: { "content-type": "application/json" },
405
+ body: JSON.stringify({ error: "Missing X-License-Key header" }),
406
+ };
407
+ }
408
+ // Validate license before processing any MCP request
409
+ const licenseCheck = await validateLicense(licenseKey);
410
+ if (!licenseCheck.valid) {
411
+ return {
412
+ statusCode: 403,
413
+ headers: { "content-type": "application/json" },
414
+ body: JSON.stringify({ error: `License denied: ${licenseCheck.reason}` }),
415
+ };
416
+ }
417
+ // Parse body
418
+ const rawBody = event.isBase64Encoded
419
+ ? Buffer.from(event.body || "", "base64").toString("utf-8")
420
+ : event.body || "";
421
+ // Input size limit (256KB)
422
+ if (rawBody.length > 256 * 1024) {
423
+ return {
424
+ statusCode: 413,
425
+ headers: { "content-type": "application/json" },
426
+ body: JSON.stringify({ error: "Request too large" }),
427
+ };
428
+ }
429
+ let parsedBody;
430
+ try {
431
+ parsedBody = JSON.parse(rawBody);
432
+ }
433
+ catch {
434
+ return {
435
+ statusCode: 400,
436
+ headers: { "content-type": "application/json" },
437
+ body: JSON.stringify({ error: "Invalid JSON" }),
438
+ };
439
+ }
440
+ // Inject licenseKey from header into tool call arguments
441
+ parsedBody = injectLicenseKey(parsedBody, licenseKey);
442
+ const injectedBody = JSON.stringify(parsedBody);
443
+ // Build Web Standard Request from API Gateway event
444
+ const url = `https://${event.headers?.host || "localhost"}${event.requestContext.http.path}`;
445
+ const webHeaders = new Headers();
446
+ for (const [k, v] of Object.entries(event.headers || {})) {
447
+ webHeaders.set(k, v);
448
+ }
449
+ // Ensure Accept header includes both required types for MCP
450
+ if (!webHeaders.get("accept")?.includes("text/event-stream")) {
451
+ webHeaders.set("accept", "application/json, text/event-stream");
452
+ }
453
+ const webRequest = new Request(url, {
454
+ method: "POST",
455
+ headers: webHeaders,
456
+ body: injectedBody,
457
+ });
458
+ // Create MCP server + transport per request (stateless)
459
+ const server = createServer();
460
+ const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
461
+ await server.connect(transport);
462
+ let result;
463
+ try {
464
+ // Process the MCP request using Web Standard API
465
+ const webResponse = await transport.handleRequest(webRequest, { parsedBody });
466
+ // Convert Web Standard Response to API Gateway format
467
+ const responseBody = await webResponse.text();
468
+ const responseHeaders = {};
469
+ webResponse.headers.forEach((v, k) => {
470
+ responseHeaders[k] = v;
471
+ });
472
+ result = {
473
+ statusCode: webResponse.status,
474
+ headers: responseHeaders,
475
+ body: responseBody,
476
+ };
477
+ }
478
+ catch (err) {
479
+ result = {
480
+ statusCode: 500,
481
+ headers: { "content-type": "application/json" },
482
+ body: JSON.stringify({ error: "Internal server error", detail: String(err) }),
483
+ };
484
+ }
485
+ // Cleanup silencioso — não pode lançar exceção após response pronta
486
+ // (evita InvalidStateTransition no Lambda runtime)
487
+ try {
488
+ await transport.close();
489
+ }
490
+ catch { /* ignore */ }
491
+ try {
492
+ await server.close();
493
+ }
494
+ catch { /* ignore */ }
495
+ return result;
496
+ }
@@ -0,0 +1 @@
1
+ { "type": "module" }
@@ -0,0 +1,7 @@
1
+ interface AdvanceProcessRequest {
2
+ processId: string;
3
+ processDir: string;
4
+ epicId?: string;
5
+ }
6
+ export declare function advanceProcess(params: AdvanceProcessRequest): Promise<string>;
7
+ export {};