epa-testeprojetoia 0.1.0
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/.idea/epa_mcp.iml +8 -0
- package/.idea/modules.xml +8 -0
- package/.idea/php.xml +19 -0
- package/.idea/vcs.xml +6 -0
- package/AGENTS.md +10 -0
- package/README.md +339 -0
- package/dist/agent/agentHelpers.js +21 -0
- package/dist/agent/openaiAgent.js +170 -0
- package/dist/agent/slidingWindow.js +13 -0
- package/dist/api/epaApiClient.js +59 -0
- package/dist/cli/index.js +47 -0
- package/dist/config/credentialStore.js +92 -0
- package/dist/config/ensureCliAuth.js +127 -0
- package/dist/config/loadConfig.js +40 -0
- package/dist/config/setup.js +23 -0
- package/dist/core/createReferenceTool.js +12 -0
- package/dist/core/createTool.js +32 -0
- package/dist/mocks/requestMocks.js +47 -0
- package/dist/server/server.js +41 -0
- package/dist/server/stdioServer.js +3 -0
- package/dist/services/request/requestFilters.js +1 -0
- package/dist/services/requestService.js +81 -0
- package/dist/services/teamService.js +18 -0
- package/dist/sql/createSqlConnection.js +13 -0
- package/dist/sql/fetchSchemaSummary.js +38 -0
- package/dist/sql/generateSqlFromQuestion.js +83 -0
- package/dist/sql/generateSqlPlan.js +111 -0
- package/dist/sql/getSqlAgentErrorMessage.js +15 -0
- package/dist/sql/loadSqlAgentConfig.js +24 -0
- package/dist/sql/loadSqlConfig.js +43 -0
- package/dist/sql/parseSqlQuestionHints.js +29 -0
- package/dist/sql/runSqlAgentCli.js +163 -0
- package/dist/sql/runSqlCli.js +34 -0
- package/dist/sql/selectRelevantTables.js +136 -0
- package/dist/sql/sqlGuard.js +21 -0
- package/dist/sql/sqlPlan.js +16 -0
- package/dist/tests/requestService.test.js +110 -0
- package/dist/tools/analytics/teamReport.draft.js +16 -0
- package/dist/tools/loadTools.js +40 -0
- package/dist/tools/requests/assignees.js +50 -0
- package/dist/tools/requests/clients.draft.js +10 -0
- package/dist/tools/requests/create.draft.js +51 -0
- package/dist/tools/requests/list.js +50 -0
- package/dist/tools/requests/priorities.js +2 -0
- package/dist/tools/requests/services.draft.js +20 -0
- package/dist/tools/requests/types.js +16 -0
- package/dist/tools/requests/units.draft.js +10 -0
- package/dist/tools/requests/view.draft.js +20 -0
- package/dist/utils/buildDateRange.js +18 -0
- package/dist/utils/findIdByDescription.js +4 -0
- package/dist/utils/resolveAssigneeId.js +14 -0
- package/dist/utils/toolNameMaps.js +23 -0
- package/package.json +31 -0
- package/src/agent/agentHelpers.ts +25 -0
- package/src/agent/openaiAgent.ts +205 -0
- package/src/agent/slidingWindow.ts +17 -0
- package/src/api/epaApiClient.ts +82 -0
- package/src/cli/index.ts +61 -0
- package/src/config/credentialStore.ts +130 -0
- package/src/config/ensureCliAuth.ts +152 -0
- package/src/config/loadConfig.ts +62 -0
- package/src/config/setup.ts +35 -0
- package/src/core/createReferenceTool.ts +17 -0
- package/src/core/createTool.ts +51 -0
- package/src/mocks/requestMocks.ts +52 -0
- package/src/server/server.ts +61 -0
- package/src/server/stdioServer.ts +5 -0
- package/src/services/request/requestFilters.ts +12 -0
- package/src/services/requestService.ts +126 -0
- package/src/services/teamService.ts +27 -0
- package/src/sql/createSqlConnection.ts +15 -0
- package/src/sql/fetchSchemaSummary.ts +64 -0
- package/src/sql/generateSqlFromQuestion.ts +105 -0
- package/src/sql/generateSqlPlan.ts +133 -0
- package/src/sql/getSqlAgentErrorMessage.ts +24 -0
- package/src/sql/loadSqlAgentConfig.ts +33 -0
- package/src/sql/loadSqlConfig.ts +75 -0
- package/src/sql/parseSqlQuestionHints.ts +46 -0
- package/src/sql/runSqlAgentCli.ts +204 -0
- package/src/sql/runSqlCli.ts +40 -0
- package/src/sql/selectRelevantTables.ts +184 -0
- package/src/sql/sqlGuard.ts +28 -0
- package/src/sql/sqlPlan.ts +28 -0
- package/src/tests/requestService.test.ts +152 -0
- package/src/tools/analytics/teamReport.draft.ts +25 -0
- package/src/tools/loadTools.ts +59 -0
- package/src/tools/requests/assignees.ts +59 -0
- package/src/tools/requests/clients.draft.ts +18 -0
- package/src/tools/requests/create.draft.ts +59 -0
- package/src/tools/requests/list.ts +57 -0
- package/src/tools/requests/priorities.ts +6 -0
- package/src/tools/requests/services.draft.ts +24 -0
- package/src/tools/requests/types.ts +18 -0
- package/src/tools/requests/units.draft.ts +18 -0
- package/src/tools/requests/view.draft.ts +27 -0
- package/src/utils/buildDateRange.ts +22 -0
- package/src/utils/findIdByDescription.ts +10 -0
- package/src/utils/resolveAssigneeId.ts +24 -0
- package/src/utils/toolNameMaps.ts +33 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { RequestService } from "../services/requestService.js";
|
|
4
|
+
import { resolveAssigneeId } from "../utils/resolveAssigneeId.js";
|
|
5
|
+
class FakeApiClient {
|
|
6
|
+
getCalls = [];
|
|
7
|
+
postCalls = [];
|
|
8
|
+
getResult;
|
|
9
|
+
postResult;
|
|
10
|
+
constructor(results) {
|
|
11
|
+
this.getResult = results.getResult;
|
|
12
|
+
this.postResult = results.postResult;
|
|
13
|
+
}
|
|
14
|
+
async get(endpoint, params) {
|
|
15
|
+
this.getCalls.push({ endpoint, params });
|
|
16
|
+
return this.getResult;
|
|
17
|
+
}
|
|
18
|
+
async post(endpoint, body = {}, params) {
|
|
19
|
+
this.postCalls.push({ endpoint, body, params });
|
|
20
|
+
return this.postResult;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
test("list usa /api/api/os/listar e aplica filtro de responsavel", async () => {
|
|
24
|
+
const fakeApi = new FakeApiClient({ postResult: [{ id: 1 }] });
|
|
25
|
+
const service = new RequestService(fakeApi);
|
|
26
|
+
await service.list(123);
|
|
27
|
+
assert.equal(fakeApi.postCalls.length, 1);
|
|
28
|
+
const call = fakeApi.postCalls[0];
|
|
29
|
+
assert.equal(call.endpoint, "/api/api/os/listar");
|
|
30
|
+
assert.deepEqual(call.body, {});
|
|
31
|
+
assert.equal(call.params?.responsavel, 123);
|
|
32
|
+
assert.match(String(call.params?.data_inclusao), /^\d{4}-01-01 00:00:00to\d{4}-12-31 23:59:59$/);
|
|
33
|
+
});
|
|
34
|
+
test("list com last_12_months envia data_inclusao em formato valido", async () => {
|
|
35
|
+
const fakeApi = new FakeApiClient({ postResult: [{ id: 1 }] });
|
|
36
|
+
const service = new RequestService(fakeApi);
|
|
37
|
+
await service.list({ assigneeId: 1, period: "last_12_months" });
|
|
38
|
+
assert.equal(fakeApi.postCalls.length, 1);
|
|
39
|
+
const call = fakeApi.postCalls[0];
|
|
40
|
+
assert.equal(call.endpoint, "/api/api/os/listar");
|
|
41
|
+
assert.match(String(call.params?.data_inclusao), /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}to\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/);
|
|
42
|
+
});
|
|
43
|
+
test("view usa /api/api/os/listar com codigo", async () => {
|
|
44
|
+
const fakeApi = new FakeApiClient({ postResult: [{ codigo: 999, titulo: "OS 999" }] });
|
|
45
|
+
const service = new RequestService(fakeApi);
|
|
46
|
+
const result = await service.view(999);
|
|
47
|
+
assert.equal(fakeApi.postCalls.length, 1);
|
|
48
|
+
const call = fakeApi.postCalls[0];
|
|
49
|
+
assert.equal(call.endpoint, "/api/api/os/listar");
|
|
50
|
+
assert.deepEqual(call.body, {});
|
|
51
|
+
assert.deepEqual(call.params, { codigo: 999 });
|
|
52
|
+
assert.deepEqual(result, { codigo: 999, titulo: "OS 999" });
|
|
53
|
+
});
|
|
54
|
+
test("create usa /api/api/solicitacao_rapida com payload mapeado", async () => {
|
|
55
|
+
const fakeApi = new FakeApiClient({ postResult: { id: 321 } });
|
|
56
|
+
const service = new RequestService(fakeApi);
|
|
57
|
+
const payload = {
|
|
58
|
+
os_tipo: 1,
|
|
59
|
+
os_tipo_servico: 2,
|
|
60
|
+
unidade_gerencial_executora: 3,
|
|
61
|
+
responsavel: 4,
|
|
62
|
+
titulo: "Nova solicitacao",
|
|
63
|
+
cliente: 5,
|
|
64
|
+
data_desejavel: "10/03/2026",
|
|
65
|
+
prioridade: 6,
|
|
66
|
+
observacao: "observacao de teste"
|
|
67
|
+
};
|
|
68
|
+
const result = await service.create(payload);
|
|
69
|
+
assert.equal(fakeApi.postCalls.length, 1);
|
|
70
|
+
const call = fakeApi.postCalls[0];
|
|
71
|
+
assert.equal(call.endpoint, "/api/api/solicitacao_rapida");
|
|
72
|
+
assert.deepEqual(call.body, payload);
|
|
73
|
+
assert.deepEqual(result, { id: 321 });
|
|
74
|
+
});
|
|
75
|
+
test("getTypes usa endpoint real com action/controller", async () => {
|
|
76
|
+
const fakeApi = new FakeApiClient({ getResult: [{ codigo: 1009, descricao: "Abrir Lojas" }] });
|
|
77
|
+
const service = new RequestService(fakeApi);
|
|
78
|
+
const result = await service.getTypes();
|
|
79
|
+
assert.equal(fakeApi.getCalls.length, 1);
|
|
80
|
+
const call = fakeApi.getCalls[0];
|
|
81
|
+
assert.equal(call.endpoint, "/epa_os/ajax.php");
|
|
82
|
+
assert.deepEqual(call.params, { action: "get", controller: "TipoSolicitacao" });
|
|
83
|
+
assert.deepEqual(result, [{ codigo: 1009, descricao: "Abrir Lojas" }]);
|
|
84
|
+
});
|
|
85
|
+
test("getAssignees monta payload obrigatorio e filtro opcional", async () => {
|
|
86
|
+
const fakeApi = new FakeApiClient({
|
|
87
|
+
getResult: { results: [{ id: 1, text: "suporte.simeon - Suporte Simeon" }] }
|
|
88
|
+
});
|
|
89
|
+
const service = new RequestService(fakeApi);
|
|
90
|
+
const result = await service.getAssignees("suporte", { unitValue: 18 });
|
|
91
|
+
assert.equal(fakeApi.getCalls.length, 1);
|
|
92
|
+
const call = fakeApi.getCalls[0];
|
|
93
|
+
assert.equal(call.endpoint, "/api/api/usuarios/search");
|
|
94
|
+
assert.deepEqual(call.params, {
|
|
95
|
+
term: "suporte",
|
|
96
|
+
_type: "query",
|
|
97
|
+
q: "suporte",
|
|
98
|
+
"filters[unidade_gerencial][type]": "pertenco",
|
|
99
|
+
"filters[unidade_gerencial][value]": 18
|
|
100
|
+
});
|
|
101
|
+
assert.deepEqual(result, [{ id: 1, text: "suporte.simeon - Suporte Simeon" }]);
|
|
102
|
+
});
|
|
103
|
+
test("resolveAssigneeId resolve login com match exato", () => {
|
|
104
|
+
const assignees = [
|
|
105
|
+
{ id: 10, text: "suporte.outro - Outro" },
|
|
106
|
+
{ id: 1, text: "suporte.simeon - Suporte Simeon" }
|
|
107
|
+
];
|
|
108
|
+
const id = resolveAssigneeId(assignees, "suporte.simeon");
|
|
109
|
+
assert.equal(id, 1);
|
|
110
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createTool } from "../../core/createTool.js";
|
|
2
|
+
import { TeamService } from "../../services/teamService.js";
|
|
3
|
+
const service = new TeamService();
|
|
4
|
+
export default createTool({
|
|
5
|
+
name: "analytics.teamReport",
|
|
6
|
+
description: "Gera relatório de tarefas de um desenvolvedor",
|
|
7
|
+
schema: {
|
|
8
|
+
name: {
|
|
9
|
+
type: "string",
|
|
10
|
+
description: "Nome do desenvolvedor"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
handler: async ({ name }) => {
|
|
14
|
+
return service.teamReport(name);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
4
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
5
|
+
const toolsDir = path.dirname(currentFilePath);
|
|
6
|
+
export async function loadTools() {
|
|
7
|
+
const tools = [];
|
|
8
|
+
async function scan(dir) {
|
|
9
|
+
const files = fs.readdirSync(dir);
|
|
10
|
+
for (const file of files) {
|
|
11
|
+
const full = path.join(dir, file);
|
|
12
|
+
const stat = fs.statSync(full);
|
|
13
|
+
if (stat.isDirectory()) {
|
|
14
|
+
await scan(full);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (!file.endsWith(".ts") && !file.endsWith(".js")) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (file.endsWith(".draft.ts") || file.endsWith(".draft.js")) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (file === "loadTools.ts" || file === "loadTools.js") {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const tool = await import(pathToFileURL(full).href);
|
|
27
|
+
if (tool.default) {
|
|
28
|
+
tools.push(tool.default);
|
|
29
|
+
}
|
|
30
|
+
if (tool.tool) {
|
|
31
|
+
tools.push(tool.tool);
|
|
32
|
+
}
|
|
33
|
+
if (tool.teamReportTool) {
|
|
34
|
+
tools.push(tool.teamReportTool);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
await scan(toolsDir);
|
|
39
|
+
return tools;
|
|
40
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createTool } from "../../core/createTool.js";
|
|
3
|
+
import { RequestService } from "../../services/requestService.js";
|
|
4
|
+
const service = new RequestService();
|
|
5
|
+
export default createTool({
|
|
6
|
+
name: "requests.assignees",
|
|
7
|
+
description: `
|
|
8
|
+
Busca responsáveis no EPA.
|
|
9
|
+
|
|
10
|
+
Campos obrigatórios:
|
|
11
|
+
- term: texto de pesquisa
|
|
12
|
+
- _type: sempre "query" (preenchido automaticamente)
|
|
13
|
+
- q: texto de pesquisa (usa term por padrão)
|
|
14
|
+
|
|
15
|
+
Filtro opcional preparado:
|
|
16
|
+
- unit_value: ID da unidade gerencial
|
|
17
|
+
- unit_type: padrão "pertenco"
|
|
18
|
+
`,
|
|
19
|
+
schema: {
|
|
20
|
+
term: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Texto de pesquisa (ex: suporte)"
|
|
23
|
+
},
|
|
24
|
+
q: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "Texto de pesquisa alternativo (opcional; padrão: term)"
|
|
27
|
+
},
|
|
28
|
+
unit_value: {
|
|
29
|
+
type: "number",
|
|
30
|
+
description: "ID da unidade gerencial para filtro opcional"
|
|
31
|
+
},
|
|
32
|
+
unit_type: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Tipo do filtro de unidade (opcional; padrao: pertenco)"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
validator: z.object({
|
|
38
|
+
term: z.string().min(1),
|
|
39
|
+
q: z.string().min(1).optional(),
|
|
40
|
+
unit_value: z.number().int().positive().optional(),
|
|
41
|
+
unit_type: z.string().min(1).optional()
|
|
42
|
+
}).strict(),
|
|
43
|
+
handler: async ({ term, q, unit_value, unit_type }) => {
|
|
44
|
+
return service.getAssignees(term, {
|
|
45
|
+
q,
|
|
46
|
+
unitValue: unit_value,
|
|
47
|
+
unitType: unit_type
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createTool } from "../../core/createTool.js";
|
|
2
|
+
import { RequestService } from "../../services/requestService.js";
|
|
3
|
+
const service = new RequestService();
|
|
4
|
+
export default createTool({
|
|
5
|
+
name: "requests.clients",
|
|
6
|
+
description: "Lista clientes disponíveis",
|
|
7
|
+
handler: async () => {
|
|
8
|
+
return service.getClients();
|
|
9
|
+
}
|
|
10
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createTool } from "../../core/createTool.js";
|
|
3
|
+
import { RequestService } from "../../services/requestService.js";
|
|
4
|
+
const service = new RequestService();
|
|
5
|
+
export default createTool({
|
|
6
|
+
name: "requests.create",
|
|
7
|
+
description: `
|
|
8
|
+
Cria uma nova solicitacao no EPA.
|
|
9
|
+
|
|
10
|
+
Antes de usar esta tool e recomendado consultar:
|
|
11
|
+
|
|
12
|
+
1. requests.types
|
|
13
|
+
2. requests.services
|
|
14
|
+
3. requests.units
|
|
15
|
+
4. requests.assignees
|
|
16
|
+
5. requests.clients
|
|
17
|
+
6. requests.priorities
|
|
18
|
+
|
|
19
|
+
Use os IDs retornados por essas ferramentas.
|
|
20
|
+
`,
|
|
21
|
+
schema: {
|
|
22
|
+
os_tipo: { type: "number", description: "ID do tipo retornado pela tool requests.types" },
|
|
23
|
+
os_tipo_servico: { type: "number", description: "ID de servico retornado pela tool requests.services" },
|
|
24
|
+
unidade_gerencial_executora: { type: "number", description: "ID da unidade retornado pela tool requests.units" },
|
|
25
|
+
responsavel: { type: "number", description: "ID do responsavel retornado pela tool requests.assignees" },
|
|
26
|
+
cliente: { type: "number", description: "ID do cliente retornado pela tool requests.clients" },
|
|
27
|
+
prioridade: { type: "number", description: "ID da prioridade retornado pela tool requests.priorities" },
|
|
28
|
+
titulo: { type: "string" },
|
|
29
|
+
observacao: { type: "string" },
|
|
30
|
+
data_desejavel: { type: "string", description: "Formato dd/mm/yyyy" }
|
|
31
|
+
},
|
|
32
|
+
validator: z.object({
|
|
33
|
+
os_tipo: z.number().int().positive(),
|
|
34
|
+
os_tipo_servico: z.number().int().positive(),
|
|
35
|
+
unidade_gerencial_executora: z.number().int().positive(),
|
|
36
|
+
responsavel: z.number().int().positive(),
|
|
37
|
+
cliente: z.number().int().positive(),
|
|
38
|
+
prioridade: z.number().int().positive(),
|
|
39
|
+
titulo: z.string().min(3),
|
|
40
|
+
observacao: z.string().optional(),
|
|
41
|
+
data_desejavel: z.string().regex(/^\d{2}\/\d{2}\/\d{4}$/)
|
|
42
|
+
}).strict(),
|
|
43
|
+
handler: async (args) => {
|
|
44
|
+
const result = await service.create(args);
|
|
45
|
+
return {
|
|
46
|
+
message: "Solicitacao criada com sucesso",
|
|
47
|
+
codigo: result.id,
|
|
48
|
+
link: `/exibir_solicitacao.php?codigo=${result.id}`
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createTool } from "../../core/createTool.js";
|
|
3
|
+
import { RequestService } from "../../services/requestService.js";
|
|
4
|
+
import { resolveAssigneeId } from "../../utils/resolveAssigneeId.js";
|
|
5
|
+
const service = new RequestService();
|
|
6
|
+
export default createTool({
|
|
7
|
+
name: "requests.list",
|
|
8
|
+
description: `
|
|
9
|
+
Lista solicitacoes do EPA.
|
|
10
|
+
|
|
11
|
+
Pode filtrar por ID do responsavel ou pelo nome/login do responsavel.
|
|
12
|
+
Tambem permite definir periodo.
|
|
13
|
+
|
|
14
|
+
Para descobrir os responsaveis disponiveis use:
|
|
15
|
+
requests.assignees
|
|
16
|
+
`,
|
|
17
|
+
schema: {
|
|
18
|
+
assignee_id: {
|
|
19
|
+
type: "number",
|
|
20
|
+
description: "ID do responsavel (retornado por requests.assignees)"
|
|
21
|
+
},
|
|
22
|
+
assignee_name: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Nome/login do responsavel (ex: suporte.simeon)"
|
|
25
|
+
},
|
|
26
|
+
period: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "Periodo de busca: current_year (padrao) ou last_12_months"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
validator: z.object({
|
|
32
|
+
assignee_id: z.number().int().positive().optional(),
|
|
33
|
+
assignee_name: z.string().min(1).optional(),
|
|
34
|
+
period: z.enum(["current_year", "last_12_months"]).optional()
|
|
35
|
+
}).strict(),
|
|
36
|
+
handler: async ({ assignee_id, assignee_name, period }) => {
|
|
37
|
+
if (assignee_id) {
|
|
38
|
+
return service.list({ assigneeId: assignee_id, period });
|
|
39
|
+
}
|
|
40
|
+
if (assignee_name) {
|
|
41
|
+
const assignees = await service.getAssignees(assignee_name, { q: assignee_name });
|
|
42
|
+
const assigneeId = resolveAssigneeId(assignees, assignee_name);
|
|
43
|
+
if (!assigneeId) {
|
|
44
|
+
return `Responsavel "${assignee_name}" nao encontrado.`;
|
|
45
|
+
}
|
|
46
|
+
return service.list({ assigneeId, period });
|
|
47
|
+
}
|
|
48
|
+
return service.list({ period });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createTool } from "../../core/createTool.js";
|
|
2
|
+
import { RequestService } from "../../services/requestService.js";
|
|
3
|
+
const service = new RequestService();
|
|
4
|
+
export default createTool({
|
|
5
|
+
name: "requests.services",
|
|
6
|
+
description: `
|
|
7
|
+
Lista serviços disponíveis para um tipo de solicitação.
|
|
8
|
+
|
|
9
|
+
Este endpoint depende de um tipo_id retornado
|
|
10
|
+
pela ferramenta "requests.types".
|
|
11
|
+
`,
|
|
12
|
+
schema: {
|
|
13
|
+
tipo_id: {
|
|
14
|
+
type: "number"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
handler: async ({ tipo_id }) => {
|
|
18
|
+
return service.getServices(tipo_id);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createTool } from "../../core/createTool.js";
|
|
2
|
+
import { RequestService } from "../../services/requestService.js";
|
|
3
|
+
const service = new RequestService();
|
|
4
|
+
export default createTool({
|
|
5
|
+
name: "requests.types",
|
|
6
|
+
description: `
|
|
7
|
+
Lista os tipos de solicitação disponíveis no EPA.
|
|
8
|
+
|
|
9
|
+
Este é o primeiro passo para criar uma solicitação.
|
|
10
|
+
Após escolher um tipo, use o tipo_id retornado
|
|
11
|
+
para buscar os serviços usando "requests.services".
|
|
12
|
+
`,
|
|
13
|
+
handler: async () => {
|
|
14
|
+
return service.getTypes();
|
|
15
|
+
}
|
|
16
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createTool } from "../../core/createTool.js";
|
|
2
|
+
import { RequestService } from "../../services/requestService.js";
|
|
3
|
+
const service = new RequestService();
|
|
4
|
+
export default createTool({
|
|
5
|
+
name: "requests.units",
|
|
6
|
+
description: "Lista unidades gerenciais executoras",
|
|
7
|
+
handler: async () => {
|
|
8
|
+
return service.getUnits();
|
|
9
|
+
}
|
|
10
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createTool } from "../../core/createTool.js";
|
|
3
|
+
import { RequestService } from "../../services/requestService.js";
|
|
4
|
+
const service = new RequestService();
|
|
5
|
+
export default createTool({
|
|
6
|
+
name: "requests.view",
|
|
7
|
+
description: "Visualiza uma solicitacao especifica",
|
|
8
|
+
schema: {
|
|
9
|
+
id: {
|
|
10
|
+
type: "number",
|
|
11
|
+
description: "ID da solicitacao"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
validator: z.object({
|
|
15
|
+
id: z.number().int().positive()
|
|
16
|
+
}).strict(),
|
|
17
|
+
handler: async ({ id }) => {
|
|
18
|
+
return service.view(id);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function formatDateTime(date, endOfDay = false) {
|
|
2
|
+
const year = date.getFullYear();
|
|
3
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
4
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
5
|
+
const hours = endOfDay ? "23" : String(date.getHours()).padStart(2, "0");
|
|
6
|
+
const minutes = endOfDay ? "59" : String(date.getMinutes()).padStart(2, "0");
|
|
7
|
+
const seconds = endOfDay ? "59" : String(date.getSeconds()).padStart(2, "0");
|
|
8
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
9
|
+
}
|
|
10
|
+
export function buildDateRange(period = "current_year", now = new Date()) {
|
|
11
|
+
if (period === "last_12_months") {
|
|
12
|
+
const start = new Date(now);
|
|
13
|
+
start.setFullYear(now.getFullYear() - 1);
|
|
14
|
+
return `${formatDateTime(start)}to${formatDateTime(now, true)}`;
|
|
15
|
+
}
|
|
16
|
+
const year = now.getFullYear();
|
|
17
|
+
return `${year}-01-01 00:00:00to${year}-12-31 23:59:59`;
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function resolveAssigneeId(assignees, assigneeName) {
|
|
2
|
+
if (!Array.isArray(assignees) || assignees.length === 0) {
|
|
3
|
+
return null;
|
|
4
|
+
}
|
|
5
|
+
const normalizedName = assigneeName.trim().toLowerCase();
|
|
6
|
+
const exact = assignees.find((item) => {
|
|
7
|
+
const text = String(item?.text ?? "").toLowerCase();
|
|
8
|
+
const login = text.split(" - ")[0]?.trim();
|
|
9
|
+
return login === normalizedName || text.startsWith(`${normalizedName} -`);
|
|
10
|
+
});
|
|
11
|
+
const selected = exact ?? assignees[0];
|
|
12
|
+
const id = Number(selected?.id);
|
|
13
|
+
return Number.isFinite(id) && id > 0 ? id : null;
|
|
14
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function sanitizeToolName(name, maxLength) {
|
|
2
|
+
const sanitized = name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
3
|
+
return sanitized.slice(0, maxLength);
|
|
4
|
+
}
|
|
5
|
+
export function buildToolNameMaps(toolNames, options = {}) {
|
|
6
|
+
const maxLength = Number.isFinite(options.maxLength) && (options.maxLength ?? 0) > 0
|
|
7
|
+
? Number(options.maxLength)
|
|
8
|
+
: 64;
|
|
9
|
+
const internalToExternal = new Map();
|
|
10
|
+
const externalToInternal = new Map();
|
|
11
|
+
const usageCount = new Map();
|
|
12
|
+
for (const internalName of toolNames) {
|
|
13
|
+
const base = sanitizeToolName(internalName, maxLength);
|
|
14
|
+
const count = usageCount.get(base) ?? 0;
|
|
15
|
+
usageCount.set(base, count + 1);
|
|
16
|
+
const suffix = count === 0 ? "" : `_${count + 1}`;
|
|
17
|
+
const maxBaseLength = Math.max(1, maxLength - suffix.length);
|
|
18
|
+
const externalName = `${base.slice(0, maxBaseLength)}${suffix}`;
|
|
19
|
+
internalToExternal.set(internalName, externalName);
|
|
20
|
+
externalToInternal.set(externalName, internalName);
|
|
21
|
+
}
|
|
22
|
+
return { internalToExternal, externalToInternal };
|
|
23
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "epa-testeprojetoia",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "EPA MCP Server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"epa-mcp": "./dist/server/stdioServer.js",
|
|
8
|
+
"epa-mcp-cli": "./dist/cli/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsx src/cli/index.ts",
|
|
13
|
+
"mcp:server": "node dist/server/stdioServer.js",
|
|
14
|
+
"start": "node dist/cli/index.js",
|
|
15
|
+
"test": "npm run build && node dist/tests/requestService.test.js"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
19
|
+
"axios": "^1.6.0",
|
|
20
|
+
"commander": "^12.0.0",
|
|
21
|
+
"dotenv": "^16.4.0",
|
|
22
|
+
"mysql2": "^3.22.3",
|
|
23
|
+
"openai": "^4.0.0",
|
|
24
|
+
"zod": "^4.3.6"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.0.0",
|
|
28
|
+
"tsx": "^4.7.0",
|
|
29
|
+
"typescript": "^5.4.0"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { buildToolNameMaps as buildBaseToolNameMaps } from "../utils/toolNameMaps.js"
|
|
2
|
+
|
|
3
|
+
export function buildToolNameMaps(toolNames: string[]) {
|
|
4
|
+
const { internalToExternal, externalToInternal } = buildBaseToolNameMaps(toolNames)
|
|
5
|
+
return {
|
|
6
|
+
mcpToOpenAi: internalToExternal,
|
|
7
|
+
openAiToMcp: externalToInternal
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isSpawnPermissionError(error: unknown): boolean {
|
|
12
|
+
if (!(error instanceof Error)) {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const maybeCode = (error as any).code
|
|
17
|
+
return maybeCode === "EPERM" && error.message.includes("spawn")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getErrorMessage(error: unknown): string {
|
|
21
|
+
if (error instanceof Error) {
|
|
22
|
+
return error.message
|
|
23
|
+
}
|
|
24
|
+
return String(error)
|
|
25
|
+
}
|