tn-elections-mcp 0.5.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/README.md +87 -0
- package/dist/api/easyvote-client.d.ts +51 -0
- package/dist/api/easyvote-client.js +254 -0
- package/dist/api/easyvote-client.js.map +1 -0
- package/dist/api/easyvote-client.test.d.ts +1 -0
- package/dist/api/easyvote-client.test.js +47 -0
- package/dist/api/easyvote-client.test.js.map +1 -0
- package/dist/api/gemini-pdf.d.ts +23 -0
- package/dist/api/gemini-pdf.js +261 -0
- package/dist/api/gemini-pdf.js.map +1 -0
- package/dist/api/sos-client.d.ts +15 -0
- package/dist/api/sos-client.js +74 -0
- package/dist/api/sos-client.js.map +1 -0
- package/dist/api/sumner-client.d.ts +12 -0
- package/dist/api/sumner-client.js +57 -0
- package/dist/api/sumner-client.js.map +1 -0
- package/dist/api/tncamp-client.d.ts +61 -0
- package/dist/api/tncamp-client.js +310 -0
- package/dist/api/tncamp-client.js.map +1 -0
- package/dist/data/sumner-cycles.d.ts +3 -0
- package/dist/data/sumner-cycles.js +19 -0
- package/dist/data/sumner-cycles.js.map +1 -0
- package/dist/data/sumner-cycles.test.d.ts +1 -0
- package/dist/data/sumner-cycles.test.js +29 -0
- package/dist/data/sumner-cycles.test.js.map +1 -0
- package/dist/data/tn-counties.d.ts +23 -0
- package/dist/data/tn-counties.js +162 -0
- package/dist/data/tn-counties.js.map +1 -0
- package/dist/data/tn-counties.test.d.ts +1 -0
- package/dist/data/tn-counties.test.js +71 -0
- package/dist/data/tn-counties.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/sos-xlsx.d.ts +24 -0
- package/dist/parsers/sos-xlsx.js +149 -0
- package/dist/parsers/sos-xlsx.js.map +1 -0
- package/dist/parsers/sos-xlsx.test.d.ts +1 -0
- package/dist/parsers/sos-xlsx.test.js +111 -0
- package/dist/parsers/sos-xlsx.test.js.map +1 -0
- package/dist/parsers/sumner-html.d.ts +8 -0
- package/dist/parsers/sumner-html.js +200 -0
- package/dist/parsers/sumner-html.js.map +1 -0
- package/dist/parsers/sumner-html.test.d.ts +1 -0
- package/dist/parsers/sumner-html.test.js +79 -0
- package/dist/parsers/sumner-html.test.js.map +1 -0
- package/dist/parsers/tncamp-form.d.ts +15 -0
- package/dist/parsers/tncamp-form.js +54 -0
- package/dist/parsers/tncamp-form.js.map +1 -0
- package/dist/parsers/tncamp-form.test.d.ts +1 -0
- package/dist/parsers/tncamp-form.test.js +114 -0
- package/dist/parsers/tncamp-form.test.js.map +1 -0
- package/dist/parsers/tncamp-report.d.ts +6 -0
- package/dist/parsers/tncamp-report.js +189 -0
- package/dist/parsers/tncamp-report.js.map +1 -0
- package/dist/parsers/tncamp-results.d.ts +10 -0
- package/dist/parsers/tncamp-results.js +113 -0
- package/dist/parsers/tncamp-results.js.map +1 -0
- package/dist/parsers/tncamp-results.test.d.ts +1 -0
- package/dist/parsers/tncamp-results.test.js +86 -0
- package/dist/parsers/tncamp-results.test.js.map +1 -0
- package/dist/tools/easyvote.d.ts +2 -0
- package/dist/tools/easyvote.js +382 -0
- package/dist/tools/easyvote.js.map +1 -0
- package/dist/tools/sos.d.ts +2 -0
- package/dist/tools/sos.js +130 -0
- package/dist/tools/sos.js.map +1 -0
- package/dist/tools/sumner.d.ts +2 -0
- package/dist/tools/sumner.js +134 -0
- package/dist/tools/sumner.js.map +1 -0
- package/dist/tools/tncamp.d.ts +2 -0
- package/dist/tools/tncamp.js +401 -0
- package/dist/tools/tncamp.js.map +1 -0
- package/dist/types.d.ts +270 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getFiledCandidates } from '../api/sos-client.js';
|
|
3
|
+
export function registerSosTools(server) {
|
|
4
|
+
// ============================================================
|
|
5
|
+
// search_filed_candidates
|
|
6
|
+
// ============================================================
|
|
7
|
+
server.tool('search_filed_candidates', 'Search candidates who have officially filed with the Tennessee Secretary of State. This is the OFFICIAL candidate filing list (who is on the ballot), distinct from tncamp campaign finance data (who has a campaign account). Covers state and federal races only: Governor, US Senate, US House, TN Senate, TN House, and party executive committees. County-level races are NOT covered — use search_county_filers for those. Only the current election cycle is available.', {
|
|
8
|
+
office: z.string().optional().describe('Filter by office name (case-insensitive partial match, e.g., "senate", "house district 5", "governor")'),
|
|
9
|
+
name: z.string().optional().describe('Filter by candidate name (case-insensitive partial match)'),
|
|
10
|
+
party: z.string().optional().describe('Filter by party (e.g., "Republican", "Democratic", "Independent")'),
|
|
11
|
+
city: z.string().optional().describe('Filter by city (case-insensitive partial match)'),
|
|
12
|
+
year: z.number().optional().describe('Election year (default 2026). Only current cycle available.'),
|
|
13
|
+
limit: z.number().optional().describe('Max results (default 50, 0 for all)'),
|
|
14
|
+
}, async ({ office, name, party, city, year, limit }) => {
|
|
15
|
+
try {
|
|
16
|
+
const index = await getFiledCandidates(year || 2026);
|
|
17
|
+
let results = index.candidates;
|
|
18
|
+
if (office) {
|
|
19
|
+
const q = office.toLowerCase();
|
|
20
|
+
results = results.filter(c => c.office.toLowerCase().includes(q) || c.officeCategory.toLowerCase().includes(q));
|
|
21
|
+
}
|
|
22
|
+
if (name) {
|
|
23
|
+
const q = name.toLowerCase();
|
|
24
|
+
results = results.filter(c => c.candidateName.toLowerCase().includes(q));
|
|
25
|
+
}
|
|
26
|
+
if (party) {
|
|
27
|
+
const q = party.toLowerCase();
|
|
28
|
+
results = results.filter(c => c.party.toLowerCase().includes(q));
|
|
29
|
+
}
|
|
30
|
+
if (city) {
|
|
31
|
+
const q = city.toLowerCase();
|
|
32
|
+
results = results.filter(c => c.city.toLowerCase().includes(q));
|
|
33
|
+
}
|
|
34
|
+
if (results.length === 0) {
|
|
35
|
+
return { content: [{ type: 'text', text: 'No filed candidates found matching your search.' }] };
|
|
36
|
+
}
|
|
37
|
+
const effectiveLimit = limit === undefined ? 50 : limit;
|
|
38
|
+
const totalCount = results.length;
|
|
39
|
+
const limited = effectiveLimit > 0 ? results.slice(0, effectiveLimit) : results;
|
|
40
|
+
const limitNote = effectiveLimit > 0 && totalCount > effectiveLimit
|
|
41
|
+
? `Showing ${limited.length} of ${totalCount} results. Use limit=0 for all.\n\n`
|
|
42
|
+
: '';
|
|
43
|
+
return {
|
|
44
|
+
content: [{
|
|
45
|
+
type: 'text',
|
|
46
|
+
text: `${limitNote}${totalCount} officially filed candidate(s):\n\n${JSON.stringify(limited, null, 2)}`,
|
|
47
|
+
}],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
53
|
+
isError: true,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
// ============================================================
|
|
58
|
+
// list_filed_offices
|
|
59
|
+
// ============================================================
|
|
60
|
+
server.tool('list_filed_offices', 'List office categories in the TN Secretary of State candidate filing list. Returns each office category with candidate count and links to the official PDF/Excel files. Only the current election cycle is available.', {
|
|
61
|
+
year: z.number().optional().describe('Election year (default 2026)'),
|
|
62
|
+
}, async ({ year }) => {
|
|
63
|
+
try {
|
|
64
|
+
const index = await getFiledCandidates(year || 2026);
|
|
65
|
+
return {
|
|
66
|
+
content: [{
|
|
67
|
+
type: 'text',
|
|
68
|
+
text: `${index.offices.length} office category(s) for ${index.year} (${index.candidates.length} total candidates):\n\n${JSON.stringify(index.offices, null, 2)}`,
|
|
69
|
+
}],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
75
|
+
isError: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
// ============================================================
|
|
80
|
+
// get_filing_summary
|
|
81
|
+
// ============================================================
|
|
82
|
+
server.tool('get_filing_summary', 'Get an aggregated summary of TN Secretary of State candidate filings by office and party. Returns total counts and breakdowns without listing individual candidates. Only the current election cycle is available.', {
|
|
83
|
+
year: z.number().optional().describe('Election year (default 2026)'),
|
|
84
|
+
office: z.string().optional().describe('Narrow to one office category (partial match)'),
|
|
85
|
+
}, async ({ year, office }) => {
|
|
86
|
+
try {
|
|
87
|
+
const index = await getFiledCandidates(year || 2026);
|
|
88
|
+
let candidates = index.candidates;
|
|
89
|
+
if (office) {
|
|
90
|
+
const q = office.toLowerCase();
|
|
91
|
+
candidates = candidates.filter(c => c.office.toLowerCase().includes(q) || c.officeCategory.toLowerCase().includes(q));
|
|
92
|
+
}
|
|
93
|
+
// Build by-office breakdown
|
|
94
|
+
const byOffice = {};
|
|
95
|
+
const byParty = {};
|
|
96
|
+
for (const c of candidates) {
|
|
97
|
+
const cat = c.officeCategory;
|
|
98
|
+
if (!byOffice[cat])
|
|
99
|
+
byOffice[cat] = {};
|
|
100
|
+
byOffice[cat][c.party] = (byOffice[cat][c.party] || 0) + 1;
|
|
101
|
+
byParty[c.party] = (byParty[c.party] || 0) + 1;
|
|
102
|
+
}
|
|
103
|
+
// Add totals per office
|
|
104
|
+
const byOfficeWithTotals = {};
|
|
105
|
+
for (const [cat, parties] of Object.entries(byOffice)) {
|
|
106
|
+
const total = Object.values(parties).reduce((sum, n) => sum + n, 0);
|
|
107
|
+
byOfficeWithTotals[cat] = { total, ...parties };
|
|
108
|
+
}
|
|
109
|
+
const summary = {
|
|
110
|
+
year: index.year,
|
|
111
|
+
totalCandidates: candidates.length,
|
|
112
|
+
byOffice: byOfficeWithTotals,
|
|
113
|
+
byParty,
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
content: [{
|
|
117
|
+
type: 'text',
|
|
118
|
+
text: `Filing summary for ${index.year}:\n\n${JSON.stringify(summary, null, 2)}`,
|
|
119
|
+
}],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
return {
|
|
124
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
125
|
+
isError: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=sos.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sos.js","sourceRoot":"","sources":["../../src/tools/sos.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { listCycles, getCycleCandidates, getReport } from '../api/sumner-client.js';
|
|
3
|
+
import { isImageFallback } from '../api/gemini-pdf.js';
|
|
4
|
+
export function registerSumnerTools(server) {
|
|
5
|
+
// ============================================================
|
|
6
|
+
// list_sumner_election_cycles
|
|
7
|
+
// ============================================================
|
|
8
|
+
server.tool('list_sumner_election_cycles', 'List available Sumner County TN campaign finance disclosure cycles (2016-2028). Sumner County publishes PDF campaign finance reports on votesumnertn.org. Use search_sumner_candidates to see candidates and their report PDFs for a specific cycle. Use get_sumner_report to extract financial data from a specific PDF via Gemini Vision AI.', {}, async () => {
|
|
9
|
+
const cycles = listCycles();
|
|
10
|
+
return {
|
|
11
|
+
content: [{
|
|
12
|
+
type: 'text',
|
|
13
|
+
text: `${cycles.length} Sumner County election cycles:\n\n${JSON.stringify(cycles, null, 2)}`,
|
|
14
|
+
}],
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
// ============================================================
|
|
18
|
+
// search_sumner_candidates
|
|
19
|
+
// ============================================================
|
|
20
|
+
server.tool('search_sumner_candidates', 'Search candidates in a Sumner County TN election cycle. Scrapes votesumnertn.org and returns candidates, offices, districts, and links to their PDF campaign finance reports. Results cached 15 minutes. This returns metadata and PDF links only — use get_sumner_report with a PDF URL to extract actual financial data (contributions, expenditures, balances).', {
|
|
21
|
+
cycle: z.string().describe('Election cycle year (e.g., "2026", "2024", "2016" matches "2016-2018")'),
|
|
22
|
+
name: z.string().optional().describe('Filter by candidate name (case-insensitive partial match)'),
|
|
23
|
+
office: z.string().optional().describe('Filter by office name (case-insensitive partial match, e.g., "mayor", "commissioner", "school")'),
|
|
24
|
+
}, async ({ cycle, name, office }) => {
|
|
25
|
+
try {
|
|
26
|
+
const page = await getCycleCandidates(cycle);
|
|
27
|
+
let candidates = page.candidates;
|
|
28
|
+
if (name) {
|
|
29
|
+
const lower = name.toLowerCase();
|
|
30
|
+
candidates = candidates.filter(c => c.name.toLowerCase().includes(lower));
|
|
31
|
+
}
|
|
32
|
+
if (office) {
|
|
33
|
+
const lower = office.toLowerCase();
|
|
34
|
+
candidates = candidates.filter(c => c.office.name.toLowerCase().includes(lower) ||
|
|
35
|
+
(c.office.district || '').toLowerCase().includes(lower) ||
|
|
36
|
+
(c.office.municipality || '').toLowerCase().includes(lower));
|
|
37
|
+
}
|
|
38
|
+
const output = {
|
|
39
|
+
cycle: page.cycle,
|
|
40
|
+
candidateCount: candidates.length,
|
|
41
|
+
candidates: candidates.map(c => ({
|
|
42
|
+
name: c.name,
|
|
43
|
+
office: c.office.name,
|
|
44
|
+
district: c.office.district || null,
|
|
45
|
+
municipality: c.office.municipality || null,
|
|
46
|
+
reportCount: c.reports.length,
|
|
47
|
+
reports: c.reports,
|
|
48
|
+
})),
|
|
49
|
+
nonDisclosureCount: page.nonDisclosures.length,
|
|
50
|
+
nonDisclosures: name
|
|
51
|
+
? page.nonDisclosures.filter(nd => nd.name.toLowerCase().includes(name.toLowerCase()))
|
|
52
|
+
: page.nonDisclosures,
|
|
53
|
+
scrapedAt: page.scrapedAt,
|
|
54
|
+
};
|
|
55
|
+
if (candidates.length === 0 && output.nonDisclosures.length === 0) {
|
|
56
|
+
const hint = name || office
|
|
57
|
+
? `No candidates matched your filters in the ${cycle} cycle. Try broader terms.`
|
|
58
|
+
: `No candidates found for the ${cycle} cycle. The page may have changed format.`;
|
|
59
|
+
return { content: [{ type: 'text', text: hint }] };
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
content: [{
|
|
63
|
+
type: 'text',
|
|
64
|
+
text: `Sumner County ${page.cycle.label} — ${candidates.length} candidate(s):\n\n${JSON.stringify(output, null, 2)}`,
|
|
65
|
+
}],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return {
|
|
70
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
71
|
+
isError: true,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
// ============================================================
|
|
76
|
+
// get_sumner_report
|
|
77
|
+
// ============================================================
|
|
78
|
+
server.tool('get_sumner_report', 'Extract financial data from a Sumner County campaign finance PDF using Gemini Flash AI. Downloads the PDF from votesumnertn.org, sends it to Gemini for structured extraction, and returns contributions, expenditures, beginning/ending balance (cash on hand), and totals. If Gemini is unavailable, falls back to returning PDF page images for Claude to read directly. IMPORTANT: Gemini path takes 10-30 seconds and uses API credits. Results cached 30 minutes. Get the PDF URL from search_sumner_candidates results. Set include_line_items=false for summary totals only.', {
|
|
79
|
+
pdf_url: z.string().describe('Full PDF URL from search_sumner_candidates report links'),
|
|
80
|
+
report_name: z.string().optional().describe('Report name for context (e.g., "YE 2025 Report")'),
|
|
81
|
+
include_line_items: z.boolean().optional().describe('Include itemized contributions/expenditures (default true). False for summary only.'),
|
|
82
|
+
}, async ({ pdf_url, report_name, include_line_items }) => {
|
|
83
|
+
try {
|
|
84
|
+
// Validate URL strictly
|
|
85
|
+
const url = new URL(pdf_url);
|
|
86
|
+
if ((url.hostname !== 'www.votesumnertn.org' && url.hostname !== 'votesumnertn.org') ||
|
|
87
|
+
url.protocol !== 'https:' || !url.pathname.endsWith('.pdf')) {
|
|
88
|
+
return {
|
|
89
|
+
content: [{ type: 'text', text: 'Error: URL must be an HTTPS PDF from votesumnertn.org' }],
|
|
90
|
+
isError: true,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const result = await getReport(pdf_url, report_name || 'Unknown Report');
|
|
94
|
+
// Image fallback: return page images for Claude to read directly
|
|
95
|
+
if (isImageFallback(result)) {
|
|
96
|
+
const content = [
|
|
97
|
+
{ type: 'text', text: result.message },
|
|
98
|
+
];
|
|
99
|
+
for (const img of result.images) {
|
|
100
|
+
content.push({
|
|
101
|
+
type: 'image',
|
|
102
|
+
data: img.base64,
|
|
103
|
+
mimeType: img.mimeType,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return { content };
|
|
107
|
+
}
|
|
108
|
+
// Structured Gemini result
|
|
109
|
+
const report = result;
|
|
110
|
+
const output = include_line_items === false
|
|
111
|
+
? {
|
|
112
|
+
...report,
|
|
113
|
+
contributions: `${report.contributions.length} item(s) (omitted — set include_line_items=true)`,
|
|
114
|
+
expenditures: `${report.expenditures.length} item(s) (omitted)`,
|
|
115
|
+
}
|
|
116
|
+
: report;
|
|
117
|
+
const confidence = report.extractionConfidence === 'high' ? ''
|
|
118
|
+
: `\nExtraction confidence: ${report.extractionConfidence}. Review warnings for details.\n`;
|
|
119
|
+
return {
|
|
120
|
+
content: [{
|
|
121
|
+
type: 'text',
|
|
122
|
+
text: `${report.candidateName} — ${report.reportPeriod}:${confidence}\n${JSON.stringify(output, null, 2)}`,
|
|
123
|
+
}],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
return {
|
|
128
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
129
|
+
isError: true,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=sumner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sumner.js","sourceRoot":"","sources":["../../src/tools/sumner.ts"],"names":[],"mappings":"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"}
|