← all branches

feat/batu-mcp

needs attentionviewing older commit
ee41aea · fullpre-PRreviewed 2026-07-09 19:12 UTC5H · 6M · 11L · 5I
The branch
Purpose
Ship an official Batu Energy MCP server as an npm-publishable package so AI agents can work with Mexican CFE energy data without custom integration code.
Goal
Publish @batu/mcp-server with 12 tools covering the full public API surface (bills, files, jobs, monitoring, webhooks) and register it as the repo's dev-mode batu MCP server.
Sub-goals
  • Implement BatuClient with JWT lifecycle (exchange bk_... key → Bearer, cache, refresh 60s before expiry, single 401 retry)
  • Register 12 MCP tools with accurate Zod schemas and agent-oriented descriptions
  • Wire into .mcp.json as dev-mode entry (tsx source path)
  • Add unit tests for auth lifecycle at the trust boundary
  • Fix date-filter param names + add client tests + publish config
The changes (whole branch)
What
New package packages/mcp-server with 7 files (+756 lines, all additions). Added batu entry to .mcp.json. No existing code modified.
Why
AI agents (Claude Code, Claude Desktop, third-party integrations) need a supported, typed, npm-installable way to query CFE bills and energy data — the public API is the canonical source and this MCP server exposes it as tool calls.
Areas
packages/mcp-server+7450.mcp.json+110
Blast
New package only — 7 files, +756 lines (all adds). No changes to existing domain code, APIs, DB schema, or infra.
public-api npm-publish
CI· Pre-PR branch — no CI run yetCodeRabbit· No .coderabbit.yaml in repotypecheck· Not verified in this run

Findings · 26

correctness7

high

mintToken race condition: concurrent requests both trigger a token mint

packages/mcp-server/src/client.ts:76

getToken() checks tokenState then calls mintToken() without mutual exclusion. Fix: cache the in-flight mint Promise.

high

batu_download_files_zip: no client-side filter guard — empty body {} POSTs org-wide bundle

packages/mcp-server/src/index.ts:182

All 3 filter fields are optional with no .refine(). Add .refine(a => a.rpu || a.bill_id || a.type, { message: 'At least one filter required' }).

medium

expiresAtMs negative if expires_in ≤ 60 → infinite re-mint loop

packages/mcp-server/src/client.ts:71

Guard: Math.max(0, expiresIn - TOKEN_REFRESH_MARGIN_S)

medium

401 retry path: same race as cold-start mint (both resolved by mint-Promise cache)

packages/mcp-server/src/client.ts:111

Covered by the same fix as the cold-start race.

low

clientSingleton lazy init hides missing BATU_API_KEY at startup

packages/mcp-server/src/index.ts:37

low

.mcp.json uses tsx source path — breaks outside the monorepo

.mcp.json:38

low

latest_only boolean serialized as string 'true'/'false' — confirm API contract

packages/mcp-server/src/index.ts:160

security5

medium

batu_create_webhook: HTTP URLs accepted — HTTPS not enforced in Zod schema

packages/mcp-server/src/index.ts:307

Add .refine(u => u.startsWith('https://'), 'Webhook URL must use HTTPS').

low

BATU_API_URL not validated — could redirect client to attacker-controlled server

packages/mcp-server/src/client.ts:48

low

period_count: -1 allows unlimited credit burn in autonomous agent loops

packages/mcp-server/src/index.ts:215

low

Webhook signing secret in tool response — no redaction guidance for MCP hosts with logging

packages/mcp-server/src/index.ts:289

info

API key not logged or included in error messages — confirmed safe

packages/mcp-server/src/client.ts:18

conventions5

high

Missing lint script — package silently excluded from Turbo CI lint pipeline

packages/mcp-server/package.json:34

Add: "lint": "eslint . --max-warnings 0" and eslint.config.js extending @batu/eslint-config/base.

high

Missing vitest.config.ts — test environment not declared

packages/mcp-server/package.json:39

Add vitest.config.ts: { test: { globals: true, environment: 'node' } }

medium

UNLICENSED on a publicly-published npm package blocks third-party use

packages/mcp-server/package.json:7

Choose MIT/Apache-2.0 for open use, or private:true for closed-source.

medium

Section banner comments in index.ts violate no-what-comments convention

packages/mcp-server/src/index.ts:74

Remove // === Bills === etc. banners.

low

moduleResolution: Bundler with plain tsc may mis-resolve .js extensions at runtime

packages/mcp-server/tsconfig.json:2

tests6

high

mintToken: empty access_token string caches blank Bearer token instead of throwing

packages/mcp-server/src/client.ts:61

!body?.data?.access_token check passes for empty string. Add test for 200 with access_token:''.

medium

batu_list_files: latest_only / bill_id mutual exclusion not enforced or tested

packages/mcp-server/src/index.ts:143

Add Zod .refine(a => !(a.latest_only && a.bill_id)) and matching test.

low

Concurrent mintToken calls not tested — race undetectable by CI even after a fix

packages/mcp-server/src/client.ts:76

low

run() errorResult branches not directly tested in tool layer

packages/mcp-server/src/index.ts:51

info

MCP server is an I/O adapter — BatuClient as trust boundary is the correct test target

packages/mcp-server/src/client.test.ts

info

Existing BatuClient tests are thorough and well-structured

packages/mcp-server/src/client.test.ts

improvement3

medium

batu_list_files: limit/cursor lack .describe() unlike other paginated tools

packages/mcp-server/src/index.ts:154

Add .describe() to limit (max differs: 100 here vs 5000 for bills) and cursor.

low

README missing guidance on enumerating all org RPUs

packages/mcp-server/README.md:39

info

mintToken/getToken two-method split is correct — no simplification needed

packages/mcp-server/src/client.ts:51

History · 5 commits

  1. 59a73bdneeds attentionincremental1H · 6M · 5L2026-07-10 05:23
  2. 5ca7218needs attentionincremental2H · 5M · 5L2026-07-10 04:56
  3. 0e187bdneeds attentionincremental0H · 1M · 2L2026-07-10 04:39
  4. 844f42fneeds attentionfull0H · 3M · 5L2026-07-09 20:13
  5. ee41aeaneeds attentionfull5H · 6M · 11L2026-07-09 19:12current