jaylog-mcp 0.1.1 → 0.1.3

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 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 JAYLOG_API_BASE_URL=https://sua-api-jaylog # URL da API
15
- npx -y jaylog-mcp-server login # abre o navegador para autenticar
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 JAYLOG_API_BASE_URL=https://sua-api-jaylog -- npx -y jaylog-mcp-server
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-server"],
52
+ "args": ["-y", "jaylog-mcp"],
34
53
  "env": {
35
- "JAYLOG_API_BASE_URL": "https://sua-api-jaylog"
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-server login` abre o navegador numa tela de consentimento do app web do Jaylog
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-server login # autentica (abre o navegador)
61
- npx -y jaylog-mcp-server logout # remove a credencial local
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
- | `JAYLOG_API_BASE_URL` | URL base da API do Jaylog (ex: `http://localhost:3000`) |
69
- | `JAYLOG_FRONTEND_URL` | URL do app web do Jaylog, usada só pelo `login` para abrir `/mcp/authorize`. Se omitida, cai para `JAYLOG_API_BASE_URL` (ok quando os dois estão atrás do mesmo domínio). |
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 a URL da API
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 dono, empresa e setor resolvidos.
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-server
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 config = {
7
- baseUrl: process.env.JAYLOG_API_BASE_URL
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
- if (!config.baseUrl) {
27
- throw new Error('JAYLOG_API_BASE_URL is not set. Configure it (and JAYLOG_API_TOKEN) in the environment.');
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
- const url = new URL(path, config.baseUrl);
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));
@@ -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 JAYLOG_FRONTEND_URL (or JAYLOG_API_BASE_URL) before running login.');
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/index.js CHANGED
@@ -11,10 +11,13 @@ const keyUsers_1 = require("./tools/keyUsers");
11
11
  const logs_1 = require("./tools/logs");
12
12
  const schedules_1 = require("./tools/schedules");
13
13
  const services_1 = require("./tools/services");
14
+ // `require` (not `import`) keeps package.json out of tsc's rootDir; the path resolves
15
+ // the same from src/ (bun) and dist/ (published), so the reported version can't drift.
16
+ const { version } = require('../package.json');
14
17
  async function runServer() {
15
18
  const server = new mcp_js_1.McpServer({
16
19
  name: 'Jaylog',
17
- version: '0.1.0'
20
+ version
18
21
  });
19
22
  (0, logs_1.registerLogTools)(server);
20
23
  (0, services_1.registerServiceTools)(server);
@@ -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 their owner, company and ' +
19
- 'sector already resolved.',
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jaylog-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Model Context Protocol server for Jaylog observability (logs, metrics, services, schedules)",
5
5
  "bin": {
6
6
  "jaylog-mcp-server": "dist/index.js"