senado-br-mcp 3.2.1 → 3.3.1
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 +12 -0
- package/README.pt-BR.md +13 -0
- package/dist/scraper/ecidadania.js +26 -10
- package/dist/utils/validation.js +17 -2
- package/dist/version.js +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -94,6 +94,18 @@ list/corpus tools: without D1 they fall back to a live scrape of the ~5 REST hig
|
|
|
94
94
|
`meta.fonte` / `possivelDesatualizacao`; the detail tools (`obter_*`) are identical. Logs go to
|
|
95
95
|
**stderr** — stdout carries only the JSON-RPC protocol stream.
|
|
96
96
|
|
|
97
|
+
## Agent Skill (optional)
|
|
98
|
+
|
|
99
|
+
This repo bundles a Claude [Agent Skill](https://platform.claude.com/docs/en/docs/agents-and-tools/agent-skills/overview)
|
|
100
|
+
at [`.claude/skills/senado-br/`](.claude/skills/senado-br/SKILL.md) that teaches Claude **when** to reach for
|
|
101
|
+
this server and **how** to use its 66 tools well — a themed tool map, common question→tool playbooks, the
|
|
102
|
+
provenance contract, and gotchas (dates, the `codigoMateria` bridge, e-Cidadania's open-set listing, pagination).
|
|
103
|
+
It points back to the server's own `senado://catalogo` / `senado://guia` resources rather than duplicating them.
|
|
104
|
+
|
|
105
|
+
Claude Code auto-discovers it when you work in this repo. To use it elsewhere, copy
|
|
106
|
+
`.claude/skills/senado-br/` into your `~/.claude/skills/`, or zip the folder and upload it in claude.ai
|
|
107
|
+
(Settings → Features). The skill assumes the `senado-br` MCP server is connected (hosted or via npx).
|
|
108
|
+
|
|
97
109
|
## Architecture
|
|
98
110
|
|
|
99
111
|
- **Runtime:** Cloudflare Workers (ESM)
|
package/README.pt-BR.md
CHANGED
|
@@ -93,6 +93,19 @@ as ferramentas de lista/corpus do e-Cidadania: sem D1, elas caem no scraping ao
|
|
|
93
93
|
REST, sinalizado por `meta.fonte` / `possivelDesatualizacao`; as de detalhe (`obter_*`) são idênticas.
|
|
94
94
|
Os logs vão para o **stderr** — o stdout carrega apenas o fluxo do protocolo JSON-RPC.
|
|
95
95
|
|
|
96
|
+
## Agent Skill (opcional)
|
|
97
|
+
|
|
98
|
+
Este repositório inclui uma [Agent Skill](https://platform.claude.com/docs/en/docs/agents-and-tools/agent-skills/overview)
|
|
99
|
+
do Claude em [`.claude/skills/senado-br/`](.claude/skills/senado-br/SKILL.md) que ensina o Claude **quando**
|
|
100
|
+
recorrer a este servidor e **como** usar bem as 66 ferramentas — um mapa de ferramentas por tema, playbooks de
|
|
101
|
+
pergunta→ferramenta, o contrato de proveniência e os gotchas (datas, a ponte `codigoMateria`, a listagem
|
|
102
|
+
só-abertas do e-Cidadania, paginação). Ela aponta para os recursos `senado://catalogo` / `senado://guia` do
|
|
103
|
+
próprio servidor em vez de duplicá-los.
|
|
104
|
+
|
|
105
|
+
O Claude Code a descobre automaticamente ao trabalhar neste repositório. Para usá-la em outro lugar, copie
|
|
106
|
+
`.claude/skills/senado-br/` para o seu `~/.claude/skills/`, ou compacte a pasta e faça upload no claude.ai
|
|
107
|
+
(Configurações → Recursos). A skill pressupõe o servidor MCP `senado-br` conectado (hospedado ou via npx).
|
|
108
|
+
|
|
96
109
|
## Arquitetura
|
|
97
110
|
|
|
98
111
|
- **Runtime:** Cloudflare Workers (ESM)
|
|
@@ -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.1";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "senado-br-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.1",
|
|
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",
|
|
@@ -29,12 +29,13 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
32
|
-
"agents": "^0.5.0",
|
|
33
32
|
"zod": "^3.23.0"
|
|
34
33
|
},
|
|
35
34
|
"devDependencies": {
|
|
36
35
|
"@cloudflare/workers-types": "^4.20250214.0",
|
|
37
36
|
"@types/node": "^25.9.3",
|
|
37
|
+
"agents": "^0.5.0",
|
|
38
|
+
"exceljs": "^4.4.0",
|
|
38
39
|
"tsx": "^4.19.2",
|
|
39
40
|
"typescript": "^5.7.0",
|
|
40
41
|
"vitest": "^4.0.18",
|