senado-br-mcp 3.2.0 → 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.
@@ -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 new Error(`e-Cidadania retornou HTTP ${resp.status} para ${path}`);
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 new Error(`e-Cidadania retornou página vazia para ${path}`);
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 new Error(`e-Cidadania: timeout (${UPSTREAM_TIMEOUT_MS / 1000}s) ao acessar ${path}`);
49
+ throw ecidadaniaFetchError(`e-Cidadania: timeout (${UPSTREAM_TIMEOUT_MS / 1000}s) ao acessar ${path}`, true);
39
50
  }
40
- throw e;
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 new Error(`e-Cidadania REST API retornou HTTP ${resp.status} para ${endpoint}`);
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 new Error(`e-Cidadania REST API retornou JSON inválido para ${endpoint}`);
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 new Error(`e-Cidadania REST API: timeout (${UPSTREAM_TIMEOUT_MS / 1000}s) ao acessar ${endpoint}`);
91
+ throw ecidadaniaFetchError(`e-Cidadania REST API: timeout (${UPSTREAM_TIMEOUT_MS / 1000}s) ao acessar ${endpoint}`, true);
78
92
  }
79
- throw e;
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 */
@@ -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
- export function toolError(message, isRetryable = false) {
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({ error: message, retryable: isRetryable }) }],
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.0";
2
+ export const VERSION = "3.3.0";
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "senado-br-mcp",
3
- "version": "3.2.0",
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",
7
+ "mcpName": "io.github.SidneyBissoli/senado-br-mcp-cloudflare",
7
8
  "bin": {
8
9
  "senado-br-mcp": "./dist/cli.js"
9
10
  },
@@ -34,6 +35,7 @@
34
35
  "devDependencies": {
35
36
  "@cloudflare/workers-types": "^4.20250214.0",
36
37
  "@types/node": "^25.9.3",
38
+ "exceljs": "^4.4.0",
37
39
  "tsx": "^4.19.2",
38
40
  "typescript": "^5.7.0",
39
41
  "vitest": "^4.0.18",