senado-br-mcp 3.2.1 → 3.3.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/dist/scraper/ecidadania.js +26 -10
- package/dist/utils/validation.js +17 -2
- package/dist/version.js +1 -1
- package/package.json +2 -1
|
@@ -11,6 +11,16 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { UPSTREAM_TIMEOUT_MS } from "../types.js";
|
|
13
13
|
export const ECIDADANIA_BASE = "https://www12.senado.leg.br/ecidadania";
|
|
14
|
+
/**
|
|
15
|
+
* Error carrying a `retryable` flag, mirroring UpstreamError so the tool layer
|
|
16
|
+
* (`errorFrom` / `ecidadaniaError`) can surface it. e-Cidadania has its own fetch
|
|
17
|
+
* (not `upstreamFetch`), so transient conditions are classified here: 5xx / 429 /
|
|
18
|
+
* timeout / network → retryable; 4xx → not.
|
|
19
|
+
*/
|
|
20
|
+
function ecidadaniaFetchError(message, retryable) {
|
|
21
|
+
return Object.assign(new Error(message), { retryable });
|
|
22
|
+
}
|
|
23
|
+
const isTransientStatus = (status) => status >= 500 || status === 429;
|
|
14
24
|
/** Fetch an HTML page from e-Cidadania with descriptive errors. */
|
|
15
25
|
export async function fetchPage(path) {
|
|
16
26
|
const url = `${ECIDADANIA_BASE}${path}`;
|
|
@@ -25,19 +35,22 @@ export async function fetchPage(path) {
|
|
|
25
35
|
signal: controller.signal,
|
|
26
36
|
});
|
|
27
37
|
clearTimeout(timeout);
|
|
28
|
-
if (!resp.ok)
|
|
29
|
-
throw
|
|
38
|
+
if (!resp.ok) {
|
|
39
|
+
throw ecidadaniaFetchError(`e-Cidadania retornou HTTP ${resp.status} para ${path}`, isTransientStatus(resp.status));
|
|
40
|
+
}
|
|
30
41
|
const text = await resp.text();
|
|
31
42
|
if (!text.trim())
|
|
32
|
-
throw
|
|
43
|
+
throw ecidadaniaFetchError(`e-Cidadania retornou página vazia para ${path}`, true);
|
|
33
44
|
return text;
|
|
34
45
|
}
|
|
35
46
|
catch (e) {
|
|
36
47
|
clearTimeout(timeout);
|
|
37
48
|
if (e.name === "AbortError") {
|
|
38
|
-
throw
|
|
49
|
+
throw ecidadaniaFetchError(`e-Cidadania: timeout (${UPSTREAM_TIMEOUT_MS / 1000}s) ao acessar ${path}`, true);
|
|
39
50
|
}
|
|
40
|
-
|
|
51
|
+
if (e instanceof Error && "retryable" in e)
|
|
52
|
+
throw e; // already classified above
|
|
53
|
+
throw ecidadaniaFetchError(`e-Cidadania: falha de rede ao acessar ${path} (${e.message})`, true);
|
|
41
54
|
}
|
|
42
55
|
}
|
|
43
56
|
/**
|
|
@@ -54,14 +67,15 @@ export async function fetchEcidadaniaJson(endpoint) {
|
|
|
54
67
|
signal: controller.signal,
|
|
55
68
|
});
|
|
56
69
|
clearTimeout(timeout);
|
|
57
|
-
if (!resp.ok)
|
|
58
|
-
throw
|
|
70
|
+
if (!resp.ok) {
|
|
71
|
+
throw ecidadaniaFetchError(`e-Cidadania REST API retornou HTTP ${resp.status} para ${endpoint}`, isTransientStatus(resp.status));
|
|
72
|
+
}
|
|
59
73
|
let data;
|
|
60
74
|
try {
|
|
61
75
|
data = await resp.json();
|
|
62
76
|
}
|
|
63
77
|
catch {
|
|
64
|
-
throw
|
|
78
|
+
throw ecidadaniaFetchError(`e-Cidadania REST API retornou JSON inválido para ${endpoint}`, true);
|
|
65
79
|
}
|
|
66
80
|
if (!Array.isArray(data)) {
|
|
67
81
|
// API returned an object or other non-array — wrap or return empty
|
|
@@ -74,9 +88,11 @@ export async function fetchEcidadaniaJson(endpoint) {
|
|
|
74
88
|
catch (e) {
|
|
75
89
|
clearTimeout(timeout);
|
|
76
90
|
if (e.name === "AbortError") {
|
|
77
|
-
throw
|
|
91
|
+
throw ecidadaniaFetchError(`e-Cidadania REST API: timeout (${UPSTREAM_TIMEOUT_MS / 1000}s) ao acessar ${endpoint}`, true);
|
|
78
92
|
}
|
|
79
|
-
|
|
93
|
+
if (e instanceof Error && "retryable" in e)
|
|
94
|
+
throw e; // already classified above
|
|
95
|
+
throw ecidadaniaFetchError(`e-Cidadania REST API: falha de rede ao acessar ${endpoint} (${e.message})`, true);
|
|
80
96
|
}
|
|
81
97
|
}
|
|
82
98
|
/** Parse Brazilian number: 1.234.567 -> 1234567 */
|
package/dist/utils/validation.js
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
/** Shared validation helpers and response builders. */
|
|
2
2
|
import { incr } from "../metrics.js";
|
|
3
3
|
import { logger } from "./logger.js";
|
|
4
|
-
|
|
4
|
+
/** Actionable next-step guidance, derived from whether retrying can help. */
|
|
5
|
+
function defaultHint(isRetryable) {
|
|
6
|
+
return isRetryable
|
|
7
|
+
? "Erro transitório na fonte de dados oficial; repita a chamada em alguns segundos."
|
|
8
|
+
: "Erro não recuperável por repetição; verifique os parâmetros (códigos, datas, filtros). Se persistir, a fonte oficial pode estar indisponível.";
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Standard error envelope, identical across all tools: `{ error, retryable, hint }`.
|
|
12
|
+
* `hint` is an actionable next step (additive, defaults from `isRetryable`; callers may
|
|
13
|
+
* override). The same payload is also returned as `structuredContent` so clients can parse
|
|
14
|
+
* errors deterministically — symmetric with toolResult() — the permissive outputSchema is
|
|
15
|
+
* skipped on isError results anyway, and a plain object satisfies it regardless.
|
|
16
|
+
*/
|
|
17
|
+
export function toolError(message, isRetryable = false, hint) {
|
|
5
18
|
incr("toolErrors");
|
|
19
|
+
const payload = { error: message, retryable: isRetryable, hint: hint ?? defaultHint(isRetryable) };
|
|
6
20
|
return {
|
|
7
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
21
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
22
|
+
structuredContent: payload,
|
|
8
23
|
isError: true,
|
|
9
24
|
};
|
|
10
25
|
}
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Single source of truth for the server version (kept in sync with package.json). */
|
|
2
|
-
export const VERSION = "3.
|
|
2
|
+
export const VERSION = "3.3.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "senado-br-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "MCP server for Brazilian Senate open data — runs hosted on Cloudflare Workers or locally via npx (stdio)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@cloudflare/workers-types": "^4.20250214.0",
|
|
37
37
|
"@types/node": "^25.9.3",
|
|
38
|
+
"exceljs": "^4.4.0",
|
|
38
39
|
"tsx": "^4.19.2",
|
|
39
40
|
"typescript": "^5.7.0",
|
|
40
41
|
"vitest": "^4.0.18",
|