senado-br-mcp 3.5.1 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -14,7 +14,8 @@
14
14
  * This file is NOT imported by the Worker entrypoint (`src/index.ts`) and never
15
15
  * touches `agents/mcp`. It only depends on `createServer` + the stdio transport.
16
16
  */
17
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
17
+ import { serveStdio, StdioServerTransport } from "@modelcontextprotocol/server/stdio";
18
+ import { unknownCursorError } from "./pagination.js";
18
19
  import { createServer } from "./server.js";
19
20
  // No Cloudflare bindings in Node: createServer defaults the base URLs when these
20
21
  // are empty, and every binding-dependent path is guarded. CACHE_KV is declared on
@@ -26,8 +27,30 @@ const env = {
26
27
  };
27
28
  async function main() {
28
29
  // No `ctx` → e-Cidadania detail write-through is a no-op (fire-and-forget skipped).
29
- const server = createServer(env);
30
- await server.connect(new StdioServerTransport());
30
+ // `serveStdio`, e não `server.connect(transport)` direto: ele serve a
31
+ // abertura MODERNA e a de 2025 no mesmo processo. Conectar o transporte na
32
+ // mão atende só o ciclo legado, e o mcpscore conta a prontidão para a spec
33
+ // 2026-07-28 dentro da nota principal — medido em 30/08/2026: 127/144 com
34
+ // `connect` direto contra 146/148 nos irmãos que usam `serveStdio`, sem UMA
35
+ // falha de diferença. Eram 17 pontos de regras que sequer eram avaliadas.
36
+ //
37
+ // O transporte é construído aqui, e não deixado a cargo do `serveStdio`, para
38
+ // que o guarda de cursor possa se pendurar nele. Cursor de paginação inválido
39
+ // -> -32602, o MESMO guarda que o Worker aplica no POST (src/pagination.ts).
40
+ // Substitui `onmessage` em vez de somar um ouvinte: só quem está NO lugar do
41
+ // `onmessage` pode interromper a entrega ao SDK, e é a interrupção que produz
42
+ // a recusa.
43
+ const transport = new StdioServerTransport();
44
+ serveStdio(() => createServer(env), { transport });
45
+ const entregaAoServidor = transport.onmessage;
46
+ transport.onmessage = (message) => {
47
+ const recusa = unknownCursorError(message);
48
+ if (recusa) {
49
+ void transport.send(recusa);
50
+ return;
51
+ }
52
+ entregaAoServidor?.(message);
53
+ };
31
54
  // Logs go to stderr (see src/utils/logger.ts); stdout carries only JSON-RPC.
32
55
  console.error(JSON.stringify({ level: "info", msg: "stdio_ready", ts: new Date().toISOString() }));
33
56
  }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `server/discover` anuncia TODAS as revisões que o servidor atende, não só as
