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,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="WEB_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager">
|
|
4
|
+
<content url="file://$MODULE_DIR$" />
|
|
5
|
+
<orderEntry type="inheritedJdk" />
|
|
6
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
7
|
+
</component>
|
|
8
|
+
</module>
|
package/.idea/php.xml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="MessDetectorOptionsConfiguration">
|
|
4
|
+
<option name="transferred" value="true" />
|
|
5
|
+
</component>
|
|
6
|
+
<component name="PHPCSFixerOptionsConfiguration">
|
|
7
|
+
<option name="transferred" value="true" />
|
|
8
|
+
</component>
|
|
9
|
+
<component name="PHPCodeSnifferOptionsConfiguration">
|
|
10
|
+
<option name="highlightLevel" value="WARNING" />
|
|
11
|
+
<option name="transferred" value="true" />
|
|
12
|
+
</component>
|
|
13
|
+
<component name="PhpStanOptionsConfiguration">
|
|
14
|
+
<option name="transferred" value="true" />
|
|
15
|
+
</component>
|
|
16
|
+
<component name="PsalmOptionsConfiguration">
|
|
17
|
+
<option name="transferred" value="true" />
|
|
18
|
+
</component>
|
|
19
|
+
</project>
|
package/.idea/vcs.xml
ADDED
package/AGENTS.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
## Documentation policy
|
|
4
|
+
|
|
5
|
+
- Sempre que houver mudanca importante no projeto (novas tools, renomeacao de contratos publicos, mudanca de comandos, fluxo de setup/execucao/teste), atualize o `README.md` no mesmo trabalho.
|
|
6
|
+
- Nao finalize alteracoes que impactem uso do projeto sem revisar consistencia entre codigo e README.
|
|
7
|
+
- Priorize manter no README:
|
|
8
|
+
- comandos reais do `package.json`
|
|
9
|
+
- nomes atuais das tools MCP
|
|
10
|
+
- pre-requisitos de configuracao
|
package/README.md
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# EPA MCP
|
|
2
|
+
|
|
3
|
+
Servidor MCP (Model Context Protocol) para integracao do sistema EPA com modelos de linguagem (LLMs).
|
|
4
|
+
|
|
5
|
+
Este projeto permite que assistentes de IA interajam com o EPA por meio de ferramentas estruturadas.
|
|
6
|
+
O objetivo e permitir que usuarios utilizem linguagem natural para operar o sistema EPA.
|
|
7
|
+
|
|
8
|
+
## Arquitetura
|
|
9
|
+
|
|
10
|
+
Fluxo principal:
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
Tool -> Service -> API Client -> EPA API
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Camadas:
|
|
17
|
+
|
|
18
|
+
- CLI: interface de execucao local
|
|
19
|
+
- Server: servidor MCP
|
|
20
|
+
- Tools: ferramentas expostas ao agente
|
|
21
|
+
- Services: regras de negocio
|
|
22
|
+
- API Client: comunicacao com a API do EPA
|
|
23
|
+
|
|
24
|
+
## Estrutura do projeto
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
src/
|
|
28
|
+
agent/
|
|
29
|
+
api/
|
|
30
|
+
cli/
|
|
31
|
+
config/
|
|
32
|
+
core/
|
|
33
|
+
services/
|
|
34
|
+
tools/
|
|
35
|
+
tests/
|
|
36
|
+
utils/
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Tools ativas
|
|
40
|
+
|
|
41
|
+
Solicitacoes:
|
|
42
|
+
|
|
43
|
+
- `requests.list`
|
|
44
|
+
|
|
45
|
+
Dados auxiliares:
|
|
46
|
+
|
|
47
|
+
- `requests.types`
|
|
48
|
+
- `requests.priorities`
|
|
49
|
+
- `requests.assignees`
|
|
50
|
+
|
|
51
|
+
## Endpoints reais configurados
|
|
52
|
+
|
|
53
|
+
- `requests.types`
|
|
54
|
+
- `GET /epa_os/ajax.php`
|
|
55
|
+
- Query params: `action=get`, `controller=TipoSolicitacao`
|
|
56
|
+
- `requests.priorities`
|
|
57
|
+
- `GET /api/api/prioridade`
|
|
58
|
+
- `requests.assignees`
|
|
59
|
+
- `GET /api/api/usuarios/search`
|
|
60
|
+
- Query params obrigatorios: `term`, `_type=query`, `q`
|
|
61
|
+
- Filtro opcional preparado: `filters[unidade_gerencial][type]=pertenco` e `filters[unidade_gerencial][value]=<id>`
|
|
62
|
+
|
|
63
|
+
### requests.list (filtros recomendados)
|
|
64
|
+
|
|
65
|
+
- `assignee_id` (opcional): ID do responsavel
|
|
66
|
+
- `assignee_name` (opcional): nome/login (ex: `suporte.simeon`)
|
|
67
|
+
- `period` (opcional): `current_year` (padrao) ou `last_12_months`
|
|
68
|
+
|
|
69
|
+
Exemplo para "listar as solicitacoes do usuario suporte.simeon do ultimo ano":
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"assignee_name": "suporte.simeon",
|
|
74
|
+
"period": "last_12_months"
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Instalacao
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm install
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Configuracao
|
|
85
|
+
|
|
86
|
+
Para clientes MCP (ex: Claude Desktop), configure as variaveis de ambiente:
|
|
87
|
+
|
|
88
|
+
- `epaApiUrl` (ou `EPA_API_URL`)
|
|
89
|
+
- `epaApiToken` (ou `EPA_API_TOKEN`)
|
|
90
|
+
- `epaApiTimeoutMs` (ou `EPA_API_TIMEOUT_MS`, opcional)
|
|
91
|
+
|
|
92
|
+
Opcionalmente, voce pode usar arquivo local `config/config.json`.
|
|
93
|
+
|
|
94
|
+
Exemplo:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"epaApiUrl": "https://dev.sysepa.com.br/epa",
|
|
99
|
+
"epaApiToken": "SEU_TOKEN",
|
|
100
|
+
"epaApiTimeoutMs": 15000
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Para uso da CLI, voce tambem pode executar o setup:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npm run dev setup
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Esse comando configura a URL da API. Para login/token EPA da CLI:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npm run dev login
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Os dados sao salvos em `.env` (se existir) ou `config/config.json`.
|
|
117
|
+
|
|
118
|
+
Arquivos de configuracao usados:
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
config/config.json
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Execucao
|
|
125
|
+
|
|
126
|
+
Iniciar MCP Server em desenvolvimento:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npm run dev server
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Iniciar agente OpenAI conectado ao MCP (CLI):
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npm run dev agent
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Ao iniciar o agent, a CLI valida se URL/token EPA estao configurados e se o token ainda e valido.
|
|
139
|
+
Se estiver ausente/invalido, ela solicita login e senha para gerar novo token automaticamente.
|
|
140
|
+
|
|
141
|
+
No Windows (PowerShell com policy restritiva), use:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npm.cmd run dev agent
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Se o ambiente bloquear `spawn` (erro `EPERM`, comum em ambientes restritos), use:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npm run build
|
|
151
|
+
node dist/cli/index.js agent
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Build de producao:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
npm run build
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Executar CLI compilada:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npm run start -- server
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Testes
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
npm test
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## SQL tecnico via CLI
|
|
173
|
+
|
|
174
|
+
Para validar consultas tecnicas em MySQL/MariaDB de forma isolada do MCP padrao, a CLI suporta um modo read-only.
|
|
175
|
+
|
|
176
|
+
Variaveis necessarias:
|
|
177
|
+
|
|
178
|
+
- `DB_HOST`
|
|
179
|
+
- `DB_PORT` (opcional, padrao `3306`)
|
|
180
|
+
- `DB_NAME`
|
|
181
|
+
- `DB_USER`
|
|
182
|
+
- `DB_PASSWORD`
|
|
183
|
+
- `DB_SSL` (opcional, `true` ou `false`)
|
|
184
|
+
|
|
185
|
+
Alternativamente, essas credenciais podem ficar em `config/config.json`:
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"epaApiUrl": "https://dev.sysepa.com.br/epa",
|
|
190
|
+
"epaApiToken": "SEU_TOKEN",
|
|
191
|
+
"epaApiTimeoutMs": 15000,
|
|
192
|
+
"sql": {
|
|
193
|
+
"host": "127.0.0.1",
|
|
194
|
+
"port": 3306,
|
|
195
|
+
"database": "nome_do_banco",
|
|
196
|
+
"user": "usuario",
|
|
197
|
+
"password": "senha",
|
|
198
|
+
"ssl": false
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Exemplo:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
npm run dev sql "SELECT * FROM sua_tabela LIMIT 10"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Para validar a ideia de linguagem natural com apoio da LLM:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
npm run dev sql-agent "quero listar os usuarios"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Voce tambem pode orientar melhor a geracao com pistas explicitas, por exemplo:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
npm run dev sql-agent "quero listar os planos de acoes tabela: iniciativa5w2h e trazer nome do usuario que e o campo: quem e deve vincular com tabela clientes"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Tambem e possivel informar filtros, ordenacao e limite explicitamente:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
npm run dev sql-agent "listar usuarios tabela: clientes filtro: ativo = 1 ordenar por: nome asc limite: 20"
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Nesse modo, a CLI:
|
|
228
|
+
|
|
229
|
+
- le um resumo do schema do banco
|
|
230
|
+
- tenta identificar as tabelas mais provaveis para a pergunta
|
|
231
|
+
- respeita pistas explicitas como `tabela:`, `campo:` e `vincular com tabela`
|
|
232
|
+
- respeita pistas explicitas como `filtro:`, `ordenar por:` e `limite:`
|
|
233
|
+
- quando houver ambiguidade, pergunta qual tabela deve ser usada
|
|
234
|
+
- se a tabela correta nao aparecer na lista, permite informar o nome manualmente
|
|
235
|
+
- pede para a LLM montar um plano antes da SQL
|
|
236
|
+
- permite aprovar, corrigir ou cancelar esse plano
|
|
237
|
+
- so depois gera a SQL final
|
|
238
|
+
- valida a SQL (somente `SELECT`)
|
|
239
|
+
- executa a consulta
|
|
240
|
+
- mostra a pergunta, a SQL gerada e o resultado
|
|
241
|
+
|
|
242
|
+
Se a consulta nao for informada no comando, a CLI pede interativamente.
|
|
243
|
+
|
|
244
|
+
Regras atuais desse modo:
|
|
245
|
+
|
|
246
|
+
- aceita somente `SELECT`
|
|
247
|
+
- bloqueia comandos de escrita/DDL
|
|
248
|
+
- aceita apenas uma consulta por vez
|
|
249
|
+
|
|
250
|
+
## Uso do Agent CLI
|
|
251
|
+
|
|
252
|
+
1. Instale dependencias:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
npm install
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
2. Configure credenciais:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
npm run dev setup
|
|
262
|
+
npm run dev login
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
No fluxo de login da CLI, informe:
|
|
266
|
+
|
|
267
|
+
- login EPA
|
|
268
|
+
- senha EPA
|
|
269
|
+
- OpenAI API KEY (somente para CLI/agent, se ainda nao estiver definida)
|
|
270
|
+
|
|
271
|
+
3. Inicie o agent:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
npm run dev agent
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
4. Interaja no terminal com linguagem natural. Exemplos:
|
|
278
|
+
|
|
279
|
+
- `liste minhas solicitacoes`
|
|
280
|
+
- `quais sao os tipos disponiveis?`
|
|
281
|
+
- `crie uma solicitacao para ...`
|
|
282
|
+
|
|
283
|
+
O agent escolhe e executa automaticamente as tools MCP (`requests.list`, `requests.types`, `requests.create`, etc.) conforme o contexto da conversa.
|
|
284
|
+
|
|
285
|
+
Observacao de contexto:
|
|
286
|
+
|
|
287
|
+
- O agent usa janela deslizante para limitar o historico enviado ao modelo.
|
|
288
|
+
- Valor padrao: `30` mensagens nao-system.
|
|
289
|
+
- Para ajustar: defina a variavel `AGENT_MAX_CONTEXT_MESSAGES`.
|
|
290
|
+
|
|
291
|
+
## Uso com Claude Desktop
|
|
292
|
+
|
|
293
|
+
### Local (sem publicar no npm)
|
|
294
|
+
|
|
295
|
+
1. Gere o build:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
npm run build
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
2. Configure o `claude_desktop_config.json` para executar o entrypoint MCP direto:
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
{
|
|
305
|
+
"mcpServers": {
|
|
306
|
+
"epa-local": {
|
|
307
|
+
"command": "node",
|
|
308
|
+
"args": [
|
|
309
|
+
"C:\\Users\\renat\\OneDrive\\Documentos\\projetos\\epa_mcp\\dist\\server\\stdioServer.js"
|
|
310
|
+
],
|
|
311
|
+
"env": {
|
|
312
|
+
"epaApiUrl": "https://dev.sysepa.com.br/epa",
|
|
313
|
+
"epaApiToken": "SEU_TOKEN",
|
|
314
|
+
"epaApiTimeoutMs": "15000"
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
3. Reinicie o Claude Desktop.
|
|
322
|
+
|
|
323
|
+
### Publicado no npm
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
npx epa-mcp
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
CLI como extra (opcional):
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
npx epa-mcp-cli setup
|
|
333
|
+
npx epa-mcp-cli agent
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Observacoes
|
|
337
|
+
|
|
338
|
+
- Os textos de prompt e mensagens do projeto permanecem em portugues.
|
|
339
|
+
- Os identificadores tecnicos (arquivos, diretorios, classes e nomes de tools) seguem padrao em ingles.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { buildToolNameMaps as buildBaseToolNameMaps } from "../utils/toolNameMaps.js";
|
|
2
|
+
export function buildToolNameMaps(toolNames) {
|
|
3
|
+
const { internalToExternal, externalToInternal } = buildBaseToolNameMaps(toolNames);
|
|
4
|
+
return {
|
|
5
|
+
mcpToOpenAi: internalToExternal,
|
|
6
|
+
openAiToMcp: externalToInternal
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function isSpawnPermissionError(error) {
|
|
10
|
+
if (!(error instanceof Error)) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const maybeCode = error.code;
|
|
14
|
+
return maybeCode === "EPERM" && error.message.includes("spawn");
|
|
15
|
+
}
|
|
16
|
+
export function getErrorMessage(error) {
|
|
17
|
+
if (error instanceof Error) {
|
|
18
|
+
return error.message;
|
|
19
|
+
}
|
|
20
|
+
return String(error);
|
|
21
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import readline from "readline";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
7
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
8
|
+
import { loadConfig } from "../config/loadConfig.js";
|
|
9
|
+
import { ensureCliAuth } from "../config/ensureCliAuth.js";
|
|
10
|
+
import { createMcpServer } from "../server/server.js";
|
|
11
|
+
import { applySlidingWindow } from "./slidingWindow.js";
|
|
12
|
+
import { buildToolNameMaps, getErrorMessage, isSpawnPermissionError } from "./agentHelpers.js";
|
|
13
|
+
const DEFAULT_MAX_NON_SYSTEM_MESSAGES = 30;
|
|
14
|
+
export async function startAgent() {
|
|
15
|
+
await ensureCliAuth();
|
|
16
|
+
const config = loadConfig();
|
|
17
|
+
const apiKey = process.env.OPENAI_API_KEY || config.openaiApiKey;
|
|
18
|
+
if (!apiKey) {
|
|
19
|
+
throw new Error("OpenAI API key nao encontrada. Defina OPENAI_API_KEY ou configure via setup.");
|
|
20
|
+
}
|
|
21
|
+
const openai = new OpenAI({ apiKey });
|
|
22
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
23
|
+
const isDevRuntime = currentFilePath.endsWith(".ts");
|
|
24
|
+
const cliEntry = path.resolve(path.dirname(currentFilePath), "..", "cli", isDevRuntime ? "index.ts" : "index.js");
|
|
25
|
+
const client = new Client({
|
|
26
|
+
name: "epa-agent",
|
|
27
|
+
version: "0.1.0"
|
|
28
|
+
}, { capabilities: {} });
|
|
29
|
+
try {
|
|
30
|
+
const transport = new StdioClientTransport(isDevRuntime
|
|
31
|
+
? {
|
|
32
|
+
command: process.execPath,
|
|
33
|
+
args: ["--import", "tsx", cliEntry, "server"]
|
|
34
|
+
}
|
|
35
|
+
: {
|
|
36
|
+
command: process.execPath,
|
|
37
|
+
args: [cliEntry, "server"]
|
|
38
|
+
});
|
|
39
|
+
await client.connect(transport);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (!isSpawnPermissionError(error)) {
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
46
|
+
const server = await createMcpServer();
|
|
47
|
+
await server.connect(serverTransport);
|
|
48
|
+
await client.connect(clientTransport);
|
|
49
|
+
console.log("Aviso: modo in-memory ativado por restricao de spawn no ambiente.");
|
|
50
|
+
}
|
|
51
|
+
const toolsResponse = await client.listTools();
|
|
52
|
+
const toolNames = toolsResponse.tools.map((tool) => tool.name);
|
|
53
|
+
const { mcpToOpenAi, openAiToMcp } = buildToolNameMaps(toolNames);
|
|
54
|
+
const tools = toolsResponse.tools.map((tool) => ({
|
|
55
|
+
type: "function",
|
|
56
|
+
function: {
|
|
57
|
+
name: mcpToOpenAi.get(tool.name) ?? tool.name,
|
|
58
|
+
description: tool.description,
|
|
59
|
+
parameters: tool.inputSchema
|
|
60
|
+
}
|
|
61
|
+
}));
|
|
62
|
+
const rl = readline.createInterface({
|
|
63
|
+
input: process.stdin,
|
|
64
|
+
output: process.stdout
|
|
65
|
+
});
|
|
66
|
+
console.log("EPA Agent iniciado");
|
|
67
|
+
const messages = [
|
|
68
|
+
{
|
|
69
|
+
role: "system",
|
|
70
|
+
content: `
|
|
71
|
+
Voce e um assistente que opera o sistema EPA.
|
|
72
|
+
|
|
73
|
+
Sempre utilize as ferramentas disponiveis quando necessario.
|
|
74
|
+
|
|
75
|
+
Fluxo recomendado para criar solicitacoes:
|
|
76
|
+
|
|
77
|
+
1. consultar requests.types
|
|
78
|
+
2. consultar requests.services
|
|
79
|
+
3. consultar requests.priorities/requests.assignees/requests.clients
|
|
80
|
+
4. executar requests.create
|
|
81
|
+
|
|
82
|
+
Para listar solicitacoes por usuario e periodo, prefira requests.list com:
|
|
83
|
+
- assignee_name (ex: suporte.simeon)
|
|
84
|
+
- period=last_12_months para "ultimo ano"
|
|
85
|
+
|
|
86
|
+
Regra para analise:
|
|
87
|
+
- Se voce ja recebeu uma lista de solicitacoes no contexto atual, faca a analise diretamente desses dados.
|
|
88
|
+
- Nao chame analytics.teamReport para analisar dados que ja estao no contexto.
|
|
89
|
+
- So use analytics.teamReport quando o usuario pedir explicitamente para buscar analise de um usuario especifico ou da equipe (ex: "analise da equipe", "relatorio do usuario X").
|
|
90
|
+
- Se analytics.teamReport nao for necessario, responda sem chamar tools.
|
|
91
|
+
`
|
|
92
|
+
}
|
|
93
|
+
];
|
|
94
|
+
const configuredMax = Number(process.env.AGENT_MAX_CONTEXT_MESSAGES);
|
|
95
|
+
const maxNonSystemMessages = Number.isFinite(configuredMax) && configuredMax > 0
|
|
96
|
+
? configuredMax
|
|
97
|
+
: DEFAULT_MAX_NON_SYSTEM_MESSAGES;
|
|
98
|
+
rl.on("line", async (input) => {
|
|
99
|
+
try {
|
|
100
|
+
messages.push({
|
|
101
|
+
role: "user",
|
|
102
|
+
content: input
|
|
103
|
+
});
|
|
104
|
+
applySlidingWindow(messages, maxNonSystemMessages);
|
|
105
|
+
let running = true;
|
|
106
|
+
while (running) {
|
|
107
|
+
const completion = await openai.chat.completions.create({
|
|
108
|
+
model: "gpt-4o-mini",
|
|
109
|
+
messages,
|
|
110
|
+
tools,
|
|
111
|
+
tool_choice: "auto"
|
|
112
|
+
});
|
|
113
|
+
const message = completion.choices[0].message;
|
|
114
|
+
messages.push(message);
|
|
115
|
+
applySlidingWindow(messages, maxNonSystemMessages);
|
|
116
|
+
if (!message.tool_calls) {
|
|
117
|
+
console.log("\n[agent]", message.content, "\n");
|
|
118
|
+
running = false;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
for (const toolCall of message.tool_calls) {
|
|
122
|
+
const openAiToolName = toolCall.function.name;
|
|
123
|
+
const toolName = openAiToMcp.get(openAiToolName) ?? openAiToolName;
|
|
124
|
+
let args;
|
|
125
|
+
try {
|
|
126
|
+
args = JSON.parse(toolCall.function.arguments || "{}");
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
const errorMessage = getErrorMessage(error);
|
|
130
|
+
console.error(`\n[erro] Argumentos invalidos para ${toolName}: ${errorMessage}\n`);
|
|
131
|
+
messages.push({
|
|
132
|
+
role: "tool",
|
|
133
|
+
tool_call_id: toolCall.id,
|
|
134
|
+
content: JSON.stringify([{ type: "text", text: `Erro: argumentos invalidos (${errorMessage})` }])
|
|
135
|
+
});
|
|
136
|
+
applySlidingWindow(messages, maxNonSystemMessages);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
console.log(`\n[tool] Executando: ${toolName}`);
|
|
140
|
+
try {
|
|
141
|
+
const result = await client.callTool({
|
|
142
|
+
name: toolName,
|
|
143
|
+
arguments: args
|
|
144
|
+
});
|
|
145
|
+
messages.push({
|
|
146
|
+
role: "tool",
|
|
147
|
+
tool_call_id: toolCall.id,
|
|
148
|
+
content: JSON.stringify(result.content)
|
|
149
|
+
});
|
|
150
|
+
applySlidingWindow(messages, maxNonSystemMessages);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
const errorMessage = getErrorMessage(error);
|
|
154
|
+
console.error(`\n[erro] Falha ao executar ${toolName}: ${errorMessage}\n`);
|
|
155
|
+
messages.push({
|
|
156
|
+
role: "tool",
|
|
157
|
+
tool_call_id: toolCall.id,
|
|
158
|
+
content: JSON.stringify([{ type: "text", text: `Erro ao executar tool: ${errorMessage}` }])
|
|
159
|
+
});
|
|
160
|
+
applySlidingWindow(messages, maxNonSystemMessages);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
const errorMessage = getErrorMessage(error);
|
|
167
|
+
console.error(`\n[erro] Falha no processamento da mensagem: ${errorMessage}\n`);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function applySlidingWindow(messages, maxNonSystemMessages) {
|
|
2
|
+
if (!Number.isFinite(maxNonSystemMessages) || maxNonSystemMessages < 1) {
|
|
3
|
+
return;
|
|
4
|
+
}
|
|
5
|
+
const systemMessage = messages.find((message) => message?.role === "system");
|
|
6
|
+
const nonSystemMessages = messages.filter((message) => message?.role !== "system");
|
|
7
|
+
const trimmedNonSystem = nonSystemMessages.slice(-maxNonSystemMessages);
|
|
8
|
+
messages.length = 0;
|
|
9
|
+
if (systemMessage) {
|
|
10
|
+
messages.push(systemMessage);
|
|
11
|
+
}
|
|
12
|
+
messages.push(...trimmedNonSystem);
|
|
13
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { loadConfig } from "../config/loadConfig.js";
|
|
3
|
+
export class EpaApiClient {
|
|
4
|
+
client;
|
|
5
|
+
constructor() {
|
|
6
|
+
const config = loadConfig();
|
|
7
|
+
const timeoutMs = Number(config.epaApiTimeoutMs ??
|
|
8
|
+
process.env.EPA_API_TIMEOUT_MS ??
|
|
9
|
+
15000);
|
|
10
|
+
this.client = axios.create({
|
|
11
|
+
baseURL: config.epaApiUrl,
|
|
12
|
+
timeout: Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 15000,
|
|
13
|
+
headers: {
|
|
14
|
+
Authorization: `Bearer ${config.epaApiToken}`
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async get(endpoint, params) {
|
|
19
|
+
try {
|
|
20
|
+
const response = await this.client.get(endpoint, { params });
|
|
21
|
+
return response.data;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw this.formatError(error, "GET", endpoint);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async post(endpoint, body = {}, params) {
|
|
28
|
+
try {
|
|
29
|
+
const response = await this.client.post(endpoint, body, { params });
|
|
30
|
+
return response.data;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw this.formatError(error, "POST", endpoint);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
formatError(error, method, endpoint) {
|
|
37
|
+
if (axios.isAxiosError(error)) {
|
|
38
|
+
const status = error.response?.status;
|
|
39
|
+
const apiMessage = this.extractApiMessage(error.response?.data);
|
|
40
|
+
const suffix = status ? `status ${status}` : "sem status HTTP";
|
|
41
|
+
return new Error(`[EPA API] ${method} ${endpoint} falhou (${suffix}): ${apiMessage}`);
|
|
42
|
+
}
|
|
43
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
44
|
+
return new Error(`[EPA API] ${method} ${endpoint} falhou: ${message}`);
|
|
45
|
+
}
|
|
46
|
+
extractApiMessage(data) {
|
|
47
|
+
if (typeof data === "string" && data.trim()) {
|
|
48
|
+
return data;
|
|
49
|
+
}
|
|
50
|
+
if (data && typeof data === "object") {
|
|
51
|
+
const maybeMessage = data.message ?? data.error;
|
|
52
|
+
if (typeof maybeMessage === "string" && maybeMessage.trim()) {
|
|
53
|
+
return maybeMessage;
|
|
54
|
+
}
|
|
55
|
+
return JSON.stringify(data);
|
|
56
|
+
}
|
|
57
|
+
return "erro sem detalhes retornados pela API";
|
|
58
|
+
}
|
|
59
|
+
}
|