@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.
- package/README.md +41 -0
- package/dist/config/cloud-proxy.d.ts +15 -0
- package/dist/config/cloud-proxy.js +63 -0
- package/dist/config/cloudwatch-store.d.ts +13 -0
- package/dist/config/cloudwatch-store.js +66 -0
- package/dist/config/license.d.ts +29 -0
- package/dist/config/license.js +165 -0
- package/dist/config/ssm-store.d.ts +2 -0
- package/dist/config/ssm-store.js +38 -0
- package/dist/config/telemetry.d.ts +17 -0
- package/dist/config/telemetry.js +93 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +460 -0
- package/dist/knowledge/dynamo-store.d.ts +17 -0
- package/dist/knowledge/dynamo-store.js +85 -0
- package/dist/knowledge/embeddings.d.ts +2 -0
- package/dist/knowledge/embeddings.js +36 -0
- package/dist/knowledge/loader.d.ts +8 -0
- package/dist/knowledge/loader.js +57 -0
- package/dist/lambda-package.zip +0 -0
- package/dist/lambda.d.ts +22 -0
- package/dist/lambda.js +496 -0
- package/dist/package.json +1 -0
- package/dist/tools/advance-process.d.ts +7 -0
- package/dist/tools/advance-process.js +128 -0
- package/dist/tools/analyze-code.d.ts +7 -0
- package/dist/tools/analyze-code.js +131 -0
- package/dist/tools/analyze-docs.d.ts +8 -0
- package/dist/tools/analyze-docs.js +147 -0
- package/dist/tools/config-registry.d.ts +3 -0
- package/dist/tools/config-registry.js +20 -0
- package/dist/tools/create-process.d.ts +6 -0
- package/dist/tools/create-process.js +257 -0
- package/dist/tools/decompose-epic.d.ts +7 -0
- package/dist/tools/decompose-epic.js +603 -0
- package/dist/tools/diagrams.d.ts +51 -0
- package/dist/tools/diagrams.js +304 -0
- package/dist/tools/generate-report.d.ts +9 -0
- package/dist/tools/generate-report.js +891 -0
- package/dist/tools/generate-wiki.d.ts +10 -0
- package/dist/tools/generate-wiki.js +700 -0
- package/dist/tools/get-architecture.d.ts +6 -0
- package/dist/tools/get-architecture.js +78 -0
- package/dist/tools/get-code-standards.d.ts +7 -0
- package/dist/tools/get-code-standards.js +52 -0
- package/dist/tools/init-process.d.ts +7 -0
- package/dist/tools/init-process.js +82 -0
- package/dist/tools/knowledge-crud.d.ts +26 -0
- package/dist/tools/knowledge-crud.js +142 -0
- package/dist/tools/logo-base64.d.ts +1 -0
- package/dist/tools/logo-base64.js +1 -0
- package/dist/tools/logs-query.d.ts +15 -0
- package/dist/tools/logs-query.js +46 -0
- package/dist/tools/reverse-engineer.d.ts +13 -0
- package/dist/tools/reverse-engineer.js +956 -0
- package/dist/tools/semantic-search.d.ts +7 -0
- package/dist/tools/semantic-search.js +68 -0
- package/dist/tools/update-process.d.ts +17 -0
- package/dist/tools/update-process.js +195 -0
- package/dist/tools/validate-idea.d.ts +7 -0
- package/dist/tools/validate-idea.js +339 -0
- package/dist/tools/validate-process.d.ts +6 -0
- package/dist/tools/validate-process.js +102 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Cortex by Creative IA
|
|
2
|
+
|
|
3
|
+
Centro de inteligencia para arquitetura de software.
|
|
4
|
+
|
|
5
|
+
Servidor MCP (Model Context Protocol) com 23 ferramentas para:
|
|
6
|
+
- Arquitetura e padroes de codigo (L1-L4)
|
|
7
|
+
- Processos de arquitetura de software
|
|
8
|
+
- Engenharia reversa de repositorios
|
|
9
|
+
- Knowledge base organizacional
|
|
10
|
+
- Documentacao e wiki automatizada
|
|
11
|
+
|
|
12
|
+
## Instalacao
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g @creative-ia/cortex
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Configuracao (mcp.json)
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"mcpServers": {
|
|
23
|
+
"cortex-local": {
|
|
24
|
+
"command": "cortex-mcp",
|
|
25
|
+
"env": {
|
|
26
|
+
"GITHUB_TOKEN": "ghp_xxx",
|
|
27
|
+
"AZURE_DEVOPS_PAT": "xxx"
|
|
28
|
+
},
|
|
29
|
+
"disabled": false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Licenca
|
|
36
|
+
|
|
37
|
+
Proprietario — Creative IA. Uso requer licenca valida.
|
|
38
|
+
|
|
39
|
+
## Suporte
|
|
40
|
+
|
|
41
|
+
creativeia50@gmail.com
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud Proxy — Delega chamadas de tools para o endpoint nuvem via HTTPS.
|
|
3
|
+
*
|
|
4
|
+
* Quando CORTEX_API_URL esta definido, as tools que dependem de AWS
|
|
5
|
+
* (DynamoDB, S3, SSM, CloudWatch, Bedrock) sao executadas na nuvem.
|
|
6
|
+
* O server local envia a chamada via JSON-RPC e retorna o resultado.
|
|
7
|
+
*
|
|
8
|
+
* Isso elimina a necessidade de credenciais AWS no cliente.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isCloudProxyEnabled(): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Chama uma tool no endpoint nuvem via JSON-RPC over HTTPS.
|
|
13
|
+
* Retorna o texto da resposta ou lanca erro.
|
|
14
|
+
*/
|
|
15
|
+
export declare function callCloudTool(toolName: string, args: Record<string, unknown>): Promise<string>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud Proxy — Delega chamadas de tools para o endpoint nuvem via HTTPS.
|
|
3
|
+
*
|
|
4
|
+
* Quando CORTEX_API_URL esta definido, as tools que dependem de AWS
|
|
5
|
+
* (DynamoDB, S3, SSM, CloudWatch, Bedrock) sao executadas na nuvem.
|
|
6
|
+
* O server local envia a chamada via JSON-RPC e retorna o resultado.
|
|
7
|
+
*
|
|
8
|
+
* Isso elimina a necessidade de credenciais AWS no cliente.
|
|
9
|
+
*/
|
|
10
|
+
const CORTEX_API_URL = process.env.CORTEX_API_URL;
|
|
11
|
+
export function isCloudProxyEnabled() {
|
|
12
|
+
return !!CORTEX_API_URL;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Chama uma tool no endpoint nuvem via JSON-RPC over HTTPS.
|
|
16
|
+
* Retorna o texto da resposta ou lanca erro.
|
|
17
|
+
*/
|
|
18
|
+
export async function callCloudTool(toolName, args) {
|
|
19
|
+
if (!CORTEX_API_URL) {
|
|
20
|
+
throw new Error("CORTEX_API_URL not configured");
|
|
21
|
+
}
|
|
22
|
+
const licenseKey = args.licenseKey || process.env.LICENSE_KEY || "";
|
|
23
|
+
const body = JSON.stringify({
|
|
24
|
+
jsonrpc: "2.0",
|
|
25
|
+
id: Date.now(),
|
|
26
|
+
method: "tools/call",
|
|
27
|
+
params: {
|
|
28
|
+
name: toolName,
|
|
29
|
+
arguments: args,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
const res = await fetch(CORTEX_API_URL, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: {
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
"X-License-Key": licenseKey,
|
|
37
|
+
},
|
|
38
|
+
body,
|
|
39
|
+
signal: AbortSignal.timeout(55000), // 55s (Lambda tem 60s timeout)
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
throw new Error(`Cloud proxy error: HTTP ${res.status}`);
|
|
43
|
+
}
|
|
44
|
+
const raw = await res.text();
|
|
45
|
+
// Endpoint pode retornar SSE (event: message\ndata: {...}) ou JSON direto
|
|
46
|
+
let json;
|
|
47
|
+
if (raw.startsWith("event:")) {
|
|
48
|
+
const dataLine = raw.split("\n").find((l) => l.startsWith("data:"));
|
|
49
|
+
json = JSON.parse(dataLine ? dataLine.slice(5).trim() : "{}");
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
json = JSON.parse(raw);
|
|
53
|
+
}
|
|
54
|
+
if (json.error) {
|
|
55
|
+
throw new Error(json.error.message || `Cloud error code ${json.error.code}`);
|
|
56
|
+
}
|
|
57
|
+
// Extrair texto da resposta MCP
|
|
58
|
+
const content = json.result?.content;
|
|
59
|
+
if (!content || content.length === 0) {
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
return content.map((c) => c.text || "").join("\n");
|
|
63
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function writeLog(level: string, message: string, meta?: Record<string, unknown>): Promise<void>;
|
|
2
|
+
export declare function filterLogs(params: {
|
|
3
|
+
pattern?: string;
|
|
4
|
+
startTime?: number;
|
|
5
|
+
endTime?: number;
|
|
6
|
+
limit?: number;
|
|
7
|
+
}): Promise<string[]>;
|
|
8
|
+
export declare function queryLogs(params: {
|
|
9
|
+
query: string;
|
|
10
|
+
startTime?: number;
|
|
11
|
+
endTime?: number;
|
|
12
|
+
limit?: number;
|
|
13
|
+
}): Promise<string[][]>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudWatch Logs — Escrita e consulta de logs do MCP Server.
|
|
3
|
+
* Log group: /creative-ia50/mcp-server
|
|
4
|
+
*/
|
|
5
|
+
import { CloudWatchLogsClient, CreateLogStreamCommand, PutLogEventsCommand, FilterLogEventsCommand, StartQueryCommand, GetQueryResultsCommand, } from "@aws-sdk/client-cloudwatch-logs";
|
|
6
|
+
const isLocal = process.env.USE_LOCALSTACK === "true";
|
|
7
|
+
const cwl = new CloudWatchLogsClient({
|
|
8
|
+
region: process.env.AWS_REGION || "sa-east-1",
|
|
9
|
+
...(isLocal && {
|
|
10
|
+
endpoint: "http://localhost:4566",
|
|
11
|
+
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
14
|
+
const LOG_GROUP = process.env.LOG_GROUP || "/creative-ia50/mcp-server";
|
|
15
|
+
// Ensure log stream exists for today
|
|
16
|
+
async function ensureStream(streamName) {
|
|
17
|
+
try {
|
|
18
|
+
await cwl.send(new CreateLogStreamCommand({ logGroupName: LOG_GROUP, logStreamName: streamName }));
|
|
19
|
+
}
|
|
20
|
+
catch { /* already exists */ }
|
|
21
|
+
}
|
|
22
|
+
// Write a log event
|
|
23
|
+
export async function writeLog(level, message, meta) {
|
|
24
|
+
const stream = `mcp-server/${new Date().toISOString().slice(0, 10)}`;
|
|
25
|
+
await ensureStream(stream);
|
|
26
|
+
const event = JSON.stringify({ level, message, ...meta, timestamp: new Date().toISOString() });
|
|
27
|
+
await cwl.send(new PutLogEventsCommand({
|
|
28
|
+
logGroupName: LOG_GROUP,
|
|
29
|
+
logStreamName: stream,
|
|
30
|
+
logEvents: [{ timestamp: Date.now(), message: event }],
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
// Filter logs by pattern
|
|
34
|
+
export async function filterLogs(params) {
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
const res = await cwl.send(new FilterLogEventsCommand({
|
|
37
|
+
logGroupName: LOG_GROUP,
|
|
38
|
+
filterPattern: params.pattern || "",
|
|
39
|
+
startTime: params.startTime || now - 24 * 60 * 60 * 1000, // default: last 24h
|
|
40
|
+
endTime: params.endTime || now,
|
|
41
|
+
limit: params.limit || 50,
|
|
42
|
+
}));
|
|
43
|
+
return (res.events || []).map(e => e.message || "");
|
|
44
|
+
}
|
|
45
|
+
// Run CloudWatch Insights query
|
|
46
|
+
export async function queryLogs(params) {
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const start = await cwl.send(new StartQueryCommand({
|
|
49
|
+
logGroupName: LOG_GROUP,
|
|
50
|
+
queryString: params.query,
|
|
51
|
+
startTime: Math.floor((params.startTime || now - 24 * 60 * 60 * 1000) / 1000),
|
|
52
|
+
endTime: Math.floor((params.endTime || now) / 1000),
|
|
53
|
+
limit: params.limit || 50,
|
|
54
|
+
}));
|
|
55
|
+
if (!start.queryId)
|
|
56
|
+
return [];
|
|
57
|
+
// Poll for results (max 10s)
|
|
58
|
+
for (let i = 0; i < 20; i++) {
|
|
59
|
+
await new Promise(r => setTimeout(r, 500));
|
|
60
|
+
const res = await cwl.send(new GetQueryResultsCommand({ queryId: start.queryId }));
|
|
61
|
+
if (res.status === "Complete" || res.status === "Failed" || res.status === "Cancelled") {
|
|
62
|
+
return (res.results || []).map(row => row.map(f => `${f.field}: ${f.value}`));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface License {
|
|
2
|
+
licenseKey: string;
|
|
3
|
+
clientName: string;
|
|
4
|
+
plan: string;
|
|
5
|
+
status: string;
|
|
6
|
+
role: "admin" | "user";
|
|
7
|
+
expiresAt: string;
|
|
8
|
+
allowedTools: Set<string>;
|
|
9
|
+
allowedDomains?: Set<string>;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
}
|
|
12
|
+
export interface LicenseCheck {
|
|
13
|
+
valid: boolean;
|
|
14
|
+
reason?: string;
|
|
15
|
+
license?: License;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Ponto de entrada unico — escolhe modo automaticamente.
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateLicense(licenseKey: string, toolName?: string): Promise<LicenseCheck>;
|
|
21
|
+
/**
|
|
22
|
+
* Verifica se a licença tem acesso a um domain específico.
|
|
23
|
+
* Admin tem acesso a tudo. User só aos domains em allowedDomains.
|
|
24
|
+
*/
|
|
25
|
+
export declare function canAccessDomain(license: License, domain: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Filtra lista de domains para apenas os permitidos pela licença.
|
|
28
|
+
*/
|
|
29
|
+
export declare function filterAllowedDomains(license: License, domains: string[]): string[];
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License Validator — Valida licença do cliente.
|
|
3
|
+
*
|
|
4
|
+
* Dois modos:
|
|
5
|
+
* 1. DynamoDB direto (Lambda / dev com AWS_PROFILE) — quando CORTEX_API_URL nao esta definido
|
|
6
|
+
* 2. HTTP remoto (cliente local via npx) — quando CORTEX_API_URL esta definido
|
|
7
|
+
* Chama o endpoint nuvem para validar, com cache em memoria (TTL 5min)
|
|
8
|
+
*
|
|
9
|
+
* RBAC:
|
|
10
|
+
* - role "admin" → acesso total a todos os domains e tools
|
|
11
|
+
* - role "user" → acesso restrito aos domains em allowedDomains
|
|
12
|
+
*/
|
|
13
|
+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
14
|
+
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
|
|
15
|
+
const isLocal = process.env.USE_LOCALSTACK === "true";
|
|
16
|
+
const CORTEX_API_URL = process.env.CORTEX_API_URL; // ex: https://zsul3dtu25.execute-api.sa-east-1.amazonaws.com/prod/mcp
|
|
17
|
+
const ddb = CORTEX_API_URL
|
|
18
|
+
? null // nao precisa de DynamoDB quando valida via HTTP
|
|
19
|
+
: DynamoDBDocumentClient.from(new DynamoDBClient({
|
|
20
|
+
region: process.env.AWS_REGION || "sa-east-1",
|
|
21
|
+
...(isLocal && {
|
|
22
|
+
endpoint: "http://localhost:4566",
|
|
23
|
+
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
24
|
+
}),
|
|
25
|
+
}));
|
|
26
|
+
const TABLE = process.env.LICENSE_TABLE || "creative-ia50-licenses";
|
|
27
|
+
const licenseCache = new Map();
|
|
28
|
+
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutos
|
|
29
|
+
/**
|
|
30
|
+
* Validacao remota via HTTP — chama get_config no endpoint nuvem.
|
|
31
|
+
* Se o endpoint responde com sucesso, a licenca e valida.
|
|
32
|
+
*/
|
|
33
|
+
async function validateLicenseRemote(licenseKey, _toolName) {
|
|
34
|
+
// Verificar cache
|
|
35
|
+
const cached = licenseCache.get(licenseKey);
|
|
36
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
37
|
+
return cached.check;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const body = JSON.stringify({
|
|
41
|
+
jsonrpc: "2.0",
|
|
42
|
+
id: 1,
|
|
43
|
+
method: "tools/call",
|
|
44
|
+
params: {
|
|
45
|
+
name: "get_config",
|
|
46
|
+
arguments: { licenseKey },
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
const res = await fetch(CORTEX_API_URL, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: {
|
|
52
|
+
"Content-Type": "application/json",
|
|
53
|
+
"X-License-Key": licenseKey,
|
|
54
|
+
},
|
|
55
|
+
body,
|
|
56
|
+
signal: AbortSignal.timeout(10000), // 10s timeout
|
|
57
|
+
});
|
|
58
|
+
if (!res.ok) {
|
|
59
|
+
return { valid: false, reason: `License validation failed (HTTP ${res.status})` };
|
|
60
|
+
}
|
|
61
|
+
const raw = await res.text();
|
|
62
|
+
// Endpoint pode retornar SSE (event: message\ndata: {...}) ou JSON direto
|
|
63
|
+
let json;
|
|
64
|
+
if (raw.startsWith("event:")) {
|
|
65
|
+
const dataLine = raw.split("\n").find((l) => l.startsWith("data:"));
|
|
66
|
+
json = JSON.parse(dataLine ? dataLine.slice(5).trim() : "{}");
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
json = JSON.parse(raw);
|
|
70
|
+
}
|
|
71
|
+
// Se retornou erro MCP
|
|
72
|
+
if (json.error) {
|
|
73
|
+
return { valid: false, reason: json.error.message || "License validation failed" };
|
|
74
|
+
}
|
|
75
|
+
// Se retornou "Access denied" no conteudo
|
|
76
|
+
const text = json.result?.content?.[0]?.text || "";
|
|
77
|
+
if (text.startsWith("Access denied")) {
|
|
78
|
+
return { valid: false, reason: text };
|
|
79
|
+
}
|
|
80
|
+
// Licenca valida — criar objeto License minimo para compatibilidade
|
|
81
|
+
const license = {
|
|
82
|
+
licenseKey,
|
|
83
|
+
clientName: "remote-client",
|
|
84
|
+
plan: "remote",
|
|
85
|
+
status: "active",
|
|
86
|
+
role: "admin", // server nuvem ja validou permissoes
|
|
87
|
+
expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(),
|
|
88
|
+
allowedTools: new Set(["*"]),
|
|
89
|
+
createdAt: new Date().toISOString(),
|
|
90
|
+
};
|
|
91
|
+
const check = { valid: true, license };
|
|
92
|
+
// Cachear resultado
|
|
93
|
+
licenseCache.set(licenseKey, { check, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
94
|
+
return check;
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
return { valid: false, reason: `License validation error: ${err.message}` };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Validacao local via DynamoDB direto.
|
|
102
|
+
*/
|
|
103
|
+
async function validateLicenseDynamo(licenseKey, toolName) {
|
|
104
|
+
try {
|
|
105
|
+
const res = await ddb.send(new GetCommand({ TableName: TABLE, Key: { licenseKey } }));
|
|
106
|
+
if (!res.Item) {
|
|
107
|
+
return { valid: false, reason: "License not found" };
|
|
108
|
+
}
|
|
109
|
+
const license = res.Item;
|
|
110
|
+
if (!license.role)
|
|
111
|
+
license.role = "user";
|
|
112
|
+
if (license.allowedTools && !(license.allowedTools instanceof Set)) {
|
|
113
|
+
license.allowedTools = new Set(license.allowedTools);
|
|
114
|
+
}
|
|
115
|
+
if (license.allowedDomains && !(license.allowedDomains instanceof Set)) {
|
|
116
|
+
license.allowedDomains = new Set(license.allowedDomains);
|
|
117
|
+
}
|
|
118
|
+
if (license.status !== "active") {
|
|
119
|
+
return { valid: false, reason: `License status: ${license.status}` };
|
|
120
|
+
}
|
|
121
|
+
if (new Date(license.expiresAt) < new Date()) {
|
|
122
|
+
return { valid: false, reason: "License expired" };
|
|
123
|
+
}
|
|
124
|
+
if (toolName && license.allowedTools && !license.allowedTools.has(toolName)) {
|
|
125
|
+
return { valid: false, reason: `Tool '${toolName}' not included in license plan` };
|
|
126
|
+
}
|
|
127
|
+
return { valid: true, license };
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
return { valid: false, reason: `License validation error: ${err.message}` };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Ponto de entrada unico — escolhe modo automaticamente.
|
|
135
|
+
*/
|
|
136
|
+
export async function validateLicense(licenseKey, toolName) {
|
|
137
|
+
if (!licenseKey) {
|
|
138
|
+
return { valid: false, reason: "License key is required" };
|
|
139
|
+
}
|
|
140
|
+
if (CORTEX_API_URL) {
|
|
141
|
+
return validateLicenseRemote(licenseKey, toolName);
|
|
142
|
+
}
|
|
143
|
+
return validateLicenseDynamo(licenseKey, toolName);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Verifica se a licença tem acesso a um domain específico.
|
|
147
|
+
* Admin tem acesso a tudo. User só aos domains em allowedDomains.
|
|
148
|
+
*/
|
|
149
|
+
export function canAccessDomain(license, domain) {
|
|
150
|
+
if (license.role === "admin")
|
|
151
|
+
return true;
|
|
152
|
+
if (!license.allowedDomains)
|
|
153
|
+
return false;
|
|
154
|
+
return license.allowedDomains.has(domain);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Filtra lista de domains para apenas os permitidos pela licença.
|
|
158
|
+
*/
|
|
159
|
+
export function filterAllowedDomains(license, domains) {
|
|
160
|
+
if (license.role === "admin")
|
|
161
|
+
return domains;
|
|
162
|
+
if (!license.allowedDomains)
|
|
163
|
+
return [];
|
|
164
|
+
return domains.filter((d) => license.allowedDomains.has(d));
|
|
165
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSM Parameter Store — Registry de configuração do MCP Server.
|
|
3
|
+
* Lê parâmetros de /creative-ia50/mcp-server/*
|
|
4
|
+
*/
|
|
5
|
+
import { SSMClient, GetParameterCommand, GetParametersByPathCommand } from "@aws-sdk/client-ssm";
|
|
6
|
+
const isLocal = process.env.USE_LOCALSTACK === "true";
|
|
7
|
+
const ssm = new SSMClient({
|
|
8
|
+
region: process.env.AWS_REGION || "sa-east-1",
|
|
9
|
+
...(isLocal && {
|
|
10
|
+
endpoint: "http://localhost:4566",
|
|
11
|
+
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
14
|
+
const PREFIX = "/creative-ia50/mcp-server";
|
|
15
|
+
export async function getParam(name) {
|
|
16
|
+
try {
|
|
17
|
+
const res = await ssm.send(new GetParameterCommand({ Name: `${PREFIX}/${name}` }));
|
|
18
|
+
return res.Parameter?.Value || null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function getAllParams() {
|
|
25
|
+
const result = {};
|
|
26
|
+
try {
|
|
27
|
+
const res = await ssm.send(new GetParametersByPathCommand({
|
|
28
|
+
Path: PREFIX,
|
|
29
|
+
Recursive: true,
|
|
30
|
+
}));
|
|
31
|
+
for (const p of res.Parameters || []) {
|
|
32
|
+
const key = p.Name?.replace(`${PREFIX}/`, "") || "";
|
|
33
|
+
result[key] = p.Value || "";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch { /* empty */ }
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface TelemetryEvent {
|
|
2
|
+
tool: string;
|
|
3
|
+
status: "success" | "error";
|
|
4
|
+
durationMs: number;
|
|
5
|
+
summary?: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
meta?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Envia evento de telemetria para ambos destinos (fire-and-forget).
|
|
11
|
+
*/
|
|
12
|
+
export declare function reportTelemetry(event: TelemetryEvent): void;
|
|
13
|
+
/**
|
|
14
|
+
* Wrapper que executa uma tool local e registra telemetria automaticamente.
|
|
15
|
+
*/
|
|
16
|
+
export declare function withTelemetry<T>(toolName: string, fn: () => Promise<T>, summarize?: (result: T) => string, meta?: Record<string, unknown>): Promise<T>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry — Registra execucao de tools locais na nuvem + Datadog Collector.
|
|
3
|
+
*
|
|
4
|
+
* Dual-destination:
|
|
5
|
+
* 1. CloudWatch via cloud proxy (log_event) — registro centralizado
|
|
6
|
+
* 2. Datadog Collector via HTTP POST — observabilidade visual (dashboard)
|
|
7
|
+
*
|
|
8
|
+
* Fire-and-forget: nao bloqueia a resposta ao cliente.
|
|
9
|
+
* Se falhar, silencia o erro (telemetria nao pode quebrar a tool).
|
|
10
|
+
*/
|
|
11
|
+
import { callCloudTool, isCloudProxyEnabled } from "./cloud-proxy.js";
|
|
12
|
+
const LICENSE_KEY = process.env.LICENSE_KEY || "";
|
|
13
|
+
const DATADOG_COLLECTOR_URL = process.env.DATADOG_COLLECTOR_URL || "";
|
|
14
|
+
/**
|
|
15
|
+
* Envia evento para o Datadog Collector (rum-collector) via HTTP POST.
|
|
16
|
+
* Formato compativel com /api/v2/backend-logs.
|
|
17
|
+
*/
|
|
18
|
+
function sendToCollector(event) {
|
|
19
|
+
if (!DATADOG_COLLECTOR_URL)
|
|
20
|
+
return;
|
|
21
|
+
const payload = {
|
|
22
|
+
service: "cortex-mcp",
|
|
23
|
+
env: process.env.CORTEX_ENV || "local",
|
|
24
|
+
level: event.status === "error" ? "error" : "info",
|
|
25
|
+
message: `[tool:${event.tool}] ${event.status} in ${event.durationMs}ms${event.summary ? ` — ${event.summary}` : ""}`,
|
|
26
|
+
timestamp: new Date().toISOString(),
|
|
27
|
+
duration_ms: event.durationMs,
|
|
28
|
+
method: "TOOL",
|
|
29
|
+
path: event.tool,
|
|
30
|
+
status_code: event.status === "error" ? 500 : 200,
|
|
31
|
+
error_message: event.error || null,
|
|
32
|
+
user_id: LICENSE_KEY || null,
|
|
33
|
+
tool: event.tool,
|
|
34
|
+
...(event.meta || {}),
|
|
35
|
+
};
|
|
36
|
+
const url = `${DATADOG_COLLECTOR_URL.replace(/\/$/, "")}/api/v2/backend-logs`;
|
|
37
|
+
fetch(url, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: { "Content-Type": "application/json" },
|
|
40
|
+
body: JSON.stringify(payload),
|
|
41
|
+
signal: AbortSignal.timeout(5000),
|
|
42
|
+
}).catch(() => {
|
|
43
|
+
// Silencia — collector pode estar offline
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Envia evento de telemetria para ambos destinos (fire-and-forget).
|
|
48
|
+
*/
|
|
49
|
+
export function reportTelemetry(event) {
|
|
50
|
+
// Destino 1: CloudWatch via cloud proxy
|
|
51
|
+
if (isCloudProxyEnabled() && LICENSE_KEY) {
|
|
52
|
+
const message = `[telemetry] tool=${event.tool} status=${event.status} duration=${event.durationMs}ms${event.summary ? ` summary=${event.summary}` : ""}${event.error ? ` error=${event.error}` : ""}`;
|
|
53
|
+
callCloudTool("log_event", {
|
|
54
|
+
licenseKey: LICENSE_KEY,
|
|
55
|
+
level: event.status === "error" ? "error" : "info",
|
|
56
|
+
message,
|
|
57
|
+
meta: {
|
|
58
|
+
tool: event.tool,
|
|
59
|
+
status: event.status,
|
|
60
|
+
durationMs: event.durationMs,
|
|
61
|
+
...(event.meta || {}),
|
|
62
|
+
},
|
|
63
|
+
}).catch(() => {
|
|
64
|
+
// Silencia — telemetria nao pode quebrar a tool
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Destino 2: Datadog Collector (rum-collector)
|
|
68
|
+
sendToCollector(event);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Wrapper que executa uma tool local e registra telemetria automaticamente.
|
|
72
|
+
*/
|
|
73
|
+
export async function withTelemetry(toolName, fn, summarize, meta) {
|
|
74
|
+
const start = Date.now();
|
|
75
|
+
try {
|
|
76
|
+
const result = await fn();
|
|
77
|
+
const durationMs = Date.now() - start;
|
|
78
|
+
const summary = summarize ? summarize(result) : undefined;
|
|
79
|
+
reportTelemetry({ tool: toolName, status: "success", durationMs, summary, meta });
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
const durationMs = Date.now() - start;
|
|
84
|
+
reportTelemetry({
|
|
85
|
+
tool: toolName,
|
|
86
|
+
status: "error",
|
|
87
|
+
durationMs,
|
|
88
|
+
error: err.message,
|
|
89
|
+
meta,
|
|
90
|
+
});
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
}
|
package/dist/index.d.ts
ADDED