3
+ * modernas.
4
+ *
5
+ * O ACHADO. O `mcpscore` reprovava `protocol_version_supported_versions_include_negotiated`:
6
+ * o servidor negocia 2025-11-25 pelo handshake `initialize` — é a revisão que o
7
+ * próprio auditor negocia, e a que a maioria dos clientes fala hoje — mas o
8
+ * `server/discover` anunciava só `["2026-07-28"]`. A mensagem do auditor é
9
+ * explícita sobre as duas saídas: "add it to supportedVersions, or disable the
10
+ * legacy lifecycle". Desligar o ciclo legado trocaria 1 ponto por uma regressão
11
+ * de compatibilidade com quase todos os clientes; sobra anunciar a verdade.
12
+ *
13
+ * POR QUE O SDK NÃO FAZ ISSO. O `_ondiscover()` do
14
+ * `@modelcontextprotocol/server` 2.0.0 devolve
15
+ * `modernProtocolVersions(this._supportedProtocolVersions)`, um filtro que
16
+ * descarta por desenho toda revisão pré-2026 ("2025-era versions are negotiated
17
+ * via `initialize`"). Não há opção que o desligue. É uma leitura mais estrita
18
+ * que a do auditor, que se apoia em Basic §Versioning — onde o exemplo de
19
+ * `UnsupportedProtocolVersionError` lista revisões legadas em `supported`.
20
+ *
21
+ * POR QUE PATCH DE MÉTODO, E NÃO TROCA DE HANDLER. Registrar o nosso próprio
22
+ * handler de `server/discover` não gruda: a entrada HTTP do SDK chama
23
+ * `installModernOnlyHandlers` A CADA REQUEST, depois da nossa factory, e ela
24
+ * reinstala `() => server._ondiscover()` por cima. Como o que ela instala é uma
25
+ * closure que chama o MÉTODO, trocar o método vale para os dois caminhos — o
26
+ * handler do construtor e o reinstalado — e sobrevive à reinstalação. Trocar o
27
+ * handler valeria só no stdio, justamente onde o achado não aparece.
28
+ *
29
+ * A GUARDA. Isto mexe em `_ondiscover`, que é interno do SDK. O risco de um
30
+ * patch assim é virar silêncio: o SDK renomeia o método, o patch não aplica
31
+ * nada e ninguém percebe. Por isso `announceServedVersions` FALHA ALTO se a
32
+ * forma esperada não estiver lá — o servidor nem constrói — em vez de seguir
33
+ * com o anúncio antigo. `tests/discover.test.ts` prende o comportamento, e a
34
+ * catraca do mcpscore no CI pega a regressão pelo lado de fora.
35
+ */
36
+ /**
37
+ * Ordena revisões datadas da mais nova para a mais antiga. São strings
38
+ * `AAAA-MM-DD`, então a ordem lexicográfica É a cronológica.
39
+ */
40
+ export function maisNovaPrimeiro(versoes) {
41
+ return [...new Set(versoes)].sort().reverse();
42
+ }
43
+ /**
44
+ * Troca o `_ondiscover` da instância por um que anuncia todas as revisões
45
+ * atendidas. Só a lista muda: capacidades e instruções continuam vindo do
46
+ * cálculo do próprio SDK.
47
+ */
48
+ export function announceServedVersions(server) {
49
+ const low = server.server;
50
+ if (typeof low._ondiscover !== "function") {
51
+ throw new Error("SDK mudou: Server._ondiscover sumiu — o anúncio de versões em src/discover.ts " +
52
+ "deixaria de aplicar em silêncio. Reveja o módulo contra a versão nova do " +
53
+ "@modelcontextprotocol/server antes de seguir.");
54
+ }
55
+ if (!Array.isArray(low._supportedProtocolVersions) ||
56
+ low._supportedProtocolVersions.some((v) => typeof v !== "string")) {
57
+ throw new Error("SDK mudou: Server._supportedProtocolVersions não é mais uma lista de strings — " +
58
+ "src/discover.ts não tem de onde tirar as revisões atendidas.");
59
+ }
60
+ const original = low._ondiscover.bind(low);
61
+ low._ondiscover = () => {
62
+ const base = original();
63
+ // LER NA HORA DA CHAMADA, não aqui na aplicação do patch. O
64
+ // `installDiscoverHandler` do SDK ACRESCENTA as revisões modernas a
65
+ // `_supportedProtocolVersions` por REATRIBUIÇÃO, já com o servidor
66
+ // construído: uma referência capturada agora ficaria presa ao array
67
+ // anterior. Foi o que aconteceu na primeira versão deste módulo — o anúncio
68
+ // saiu com as cinco revisões legadas e SEM a 2026-07-28, e a regra do
69
+ // auditor passou assim mesmo, porque ela só confere se a negociada está na
70
+ // lista. Trocar uma omissão por outra não é corrigir.
71
+ const agora = low._supportedProtocolVersions;
72
+ if (!Array.isArray(agora) || agora.some((v) => typeof v !== "string"))
73
+ return base;
74
+ // A lista vem do próprio SDK, nunca de literais nossas: anunciar uma
75
+ // revisão que o servidor não atende seria a mentira simétrica da que este
76
+ // módulo corrige.
77
+ return { ...base, supportedVersions: maisNovaPrimeiro(agora) };
78
+ };
79
+ }
package/dist/metrics.js CHANGED
@@ -3,6 +3,8 @@ const counters = {
3
3
  requests: 0,
4
4
  errors: 0,
5
5
  authFailures: 0,
6
+ /** Lista pedida com cursor de paginação que este servidor nunca emitiu. */
7
+ invalidCursor: 0,
6
8
  cacheL0Hits: 0,
7
9
  cacheL1Hits: 0,
8
10
  cacheMisses: 0,
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Cursor de paginação inválido — a recusa que o SDK não faz.
3
+ *
4
+ * O PROBLEMA. Toda lista deste servidor cabe numa página só, então
5
+ * nenhuma resposta traz `nextCursor` e
6
+ * nenhum cursor emitido por este servidor existe. Os quatro handlers de lista do
7
+ * `@modelcontextprotocol/server` 2.x simplesmente IGNORAM `params.cursor` e
8
+ * devolvem a lista inteira — inclusive para um cursor que o servidor nunca
9
+ * emitiu. A spec (§Pagination, Error Handling, em toda revisão datada) diz que
10
+ * cursor inválido DEVE virar `-32602`, e o `mcpscore` reprovava os quatro
11
+ * endpoints. Medido aqui em 30/08/2026, logo depois da migração para o SDK v2:
12
+ * 161/173 local, e estes quatro eram 4 das 6 falhas reais.
13
+ *
14
+ * POR QUE AQUI, E NÃO NO HANDLER DO SDK. Sobrescrever `tools/list` exigiria
15
+ * reimplementar a listagem a partir do registro privado do McpServer
16
+ * (`_registeredTools`, com a conversão de schema junto) — trocar um defeito
17
+ * pequeno por uma cópia do SDK que envelhece sozinha. A recusa é uma decisão
18
+ * sobre a MENSAGEM, não sobre o conteúdo da lista, então mora na borda por onde
19
+ * a mensagem entra: o POST do Worker (src/index.ts) e o transporte stdio
20
+ * (src/cli.ts). O predicado é o mesmo nos dois — este módulo.
21
+ *
22
+ * ENQUANTO NÃO HOUVER SEGUNDA PÁGINA. Se algum dia uma lista passar a paginar de
23
+ * verdade, este módulo deixa de valer: a recusa passa a ser "cursor que não
24
+ * decodifica", e quem emite o cursor é quem sabe reconhecê-lo. O teste
25
+ * `pagination.test.ts` prende a premissa (nenhuma lista emite `nextCursor`) para
26
+ * que a mudança não passe em silêncio.
27
+ *
28
+ * Transplantado de bcb-br-mcp/src/pagination.ts, por sua vez transplantado do
29
+ * ilo-mcp-server — a implementação de referência do portfólio.
30
+ */
31
+ /** Código JSON-RPC de parâmetro inválido (spec §Pagination, Error Handling). */
32
+ export const INVALID_PARAMS = -32602;
33
+ /** Os quatro métodos de lista paginável do MCP. */
34
+ export const PAGINATED_LIST_METHODS = [
35
+ "tools/list",
36
+ "resources/list",
37
+ "resources/templates/list",
38
+ "prompts/list",
39
+ ];
40
+ /**
41
+ * Devolve a recusa `-32602` quando a mensagem é uma requisição de lista que
42
+ * carrega `cursor`; `undefined` em qualquer outro caso (inclusive notificação,
43
+ * lote e mensagem malformada — nenhum deles é assunto deste guarda, e o SDK
44
+ * responde por eles).
45
+ */
46
+ export function unknownCursorError(message) {
47
+ if (typeof message !== "object" || message === null || Array.isArray(message))
48
+ return undefined;
49
+ const { jsonrpc, id, method, params } = message;
50
+ if (jsonrpc !== "2.0")
51
+ return undefined;
52
+ // Sem id é notificação: não existe resposta para devolver.
53
+ if (typeof id !== "string" && typeof id !== "number")
54
+ return undefined;
55
+ if (typeof method !== "string")
56
+ return undefined;
57
+ if (!PAGINATED_LIST_METHODS.includes(method))
58
+ return undefined;
59
+ if (params === null || typeof params !== "object" || params.cursor === undefined)
60
+ return undefined;
61
+ return {
62
+ jsonrpc: "2.0",
63
+ id,
64
+ error: {
65
+ code: INVALID_PARAMS,
66
+ message: `Invalid params: unknown pagination cursor for ${method}. ` +
67
+ "This server returns every list in a single page and never issues a nextCursor, " +
68
+ "so no cursor value is valid — retry without one.",
69
+ },
70
+ };
71
+ }
package/dist/server.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * McpServer factory — creates a new instance per request (required by SDK 1.26.0+).
3
3
  * Registers all tools from each group module.
4
4
  */
5
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { McpServer } from "@modelcontextprotocol/server";
6
6
  import { z } from "zod";
7
7
  import { registerReferenciaTools } from "./tools/referencia.js";
8
8
  import { registerSenadoresTools } from "./tools/senadores.js";
@@ -31,12 +31,19 @@ import { VERSION } from "./version.js";
31
31
  import { instructionsForProfile, isToolEnabledForProfile, minimizeToolResultForProfile, toolMetaForProfile, } from "./app-surface.js";
32
32
  import { registerOpenAiAppWidget } from "./openai-app-widget.js";
33
33
  import { titleForTool } from "./tool-titles.js";
34
+ import { announceServedVersions } from "./discover.js";
34
35
  export function createServer(env, ctx, options = {}) {
35
36
  const toolProfile = options.toolProfile ?? "full";
36
37
  const server = new McpServer({
37
38
  name: "senado-br-mcp",
38
39
  version: VERSION,
39
- websiteUrl: "https://github.com/SidneyBissoli/senado-br-mcp-cloudflare",
40
+ // `title` no serverInfo do handshake: existia no `server.json` — o que os
41
+ // diretórios leem — mas não no que o cliente recebe ao conectar, e o
42
+ // mcpscore mede o handshake (`server_title_present`).
43
+ title: "Dados Abertos Senado BR MCP",
44
+ // O DOMÍNIO PRÓPRIO, não o repositório: é o que o `server.json` declara e
45
+ // é quem serve o ícone logo abaixo. O par estava desalinhado.
46
+ websiteUrl: "https://senado.sidneybissoli.com",
40
47
  icons: [
41
48
  {
42
49
  src: "https://senado.sidneybissoli.com/icon.jpg",
@@ -55,7 +62,12 @@ export function createServer(env, ctx, options = {}) {
55
62
  const outputSchema = z.object({}).passthrough();
56
63
  const registerTool = server.registerTool.bind(server);
57
64
  const analytics = env.SENADO_ANALYTICS;
58
- server.tool = (name, description, shape, cb) => {
65
+ // O shim é instalado sobre a instância e o resultado é NOMEADO: os módulos de
66
+ // grupo recebem `host`, não `server`. Até a migração v2 eles declaravam
67
+ // `McpServer` e chamavam `.tool()` — método que a v1 expunha e a v2 não —, uma
68
+ // imprecisão que o tipo antigo tolerava. Ver src/tool-host.ts.
69
+ const host = server;
70
+ host.tool = (name, description, shape, cb) => {
59
71
  if (!isToolEnabledForProfile(name, toolProfile)) {
60
72
  return undefined;
61
73
  }
@@ -78,45 +90,45 @@ export function createServer(env, ctx, options = {}) {
78
90
  const baseUrl = env.SENADO_BASE_URL || "https://legis.senado.leg.br/dadosabertos";
79
91
  const admBaseUrl = env.SENADO_ADM_BASE_URL || "https://adm.senado.gov.br/adm-dadosabertos";
80
92
  // Group H — Reference/metadata (1 tool — enum `tabela`)
81
- registerReferenciaTools(server, baseUrl);
93
+ registerReferenciaTools(host, baseUrl);
82
94
  // Group A — Senators (5 tools)
83
- registerSenadoresTools(server, baseUrl);
95
+ registerSenadoresTools(host, baseUrl);
84
96
  // Group B — Bills (2 tools; votos_materia is registered in Group D)
85
- registerMateriasTools(server, baseUrl);
97
+ registerMateriasTools(host, baseUrl);
86
98
  // Group D — Votes (3 tools)
87
- registerVotacoesTools(server, baseUrl);
99
+ registerVotacoesTools(host, baseUrl);
88
100
  // Group E — Committees (7 tools)
89
- registerComissoesTools(server, baseUrl);
101
+ registerComissoesTools(host, baseUrl);
90
102
  // Group F — Plenary (7 tools)
91
- registerPlenarioTools(server, baseUrl);
103
+ registerPlenarioTools(host, baseUrl);
92
104
  // Group C — Processes (5 tools)
93
- registerProcessosTools(server, baseUrl);
105
+ registerProcessosTools(host, baseUrl);
94
106
  // Group G — e-Cidadania (9 tools) — reads from D1 (env) and write-through detail (ctx)
95
- registerECidadaniaTools(server, baseUrl, env, ctx);
107
+ registerECidadaniaTools(host, baseUrl, env, ctx);
96
108
  // Group I — Speeches (3 tools)
97
- registerDiscursosTools(server, baseUrl);
109
+ registerDiscursosTools(host, baseUrl);
98
110
  // Group J — Blocs & Leadership (4 tools)
99
- registerComposicaoTools(server, baseUrl);
111
+ registerComposicaoTools(host, baseUrl);
100
112
  // Group K — Budget (1 tool — enum `tipo`)
101
- registerOrcamentoTools(server, baseUrl);
113
+ registerOrcamentoTools(host, baseUrl);
102
114
  // Group L — Federal Law (2 tools)
103
- registerLegislacaoTools(server, baseUrl);
115
+ registerLegislacaoTools(host, baseUrl);
104
116
  // Group M — Committee Voting (1 tool — enum `por`)
105
- registerVotacaoComissaoTools(server, baseUrl);
117
+ registerVotacaoComissaoTools(host, baseUrl);
106
118
  // Group N — Taquigrafia (2 tools)
107
- registerTaquigrafiaTools(server, baseUrl);
119
+ registerTaquigrafiaTools(host, baseUrl);
108
120
  // Group O — Senadores/Administrativo (2 tools)
109
- registerSenadoresAdminTools(server, admBaseUrl);
121
+ registerSenadoresAdminTools(host, admBaseUrl);
110
122
  // Group P — Servidores / Gestão de Pessoas (4 tools)
111
- registerServidoresTools(server, admBaseUrl);
123
+ registerServidoresTools(host, admBaseUrl);
112
124
  // Group Q — Contratações (6 tools)
113
- registerContratacoesTools(server, admBaseUrl);
125
+ registerContratacoesTools(host, admBaseUrl);
114
126
  // Group R — Suprimento de Fundos (1 tool)
115
- registerSupridosTools(server, admBaseUrl);
127
+ registerSupridosTools(host, admBaseUrl);
116
128
  // Group S — Orçamento do Senado (1 tool)
117
- registerOrcamentoSenadoTools(server);
129
+ registerOrcamentoSenadoTools(host);
118
130
  // Group T — Estrutura Organizacional (1 tool — árvore de órgãos, snapshot bundlado)
119
- registerEstruturaTools(server);
131
+ registerEstruturaTools(host);
120
132
  // MCP prompts (4 reusable pt-BR workflow templates) and resources (5 static
121
133
  // context docs/tables) — advertised as the `prompts` and `resources` capabilities.
122
134
  registerPrompts(server);
@@ -124,5 +136,8 @@ export function createServer(env, ctx, options = {}) {
124
136
  if (toolProfile === "openai-app") {
125
137
  registerOpenAiAppWidget(server);
126
138
  }
139
+ // `server/discover` anuncia TODAS as revisões atendidas, não só as modernas
140
+ // que o SDK filtra — ver src/discover.ts.
141
+ announceServedVersions(server);
127
142
  return server;
128
143
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * O tipo que os 20 módulos de tools realmente falam.
3
+ *
4
+ * POR QUE ESTE ARQUIVO EXISTE. Até a migração para o SDK v2 os módulos
5
+ * declaravam receber `McpServer` e chamavam `server.tool(...)` — um método que
6
+ * a v1 expunha e a v2 NÃO expõe. O que eles sempre chamaram, na prática, foi o
7
+ * SHIM instalado por `createServer` (src/server.ts), que aplica título,
8
+ * anotações read-only, `outputSchema` permissivo, instrumentação e o filtro de
9
+ * perfil antes de delegar ao `registerTool` do SDK.
10
+ *
11
+ * Declarar `McpServer` era, portanto, uma imprecisão que a v1 tolerava: o tipo
12
+ * dizia "SDK" e o objeto entregava "SDK mais o shim". A v2 deixou de tolerar, e
13
+ * a correção não é um cast — é nomear o que os módulos consomem de fato.
14
+ *
15
+ * Efeito colateral que vale por si: com a assinatura do callback declarada
16
+ * aqui, o `params` de cada tool deixa de ser `any` implícito. Eram 65
17
+ * ocorrências, todas silenciosas sob a v1.
18
+ */
19
+ export {};
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  /** Single source of truth for the server version (kept in sync with package.json). */
2
- export const VERSION = "3.5.1";
2
+ export const VERSION = "3.6.0";
3
3
  export const USER_AGENT = `senado-br-mcp/${VERSION} (+https://senado.sidneybissoli.com; sbissoli76@gmail.com)`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "senado-br-mcp",
3
- "version": "3.5.1",
3
+ "version": "3.6.0",
4
4
  "description": "MCP server for Brazilian Senate open data — runs hosted on Cloudflare Workers or locally via npx (stdio)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -47,21 +47,23 @@
47
47
  "release:zenodo": "tsx scripts/build-dataset/zenodo-publish.ts"
48
48
  },
49
49
  "dependencies": {
50
- "@modelcontextprotocol/sdk": "^1.26.0",
50
+ "@modelcontextprotocol/sdk": "^1.30.0",
51
+ "@modelcontextprotocol/server": "^2.0.0",
51
52
  "@sbissoli/mcp-provenance": "^0.1.0",
52
- "@sbissoli/mcp-stats": "^0.1.0",
53
- "zod": "^4.0.0"
53
+ "@sbissoli/mcp-stats": "^0.2.0",
54
+ "zod": "^4.5.4"
54
55
  },
55
56
  "devDependencies": {
56
- "@cloudflare/workers-types": "^4.20250214.0",
57
+ "@cloudflare/workers-types": "^5.20260830.1",
58
+ "@modelcontextprotocol/client": "^2.0.0",
57
59
  "@sbissoli/mcp-evals": "^0.1.0",
58
- "@types/node": "^25.9.3",
59
- "agents": "^0.5.0",
60
+ "@types/node": "^26.4.0",
61
+ "agents": "^0.22.0",
60
62
  "exceljs": "^4.4.0",
61
- "tsx": "^4.19.2",
62
- "typescript": "^5.7.0",
63
- "vitest": "^4.1.9",
64
- "wrangler": "^4.105.0"
63
+ "tsx": "^4.23.13",
64
+ "typescript": "^7.0.2",
65
+ "vitest": "^4.1.11",
66
+ "wrangler": "^4.127.1"
65
67
  },
66
68
  "overrides": {
67
69
  "fast-uri": "^4.0.0",