jaylog-mcp 0.1.1 → 0.1.2
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 +59 -13
- package/dist/api.js +24 -6
- package/dist/auth/login.js +2 -2
- package/dist/tools/services.js +39 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,21 +6,40 @@ agendamento de tarefas, contra a API do Jaylog.
|
|
|
6
6
|
|
|
7
7
|
> **Escopo inicial:** apenas ferramentas de leitura (GET). Nenhuma tool cria, altera ou apaga dados.
|
|
8
8
|
|
|
9
|
+
## Sumário
|
|
10
|
+
|
|
11
|
+
- [Instalação](#instalação)
|
|
12
|
+
- [Autenticação](#autenticação)
|
|
13
|
+
- [Configuração](#configuração)
|
|
14
|
+
- [Desenvolvimento](#desenvolvimento)
|
|
15
|
+
- [Tools](#tools)
|
|
16
|
+
- [Logs, métricas e recursos](#logs-métricas-e-recursos)
|
|
17
|
+
- [Serviços](#serviços)
|
|
18
|
+
- [Agendamento de tarefas](#agendamento-de-tarefas)
|
|
19
|
+
- [Usuários chave](#usuários-chave)
|
|
20
|
+
- [Erros / Issues](#erros--issues)
|
|
21
|
+
- [Skills](#skills)
|
|
22
|
+
- [Estrutura](#estrutura)
|
|
23
|
+
- [Debugging](#debugging)
|
|
24
|
+
|
|
9
25
|
## Instalação
|
|
10
26
|
|
|
11
27
|
Requer Node.js 18+. Não precisa instalar nada: o `npx` baixa e executa o pacote.
|
|
12
28
|
|
|
13
29
|
```bash
|
|
14
|
-
export
|
|
15
|
-
npx -y jaylog-mcp
|
|
30
|
+
export JAYLOG_URL=https://seu-app-jaylog # URL do app web do Jaylog
|
|
31
|
+
npx -y jaylog-mcp login # abre o navegador para autenticar
|
|
16
32
|
```
|
|
17
33
|
|
|
34
|
+
> Uma única URL basta: o MCP chama a API pelo proxy do app web (`<JAYLOG_URL>/proxy/*`). Para
|
|
35
|
+
> apontar a API diretamente (ex.: desenvolvimento local), veja [Configuração](#configuração).
|
|
36
|
+
|
|
18
37
|
Depois do login, registre o servidor no seu cliente MCP.
|
|
19
38
|
|
|
20
39
|
**Claude Code:**
|
|
21
40
|
|
|
22
41
|
```bash
|
|
23
|
-
claude mcp add jaylog --env
|
|
42
|
+
claude mcp add jaylog --env JAYLOG_URL=https://seu-app-jaylog -- npx -y jaylog-mcp
|
|
24
43
|
```
|
|
25
44
|
|
|
26
45
|
**Outros clientes (Claude Desktop, Cursor etc.):**
|
|
@@ -30,9 +49,9 @@ claude mcp add jaylog --env JAYLOG_API_BASE_URL=https://sua-api-jaylog -- npx -y
|
|
|
30
49
|
"mcpServers": {
|
|
31
50
|
"jaylog": {
|
|
32
51
|
"command": "npx",
|
|
33
|
-
"args": ["-y", "jaylog-mcp
|
|
52
|
+
"args": ["-y", "jaylog-mcp"],
|
|
34
53
|
"env": {
|
|
35
|
-
"
|
|
54
|
+
"JAYLOG_URL": "https://seu-app-jaylog"
|
|
36
55
|
}
|
|
37
56
|
}
|
|
38
57
|
}
|
|
@@ -44,7 +63,7 @@ claude mcp add jaylog --env JAYLOG_API_BASE_URL=https://sua-api-jaylog -- npx -y
|
|
|
44
63
|
|
|
45
64
|
## Autenticação
|
|
46
65
|
|
|
47
|
-
`npx -y jaylog-mcp
|
|
66
|
+
`npx -y jaylog-mcp login` abre o navegador numa tela de consentimento do app web do Jaylog
|
|
48
67
|
(`/mcp/authorize`) — o mesmo padrão do `claude login`/`gh auth login`: o MCP sobe um servidor HTTP
|
|
49
68
|
local efêmero (`127.0.0.1:<porta>`), o navegador confirma sua identidade (sessão já logada ou
|
|
50
69
|
login + 2FA) e, ao clicar em "Permitir", o Jaylog emite um token pessoal que volta pro MCP via esse
|
|
@@ -57,16 +76,17 @@ callback local.
|
|
|
57
76
|
- `logout` apenas apaga a credencial local; para invalidar o token de fato, revogue-o no app.
|
|
58
77
|
|
|
59
78
|
```bash
|
|
60
|
-
npx -y jaylog-mcp
|
|
61
|
-
npx -y jaylog-mcp
|
|
79
|
+
npx -y jaylog-mcp login # autentica (abre o navegador)
|
|
80
|
+
npx -y jaylog-mcp logout # remove a credencial local
|
|
62
81
|
```
|
|
63
82
|
|
|
64
83
|
## Configuração
|
|
65
84
|
|
|
66
85
|
| Variável | Descrição |
|
|
67
86
|
|---|---|
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
87
|
+
| `JAYLOG_URL` | URL do app web do Jaylog. É a única variável necessária: o `login` abre `<JAYLOG_URL>/mcp/authorize` e as tools chamam a API em `<JAYLOG_URL>/proxy/*`. |
|
|
88
|
+
| `JAYLOG_API_BASE_URL` | Override opcional: URL base da API chamada diretamente, sem passar pelo proxy (ex: `http://localhost:3000`). Quando definida, tem prioridade sobre `JAYLOG_URL` nas chamadas à API. |
|
|
89
|
+
| `JAYLOG_FRONTEND_URL` | Override opcional: URL do app web usada só pelo `login`. Útil em desenvolvimento local, onde a API e o app web rodam em portas diferentes. Se omitida, cai para `JAYLOG_URL`. |
|
|
70
90
|
| `JAYLOG_API_TOKEN` | Override explícito: se definida, tem prioridade sobre a credencial salva pelo `login`. Útil para CI/scripting. Qualquer perfil (guest, standard, staff, admin) funciona — cada tool exige o perfil mínimo que a rota correspondente já exige na API. |
|
|
71
91
|
|
|
72
92
|
## Desenvolvimento
|
|
@@ -75,7 +95,7 @@ Para rodar a partir do código-fonte (requer [Bun](https://bun.sh)):
|
|
|
75
95
|
|
|
76
96
|
```bash
|
|
77
97
|
bun install
|
|
78
|
-
cp .env.example .env # edite com
|
|
98
|
+
cp .env.example .env # edite com as URLs do app web/API
|
|
79
99
|
bun run login # autentica (abre o navegador)
|
|
80
100
|
bun run start # roda direto com Bun
|
|
81
101
|
bun run dev # com --watch
|
|
@@ -97,7 +117,9 @@ bun run build # compila para dist/ (uso via Node/bin)
|
|
|
97
117
|
|
|
98
118
|
### Serviços
|
|
99
119
|
|
|
100
|
-
- **`list-services`**: lista serviços (aplicações/bots que enviam logs), com
|
|
120
|
+
- **`list-services`**: lista serviços (aplicações/bots que enviam logs), com empresa e setor resolvidos. O `owner` vem como ID de usuário.
|
|
121
|
+
- **`list-favorite-services`**: serviços favoritados pelo usuário autenticado (os favoritos são sempre do usuário logado no MCP).
|
|
122
|
+
- **`search-services-by-owner`**: busca serviços pelo nome e/ou e-mail do dono (substring, sem diferenciar maiúsculas), retornando um item por usuário encontrado com seus serviços. Requer perfil `standard` ou superior.
|
|
101
123
|
|
|
102
124
|
### Agendamento de tarefas
|
|
103
125
|
|
|
@@ -113,9 +135,33 @@ bun run build # compila para dist/ (uso via Node/bin)
|
|
|
113
135
|
- **`list-issues`**: issues (bugs/erros) abertos manualmente contra um serviço, com autor e responsável resolvidos.
|
|
114
136
|
- **`get-issue`**: uma issue específica por ID.
|
|
115
137
|
|
|
138
|
+
## Skills
|
|
139
|
+
|
|
140
|
+
O repositório inclui skills (pasta `skills/`) com fluxos prontos que usam as tools acima. Elas
|
|
141
|
+
seguem o padrão do [skills.sh](https://skills.sh) e funcionam em Claude Code, Cursor e outros
|
|
142
|
+
agentes compatíveis. Requerem o MCP configurado e autenticado.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
npx skills add Gpocas/jaylog-mcp # instala todas
|
|
146
|
+
npx skills add Gpocas/jaylog-mcp --skill jaylog-incident-report # instala uma
|
|
147
|
+
npx skills add Gpocas/jaylog-mcp -g # global (~/.claude/skills)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
- **`jaylog-incident-report`**: relatório em markdown de incidentes (erros nos logs, serviços
|
|
151
|
+
parados, agendamentos não cumpridos e issues). Começa com uma entrevista de múltipla escolha
|
|
152
|
+
para personalizar escopo (todos, favoritos ou serviços de um owner), categorias, período
|
|
153
|
+
(padrão: dia anterior) e extras (contatos, mensagens completas, só críticos). Salva em
|
|
154
|
+
`./reports/incidentes-AAAA-MM-DD.md`.
|
|
155
|
+
- **`jaylog-fix-service-errors`**: levanta os erros recentes de um serviço (escolhido por nome,
|
|
156
|
+
owner ou entre os favoritos), localiza o código responsável na pasta atual e **propõe**
|
|
157
|
+
correções (não edita arquivos).
|
|
158
|
+
|
|
116
159
|
## Estrutura
|
|
117
160
|
|
|
118
161
|
```
|
|
162
|
+
skills/
|
|
163
|
+
├── jaylog-incident-report/SKILL.md
|
|
164
|
+
└── jaylog-fix-service-errors/SKILL.md
|
|
119
165
|
src/
|
|
120
166
|
├── index.ts → bootstrap do McpServer (stdio)
|
|
121
167
|
├── api.ts → fetch autenticado contra a API do Jaylog
|
|
@@ -132,5 +178,5 @@ src/
|
|
|
132
178
|
Como o MCP roda via stdio, use o [MCP Inspector](https://github.com/modelcontextprotocol/inspector):
|
|
133
179
|
|
|
134
180
|
```bash
|
|
135
|
-
npx @modelcontextprotocol/inspector npx -y jaylog-mcp
|
|
181
|
+
npx @modelcontextprotocol/inspector npx -y jaylog-mcp
|
|
136
182
|
```
|
package/dist/api.js
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveApiBaseUrl = resolveApiBaseUrl;
|
|
3
4
|
exports.buildUrl = buildUrl;
|
|
4
5
|
exports.fetchJSON = fetchJSON;
|
|
5
6
|
const credentials_1 = require("./credentials");
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const trimSlashes = (value) => value.replace(/\/+$/, '');
|
|
8
|
+
/**
|
|
9
|
+
* Where API calls go. `JAYLOG_API_BASE_URL` wins when set (local dev, where the
|
|
10
|
+
* backend and the web app run on different ports, or a deployment that exposes
|
|
11
|
+
* the API directly). Otherwise the single `JAYLOG_URL` is the web app's address
|
|
12
|
+
* and the API is reached through its `/proxy/*` route, which forwards to the
|
|
13
|
+
* backend — that is what lets users configure one URL instead of two.
|
|
14
|
+
*/
|
|
15
|
+
function resolveApiBaseUrl() {
|
|
16
|
+
const apiBase = process.env.JAYLOG_API_BASE_URL;
|
|
17
|
+
if (apiBase)
|
|
18
|
+
return trimSlashes(apiBase);
|
|
19
|
+
const appUrl = process.env.JAYLOG_URL;
|
|
20
|
+
if (appUrl)
|
|
21
|
+
return `${trimSlashes(appUrl)}/proxy`;
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
9
24
|
/**
|
|
10
25
|
* Resolution order: `JAYLOG_API_TOKEN` env var (explicit override, handy for CI
|
|
11
26
|
* or scripting) first, then the credential saved by `login` — a personal,
|
|
@@ -23,10 +38,13 @@ async function getAuthHeaders() {
|
|
|
23
38
|
return { Authorization: `Bearer ${stored.token}` };
|
|
24
39
|
}
|
|
25
40
|
function buildUrl(path, qs = {}) {
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
const baseUrl = resolveApiBaseUrl();
|
|
42
|
+
if (!baseUrl) {
|
|
43
|
+
throw new Error('JAYLOG_URL is not set. Set it to the Jaylog web app URL (or JAYLOG_API_BASE_URL to call the API directly).');
|
|
28
44
|
}
|
|
29
|
-
|
|
45
|
+
// `new URL('/x', 'https://host/proxy')` drops `/proxy`, so keep the base's
|
|
46
|
+
// path by resolving a relative path against a base that ends in `/`.
|
|
47
|
+
const url = new URL(path.replace(/^\/+/, ''), `${baseUrl}/`);
|
|
30
48
|
for (const [k, v] of Object.entries(qs)) {
|
|
31
49
|
if (v !== undefined && v !== null)
|
|
32
50
|
url.searchParams.set(k, String(v));
|
package/dist/auth/login.js
CHANGED
|
@@ -30,9 +30,9 @@ p { color: #a1a1aa; font-size: 0.9rem; }
|
|
|
30
30
|
* freshly minted personal token back to a one-shot local server, store it.
|
|
31
31
|
*/
|
|
32
32
|
async function login() {
|
|
33
|
-
const frontendUrl = process.env.JAYLOG_FRONTEND_URL || process.env.JAYLOG_API_BASE_URL;
|
|
33
|
+
const frontendUrl = process.env.JAYLOG_FRONTEND_URL || process.env.JAYLOG_URL || process.env.JAYLOG_API_BASE_URL;
|
|
34
34
|
if (!frontendUrl) {
|
|
35
|
-
throw new Error('Set
|
|
35
|
+
throw new Error('Set JAYLOG_URL (the Jaylog web app URL) before running login.');
|
|
36
36
|
}
|
|
37
37
|
const state = (0, node_crypto_1.randomBytes)(16).toString('hex');
|
|
38
38
|
const result = await new Promise((resolve, reject) => {
|
package/dist/tools/services.js
CHANGED
|
@@ -15,8 +15,9 @@ function asText(data) {
|
|
|
15
15
|
}
|
|
16
16
|
function registerServiceTools(server) {
|
|
17
17
|
server.registerTool('list-services', {
|
|
18
|
-
description: 'List registered services (the applications/bots that send logs), with
|
|
19
|
-
'
|
|
18
|
+
description: 'List registered services (the applications/bots that send logs), with company and sector names ' +
|
|
19
|
+
'already resolved. `owner` is a user ID; use `search-services-by-owner` to search by the ' +
|
|
20
|
+
'owner\'s name or email instead.',
|
|
20
21
|
inputSchema: {
|
|
21
22
|
name: zod_1.z.string().optional().describe('Substring match against the service name'),
|
|
22
23
|
owner: zod_1.z.string().uuid().optional().describe('User ID of the service owner'),
|
|
@@ -24,4 +25,40 @@ function registerServiceTools(server) {
|
|
|
24
25
|
sector_id: zod_1.z.string().uuid().optional().describe('Restrict to a sector')
|
|
25
26
|
}
|
|
26
27
|
}, async (params) => asText(await (0, api_1.fetchJSON)('/service/list', params)));
|
|
28
|
+
server.registerTool('list-favorite-services', {
|
|
29
|
+
description: 'List the services the authenticated user has marked as favorites, with the same fields as ' +
|
|
30
|
+
'`list-services` (company and sector names resolved). Favorites belong to the user the MCP is ' +
|
|
31
|
+
'logged in as; there is no way to read someone else\'s.'
|
|
32
|
+
}, async () => {
|
|
33
|
+
const favorites = await (0, api_1.fetchJSON)('/favorites/list');
|
|
34
|
+
if (favorites.length === 0)
|
|
35
|
+
return asText([]);
|
|
36
|
+
// The favorites route only returns IDs, so resolve them against the service list.
|
|
37
|
+
const favoriteIds = new Set(favorites.map(favorite => favorite.service_id));
|
|
38
|
+
const services = await (0, api_1.fetchJSON)('/service/list');
|
|
39
|
+
return asText(services.filter(service => favoriteIds.has(service.id)));
|
|
40
|
+
});
|
|
41
|
+
server.registerTool('search-services-by-owner', {
|
|
42
|
+
description: 'Find the services owned by a person, searching by the owner\'s name and/or email (case-insensitive ' +
|
|
43
|
+
'substring match). Returns one entry per matching user with that user\'s services. A user that ' +
|
|
44
|
+
'matches but owns nothing is returned with an empty list. Use `list-services` with `owner` ' +
|
|
45
|
+
'instead when you already have the owner\'s user ID.',
|
|
46
|
+
inputSchema: {
|
|
47
|
+
name: zod_1.z.string().min(1).optional().describe('Substring match against the owner\'s name'),
|
|
48
|
+
email: zod_1.z.string().min(1).optional().describe('Substring match against the owner\'s email')
|
|
49
|
+
}
|
|
50
|
+
}, async ({ name, email }) => {
|
|
51
|
+
if (!name && !email) {
|
|
52
|
+
throw new Error('Provide at least one of `name` or `email`.');
|
|
53
|
+
}
|
|
54
|
+
const users = await (0, api_1.fetchJSON)('/user/list', { name, email });
|
|
55
|
+
if (users.length === 0)
|
|
56
|
+
return asText([]);
|
|
57
|
+
// One service request filtered client-side beats one request per matching user.
|
|
58
|
+
const services = await (0, api_1.fetchJSON)('/service/list');
|
|
59
|
+
return asText(users.map(user => ({
|
|
60
|
+
owner: { id: user.id, name: user.name, email: user.email, is_active: user.is_active },
|
|
61
|
+
services: services.filter(service => service.owner === user.id)
|
|
62
|
+
})));
|
|
63
|
+
});
|
|
27
64
|
}
|