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,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { startServer } from "../server/server.js";
|
|
4
|
+
import { setupConfig } from "../config/setup.js";
|
|
5
|
+
import { ensureCliAuth } from "../config/ensureCliAuth.js";
|
|
6
|
+
import { startAgent } from "../agent/openaiAgent.js";
|
|
7
|
+
import { runSqlCli } from "../sql/runSqlCli.js";
|
|
8
|
+
import { runSqlAgentCli } from "../sql/runSqlAgentCli.js";
|
|
9
|
+
const program = new Command();
|
|
10
|
+
program
|
|
11
|
+
.command("setup")
|
|
12
|
+
.description("Configurar URL da API do EPA")
|
|
13
|
+
.action(async () => {
|
|
14
|
+
await setupConfig();
|
|
15
|
+
});
|
|
16
|
+
program
|
|
17
|
+
.command("login")
|
|
18
|
+
.description("Realiza login na API do EPA e salva token para CLI")
|
|
19
|
+
.action(async () => {
|
|
20
|
+
await ensureCliAuth();
|
|
21
|
+
});
|
|
22
|
+
program
|
|
23
|
+
.command("server")
|
|
24
|
+
.description("Iniciar MCP Server")
|
|
25
|
+
.action(async () => {
|
|
26
|
+
console.error("Iniciando EPA MCP Server...");
|
|
27
|
+
await startServer();
|
|
28
|
+
});
|
|
29
|
+
program
|
|
30
|
+
.command("agent")
|
|
31
|
+
.description("Inicia agente OpenAI conectado ao MCP")
|
|
32
|
+
.action(async () => {
|
|
33
|
+
await startAgent();
|
|
34
|
+
});
|
|
35
|
+
program
|
|
36
|
+
.command("sql [query]")
|
|
37
|
+
.description("Executa consulta SQL técnica read-only no MySQL/MariaDB")
|
|
38
|
+
.action(async (query) => {
|
|
39
|
+
await runSqlCli(query);
|
|
40
|
+
});
|
|
41
|
+
program
|
|
42
|
+
.command("sql-agent [question]")
|
|
43
|
+
.description("Usa LLM para gerar e executar SQL read-only no MySQL/MariaDB")
|
|
44
|
+
.action(async (question) => {
|
|
45
|
+
await runSqlAgentCli(question);
|
|
46
|
+
});
|
|
47
|
+
program.parse();
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
function parseEnvFile(raw) {
|
|
4
|
+
const result = {};
|
|
5
|
+
const lines = raw.split(/\r?\n/);
|
|
6
|
+
for (const line of lines) {
|
|
7
|
+
const trimmed = line.trim();
|
|
8
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
const index = trimmed.indexOf("=");
|
|
12
|
+
if (index < 0) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const key = trimmed.slice(0, index).trim();
|
|
16
|
+
const value = trimmed.slice(index + 1).trim();
|
|
17
|
+
result[key] = value;
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
function stringifyEnv(values) {
|
|
22
|
+
return Object.entries(values)
|
|
23
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
24
|
+
.join("\n");
|
|
25
|
+
}
|
|
26
|
+
export function resolveCredentialStorePath() {
|
|
27
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
28
|
+
if (fs.existsSync(envPath)) {
|
|
29
|
+
return { kind: "env", path: envPath };
|
|
30
|
+
}
|
|
31
|
+
const configPath = path.join(process.cwd(), "config", "config.json");
|
|
32
|
+
return { kind: "config", path: configPath };
|
|
33
|
+
}
|
|
34
|
+
export function readStoredCredentials() {
|
|
35
|
+
const store = resolveCredentialStorePath();
|
|
36
|
+
if (store.kind === "env") {
|
|
37
|
+
const raw = fs.readFileSync(store.path, "utf-8");
|
|
38
|
+
const values = parseEnvFile(raw);
|
|
39
|
+
const timeout = Number(values.EPA_API_TIMEOUT_MS ?? values.epaApiTimeoutMs);
|
|
40
|
+
return {
|
|
41
|
+
epaApiUrl: values.EPA_API_URL ?? values.epaApiUrl,
|
|
42
|
+
epaApiToken: values.EPA_API_TOKEN ?? values.epaApiToken,
|
|
43
|
+
openaiApiKey: values.OPENAI_API_KEY ?? values.openaiApiKey,
|
|
44
|
+
epaApiTimeoutMs: Number.isFinite(timeout) && timeout > 0 ? timeout : undefined
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (!fs.existsSync(store.path)) {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
const raw = fs.readFileSync(store.path, "utf-8");
|
|
51
|
+
const json = JSON.parse(raw);
|
|
52
|
+
return {
|
|
53
|
+
epaApiUrl: json.epaApiUrl,
|
|
54
|
+
epaApiToken: json.epaApiToken,
|
|
55
|
+
openaiApiKey: json.openaiApiKey,
|
|
56
|
+
epaApiTimeoutMs: json.epaApiTimeoutMs
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function writeCredentials(values) {
|
|
60
|
+
const store = resolveCredentialStorePath();
|
|
61
|
+
if (store.kind === "env") {
|
|
62
|
+
const existing = fs.existsSync(store.path)
|
|
63
|
+
? parseEnvFile(fs.readFileSync(store.path, "utf-8"))
|
|
64
|
+
: {};
|
|
65
|
+
if (values.epaApiUrl) {
|
|
66
|
+
existing.EPA_API_URL = values.epaApiUrl;
|
|
67
|
+
}
|
|
68
|
+
if (values.epaApiToken) {
|
|
69
|
+
existing.EPA_API_TOKEN = values.epaApiToken;
|
|
70
|
+
}
|
|
71
|
+
if (values.openaiApiKey) {
|
|
72
|
+
existing.OPENAI_API_KEY = values.openaiApiKey;
|
|
73
|
+
}
|
|
74
|
+
if (values.epaApiTimeoutMs !== undefined) {
|
|
75
|
+
existing.EPA_API_TIMEOUT_MS = String(values.epaApiTimeoutMs);
|
|
76
|
+
}
|
|
77
|
+
fs.writeFileSync(store.path, `${stringifyEnv(existing)}\n`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const configDir = path.dirname(store.path);
|
|
81
|
+
if (!fs.existsSync(configDir)) {
|
|
82
|
+
fs.mkdirSync(configDir);
|
|
83
|
+
}
|
|
84
|
+
const current = fs.existsSync(store.path)
|
|
85
|
+
? JSON.parse(fs.readFileSync(store.path, "utf-8"))
|
|
86
|
+
: {};
|
|
87
|
+
const next = {
|
|
88
|
+
...current,
|
|
89
|
+
...values
|
|
90
|
+
};
|
|
91
|
+
fs.writeFileSync(store.path, JSON.stringify(next, null, 2));
|
|
92
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import readline from "readline";
|
|
3
|
+
import { readStoredCredentials, writeCredentials } from "./credentialStore.js";
|
|
4
|
+
function askQuestion(rl, question) {
|
|
5
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
6
|
+
}
|
|
7
|
+
function askHiddenQuestion(question) {
|
|
8
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
9
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
rl.question(question, (answer) => {
|
|
12
|
+
rl.close();
|
|
13
|
+
resolve(answer);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
let value = "";
|
|
19
|
+
const stdin = process.stdin;
|
|
20
|
+
const stdout = process.stdout;
|
|
21
|
+
const canSetRawMode = typeof stdin.setRawMode === "function";
|
|
22
|
+
stdout.write(question);
|
|
23
|
+
stdout.write("\x1B[8m");
|
|
24
|
+
stdin.setEncoding("utf8");
|
|
25
|
+
if (canSetRawMode) {
|
|
26
|
+
stdin.setRawMode(true);
|
|
27
|
+
}
|
|
28
|
+
stdin.resume();
|
|
29
|
+
const onData = (char) => {
|
|
30
|
+
if (char === "\u0003") {
|
|
31
|
+
cleanup();
|
|
32
|
+
reject(new Error("Entrada cancelada pelo usuário."));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (char === "\r" || char === "\n") {
|
|
36
|
+
stdout.write("\n");
|
|
37
|
+
cleanup();
|
|
38
|
+
resolve(value);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (char === "\u0008" || char === "\u007F") {
|
|
42
|
+
if (value.length > 0) {
|
|
43
|
+
value = value.slice(0, -1);
|
|
44
|
+
stdout.write("\b \b");
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const sanitized = char.replace(/[\r\n]/g, "");
|
|
49
|
+
if (!sanitized) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
value += sanitized;
|
|
53
|
+
stdout.write("*".repeat(sanitized.length));
|
|
54
|
+
};
|
|
55
|
+
const cleanup = () => {
|
|
56
|
+
stdin.off("data", onData);
|
|
57
|
+
if (canSetRawMode) {
|
|
58
|
+
stdin.setRawMode(false);
|
|
59
|
+
}
|
|
60
|
+
stdout.write("\x1B[28m");
|
|
61
|
+
stdin.pause();
|
|
62
|
+
};
|
|
63
|
+
stdin.on("data", onData);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function isTokenValid(baseURL, token) {
|
|
67
|
+
try {
|
|
68
|
+
await axios.get(`${baseURL}/api/api/prioridade`, {
|
|
69
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
70
|
+
timeout: 15000
|
|
71
|
+
});
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function loginToEpa(baseURL, login, senha) {
|
|
79
|
+
const response = await axios.post(`${baseURL}/api/api/novo/login`, { login, senha }, { timeout: 15000 });
|
|
80
|
+
const token = response?.data?.access_token;
|
|
81
|
+
if (!token || typeof token !== "string") {
|
|
82
|
+
throw new Error("Login realizado, mas access_token nao foi retornado pela API.");
|
|
83
|
+
}
|
|
84
|
+
return token;
|
|
85
|
+
}
|
|
86
|
+
export async function ensureCliAuth() {
|
|
87
|
+
const stored = readStoredCredentials();
|
|
88
|
+
const rl = readline.createInterface({
|
|
89
|
+
input: process.stdin,
|
|
90
|
+
output: process.stdout
|
|
91
|
+
});
|
|
92
|
+
try {
|
|
93
|
+
let epaApiUrl = process.env.EPA_API_URL || process.env.epaApiUrl || stored.epaApiUrl;
|
|
94
|
+
let epaApiToken = process.env.EPA_API_TOKEN || process.env.epaApiToken || stored.epaApiToken;
|
|
95
|
+
const openaiApiKey = process.env.OPENAI_API_KEY || process.env.openaiApiKey || stored.openaiApiKey;
|
|
96
|
+
const epaApiTimeoutMs = stored.epaApiTimeoutMs ?? 15000;
|
|
97
|
+
if (!epaApiUrl) {
|
|
98
|
+
epaApiUrl = (await askQuestion(rl, "EPA API URL: ")).trim();
|
|
99
|
+
}
|
|
100
|
+
let tokenValid = false;
|
|
101
|
+
if (epaApiUrl && epaApiToken) {
|
|
102
|
+
tokenValid = await isTokenValid(epaApiUrl, epaApiToken);
|
|
103
|
+
}
|
|
104
|
+
if (!epaApiToken || !tokenValid) {
|
|
105
|
+
console.log("Token EPA ausente ou invalido. Realizando login...");
|
|
106
|
+
const login = (await askQuestion(rl, "Login EPA: ")).trim();
|
|
107
|
+
rl.pause();
|
|
108
|
+
const senha = (await askHiddenQuestion("Senha EPA: ")).trim();
|
|
109
|
+
rl.resume();
|
|
110
|
+
epaApiToken = await loginToEpa(epaApiUrl, login, senha);
|
|
111
|
+
console.log("Login realizado com sucesso.");
|
|
112
|
+
}
|
|
113
|
+
let nextOpenAi = openaiApiKey;
|
|
114
|
+
if (!nextOpenAi) {
|
|
115
|
+
nextOpenAi = (await askQuestion(rl, "OpenAI API KEY (CLI): ")).trim();
|
|
116
|
+
}
|
|
117
|
+
writeCredentials({
|
|
118
|
+
epaApiUrl,
|
|
119
|
+
epaApiToken,
|
|
120
|
+
openaiApiKey: nextOpenAi || undefined,
|
|
121
|
+
epaApiTimeoutMs
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
rl.close();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import dotenv from "dotenv";
|
|
4
|
+
dotenv.config();
|
|
5
|
+
function readConfigFile() {
|
|
6
|
+
const configPath = path.join(process.cwd(), "config", "config.json");
|
|
7
|
+
if (!fs.existsSync(configPath)) {
|
|
8
|
+
return {};
|
|
9
|
+
}
|
|
10
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
11
|
+
return JSON.parse(raw);
|
|
12
|
+
}
|
|
13
|
+
function getEnvValue(...keys) {
|
|
14
|
+
for (const key of keys) {
|
|
15
|
+
const value = process.env[key];
|
|
16
|
+
if (value && value.trim()) {
|
|
17
|
+
return value.trim();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
export function loadConfig() {
|
|
23
|
+
const fileConfig = readConfigFile();
|
|
24
|
+
const epaApiUrl = getEnvValue("EPA_API_URL", "epaApiUrl") ?? fileConfig.epaApiUrl;
|
|
25
|
+
const epaApiToken = getEnvValue("EPA_API_TOKEN", "epaApiToken") ?? fileConfig.epaApiToken;
|
|
26
|
+
const openaiApiKey = getEnvValue("OPENAI_API_KEY", "openaiApiKey") ?? fileConfig.openaiApiKey;
|
|
27
|
+
const timeoutRaw = getEnvValue("EPA_API_TIMEOUT_MS", "epaApiTimeoutMs") ??
|
|
28
|
+
(fileConfig.epaApiTimeoutMs !== undefined ? String(fileConfig.epaApiTimeoutMs) : undefined);
|
|
29
|
+
const timeout = timeoutRaw ? Number(timeoutRaw) : undefined;
|
|
30
|
+
const epaApiTimeoutMs = Number.isFinite(timeout) && Number(timeout) > 0 ? Number(timeout) : undefined;
|
|
31
|
+
if (!epaApiUrl || !epaApiToken) {
|
|
32
|
+
throw new Error('Configuracao incompleta. Defina "EPA_API_URL" e "EPA_API_TOKEN" nas variaveis de ambiente ou em config/config.json.');
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
epaApiUrl,
|
|
36
|
+
epaApiToken,
|
|
37
|
+
openaiApiKey,
|
|
38
|
+
epaApiTimeoutMs
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import readline from "readline";
|
|
4
|
+
export async function setupConfig() {
|
|
5
|
+
const rl = readline.createInterface({
|
|
6
|
+
input: process.stdin,
|
|
7
|
+
output: process.stdout
|
|
8
|
+
});
|
|
9
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
10
|
+
const apiUrl = await ask("EPA API URL: ");
|
|
11
|
+
rl.close();
|
|
12
|
+
const config = {
|
|
13
|
+
epaApiUrl: apiUrl,
|
|
14
|
+
epaApiTimeoutMs: 15000
|
|
15
|
+
};
|
|
16
|
+
const configDir = path.join(process.cwd(), "config");
|
|
17
|
+
if (!fs.existsSync(configDir)) {
|
|
18
|
+
fs.mkdirSync(configDir);
|
|
19
|
+
}
|
|
20
|
+
const configPath = path.join(configDir, "config.json");
|
|
21
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
22
|
+
console.log("Config salvo em:", configPath);
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createTool } from "./createTool.js";
|
|
2
|
+
import { EpaApiClient } from "../api/epaApiClient.js";
|
|
3
|
+
const api = new EpaApiClient();
|
|
4
|
+
export function createReferenceTool(name, endpoint) {
|
|
5
|
+
return createTool({
|
|
6
|
+
name,
|
|
7
|
+
description: `Lista dados de referência: ${name}`,
|
|
8
|
+
handler: async () => {
|
|
9
|
+
return api.get(endpoint);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function createTool(def) {
|
|
2
|
+
return {
|
|
3
|
+
name: def.name,
|
|
4
|
+
description: def.description,
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: def.schema || {},
|
|
8
|
+
},
|
|
9
|
+
async execute(args) {
|
|
10
|
+
let parsedArgs = args ?? {};
|
|
11
|
+
if (def.validator) {
|
|
12
|
+
const parsed = def.validator.safeParse(parsedArgs);
|
|
13
|
+
if (!parsed.success) {
|
|
14
|
+
const issues = parsed.error.issues
|
|
15
|
+
.map((issue) => `${issue.path.join(".") || "args"}: ${issue.message}`)
|
|
16
|
+
.join("; ");
|
|
17
|
+
throw new Error(`Parametros invalidos para "${def.name}": ${issues}`);
|
|
18
|
+
}
|
|
19
|
+
parsedArgs = parsed.data;
|
|
20
|
+
}
|
|
21
|
+
const result = await def.handler(parsedArgs);
|
|
22
|
+
return {
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: JSON.stringify(result, null, 2)
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const OS_TIPO = [
|
|
2
|
+
{ "id": 1009, "descricao": "Abrir Lojas" },
|
|
3
|
+
{ "id": 1005, "descricao": "Apoio Tecnológico" },
|
|
4
|
+
{ "id": 1008, "descricao": "Chamados de TI" },
|
|
5
|
+
{ "id": 1003, "descricao": "Desenvolvimento de Software" },
|
|
6
|
+
{ "id": 1019, "descricao": "Engenharia Clínica" },
|
|
7
|
+
{ "id": 999, "descricao": "Gestão da Qualidade" },
|
|
8
|
+
{ "id": 1013, "descricao": "Gestão de Pessoas / DP" },
|
|
9
|
+
{ "id": 1012, "descricao": "Instalação" },
|
|
10
|
+
{ "id": 1010, "descricao": "Manutenção" },
|
|
11
|
+
{ "id": 1002, "descricao": "Manutenção Preventiva" },
|
|
12
|
+
{ "id": 1001, "descricao": "Ouvidoria" },
|
|
13
|
+
{ "id": 1020, "descricao": "Qualidade - envio de documentos" },
|
|
14
|
+
{ "id": 2, "descricao": "Reclamação" },
|
|
15
|
+
{ "id": 1000, "descricao": "Recursos Humanos" },
|
|
16
|
+
{ "id": 1015, "descricao": "Reembolso de despesas" },
|
|
17
|
+
{ "id": 1007, "descricao": "Reembolso de Despesa_" },
|
|
18
|
+
{ "id": 1018, "descricao": "Suporte Técnico de TI" },
|
|
19
|
+
{ "id": 1014, "descricao": "Teste" },
|
|
20
|
+
{ "id": 1017, "descricao": "teste" },
|
|
21
|
+
{ "id": 1016, "descricao": "teste 123" },
|
|
22
|
+
{ "id": 1, "descricao": "TI" }
|
|
23
|
+
];
|
|
24
|
+
export const OS_TIPO_SERVICO = [
|
|
25
|
+
{ "id": 1005, "descricao": "Analisar documento" },
|
|
26
|
+
{ "id": 1007, "descricao": "Formatar padrão" },
|
|
27
|
+
{ "id": 1016, "descricao": "Adicionar Campo" },
|
|
28
|
+
{ "id": 1015, "descricao": "Criar Formulário" },
|
|
29
|
+
{ "id": 1017, "descricao": "Criar Função" },
|
|
30
|
+
{ "id": 995, "descricao": "Geração de Backup" }
|
|
31
|
+
];
|
|
32
|
+
export const UNIDADE_GERENCIAL = [
|
|
33
|
+
{ "id": 18, "descricao": "DESEMPENHO PLANEJAMENTO > TI" }
|
|
34
|
+
];
|
|
35
|
+
export const RESPONSAVEL = [
|
|
36
|
+
{ "id": 1, "descricao": "suporte.simeon" },
|
|
37
|
+
{ "id": 577, "descricao": "maely.superusuario - Maely Teste Super Usuário" }
|
|
38
|
+
];
|
|
39
|
+
export const CLIENTE = [
|
|
40
|
+
{ "id": 1, "descricao": "Suporte Simeon" },
|
|
41
|
+
{ "id": 349, "descricao": "Comercial-Simeon" }
|
|
42
|
+
];
|
|
43
|
+
export const PRIORIDADE = [
|
|
44
|
+
{ "id": 6, "descricao": "Média" },
|
|
45
|
+
{ "id": 7, "descricao": "Alta" },
|
|
46
|
+
{ "id": 8, "descricao": "Baixa" }
|
|
47
|
+
];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { loadTools } from "../tools/loadTools.js";
|
|
5
|
+
import { buildToolNameMaps } from "../utils/toolNameMaps.js";
|
|
6
|
+
export async function createMcpServer() {
|
|
7
|
+
const server = new Server({
|
|
8
|
+
name: "epa-mcp",
|
|
9
|
+
version: "0.1.0"
|
|
10
|
+
}, {
|
|
11
|
+
capabilities: {
|
|
12
|
+
tools: {}
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
const tools = await loadTools();
|
|
16
|
+
const toolNames = tools.map((tool) => tool.name);
|
|
17
|
+
const { internalToExternal, externalToInternal } = buildToolNameMaps(toolNames);
|
|
18
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
19
|
+
return {
|
|
20
|
+
tools: tools.map((tool) => ({
|
|
21
|
+
name: internalToExternal.get(tool.name) ?? tool.name,
|
|
22
|
+
description: tool.description,
|
|
23
|
+
inputSchema: tool.inputSchema
|
|
24
|
+
}))
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
28
|
+
const internalName = externalToInternal.get(request.params.name) ?? request.params.name;
|
|
29
|
+
const tool = tools.find((t) => t.name === internalName);
|
|
30
|
+
if (!tool) {
|
|
31
|
+
throw new Error("Tool não encontrada");
|
|
32
|
+
}
|
|
33
|
+
return await tool.execute(request.params.arguments);
|
|
34
|
+
});
|
|
35
|
+
return server;
|
|
36
|
+
}
|
|
37
|
+
export async function startServer() {
|
|
38
|
+
const server = await createMcpServer();
|
|
39
|
+
const transport = new StdioServerTransport();
|
|
40
|
+
await server.connect(transport);
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { EpaApiClient } from "../api/epaApiClient.js";
|
|
2
|
+
import { buildDateRange } from "../utils/buildDateRange.js";
|
|
3
|
+
export class RequestService {
|
|
4
|
+
api;
|
|
5
|
+
constructor(apiClient) {
|
|
6
|
+
this.api = apiClient ?? new EpaApiClient();
|
|
7
|
+
}
|
|
8
|
+
async getTypes() {
|
|
9
|
+
return this.api.get("/epa_os/ajax.php", {
|
|
10
|
+
action: "get",
|
|
11
|
+
controller: "TipoSolicitacao"
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
async getServices(typeId) {
|
|
15
|
+
return this.api.get("/api/tipos_servico", {
|
|
16
|
+
tipo_id: typeId
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async getAssignees(term, options = {}) {
|
|
20
|
+
const params = {
|
|
21
|
+
term,
|
|
22
|
+
_type: "query",
|
|
23
|
+
q: options.q ?? term
|
|
24
|
+
};
|
|
25
|
+
if (options.unitValue !== undefined && options.unitValue !== null) {
|
|
26
|
+
params["filters[unidade_gerencial][type]"] = options.unitType ?? "pertenco";
|
|
27
|
+
params["filters[unidade_gerencial][value]"] = options.unitValue;
|
|
28
|
+
}
|
|
29
|
+
const response = await this.api.get("/api/api/usuarios/search", params);
|
|
30
|
+
if (response && Array.isArray(response.results)) {
|
|
31
|
+
return response.results;
|
|
32
|
+
}
|
|
33
|
+
return response;
|
|
34
|
+
}
|
|
35
|
+
async getClients() {
|
|
36
|
+
return this.api.get("/api/clientes");
|
|
37
|
+
}
|
|
38
|
+
async getUnits() {
|
|
39
|
+
return this.api.get("/api/unidades_gerenciais");
|
|
40
|
+
}
|
|
41
|
+
async list(filters = {}) {
|
|
42
|
+
const normalizedFilters = typeof filters === "number" ? { assigneeId: filters } : filters;
|
|
43
|
+
const params = {
|
|
44
|
+
data_inclusao: buildDateRange(normalizedFilters.period)
|
|
45
|
+
};
|
|
46
|
+
if (normalizedFilters.assigneeId) {
|
|
47
|
+
params.responsavel = normalizedFilters.assigneeId;
|
|
48
|
+
}
|
|
49
|
+
const response = await this.api.post("/api/api/os/listar", {}, params);
|
|
50
|
+
if (!response || response.length === 0) {
|
|
51
|
+
return "Nenhuma solicitacao encontrada.";
|
|
52
|
+
}
|
|
53
|
+
return response;
|
|
54
|
+
}
|
|
55
|
+
async view(id) {
|
|
56
|
+
const response = await this.api.post("/api/api/os/listar", {}, { codigo: id });
|
|
57
|
+
if (!response || response.length === 0) {
|
|
58
|
+
return `Solicitacao ${id} nao encontrada.`;
|
|
59
|
+
}
|
|
60
|
+
if (Array.isArray(response)) {
|
|
61
|
+
const found = response.find((item) => Number(item.codigo ?? item.id) === id);
|
|
62
|
+
return found ?? response[0];
|
|
63
|
+
}
|
|
64
|
+
return response;
|
|
65
|
+
}
|
|
66
|
+
async create(data) {
|
|
67
|
+
const payload = {
|
|
68
|
+
os_tipo: data.os_tipo,
|
|
69
|
+
os_tipo_servico: data.os_tipo_servico,
|
|
70
|
+
unidade_gerencial_executora: data.unidade_gerencial_executora,
|
|
71
|
+
responsavel: data.responsavel,
|
|
72
|
+
titulo: data.titulo,
|
|
73
|
+
cliente: data.cliente,
|
|
74
|
+
data_desejavel: data.data_desejavel,
|
|
75
|
+
prioridade: data.prioridade,
|
|
76
|
+
observacao: data.observacao
|
|
77
|
+
};
|
|
78
|
+
const result = await this.api.post("/api/api/solicitacao_rapida", payload);
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EpaApiClient } from "../api/epaApiClient.js";
|
|
2
|
+
export class TeamService {
|
|
3
|
+
api;
|
|
4
|
+
constructor() {
|
|
5
|
+
this.api = new EpaApiClient();
|
|
6
|
+
}
|
|
7
|
+
async teamReport(name) {
|
|
8
|
+
const requests = await this.api.get("/solicitacoes", { responsavel: name });
|
|
9
|
+
const total = requests.length;
|
|
10
|
+
const overdue = requests.filter((request) => request.vencida);
|
|
11
|
+
return {
|
|
12
|
+
desenvolvedor: name,
|
|
13
|
+
total,
|
|
14
|
+
vencidas: overdue.length,
|
|
15
|
+
solicitacoes: requests
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import mysql from "mysql2/promise";
|
|
2
|
+
import { loadSqlConfig } from "./loadSqlConfig.js";
|
|
3
|
+
export async function createSqlConnection() {
|
|
4
|
+
const sqlConfig = loadSqlConfig();
|
|
5
|
+
return mysql.createConnection({
|
|
6
|
+
host: sqlConfig.host,
|
|
7
|
+
port: sqlConfig.port,
|
|
8
|
+
database: sqlConfig.database,
|
|
9
|
+
user: sqlConfig.user,
|
|
10
|
+
password: sqlConfig.password,
|
|
11
|
+
ssl: sqlConfig.ssl ? {} : undefined
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { loadSqlConfig } from "./loadSqlConfig.js";
|
|
2
|
+
const MAX_COLUMNS_PER_TABLE = 20;
|
|
3
|
+
export async function fetchAvailableTableNames(connection) {
|
|
4
|
+
const sqlConfig = loadSqlConfig();
|
|
5
|
+
const [tableRows] = await connection.query(`
|
|
6
|
+
SELECT TABLE_NAME
|
|
7
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
8
|
+
WHERE TABLE_SCHEMA = ?
|
|
9
|
+
AND TABLE_TYPE = 'BASE TABLE'
|
|
10
|
+
ORDER BY TABLE_NAME
|
|
11
|
+
`, [sqlConfig.database]);
|
|
12
|
+
return tableRows.map((row) => row.TABLE_NAME);
|
|
13
|
+
}
|
|
14
|
+
export async function fetchSchemaSummary(connection, tableNames) {
|
|
15
|
+
const sqlConfig = loadSqlConfig();
|
|
16
|
+
const placeholders = tableNames.map(() => "?").join(", ");
|
|
17
|
+
const [columnRows] = await connection.query(`
|
|
18
|
+
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE
|
|
19
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
20
|
+
WHERE TABLE_SCHEMA = ?
|
|
21
|
+
AND TABLE_NAME IN (${placeholders})
|
|
22
|
+
ORDER BY TABLE_NAME, ORDINAL_POSITION
|
|
23
|
+
`, [sqlConfig.database, ...tableNames]);
|
|
24
|
+
const columnsByTable = new Map();
|
|
25
|
+
for (const row of columnRows) {
|
|
26
|
+
const columns = columnsByTable.get(row.TABLE_NAME) ?? [];
|
|
27
|
+
if (columns.length < MAX_COLUMNS_PER_TABLE) {
|
|
28
|
+
columns.push(`${row.COLUMN_NAME} (${row.DATA_TYPE})`);
|
|
29
|
+
}
|
|
30
|
+
columnsByTable.set(row.TABLE_NAME, columns);
|
|
31
|
+
}
|
|
32
|
+
return tableNames
|
|
33
|
+
.map((tableName) => {
|
|
34
|
+
const columns = columnsByTable.get(tableName) ?? [];
|
|
35
|
+
return `Tabela ${tableName}: ${columns.join(", ")}`;
|
|
36
|
+
})
|
|
37
|
+
.join("\n");
|
|
38
|
+
}
|