@pipeworx/mcp-openstates 0.1.3 → 0.1.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipeworx/mcp-openstates",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "OpenStates MCP — bills, legislators, votes in all 50 US states",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -26,7 +26,7 @@
26
26
  "@cloudflare/workers-types": "^4.20260405.1"
27
27
  },
28
28
  "pipeworx": {
29
- "sourceHash": "v1-cb3703013cf69fc6e2fc70bca57a6857b7ead99de0e71beaa6a32b1312e11733",
30
- "sourceCommit": "c7cc6672266b20d1b5feb0c4c2d631e50ad8ba17"
29
+ "sourceHash": "v1-d9939703c0e177f41da0fbd131835af380ebc15e9c75b8fd58b9dbebd35552be",
30
+ "sourceCommit": "69152ffc15233d5998bfe6fe195c040cdb328366"
31
31
  }
32
32
  }
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.pipeworx-io/openstates",
4
4
  "title": "Openstates",
5
5
  "description": "OpenStates MCP — bills, legislators, votes in all 50 US states",
6
- "version": "0.1.3",
6
+ "version": "0.1.4",
7
7
  "websiteUrl": "https://pipeworx.io/packs/openstates",
8
8
  "repository": {
9
9
  "url": "https://github.com/pipeworx-io/mcp-openstates",
package/src/index.ts CHANGED
@@ -997,8 +997,8 @@ export function resolveSessionCandidates(requested: string, sessions: OsSession[
997
997
  return hits
998
998
  .map((s, i) => ({ s, i }))
999
999
  .sort((a, b) => {
1000
- const pa = a.s.classification === 'primary' ? 0 : 1;
1001
- const pb = b.s.classification === 'primary' ? 0 : 1;
1000
+ const pa = isSpecialSession(a.s) ? 1 : 0;
1001
+ const pb = isSpecialSession(b.s) ? 1 : 0;
1002
1002
  if (pa !== pb) return pa - pb;
1003
1003
  const da = a.s.start_date ?? '';
1004
1004
  const db = b.s.start_date ?? '';
@@ -1009,6 +1009,14 @@ export function resolveSessionCandidates(requested: string, sessions: OsSession[
1009
1009
  .slice(0, 4);
1010
1010
  }
1011
1011
 
1012
+ // OpenStates does not always populate `classification` (Texas leaves it off and
1013
+ // names its specials "…Called Session"), so read the name too — otherwise the
1014
+ // newest special session outranks the regular one for the same year.
1015
+ function isSpecialSession(s: OsSession): boolean {
1016
+ if (s.classification) return s.classification !== 'primary';
1017
+ return /\b(special|called|extraordinary|extra)\b/i.test(s.name ?? '');
1018
+ }
1019
+
1012
1020
  const TERRITORIES = new Set(['pr', 'gu', 'vi', 'as', 'mp']);
1013
1021
 
1014
1022
  function jurisdictionPathId(jurisdiction: string): string {
@@ -1021,6 +1029,25 @@ function jurisdictionPathId(jurisdiction: string): string {
1021
1029
  return j.startsWith('ocd-jurisdiction/') ? j : encodeURIComponent(j);
1022
1030
  }
1023
1031
 
1032
+ // Session lists change a few times a year; the upstream free tier is small and
1033
+ // per-minute throttled, so keep them per isolate for an hour rather than
1034
+ // spending a request on every zero-row search.
1035
+ const SESSION_TTL_MS = 60 * 60 * 1000;
1036
+ const sessionCache = new Map<string, { at: number; data: { name?: string; legislative_sessions?: OsSession[] } }>();
1037
+
1038
+ async function jurisdictionSessions(apiKey: string, jurisdiction: string) {
1039
+ const key = jurisdictionPathId(jurisdiction);
1040
+ const hit = sessionCache.get(key);
1041
+ if (hit && Date.now() - hit.at < SESSION_TTL_MS) return hit.data;
1042
+ const data = await osFetch<{ name?: string; legislative_sessions?: OsSession[] }>(
1043
+ apiKey,
1044
+ `/jurisdictions/${key}`,
1045
+ new URLSearchParams({ include: 'legislative_sessions' }),
1046
+ );
1047
+ sessionCache.set(key, { at: Date.now(), data });
1048
+ return data;
1049
+ }
1050
+
1024
1051
  /**
1025
1052
  * Check a session value against the jurisdiction's real session list.
1026
1053
  * exact → it IS a valid identifier (an empty result is genuine).
@@ -1033,11 +1060,7 @@ async function resolveSession(
1033
1060
  jurisdiction: string,
1034
1061
  session: string,
1035
1062
  ): Promise<{ exact?: string; candidates: OsSession[] }> {
1036
- const data = await osFetch<{ name?: string; legislative_sessions?: OsSession[] }>(
1037
- apiKey,
1038
- `/jurisdictions/${jurisdictionPathId(jurisdiction)}`,
1039
- new URLSearchParams({ include: 'legislative_sessions' }),
1040
- );
1063
+ const data = await jurisdictionSessions(apiKey, jurisdiction);
1041
1064
  const sessions = (data.legislative_sessions ?? []).filter((s) => s && s.identifier);
1042
1065
  const norm = session.trim().toLowerCase();
1043
1066
  const exact = sessions.find(
package/src/server.ts CHANGED
@@ -9,7 +9,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
9
9
  import pack from './index.js';
10
10
 
11
11
  const server = new Server(
12
- { name: '@pipeworx/mcp-openstates', version: '0.1.3' },
12
+ { name: '@pipeworx/mcp-openstates', version: '0.1.4' },
13
13
  { capabilities: { tools: {} } },
14
14
  );
15
15