senado-br-mcp 1.2.0 → 3.2.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/LICENSE +21 -21
- package/README.md +810 -408
- package/README.pt-BR.md +788 -0
- package/dist/auth.js +46 -0
- package/dist/cache/l0-memory.js +30 -0
- package/dist/cache/l1-cache-api.js +36 -0
- package/dist/cache/manager.js +116 -0
- package/dist/cli.js +37 -0
- package/dist/icon.js +3 -0
- package/dist/instrument.js +65 -0
- package/dist/metrics.js +40 -0
- package/dist/observability/call-context.js +35 -0
- package/dist/prompts.js +101 -0
- package/dist/resources.js +141 -0
- package/dist/scraper/anomaly.js +34 -0
- package/dist/scraper/ecidadania.js +442 -0
- package/dist/scraper/pipeline.js +180 -0
- package/dist/scraper/store.js +147 -0
- package/dist/server.js +101 -3383
- package/dist/status.js +21 -0
- package/dist/throttle/adm.js +26 -0
- package/dist/throttle/token-bucket.js +46 -0
- package/dist/throttle/upstream.js +151 -0
- package/dist/tools/comissoes.js +407 -0
- package/dist/tools/composicao.js +137 -0
- package/dist/tools/contratacoes.js +263 -0
- package/dist/tools/discursos.js +141 -0
- package/dist/tools/ecidadania.js +344 -0
- package/dist/tools/legislacao.js +92 -0
- package/dist/tools/materias.js +241 -0
- package/dist/tools/orcamento-senado.js +155 -0
- package/dist/tools/orcamento.js +70 -0
- package/dist/tools/plenario.js +344 -0
- package/dist/tools/processos.js +330 -0
- package/dist/tools/referencia.js +178 -0
- package/dist/tools/senadores-admin.js +193 -0
- package/dist/tools/senadores.js +287 -0
- package/dist/tools/servidores.js +219 -0
- package/dist/tools/supridos.js +58 -0
- package/dist/tools/taquigrafia.js +127 -0
- package/dist/tools/votacao-comissao.js +122 -0
- package/dist/tools/votacoes.js +143 -0
- package/dist/types.js +10 -0
- package/dist/utils/logger.js +23 -0
- package/dist/utils/provenance.js +160 -0
- package/dist/utils/validation.js +69 -0
- package/dist/version.js +2 -0
- package/package.json +42 -75
- package/dist/index.js +0 -3196
package/dist/server.js
CHANGED
|
@@ -1,3386 +1,104 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
var import_zod2 = require("zod");
|
|
41
|
-
|
|
42
|
-
// src/api/endpoints.ts
|
|
43
|
-
var BASE_URL = "https://legis.senado.leg.br/dadosabertos";
|
|
44
|
-
var ENDPOINTS = {
|
|
45
|
-
// Senadores
|
|
46
|
-
SENADORES_ATUAIS: "/senador/lista/atual",
|
|
47
|
-
SENADORES_LEGISLATURA: (leg) => `/senador/lista/legislatura/${leg}`,
|
|
48
|
-
SENADOR: (codigo) => `/senador/${codigo}`,
|
|
49
|
-
SENADOR_VOTACOES: (codigo) => `/senador/${codigo}/votacoes`,
|
|
50
|
-
SENADOR_MANDATOS: (codigo) => `/senador/${codigo}/mandatos`,
|
|
51
|
-
SENADOR_CARGOS: (codigo) => `/senador/${codigo}/cargos`,
|
|
52
|
-
SENADOR_COMISSOES: (codigo) => `/senador/${codigo}/comissoes`,
|
|
53
|
-
// Matérias
|
|
54
|
-
MATERIAS_PESQUISA: "/materia/pesquisa/lista",
|
|
55
|
-
MATERIA: (codigo) => `/materia/${codigo}`,
|
|
56
|
-
MATERIA_TEXTOS: (codigo) => `/materia/${codigo}/textos`,
|
|
57
|
-
MATERIA_VOTACOES: (codigo) => `/materia/${codigo}/votacoes`,
|
|
58
|
-
MATERIAS_TRAMITANDO: "/materia/tramitando",
|
|
59
|
-
// Votações
|
|
60
|
-
VOTACOES_ANO: (ano) => `/plenario/lista/votacao/${ano}`,
|
|
61
|
-
VOTACAO: (codigo) => `/plenario/votacao/${codigo}`,
|
|
62
|
-
VOTACAO_VOTOS: (codigo) => `/plenario/votacao/${codigo}/votos`,
|
|
63
|
-
// Comissões
|
|
64
|
-
COMISSOES_LISTA: "/comissao/lista",
|
|
65
|
-
COMISSAO: (sigla) => `/comissao/${sigla}`,
|
|
66
|
-
COMISSAO_COMPOSICAO: (sigla) => `/comissao/${sigla}/composicao`,
|
|
67
|
-
// Agenda
|
|
68
|
-
AGENDA: (data) => `/agenda/${data}`,
|
|
69
|
-
SESSOES_ANO: (ano) => `/plenario/lista/sessoes/${ano}`,
|
|
70
|
-
// Auxiliares
|
|
71
|
-
LEGISLATURA_ATUAL: "/legislatura/atual",
|
|
72
|
-
MATERIA_SIGLAS: "/materia/siglas",
|
|
73
|
-
PARTIDOS_LISTA: "/partido/lista"
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// src/api/client.ts
|
|
77
|
-
async function apiRequest(endpoint, options = {}) {
|
|
78
|
-
const { timeout = 3e4, retries = 3, format = "json" } = options;
|
|
79
|
-
const url = `${BASE_URL}${endpoint}`;
|
|
80
|
-
const headers = {
|
|
81
|
-
"Accept": format === "json" ? "application/json" : "application/xml",
|
|
82
|
-
"User-Agent": "senado-br-mcp/1.0.0"
|
|
83
|
-
};
|
|
84
|
-
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
85
|
-
try {
|
|
86
|
-
const controller = new AbortController();
|
|
87
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
88
|
-
logger.debug({ endpoint, attempt }, "Making API request");
|
|
89
|
-
const response = await fetch(url, {
|
|
90
|
-
headers,
|
|
91
|
-
signal: controller.signal
|
|
92
|
-
});
|
|
93
|
-
clearTimeout(timeoutId);
|
|
94
|
-
if (!response.ok) {
|
|
95
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
96
|
-
}
|
|
97
|
-
const data = await response.json();
|
|
98
|
-
logger.debug({ endpoint, attempt }, "API request successful");
|
|
99
|
-
return data;
|
|
100
|
-
} catch (error) {
|
|
101
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
102
|
-
logger.warn({ endpoint, attempt, error: errorMessage }, "API request failed");
|
|
103
|
-
if (attempt === retries) {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`Failed to fetch ${endpoint} after ${retries} attempts: ${errorMessage}`
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
const delay = Math.pow(2, attempt) * 1e3;
|
|
109
|
-
logger.debug({ delay }, "Waiting before retry");
|
|
110
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
throw new Error("Unreachable");
|
|
114
|
-
}
|
|
115
|
-
function createSuccessResponse(data, endpoint) {
|
|
116
|
-
return {
|
|
117
|
-
success: true,
|
|
118
|
-
data,
|
|
119
|
-
metadata: {
|
|
120
|
-
fonte: "Senado Federal - Dados Abertos",
|
|
121
|
-
dataConsulta: (/* @__PURE__ */ new Date()).toISOString(),
|
|
122
|
-
endpoint
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
function createErrorResponse(code, message, suggestion) {
|
|
127
|
-
return {
|
|
128
|
-
success: false,
|
|
129
|
-
error: {
|
|
130
|
-
code,
|
|
131
|
-
message,
|
|
132
|
-
...suggestion && { suggestion }
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// src/schemas/senador.ts
|
|
138
|
-
var import_zod = require("zod");
|
|
139
|
-
var ListarSenadoresInput = import_zod.z.object({
|
|
140
|
-
legislatura: import_zod.z.number().int().min(1).optional().describe("N\xFAmero da legislatura (ex: 57 para 2023-2027)"),
|
|
141
|
-
uf: import_zod.z.string().length(2).toUpperCase().optional().describe("Sigla do estado (ex: SP, RJ, MG)"),
|
|
142
|
-
partido: import_zod.z.string().toUpperCase().optional().describe("Sigla do partido (ex: PT, PL, MDB)"),
|
|
143
|
-
emExercicio: import_zod.z.boolean().optional().default(true).describe("Filtrar apenas senadores em exerc\xEDcio")
|
|
144
|
-
});
|
|
145
|
-
var ObterSenadorInput = import_zod.z.object({
|
|
146
|
-
codigoSenador: import_zod.z.number().int().positive().describe("C\xF3digo \xFAnico do senador no sistema do Senado")
|
|
147
|
-
});
|
|
148
|
-
var BuscarSenadorPorNomeInput = import_zod.z.object({
|
|
149
|
-
nome: import_zod.z.string().min(2).describe("Nome ou parte do nome do senador")
|
|
150
|
-
});
|
|
151
|
-
var VotacoesSenadorInput = import_zod.z.object({
|
|
152
|
-
codigoSenador: import_zod.z.number().int().positive().describe("C\xF3digo \xFAnico do senador"),
|
|
153
|
-
ano: import_zod.z.number().int().min(1900).max(2100).optional().describe("Ano das vota\xE7\xF5es"),
|
|
154
|
-
dataInicio: import_zod.z.string().regex(/^\d{8}$/, "Formato deve ser YYYYMMDD").optional().describe("Data in\xEDcio (YYYYMMDD)"),
|
|
155
|
-
dataFim: import_zod.z.string().regex(/^\d{8}$/, "Formato deve ser YYYYMMDD").optional().describe("Data fim (YYYYMMDD)")
|
|
156
|
-
});
|
|
157
|
-
var SenadorResumo = import_zod.z.object({
|
|
158
|
-
codigo: import_zod.z.number(),
|
|
159
|
-
nome: import_zod.z.string(),
|
|
160
|
-
nomeCompleto: import_zod.z.string(),
|
|
161
|
-
partido: import_zod.z.string().nullable(),
|
|
162
|
-
uf: import_zod.z.string(),
|
|
163
|
-
foto: import_zod.z.string().nullable(),
|
|
164
|
-
emExercicio: import_zod.z.boolean()
|
|
165
|
-
});
|
|
166
|
-
var SenadorDetalhe = import_zod.z.object({
|
|
167
|
-
codigo: import_zod.z.number(),
|
|
168
|
-
nome: import_zod.z.string(),
|
|
169
|
-
nomeCompleto: import_zod.z.string(),
|
|
170
|
-
nomeCivil: import_zod.z.string().nullable(),
|
|
171
|
-
sexo: import_zod.z.string().nullable(),
|
|
172
|
-
dataNascimento: import_zod.z.string().nullable(),
|
|
173
|
-
naturalidade: import_zod.z.string().nullable(),
|
|
174
|
-
ufNaturalidade: import_zod.z.string().nullable(),
|
|
175
|
-
partido: import_zod.z.string().nullable(),
|
|
176
|
-
uf: import_zod.z.string(),
|
|
177
|
-
foto: import_zod.z.string().nullable(),
|
|
178
|
-
email: import_zod.z.string().nullable(),
|
|
179
|
-
telefone: import_zod.z.string().nullable(),
|
|
180
|
-
emExercicio: import_zod.z.boolean(),
|
|
181
|
-
mandatos: import_zod.z.array(import_zod.z.object({
|
|
182
|
-
legislatura: import_zod.z.number(),
|
|
183
|
-
uf: import_zod.z.string(),
|
|
184
|
-
participacao: import_zod.z.string(),
|
|
185
|
-
dataInicio: import_zod.z.string().nullable(),
|
|
186
|
-
dataFim: import_zod.z.string().nullable()
|
|
187
|
-
})).optional(),
|
|
188
|
-
comissoes: import_zod.z.array(import_zod.z.object({
|
|
189
|
-
sigla: import_zod.z.string(),
|
|
190
|
-
nome: import_zod.z.string(),
|
|
191
|
-
cargo: import_zod.z.string().nullable()
|
|
192
|
-
})).optional()
|
|
193
|
-
});
|
|
194
|
-
var VotoSenador = import_zod.z.object({
|
|
195
|
-
codigoVotacao: import_zod.z.number(),
|
|
196
|
-
data: import_zod.z.string(),
|
|
197
|
-
materia: import_zod.z.string(),
|
|
198
|
-
descricao: import_zod.z.string().nullable(),
|
|
199
|
-
voto: import_zod.z.string(),
|
|
200
|
-
resultado: import_zod.z.string().nullable()
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// src/tools/senadores.ts
|
|
204
|
-
function parseSenadorResumo(parlamentar) {
|
|
205
|
-
const identificacao = parlamentar.IdentificacaoParlamentar || parlamentar;
|
|
206
|
-
const mandato = parlamentar.Mandato || {};
|
|
207
|
-
return {
|
|
208
|
-
codigo: parseInt(identificacao.CodigoParlamentar || "0"),
|
|
209
|
-
nome: identificacao.NomeParlamentar || "",
|
|
210
|
-
nomeCompleto: identificacao.NomeCompletoParlamentar || identificacao.NomeParlamentar || "",
|
|
211
|
-
partido: mandato.Partido?.SiglaPartido || identificacao.SiglaPartidoParlamentar || null,
|
|
212
|
-
uf: mandato.UfParlamentar || identificacao.UfParlamentar || "",
|
|
213
|
-
foto: identificacao.UrlFotoParlamentar || null,
|
|
214
|
-
emExercicio: parlamentar.DescricaoParticipacao !== "Suplente" && !parlamentar.DataFim
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
function parseSenadorDetalhe(dados) {
|
|
218
|
-
const parlamentar = dados.Parlamentar || dados;
|
|
219
|
-
const identificacao = parlamentar.IdentificacaoParlamentar || {};
|
|
220
|
-
const dadosBasicos = parlamentar.DadosBasicosParlamentar || {};
|
|
221
|
-
return {
|
|
222
|
-
codigo: parseInt(identificacao.CodigoParlamentar || "0"),
|
|
223
|
-
nome: identificacao.NomeParlamentar || "",
|
|
224
|
-
nomeCompleto: identificacao.NomeCompletoParlamentar || "",
|
|
225
|
-
nomeCivil: dadosBasicos.NomeCivilParlamentar || null,
|
|
226
|
-
sexo: identificacao.SexoParlamentar || null,
|
|
227
|
-
dataNascimento: dadosBasicos.DataNascimento || null,
|
|
228
|
-
naturalidade: dadosBasicos.Naturalidade || null,
|
|
229
|
-
ufNaturalidade: dadosBasicos.UfNaturalidade || null,
|
|
230
|
-
partido: identificacao.SiglaPartidoParlamentar || null,
|
|
231
|
-
uf: identificacao.UfParlamentar || "",
|
|
232
|
-
foto: identificacao.UrlFotoParlamentar || null,
|
|
233
|
-
email: identificacao.EmailParlamentar || null,
|
|
234
|
-
telefone: null,
|
|
235
|
-
emExercicio: true,
|
|
236
|
-
mandatos: parlamentar.Mandatos?.Mandato ? (Array.isArray(parlamentar.Mandatos.Mandato) ? parlamentar.Mandatos.Mandato : [parlamentar.Mandatos.Mandato]).map((m) => ({
|
|
237
|
-
legislatura: parseInt(m.PrimeiraLegislaturaDoMandato?.NumeroLegislatura || "0"),
|
|
238
|
-
uf: m.UfParlamentar || "",
|
|
239
|
-
participacao: m.DescricaoParticipacao || "",
|
|
240
|
-
dataInicio: m.DataInicio || null,
|
|
241
|
-
dataFim: m.DataFim || null
|
|
242
|
-
})) : void 0,
|
|
243
|
-
comissoes: void 0
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
function parseVotoSenador(votacao) {
|
|
247
|
-
return {
|
|
248
|
-
codigoVotacao: parseInt(votacao.CodigoSessaoVotacao || votacao.CodigoVotacao || "0"),
|
|
249
|
-
data: votacao.DataSessao || votacao.Data || "",
|
|
250
|
-
materia: votacao.IdentificacaoMateria?.DescricaoIdentificacaoMateria || `${votacao.SiglaMateria || ""} ${votacao.NumeroMateria || ""}/${votacao.AnoMateria || ""}`.trim(),
|
|
251
|
-
descricao: votacao.DescricaoVotacao || null,
|
|
252
|
-
voto: votacao.DescricaoVoto || votacao.SiglaDescricaoVoto || "",
|
|
253
|
-
resultado: votacao.Resultado || null
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
function registerSenadoresTools(server) {
|
|
257
|
-
server.tool(
|
|
258
|
-
"senado_listar_senadores",
|
|
259
|
-
"Lista senadores em exerc\xEDcio ou de uma legislatura espec\xEDfica. Pode filtrar por UF e partido.",
|
|
260
|
-
{
|
|
261
|
-
legislatura: import_zod2.z.number().int().min(1).optional().describe("N\xFAmero da legislatura (ex: 57 para 2023-2027)"),
|
|
262
|
-
uf: import_zod2.z.string().length(2).optional().describe("Sigla do estado (ex: SP, RJ, MG)"),
|
|
263
|
-
partido: import_zod2.z.string().optional().describe("Sigla do partido (ex: PT, PL, MDB)"),
|
|
264
|
-
emExercicio: import_zod2.z.boolean().optional().default(true).describe("Filtrar apenas senadores em exerc\xEDcio")
|
|
265
|
-
},
|
|
266
|
-
async (params) => {
|
|
267
|
-
try {
|
|
268
|
-
const input = ListarSenadoresInput.parse(params);
|
|
269
|
-
logger.info({ input }, "Listing senators");
|
|
270
|
-
let endpoint;
|
|
271
|
-
if (input.legislatura) {
|
|
272
|
-
endpoint = ENDPOINTS.SENADORES_LEGISLATURA(input.legislatura);
|
|
273
|
-
} else {
|
|
274
|
-
endpoint = ENDPOINTS.SENADORES_ATUAIS;
|
|
275
|
-
}
|
|
276
|
-
const response = await apiRequest(endpoint);
|
|
277
|
-
let parlamentares = [];
|
|
278
|
-
if (response.ListaParlamentarEmExercicio?.Parlamentares?.Parlamentar) {
|
|
279
|
-
parlamentares = response.ListaParlamentarEmExercicio.Parlamentares.Parlamentar;
|
|
280
|
-
} else if (response.ListaParlamentarLegislatura?.Parlamentares?.Parlamentar) {
|
|
281
|
-
parlamentares = response.ListaParlamentarLegislatura.Parlamentares.Parlamentar;
|
|
282
|
-
}
|
|
283
|
-
if (!Array.isArray(parlamentares)) {
|
|
284
|
-
parlamentares = parlamentares ? [parlamentares] : [];
|
|
285
|
-
}
|
|
286
|
-
let senadores = parlamentares.map(parseSenadorResumo);
|
|
287
|
-
if (input.uf) {
|
|
288
|
-
const ufUpper = input.uf.toUpperCase();
|
|
289
|
-
senadores = senadores.filter((s) => s.uf.toUpperCase() === ufUpper);
|
|
290
|
-
}
|
|
291
|
-
if (input.partido) {
|
|
292
|
-
const partidoUpper = input.partido.toUpperCase();
|
|
293
|
-
senadores = senadores.filter((s) => s.partido?.toUpperCase() === partidoUpper);
|
|
294
|
-
}
|
|
295
|
-
const result = createSuccessResponse(
|
|
296
|
-
{ count: senadores.length, senadores },
|
|
297
|
-
endpoint
|
|
298
|
-
);
|
|
299
|
-
return {
|
|
300
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
301
|
-
};
|
|
302
|
-
} catch (error) {
|
|
303
|
-
logger.error({ error }, "Error listing senators");
|
|
304
|
-
const errorResult = createErrorResponse(
|
|
305
|
-
"ERRO_LISTAR_SENADORES",
|
|
306
|
-
error instanceof Error ? error.message : "Erro desconhecido",
|
|
307
|
-
"Verifique os par\xE2metros e tente novamente"
|
|
308
|
-
);
|
|
309
|
-
return {
|
|
310
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
);
|
|
315
|
-
server.tool(
|
|
316
|
-
"senado_obter_senador",
|
|
317
|
-
"Obt\xE9m informa\xE7\xF5es detalhadas de um senador espec\xEDfico, incluindo dados biogr\xE1ficos, mandatos e comiss\xF5es.",
|
|
318
|
-
{
|
|
319
|
-
codigoSenador: import_zod2.z.number().int().positive().describe("C\xF3digo \xFAnico do senador no sistema do Senado")
|
|
320
|
-
},
|
|
321
|
-
async (params) => {
|
|
322
|
-
try {
|
|
323
|
-
const input = ObterSenadorInput.parse(params);
|
|
324
|
-
logger.info({ input }, "Getting senator details");
|
|
325
|
-
const endpoint = ENDPOINTS.SENADOR(input.codigoSenador);
|
|
326
|
-
const response = await apiRequest(endpoint);
|
|
327
|
-
const dados = response.DetalheParlamentar || response;
|
|
328
|
-
const senador = parseSenadorDetalhe(dados);
|
|
329
|
-
const result = createSuccessResponse(senador, endpoint);
|
|
330
|
-
return {
|
|
331
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
332
|
-
};
|
|
333
|
-
} catch (error) {
|
|
334
|
-
logger.error({ error }, "Error getting senator details");
|
|
335
|
-
const errorResult = createErrorResponse(
|
|
336
|
-
"SENADOR_NAO_ENCONTRADO",
|
|
337
|
-
error instanceof Error ? error.message : "Senador n\xE3o encontrado",
|
|
338
|
-
"Use senado_buscar_senador_por_nome para encontrar o c\xF3digo correto"
|
|
339
|
-
);
|
|
340
|
-
return {
|
|
341
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
);
|
|
346
|
-
server.tool(
|
|
347
|
-
"senado_buscar_senador_por_nome",
|
|
348
|
-
"Busca senadores por nome (\xFAtil quando n\xE3o se tem o c\xF3digo). Retorna lista de senadores correspondentes.",
|
|
349
|
-
{
|
|
350
|
-
nome: import_zod2.z.string().min(2).describe("Nome ou parte do nome do senador")
|
|
351
|
-
},
|
|
352
|
-
async (params) => {
|
|
353
|
-
try {
|
|
354
|
-
const input = BuscarSenadorPorNomeInput.parse(params);
|
|
355
|
-
logger.info({ input }, "Searching senator by name");
|
|
356
|
-
const endpoint = ENDPOINTS.SENADORES_ATUAIS;
|
|
357
|
-
const response = await apiRequest(endpoint);
|
|
358
|
-
let parlamentares = [];
|
|
359
|
-
if (response.ListaParlamentarEmExercicio?.Parlamentares?.Parlamentar) {
|
|
360
|
-
parlamentares = response.ListaParlamentarEmExercicio.Parlamentares.Parlamentar;
|
|
361
|
-
}
|
|
362
|
-
if (!Array.isArray(parlamentares)) {
|
|
363
|
-
parlamentares = parlamentares ? [parlamentares] : [];
|
|
364
|
-
}
|
|
365
|
-
const nomeNormalizado = input.nome.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
366
|
-
const senadores = parlamentares.map(parseSenadorResumo).filter((s) => {
|
|
367
|
-
const nomeCompleto = s.nomeCompleto.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
368
|
-
const nomeParlamentar = s.nome.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
369
|
-
return nomeCompleto.includes(nomeNormalizado) || nomeParlamentar.includes(nomeNormalizado);
|
|
370
|
-
});
|
|
371
|
-
const result = createSuccessResponse(
|
|
372
|
-
{ count: senadores.length, senadores },
|
|
373
|
-
endpoint
|
|
374
|
-
);
|
|
375
|
-
return {
|
|
376
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
377
|
-
};
|
|
378
|
-
} catch (error) {
|
|
379
|
-
logger.error({ error }, "Error searching senator by name");
|
|
380
|
-
const errorResult = createErrorResponse(
|
|
381
|
-
"ERRO_BUSCAR_SENADOR",
|
|
382
|
-
error instanceof Error ? error.message : "Erro na busca",
|
|
383
|
-
"Tente com um nome diferente ou mais completo"
|
|
384
|
-
);
|
|
385
|
-
return {
|
|
386
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
);
|
|
391
|
-
server.tool(
|
|
392
|
-
"senado_votacoes_senador",
|
|
393
|
-
"Lista vota\xE7\xF5es de um senador espec\xEDfico, mostrando como o senador votou em cada mat\xE9ria.",
|
|
394
|
-
{
|
|
395
|
-
codigoSenador: import_zod2.z.number().int().positive().describe("C\xF3digo \xFAnico do senador"),
|
|
396
|
-
ano: import_zod2.z.number().int().min(1900).max(2100).optional().describe("Ano das vota\xE7\xF5es"),
|
|
397
|
-
dataInicio: import_zod2.z.string().regex(/^\d{8}$/).optional().describe("Data in\xEDcio (YYYYMMDD)"),
|
|
398
|
-
dataFim: import_zod2.z.string().regex(/^\d{8}$/).optional().describe("Data fim (YYYYMMDD)")
|
|
399
|
-
},
|
|
400
|
-
async (params) => {
|
|
401
|
-
try {
|
|
402
|
-
const input = VotacoesSenadorInput.parse(params);
|
|
403
|
-
logger.info({ input }, "Getting senator votes");
|
|
404
|
-
let endpoint = ENDPOINTS.SENADOR_VOTACOES(input.codigoSenador);
|
|
405
|
-
const queryParams = [];
|
|
406
|
-
if (input.ano) queryParams.push(`ano=${input.ano}`);
|
|
407
|
-
if (input.dataInicio) queryParams.push(`dataInicio=${input.dataInicio}`);
|
|
408
|
-
if (input.dataFim) queryParams.push(`dataFim=${input.dataFim}`);
|
|
409
|
-
if (queryParams.length > 0) {
|
|
410
|
-
endpoint += "?" + queryParams.join("&");
|
|
411
|
-
}
|
|
412
|
-
const response = await apiRequest(endpoint);
|
|
413
|
-
let votacoes = [];
|
|
414
|
-
if (response.VotacaoParlamentar?.Parlamentar?.Votacoes?.Votacao) {
|
|
415
|
-
votacoes = response.VotacaoParlamentar.Parlamentar.Votacoes.Votacao;
|
|
416
|
-
} else if (response.VotacoesParlamentar?.Votacoes?.Votacao) {
|
|
417
|
-
votacoes = response.VotacoesParlamentar.Votacoes.Votacao;
|
|
418
|
-
}
|
|
419
|
-
if (!Array.isArray(votacoes)) {
|
|
420
|
-
votacoes = votacoes ? [votacoes] : [];
|
|
421
|
-
}
|
|
422
|
-
const votos = votacoes.map(parseVotoSenador);
|
|
423
|
-
const result = createSuccessResponse(
|
|
424
|
-
{ count: votos.length, votos },
|
|
425
|
-
endpoint
|
|
426
|
-
);
|
|
427
|
-
return {
|
|
428
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
429
|
-
};
|
|
430
|
-
} catch (error) {
|
|
431
|
-
logger.error({ error }, "Error getting senator votes");
|
|
432
|
-
const errorResult = createErrorResponse(
|
|
433
|
-
"ERRO_VOTACOES_SENADOR",
|
|
434
|
-
error instanceof Error ? error.message : "Erro ao obter vota\xE7\xF5es",
|
|
435
|
-
"Verifique se o c\xF3digo do senador est\xE1 correto"
|
|
436
|
-
);
|
|
437
|
-
return {
|
|
438
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// src/tools/materias.ts
|
|
446
|
-
var import_zod4 = require("zod");
|
|
447
|
-
|
|
448
|
-
// src/schemas/materia.ts
|
|
449
|
-
var import_zod3 = require("zod");
|
|
450
|
-
var BuscarMateriasInput = import_zod3.z.object({
|
|
451
|
-
sigla: import_zod3.z.string().toUpperCase().optional().describe("Tipo: PEC, PL, PLP, MPV, PDL, PRS, etc."),
|
|
452
|
-
numero: import_zod3.z.number().int().positive().optional().describe("N\xFAmero da mat\xE9ria"),
|
|
453
|
-
ano: import_zod3.z.number().int().min(1900).max(2100).optional().describe("Ano da mat\xE9ria"),
|
|
454
|
-
tramitando: import_zod3.z.boolean().optional().describe("Apenas em tramita\xE7\xE3o"),
|
|
455
|
-
palavraChave: import_zod3.z.string().optional().describe("Busca na ementa"),
|
|
456
|
-
autorNome: import_zod3.z.string().optional().describe("Nome do autor"),
|
|
457
|
-
relatorNome: import_zod3.z.string().optional().describe("Nome do relator")
|
|
458
|
-
});
|
|
459
|
-
var ObterMateriaInput = import_zod3.z.object({
|
|
460
|
-
codigoMateria: import_zod3.z.number().int().positive().describe("C\xF3digo \xFAnico da mat\xE9ria")
|
|
461
|
-
});
|
|
462
|
-
var TramitacaoMateriaInput = import_zod3.z.object({
|
|
463
|
-
codigoMateria: import_zod3.z.number().int().positive().describe("C\xF3digo \xFAnico da mat\xE9ria")
|
|
464
|
-
});
|
|
465
|
-
var TextosMateriaInput = import_zod3.z.object({
|
|
466
|
-
codigoMateria: import_zod3.z.number().int().positive().describe("C\xF3digo \xFAnico da mat\xE9ria")
|
|
467
|
-
});
|
|
468
|
-
var VotosMateriaInput = import_zod3.z.object({
|
|
469
|
-
codigoMateria: import_zod3.z.number().int().positive().describe("C\xF3digo \xFAnico da mat\xE9ria")
|
|
470
|
-
});
|
|
471
|
-
var MateriaResumo = import_zod3.z.object({
|
|
472
|
-
codigo: import_zod3.z.number(),
|
|
473
|
-
sigla: import_zod3.z.string(),
|
|
474
|
-
numero: import_zod3.z.number(),
|
|
475
|
-
ano: import_zod3.z.number(),
|
|
476
|
-
ementa: import_zod3.z.string().nullable(),
|
|
477
|
-
autor: import_zod3.z.string().nullable(),
|
|
478
|
-
situacao: import_zod3.z.string().nullable(),
|
|
479
|
-
dataApresentacao: import_zod3.z.string().nullable(),
|
|
480
|
-
url: import_zod3.z.string().nullable()
|
|
481
|
-
});
|
|
482
|
-
var MateriaDetalhe = import_zod3.z.object({
|
|
483
|
-
codigo: import_zod3.z.number(),
|
|
484
|
-
sigla: import_zod3.z.string(),
|
|
485
|
-
numero: import_zod3.z.number(),
|
|
486
|
-
ano: import_zod3.z.number(),
|
|
487
|
-
ementa: import_zod3.z.string().nullable(),
|
|
488
|
-
ementaDetalhada: import_zod3.z.string().nullable(),
|
|
489
|
-
autor: import_zod3.z.string().nullable(),
|
|
490
|
-
tipoAutor: import_zod3.z.string().nullable(),
|
|
491
|
-
situacao: import_zod3.z.string().nullable(),
|
|
492
|
-
localAtual: import_zod3.z.string().nullable(),
|
|
493
|
-
dataApresentacao: import_zod3.z.string().nullable(),
|
|
494
|
-
dataUltimaAtualizacao: import_zod3.z.string().nullable(),
|
|
495
|
-
indexacao: import_zod3.z.string().nullable(),
|
|
496
|
-
url: import_zod3.z.string().nullable(),
|
|
497
|
-
relator: import_zod3.z.object({
|
|
498
|
-
nome: import_zod3.z.string(),
|
|
499
|
-
partido: import_zod3.z.string().nullable(),
|
|
500
|
-
uf: import_zod3.z.string().nullable()
|
|
501
|
-
}).nullable()
|
|
502
|
-
});
|
|
503
|
-
var Tramitacao = import_zod3.z.object({
|
|
504
|
-
data: import_zod3.z.string(),
|
|
505
|
-
local: import_zod3.z.string().nullable(),
|
|
506
|
-
situacao: import_zod3.z.string().nullable(),
|
|
507
|
-
descricao: import_zod3.z.string().nullable()
|
|
508
|
-
});
|
|
509
|
-
var TextoMateria = import_zod3.z.object({
|
|
510
|
-
tipo: import_zod3.z.string(),
|
|
511
|
-
data: import_zod3.z.string().nullable(),
|
|
512
|
-
url: import_zod3.z.string(),
|
|
513
|
-
formato: import_zod3.z.string().nullable()
|
|
514
|
-
});
|
|
515
|
-
var VotacaoMateria = import_zod3.z.object({
|
|
516
|
-
codigoVotacao: import_zod3.z.number(),
|
|
517
|
-
data: import_zod3.z.string(),
|
|
518
|
-
descricao: import_zod3.z.string().nullable(),
|
|
519
|
-
resultado: import_zod3.z.string().nullable(),
|
|
520
|
-
totalSim: import_zod3.z.number().nullable(),
|
|
521
|
-
totalNao: import_zod3.z.number().nullable(),
|
|
522
|
-
totalAbstencao: import_zod3.z.number().nullable()
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
// src/tools/materias.ts
|
|
526
|
-
function parseMateriaResumo(materia) {
|
|
527
|
-
const identificacao = materia.IdentificacaoMateria || materia;
|
|
528
|
-
return {
|
|
529
|
-
codigo: parseInt(identificacao.CodigoMateria || materia.CodigoMateria || "0"),
|
|
530
|
-
sigla: identificacao.SiglaSubtipoMateria || materia.SiglaMateria || "",
|
|
531
|
-
numero: parseInt(identificacao.NumeroMateria || materia.NumeroMateria || "0"),
|
|
532
|
-
ano: parseInt(identificacao.AnoMateria || materia.AnoMateria || "0"),
|
|
533
|
-
ementa: materia.EmentaMateria || materia.Ementa || null,
|
|
534
|
-
autor: materia.AutorPrincipal?.NomeAutor || materia.Autor || null,
|
|
535
|
-
situacao: materia.SituacaoAtual?.DescricaoSituacao || materia.Situacao || null,
|
|
536
|
-
dataApresentacao: materia.DataApresentacao || null,
|
|
537
|
-
url: materia.UrlDetalheMateria || null
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
function parseMateriaDetalhe(dados) {
|
|
541
|
-
const materia = dados.Materia || dados;
|
|
542
|
-
const identificacao = materia.IdentificacaoMateria || {};
|
|
543
|
-
const dadosBasicos = materia.DadosBasicosMateria || {};
|
|
544
|
-
const situacao = materia.SituacaoAtual || {};
|
|
545
|
-
const autoria = materia.Autoria || {};
|
|
546
|
-
let relator = null;
|
|
547
|
-
if (materia.Relator || situacao.Relator) {
|
|
548
|
-
const rel = materia.Relator || situacao.Relator;
|
|
549
|
-
relator = {
|
|
550
|
-
nome: rel.NomeRelator || rel.NomeParlamentar || "",
|
|
551
|
-
partido: rel.SiglaPartido || null,
|
|
552
|
-
uf: rel.UfRelator || null
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
return {
|
|
556
|
-
codigo: parseInt(identificacao.CodigoMateria || "0"),
|
|
557
|
-
sigla: identificacao.SiglaSubtipoMateria || "",
|
|
558
|
-
numero: parseInt(identificacao.NumeroMateria || "0"),
|
|
559
|
-
ano: parseInt(identificacao.AnoMateria || "0"),
|
|
560
|
-
ementa: dadosBasicos.EmentaMateria || null,
|
|
561
|
-
ementaDetalhada: dadosBasicos.ExplicacaoEmentaMateria || null,
|
|
562
|
-
autor: autoria.Autor?.[0]?.NomeAutor || autoria.AutorPrincipal?.NomeAutor || null,
|
|
563
|
-
tipoAutor: autoria.Autor?.[0]?.TipoAutor || null,
|
|
564
|
-
situacao: situacao.DescricaoSituacao || null,
|
|
565
|
-
localAtual: situacao.Local?.NomeLocal || situacao.NomeLocal || null,
|
|
566
|
-
dataApresentacao: dadosBasicos.DataApresentacao || null,
|
|
567
|
-
dataUltimaAtualizacao: materia.DataUltimaAtualizacao || null,
|
|
568
|
-
indexacao: dadosBasicos.IndexacaoMateria || null,
|
|
569
|
-
url: identificacao.UrlDetalheMateria || null,
|
|
570
|
-
relator
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
function parseTramitacao(tram) {
|
|
574
|
-
return {
|
|
575
|
-
data: tram.DataTramitacao || tram.Data || "",
|
|
576
|
-
local: tram.Local?.NomeLocal || tram.DescricaoLocal || null,
|
|
577
|
-
situacao: tram.Situacao?.DescricaoSituacao || tram.DescricaoSituacao || null,
|
|
578
|
-
descricao: tram.TextoTramitacao || tram.Descricao || null
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
function parseTextoMateria(texto) {
|
|
582
|
-
return {
|
|
583
|
-
tipo: texto.TipoTexto?.DescricaoTipoTexto || texto.DescricaoTipoTexto || "Texto",
|
|
584
|
-
data: texto.DataTexto || null,
|
|
585
|
-
url: texto.UrlTexto || "",
|
|
586
|
-
formato: texto.FormatoTexto || texto.TipoDocumento || null
|
|
587
|
-
};
|
|
588
|
-
}
|
|
589
|
-
function parseVotacaoMateria(votacao) {
|
|
590
|
-
return {
|
|
591
|
-
codigoVotacao: parseInt(votacao.CodigoSessaoVotacao || votacao.CodigoVotacao || "0"),
|
|
592
|
-
data: votacao.DataSessao || votacao.Data || "",
|
|
593
|
-
descricao: votacao.DescricaoVotacao || votacao.Descricao || null,
|
|
594
|
-
resultado: votacao.DescricaoResultado || votacao.Resultado || null,
|
|
595
|
-
totalSim: votacao.TotalVotosSim ? parseInt(votacao.TotalVotosSim) : null,
|
|
596
|
-
totalNao: votacao.TotalVotosNao ? parseInt(votacao.TotalVotosNao) : null,
|
|
597
|
-
totalAbstencao: votacao.TotalVotosAbstencao ? parseInt(votacao.TotalVotosAbstencao) : null
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
function registerMateriasTools(server) {
|
|
601
|
-
server.tool(
|
|
602
|
-
"senado_buscar_materias",
|
|
603
|
-
"Busca mat\xE9rias legislativas por diversos crit\xE9rios: tipo (PEC, PL, PLP, MPV), n\xFAmero, ano, palavras-chave, autor ou relator.",
|
|
604
|
-
{
|
|
605
|
-
sigla: import_zod4.z.string().optional().describe("Tipo: PEC, PL, PLP, MPV, PDL, PRS, etc."),
|
|
606
|
-
numero: import_zod4.z.number().int().positive().optional().describe("N\xFAmero da mat\xE9ria"),
|
|
607
|
-
ano: import_zod4.z.number().int().min(1900).max(2100).optional().describe("Ano da mat\xE9ria"),
|
|
608
|
-
tramitando: import_zod4.z.boolean().optional().describe("Apenas em tramita\xE7\xE3o"),
|
|
609
|
-
palavraChave: import_zod4.z.string().optional().describe("Busca na ementa"),
|
|
610
|
-
autorNome: import_zod4.z.string().optional().describe("Nome do autor"),
|
|
611
|
-
relatorNome: import_zod4.z.string().optional().describe("Nome do relator")
|
|
612
|
-
},
|
|
613
|
-
async (params) => {
|
|
614
|
-
try {
|
|
615
|
-
const input = BuscarMateriasInput.parse(params);
|
|
616
|
-
logger.info({ input }, "Searching materias");
|
|
617
|
-
const queryParams = [];
|
|
618
|
-
if (input.sigla) queryParams.push(`sigla=${input.sigla.toUpperCase()}`);
|
|
619
|
-
if (input.numero) queryParams.push(`numero=${input.numero}`);
|
|
620
|
-
if (input.ano) queryParams.push(`ano=${input.ano}`);
|
|
621
|
-
if (input.tramitando !== void 0) queryParams.push(`tramitando=${input.tramitando ? "S" : "N"}`);
|
|
622
|
-
if (input.palavraChave) queryParams.push(`palavraChave=${encodeURIComponent(input.palavraChave)}`);
|
|
623
|
-
if (input.autorNome) queryParams.push(`nomeAutor=${encodeURIComponent(input.autorNome)}`);
|
|
624
|
-
if (input.relatorNome) queryParams.push(`nomeRelator=${encodeURIComponent(input.relatorNome)}`);
|
|
625
|
-
const endpoint = ENDPOINTS.MATERIAS_PESQUISA + (queryParams.length > 0 ? "?" + queryParams.join("&") : "");
|
|
626
|
-
const response = await apiRequest(endpoint);
|
|
627
|
-
let materias = [];
|
|
628
|
-
if (response.PesquisaBasicaMateria?.Materias?.Materia) {
|
|
629
|
-
materias = response.PesquisaBasicaMateria.Materias.Materia;
|
|
630
|
-
} else if (response.ListaMaterias?.Materias?.Materia) {
|
|
631
|
-
materias = response.ListaMaterias.Materias.Materia;
|
|
632
|
-
}
|
|
633
|
-
if (!Array.isArray(materias)) {
|
|
634
|
-
materias = materias ? [materias] : [];
|
|
635
|
-
}
|
|
636
|
-
const materiasFormatadas = materias.map(parseMateriaResumo);
|
|
637
|
-
const result = createSuccessResponse(
|
|
638
|
-
{ count: materiasFormatadas.length, materias: materiasFormatadas },
|
|
639
|
-
endpoint
|
|
640
|
-
);
|
|
641
|
-
return {
|
|
642
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
643
|
-
};
|
|
644
|
-
} catch (error) {
|
|
645
|
-
logger.error({ error }, "Error searching materias");
|
|
646
|
-
const errorResult = createErrorResponse(
|
|
647
|
-
"ERRO_BUSCAR_MATERIAS",
|
|
648
|
-
error instanceof Error ? error.message : "Erro na busca",
|
|
649
|
-
"Verifique os par\xE2metros e tente novamente. Use senado_tipos_materia para ver tipos v\xE1lidos."
|
|
650
|
-
);
|
|
651
|
-
return {
|
|
652
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
);
|
|
657
|
-
server.tool(
|
|
658
|
-
"senado_obter_materia",
|
|
659
|
-
"Obt\xE9m detalhes completos de uma mat\xE9ria legislativa, incluindo ementa, autoria, situa\xE7\xE3o atual e relator.",
|
|
660
|
-
{
|
|
661
|
-
codigoMateria: import_zod4.z.number().int().positive().describe("C\xF3digo \xFAnico da mat\xE9ria")
|
|
662
|
-
},
|
|
663
|
-
async (params) => {
|
|
664
|
-
try {
|
|
665
|
-
const input = ObterMateriaInput.parse(params);
|
|
666
|
-
logger.info({ input }, "Getting materia details");
|
|
667
|
-
const endpoint = ENDPOINTS.MATERIA(input.codigoMateria);
|
|
668
|
-
const response = await apiRequest(endpoint);
|
|
669
|
-
const dados = response.DetalheMateria || response;
|
|
670
|
-
const materia = parseMateriaDetalhe(dados);
|
|
671
|
-
const result = createSuccessResponse(materia, endpoint);
|
|
672
|
-
return {
|
|
673
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
674
|
-
};
|
|
675
|
-
} catch (error) {
|
|
676
|
-
logger.error({ error }, "Error getting materia details");
|
|
677
|
-
const errorResult = createErrorResponse(
|
|
678
|
-
"MATERIA_NAO_ENCONTRADA",
|
|
679
|
-
error instanceof Error ? error.message : "Mat\xE9ria n\xE3o encontrada",
|
|
680
|
-
"Use senado_buscar_materias para encontrar o c\xF3digo correto"
|
|
681
|
-
);
|
|
682
|
-
return {
|
|
683
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
);
|
|
688
|
-
server.tool(
|
|
689
|
-
"senado_tramitacao_materia",
|
|
690
|
-
"Obt\xE9m hist\xF3rico de tramita\xE7\xE3o de uma mat\xE9ria, mostrando todas as movimenta\xE7\xF5es em ordem cronol\xF3gica.",
|
|
691
|
-
{
|
|
692
|
-
codigoMateria: import_zod4.z.number().int().positive().describe("C\xF3digo \xFAnico da mat\xE9ria")
|
|
693
|
-
},
|
|
694
|
-
async (params) => {
|
|
695
|
-
try {
|
|
696
|
-
const input = TramitacaoMateriaInput.parse(params);
|
|
697
|
-
logger.info({ input }, "Getting materia tramitacao");
|
|
698
|
-
const endpoint = ENDPOINTS.MATERIA(input.codigoMateria);
|
|
699
|
-
const response = await apiRequest(endpoint);
|
|
700
|
-
const dados = response.DetalheMateria?.Materia || response.Materia || response;
|
|
701
|
-
let tramitacoes = [];
|
|
702
|
-
if (dados.Tramitacoes?.Tramitacao) {
|
|
703
|
-
tramitacoes = dados.Tramitacoes.Tramitacao;
|
|
704
|
-
} else if (dados.HistoricoTramitacao?.Tramitacao) {
|
|
705
|
-
tramitacoes = dados.HistoricoTramitacao.Tramitacao;
|
|
706
|
-
}
|
|
707
|
-
if (!Array.isArray(tramitacoes)) {
|
|
708
|
-
tramitacoes = tramitacoes ? [tramitacoes] : [];
|
|
709
|
-
}
|
|
710
|
-
const tramitacoesFormatadas = tramitacoes.map(parseTramitacao);
|
|
711
|
-
const result = createSuccessResponse(
|
|
712
|
-
{
|
|
713
|
-
codigoMateria: input.codigoMateria,
|
|
714
|
-
count: tramitacoesFormatadas.length,
|
|
715
|
-
tramitacoes: tramitacoesFormatadas
|
|
716
|
-
},
|
|
717
|
-
endpoint
|
|
718
|
-
);
|
|
719
|
-
return {
|
|
720
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
721
|
-
};
|
|
722
|
-
} catch (error) {
|
|
723
|
-
logger.error({ error }, "Error getting tramitacao");
|
|
724
|
-
const errorResult = createErrorResponse(
|
|
725
|
-
"ERRO_TRAMITACAO",
|
|
726
|
-
error instanceof Error ? error.message : "Erro ao obter tramita\xE7\xE3o",
|
|
727
|
-
"Verifique se o c\xF3digo da mat\xE9ria est\xE1 correto"
|
|
728
|
-
);
|
|
729
|
-
return {
|
|
730
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
);
|
|
735
|
-
server.tool(
|
|
736
|
-
"senado_textos_materia",
|
|
737
|
-
"Obt\xE9m textos dispon\xEDveis de uma mat\xE9ria (inicial, substitutivo, final) com URLs para download.",
|
|
738
|
-
{
|
|
739
|
-
codigoMateria: import_zod4.z.number().int().positive().describe("C\xF3digo \xFAnico da mat\xE9ria")
|
|
740
|
-
},
|
|
741
|
-
async (params) => {
|
|
742
|
-
try {
|
|
743
|
-
const input = TextosMateriaInput.parse(params);
|
|
744
|
-
logger.info({ input }, "Getting materia texts");
|
|
745
|
-
const endpoint = ENDPOINTS.MATERIA_TEXTOS(input.codigoMateria);
|
|
746
|
-
const response = await apiRequest(endpoint);
|
|
747
|
-
let textos = [];
|
|
748
|
-
if (response.TextoMateria?.Materia?.Textos?.Texto) {
|
|
749
|
-
textos = response.TextoMateria.Materia.Textos.Texto;
|
|
750
|
-
} else if (response.Textos?.Texto) {
|
|
751
|
-
textos = response.Textos.Texto;
|
|
752
|
-
}
|
|
753
|
-
if (!Array.isArray(textos)) {
|
|
754
|
-
textos = textos ? [textos] : [];
|
|
755
|
-
}
|
|
756
|
-
const textosFormatados = textos.map(parseTextoMateria);
|
|
757
|
-
const result = createSuccessResponse(
|
|
758
|
-
{
|
|
759
|
-
codigoMateria: input.codigoMateria,
|
|
760
|
-
count: textosFormatados.length,
|
|
761
|
-
textos: textosFormatados
|
|
762
|
-
},
|
|
763
|
-
endpoint
|
|
764
|
-
);
|
|
765
|
-
return {
|
|
766
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
767
|
-
};
|
|
768
|
-
} catch (error) {
|
|
769
|
-
logger.error({ error }, "Error getting materia texts");
|
|
770
|
-
const errorResult = createErrorResponse(
|
|
771
|
-
"ERRO_TEXTOS_MATERIA",
|
|
772
|
-
error instanceof Error ? error.message : "Erro ao obter textos",
|
|
773
|
-
"Nem todas as mat\xE9rias possuem textos dispon\xEDveis"
|
|
774
|
-
);
|
|
775
|
-
return {
|
|
776
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
);
|
|
781
|
-
server.tool(
|
|
782
|
-
"senado_votos_materia",
|
|
783
|
-
"Obt\xE9m resultado de vota\xE7\xF5es de uma mat\xE9ria, incluindo placar e votos nominais quando dispon\xEDveis.",
|
|
784
|
-
{
|
|
785
|
-
codigoMateria: import_zod4.z.number().int().positive().describe("C\xF3digo \xFAnico da mat\xE9ria")
|
|
786
|
-
},
|
|
787
|
-
async (params) => {
|
|
788
|
-
try {
|
|
789
|
-
const input = VotosMateriaInput.parse(params);
|
|
790
|
-
logger.info({ input }, "Getting materia votes");
|
|
791
|
-
const endpoint = ENDPOINTS.MATERIA_VOTACOES(input.codigoMateria);
|
|
792
|
-
const response = await apiRequest(endpoint);
|
|
793
|
-
let votacoes = [];
|
|
794
|
-
if (response.VotacaoMateria?.Materia?.Votacoes?.Votacao) {
|
|
795
|
-
votacoes = response.VotacaoMateria.Materia.Votacoes.Votacao;
|
|
796
|
-
} else if (response.Votacoes?.Votacao) {
|
|
797
|
-
votacoes = response.Votacoes.Votacao;
|
|
798
|
-
}
|
|
799
|
-
if (!Array.isArray(votacoes)) {
|
|
800
|
-
votacoes = votacoes ? [votacoes] : [];
|
|
801
|
-
}
|
|
802
|
-
const votacoesFormatadas = votacoes.map(parseVotacaoMateria);
|
|
803
|
-
const result = createSuccessResponse(
|
|
804
|
-
{
|
|
805
|
-
codigoMateria: input.codigoMateria,
|
|
806
|
-
count: votacoesFormatadas.length,
|
|
807
|
-
votacoes: votacoesFormatadas
|
|
808
|
-
},
|
|
809
|
-
endpoint
|
|
810
|
-
);
|
|
811
|
-
return {
|
|
812
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
813
|
-
};
|
|
814
|
-
} catch (error) {
|
|
815
|
-
logger.error({ error }, "Error getting materia votes");
|
|
816
|
-
const errorResult = createErrorResponse(
|
|
817
|
-
"ERRO_VOTOS_MATERIA",
|
|
818
|
-
error instanceof Error ? error.message : "Erro ao obter vota\xE7\xF5es",
|
|
819
|
-
"Nem todas as mat\xE9rias possuem vota\xE7\xF5es registradas"
|
|
820
|
-
);
|
|
821
|
-
return {
|
|
822
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
823
|
-
};
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
// src/tools/votacoes.ts
|
|
830
|
-
var import_zod6 = require("zod");
|
|
831
|
-
|
|
832
|
-
// src/schemas/votacao.ts
|
|
833
|
-
var import_zod5 = require("zod");
|
|
834
|
-
var ListarVotacoesInput = import_zod5.z.object({
|
|
835
|
-
ano: import_zod5.z.number().int().min(1900).max(2100).describe("Ano das vota\xE7\xF5es (obrigat\xF3rio)"),
|
|
836
|
-
mes: import_zod5.z.number().int().min(1).max(12).optional().describe("M\xEAs (1-12)"),
|
|
837
|
-
dataInicio: import_zod5.z.string().regex(/^\d{8}$/, "Formato deve ser YYYYMMDD").optional().describe("Data in\xEDcio (YYYYMMDD)"),
|
|
838
|
-
dataFim: import_zod5.z.string().regex(/^\d{8}$/, "Formato deve ser YYYYMMDD").optional().describe("Data fim (YYYYMMDD)")
|
|
839
|
-
});
|
|
840
|
-
var ObterVotacaoInput = import_zod5.z.object({
|
|
841
|
-
codigoVotacao: import_zod5.z.number().int().positive().describe("C\xF3digo \xFAnico da vota\xE7\xE3o")
|
|
842
|
-
});
|
|
843
|
-
var VotacoesRecentesInput = import_zod5.z.object({
|
|
844
|
-
dias: import_zod5.z.number().int().min(1).max(365).optional().default(7).describe("Quantidade de dias (padr\xE3o: 7)")
|
|
845
|
-
});
|
|
846
|
-
var VotacaoResumo = import_zod5.z.object({
|
|
847
|
-
codigo: import_zod5.z.number(),
|
|
848
|
-
data: import_zod5.z.string(),
|
|
849
|
-
hora: import_zod5.z.string().nullable(),
|
|
850
|
-
materia: import_zod5.z.string().nullable(),
|
|
851
|
-
descricao: import_zod5.z.string().nullable(),
|
|
852
|
-
resultado: import_zod5.z.string().nullable(),
|
|
853
|
-
totalSim: import_zod5.z.number().nullable(),
|
|
854
|
-
totalNao: import_zod5.z.number().nullable(),
|
|
855
|
-
totalAbstencao: import_zod5.z.number().nullable()
|
|
856
|
-
});
|
|
857
|
-
var VotoNominal = import_zod5.z.object({
|
|
858
|
-
codigoSenador: import_zod5.z.number(),
|
|
859
|
-
nomeSenador: import_zod5.z.string(),
|
|
860
|
-
partido: import_zod5.z.string().nullable(),
|
|
861
|
-
uf: import_zod5.z.string().nullable(),
|
|
862
|
-
voto: import_zod5.z.string()
|
|
863
|
-
});
|
|
864
|
-
var VotacaoDetalhe = import_zod5.z.object({
|
|
865
|
-
codigo: import_zod5.z.number(),
|
|
866
|
-
data: import_zod5.z.string(),
|
|
867
|
-
hora: import_zod5.z.string().nullable(),
|
|
868
|
-
materia: import_zod5.z.object({
|
|
869
|
-
codigo: import_zod5.z.number().nullable(),
|
|
870
|
-
sigla: import_zod5.z.string().nullable(),
|
|
871
|
-
numero: import_zod5.z.number().nullable(),
|
|
872
|
-
ano: import_zod5.z.number().nullable(),
|
|
873
|
-
ementa: import_zod5.z.string().nullable()
|
|
874
|
-
}).nullable(),
|
|
875
|
-
descricao: import_zod5.z.string().nullable(),
|
|
876
|
-
resultado: import_zod5.z.string().nullable(),
|
|
877
|
-
totalSim: import_zod5.z.number().nullable(),
|
|
878
|
-
totalNao: import_zod5.z.number().nullable(),
|
|
879
|
-
totalAbstencao: import_zod5.z.number().nullable(),
|
|
880
|
-
totalPresente: import_zod5.z.number().nullable(),
|
|
881
|
-
votos: import_zod5.z.array(VotoNominal).optional()
|
|
882
|
-
});
|
|
883
|
-
|
|
884
|
-
// src/tools/votacoes.ts
|
|
885
|
-
function parseVotacaoResumo(votacao) {
|
|
886
|
-
const sessao = votacao.SessaoPlenaria || votacao;
|
|
887
|
-
const materia = votacao.IdentificacaoMateria || {};
|
|
888
|
-
return {
|
|
889
|
-
codigo: parseInt(votacao.CodigoSessaoVotacao || votacao.CodigoVotacao || sessao.CodigoSessao || "0"),
|
|
890
|
-
data: sessao.DataSessao || votacao.DataSessao || votacao.Data || "",
|
|
891
|
-
hora: sessao.HoraInicioSessao || votacao.Hora || null,
|
|
892
|
-
materia: materia.DescricaoIdentificacaoMateria || `${materia.SiglaSubtipoMateria || ""} ${materia.NumeroMateria || ""}/${materia.AnoMateria || ""}`.trim() || null,
|
|
893
|
-
descricao: votacao.DescricaoVotacao || sessao.DescricaoTipoSessao || null,
|
|
894
|
-
resultado: votacao.DescricaoResultado || votacao.Resultado || null,
|
|
895
|
-
totalSim: votacao.TotalVotosSim ? parseInt(votacao.TotalVotosSim) : null,
|
|
896
|
-
totalNao: votacao.TotalVotosNao ? parseInt(votacao.TotalVotosNao) : null,
|
|
897
|
-
totalAbstencao: votacao.TotalVotosAbstencao ? parseInt(votacao.TotalVotosAbstencao) : null
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
function parseVotoNominal(voto) {
|
|
901
|
-
const identificacao = voto.IdentificacaoParlamentar || voto;
|
|
902
|
-
return {
|
|
903
|
-
codigoSenador: parseInt(identificacao.CodigoParlamentar || voto.CodigoParlamentar || "0"),
|
|
904
|
-
nomeSenador: identificacao.NomeParlamentar || voto.NomeParlamentar || "",
|
|
905
|
-
partido: identificacao.SiglaPartidoParlamentar || voto.SiglaPartido || null,
|
|
906
|
-
uf: identificacao.UfParlamentar || voto.SiglaUf || null,
|
|
907
|
-
voto: voto.SiglaDescricaoVoto || voto.DescricaoVoto || voto.Voto || ""
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
function parseVotacaoDetalhe(dados) {
|
|
911
|
-
const votacao = dados.Votacao || dados;
|
|
912
|
-
const sessao = votacao.SessaoPlenaria || votacao;
|
|
913
|
-
const materiaData = votacao.IdentificacaoMateria || votacao.Materia || {};
|
|
914
|
-
let votos;
|
|
915
|
-
if (votacao.Votos?.VotoParlamentar) {
|
|
916
|
-
let votosArray = votacao.Votos.VotoParlamentar;
|
|
917
|
-
if (!Array.isArray(votosArray)) {
|
|
918
|
-
votosArray = [votosArray];
|
|
919
|
-
}
|
|
920
|
-
votos = votosArray.map(parseVotoNominal);
|
|
921
|
-
}
|
|
922
|
-
return {
|
|
923
|
-
codigo: parseInt(votacao.CodigoSessaoVotacao || votacao.CodigoVotacao || sessao.CodigoSessao || "0"),
|
|
924
|
-
data: sessao.DataSessao || votacao.DataSessao || votacao.Data || "",
|
|
925
|
-
hora: sessao.HoraInicioSessao || votacao.Hora || null,
|
|
926
|
-
materia: materiaData.CodigoMateria ? {
|
|
927
|
-
codigo: parseInt(materiaData.CodigoMateria) || null,
|
|
928
|
-
sigla: materiaData.SiglaSubtipoMateria || materiaData.SiglaMateria || null,
|
|
929
|
-
numero: materiaData.NumeroMateria ? parseInt(materiaData.NumeroMateria) : null,
|
|
930
|
-
ano: materiaData.AnoMateria ? parseInt(materiaData.AnoMateria) : null,
|
|
931
|
-
ementa: materiaData.EmentaMateria || materiaData.Ementa || null
|
|
932
|
-
} : null,
|
|
933
|
-
descricao: votacao.DescricaoVotacao || sessao.DescricaoTipoSessao || null,
|
|
934
|
-
resultado: votacao.DescricaoResultado || votacao.Resultado || null,
|
|
935
|
-
totalSim: votacao.TotalVotosSim ? parseInt(votacao.TotalVotosSim) : null,
|
|
936
|
-
totalNao: votacao.TotalVotosNao ? parseInt(votacao.TotalVotosNao) : null,
|
|
937
|
-
totalAbstencao: votacao.TotalVotosAbstencao ? parseInt(votacao.TotalVotosAbstencao) : null,
|
|
938
|
-
totalPresente: votacao.TotalVotosPresente ? parseInt(votacao.TotalVotosPresente) : null,
|
|
939
|
-
votos
|
|
940
|
-
};
|
|
941
|
-
}
|
|
942
|
-
function registerVotacoesTools(server) {
|
|
943
|
-
server.tool(
|
|
944
|
-
"senado_listar_votacoes",
|
|
945
|
-
"Lista vota\xE7\xF5es do plen\xE1rio do Senado por ano, podendo filtrar por m\xEAs ou per\xEDodo espec\xEDfico.",
|
|
946
|
-
{
|
|
947
|
-
ano: import_zod6.z.number().int().min(1900).max(2100).describe("Ano das vota\xE7\xF5es (obrigat\xF3rio)"),
|
|
948
|
-
mes: import_zod6.z.number().int().min(1).max(12).optional().describe("M\xEAs (1-12)"),
|
|
949
|
-
dataInicio: import_zod6.z.string().regex(/^\d{8}$/).optional().describe("Data in\xEDcio (YYYYMMDD)"),
|
|
950
|
-
dataFim: import_zod6.z.string().regex(/^\d{8}$/).optional().describe("Data fim (YYYYMMDD)")
|
|
951
|
-
},
|
|
952
|
-
async (params) => {
|
|
953
|
-
try {
|
|
954
|
-
const input = ListarVotacoesInput.parse(params);
|
|
955
|
-
logger.info({ input }, "Listing votacoes");
|
|
956
|
-
let endpoint = ENDPOINTS.VOTACOES_ANO(input.ano);
|
|
957
|
-
const queryParams = [];
|
|
958
|
-
if (input.mes) queryParams.push(`mes=${input.mes}`);
|
|
959
|
-
if (input.dataInicio) queryParams.push(`dataInicio=${input.dataInicio}`);
|
|
960
|
-
if (input.dataFim) queryParams.push(`dataFim=${input.dataFim}`);
|
|
961
|
-
if (queryParams.length > 0) {
|
|
962
|
-
endpoint += "?" + queryParams.join("&");
|
|
963
|
-
}
|
|
964
|
-
const response = await apiRequest(endpoint);
|
|
965
|
-
let votacoes = [];
|
|
966
|
-
if (response.ListaVotacoes?.Votacoes?.Votacao) {
|
|
967
|
-
votacoes = response.ListaVotacoes.Votacoes.Votacao;
|
|
968
|
-
} else if (response.VotacoesPlenario?.Votacoes?.Votacao) {
|
|
969
|
-
votacoes = response.VotacoesPlenario.Votacoes.Votacao;
|
|
970
|
-
}
|
|
971
|
-
if (!Array.isArray(votacoes)) {
|
|
972
|
-
votacoes = votacoes ? [votacoes] : [];
|
|
973
|
-
}
|
|
974
|
-
const votacoesFormatadas = votacoes.map(parseVotacaoResumo);
|
|
975
|
-
const result = createSuccessResponse(
|
|
976
|
-
{
|
|
977
|
-
ano: input.ano,
|
|
978
|
-
count: votacoesFormatadas.length,
|
|
979
|
-
votacoes: votacoesFormatadas
|
|
980
|
-
},
|
|
981
|
-
endpoint
|
|
982
|
-
);
|
|
983
|
-
return {
|
|
984
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
985
|
-
};
|
|
986
|
-
} catch (error) {
|
|
987
|
-
logger.error({ error }, "Error listing votacoes");
|
|
988
|
-
const errorResult = createErrorResponse(
|
|
989
|
-
"ERRO_LISTAR_VOTACOES",
|
|
990
|
-
error instanceof Error ? error.message : "Erro ao listar vota\xE7\xF5es",
|
|
991
|
-
"Verifique se o ano est\xE1 correto"
|
|
992
|
-
);
|
|
993
|
-
return {
|
|
994
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
995
|
-
};
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
);
|
|
999
|
-
server.tool(
|
|
1000
|
-
"senado_obter_votacao",
|
|
1001
|
-
"Obt\xE9m detalhes de uma vota\xE7\xE3o espec\xEDfica, incluindo votos nominais de cada senador.",
|
|
1002
|
-
{
|
|
1003
|
-
codigoVotacao: import_zod6.z.number().int().positive().describe("C\xF3digo \xFAnico da vota\xE7\xE3o")
|
|
1004
|
-
},
|
|
1005
|
-
async (params) => {
|
|
1006
|
-
try {
|
|
1007
|
-
const input = ObterVotacaoInput.parse(params);
|
|
1008
|
-
logger.info({ input }, "Getting votacao details");
|
|
1009
|
-
const endpoint = ENDPOINTS.VOTACAO(input.codigoVotacao);
|
|
1010
|
-
const response = await apiRequest(endpoint);
|
|
1011
|
-
const dados = response.VotacaoPlenario || response;
|
|
1012
|
-
const votacao = parseVotacaoDetalhe(dados);
|
|
1013
|
-
const result = createSuccessResponse(votacao, endpoint);
|
|
1014
|
-
return {
|
|
1015
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1016
|
-
};
|
|
1017
|
-
} catch (error) {
|
|
1018
|
-
logger.error({ error }, "Error getting votacao details");
|
|
1019
|
-
const errorResult = createErrorResponse(
|
|
1020
|
-
"VOTACAO_NAO_ENCONTRADA",
|
|
1021
|
-
error instanceof Error ? error.message : "Vota\xE7\xE3o n\xE3o encontrada",
|
|
1022
|
-
"Use senado_listar_votacoes para encontrar o c\xF3digo correto"
|
|
1023
|
-
);
|
|
1024
|
-
return {
|
|
1025
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
1026
|
-
};
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
);
|
|
1030
|
-
server.tool(
|
|
1031
|
-
"senado_votacoes_recentes",
|
|
1032
|
-
"Obt\xE9m as vota\xE7\xF5es mais recentes do plen\xE1rio (\xFAltimos N dias). \xDAtil para acompanhar atividade legislativa recente.",
|
|
1033
|
-
{
|
|
1034
|
-
dias: import_zod6.z.number().int().min(1).max(365).optional().default(7).describe("Quantidade de dias (padr\xE3o: 7)")
|
|
1035
|
-
},
|
|
1036
|
-
async (params) => {
|
|
1037
|
-
try {
|
|
1038
|
-
const input = VotacoesRecentesInput.parse(params);
|
|
1039
|
-
logger.info({ input }, "Getting recent votacoes");
|
|
1040
|
-
const hoje = /* @__PURE__ */ new Date();
|
|
1041
|
-
const dataInicio = new Date(hoje);
|
|
1042
|
-
dataInicio.setDate(dataInicio.getDate() - (input.dias || 7));
|
|
1043
|
-
const formatDate = (d) => {
|
|
1044
|
-
const year = d.getFullYear();
|
|
1045
|
-
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
1046
|
-
const day = String(d.getDate()).padStart(2, "0");
|
|
1047
|
-
return `${year}${month}${day}`;
|
|
1048
|
-
};
|
|
1049
|
-
const endpoint = `${ENDPOINTS.VOTACOES_ANO(hoje.getFullYear())}?dataInicio=${formatDate(dataInicio)}&dataFim=${formatDate(hoje)}`;
|
|
1050
|
-
const response = await apiRequest(endpoint);
|
|
1051
|
-
let votacoes = [];
|
|
1052
|
-
if (response.ListaVotacoes?.Votacoes?.Votacao) {
|
|
1053
|
-
votacoes = response.ListaVotacoes.Votacoes.Votacao;
|
|
1054
|
-
} else if (response.VotacoesPlenario?.Votacoes?.Votacao) {
|
|
1055
|
-
votacoes = response.VotacoesPlenario.Votacoes.Votacao;
|
|
1056
|
-
}
|
|
1057
|
-
if (!Array.isArray(votacoes)) {
|
|
1058
|
-
votacoes = votacoes ? [votacoes] : [];
|
|
1059
|
-
}
|
|
1060
|
-
const votacoesFormatadas = votacoes.map(parseVotacaoResumo).sort((a, b) => {
|
|
1061
|
-
const dateA = new Date(a.data.split("/").reverse().join("-") || a.data);
|
|
1062
|
-
const dateB = new Date(b.data.split("/").reverse().join("-") || b.data);
|
|
1063
|
-
return dateB.getTime() - dateA.getTime();
|
|
1064
|
-
});
|
|
1065
|
-
const result = createSuccessResponse(
|
|
1066
|
-
{
|
|
1067
|
-
periodo: {
|
|
1068
|
-
dias: input.dias || 7,
|
|
1069
|
-
dataInicio: formatDate(dataInicio),
|
|
1070
|
-
dataFim: formatDate(hoje)
|
|
1
|
+
/**
|
|
2
|
+
* McpServer factory — creates a new instance per request (required by SDK 1.26.0+).
|
|
3
|
+
* Registers all tools from each group module.
|
|
4
|
+
*/
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { registerReferenciaTools } from "./tools/referencia.js";
|
|
8
|
+
import { registerSenadoresTools } from "./tools/senadores.js";
|
|
9
|
+
import { registerMateriasTools } from "./tools/materias.js";
|
|
10
|
+
import { registerVotacoesTools } from "./tools/votacoes.js";
|
|
11
|
+
import { registerComissoesTools } from "./tools/comissoes.js";
|
|
12
|
+
import { registerPlenarioTools } from "./tools/plenario.js";
|
|
13
|
+
import { registerProcessosTools } from "./tools/processos.js";
|
|
14
|
+
import { registerECidadaniaTools } from "./tools/ecidadania.js";
|
|
15
|
+
import { registerDiscursosTools } from "./tools/discursos.js";
|
|
16
|
+
import { registerComposicaoTools } from "./tools/composicao.js";
|
|
17
|
+
import { registerOrcamentoTools } from "./tools/orcamento.js";
|
|
18
|
+
import { registerLegislacaoTools } from "./tools/legislacao.js";
|
|
19
|
+
import { registerVotacaoComissaoTools } from "./tools/votacao-comissao.js";
|
|
20
|
+
import { registerTaquigrafiaTools } from "./tools/taquigrafia.js";
|
|
21
|
+
import { registerSenadoresAdminTools } from "./tools/senadores-admin.js";
|
|
22
|
+
import { registerContratacoesTools } from "./tools/contratacoes.js";
|
|
23
|
+
import { registerServidoresTools } from "./tools/servidores.js";
|
|
24
|
+
import { registerSupridosTools } from "./tools/supridos.js";
|
|
25
|
+
import { registerOrcamentoSenadoTools } from "./tools/orcamento-senado.js";
|
|
26
|
+
import { registerPrompts } from "./prompts.js";
|
|
27
|
+
import { registerResources } from "./resources.js";
|
|
28
|
+
import { instrumentTool } from "./instrument.js";
|
|
29
|
+
import { VERSION } from "./version.js";
|
|
30
|
+
export function createServer(env, ctx) {
|
|
31
|
+
const server = new McpServer({
|
|
32
|
+
name: "senado-br-mcp",
|
|
33
|
+
version: VERSION,
|
|
34
|
+
websiteUrl: "https://github.com/SidneyBissoli/senado-br-mcp-cloudflare",
|
|
35
|
+
icons: [
|
|
36
|
+
{
|
|
37
|
+
src: "https://senado.sidneybissoli.com/icon.jpg",
|
|
38
|
+
mimeType: "image/jpeg",
|
|
39
|
+
sizes: ["512x512"],
|
|
1071
40
|
},
|
|
1072
|
-
|
|
1073
|
-
votacoes: votacoesFormatadas
|
|
1074
|
-
},
|
|
1075
|
-
endpoint
|
|
1076
|
-
);
|
|
1077
|
-
return {
|
|
1078
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1079
|
-
};
|
|
1080
|
-
} catch (error) {
|
|
1081
|
-
logger.error({ error }, "Error getting recent votacoes");
|
|
1082
|
-
const errorResult = createErrorResponse(
|
|
1083
|
-
"ERRO_VOTACOES_RECENTES",
|
|
1084
|
-
error instanceof Error ? error.message : "Erro ao obter vota\xE7\xF5es recentes",
|
|
1085
|
-
"Pode n\xE3o haver vota\xE7\xF5es no per\xEDodo especificado"
|
|
1086
|
-
);
|
|
1087
|
-
return {
|
|
1088
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
1089
|
-
};
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
);
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
// src/tools/comissoes.ts
|
|
1096
|
-
var import_zod8 = require("zod");
|
|
1097
|
-
|
|
1098
|
-
// src/schemas/comissao.ts
|
|
1099
|
-
var import_zod7 = require("zod");
|
|
1100
|
-
var ListarComissoesInput = import_zod7.z.object({
|
|
1101
|
-
tipo: import_zod7.z.enum(["permanente", "temporaria", "cpi", "mista"]).optional().describe("Tipo: permanente, temporaria, cpi, mista"),
|
|
1102
|
-
ativa: import_zod7.z.boolean().optional().describe("Apenas comiss\xF5es ativas")
|
|
1103
|
-
});
|
|
1104
|
-
var ObterComissaoInput = import_zod7.z.object({
|
|
1105
|
-
sigla: import_zod7.z.string().min(2).toUpperCase().describe("Sigla da comiss\xE3o (ex: CCJ, CAE)")
|
|
1106
|
-
});
|
|
1107
|
-
var MembrosComissaoInput = import_zod7.z.object({
|
|
1108
|
-
sigla: import_zod7.z.string().min(2).toUpperCase().describe("Sigla da comiss\xE3o")
|
|
1109
|
-
});
|
|
1110
|
-
var ReunioesComissaoInput = import_zod7.z.object({
|
|
1111
|
-
sigla: import_zod7.z.string().min(2).toUpperCase().describe("Sigla da comiss\xE3o"),
|
|
1112
|
-
dataInicio: import_zod7.z.string().regex(/^\d{8}$/, "Formato deve ser YYYYMMDD").optional().describe("Data in\xEDcio (YYYYMMDD)"),
|
|
1113
|
-
dataFim: import_zod7.z.string().regex(/^\d{8}$/, "Formato deve ser YYYYMMDD").optional().describe("Data fim (YYYYMMDD)")
|
|
1114
|
-
});
|
|
1115
|
-
var ComissaoResumo = import_zod7.z.object({
|
|
1116
|
-
codigo: import_zod7.z.number(),
|
|
1117
|
-
sigla: import_zod7.z.string(),
|
|
1118
|
-
nome: import_zod7.z.string(),
|
|
1119
|
-
tipo: import_zod7.z.string().nullable(),
|
|
1120
|
-
casa: import_zod7.z.string().nullable(),
|
|
1121
|
-
ativa: import_zod7.z.boolean()
|
|
1122
|
-
});
|
|
1123
|
-
var ComissaoDetalhe = import_zod7.z.object({
|
|
1124
|
-
codigo: import_zod7.z.number(),
|
|
1125
|
-
sigla: import_zod7.z.string(),
|
|
1126
|
-
nome: import_zod7.z.string(),
|
|
1127
|
-
tipo: import_zod7.z.string().nullable(),
|
|
1128
|
-
casa: import_zod7.z.string().nullable(),
|
|
1129
|
-
ativa: import_zod7.z.boolean(),
|
|
1130
|
-
dataInicio: import_zod7.z.string().nullable(),
|
|
1131
|
-
dataFim: import_zod7.z.string().nullable(),
|
|
1132
|
-
finalidade: import_zod7.z.string().nullable(),
|
|
1133
|
-
presidente: import_zod7.z.object({
|
|
1134
|
-
codigo: import_zod7.z.number(),
|
|
1135
|
-
nome: import_zod7.z.string(),
|
|
1136
|
-
partido: import_zod7.z.string().nullable(),
|
|
1137
|
-
uf: import_zod7.z.string().nullable()
|
|
1138
|
-
}).nullable(),
|
|
1139
|
-
vicePresidente: import_zod7.z.object({
|
|
1140
|
-
codigo: import_zod7.z.number(),
|
|
1141
|
-
nome: import_zod7.z.string(),
|
|
1142
|
-
partido: import_zod7.z.string().nullable(),
|
|
1143
|
-
uf: import_zod7.z.string().nullable()
|
|
1144
|
-
}).nullable()
|
|
1145
|
-
});
|
|
1146
|
-
var MembroComissao = import_zod7.z.object({
|
|
1147
|
-
codigo: import_zod7.z.number(),
|
|
1148
|
-
nome: import_zod7.z.string(),
|
|
1149
|
-
partido: import_zod7.z.string().nullable(),
|
|
1150
|
-
uf: import_zod7.z.string().nullable(),
|
|
1151
|
-
cargo: import_zod7.z.string().nullable(),
|
|
1152
|
-
titular: import_zod7.z.boolean()
|
|
1153
|
-
});
|
|
1154
|
-
var ReuniaoComissao = import_zod7.z.object({
|
|
1155
|
-
codigo: import_zod7.z.number(),
|
|
1156
|
-
data: import_zod7.z.string(),
|
|
1157
|
-
hora: import_zod7.z.string().nullable(),
|
|
1158
|
-
local: import_zod7.z.string().nullable(),
|
|
1159
|
-
tipo: import_zod7.z.string().nullable(),
|
|
1160
|
-
situacao: import_zod7.z.string().nullable(),
|
|
1161
|
-
pauta: import_zod7.z.string().nullable()
|
|
1162
|
-
});
|
|
1163
|
-
|
|
1164
|
-
// src/tools/comissoes.ts
|
|
1165
|
-
function parseComissaoResumo(comissao) {
|
|
1166
|
-
const identificacao = comissao.IdentificacaoComissao || comissao;
|
|
1167
|
-
return {
|
|
1168
|
-
codigo: parseInt(identificacao.CodigoComissao || comissao.Codigo || "0"),
|
|
1169
|
-
sigla: identificacao.SiglaComissao || comissao.Sigla || "",
|
|
1170
|
-
nome: identificacao.NomeComissao || comissao.Nome || "",
|
|
1171
|
-
tipo: comissao.TipoComissao?.DescricaoTipoComissao || comissao.Tipo || null,
|
|
1172
|
-
casa: identificacao.SiglaCasaComissao || comissao.Casa || null,
|
|
1173
|
-
ativa: comissao.DataFim === null || comissao.DataFim === void 0
|
|
1174
|
-
};
|
|
1175
|
-
}
|
|
1176
|
-
function parseComissaoDetalhe(dados) {
|
|
1177
|
-
const comissao = dados.Comissao || dados;
|
|
1178
|
-
const identificacao = comissao.IdentificacaoComissao || {};
|
|
1179
|
-
let presidente = null;
|
|
1180
|
-
let vicePresidente = null;
|
|
1181
|
-
if (comissao.Presidente || comissao.MembroPresidente) {
|
|
1182
|
-
const pres = comissao.Presidente || comissao.MembroPresidente;
|
|
1183
|
-
const identPres = pres.IdentificacaoParlamentar || pres;
|
|
1184
|
-
presidente = {
|
|
1185
|
-
codigo: parseInt(identPres.CodigoParlamentar || "0"),
|
|
1186
|
-
nome: identPres.NomeParlamentar || pres.NomeParlamentar || "",
|
|
1187
|
-
partido: identPres.SiglaPartidoParlamentar || pres.SiglaPartido || null,
|
|
1188
|
-
uf: identPres.UfParlamentar || pres.UfParlamentar || null
|
|
1189
|
-
};
|
|
1190
|
-
}
|
|
1191
|
-
if (comissao.VicePresidente || comissao.MembroVicePresidente) {
|
|
1192
|
-
const vice = comissao.VicePresidente || comissao.MembroVicePresidente;
|
|
1193
|
-
const identVice = vice.IdentificacaoParlamentar || vice;
|
|
1194
|
-
vicePresidente = {
|
|
1195
|
-
codigo: parseInt(identVice.CodigoParlamentar || "0"),
|
|
1196
|
-
nome: identVice.NomeParlamentar || vice.NomeParlamentar || "",
|
|
1197
|
-
partido: identVice.SiglaPartidoParlamentar || vice.SiglaPartido || null,
|
|
1198
|
-
uf: identVice.UfParlamentar || vice.UfParlamentar || null
|
|
1199
|
-
};
|
|
1200
|
-
}
|
|
1201
|
-
return {
|
|
1202
|
-
codigo: parseInt(identificacao.CodigoComissao || comissao.Codigo || "0"),
|
|
1203
|
-
sigla: identificacao.SiglaComissao || comissao.Sigla || "",
|
|
1204
|
-
nome: identificacao.NomeComissao || comissao.Nome || "",
|
|
1205
|
-
tipo: comissao.TipoComissao?.DescricaoTipoComissao || comissao.Tipo || null,
|
|
1206
|
-
casa: identificacao.SiglaCasaComissao || comissao.Casa || null,
|
|
1207
|
-
ativa: comissao.DataFim === null || comissao.DataFim === void 0,
|
|
1208
|
-
dataInicio: comissao.DataInicio || null,
|
|
1209
|
-
dataFim: comissao.DataFim || null,
|
|
1210
|
-
finalidade: comissao.Finalidade || null,
|
|
1211
|
-
presidente,
|
|
1212
|
-
vicePresidente
|
|
1213
|
-
};
|
|
1214
|
-
}
|
|
1215
|
-
function parseMembroComissao(membro) {
|
|
1216
|
-
const identificacao = membro.IdentificacaoParlamentar || membro;
|
|
1217
|
-
return {
|
|
1218
|
-
codigo: parseInt(identificacao.CodigoParlamentar || membro.CodigoParlamentar || "0"),
|
|
1219
|
-
nome: identificacao.NomeParlamentar || membro.NomeParlamentar || "",
|
|
1220
|
-
partido: identificacao.SiglaPartidoParlamentar || membro.SiglaPartido || null,
|
|
1221
|
-
uf: identificacao.UfParlamentar || membro.UfParlamentar || null,
|
|
1222
|
-
cargo: membro.DescricaoCargo || membro.Cargo || null,
|
|
1223
|
-
titular: membro.DescricaoParticipacao !== "Suplente"
|
|
1224
|
-
};
|
|
1225
|
-
}
|
|
1226
|
-
function parseReuniaoComissao(reuniao) {
|
|
1227
|
-
return {
|
|
1228
|
-
codigo: parseInt(reuniao.CodigoReuniao || reuniao.Codigo || "0"),
|
|
1229
|
-
data: reuniao.DataReuniao || reuniao.Data || "",
|
|
1230
|
-
hora: reuniao.HoraReuniao || reuniao.Hora || null,
|
|
1231
|
-
local: reuniao.LocalReuniao || reuniao.Local || null,
|
|
1232
|
-
tipo: reuniao.TipoReuniao?.DescricaoTipoReuniao || reuniao.Tipo || null,
|
|
1233
|
-
situacao: reuniao.SituacaoReuniao?.DescricaoSituacaoReuniao || reuniao.Situacao || null,
|
|
1234
|
-
pauta: reuniao.Pauta || reuniao.DescricaoPauta || null
|
|
1235
|
-
};
|
|
1236
|
-
}
|
|
1237
|
-
function registerComissoesTools(server) {
|
|
1238
|
-
server.tool(
|
|
1239
|
-
"senado_listar_comissoes",
|
|
1240
|
-
"Lista comiss\xF5es do Senado. Pode filtrar por tipo (permanente, tempor\xE1ria, CPI, mista) e status (ativa/inativa).",
|
|
1241
|
-
{
|
|
1242
|
-
tipo: import_zod8.z.enum(["permanente", "temporaria", "cpi", "mista"]).optional().describe("Tipo: permanente, temporaria, cpi, mista"),
|
|
1243
|
-
ativa: import_zod8.z.boolean().optional().describe("Apenas comiss\xF5es ativas")
|
|
1244
|
-
},
|
|
1245
|
-
async (params) => {
|
|
1246
|
-
try {
|
|
1247
|
-
const input = ListarComissoesInput.parse(params);
|
|
1248
|
-
logger.info({ input }, "Listing comissoes");
|
|
1249
|
-
const endpoint = ENDPOINTS.COMISSOES_LISTA;
|
|
1250
|
-
const response = await apiRequest(endpoint);
|
|
1251
|
-
let comissoes = [];
|
|
1252
|
-
if (response.ListaComissoes?.Comissoes?.Comissao) {
|
|
1253
|
-
comissoes = response.ListaComissoes.Comissoes.Comissao;
|
|
1254
|
-
} else if (response.Comissoes?.Comissao) {
|
|
1255
|
-
comissoes = response.Comissoes.Comissao;
|
|
1256
|
-
}
|
|
1257
|
-
if (!Array.isArray(comissoes)) {
|
|
1258
|
-
comissoes = comissoes ? [comissoes] : [];
|
|
1259
|
-
}
|
|
1260
|
-
let comissoesFormatadas = comissoes.map(parseComissaoResumo);
|
|
1261
|
-
if (input.tipo) {
|
|
1262
|
-
const tipoMap = {
|
|
1263
|
-
"permanente": ["Permanente", "PERMANENTE"],
|
|
1264
|
-
"temporaria": ["Tempor\xE1ria", "TEMPORARIA", "Temporaria"],
|
|
1265
|
-
"cpi": ["CPI", "Comiss\xE3o Parlamentar de Inqu\xE9rito"],
|
|
1266
|
-
"mista": ["Mista", "MISTA"]
|
|
1267
|
-
};
|
|
1268
|
-
const tiposValidos = tipoMap[input.tipo] || [];
|
|
1269
|
-
comissoesFormatadas = comissoesFormatadas.filter(
|
|
1270
|
-
(c) => c.tipo && tiposValidos.some((t) => c.tipo.toLowerCase().includes(t.toLowerCase()))
|
|
1271
|
-
);
|
|
1272
|
-
}
|
|
1273
|
-
if (input.ativa !== void 0) {
|
|
1274
|
-
comissoesFormatadas = comissoesFormatadas.filter((c) => c.ativa === input.ativa);
|
|
1275
|
-
}
|
|
1276
|
-
const result = createSuccessResponse(
|
|
1277
|
-
{ count: comissoesFormatadas.length, comissoes: comissoesFormatadas },
|
|
1278
|
-
endpoint
|
|
1279
|
-
);
|
|
1280
|
-
return {
|
|
1281
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1282
|
-
};
|
|
1283
|
-
} catch (error) {
|
|
1284
|
-
logger.error({ error }, "Error listing comissoes");
|
|
1285
|
-
const errorResult = createErrorResponse(
|
|
1286
|
-
"ERRO_LISTAR_COMISSOES",
|
|
1287
|
-
error instanceof Error ? error.message : "Erro ao listar comiss\xF5es",
|
|
1288
|
-
"Verifique os par\xE2metros e tente novamente"
|
|
1289
|
-
);
|
|
1290
|
-
return {
|
|
1291
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
1292
|
-
};
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
);
|
|
1296
|
-
server.tool(
|
|
1297
|
-
"senado_obter_comissao",
|
|
1298
|
-
"Obt\xE9m detalhes de uma comiss\xE3o, incluindo presidente, vice-presidente e finalidade.",
|
|
1299
|
-
{
|
|
1300
|
-
sigla: import_zod8.z.string().min(2).describe("Sigla da comiss\xE3o (ex: CCJ, CAE)")
|
|
1301
|
-
},
|
|
1302
|
-
async (params) => {
|
|
1303
|
-
try {
|
|
1304
|
-
const input = ObterComissaoInput.parse(params);
|
|
1305
|
-
const sigla = input.sigla.toUpperCase();
|
|
1306
|
-
logger.info({ sigla }, "Getting comissao details");
|
|
1307
|
-
const endpoint = ENDPOINTS.COMISSAO(sigla);
|
|
1308
|
-
const response = await apiRequest(endpoint);
|
|
1309
|
-
const dados = response.DetalheComissao || response;
|
|
1310
|
-
const comissao = parseComissaoDetalhe(dados);
|
|
1311
|
-
const result = createSuccessResponse(comissao, endpoint);
|
|
1312
|
-
return {
|
|
1313
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1314
|
-
};
|
|
1315
|
-
} catch (error) {
|
|
1316
|
-
logger.error({ error }, "Error getting comissao details");
|
|
1317
|
-
const errorResult = createErrorResponse(
|
|
1318
|
-
"COMISSAO_NAO_ENCONTRADA",
|
|
1319
|
-
error instanceof Error ? error.message : "Comiss\xE3o n\xE3o encontrada",
|
|
1320
|
-
"Use senado_listar_comissoes para ver as siglas v\xE1lidas"
|
|
1321
|
-
);
|
|
1322
|
-
return {
|
|
1323
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
1324
|
-
};
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
);
|
|
1328
|
-
server.tool(
|
|
1329
|
-
"senado_membros_comissao",
|
|
1330
|
-
"Lista membros atuais de uma comiss\xE3o, incluindo cargo (presidente, vice, titular, suplente).",
|
|
1331
|
-
{
|
|
1332
|
-
sigla: import_zod8.z.string().min(2).describe("Sigla da comiss\xE3o")
|
|
1333
|
-
},
|
|
1334
|
-
async (params) => {
|
|
1335
|
-
try {
|
|
1336
|
-
const input = MembrosComissaoInput.parse(params);
|
|
1337
|
-
const sigla = input.sigla.toUpperCase();
|
|
1338
|
-
logger.info({ sigla }, "Getting comissao members");
|
|
1339
|
-
const endpoint = ENDPOINTS.COMISSAO_COMPOSICAO(sigla);
|
|
1340
|
-
const response = await apiRequest(endpoint);
|
|
1341
|
-
let membros = [];
|
|
1342
|
-
if (response.ComposicaoComissao?.Comissao?.Membros?.Membro) {
|
|
1343
|
-
membros = response.ComposicaoComissao.Comissao.Membros.Membro;
|
|
1344
|
-
} else if (response.Membros?.Membro) {
|
|
1345
|
-
membros = response.Membros.Membro;
|
|
1346
|
-
}
|
|
1347
|
-
if (!Array.isArray(membros)) {
|
|
1348
|
-
membros = membros ? [membros] : [];
|
|
1349
|
-
}
|
|
1350
|
-
const membrosFormatados = membros.map(parseMembroComissao);
|
|
1351
|
-
const result = createSuccessResponse(
|
|
1352
|
-
{
|
|
1353
|
-
sigla,
|
|
1354
|
-
count: membrosFormatados.length,
|
|
1355
|
-
membros: membrosFormatados
|
|
1356
|
-
},
|
|
1357
|
-
endpoint
|
|
1358
|
-
);
|
|
1359
|
-
return {
|
|
1360
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1361
|
-
};
|
|
1362
|
-
} catch (error) {
|
|
1363
|
-
logger.error({ error }, "Error getting comissao members");
|
|
1364
|
-
const errorResult = createErrorResponse(
|
|
1365
|
-
"ERRO_MEMBROS_COMISSAO",
|
|
1366
|
-
error instanceof Error ? error.message : "Erro ao obter membros",
|
|
1367
|
-
"Verifique se a sigla da comiss\xE3o est\xE1 correta"
|
|
1368
|
-
);
|
|
1369
|
-
return {
|
|
1370
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
1371
|
-
};
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
);
|
|
1375
|
-
server.tool(
|
|
1376
|
-
"senado_reunioes_comissao",
|
|
1377
|
-
"Lista reuni\xF5es agendadas ou realizadas de uma comiss\xE3o, com data, hora, local e pauta.",
|
|
1378
|
-
{
|
|
1379
|
-
sigla: import_zod8.z.string().min(2).describe("Sigla da comiss\xE3o"),
|
|
1380
|
-
dataInicio: import_zod8.z.string().regex(/^\d{8}$/).optional().describe("Data in\xEDcio (YYYYMMDD)"),
|
|
1381
|
-
dataFim: import_zod8.z.string().regex(/^\d{8}$/).optional().describe("Data fim (YYYYMMDD)")
|
|
1382
|
-
},
|
|
1383
|
-
async (params) => {
|
|
1384
|
-
try {
|
|
1385
|
-
const input = ReunioesComissaoInput.parse(params);
|
|
1386
|
-
const sigla = input.sigla.toUpperCase();
|
|
1387
|
-
logger.info({ input }, "Getting comissao reunioes");
|
|
1388
|
-
let endpoint = ENDPOINTS.COMISSAO(sigla);
|
|
1389
|
-
const queryParams = [];
|
|
1390
|
-
if (input.dataInicio) queryParams.push(`dataInicio=${input.dataInicio}`);
|
|
1391
|
-
if (input.dataFim) queryParams.push(`dataFim=${input.dataFim}`);
|
|
1392
|
-
if (queryParams.length > 0) {
|
|
1393
|
-
endpoint += "?" + queryParams.join("&");
|
|
1394
|
-
}
|
|
1395
|
-
const response = await apiRequest(endpoint);
|
|
1396
|
-
let reunioes = [];
|
|
1397
|
-
if (response.DetalheComissao?.Comissao?.Reunioes?.Reuniao) {
|
|
1398
|
-
reunioes = response.DetalheComissao.Comissao.Reunioes.Reuniao;
|
|
1399
|
-
} else if (response.Reunioes?.Reuniao) {
|
|
1400
|
-
reunioes = response.Reunioes.Reuniao;
|
|
1401
|
-
}
|
|
1402
|
-
if (!Array.isArray(reunioes)) {
|
|
1403
|
-
reunioes = reunioes ? [reunioes] : [];
|
|
1404
|
-
}
|
|
1405
|
-
const reunioesFormatadas = reunioes.map(parseReuniaoComissao);
|
|
1406
|
-
const result = createSuccessResponse(
|
|
1407
|
-
{
|
|
1408
|
-
sigla,
|
|
1409
|
-
count: reunioesFormatadas.length,
|
|
1410
|
-
reunioes: reunioesFormatadas
|
|
1411
|
-
},
|
|
1412
|
-
endpoint
|
|
1413
|
-
);
|
|
1414
|
-
return {
|
|
1415
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1416
|
-
};
|
|
1417
|
-
} catch (error) {
|
|
1418
|
-
logger.error({ error }, "Error getting comissao reunioes");
|
|
1419
|
-
const errorResult = createErrorResponse(
|
|
1420
|
-
"ERRO_REUNIOES_COMISSAO",
|
|
1421
|
-
error instanceof Error ? error.message : "Erro ao obter reuni\xF5es",
|
|
1422
|
-
"Verifique se a sigla da comiss\xE3o est\xE1 correta"
|
|
1423
|
-
);
|
|
1424
|
-
return {
|
|
1425
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
1426
|
-
};
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
);
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
// src/tools/agenda.ts
|
|
1433
|
-
var import_zod10 = require("zod");
|
|
1434
|
-
|
|
1435
|
-
// src/schemas/agenda.ts
|
|
1436
|
-
var import_zod9 = require("zod");
|
|
1437
|
-
var AgendaPlenarioInput = import_zod9.z.object({
|
|
1438
|
-
data: import_zod9.z.string().regex(/^\d{8}$/, "Formato deve ser YYYYMMDD").optional().describe("Data espec\xEDfica (YYYYMMDD)"),
|
|
1439
|
-
dataInicio: import_zod9.z.string().regex(/^\d{8}$/, "Formato deve ser YYYYMMDD").optional().describe("Data in\xEDcio (YYYYMMDD)"),
|
|
1440
|
-
dataFim: import_zod9.z.string().regex(/^\d{8}$/, "Formato deve ser YYYYMMDD").optional().describe("Data fim (YYYYMMDD)")
|
|
1441
|
-
});
|
|
1442
|
-
var AgendaComissoesInput = import_zod9.z.object({
|
|
1443
|
-
data: import_zod9.z.string().regex(/^\d{8}$/, "Formato deve ser YYYYMMDD").optional().describe("Data espec\xEDfica (YYYYMMDD)"),
|
|
1444
|
-
siglaComissao: import_zod9.z.string().min(2).toUpperCase().optional().describe("Filtrar por comiss\xE3o espec\xEDfica")
|
|
1445
|
-
});
|
|
1446
|
-
var SessaoPlenario = import_zod9.z.object({
|
|
1447
|
-
codigo: import_zod9.z.number(),
|
|
1448
|
-
data: import_zod9.z.string(),
|
|
1449
|
-
hora: import_zod9.z.string().nullable(),
|
|
1450
|
-
tipo: import_zod9.z.string().nullable(),
|
|
1451
|
-
situacao: import_zod9.z.string().nullable(),
|
|
1452
|
-
pauta: import_zod9.z.array(import_zod9.z.object({
|
|
1453
|
-
materia: import_zod9.z.string().nullable(),
|
|
1454
|
-
ementa: import_zod9.z.string().nullable(),
|
|
1455
|
-
relator: import_zod9.z.string().nullable()
|
|
1456
|
-
})).optional()
|
|
1457
|
-
});
|
|
1458
|
-
var ReuniaoAgendada = import_zod9.z.object({
|
|
1459
|
-
codigo: import_zod9.z.number(),
|
|
1460
|
-
comissao: import_zod9.z.object({
|
|
1461
|
-
sigla: import_zod9.z.string(),
|
|
1462
|
-
nome: import_zod9.z.string()
|
|
1463
|
-
}),
|
|
1464
|
-
data: import_zod9.z.string(),
|
|
1465
|
-
hora: import_zod9.z.string().nullable(),
|
|
1466
|
-
local: import_zod9.z.string().nullable(),
|
|
1467
|
-
tipo: import_zod9.z.string().nullable(),
|
|
1468
|
-
situacao: import_zod9.z.string().nullable()
|
|
1469
|
-
});
|
|
1470
|
-
|
|
1471
|
-
// src/tools/agenda.ts
|
|
1472
|
-
function formatDateYYYYMMDD(date) {
|
|
1473
|
-
const year = date.getFullYear();
|
|
1474
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
1475
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
1476
|
-
return `${year}${month}${day}`;
|
|
1477
|
-
}
|
|
1478
|
-
function parseSessaoPlenario(sessao) {
|
|
1479
|
-
let pauta = void 0;
|
|
1480
|
-
if (sessao.Materias?.Materia) {
|
|
1481
|
-
let materias = sessao.Materias.Materia;
|
|
1482
|
-
if (!Array.isArray(materias)) {
|
|
1483
|
-
materias = [materias];
|
|
1484
|
-
}
|
|
1485
|
-
pauta = materias.map((m) => ({
|
|
1486
|
-
materia: m.IdentificacaoMateria?.DescricaoIdentificacaoMateria || `${m.SiglaSubtipoMateria || ""} ${m.NumeroMateria || ""}/${m.AnoMateria || ""}`.trim() || null,
|
|
1487
|
-
ementa: m.EmentaMateria || m.Ementa || null,
|
|
1488
|
-
relator: m.Relator?.NomeRelator || null
|
|
1489
|
-
}));
|
|
1490
|
-
}
|
|
1491
|
-
return {
|
|
1492
|
-
codigo: parseInt(sessao.CodigoSessao || sessao.Codigo || "0"),
|
|
1493
|
-
data: sessao.DataSessao || sessao.Data || "",
|
|
1494
|
-
hora: sessao.HoraInicioSessao || sessao.Hora || null,
|
|
1495
|
-
tipo: sessao.TipoSessao?.DescricaoTipoSessao || sessao.DescricaoTipoSessao || sessao.Tipo || null,
|
|
1496
|
-
situacao: sessao.SituacaoSessao?.DescricaoSituacaoSessao || sessao.Situacao || null,
|
|
1497
|
-
pauta
|
|
1498
|
-
};
|
|
1499
|
-
}
|
|
1500
|
-
function parseReuniaoAgendada(reuniao) {
|
|
1501
|
-
const comissaoData = reuniao.Comissao || reuniao.IdentificacaoComissao || {};
|
|
1502
|
-
return {
|
|
1503
|
-
codigo: parseInt(reuniao.CodigoReuniao || reuniao.Codigo || "0"),
|
|
1504
|
-
comissao: {
|
|
1505
|
-
sigla: comissaoData.SiglaComissao || comissaoData.Sigla || "",
|
|
1506
|
-
nome: comissaoData.NomeComissao || comissaoData.Nome || ""
|
|
1507
|
-
},
|
|
1508
|
-
data: reuniao.DataReuniao || reuniao.Data || "",
|
|
1509
|
-
hora: reuniao.HoraReuniao || reuniao.Hora || null,
|
|
1510
|
-
local: reuniao.LocalReuniao || reuniao.Local || null,
|
|
1511
|
-
tipo: reuniao.TipoReuniao?.DescricaoTipoReuniao || reuniao.Tipo || null,
|
|
1512
|
-
situacao: reuniao.SituacaoReuniao?.DescricaoSituacaoReuniao || reuniao.Situacao || null
|
|
1513
|
-
};
|
|
1514
|
-
}
|
|
1515
|
-
function registerAgendaTools(server) {
|
|
1516
|
-
server.tool(
|
|
1517
|
-
"senado_agenda_plenario",
|
|
1518
|
-
"Obt\xE9m agenda de sess\xF5es do plen\xE1rio do Senado, incluindo pauta com mat\xE9rias a serem votadas.",
|
|
1519
|
-
{
|
|
1520
|
-
data: import_zod10.z.string().regex(/^\d{8}$/).optional().describe("Data espec\xEDfica (YYYYMMDD)"),
|
|
1521
|
-
dataInicio: import_zod10.z.string().regex(/^\d{8}$/).optional().describe("Data in\xEDcio (YYYYMMDD)"),
|
|
1522
|
-
dataFim: import_zod10.z.string().regex(/^\d{8}$/).optional().describe("Data fim (YYYYMMDD)")
|
|
1523
|
-
},
|
|
1524
|
-
async (params) => {
|
|
1525
|
-
try {
|
|
1526
|
-
const input = AgendaPlenarioInput.parse(params);
|
|
1527
|
-
logger.info({ input }, "Getting agenda plenario");
|
|
1528
|
-
const data = input.data || formatDateYYYYMMDD(/* @__PURE__ */ new Date());
|
|
1529
|
-
const endpoint = ENDPOINTS.AGENDA(data);
|
|
1530
|
-
const response = await apiRequest(endpoint);
|
|
1531
|
-
let sessoes = [];
|
|
1532
|
-
if (response.Agenda?.Sessoes?.Sessao) {
|
|
1533
|
-
sessoes = response.Agenda.Sessoes.Sessao;
|
|
1534
|
-
} else if (response.AgendaPlenario?.Sessoes?.Sessao) {
|
|
1535
|
-
sessoes = response.AgendaPlenario.Sessoes.Sessao;
|
|
1536
|
-
} else if (response.Sessoes?.Sessao) {
|
|
1537
|
-
sessoes = response.Sessoes.Sessao;
|
|
1538
|
-
}
|
|
1539
|
-
if (!Array.isArray(sessoes)) {
|
|
1540
|
-
sessoes = sessoes ? [sessoes] : [];
|
|
1541
|
-
}
|
|
1542
|
-
const sessoesFormatadas = sessoes.map(parseSessaoPlenario);
|
|
1543
|
-
const result = createSuccessResponse(
|
|
1544
|
-
{
|
|
1545
|
-
data,
|
|
1546
|
-
count: sessoesFormatadas.length,
|
|
1547
|
-
sessoes: sessoesFormatadas
|
|
1548
|
-
},
|
|
1549
|
-
endpoint
|
|
1550
|
-
);
|
|
1551
|
-
return {
|
|
1552
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1553
|
-
};
|
|
1554
|
-
} catch (error) {
|
|
1555
|
-
logger.error({ error }, "Error getting agenda plenario");
|
|
1556
|
-
const errorResult = createErrorResponse(
|
|
1557
|
-
"ERRO_AGENDA_PLENARIO",
|
|
1558
|
-
error instanceof Error ? error.message : "Erro ao obter agenda do plen\xE1rio",
|
|
1559
|
-
"Verifique se a data est\xE1 no formato correto (YYYYMMDD)"
|
|
1560
|
-
);
|
|
1561
|
-
return {
|
|
1562
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
1563
|
-
};
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
);
|
|
1567
|
-
server.tool(
|
|
1568
|
-
"senado_agenda_comissoes",
|
|
1569
|
-
"Obt\xE9m agenda de reuni\xF5es das comiss\xF5es do Senado. Pode filtrar por data e comiss\xE3o espec\xEDfica.",
|
|
1570
|
-
{
|
|
1571
|
-
data: import_zod10.z.string().regex(/^\d{8}$/).optional().describe("Data espec\xEDfica (YYYYMMDD)"),
|
|
1572
|
-
siglaComissao: import_zod10.z.string().min(2).optional().describe("Filtrar por comiss\xE3o espec\xEDfica")
|
|
1573
|
-
},
|
|
1574
|
-
async (params) => {
|
|
1575
|
-
try {
|
|
1576
|
-
const input = AgendaComissoesInput.parse(params);
|
|
1577
|
-
logger.info({ input }, "Getting agenda comissoes");
|
|
1578
|
-
const data = input.data || formatDateYYYYMMDD(/* @__PURE__ */ new Date());
|
|
1579
|
-
const endpoint = ENDPOINTS.AGENDA(data);
|
|
1580
|
-
const response = await apiRequest(endpoint);
|
|
1581
|
-
let reunioes = [];
|
|
1582
|
-
if (response.Agenda?.Reunioes?.Reuniao) {
|
|
1583
|
-
reunioes = response.Agenda.Reunioes.Reuniao;
|
|
1584
|
-
} else if (response.AgendaComissoes?.Reunioes?.Reuniao) {
|
|
1585
|
-
reunioes = response.AgendaComissoes.Reunioes.Reuniao;
|
|
1586
|
-
} else if (response.Reunioes?.Reuniao) {
|
|
1587
|
-
reunioes = response.Reunioes.Reuniao;
|
|
1588
|
-
}
|
|
1589
|
-
if (!Array.isArray(reunioes)) {
|
|
1590
|
-
reunioes = reunioes ? [reunioes] : [];
|
|
1591
|
-
}
|
|
1592
|
-
let reunioesFormatadas = reunioes.map(parseReuniaoAgendada);
|
|
1593
|
-
if (input.siglaComissao) {
|
|
1594
|
-
const siglaUpper = input.siglaComissao.toUpperCase();
|
|
1595
|
-
reunioesFormatadas = reunioesFormatadas.filter(
|
|
1596
|
-
(r) => r.comissao.sigla.toUpperCase() === siglaUpper
|
|
1597
|
-
);
|
|
1598
|
-
}
|
|
1599
|
-
const result = createSuccessResponse(
|
|
1600
|
-
{
|
|
1601
|
-
data,
|
|
1602
|
-
siglaComissao: input.siglaComissao || null,
|
|
1603
|
-
count: reunioesFormatadas.length,
|
|
1604
|
-
reunioes: reunioesFormatadas
|
|
1605
|
-
},
|
|
1606
|
-
endpoint
|
|
1607
|
-
);
|
|
1608
|
-
return {
|
|
1609
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1610
|
-
};
|
|
1611
|
-
} catch (error) {
|
|
1612
|
-
logger.error({ error }, "Error getting agenda comissoes");
|
|
1613
|
-
const errorResult = createErrorResponse(
|
|
1614
|
-
"ERRO_AGENDA_COMISSOES",
|
|
1615
|
-
error instanceof Error ? error.message : "Erro ao obter agenda das comiss\xF5es",
|
|
1616
|
-
"Verifique se a data est\xE1 no formato correto (YYYYMMDD)"
|
|
1617
|
-
);
|
|
1618
|
-
return {
|
|
1619
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
1620
|
-
};
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
);
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
// src/tools/auxiliares.ts
|
|
1627
|
-
var TIPOS_MATERIA = [
|
|
1628
|
-
{ sigla: "PEC", nome: "Proposta de Emenda \xE0 Constitui\xE7\xE3o", descricao: "Altera a Constitui\xE7\xE3o Federal" },
|
|
1629
|
-
{ sigla: "PL", nome: "Projeto de Lei", descricao: "Projeto de lei ordin\xE1ria" },
|
|
1630
|
-
{ sigla: "PLP", nome: "Projeto de Lei Complementar", descricao: "Regulamenta dispositivos constitucionais" },
|
|
1631
|
-
{ sigla: "MPV", nome: "Medida Provis\xF3ria", descricao: "Medida com for\xE7a de lei editada pelo Executivo" },
|
|
1632
|
-
{ sigla: "PDL", nome: "Projeto de Decreto Legislativo", descricao: "Mat\xE9ria de compet\xEAncia exclusiva do Congresso" },
|
|
1633
|
-
{ sigla: "PRS", nome: "Projeto de Resolu\xE7\xE3o do Senado", descricao: "Mat\xE9ria de compet\xEAncia privativa do Senado" },
|
|
1634
|
-
{ sigla: "PLC", nome: "Projeto de Lei da C\xE2mara", descricao: "Projeto de lei origin\xE1rio da C\xE2mara dos Deputados" },
|
|
1635
|
-
{ sigla: "PLS", nome: "Projeto de Lei do Senado", descricao: "Projeto de lei origin\xE1rio do Senado (nomenclatura antiga)" },
|
|
1636
|
-
{ sigla: "REQ", nome: "Requerimento", descricao: "Solicita\xE7\xE3o de provid\xEAncia ou informa\xE7\xE3o" },
|
|
1637
|
-
{ sigla: "RQS", nome: "Requerimento do Senado", descricao: "Requerimento de compet\xEAncia do Senado" },
|
|
1638
|
-
{ sigla: "INC", nome: "Indica\xE7\xE3o", descricao: "Sugest\xE3o a outro Poder ou \xF3rg\xE3o" },
|
|
1639
|
-
{ sigla: "SUG", nome: "Sugest\xE3o Legislativa", descricao: "Sugest\xE3o da sociedade civil" }
|
|
1640
|
-
];
|
|
1641
|
-
var UFS = [
|
|
1642
|
-
{ sigla: "AC", nome: "Acre" },
|
|
1643
|
-
{ sigla: "AL", nome: "Alagoas" },
|
|
1644
|
-
{ sigla: "AP", nome: "Amap\xE1" },
|
|
1645
|
-
{ sigla: "AM", nome: "Amazonas" },
|
|
1646
|
-
{ sigla: "BA", nome: "Bahia" },
|
|
1647
|
-
{ sigla: "CE", nome: "Cear\xE1" },
|
|
1648
|
-
{ sigla: "DF", nome: "Distrito Federal" },
|
|
1649
|
-
{ sigla: "ES", nome: "Esp\xEDrito Santo" },
|
|
1650
|
-
{ sigla: "GO", nome: "Goi\xE1s" },
|
|
1651
|
-
{ sigla: "MA", nome: "Maranh\xE3o" },
|
|
1652
|
-
{ sigla: "MT", nome: "Mato Grosso" },
|
|
1653
|
-
{ sigla: "MS", nome: "Mato Grosso do Sul" },
|
|
1654
|
-
{ sigla: "MG", nome: "Minas Gerais" },
|
|
1655
|
-
{ sigla: "PA", nome: "Par\xE1" },
|
|
1656
|
-
{ sigla: "PB", nome: "Para\xEDba" },
|
|
1657
|
-
{ sigla: "PR", nome: "Paran\xE1" },
|
|
1658
|
-
{ sigla: "PE", nome: "Pernambuco" },
|
|
1659
|
-
{ sigla: "PI", nome: "Piau\xED" },
|
|
1660
|
-
{ sigla: "RJ", nome: "Rio de Janeiro" },
|
|
1661
|
-
{ sigla: "RN", nome: "Rio Grande do Norte" },
|
|
1662
|
-
{ sigla: "RS", nome: "Rio Grande do Sul" },
|
|
1663
|
-
{ sigla: "RO", nome: "Rond\xF4nia" },
|
|
1664
|
-
{ sigla: "RR", nome: "Roraima" },
|
|
1665
|
-
{ sigla: "SC", nome: "Santa Catarina" },
|
|
1666
|
-
{ sigla: "SP", nome: "S\xE3o Paulo" },
|
|
1667
|
-
{ sigla: "SE", nome: "Sergipe" },
|
|
1668
|
-
{ sigla: "TO", nome: "Tocantins" }
|
|
1669
|
-
];
|
|
1670
|
-
function registerAuxiliaresTools(server) {
|
|
1671
|
-
server.tool(
|
|
1672
|
-
"senado_legislatura_atual",
|
|
1673
|
-
"Retorna informa\xE7\xF5es sobre a legislatura vigente, incluindo n\xFAmero, per\xEDodo e datas de in\xEDcio/fim.",
|
|
1674
|
-
{},
|
|
1675
|
-
async () => {
|
|
1676
|
-
try {
|
|
1677
|
-
logger.info("Getting current legislature");
|
|
1678
|
-
try {
|
|
1679
|
-
const endpoint = ENDPOINTS.SENADORES_ATUAIS;
|
|
1680
|
-
const response = await apiRequest(endpoint);
|
|
1681
|
-
let legislatura = null;
|
|
1682
|
-
if (response.ListaParlamentarEmExercicio?.Parlamentares?.Parlamentar) {
|
|
1683
|
-
const parlamentares = response.ListaParlamentarEmExercicio.Parlamentares.Parlamentar;
|
|
1684
|
-
const primeiro = Array.isArray(parlamentares) ? parlamentares[0] : parlamentares;
|
|
1685
|
-
if (primeiro?.Mandato?.PrimeiraLegislaturaDoMandato?.NumeroLegislatura) {
|
|
1686
|
-
legislatura = parseInt(primeiro.Mandato.PrimeiraLegislaturaDoMandato.NumeroLegislatura);
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
if (legislatura) {
|
|
1690
|
-
const anoInicio = 2023 - (57 - legislatura) * 4;
|
|
1691
|
-
const anoFim = anoInicio + 4;
|
|
1692
|
-
const result2 = createSuccessResponse(
|
|
1693
|
-
{
|
|
1694
|
-
numero: legislatura,
|
|
1695
|
-
periodo: `${anoInicio}-${anoFim}`,
|
|
1696
|
-
dataInicio: `${anoInicio}-02-01`,
|
|
1697
|
-
dataFim: `${anoFim}-01-31`
|
|
1698
|
-
},
|
|
1699
|
-
endpoint
|
|
1700
|
-
);
|
|
1701
|
-
return {
|
|
1702
|
-
content: [{ type: "text", text: JSON.stringify(result2, null, 2) }]
|
|
1703
|
-
};
|
|
1704
|
-
}
|
|
1705
|
-
} catch {
|
|
1706
|
-
}
|
|
1707
|
-
const result = createSuccessResponse(
|
|
1708
|
-
{
|
|
1709
|
-
numero: 57,
|
|
1710
|
-
periodo: "2023-2027",
|
|
1711
|
-
dataInicio: "2023-02-01",
|
|
1712
|
-
dataFim: "2027-01-31"
|
|
1713
|
-
},
|
|
1714
|
-
"/legislatura/atual"
|
|
1715
|
-
);
|
|
1716
|
-
return {
|
|
1717
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1718
|
-
};
|
|
1719
|
-
} catch (error) {
|
|
1720
|
-
logger.error({ error }, "Error getting current legislature");
|
|
1721
|
-
const errorResult = createErrorResponse(
|
|
1722
|
-
"ERRO_LEGISLATURA",
|
|
1723
|
-
error instanceof Error ? error.message : "Erro ao obter legislatura",
|
|
1724
|
-
"Tente novamente mais tarde"
|
|
1725
|
-
);
|
|
1726
|
-
return {
|
|
1727
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
1728
|
-
};
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
);
|
|
1732
|
-
server.tool(
|
|
1733
|
-
"senado_tipos_materia",
|
|
1734
|
-
"Lista os tipos de mat\xE9rias legislativas v\xE1lidos com sigla, nome completo e descri\xE7\xE3o. \xDAtil para usar em buscas.",
|
|
1735
|
-
{},
|
|
1736
|
-
async () => {
|
|
1737
|
-
try {
|
|
1738
|
-
logger.info("Getting tipos materia");
|
|
1739
|
-
const result = createSuccessResponse(
|
|
1740
|
-
{
|
|
1741
|
-
count: TIPOS_MATERIA.length,
|
|
1742
|
-
tipos: TIPOS_MATERIA
|
|
1743
|
-
},
|
|
1744
|
-
"/materia/siglas"
|
|
1745
|
-
);
|
|
1746
|
-
return {
|
|
1747
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1748
|
-
};
|
|
1749
|
-
} catch (error) {
|
|
1750
|
-
logger.error({ error }, "Error getting tipos materia");
|
|
1751
|
-
const errorResult = createErrorResponse(
|
|
1752
|
-
"ERRO_TIPOS_MATERIA",
|
|
1753
|
-
error instanceof Error ? error.message : "Erro ao obter tipos de mat\xE9ria",
|
|
1754
|
-
"Tente novamente mais tarde"
|
|
1755
|
-
);
|
|
1756
|
-
return {
|
|
1757
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
1758
|
-
};
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
);
|
|
1762
|
-
server.tool(
|
|
1763
|
-
"senado_partidos",
|
|
1764
|
-
"Lista partidos com representa\xE7\xE3o atual no Senado, incluindo sigla, nome completo e n\xFAmero de senadores.",
|
|
1765
|
-
{},
|
|
1766
|
-
async () => {
|
|
1767
|
-
try {
|
|
1768
|
-
logger.info("Getting partidos");
|
|
1769
|
-
const endpoint = ENDPOINTS.SENADORES_ATUAIS;
|
|
1770
|
-
const response = await apiRequest(endpoint);
|
|
1771
|
-
let parlamentares = [];
|
|
1772
|
-
if (response.ListaParlamentarEmExercicio?.Parlamentares?.Parlamentar) {
|
|
1773
|
-
parlamentares = response.ListaParlamentarEmExercicio.Parlamentares.Parlamentar;
|
|
1774
|
-
}
|
|
1775
|
-
if (!Array.isArray(parlamentares)) {
|
|
1776
|
-
parlamentares = parlamentares ? [parlamentares] : [];
|
|
1777
|
-
}
|
|
1778
|
-
const partidoCount = {};
|
|
1779
|
-
for (const p of parlamentares) {
|
|
1780
|
-
const mandato = p.Mandato || {};
|
|
1781
|
-
const sigla = mandato.Partido?.SiglaPartido || p.IdentificacaoParlamentar?.SiglaPartidoParlamentar || "S/Partido";
|
|
1782
|
-
const nome = mandato.Partido?.NomePartido || sigla;
|
|
1783
|
-
if (!partidoCount[sigla]) {
|
|
1784
|
-
partidoCount[sigla] = { sigla, nome, senadores: 0 };
|
|
1785
|
-
}
|
|
1786
|
-
partidoCount[sigla].senadores++;
|
|
1787
|
-
}
|
|
1788
|
-
const partidos = Object.values(partidoCount).sort((a, b) => b.senadores - a.senadores);
|
|
1789
|
-
const result = createSuccessResponse(
|
|
1790
|
-
{
|
|
1791
|
-
count: partidos.length,
|
|
1792
|
-
totalSenadores: parlamentares.length,
|
|
1793
|
-
partidos
|
|
1794
|
-
},
|
|
1795
|
-
endpoint
|
|
1796
|
-
);
|
|
1797
|
-
return {
|
|
1798
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1799
|
-
};
|
|
1800
|
-
} catch (error) {
|
|
1801
|
-
logger.error({ error }, "Error getting partidos");
|
|
1802
|
-
const errorResult = createErrorResponse(
|
|
1803
|
-
"ERRO_PARTIDOS",
|
|
1804
|
-
error instanceof Error ? error.message : "Erro ao obter partidos",
|
|
1805
|
-
"Tente novamente mais tarde"
|
|
1806
|
-
);
|
|
1807
|
-
return {
|
|
1808
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
1809
|
-
};
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
);
|
|
1813
|
-
server.tool(
|
|
1814
|
-
"senado_ufs",
|
|
1815
|
-
"Lista unidades federativas com n\xFAmero de senadores atualmente em exerc\xEDcio por estado.",
|
|
1816
|
-
{},
|
|
1817
|
-
async () => {
|
|
1818
|
-
try {
|
|
1819
|
-
logger.info("Getting UFs");
|
|
1820
|
-
const endpoint = ENDPOINTS.SENADORES_ATUAIS;
|
|
1821
|
-
const response = await apiRequest(endpoint);
|
|
1822
|
-
let parlamentares = [];
|
|
1823
|
-
if (response.ListaParlamentarEmExercicio?.Parlamentares?.Parlamentar) {
|
|
1824
|
-
parlamentares = response.ListaParlamentarEmExercicio.Parlamentares.Parlamentar;
|
|
1825
|
-
}
|
|
1826
|
-
if (!Array.isArray(parlamentares)) {
|
|
1827
|
-
parlamentares = parlamentares ? [parlamentares] : [];
|
|
1828
|
-
}
|
|
1829
|
-
const ufCount = {};
|
|
1830
|
-
for (const p of parlamentares) {
|
|
1831
|
-
const mandato = p.Mandato || {};
|
|
1832
|
-
const uf = mandato.UfParlamentar || p.IdentificacaoParlamentar?.UfParlamentar || "";
|
|
1833
|
-
if (uf) {
|
|
1834
|
-
ufCount[uf] = (ufCount[uf] || 0) + 1;
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
const ufs = UFS.map((uf) => ({
|
|
1838
|
-
...uf,
|
|
1839
|
-
senadores: ufCount[uf.sigla] || 0
|
|
1840
|
-
})).sort((a, b) => a.sigla.localeCompare(b.sigla));
|
|
1841
|
-
const result = createSuccessResponse(
|
|
1842
|
-
{
|
|
1843
|
-
count: ufs.length,
|
|
1844
|
-
totalSenadores: parlamentares.length,
|
|
1845
|
-
ufs
|
|
1846
|
-
},
|
|
1847
|
-
endpoint
|
|
1848
|
-
);
|
|
1849
|
-
return {
|
|
1850
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1851
|
-
};
|
|
1852
|
-
} catch (error) {
|
|
1853
|
-
logger.error({ error }, "Error getting UFs");
|
|
1854
|
-
const errorResult = createErrorResponse(
|
|
1855
|
-
"ERRO_UFS",
|
|
1856
|
-
error instanceof Error ? error.message : "Erro ao obter UFs",
|
|
1857
|
-
"Tente novamente mais tarde"
|
|
1858
|
-
);
|
|
1859
|
-
return {
|
|
1860
|
-
content: [{ type: "text", text: JSON.stringify(errorResult, null, 2) }]
|
|
1861
|
-
};
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
|
-
);
|
|
1865
|
-
}
|
|
1866
|
-
|
|
1867
|
-
// src/tools/ecidadania/consultas.ts
|
|
1868
|
-
var import_zod11 = require("zod");
|
|
1869
|
-
|
|
1870
|
-
// src/scraper/client.ts
|
|
1871
|
-
var BASE_URL2 = "https://www12.senado.leg.br/ecidadania";
|
|
1872
|
-
var MIN_REQUEST_INTERVAL = 1e3;
|
|
1873
|
-
var lastRequestTime = 0;
|
|
1874
|
-
async function waitForRateLimit() {
|
|
1875
|
-
const now = Date.now();
|
|
1876
|
-
const timeSinceLastRequest = now - lastRequestTime;
|
|
1877
|
-
if (timeSinceLastRequest < MIN_REQUEST_INTERVAL) {
|
|
1878
|
-
const waitTime = MIN_REQUEST_INTERVAL - timeSinceLastRequest;
|
|
1879
|
-
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
1880
|
-
}
|
|
1881
|
-
lastRequestTime = Date.now();
|
|
1882
|
-
}
|
|
1883
|
-
async function fetchPage(path, options = {}) {
|
|
1884
|
-
const { timeout = 3e4, retries = 3 } = options;
|
|
1885
|
-
const url = path.startsWith("http") ? path : `${BASE_URL2}${path}`;
|
|
1886
|
-
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
1887
|
-
try {
|
|
1888
|
-
await waitForRateLimit();
|
|
1889
|
-
const controller = new AbortController();
|
|
1890
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1891
|
-
logger.debug({ url, attempt }, "Fetching page");
|
|
1892
|
-
const response = await fetch(url, {
|
|
1893
|
-
headers: {
|
|
1894
|
-
"User-Agent": "Mozilla/5.0 (compatible; senado-br-mcp/1.0; +https://github.com/SidneyBissoli/senado-br-mcp)",
|
|
1895
|
-
"Accept": "text/html,application/xhtml+xml",
|
|
1896
|
-
"Accept-Language": "pt-BR,pt;q=0.9,en;q=0.8"
|
|
1897
|
-
},
|
|
1898
|
-
signal: controller.signal
|
|
1899
|
-
});
|
|
1900
|
-
clearTimeout(timeoutId);
|
|
1901
|
-
if (!response.ok) {
|
|
1902
|
-
if (response.status === 404) {
|
|
1903
|
-
throw createScrapingError("PAGINA_NAO_ENCONTRADA", url, `P\xE1gina n\xE3o encontrada: ${url}`);
|
|
1904
|
-
}
|
|
1905
|
-
if (response.status === 429) {
|
|
1906
|
-
throw createScrapingError("BLOQUEADO", url, "Muitas requisi\xE7\xF5es. Tente novamente mais tarde.");
|
|
1907
|
-
}
|
|
1908
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1909
|
-
}
|
|
1910
|
-
const html = await response.text();
|
|
1911
|
-
logger.debug({ url, htmlLength: html.length }, "Page fetched successfully");
|
|
1912
|
-
return html;
|
|
1913
|
-
} catch (error) {
|
|
1914
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1915
|
-
if (error.tipo) {
|
|
1916
|
-
throw error;
|
|
1917
|
-
}
|
|
1918
|
-
logger.warn({ url, attempt, error: errorMessage }, "Fetch failed");
|
|
1919
|
-
if (attempt === retries) {
|
|
1920
|
-
if (errorMessage.includes("abort")) {
|
|
1921
|
-
throw createScrapingError("TIMEOUT", url, "Tempo limite excedido ao acessar a p\xE1gina");
|
|
1922
|
-
}
|
|
1923
|
-
throw createScrapingError("ERRO_REDE", url, `Erro de rede: ${errorMessage}`);
|
|
1924
|
-
}
|
|
1925
|
-
const delay = Math.pow(2, attempt) * 1e3;
|
|
1926
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
throw createScrapingError("ERRO_REDE", url, "Falha ap\xF3s m\xFAltiplas tentativas");
|
|
1930
|
-
}
|
|
1931
|
-
function createScrapingError(tipo, url, mensagem) {
|
|
1932
|
-
const sugestoes = {
|
|
1933
|
-
PAGINA_NAO_ENCONTRADA: "Verifique se o ID est\xE1 correto",
|
|
1934
|
-
ESTRUTURA_ALTERADA: "O site pode ter sido atualizado. Reporte o problema.",
|
|
1935
|
-
TIMEOUT: "O site pode estar lento. Tente novamente.",
|
|
1936
|
-
BLOQUEADO: "Aguarde alguns minutos antes de tentar novamente.",
|
|
1937
|
-
ERRO_REDE: "Verifique sua conex\xE3o ou tente novamente."
|
|
1938
|
-
};
|
|
1939
|
-
return {
|
|
1940
|
-
tipo,
|
|
1941
|
-
url,
|
|
1942
|
-
mensagem,
|
|
1943
|
-
sugestao: sugestoes[tipo]
|
|
1944
|
-
};
|
|
1945
|
-
}
|
|
1946
|
-
function getBaseUrl() {
|
|
1947
|
-
return BASE_URL2;
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
// src/scraper/cache.ts
|
|
1951
|
-
var import_node_cache = __toESM(require("node-cache"));
|
|
1952
|
-
var CACHE_TTL = {
|
|
1953
|
-
LISTAGEM: 15 * 60,
|
|
1954
|
-
// 15 minutes for listings
|
|
1955
|
-
DETALHE: 60 * 60,
|
|
1956
|
-
// 1 hour for details
|
|
1957
|
-
RELATORIO: 24 * 60 * 60
|
|
1958
|
-
// 24 hours for reports
|
|
1959
|
-
};
|
|
1960
|
-
var cache = new import_node_cache.default({
|
|
1961
|
-
stdTTL: CACHE_TTL.LISTAGEM,
|
|
1962
|
-
checkperiod: 120,
|
|
1963
|
-
useClones: false
|
|
1964
|
-
});
|
|
1965
|
-
function getCached(key) {
|
|
1966
|
-
const value = cache.get(key);
|
|
1967
|
-
if (value) {
|
|
1968
|
-
logger.debug({ key }, "Cache hit");
|
|
1969
|
-
}
|
|
1970
|
-
return value;
|
|
1971
|
-
}
|
|
1972
|
-
function setCache(key, value, type = "LISTAGEM") {
|
|
1973
|
-
const ttl = CACHE_TTL[type];
|
|
1974
|
-
cache.set(key, value, ttl);
|
|
1975
|
-
logger.debug({ key, ttl }, "Cache set");
|
|
1976
|
-
}
|
|
1977
|
-
|
|
1978
|
-
// src/scraper/parser.ts
|
|
1979
|
-
var cheerio = __toESM(require("cheerio"));
|
|
1980
|
-
function parseHtml(html) {
|
|
1981
|
-
return cheerio.load(html);
|
|
1982
|
-
}
|
|
1983
|
-
function extractText($el) {
|
|
1984
|
-
return $el.text().trim();
|
|
1985
|
-
}
|
|
1986
|
-
function extractId(url) {
|
|
1987
|
-
const match = url.match(/[?&]id=(\d+)/);
|
|
1988
|
-
return match ? parseInt(match[1]) : null;
|
|
1989
|
-
}
|
|
1990
|
-
function extractDate(text) {
|
|
1991
|
-
const match = text.match(/(\d{2})\/(\d{2})\/(\d{2,4})/);
|
|
1992
|
-
if (match) {
|
|
1993
|
-
const [, day, month, year] = match;
|
|
1994
|
-
const fullYear = year.length === 2 ? `20${year}` : year;
|
|
1995
|
-
return `${fullYear}-${month}-${day}`;
|
|
1996
|
-
}
|
|
1997
|
-
return null;
|
|
1998
|
-
}
|
|
1999
|
-
function safeExtract(fn, defaultValue, context) {
|
|
2000
|
-
try {
|
|
2001
|
-
const result = fn();
|
|
2002
|
-
return result ?? defaultValue;
|
|
2003
|
-
} catch (error) {
|
|
2004
|
-
logger.warn({ error, context }, "Safe extract failed, using default");
|
|
2005
|
-
return defaultValue;
|
|
2006
|
-
}
|
|
2007
|
-
}
|
|
2008
|
-
|
|
2009
|
-
// src/scraper/pages/consultas.ts
|
|
2010
|
-
function parseBrazilianNumber(str) {
|
|
2011
|
-
const cleaned = str.replace(/\./g, "");
|
|
2012
|
-
const num = parseInt(cleaned, 10);
|
|
2013
|
-
return isNaN(num) ? 0 : num;
|
|
2014
|
-
}
|
|
2015
|
-
async function listarConsultas(params = {}) {
|
|
2016
|
-
const { pagina = 1, limite = 20 } = params;
|
|
2017
|
-
const cacheKey = `consultas:lista:${JSON.stringify(params)}`;
|
|
2018
|
-
const cached = getCached(cacheKey);
|
|
2019
|
-
if (cached) return cached;
|
|
2020
|
-
try {
|
|
2021
|
-
const url = `/pesquisamateria?p=${pagina}`;
|
|
2022
|
-
const html = await fetchPage(url);
|
|
2023
|
-
const $ = parseHtml(html);
|
|
2024
|
-
const consultas = [];
|
|
2025
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
2026
|
-
$('a[href*="visualizacaomateria?id="]').each((_, el) => {
|
|
2027
|
-
const $el = $(el);
|
|
2028
|
-
const href = $el.attr("href") || "";
|
|
2029
|
-
const id = extractId(href);
|
|
2030
|
-
if (!id || seenIds.has(id)) return;
|
|
2031
|
-
seenIds.add(id);
|
|
2032
|
-
const $container = $el.closest("div").parent();
|
|
2033
|
-
const fullText = extractText($container);
|
|
2034
|
-
const materiaMatch = fullText.match(/(PL|PEC|PLP|PDL|MPV|SUG)\s*n?º?\s*(\d+)\s*(?:de\s*)?(\d{4})/i);
|
|
2035
|
-
const materia = materiaMatch ? `${materiaMatch[1].toUpperCase()} ${materiaMatch[2]}/${materiaMatch[3]}` : "";
|
|
2036
|
-
const ementa = safeExtract(() => extractText($el), "", "ementa");
|
|
2037
|
-
let votosSim = 0;
|
|
2038
|
-
let votosNao = 0;
|
|
2039
|
-
const listVotesMatch = fullText.match(/(\d{1,3}(?:\.\d{3})*)\s+(\d{1,3}(?:\.\d{3})*)\s*SIM\s*N[ÃA]O/i);
|
|
2040
|
-
if (listVotesMatch) {
|
|
2041
|
-
votosSim = parseBrazilianNumber(listVotesMatch[1]);
|
|
2042
|
-
votosNao = parseBrazilianNumber(listVotesMatch[2]);
|
|
2043
|
-
} else {
|
|
2044
|
-
const detailVotesMatch = fullText.match(/(\d{1,3}(?:\.\d{3})*)\s+(\d{1,3}(?:\.\d{3})*)\s*Votos?\s*apurados?/i);
|
|
2045
|
-
if (detailVotesMatch) {
|
|
2046
|
-
votosSim = parseBrazilianNumber(detailVotesMatch[1]);
|
|
2047
|
-
votosNao = parseBrazilianNumber(detailVotesMatch[2]);
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
|
-
const totalVotos = votosSim + votosNao;
|
|
2051
|
-
const percentualSim = totalVotos > 0 ? Math.round(votosSim / totalVotos * 100) : 0;
|
|
2052
|
-
const percentualNao = totalVotos > 0 ? Math.round(votosNao / totalVotos * 100) : 0;
|
|
2053
|
-
let status = "aberta";
|
|
2054
|
-
if (fullText.toLowerCase().includes("encerrad")) {
|
|
2055
|
-
status = "encerrada";
|
|
2056
|
-
}
|
|
2057
|
-
consultas.push({
|
|
2058
|
-
id,
|
|
2059
|
-
materia,
|
|
2060
|
-
ementa: ementa.substring(0, 500),
|
|
2061
|
-
votosSim,
|
|
2062
|
-
votosNao,
|
|
2063
|
-
totalVotos,
|
|
2064
|
-
percentualSim,
|
|
2065
|
-
percentualNao,
|
|
2066
|
-
status,
|
|
2067
|
-
url: `${getBaseUrl()}${href}`
|
|
2068
|
-
});
|
|
2069
|
-
});
|
|
2070
|
-
const result = consultas.slice(0, limite);
|
|
2071
|
-
setCache(cacheKey, result, "LISTAGEM");
|
|
2072
|
-
return result;
|
|
2073
|
-
} catch (error) {
|
|
2074
|
-
logger.error({ error, params }, "Error listing consultas");
|
|
2075
|
-
throw error;
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
async function obterConsulta(id) {
|
|
2079
|
-
const cacheKey = `consultas:detalhe:${id}`;
|
|
2080
|
-
const cached = getCached(cacheKey);
|
|
2081
|
-
if (cached) return cached;
|
|
2082
|
-
try {
|
|
2083
|
-
const url = `/visualizacaomateria?id=${id}`;
|
|
2084
|
-
const html = await fetchPage(url);
|
|
2085
|
-
const $ = parseHtml(html);
|
|
2086
|
-
const fullText = $("body").text();
|
|
2087
|
-
const materiaMatch = fullText.match(/(PROJETO DE LEI|PEC|PLP|PDL|MPV|SUGEST[ÃA]O)\s*n?º?\s*(\d+)\s*(?:de\s*)?(\d{4})/i);
|
|
2088
|
-
let materia = "";
|
|
2089
|
-
if (materiaMatch) {
|
|
2090
|
-
const tipo = materiaMatch[1].toUpperCase().includes("PROJETO DE LEI") ? "PL" : materiaMatch[1].toUpperCase().includes("SUGEST") ? "SUG" : materiaMatch[1].toUpperCase();
|
|
2091
|
-
materia = `${tipo} ${materiaMatch[2]}/${materiaMatch[3]}`;
|
|
2092
|
-
}
|
|
2093
|
-
const ementa = safeExtract(
|
|
2094
|
-
() => {
|
|
2095
|
-
const ementaMatch = fullText.match(/(?:Concede|Altera|Dispõe|Institui|Estabelece|Autoriza|Revoga|Acrescenta)[^.]+\./i);
|
|
2096
|
-
return ementaMatch ? ementaMatch[0] : extractText($("h1, h2").first());
|
|
2097
|
-
},
|
|
2098
|
-
"",
|
|
2099
|
-
"ementa"
|
|
2100
|
-
);
|
|
2101
|
-
const votesMatch = fullText.match(/(\d{1,3}(?:\.\d{3})*)\s+(\d{1,3}(?:\.\d{3})*)\s*Votos?\s*apurados?/i);
|
|
2102
|
-
let votosSim = 0;
|
|
2103
|
-
let votosNao = 0;
|
|
2104
|
-
if (votesMatch) {
|
|
2105
|
-
votosSim = parseBrazilianNumber(votesMatch[1]);
|
|
2106
|
-
votosNao = parseBrazilianNumber(votesMatch[2]);
|
|
2107
|
-
}
|
|
2108
|
-
const totalVotos = votosSim + votosNao;
|
|
2109
|
-
const percentualSim = totalVotos > 0 ? Math.round(votosSim / totalVotos * 100) : 0;
|
|
2110
|
-
const percentualNao = totalVotos > 0 ? Math.round(votosNao / totalVotos * 100) : 0;
|
|
2111
|
-
const autor = safeExtract(
|
|
2112
|
-
() => {
|
|
2113
|
-
const autorMatch = fullText.match(/Autoria:\s*([^(]+(?:\([^)]+\))?)/i);
|
|
2114
|
-
return autorMatch ? autorMatch[1].trim() : null;
|
|
2115
|
-
},
|
|
2116
|
-
null,
|
|
2117
|
-
"autor"
|
|
2118
|
-
);
|
|
2119
|
-
const relator = safeExtract(
|
|
2120
|
-
() => {
|
|
2121
|
-
const relatorMatch = fullText.match(/Relator(?:a)?:\s*([^(]+(?:\([^)]+\))?)/i);
|
|
2122
|
-
return relatorMatch ? relatorMatch[1].trim() : null;
|
|
2123
|
-
},
|
|
2124
|
-
null,
|
|
2125
|
-
"relator"
|
|
2126
|
-
);
|
|
2127
|
-
const comentarios = safeExtract(
|
|
2128
|
-
() => {
|
|
2129
|
-
const comentarioMatch = fullText.match(/(\d+)\s*coment[aá]rio/i);
|
|
2130
|
-
return comentarioMatch ? parseInt(comentarioMatch[1]) : 0;
|
|
2131
|
-
},
|
|
2132
|
-
0,
|
|
2133
|
-
"comentarios"
|
|
2134
|
-
);
|
|
2135
|
-
let status = "aberta";
|
|
2136
|
-
if (fullText.toLowerCase().includes("encerrad")) {
|
|
2137
|
-
status = "encerrada";
|
|
2138
|
-
}
|
|
2139
|
-
const result = {
|
|
2140
|
-
id,
|
|
2141
|
-
materia,
|
|
2142
|
-
ementa,
|
|
2143
|
-
votosSim,
|
|
2144
|
-
votosNao,
|
|
2145
|
-
totalVotos,
|
|
2146
|
-
percentualSim,
|
|
2147
|
-
percentualNao,
|
|
2148
|
-
status,
|
|
2149
|
-
url: `${getBaseUrl()}${url}`,
|
|
2150
|
-
autor,
|
|
2151
|
-
relator,
|
|
2152
|
-
comissao: null,
|
|
2153
|
-
dataAbertura: null,
|
|
2154
|
-
dataEncerramento: null,
|
|
2155
|
-
comentarios,
|
|
2156
|
-
linkMateria: materia ? `https://legis.senado.leg.br/dadosabertos/materia/pesquisa/lista?sigla=${materia.split(" ")[0]}&numero=${materia.match(/\d+/)?.[0]}&ano=${materia.match(/\/(\d{4})/)?.[1]}` : null
|
|
2157
|
-
};
|
|
2158
|
-
setCache(cacheKey, result, "DETALHE");
|
|
2159
|
-
return result;
|
|
2160
|
-
} catch (error) {
|
|
2161
|
-
logger.error({ error, id }, "Error getting consulta details");
|
|
2162
|
-
throw error;
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2165
|
-
async function consultasPolarizadas(margemPolarizacao = 15, minimoVotos = 1e3, limite = 10) {
|
|
2166
|
-
const consultas = await listarConsultas({ limite: 100 });
|
|
2167
|
-
return consultas.filter((c) => {
|
|
2168
|
-
const diferenca = Math.abs(c.percentualSim - c.percentualNao);
|
|
2169
|
-
return c.totalVotos >= minimoVotos && diferenca <= margemPolarizacao;
|
|
2170
|
-
}).sort((a, b) => {
|
|
2171
|
-
const difA = Math.abs(a.percentualSim - a.percentualNao);
|
|
2172
|
-
const difB = Math.abs(b.percentualSim - b.percentualNao);
|
|
2173
|
-
return difA - difB;
|
|
2174
|
-
}).slice(0, limite);
|
|
2175
|
-
}
|
|
2176
|
-
async function consultasConsensuais(percentualMinimo = 85, minimoVotos = 1e3, limite = 10) {
|
|
2177
|
-
const consultas = await listarConsultas({ limite: 100 });
|
|
2178
|
-
return consultas.filter((c) => {
|
|
2179
|
-
const maiorPercentual = Math.max(c.percentualSim, c.percentualNao);
|
|
2180
|
-
return c.totalVotos >= minimoVotos && maiorPercentual >= percentualMinimo;
|
|
2181
|
-
}).sort((a, b) => {
|
|
2182
|
-
const maxA = Math.max(a.percentualSim, a.percentualNao);
|
|
2183
|
-
const maxB = Math.max(b.percentualSim, b.percentualNao);
|
|
2184
|
-
return maxB - maxA;
|
|
2185
|
-
}).slice(0, limite);
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
// src/tools/ecidadania/consultas.ts
|
|
2189
|
-
function handleScrapingError(error, toolName) {
|
|
2190
|
-
logger.error({ error, toolName }, "e-Cidadania scraping error");
|
|
2191
|
-
if (error.tipo) {
|
|
2192
|
-
const scrapingError = error;
|
|
2193
|
-
return {
|
|
2194
|
-
content: [{
|
|
2195
|
-
type: "text",
|
|
2196
|
-
text: JSON.stringify({
|
|
2197
|
-
success: false,
|
|
2198
|
-
error: {
|
|
2199
|
-
code: `ECIDADANIA_${scrapingError.tipo}`,
|
|
2200
|
-
message: scrapingError.mensagem,
|
|
2201
|
-
suggestion: scrapingError.sugestao,
|
|
2202
|
-
nota: "As demais funcionalidades do senado-br-mcp (senadores, mat\xE9rias, vota\xE7\xF5es) continuam operacionais."
|
|
2203
|
-
}
|
|
2204
|
-
}, null, 2)
|
|
2205
|
-
}]
|
|
2206
|
-
};
|
|
2207
|
-
}
|
|
2208
|
-
return {
|
|
2209
|
-
content: [{
|
|
2210
|
-
type: "text",
|
|
2211
|
-
text: JSON.stringify(createErrorResponse(
|
|
2212
|
-
"ECIDADANIA_SCRAPING_FALHOU",
|
|
2213
|
-
error instanceof Error ? error.message : "Erro desconhecido ao acessar e-Cidadania",
|
|
2214
|
-
"Acesse diretamente: https://www12.senado.leg.br/ecidadania/principalmateria"
|
|
2215
|
-
), null, 2)
|
|
2216
|
-
}]
|
|
2217
|
-
};
|
|
2218
|
-
}
|
|
2219
|
-
function registerConsultasTools(server) {
|
|
2220
|
-
server.tool(
|
|
2221
|
-
"senado_ecidadania_listar_consultas",
|
|
2222
|
-
"Lista consultas p\xFAblicas do e-Cidadania com vota\xE7\xE3o cidad\xE3 sobre mat\xE9rias em tramita\xE7\xE3o.",
|
|
2223
|
-
{
|
|
2224
|
-
status: import_zod11.z.enum(["aberta", "encerrada", "todas"]).optional().describe("Filtrar por status"),
|
|
2225
|
-
limite: import_zod11.z.number().int().min(1).max(100).optional().default(20).describe("N\xFAmero m\xE1ximo de resultados"),
|
|
2226
|
-
pagina: import_zod11.z.number().int().min(1).optional().default(1).describe("P\xE1gina de resultados")
|
|
2227
|
-
},
|
|
2228
|
-
async (params) => {
|
|
2229
|
-
try {
|
|
2230
|
-
const consultas = await listarConsultas(params);
|
|
2231
|
-
const result = createSuccessResponse(
|
|
2232
|
-
{ count: consultas.length, consultas },
|
|
2233
|
-
"/ecidadania/pesquisamateria"
|
|
2234
|
-
);
|
|
2235
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2236
|
-
} catch (error) {
|
|
2237
|
-
return handleScrapingError(error, "listar_consultas");
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2240
|
-
);
|
|
2241
|
-
server.tool(
|
|
2242
|
-
"senado_ecidadania_obter_consulta",
|
|
2243
|
-
"Obt\xE9m detalhes de uma consulta p\xFAblica espec\xEDfica, incluindo votos, autor e coment\xE1rios.",
|
|
2244
|
-
{
|
|
2245
|
-
id: import_zod11.z.number().int().positive().describe("ID da consulta p\xFAblica")
|
|
2246
|
-
},
|
|
2247
|
-
async (params) => {
|
|
2248
|
-
try {
|
|
2249
|
-
const consulta = await obterConsulta(params.id);
|
|
2250
|
-
const result = createSuccessResponse(consulta, `/ecidadania/visualizacaomateria?id=${params.id}`);
|
|
2251
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2252
|
-
} catch (error) {
|
|
2253
|
-
return handleScrapingError(error, "obter_consulta");
|
|
2254
|
-
}
|
|
2255
|
-
}
|
|
2256
|
-
);
|
|
2257
|
-
server.tool(
|
|
2258
|
-
"senado_ecidadania_consultas_polarizadas",
|
|
2259
|
-
"Retorna consultas com vota\xE7\xE3o equilibrada (~50/50), \xFAtil para identificar temas polarizados na sociedade.",
|
|
2260
|
-
{
|
|
2261
|
-
margemPolarizacao: import_zod11.z.number().int().min(0).max(50).optional().default(15).describe("Considera polarizado se diferen\xE7a < este percentual"),
|
|
2262
|
-
minimoVotos: import_zod11.z.number().int().min(0).optional().default(1e3).describe("M\xEDnimo de votos para considerar"),
|
|
2263
|
-
limite: import_zod11.z.number().int().min(1).max(50).optional().default(10).describe("N\xFAmero m\xE1ximo de resultados")
|
|
2264
|
-
},
|
|
2265
|
-
async (params) => {
|
|
2266
|
-
try {
|
|
2267
|
-
const consultas = await consultasPolarizadas(
|
|
2268
|
-
params.margemPolarizacao,
|
|
2269
|
-
params.minimoVotos,
|
|
2270
|
-
params.limite
|
|
2271
|
-
);
|
|
2272
|
-
const result = createSuccessResponse(
|
|
2273
|
-
{
|
|
2274
|
-
criterio: `Diferen\xE7a entre sim/n\xE3o < ${params.margemPolarizacao}%, m\xEDnimo ${params.minimoVotos} votos`,
|
|
2275
|
-
count: consultas.length,
|
|
2276
|
-
consultas
|
|
2277
|
-
},
|
|
2278
|
-
"/ecidadania/pesquisamateria"
|
|
2279
|
-
);
|
|
2280
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2281
|
-
} catch (error) {
|
|
2282
|
-
return handleScrapingError(error, "consultas_polarizadas");
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
);
|
|
2286
|
-
server.tool(
|
|
2287
|
-
"senado_ecidadania_consultas_consensuais",
|
|
2288
|
-
"Retorna consultas com alta concord\xE2ncia (>85% em uma dire\xE7\xE3o), \xFAtil para identificar temas de consenso.",
|
|
2289
|
-
{
|
|
2290
|
-
percentualMinimo: import_zod11.z.number().int().min(50).max(100).optional().default(85).describe("Percentual m\xEDnimo em uma dire\xE7\xE3o"),
|
|
2291
|
-
minimoVotos: import_zod11.z.number().int().min(0).optional().default(1e3).describe("M\xEDnimo de votos para considerar"),
|
|
2292
|
-
limite: import_zod11.z.number().int().min(1).max(50).optional().default(10).describe("N\xFAmero m\xE1ximo de resultados")
|
|
2293
|
-
},
|
|
2294
|
-
async (params) => {
|
|
2295
|
-
try {
|
|
2296
|
-
const consultas = await consultasConsensuais(
|
|
2297
|
-
params.percentualMinimo,
|
|
2298
|
-
params.minimoVotos,
|
|
2299
|
-
params.limite
|
|
2300
|
-
);
|
|
2301
|
-
const result = createSuccessResponse(
|
|
2302
|
-
{
|
|
2303
|
-
criterio: `>${params.percentualMinimo}% em uma dire\xE7\xE3o, m\xEDnimo ${params.minimoVotos} votos`,
|
|
2304
|
-
count: consultas.length,
|
|
2305
|
-
consultas
|
|
2306
|
-
},
|
|
2307
|
-
"/ecidadania/pesquisamateria"
|
|
2308
|
-
);
|
|
2309
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2310
|
-
} catch (error) {
|
|
2311
|
-
return handleScrapingError(error, "consultas_consensuais");
|
|
2312
|
-
}
|
|
2313
|
-
}
|
|
2314
|
-
);
|
|
2315
|
-
}
|
|
2316
|
-
|
|
2317
|
-
// src/tools/ecidadania/ideias.ts
|
|
2318
|
-
var import_zod12 = require("zod");
|
|
2319
|
-
|
|
2320
|
-
// src/scraper/pages/ideias.ts
|
|
2321
|
-
function parseBrazilianNumber2(str) {
|
|
2322
|
-
const cleaned = str.replace(/\./g, "");
|
|
2323
|
-
const num = parseInt(cleaned, 10);
|
|
2324
|
-
return isNaN(num) ? 0 : num;
|
|
2325
|
-
}
|
|
2326
|
-
function mapStatusToSituacao(status) {
|
|
2327
|
-
const map = {
|
|
2328
|
-
"aberta": "5",
|
|
2329
|
-
"encerrada": "6",
|
|
2330
|
-
"convertida": "10"
|
|
2331
|
-
};
|
|
2332
|
-
return status && map[status] ? map[status] : "";
|
|
2333
|
-
}
|
|
2334
|
-
async function listarIdeias(params = {}) {
|
|
2335
|
-
const { status, limite = 20, pagina = 1 } = params;
|
|
2336
|
-
const cacheKey = `ideias:lista:${JSON.stringify(params)}`;
|
|
2337
|
-
const cached = getCached(cacheKey);
|
|
2338
|
-
if (cached) return cached;
|
|
2339
|
-
try {
|
|
2340
|
-
let url = "/principalideia";
|
|
2341
|
-
if (status && status !== "todas") {
|
|
2342
|
-
url = "/pesquisaideia";
|
|
2343
|
-
const queryParams = [`situacao=${mapStatusToSituacao(status)}`];
|
|
2344
|
-
if (pagina > 1) {
|
|
2345
|
-
queryParams.push(`p=${pagina}`);
|
|
2346
|
-
}
|
|
2347
|
-
url += "?" + queryParams.join("&");
|
|
2348
|
-
}
|
|
2349
|
-
const html = await fetchPage(url);
|
|
2350
|
-
const $ = parseHtml(html);
|
|
2351
|
-
const ideias = [];
|
|
2352
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
2353
|
-
$('a[href*="visualizacaoideia?id="]').each((_, el) => {
|
|
2354
|
-
const $el = $(el);
|
|
2355
|
-
const href = $el.attr("href") || "";
|
|
2356
|
-
const id = extractId(href);
|
|
2357
|
-
if (!id || seenIds.has(id)) return;
|
|
2358
|
-
seenIds.add(id);
|
|
2359
|
-
const $container = $el.closest("div").parent().parent();
|
|
2360
|
-
const fullText = extractText($container);
|
|
2361
|
-
const titulo = safeExtract(() => extractText($el), "", "titulo");
|
|
2362
|
-
const apoios = safeExtract(
|
|
2363
|
-
() => {
|
|
2364
|
-
const apoiosMatch = fullText.match(/(\d{1,3}(?:\.\d{3})*)\s*apoios?/i);
|
|
2365
|
-
return apoiosMatch ? parseBrazilianNumber2(apoiosMatch[1]) : 0;
|
|
2366
|
-
},
|
|
2367
|
-
0,
|
|
2368
|
-
"apoios"
|
|
2369
|
-
);
|
|
2370
|
-
const dataPublicacao = safeExtract(() => extractDate(fullText), null, "dataPublicacao");
|
|
2371
|
-
let ideiaStatus = "aberta";
|
|
2372
|
-
if (fullText.toLowerCase().includes("encerrad")) {
|
|
2373
|
-
ideiaStatus = "encerrada";
|
|
2374
|
-
} else if (fullText.toLowerCase().includes("transformad") || fullText.toLowerCase().includes("convertid")) {
|
|
2375
|
-
ideiaStatus = "convertida";
|
|
2376
|
-
}
|
|
2377
|
-
const autor = safeExtract(
|
|
2378
|
-
() => {
|
|
2379
|
-
const autorMatch = fullText.match(/([A-ZÁÉÍÓÚÂÊÎÔÛÃÕÇ][A-ZÁÉÍÓÚÂÊÎÔÛÃÕÇ\s.]+)\s*\(([A-Z]{2})\)/);
|
|
2380
|
-
return autorMatch ? `${autorMatch[1].trim()} (${autorMatch[2]})` : null;
|
|
2381
|
-
},
|
|
2382
|
-
null,
|
|
2383
|
-
"autor"
|
|
2384
|
-
);
|
|
2385
|
-
ideias.push({
|
|
2386
|
-
id,
|
|
2387
|
-
titulo: titulo.substring(0, 500),
|
|
2388
|
-
apoios,
|
|
2389
|
-
dataPublicacao,
|
|
2390
|
-
status: ideiaStatus,
|
|
2391
|
-
autor,
|
|
2392
|
-
url: `${getBaseUrl()}${href}`
|
|
2393
|
-
});
|
|
2394
|
-
});
|
|
2395
|
-
if (params.ordenarPor === "apoios") {
|
|
2396
|
-
ideias.sort((a, b) => params.ordem === "asc" ? a.apoios - b.apoios : b.apoios - a.apoios);
|
|
2397
|
-
}
|
|
2398
|
-
const result = ideias.slice(0, limite);
|
|
2399
|
-
setCache(cacheKey, result, "LISTAGEM");
|
|
2400
|
-
return result;
|
|
2401
|
-
} catch (error) {
|
|
2402
|
-
logger.error({ error, params }, "Error listing ideias");
|
|
2403
|
-
throw error;
|
|
2404
|
-
}
|
|
2405
|
-
}
|
|
2406
|
-
async function obterIdeia(id) {
|
|
2407
|
-
const cacheKey = `ideias:detalhe:${id}`;
|
|
2408
|
-
const cached = getCached(cacheKey);
|
|
2409
|
-
if (cached) return cached;
|
|
2410
|
-
try {
|
|
2411
|
-
const url = `/visualizacaoideia?id=${id}`;
|
|
2412
|
-
const html = await fetchPage(url);
|
|
2413
|
-
const $ = parseHtml(html);
|
|
2414
|
-
const fullText = $("body").text();
|
|
2415
|
-
const titulo = safeExtract(
|
|
2416
|
-
() => {
|
|
2417
|
-
const strongText = extractText($("strong").first());
|
|
2418
|
-
if (strongText && strongText.length > 10) return strongText;
|
|
2419
|
-
return extractText($("h1, h2").first());
|
|
2420
|
-
},
|
|
2421
|
-
"",
|
|
2422
|
-
"titulo"
|
|
2423
|
-
);
|
|
2424
|
-
const descricao = safeExtract(
|
|
2425
|
-
() => {
|
|
2426
|
-
const descEl = $("p").filter((_, el) => {
|
|
2427
|
-
const text = $(el).text();
|
|
2428
|
-
return text.length > 100;
|
|
2429
|
-
}).first();
|
|
2430
|
-
return extractText(descEl) || "";
|
|
2431
|
-
},
|
|
2432
|
-
"",
|
|
2433
|
-
"descricao"
|
|
2434
|
-
);
|
|
2435
|
-
const apoios = safeExtract(
|
|
2436
|
-
() => {
|
|
2437
|
-
const apoiosMatch = fullText.match(/(\d{1,3}(?:\.\d{3})*)\s*apoios?/i);
|
|
2438
|
-
return apoiosMatch ? parseBrazilianNumber2(apoiosMatch[1]) : 0;
|
|
2439
|
-
},
|
|
2440
|
-
0,
|
|
2441
|
-
"apoios"
|
|
2442
|
-
);
|
|
2443
|
-
const autor = safeExtract(
|
|
2444
|
-
() => {
|
|
2445
|
-
const autorMatch = fullText.match(/([A-ZÁÉÍÓÚÂÊÎÔÛÃÕÇ][A-ZÁÉÍÓÚÂÊÎÔÛÃÕÇ\s.]+)\s*\(([A-Z]{2})\)/);
|
|
2446
|
-
return autorMatch ? `${autorMatch[1].trim()} (${autorMatch[2]})` : null;
|
|
2447
|
-
},
|
|
2448
|
-
null,
|
|
2449
|
-
"autor"
|
|
2450
|
-
);
|
|
2451
|
-
const comentarios = safeExtract(
|
|
2452
|
-
() => {
|
|
2453
|
-
const comentarioMatch = fullText.match(/(\d+)\s*coment[aá]rio/i);
|
|
2454
|
-
return comentarioMatch ? parseInt(comentarioMatch[1]) : 0;
|
|
2455
|
-
},
|
|
2456
|
-
0,
|
|
2457
|
-
"comentarios"
|
|
2458
|
-
);
|
|
2459
|
-
const plConvertido = safeExtract(
|
|
2460
|
-
() => {
|
|
2461
|
-
const plMatch = fullText.match(/(SUGEST[ÃA]O|PEC|PL|PLP)\s*n?º?\s*(\d+)\s*(?:de\s*)?(\d{4})/i);
|
|
2462
|
-
if (plMatch) {
|
|
2463
|
-
const tipo = plMatch[1].toUpperCase().includes("SUGEST") ? "SUG" : plMatch[1].toUpperCase();
|
|
2464
|
-
return `${tipo} ${plMatch[2]}/${plMatch[3]}`;
|
|
2465
|
-
}
|
|
2466
|
-
return null;
|
|
2467
|
-
},
|
|
2468
|
-
null,
|
|
2469
|
-
"plConvertido"
|
|
2470
|
-
);
|
|
2471
|
-
let status = "aberta";
|
|
2472
|
-
if (fullText.toLowerCase().includes("convertida em proposi")) {
|
|
2473
|
-
status = "convertida";
|
|
2474
|
-
} else if (fullText.toLowerCase().includes("encerrad")) {
|
|
2475
|
-
status = "encerrada";
|
|
2476
|
-
}
|
|
2477
|
-
const result = {
|
|
2478
|
-
id,
|
|
2479
|
-
titulo,
|
|
2480
|
-
apoios,
|
|
2481
|
-
dataPublicacao: extractDate(fullText),
|
|
2482
|
-
status,
|
|
2483
|
-
autor,
|
|
2484
|
-
url: `${getBaseUrl()}${url}`,
|
|
2485
|
-
descricao,
|
|
2486
|
-
problema: null,
|
|
2487
|
-
solucao: null,
|
|
2488
|
-
comentarios,
|
|
2489
|
-
dataEncerramento: null,
|
|
2490
|
-
plConvertido
|
|
2491
|
-
};
|
|
2492
|
-
setCache(cacheKey, result, "DETALHE");
|
|
2493
|
-
return result;
|
|
2494
|
-
} catch (error) {
|
|
2495
|
-
logger.error({ error, id }, "Error getting ideia details");
|
|
2496
|
-
throw error;
|
|
2497
|
-
}
|
|
2498
|
-
}
|
|
2499
|
-
async function ideiasPopulares(limite = 10, apenasAbertas = true) {
|
|
2500
|
-
const ideias = await listarIdeias({
|
|
2501
|
-
status: apenasAbertas ? "aberta" : "todas",
|
|
2502
|
-
ordenarPor: "apoios",
|
|
2503
|
-
ordem: "desc",
|
|
2504
|
-
limite: limite * 2
|
|
2505
|
-
// Get more to filter
|
|
2506
|
-
});
|
|
2507
|
-
return ideias.slice(0, limite);
|
|
2508
|
-
}
|
|
2509
|
-
|
|
2510
|
-
// src/tools/ecidadania/ideias.ts
|
|
2511
|
-
function handleScrapingError2(error, toolName) {
|
|
2512
|
-
logger.error({ error, toolName }, "e-Cidadania scraping error");
|
|
2513
|
-
if (error.tipo) {
|
|
2514
|
-
const scrapingError = error;
|
|
2515
|
-
return {
|
|
2516
|
-
content: [{
|
|
2517
|
-
type: "text",
|
|
2518
|
-
text: JSON.stringify({
|
|
2519
|
-
success: false,
|
|
2520
|
-
error: {
|
|
2521
|
-
code: `ECIDADANIA_${scrapingError.tipo}`,
|
|
2522
|
-
message: scrapingError.mensagem,
|
|
2523
|
-
suggestion: scrapingError.sugestao,
|
|
2524
|
-
nota: "As demais funcionalidades do senado-br-mcp (senadores, mat\xE9rias, vota\xE7\xF5es) continuam operacionais."
|
|
2525
|
-
}
|
|
2526
|
-
}, null, 2)
|
|
2527
|
-
}]
|
|
2528
|
-
};
|
|
2529
|
-
}
|
|
2530
|
-
return {
|
|
2531
|
-
content: [{
|
|
2532
|
-
type: "text",
|
|
2533
|
-
text: JSON.stringify(createErrorResponse(
|
|
2534
|
-
"ECIDADANIA_SCRAPING_FALHOU",
|
|
2535
|
-
error instanceof Error ? error.message : "Erro desconhecido ao acessar e-Cidadania",
|
|
2536
|
-
"Acesse diretamente: https://www12.senado.leg.br/ecidadania/principalideia"
|
|
2537
|
-
), null, 2)
|
|
2538
|
-
}]
|
|
2539
|
-
};
|
|
2540
|
-
}
|
|
2541
|
-
function registerIdeiasTools(server) {
|
|
2542
|
-
server.tool(
|
|
2543
|
-
"senado_ecidadania_listar_ideias",
|
|
2544
|
-
"Lista ideias legislativas propostas por cidad\xE3os no e-Cidadania.",
|
|
2545
|
-
{
|
|
2546
|
-
status: import_zod12.z.enum(["aberta", "encerrada", "convertida", "todas"]).optional().describe("Filtrar por status"),
|
|
2547
|
-
ordenarPor: import_zod12.z.enum(["apoios", "data", "comentarios"]).optional().describe("Campo para ordena\xE7\xE3o"),
|
|
2548
|
-
ordem: import_zod12.z.enum(["asc", "desc"]).optional().describe("Ordem de ordena\xE7\xE3o"),
|
|
2549
|
-
limite: import_zod12.z.number().int().min(1).max(100).optional().default(20).describe("N\xFAmero m\xE1ximo de resultados"),
|
|
2550
|
-
pagina: import_zod12.z.number().int().min(1).optional().default(1).describe("P\xE1gina de resultados")
|
|
2551
|
-
},
|
|
2552
|
-
async (params) => {
|
|
2553
|
-
try {
|
|
2554
|
-
const ideias = await listarIdeias(params);
|
|
2555
|
-
const result = createSuccessResponse(
|
|
2556
|
-
{ count: ideias.length, ideias },
|
|
2557
|
-
"/ecidadania/pesquisaideia"
|
|
2558
|
-
);
|
|
2559
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2560
|
-
} catch (error) {
|
|
2561
|
-
return handleScrapingError2(error, "listar_ideias");
|
|
2562
|
-
}
|
|
2563
|
-
}
|
|
2564
|
-
);
|
|
2565
|
-
server.tool(
|
|
2566
|
-
"senado_ecidadania_obter_ideia",
|
|
2567
|
-
"Obt\xE9m detalhes de uma ideia legislativa, incluindo descri\xE7\xE3o completa, apoios e se foi convertida em PL.",
|
|
2568
|
-
{
|
|
2569
|
-
id: import_zod12.z.number().int().positive().describe("ID da ideia legislativa")
|
|
2570
|
-
},
|
|
2571
|
-
async (params) => {
|
|
2572
|
-
try {
|
|
2573
|
-
const ideia = await obterIdeia(params.id);
|
|
2574
|
-
const result = createSuccessResponse(ideia, `/ecidadania/visualizacaoideia?id=${params.id}`);
|
|
2575
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2576
|
-
} catch (error) {
|
|
2577
|
-
return handleScrapingError2(error, "obter_ideia");
|
|
2578
|
-
}
|
|
2579
|
-
}
|
|
2580
|
-
);
|
|
2581
|
-
server.tool(
|
|
2582
|
-
"senado_ecidadania_ideias_populares",
|
|
2583
|
-
"Retorna as ideias legislativas mais apoiadas pelos cidad\xE3os.",
|
|
2584
|
-
{
|
|
2585
|
-
limite: import_zod12.z.number().int().min(1).max(50).optional().default(10).describe("N\xFAmero m\xE1ximo de resultados"),
|
|
2586
|
-
apenasAbertas: import_zod12.z.boolean().optional().default(true).describe("Apenas ideias com apoiamento aberto")
|
|
2587
|
-
},
|
|
2588
|
-
async (params) => {
|
|
2589
|
-
try {
|
|
2590
|
-
const ideias = await ideiasPopulares(params.limite, params.apenasAbertas);
|
|
2591
|
-
const result = createSuccessResponse(
|
|
2592
|
-
{
|
|
2593
|
-
criterio: params.apenasAbertas ? "Ideias abertas mais apoiadas" : "Todas as ideias mais apoiadas",
|
|
2594
|
-
count: ideias.length,
|
|
2595
|
-
ideias
|
|
2596
|
-
},
|
|
2597
|
-
"/ecidadania/principalideia"
|
|
2598
|
-
);
|
|
2599
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2600
|
-
} catch (error) {
|
|
2601
|
-
return handleScrapingError2(error, "ideias_populares");
|
|
2602
|
-
}
|
|
2603
|
-
}
|
|
2604
|
-
);
|
|
2605
|
-
}
|
|
2606
|
-
|
|
2607
|
-
// src/tools/ecidadania/eventos.ts
|
|
2608
|
-
var import_zod13 = require("zod");
|
|
2609
|
-
|
|
2610
|
-
// src/scraper/pages/eventos.ts
|
|
2611
|
-
function extractEventDate(text) {
|
|
2612
|
-
const match = text.match(/(\d{2})\/(\d{2})\/(\d{2,4})/);
|
|
2613
|
-
if (match) {
|
|
2614
|
-
const [, day, month, year] = match;
|
|
2615
|
-
const fullYear = year.length === 2 ? `20${year}` : year;
|
|
2616
|
-
return `${fullYear}-${month}-${day}`;
|
|
2617
|
-
}
|
|
2618
|
-
return null;
|
|
2619
|
-
}
|
|
2620
|
-
function extractEventTime(text) {
|
|
2621
|
-
const match = text.match(/(\d{2}):(\d{2})/);
|
|
2622
|
-
return match ? `${match[1]}:${match[2]}` : null;
|
|
2623
|
-
}
|
|
2624
|
-
async function listarEventos(params = {}) {
|
|
2625
|
-
const { limite = 20, pagina = 1 } = params;
|
|
2626
|
-
const cacheKey = `eventos:lista:${JSON.stringify(params)}`;
|
|
2627
|
-
const cached = getCached(cacheKey);
|
|
2628
|
-
if (cached) return cached;
|
|
2629
|
-
try {
|
|
2630
|
-
let url = "/principalaudiencia";
|
|
2631
|
-
if (pagina > 1) {
|
|
2632
|
-
url += `?p=${pagina}`;
|
|
2633
|
-
}
|
|
2634
|
-
const html = await fetchPage(url);
|
|
2635
|
-
const $ = parseHtml(html);
|
|
2636
|
-
const eventos = [];
|
|
2637
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
2638
|
-
$('a[href*="visualizacaoaudiencia?id="]').each((_, el) => {
|
|
2639
|
-
const $el = $(el);
|
|
2640
|
-
const href = $el.attr("href") || "";
|
|
2641
|
-
const id = extractId(href);
|
|
2642
|
-
if (!id || seenIds.has(id)) return;
|
|
2643
|
-
seenIds.add(id);
|
|
2644
|
-
const $container = $el.closest("div").parent().parent();
|
|
2645
|
-
const fullText = extractText($container);
|
|
2646
|
-
const titulo = safeExtract(() => extractText($el), "", "titulo");
|
|
2647
|
-
const data = safeExtract(() => extractEventDate(fullText), null, "data");
|
|
2648
|
-
const hora = safeExtract(() => extractEventTime(fullText), null, "hora");
|
|
2649
|
-
const comissao = safeExtract(
|
|
2650
|
-
() => {
|
|
2651
|
-
const comissaoMatch = fullText.match(/\d{2}:\d{2}\s*\|?\s*([A-Z]{2,15})/);
|
|
2652
|
-
if (comissaoMatch) return comissaoMatch[1];
|
|
2653
|
-
const knownMatch = fullText.match(/(CPICRIME|CPI[A-Z]*|CCJ|CAE|CRE|CAS|CDH|CI|CE|CMA|CRA)/);
|
|
2654
|
-
return knownMatch ? knownMatch[1] : null;
|
|
2655
|
-
},
|
|
2656
|
-
null,
|
|
2657
|
-
"comissao"
|
|
2658
|
-
);
|
|
2659
|
-
const comentarios = safeExtract(
|
|
2660
|
-
() => {
|
|
2661
|
-
const nums = fullText.match(/\b(\d{1,4})\b(?!\s*[/:.])/g);
|
|
2662
|
-
if (nums && nums.length > 0) {
|
|
2663
|
-
const lastNum = nums.filter((n) => {
|
|
2664
|
-
const num = parseInt(n);
|
|
2665
|
-
return num < 1e3 && num !== 26 && num !== 25 && num !== 24;
|
|
2666
|
-
}).pop();
|
|
2667
|
-
return lastNum ? parseInt(lastNum) : 0;
|
|
2668
|
-
}
|
|
2669
|
-
return 0;
|
|
2670
|
-
},
|
|
2671
|
-
0,
|
|
2672
|
-
"comentarios"
|
|
2673
|
-
);
|
|
2674
|
-
let status = "agendado";
|
|
2675
|
-
if (fullText.toLowerCase().includes("encerrad")) {
|
|
2676
|
-
status = "encerrado";
|
|
2677
|
-
} else if (fullText.toLowerCase().includes("andamento")) {
|
|
2678
|
-
status = "em_andamento";
|
|
2679
|
-
} else if (data) {
|
|
2680
|
-
const eventDate = new Date(data);
|
|
2681
|
-
const today = /* @__PURE__ */ new Date();
|
|
2682
|
-
today.setHours(0, 0, 0, 0);
|
|
2683
|
-
if (eventDate < today) {
|
|
2684
|
-
status = "encerrado";
|
|
2685
|
-
}
|
|
2686
|
-
}
|
|
2687
|
-
eventos.push({
|
|
2688
|
-
id,
|
|
2689
|
-
titulo: titulo.substring(0, 500),
|
|
2690
|
-
data,
|
|
2691
|
-
hora,
|
|
2692
|
-
comissao,
|
|
2693
|
-
comentarios,
|
|
2694
|
-
status,
|
|
2695
|
-
url: `${getBaseUrl()}${href}`
|
|
2696
|
-
});
|
|
2697
|
-
});
|
|
2698
|
-
let result = eventos;
|
|
2699
|
-
if (params.status && params.status !== "todos") {
|
|
2700
|
-
result = result.filter((e) => e.status === params.status);
|
|
2701
|
-
}
|
|
2702
|
-
if (params.comissao) {
|
|
2703
|
-
const comissaoUpper = params.comissao.toUpperCase();
|
|
2704
|
-
result = result.filter((e) => e.comissao?.toUpperCase().includes(comissaoUpper));
|
|
2705
|
-
}
|
|
2706
|
-
result = result.slice(0, limite);
|
|
2707
|
-
setCache(cacheKey, result, "LISTAGEM");
|
|
2708
|
-
return result;
|
|
2709
|
-
} catch (error) {
|
|
2710
|
-
logger.error({ error, params }, "Error listing eventos");
|
|
2711
|
-
throw error;
|
|
2712
|
-
}
|
|
2713
|
-
}
|
|
2714
|
-
async function obterEvento(id) {
|
|
2715
|
-
const cacheKey = `eventos:detalhe:${id}`;
|
|
2716
|
-
const cached = getCached(cacheKey);
|
|
2717
|
-
if (cached) return cached;
|
|
2718
|
-
try {
|
|
2719
|
-
const url = `/visualizacaoaudiencia?id=${id}`;
|
|
2720
|
-
const html = await fetchPage(url);
|
|
2721
|
-
const $ = parseHtml(html);
|
|
2722
|
-
const fullText = $("body").text();
|
|
2723
|
-
const titulo = safeExtract(
|
|
2724
|
-
() => {
|
|
2725
|
-
const h1Text = extractText($("h1").first());
|
|
2726
|
-
if (h1Text && h1Text.length > 10 && !h1Text.toLowerCase().includes("e-cidadania")) {
|
|
2727
|
-
return h1Text;
|
|
2728
|
-
}
|
|
2729
|
-
const strongText = extractText($("strong").filter((_, el) => {
|
|
2730
|
-
const text = $(el).text();
|
|
2731
|
-
return text.length > 20 && text.length < 300;
|
|
2732
|
-
}).first());
|
|
2733
|
-
return strongText || h1Text;
|
|
2734
|
-
},
|
|
2735
|
-
"",
|
|
2736
|
-
"titulo"
|
|
2737
|
-
);
|
|
2738
|
-
const descricao = safeExtract(
|
|
2739
|
-
() => {
|
|
2740
|
-
const descEl = $("p").filter((_, el) => {
|
|
2741
|
-
const text = $(el).text();
|
|
2742
|
-
return text.length > 50 && text.length < 2e3;
|
|
2743
|
-
}).first();
|
|
2744
|
-
return extractText(descEl) || "";
|
|
2745
|
-
},
|
|
2746
|
-
"",
|
|
2747
|
-
"descricao"
|
|
2748
|
-
);
|
|
2749
|
-
const data = safeExtract(() => extractEventDate(fullText), null, "data");
|
|
2750
|
-
const hora = safeExtract(() => extractEventTime(fullText), null, "hora");
|
|
2751
|
-
const comissao = safeExtract(
|
|
2752
|
-
() => {
|
|
2753
|
-
const comissaoMatch = fullText.match(/Comiss[ãa]o\s+(?:de\s+)?([^,\n]+?)(?:\s*[-–]|\s*$)/i);
|
|
2754
|
-
if (comissaoMatch) return comissaoMatch[1].trim();
|
|
2755
|
-
const abbrMatch = fullText.match(/(CPICRIME|CPI[A-Z]*|CCJ|CAE|CRE|CAS|CDH|CI|CE|CMA|CRA)/);
|
|
2756
|
-
return abbrMatch ? abbrMatch[1] : null;
|
|
2757
|
-
},
|
|
2758
|
-
null,
|
|
2759
|
-
"comissao"
|
|
2760
|
-
);
|
|
2761
|
-
const comentarios = safeExtract(
|
|
2762
|
-
() => {
|
|
2763
|
-
const comentarioMatch = fullText.match(/(\d+)\s*coment[aá]rio/i);
|
|
2764
|
-
return comentarioMatch ? parseInt(comentarioMatch[1]) : 0;
|
|
2765
|
-
},
|
|
2766
|
-
0,
|
|
2767
|
-
"comentarios"
|
|
2768
|
-
);
|
|
2769
|
-
const videoUrl = safeExtract(
|
|
2770
|
-
() => {
|
|
2771
|
-
const youtubeLink = $('a[href*="youtube"], a[href*="youtu.be"]').attr("href");
|
|
2772
|
-
if (youtubeLink) return youtubeLink;
|
|
2773
|
-
const iframeSrc = $('iframe[src*="youtube"]').attr("src");
|
|
2774
|
-
return iframeSrc || null;
|
|
2775
|
-
},
|
|
2776
|
-
null,
|
|
2777
|
-
"videoUrl"
|
|
2778
|
-
);
|
|
2779
|
-
const pauta = [];
|
|
2780
|
-
$("li").each((_, el) => {
|
|
2781
|
-
const text = extractText($(el));
|
|
2782
|
-
if (text.length > 15 && text.length < 500 && !text.includes("http")) {
|
|
2783
|
-
pauta.push(text);
|
|
2784
|
-
}
|
|
41
|
+
],
|
|
2785
42
|
});
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
//
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
type: "text",
|
|
2848
|
-
text: JSON.stringify({
|
|
2849
|
-
success: false,
|
|
2850
|
-
error: {
|
|
2851
|
-
code: `ECIDADANIA_${scrapingError.tipo}`,
|
|
2852
|
-
message: scrapingError.mensagem,
|
|
2853
|
-
suggestion: scrapingError.sugestao,
|
|
2854
|
-
nota: "As demais funcionalidades do senado-br-mcp (senadores, mat\xE9rias, vota\xE7\xF5es) continuam operacionais."
|
|
2855
|
-
}
|
|
2856
|
-
}, null, 2)
|
|
2857
|
-
}]
|
|
2858
|
-
};
|
|
2859
|
-
}
|
|
2860
|
-
return {
|
|
2861
|
-
content: [{
|
|
2862
|
-
type: "text",
|
|
2863
|
-
text: JSON.stringify(createErrorResponse(
|
|
2864
|
-
"ECIDADANIA_SCRAPING_FALHOU",
|
|
2865
|
-
error instanceof Error ? error.message : "Erro desconhecido ao acessar e-Cidadania",
|
|
2866
|
-
"Acesse diretamente: https://www12.senado.leg.br/ecidadania/principalaudiencia"
|
|
2867
|
-
), null, 2)
|
|
2868
|
-
}]
|
|
2869
|
-
};
|
|
43
|
+
// Every tool here only reads upstream open data — no writes, no side effects — and
|
|
44
|
+
// reaches external systems (Senate APIs / e-Cidadania) whose data is an open, changing
|
|
45
|
+
// set; and every tool returns a JSON object via toolResult(). Rather than repeat that
|
|
46
|
+
// metadata at every call site, wrap the group modules' `server.tool(name, desc, shape, cb)`
|
|
47
|
+
// calls and route them through registerTool() with shared annotations and a permissive
|
|
48
|
+
// object outputSchema. toolResult() supplies the matching structuredContent. The callback
|
|
49
|
+
// is also wrapped with instrumentTool() so every invocation is counted per tool.
|
|
50
|
+
const outputSchema = z.object({}).passthrough();
|
|
51
|
+
const registerTool = server.registerTool.bind(server);
|
|
52
|
+
const analytics = env.SENADO_ANALYTICS;
|
|
53
|
+
server.tool = (name, description, shape, cb) => registerTool(name, {
|
|
54
|
+
description,
|
|
55
|
+
inputSchema: shape,
|
|
56
|
+
outputSchema: outputSchema,
|
|
57
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
58
|
+
}, instrumentTool(name, cb, analytics));
|
|
59
|
+
const baseUrl = env.SENADO_BASE_URL || "https://legis.senado.leg.br/dadosabertos";
|
|
60
|
+
const admBaseUrl = env.SENADO_ADM_BASE_URL || "https://adm.senado.gov.br/adm-dadosabertos";
|
|
61
|
+
// Group H — Reference/metadata (1 tool — enum `tabela`)
|
|
62
|
+
registerReferenciaTools(server, baseUrl);
|
|
63
|
+
// Group A — Senators (5 tools)
|
|
64
|
+
registerSenadoresTools(server, baseUrl);
|
|
65
|
+
// Group B — Bills (2 tools; votos_materia is registered in Group D)
|
|
66
|
+
registerMateriasTools(server, baseUrl);
|
|
67
|
+
// Group D — Votes (3 tools)
|
|
68
|
+
registerVotacoesTools(server, baseUrl);
|
|
69
|
+
// Group E — Committees (7 tools)
|
|
70
|
+
registerComissoesTools(server, baseUrl);
|
|
71
|
+
// Group F — Plenary (7 tools)
|
|
72
|
+
registerPlenarioTools(server, baseUrl);
|
|
73
|
+
// Group C — Processes (5 tools)
|
|
74
|
+
registerProcessosTools(server, baseUrl);
|
|
75
|
+
// Group G — e-Cidadania (8 tools) — reads from D1 (env) and write-through detail (ctx)
|
|
76
|
+
registerECidadaniaTools(server, baseUrl, env, ctx);
|
|
77
|
+
// Group I — Speeches (3 tools)
|
|
78
|
+
registerDiscursosTools(server, baseUrl);
|
|
79
|
+
// Group J — Blocs & Leadership (4 tools)
|
|
80
|
+
registerComposicaoTools(server, baseUrl);
|
|
81
|
+
// Group K — Budget (1 tool — enum `tipo`)
|
|
82
|
+
registerOrcamentoTools(server, baseUrl);
|
|
83
|
+
// Group L — Federal Law (2 tools)
|
|
84
|
+
registerLegislacaoTools(server, baseUrl);
|
|
85
|
+
// Group M — Committee Voting (1 tool — enum `por`)
|
|
86
|
+
registerVotacaoComissaoTools(server, baseUrl);
|
|
87
|
+
// Group N — Taquigrafia (2 tools)
|
|
88
|
+
registerTaquigrafiaTools(server, baseUrl);
|
|
89
|
+
// Group O — Senadores/Administrativo (2 tools)
|
|
90
|
+
registerSenadoresAdminTools(server, admBaseUrl);
|
|
91
|
+
// Group P — Servidores / Gestão de Pessoas (4 tools)
|
|
92
|
+
registerServidoresTools(server, admBaseUrl);
|
|
93
|
+
// Group Q — Contratações (6 tools)
|
|
94
|
+
registerContratacoesTools(server, admBaseUrl);
|
|
95
|
+
// Group R — Suprimento de Fundos (1 tool)
|
|
96
|
+
registerSupridosTools(server, admBaseUrl);
|
|
97
|
+
// Group S — Orçamento do Senado (1 tool)
|
|
98
|
+
registerOrcamentoSenadoTools(server);
|
|
99
|
+
// MCP prompts (4 reusable pt-BR workflow templates) and resources (5 static
|
|
100
|
+
// context docs/tables) — advertised as the `prompts` and `resources` capabilities.
|
|
101
|
+
registerPrompts(server);
|
|
102
|
+
registerResources(server);
|
|
103
|
+
return server;
|
|
2870
104
|
}
|
|
2871
|
-
function registerEventosTools(server) {
|
|
2872
|
-
server.tool(
|
|
2873
|
-
"senado_ecidadania_listar_eventos",
|
|
2874
|
-
"Lista eventos interativos (audi\xEAncias p\xFAblicas, sabatinas, lives) do e-Cidadania.",
|
|
2875
|
-
{
|
|
2876
|
-
status: import_zod13.z.enum(["agendado", "encerrado", "todos"]).optional().describe("Filtrar por status"),
|
|
2877
|
-
comissao: import_zod13.z.string().optional().describe("Sigla da comiss\xE3o"),
|
|
2878
|
-
limite: import_zod13.z.number().int().min(1).max(100).optional().default(20).describe("N\xFAmero m\xE1ximo de resultados")
|
|
2879
|
-
},
|
|
2880
|
-
async (params) => {
|
|
2881
|
-
try {
|
|
2882
|
-
const eventos = await listarEventos(params);
|
|
2883
|
-
const result = createSuccessResponse(
|
|
2884
|
-
{ count: eventos.length, eventos },
|
|
2885
|
-
"/ecidadania/principalaudiencia"
|
|
2886
|
-
);
|
|
2887
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2888
|
-
} catch (error) {
|
|
2889
|
-
return handleScrapingError3(error, "listar_eventos");
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
|
-
);
|
|
2893
|
-
server.tool(
|
|
2894
|
-
"senado_ecidadania_obter_evento",
|
|
2895
|
-
"Obt\xE9m detalhes de um evento interativo, incluindo pauta, convidados e link para v\xEDdeo.",
|
|
2896
|
-
{
|
|
2897
|
-
id: import_zod13.z.number().int().positive().describe("ID do evento")
|
|
2898
|
-
},
|
|
2899
|
-
async (params) => {
|
|
2900
|
-
try {
|
|
2901
|
-
const evento = await obterEvento(params.id);
|
|
2902
|
-
const result = createSuccessResponse(evento, `/ecidadania/visualizacaoaudiencia?id=${params.id}`);
|
|
2903
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2904
|
-
} catch (error) {
|
|
2905
|
-
return handleScrapingError3(error, "obter_evento");
|
|
2906
|
-
}
|
|
2907
|
-
}
|
|
2908
|
-
);
|
|
2909
|
-
server.tool(
|
|
2910
|
-
"senado_ecidadania_eventos_populares",
|
|
2911
|
-
"Retorna eventos com mais coment\xE1rios e perguntas dos cidad\xE3os.",
|
|
2912
|
-
{
|
|
2913
|
-
limite: import_zod13.z.number().int().min(1).max(50).optional().default(10).describe("N\xFAmero m\xE1ximo de resultados"),
|
|
2914
|
-
apenasAgendados: import_zod13.z.boolean().optional().default(false).describe("Apenas eventos ainda n\xE3o realizados")
|
|
2915
|
-
},
|
|
2916
|
-
async (params) => {
|
|
2917
|
-
try {
|
|
2918
|
-
const eventos = await eventosPopulares(params.limite, params.apenasAgendados);
|
|
2919
|
-
const result = createSuccessResponse(
|
|
2920
|
-
{
|
|
2921
|
-
criterio: params.apenasAgendados ? "Eventos agendados mais comentados" : "Eventos mais comentados",
|
|
2922
|
-
count: eventos.length,
|
|
2923
|
-
eventos
|
|
2924
|
-
},
|
|
2925
|
-
"/ecidadania/principalaudiencia"
|
|
2926
|
-
);
|
|
2927
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2928
|
-
} catch (error) {
|
|
2929
|
-
return handleScrapingError3(error, "eventos_populares");
|
|
2930
|
-
}
|
|
2931
|
-
}
|
|
2932
|
-
);
|
|
2933
|
-
}
|
|
2934
|
-
|
|
2935
|
-
// src/tools/ecidadania/sugestao.ts
|
|
2936
|
-
var import_zod14 = require("zod");
|
|
2937
|
-
function handleScrapingError4(error, toolName) {
|
|
2938
|
-
logger.error({ error, toolName }, "e-Cidadania scraping error");
|
|
2939
|
-
if (error.tipo) {
|
|
2940
|
-
const scrapingError = error;
|
|
2941
|
-
return {
|
|
2942
|
-
content: [{
|
|
2943
|
-
type: "text",
|
|
2944
|
-
text: JSON.stringify({
|
|
2945
|
-
success: false,
|
|
2946
|
-
error: {
|
|
2947
|
-
code: `ECIDADANIA_${scrapingError.tipo}`,
|
|
2948
|
-
message: scrapingError.mensagem,
|
|
2949
|
-
suggestion: scrapingError.sugestao,
|
|
2950
|
-
nota: "As demais funcionalidades do senado-br-mcp continuam operacionais."
|
|
2951
|
-
}
|
|
2952
|
-
}, null, 2)
|
|
2953
|
-
}]
|
|
2954
|
-
};
|
|
2955
|
-
}
|
|
2956
|
-
return {
|
|
2957
|
-
content: [{
|
|
2958
|
-
type: "text",
|
|
2959
|
-
text: JSON.stringify(createErrorResponse(
|
|
2960
|
-
"ECIDADANIA_SCRAPING_FALHOU",
|
|
2961
|
-
error instanceof Error ? error.message : "Erro desconhecido",
|
|
2962
|
-
"Acesse diretamente: https://www12.senado.leg.br/ecidadania"
|
|
2963
|
-
), null, 2)
|
|
2964
|
-
}]
|
|
2965
|
-
};
|
|
2966
|
-
}
|
|
2967
|
-
function registerSugestaoTools(server) {
|
|
2968
|
-
server.tool(
|
|
2969
|
-
"senado_ecidadania_sugerir_tema_enquete",
|
|
2970
|
-
"Analisa e sugere temas para enquete mensal baseado em crit\xE9rios configur\xE1veis. Evita temas muito polarizados ou com consenso total.",
|
|
2971
|
-
{
|
|
2972
|
-
criterios: import_zod14.z.object({
|
|
2973
|
-
evitarPolarizacao: import_zod14.z.boolean().optional().default(true).describe("Evita temas com ~50/50"),
|
|
2974
|
-
evitarConsenso: import_zod14.z.boolean().optional().default(true).describe("Evita temas com >85%"),
|
|
2975
|
-
minimoParticipacao: import_zod14.z.number().int().min(0).optional().default(500).describe("M\xEDnimo de votos/apoios"),
|
|
2976
|
-
apenasEmTramitacao: import_zod14.z.boolean().optional().default(true).describe("Apenas mat\xE9rias em tramita\xE7\xE3o")
|
|
2977
|
-
}).optional()
|
|
2978
|
-
},
|
|
2979
|
-
async (params) => {
|
|
2980
|
-
try {
|
|
2981
|
-
const criterios = params.criterios || {
|
|
2982
|
-
evitarPolarizacao: true,
|
|
2983
|
-
evitarConsenso: true,
|
|
2984
|
-
minimoParticipacao: 500,
|
|
2985
|
-
apenasEmTramitacao: true
|
|
2986
|
-
};
|
|
2987
|
-
logger.info({ criterios }, "Suggesting themes for survey");
|
|
2988
|
-
const [consultas, ideias] = await Promise.all([
|
|
2989
|
-
listarConsultas({ limite: 50 }),
|
|
2990
|
-
listarIdeias({ status: "aberta", limite: 50, ordenarPor: "apoios", ordem: "desc" })
|
|
2991
|
-
]);
|
|
2992
|
-
const sugestoes = [];
|
|
2993
|
-
for (const consulta of consultas) {
|
|
2994
|
-
if (consulta.totalVotos < criterios.minimoParticipacao) continue;
|
|
2995
|
-
const polarizacao = Math.abs(consulta.percentualSim - consulta.percentualNao);
|
|
2996
|
-
if (criterios.evitarPolarizacao && polarizacao < 20) {
|
|
2997
|
-
continue;
|
|
2998
|
-
}
|
|
2999
|
-
const maiorPercentual = Math.max(consulta.percentualSim, consulta.percentualNao);
|
|
3000
|
-
if (criterios.evitarConsenso && maiorPercentual > 85) {
|
|
3001
|
-
continue;
|
|
3002
|
-
}
|
|
3003
|
-
let motivo = "";
|
|
3004
|
-
if (polarizacao >= 20 && polarizacao <= 40) {
|
|
3005
|
-
motivo = "Tema com divis\xE3o moderada de opini\xF5es, ideal para debate";
|
|
3006
|
-
} else if (polarizacao > 40 && polarizacao <= 70) {
|
|
3007
|
-
motivo = "Tema com tend\xEAncia clara mas ainda com debate significativo";
|
|
3008
|
-
} else {
|
|
3009
|
-
motivo = "Tema com boa participa\xE7\xE3o cidad\xE3";
|
|
3010
|
-
}
|
|
3011
|
-
sugestoes.push({
|
|
3012
|
-
tipo: "consulta",
|
|
3013
|
-
id: consulta.id,
|
|
3014
|
-
titulo: consulta.ementa.substring(0, 200),
|
|
3015
|
-
motivo,
|
|
3016
|
-
metricas: {
|
|
3017
|
-
participacao: consulta.totalVotos,
|
|
3018
|
-
polarizacao: 100 - polarizacao
|
|
3019
|
-
// Higher = more polarized (closer to 50/50)
|
|
3020
|
-
},
|
|
3021
|
-
materiaRelacionada: consulta.materia || void 0,
|
|
3022
|
-
url: consulta.url
|
|
3023
|
-
});
|
|
3024
|
-
}
|
|
3025
|
-
for (const ideia of ideias) {
|
|
3026
|
-
if (ideia.apoios < criterios.minimoParticipacao) continue;
|
|
3027
|
-
sugestoes.push({
|
|
3028
|
-
tipo: "ideia",
|
|
3029
|
-
id: ideia.id,
|
|
3030
|
-
titulo: ideia.titulo.substring(0, 200),
|
|
3031
|
-
motivo: `Ideia popular com ${ideia.apoios.toLocaleString()} apoios`,
|
|
3032
|
-
metricas: {
|
|
3033
|
-
participacao: ideia.apoios
|
|
3034
|
-
},
|
|
3035
|
-
url: ideia.url
|
|
3036
|
-
});
|
|
3037
|
-
}
|
|
3038
|
-
sugestoes.sort((a, b) => b.metricas.participacao - a.metricas.participacao);
|
|
3039
|
-
const result = createSuccessResponse(
|
|
3040
|
-
{
|
|
3041
|
-
criteriosAplicados: criterios,
|
|
3042
|
-
totalAnalisados: consultas.length + ideias.length,
|
|
3043
|
-
count: sugestoes.length,
|
|
3044
|
-
sugestoes: sugestoes.slice(0, 10)
|
|
3045
|
-
},
|
|
3046
|
-
"/ecidadania"
|
|
3047
|
-
);
|
|
3048
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3049
|
-
} catch (error) {
|
|
3050
|
-
return handleScrapingError4(error, "sugerir_tema_enquete");
|
|
3051
|
-
}
|
|
3052
|
-
}
|
|
3053
|
-
);
|
|
3054
|
-
}
|
|
3055
|
-
|
|
3056
|
-
// src/tools/ecidadania/index.ts
|
|
3057
|
-
function registerEcidadaniaTools(server) {
|
|
3058
|
-
registerConsultasTools(server);
|
|
3059
|
-
registerIdeiasTools(server);
|
|
3060
|
-
registerEventosTools(server);
|
|
3061
|
-
registerSugestaoTools(server);
|
|
3062
|
-
}
|
|
3063
|
-
|
|
3064
|
-
// src/resources-prompts.ts
|
|
3065
|
-
var import_zod15 = require("zod");
|
|
3066
|
-
var TIPOS_MATERIA2 = [
|
|
3067
|
-
{ sigla: "PEC", nome: "Proposta de Emenda \xE0 Constitui\xE7\xE3o" },
|
|
3068
|
-
{ sigla: "PLP", nome: "Projeto de Lei Complementar" },
|
|
3069
|
-
{ sigla: "PL", nome: "Projeto de Lei" },
|
|
3070
|
-
{ sigla: "PLN", nome: "Projeto de Lei do Congresso Nacional (or\xE7ament\xE1rio)" },
|
|
3071
|
-
{ sigla: "MPV", nome: "Medida Provis\xF3ria" },
|
|
3072
|
-
{ sigla: "PDL", nome: "Projeto de Decreto Legislativo" },
|
|
3073
|
-
{ sigla: "PRS", nome: "Projeto de Resolu\xE7\xE3o do Senado" },
|
|
3074
|
-
{ sigla: "RQS", nome: "Requerimento" },
|
|
3075
|
-
{ sigla: "SUG", nome: "Sugest\xE3o (inclui ideias legislativas do e-Cidadania)" }
|
|
3076
|
-
];
|
|
3077
|
-
var UFS2 = [
|
|
3078
|
-
{ sigla: "AC", nome: "Acre", regiao: "Norte" },
|
|
3079
|
-
{ sigla: "AL", nome: "Alagoas", regiao: "Nordeste" },
|
|
3080
|
-
{ sigla: "AP", nome: "Amap\xE1", regiao: "Norte" },
|
|
3081
|
-
{ sigla: "AM", nome: "Amazonas", regiao: "Norte" },
|
|
3082
|
-
{ sigla: "BA", nome: "Bahia", regiao: "Nordeste" },
|
|
3083
|
-
{ sigla: "CE", nome: "Cear\xE1", regiao: "Nordeste" },
|
|
3084
|
-
{ sigla: "DF", nome: "Distrito Federal", regiao: "Centro-Oeste" },
|
|
3085
|
-
{ sigla: "ES", nome: "Esp\xEDrito Santo", regiao: "Sudeste" },
|
|
3086
|
-
{ sigla: "GO", nome: "Goi\xE1s", regiao: "Centro-Oeste" },
|
|
3087
|
-
{ sigla: "MA", nome: "Maranh\xE3o", regiao: "Nordeste" },
|
|
3088
|
-
{ sigla: "MT", nome: "Mato Grosso", regiao: "Centro-Oeste" },
|
|
3089
|
-
{ sigla: "MS", nome: "Mato Grosso do Sul", regiao: "Centro-Oeste" },
|
|
3090
|
-
{ sigla: "MG", nome: "Minas Gerais", regiao: "Sudeste" },
|
|
3091
|
-
{ sigla: "PA", nome: "Par\xE1", regiao: "Norte" },
|
|
3092
|
-
{ sigla: "PB", nome: "Para\xEDba", regiao: "Nordeste" },
|
|
3093
|
-
{ sigla: "PR", nome: "Paran\xE1", regiao: "Sul" },
|
|
3094
|
-
{ sigla: "PE", nome: "Pernambuco", regiao: "Nordeste" },
|
|
3095
|
-
{ sigla: "PI", nome: "Piau\xED", regiao: "Nordeste" },
|
|
3096
|
-
{ sigla: "RJ", nome: "Rio de Janeiro", regiao: "Sudeste" },
|
|
3097
|
-
{ sigla: "RN", nome: "Rio Grande do Norte", regiao: "Nordeste" },
|
|
3098
|
-
{ sigla: "RS", nome: "Rio Grande do Sul", regiao: "Sul" },
|
|
3099
|
-
{ sigla: "RO", nome: "Rond\xF4nia", regiao: "Norte" },
|
|
3100
|
-
{ sigla: "RR", nome: "Roraima", regiao: "Norte" },
|
|
3101
|
-
{ sigla: "SC", nome: "Santa Catarina", regiao: "Sul" },
|
|
3102
|
-
{ sigla: "SP", nome: "S\xE3o Paulo", regiao: "Sudeste" },
|
|
3103
|
-
{ sigla: "SE", nome: "Sergipe", regiao: "Nordeste" },
|
|
3104
|
-
{ sigla: "TO", nome: "Tocantins", regiao: "Norte" }
|
|
3105
|
-
];
|
|
3106
|
-
function registerResourcesAndPrompts(server) {
|
|
3107
|
-
server.resource(
|
|
3108
|
-
"tipos-materia",
|
|
3109
|
-
"senado://tipos-materia",
|
|
3110
|
-
{
|
|
3111
|
-
description: "Cat\xE1logo dos tipos de mat\xE9ria legislativa do Senado (sigla e nome completo). \xDAtil para montar buscas em senado_buscar_materias.",
|
|
3112
|
-
mimeType: "application/json"
|
|
3113
|
-
},
|
|
3114
|
-
async () => ({
|
|
3115
|
-
contents: [
|
|
3116
|
-
{
|
|
3117
|
-
uri: "senado://tipos-materia",
|
|
3118
|
-
mimeType: "application/json",
|
|
3119
|
-
text: JSON.stringify(TIPOS_MATERIA2, null, 2)
|
|
3120
|
-
}
|
|
3121
|
-
]
|
|
3122
|
-
})
|
|
3123
|
-
);
|
|
3124
|
-
server.resource(
|
|
3125
|
-
"ufs",
|
|
3126
|
-
"senado://ufs",
|
|
3127
|
-
{
|
|
3128
|
-
description: "As 27 unidades da federa\xE7\xE3o representadas no Senado (3 senadores cada), com sigla, nome e regi\xE3o.",
|
|
3129
|
-
mimeType: "application/json"
|
|
3130
|
-
},
|
|
3131
|
-
async () => ({
|
|
3132
|
-
contents: [
|
|
3133
|
-
{
|
|
3134
|
-
uri: "senado://ufs",
|
|
3135
|
-
mimeType: "application/json",
|
|
3136
|
-
text: JSON.stringify(UFS2, null, 2)
|
|
3137
|
-
}
|
|
3138
|
-
]
|
|
3139
|
-
})
|
|
3140
|
-
);
|
|
3141
|
-
server.prompt(
|
|
3142
|
-
"panorama_senador",
|
|
3143
|
-
"Monta um panorama de um senador: identifica\xE7\xE3o, mandato, comiss\xF5es e vota\xE7\xF5es recentes.",
|
|
3144
|
-
{ nome: import_zod15.z.string().describe('Nome (ou parte) do senador, ex.: "Renan", "Simone Tebet"') },
|
|
3145
|
-
async ({ nome }) => ({
|
|
3146
|
-
messages: [
|
|
3147
|
-
{
|
|
3148
|
-
role: "user",
|
|
3149
|
-
content: {
|
|
3150
|
-
type: "text",
|
|
3151
|
-
text: `Monte um panorama do senador "${nome}". Use senado_buscar_senador_por_nome para localizar o c\xF3digo, depois senado_obter_senador para os dados (partido, UF, mandato, comiss\xF5es) e senado_votacoes_senador para as vota\xE7\xF5es recentes. Apresente um resumo organizado.`
|
|
3152
|
-
}
|
|
3153
|
-
}
|
|
3154
|
-
]
|
|
3155
|
-
})
|
|
3156
|
-
);
|
|
3157
|
-
server.prompt(
|
|
3158
|
-
"acompanhar_materia",
|
|
3159
|
-
"Acompanha uma mat\xE9ria legislativa: situa\xE7\xE3o atual, tramita\xE7\xE3o e textos.",
|
|
3160
|
-
{ identificacao: import_zod15.z.string().describe('Identifica\xE7\xE3o da mat\xE9ria, ex.: "PEC 45/2019", "PL 1234/2023"') },
|
|
3161
|
-
async ({ identificacao }) => ({
|
|
3162
|
-
messages: [
|
|
3163
|
-
{
|
|
3164
|
-
role: "user",
|
|
3165
|
-
content: {
|
|
3166
|
-
type: "text",
|
|
3167
|
-
text: `Acompanhe a mat\xE9ria "${identificacao}" no Senado. Use senado_buscar_materias para localiz\xE1-la, senado_obter_materia para a ementa e a situa\xE7\xE3o atual, e senado_tramitacao_materia para o hist\xF3rico de movimenta\xE7\xF5es. Resuma o est\xE1gio atual e os pr\xF3ximos passos.`
|
|
3168
|
-
}
|
|
3169
|
-
}
|
|
3170
|
-
]
|
|
3171
|
-
})
|
|
3172
|
-
);
|
|
3173
|
-
}
|
|
3174
|
-
|
|
3175
|
-
// src/middleware/rateLimiter.ts
|
|
3176
|
-
var MONTHLY_LIMIT = parseInt(process.env.MONTHLY_REQUEST_LIMIT || "10000");
|
|
3177
|
-
var ALERT_THRESHOLDS = [50, 80, 100];
|
|
3178
|
-
var WEBHOOK_URL = process.env.ALERT_WEBHOOK_URL || "";
|
|
3179
|
-
var usageData = {
|
|
3180
|
-
count: 0,
|
|
3181
|
-
month: getCurrentMonth(),
|
|
3182
|
-
alertsSent: []
|
|
3183
|
-
};
|
|
3184
|
-
function getCurrentMonth() {
|
|
3185
|
-
const now = /* @__PURE__ */ new Date();
|
|
3186
|
-
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
3187
|
-
}
|
|
3188
|
-
function getResetDate() {
|
|
3189
|
-
const now = /* @__PURE__ */ new Date();
|
|
3190
|
-
const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1);
|
|
3191
|
-
return nextMonth.toISOString().split("T")[0];
|
|
3192
|
-
}
|
|
3193
|
-
async function sendAlert(percentage) {
|
|
3194
|
-
const message = `\u26A0\uFE0F senado-br-mcp: ${percentage}% do limite mensal atingido (${usageData.count}/${MONTHLY_LIMIT} requisi\xE7\xF5es)`;
|
|
3195
|
-
logger.warn({ percentage, count: usageData.count, limit: MONTHLY_LIMIT }, message);
|
|
3196
|
-
if (WEBHOOK_URL) {
|
|
3197
|
-
try {
|
|
3198
|
-
await fetch(WEBHOOK_URL, {
|
|
3199
|
-
method: "POST",
|
|
3200
|
-
headers: { "Content-Type": "application/json" },
|
|
3201
|
-
body: JSON.stringify({
|
|
3202
|
-
text: message,
|
|
3203
|
-
usage: {
|
|
3204
|
-
current: usageData.count,
|
|
3205
|
-
limit: MONTHLY_LIMIT,
|
|
3206
|
-
percentage,
|
|
3207
|
-
month: usageData.month,
|
|
3208
|
-
resetDate: getResetDate()
|
|
3209
|
-
}
|
|
3210
|
-
})
|
|
3211
|
-
});
|
|
3212
|
-
} catch (error) {
|
|
3213
|
-
logger.error({ error }, "Failed to send webhook alert");
|
|
3214
|
-
}
|
|
3215
|
-
}
|
|
3216
|
-
}
|
|
3217
|
-
async function checkMonthlyLimit() {
|
|
3218
|
-
const currentMonth = getCurrentMonth();
|
|
3219
|
-
if (usageData.month !== currentMonth) {
|
|
3220
|
-
logger.info({ oldMonth: usageData.month, newMonth: currentMonth }, "Monthly reset");
|
|
3221
|
-
usageData = {
|
|
3222
|
-
count: 0,
|
|
3223
|
-
month: currentMonth,
|
|
3224
|
-
alertsSent: []
|
|
3225
|
-
};
|
|
3226
|
-
}
|
|
3227
|
-
const percentage = Math.round(usageData.count / MONTHLY_LIMIT * 100);
|
|
3228
|
-
const exceeded = usageData.count >= MONTHLY_LIMIT;
|
|
3229
|
-
for (const threshold of ALERT_THRESHOLDS) {
|
|
3230
|
-
if (percentage >= threshold && !usageData.alertsSent.includes(threshold)) {
|
|
3231
|
-
usageData.alertsSent.push(threshold);
|
|
3232
|
-
await sendAlert(threshold);
|
|
3233
|
-
}
|
|
3234
|
-
}
|
|
3235
|
-
const status = {
|
|
3236
|
-
exceeded,
|
|
3237
|
-
current: usageData.count,
|
|
3238
|
-
limit: MONTHLY_LIMIT,
|
|
3239
|
-
percentage,
|
|
3240
|
-
month: usageData.month,
|
|
3241
|
-
resetDate: getResetDate()
|
|
3242
|
-
};
|
|
3243
|
-
if (exceeded) {
|
|
3244
|
-
status.message = `O servidor atingiu o limite mensal de requisi\xE7\xF5es gratuitas. Voltar\xE1 a funcionar em ${getResetDate()}. Para uso imediato, instale localmente via npm.`;
|
|
3245
|
-
status.alternatives = {
|
|
3246
|
-
npm: "npx senado-br-mcp",
|
|
3247
|
-
docs: "https://github.com/SidneyBissoli/senado-br-mcp#installation",
|
|
3248
|
-
sponsor: "https://github.com/sponsors/SidneyBissoli"
|
|
3249
|
-
};
|
|
3250
|
-
}
|
|
3251
|
-
return status;
|
|
3252
|
-
}
|
|
3253
|
-
async function incrementCounter() {
|
|
3254
|
-
usageData.count++;
|
|
3255
|
-
logger.debug({ count: usageData.count }, "Request counter incremented");
|
|
3256
|
-
}
|
|
3257
|
-
function getUsageStats() {
|
|
3258
|
-
const percentage = Math.round(usageData.count / MONTHLY_LIMIT * 100);
|
|
3259
|
-
return {
|
|
3260
|
-
exceeded: usageData.count >= MONTHLY_LIMIT,
|
|
3261
|
-
current: usageData.count,
|
|
3262
|
-
limit: MONTHLY_LIMIT,
|
|
3263
|
-
percentage,
|
|
3264
|
-
month: usageData.month,
|
|
3265
|
-
resetDate: getResetDate()
|
|
3266
|
-
};
|
|
3267
|
-
}
|
|
3268
|
-
|
|
3269
|
-
// src/server.ts
|
|
3270
|
-
var PORT = parseInt(process.env.PORT || "3000");
|
|
3271
|
-
function createMcpServer() {
|
|
3272
|
-
const server = new import_mcp.McpServer({
|
|
3273
|
-
name: "senado-br-mcp",
|
|
3274
|
-
version: "1.2.0"
|
|
3275
|
-
});
|
|
3276
|
-
registerSenadoresTools(server);
|
|
3277
|
-
registerMateriasTools(server);
|
|
3278
|
-
registerVotacoesTools(server);
|
|
3279
|
-
registerComissoesTools(server);
|
|
3280
|
-
registerAgendaTools(server);
|
|
3281
|
-
registerAuxiliaresTools(server);
|
|
3282
|
-
registerEcidadaniaTools(server);
|
|
3283
|
-
registerResourcesAndPrompts(server);
|
|
3284
|
-
return server;
|
|
3285
|
-
}
|
|
3286
|
-
var app = (0, import_express.default)();
|
|
3287
|
-
app.use(import_express.default.json());
|
|
3288
|
-
app.get("/health", (_req, res) => {
|
|
3289
|
-
res.status(200).send("OK");
|
|
3290
|
-
});
|
|
3291
|
-
app.get("/stats", (_req, res) => {
|
|
3292
|
-
const stats = getUsageStats();
|
|
3293
|
-
res.json(stats);
|
|
3294
|
-
});
|
|
3295
|
-
app.post("/mcp", async (req, res) => {
|
|
3296
|
-
try {
|
|
3297
|
-
const limitStatus = await checkMonthlyLimit();
|
|
3298
|
-
if (limitStatus.exceeded) {
|
|
3299
|
-
res.status(429).json({
|
|
3300
|
-
error: "MONTHLY_LIMIT_REACHED",
|
|
3301
|
-
message: limitStatus.message,
|
|
3302
|
-
alternatives: limitStatus.alternatives,
|
|
3303
|
-
resetDate: limitStatus.resetDate
|
|
3304
|
-
});
|
|
3305
|
-
return;
|
|
3306
|
-
}
|
|
3307
|
-
const server = createMcpServer();
|
|
3308
|
-
const transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
3309
|
-
sessionIdGenerator: void 0
|
|
3310
|
-
});
|
|
3311
|
-
res.on("close", () => {
|
|
3312
|
-
transport.close();
|
|
3313
|
-
});
|
|
3314
|
-
await server.connect(transport);
|
|
3315
|
-
await transport.handleRequest(req, res, req.body);
|
|
3316
|
-
await incrementCounter();
|
|
3317
|
-
} catch (error) {
|
|
3318
|
-
logger.error({ error }, "Error handling MCP request");
|
|
3319
|
-
res.status(500).json({
|
|
3320
|
-
error: "INTERNAL_ERROR",
|
|
3321
|
-
message: "An internal error occurred"
|
|
3322
|
-
});
|
|
3323
|
-
}
|
|
3324
|
-
});
|
|
3325
|
-
app.get("/mcp", async (req, res) => {
|
|
3326
|
-
try {
|
|
3327
|
-
const limitStatus = await checkMonthlyLimit();
|
|
3328
|
-
if (limitStatus.exceeded) {
|
|
3329
|
-
res.status(429).json({
|
|
3330
|
-
error: "MONTHLY_LIMIT_REACHED",
|
|
3331
|
-
message: limitStatus.message,
|
|
3332
|
-
alternatives: limitStatus.alternatives,
|
|
3333
|
-
resetDate: limitStatus.resetDate
|
|
3334
|
-
});
|
|
3335
|
-
return;
|
|
3336
|
-
}
|
|
3337
|
-
const server = createMcpServer();
|
|
3338
|
-
const transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
3339
|
-
sessionIdGenerator: void 0
|
|
3340
|
-
});
|
|
3341
|
-
res.on("close", () => {
|
|
3342
|
-
transport.close();
|
|
3343
|
-
});
|
|
3344
|
-
await server.connect(transport);
|
|
3345
|
-
await transport.handleRequest(req, res);
|
|
3346
|
-
} catch (error) {
|
|
3347
|
-
logger.error({ error }, "Error handling MCP SSE request");
|
|
3348
|
-
res.status(500).json({
|
|
3349
|
-
error: "INTERNAL_ERROR",
|
|
3350
|
-
message: "An internal error occurred"
|
|
3351
|
-
});
|
|
3352
|
-
}
|
|
3353
|
-
});
|
|
3354
|
-
app.delete("/mcp", async (_req, res) => {
|
|
3355
|
-
res.status(200).json({ message: "Session closed" });
|
|
3356
|
-
});
|
|
3357
|
-
app.get("/", (_req, res) => {
|
|
3358
|
-
res.json({
|
|
3359
|
-
name: "senado-br-mcp",
|
|
3360
|
-
description: "MCP server for Brazilian Federal Senate open data",
|
|
3361
|
-
version: "1.2.0",
|
|
3362
|
-
endpoints: {
|
|
3363
|
-
mcp: "/mcp",
|
|
3364
|
-
health: "/health",
|
|
3365
|
-
stats: "/stats"
|
|
3366
|
-
},
|
|
3367
|
-
documentation: "https://github.com/SidneyBissoli/senado-br-mcp",
|
|
3368
|
-
usage: {
|
|
3369
|
-
claude_desktop: {
|
|
3370
|
-
url: `https://web-production-e32f8.up.railway.app/mcp`,
|
|
3371
|
-
instructions: "Add as custom connector in Claude Desktop settings"
|
|
3372
|
-
},
|
|
3373
|
-
npm: {
|
|
3374
|
-
command: "npx senado-br-mcp",
|
|
3375
|
-
instructions: "Add to claude_desktop_config.json"
|
|
3376
|
-
}
|
|
3377
|
-
}
|
|
3378
|
-
});
|
|
3379
|
-
});
|
|
3380
|
-
app.listen(PORT, () => {
|
|
3381
|
-
logger.info({ port: PORT }, "senado-br-mcp HTTP server started");
|
|
3382
|
-
console.log(`\u{1F680} senado-br-mcp server running on port ${PORT}`);
|
|
3383
|
-
console.log(` Health: http://localhost:${PORT}/health`);
|
|
3384
|
-
console.log(` MCP: http://localhost:${PORT}/mcp`);
|
|
3385
|
-
console.log(` Stats: http://localhost:${PORT}/stats`);
|
|
3386
|
-
});
|