sc-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 +187 -0
- package/dist/api/ethics-client.d.ts +45 -0
- package/dist/api/ethics-client.js +662 -0
- package/dist/api/ethics-client.js.map +1 -0
- package/dist/api/vrems-client.d.ts +18 -0
- package/dist/api/vrems-client.js +93 -0
- package/dist/api/vrems-client.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/candidate-detail.d.ts +12 -0
- package/dist/parsers/candidate-detail.js +112 -0
- package/dist/parsers/candidate-detail.js.map +1 -0
- package/dist/parsers/candidate-search.d.ts +6 -0
- package/dist/parsers/candidate-search.js +39 -0
- package/dist/parsers/candidate-search.js.map +1 -0
- package/dist/parsers/csv-export.d.ts +5 -0
- package/dist/parsers/csv-export.js +81 -0
- package/dist/parsers/csv-export.js.map +1 -0
- package/dist/tools/campaign.d.ts +2 -0
- package/dist/tools/campaign.js +191 -0
- package/dist/tools/campaign.js.map +1 -0
- package/dist/tools/cross-search.d.ts +26 -0
- package/dist/tools/cross-search.js +219 -0
- package/dist/tools/cross-search.js.map +1 -0
- package/dist/tools/overlap.d.ts +25 -0
- package/dist/tools/overlap.js +201 -0
- package/dist/tools/overlap.js.map +1 -0
- package/dist/tools/search.d.ts +2 -0
- package/dist/tools/search.js +146 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/sei.d.ts +2 -0
- package/dist/tools/sei.js +99 -0
- package/dist/tools/sei.js.map +1 -0
- package/dist/tools/vrems.d.ts +2 -0
- package/dist/tools/vrems.js +138 -0
- package/dist/tools/vrems.js.map +1 -0
- package/dist/types.d.ts +430 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getElectionYears, getElections, searchCandidates, getCandidateDetailHtml } from '../api/vrems-client.js';
|
|
3
|
+
import { parseCandidateDetail } from '../parsers/candidate-detail.js';
|
|
4
|
+
export function registerVremsTools(server) {
|
|
5
|
+
server.tool('list_elections', 'List SC elections by type and year. Returns election IDs needed for search_candidates. Types: General (statewide primaries + general), Special, Local. Use keyword to filter by location or type (e.g. "Sumter", "Primary"). Default limit 50 results (0 for all) — Local elections can have 200+ entries statewide. Start here for race research.', {
|
|
6
|
+
election_type: z.enum(['General', 'Special', 'Local']).describe('Election type'),
|
|
7
|
+
year: z.number().optional().describe('Specific year. Omit to get available years for this type.'),
|
|
8
|
+
keyword: z.string().optional().describe('Filter election names containing this keyword (case-insensitive, e.g. "Sumter", "Greenville", "Primary")'),
|
|
9
|
+
limit: z.number().optional().describe('Max results to return (default 50, 0 for all). Local elections can have 200+ entries per year.'),
|
|
10
|
+
}, async ({ election_type, year, keyword, limit }) => {
|
|
11
|
+
try {
|
|
12
|
+
if (year) {
|
|
13
|
+
let elections = await getElections(election_type, year);
|
|
14
|
+
// Apply keyword filter
|
|
15
|
+
if (keyword) {
|
|
16
|
+
const needle = keyword.toLowerCase();
|
|
17
|
+
elections = elections.filter((e) => (e.electionName || '').toLowerCase().includes(needle) ||
|
|
18
|
+
(e.displayName || '').toLowerCase().includes(needle));
|
|
19
|
+
}
|
|
20
|
+
// Apply limit
|
|
21
|
+
const effectiveLimit = limit === undefined ? 50 : limit;
|
|
22
|
+
const totalCount = elections.length;
|
|
23
|
+
const limited = effectiveLimit > 0 ? elections.slice(0, effectiveLimit) : elections;
|
|
24
|
+
const limitNote = effectiveLimit > 0 && totalCount > effectiveLimit
|
|
25
|
+
? `\nShowing ${effectiveLimit} of ${totalCount}. Use limit=0 for all.`
|
|
26
|
+
: '';
|
|
27
|
+
return {
|
|
28
|
+
content: [{
|
|
29
|
+
type: 'text',
|
|
30
|
+
text: limited.length === 0
|
|
31
|
+
? `No ${election_type} elections found for ${year}${keyword ? ` matching "${keyword}"` : ''}`
|
|
32
|
+
: `${totalCount} election(s)${keyword ? ` matching "${keyword}"` : ''}:${limitNote}\n${JSON.stringify(limited, null, 2)}`,
|
|
33
|
+
}],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const years = await getElectionYears(election_type);
|
|
38
|
+
return {
|
|
39
|
+
content: [{
|
|
40
|
+
type: 'text',
|
|
41
|
+
text: years.length === 0
|
|
42
|
+
? `No years found for ${election_type} elections`
|
|
43
|
+
: `Available years for ${election_type} elections:\n${JSON.stringify(years, null, 2)}`,
|
|
44
|
+
}],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return {
|
|
50
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
51
|
+
isError: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
server.tool('search_candidates', 'Search for candidates in a specific SC election. Returns contact info, filing fee, address, status. Default limit 50 results (0 for all) — statewide elections can return 500+. Get election_id from list_elections. To bridge to campaign finance, use search_filers with candidate names — the two systems don\'t share IDs. Try last name only if no match.', {
|
|
56
|
+
election_id: z.string().describe('Election ID from list_elections results'),
|
|
57
|
+
office: z.string().optional().describe('Office filter code (-1 for all). Common: 380=State House, 379=State Senate, 469=County Council District'),
|
|
58
|
+
county: z.string().optional().describe('County code (e.g. "23" for Greenville). Omit for all counties.'),
|
|
59
|
+
party: z.string().optional().describe('Party filter: Republican, Democratic, Libertarian, Nonpartisan, or All'),
|
|
60
|
+
status: z.string().optional().describe('Status filter: All, Active, Elected, DefeatedInPrimary, Withdrew, etc.'),
|
|
61
|
+
first_name: z.string().optional().describe('Candidate first name search'),
|
|
62
|
+
last_name: z.string().optional().describe('Candidate last name search'),
|
|
63
|
+
limit: z.number().optional().describe('Max candidates to return (default 50, 0 for all). Statewide elections can return hundreds.'),
|
|
64
|
+
}, async ({ election_id, office, county, party, status, first_name, last_name, limit }) => {
|
|
65
|
+
try {
|
|
66
|
+
const result = await searchCandidates({
|
|
67
|
+
electionId: election_id,
|
|
68
|
+
office,
|
|
69
|
+
county,
|
|
70
|
+
party,
|
|
71
|
+
status,
|
|
72
|
+
firstName: first_name,
|
|
73
|
+
lastName: last_name,
|
|
74
|
+
});
|
|
75
|
+
const effectiveLimit = limit === undefined ? 50 : limit;
|
|
76
|
+
if (result.candidates.length > 0) {
|
|
77
|
+
const totalCount = result.candidates.length;
|
|
78
|
+
const limited = effectiveLimit > 0 ? result.candidates.slice(0, effectiveLimit) : result.candidates;
|
|
79
|
+
const limitNote = effectiveLimit > 0 && totalCount > effectiveLimit
|
|
80
|
+
? `\nShowing ${effectiveLimit} of ${totalCount}. Use limit=0 for all.`
|
|
81
|
+
: '';
|
|
82
|
+
return {
|
|
83
|
+
content: [{
|
|
84
|
+
type: 'text',
|
|
85
|
+
text: `${totalCount} candidate(s) found (rich data from CSV export):${limitNote}\n${JSON.stringify(limited, null, 2)}`,
|
|
86
|
+
}],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (result.fallback && result.fallback.length > 0) {
|
|
90
|
+
const totalCount = result.fallback.length;
|
|
91
|
+
const limited = effectiveLimit > 0 ? result.fallback.slice(0, effectiveLimit) : result.fallback;
|
|
92
|
+
const limitNote = effectiveLimit > 0 && totalCount > effectiveLimit
|
|
93
|
+
? `\nShowing ${effectiveLimit} of ${totalCount}. Use limit=0 for all.`
|
|
94
|
+
: '';
|
|
95
|
+
return {
|
|
96
|
+
content: [{
|
|
97
|
+
type: 'text',
|
|
98
|
+
text: `${totalCount} candidate(s) found (basic data from HTML — CSV export unavailable):${limitNote}\n${JSON.stringify(limited, null, 2)}`,
|
|
99
|
+
}],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
content: [{ type: 'text', text: 'No candidates found matching filters' }],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
return {
|
|
108
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
109
|
+
isError: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
server.tool('get_candidate_details', 'Get filing details for a specific candidate: address, filing date, status, document links (filing form PDF, fee receipt). Use candidateId and electionId from search_candidates. candidateId is only available from HTML results, not CSV export.', {
|
|
114
|
+
candidate_id: z.string().describe('Candidate ID from search_candidates results'),
|
|
115
|
+
election_id: z.string().describe('Election ID from list_elections or search_candidates results'),
|
|
116
|
+
}, async ({ candidate_id, election_id }) => {
|
|
117
|
+
try {
|
|
118
|
+
const html = await getCandidateDetailHtml(candidate_id, election_id);
|
|
119
|
+
const detail = parseCandidateDetail(html);
|
|
120
|
+
if (!detail.name) {
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: 'text', text: 'Could not parse candidate detail page. The page structure may have changed.' }],
|
|
123
|
+
isError: true,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
content: [{ type: 'text', text: JSON.stringify(detail, null, 2) }],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
return {
|
|
132
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
133
|
+
isError: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=vrems.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vrems.js","sourceRoot":"","sources":["../../src/tools/vrems.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/** Search result from filer name search */
|
|
2
|
+
export interface EthicsFiler {
|
|
3
|
+
candidate: string;
|
|
4
|
+
address: string;
|
|
5
|
+
lastSubmission: string;
|
|
6
|
+
lastSeiReport: string;
|
|
7
|
+
lastCampaignDisclosureReport: string;
|
|
8
|
+
isAccountConsolidated: boolean;
|
|
9
|
+
universalUserId: number;
|
|
10
|
+
candidateFilerId: number;
|
|
11
|
+
seiFilerId: number;
|
|
12
|
+
officeId: number;
|
|
13
|
+
officeName: string;
|
|
14
|
+
accountType: number;
|
|
15
|
+
percentageAccuracy: number;
|
|
16
|
+
}
|
|
17
|
+
/** Result from office-based filer search (26-letter sweep) */
|
|
18
|
+
export interface OfficeFilerResult {
|
|
19
|
+
filers: EthicsFiler[];
|
|
20
|
+
totalSearched: number;
|
|
21
|
+
totalFailed: number;
|
|
22
|
+
}
|
|
23
|
+
/** Filer profile with positions and offices */
|
|
24
|
+
export interface FilerProfile {
|
|
25
|
+
name: string;
|
|
26
|
+
address: {
|
|
27
|
+
addressLine1: string;
|
|
28
|
+
addressLine2: string;
|
|
29
|
+
city: string;
|
|
30
|
+
zipCode: string;
|
|
31
|
+
county: string;
|
|
32
|
+
state: string;
|
|
33
|
+
};
|
|
34
|
+
phone: string;
|
|
35
|
+
accountType: number;
|
|
36
|
+
hasSei: boolean;
|
|
37
|
+
hasCandidate: boolean;
|
|
38
|
+
isCandidateSeiMismatch: boolean;
|
|
39
|
+
allPositions: FilerPosition[];
|
|
40
|
+
recentPositions: string[];
|
|
41
|
+
openOffices: FilerOffice[];
|
|
42
|
+
closedOffices: FilerOffice[];
|
|
43
|
+
}
|
|
44
|
+
export interface FilerPosition {
|
|
45
|
+
id: number;
|
|
46
|
+
reportId: number;
|
|
47
|
+
reportYear: string;
|
|
48
|
+
position: string;
|
|
49
|
+
entity: string;
|
|
50
|
+
start: string;
|
|
51
|
+
startYear: string;
|
|
52
|
+
end: string;
|
|
53
|
+
endYear: string;
|
|
54
|
+
categoryType: string;
|
|
55
|
+
positionType: string;
|
|
56
|
+
}
|
|
57
|
+
export interface FilerOffice {
|
|
58
|
+
filerId: number;
|
|
59
|
+
campaignId: number;
|
|
60
|
+
name: string;
|
|
61
|
+
isClosed: boolean;
|
|
62
|
+
start: string;
|
|
63
|
+
end: string;
|
|
64
|
+
}
|
|
65
|
+
/** Campaign report summary (open/closed offices with balances) */
|
|
66
|
+
export interface CampaignSummary {
|
|
67
|
+
name: string | null;
|
|
68
|
+
address: string | null;
|
|
69
|
+
openReports: CampaignOfficeReport[];
|
|
70
|
+
closedReports: CampaignOfficeReport[];
|
|
71
|
+
}
|
|
72
|
+
export interface CampaignOfficeReport {
|
|
73
|
+
filerId: number;
|
|
74
|
+
officeId: number;
|
|
75
|
+
officeName: string;
|
|
76
|
+
initialReportFiledDate: string;
|
|
77
|
+
lastReportSubmitted: string;
|
|
78
|
+
latestActivity: string;
|
|
79
|
+
balance: number;
|
|
80
|
+
officeClosedDate: string | null;
|
|
81
|
+
contributions: number;
|
|
82
|
+
}
|
|
83
|
+
/** Metadata header prepended to contribution/expenditure responses for verification */
|
|
84
|
+
export interface CampaignContext {
|
|
85
|
+
candidateName: string;
|
|
86
|
+
officeName: string;
|
|
87
|
+
campaignId: number;
|
|
88
|
+
candidateFilerId: number;
|
|
89
|
+
campaignStatus: 'open' | 'closed';
|
|
90
|
+
}
|
|
91
|
+
/** Aggregated contribution view — top donors, totals by type */
|
|
92
|
+
export interface ContributionSummary {
|
|
93
|
+
context: CampaignContext;
|
|
94
|
+
totalCount: number;
|
|
95
|
+
totalAmount: number;
|
|
96
|
+
dateRange: {
|
|
97
|
+
earliest: string;
|
|
98
|
+
latest: string;
|
|
99
|
+
} | null;
|
|
100
|
+
byType: Record<string, {
|
|
101
|
+
count: number;
|
|
102
|
+
amount: number;
|
|
103
|
+
}>;
|
|
104
|
+
selfFundingTotal: number;
|
|
105
|
+
topDonors: {
|
|
106
|
+
name: string;
|
|
107
|
+
totalAmount: number;
|
|
108
|
+
count: number;
|
|
109
|
+
}[];
|
|
110
|
+
}
|
|
111
|
+
/** Aggregated expenditure view — top vendors, totals by type */
|
|
112
|
+
export interface ExpenditureSummary {
|
|
113
|
+
context: CampaignContext;
|
|
114
|
+
totalCount: number;
|
|
115
|
+
totalAmount: number;
|
|
116
|
+
dateRange: {
|
|
117
|
+
earliest: string;
|
|
118
|
+
latest: string;
|
|
119
|
+
} | null;
|
|
120
|
+
byType: Record<string, {
|
|
121
|
+
count: number;
|
|
122
|
+
amount: number;
|
|
123
|
+
}>;
|
|
124
|
+
topVendors: {
|
|
125
|
+
name: string;
|
|
126
|
+
totalAmount: number;
|
|
127
|
+
count: number;
|
|
128
|
+
}[];
|
|
129
|
+
}
|
|
130
|
+
/** Office name with extracted district/body for normalization */
|
|
131
|
+
export interface NormalizedOffice {
|
|
132
|
+
raw: string;
|
|
133
|
+
normalized: string;
|
|
134
|
+
district?: string;
|
|
135
|
+
body?: string;
|
|
136
|
+
}
|
|
137
|
+
/** Grouped filer result — one entry per person, with all their office filings */
|
|
138
|
+
export interface GroupedFiler {
|
|
139
|
+
candidate: string;
|
|
140
|
+
address: string;
|
|
141
|
+
universalUserId: number;
|
|
142
|
+
candidateFilerId: number;
|
|
143
|
+
seiFilerId: number;
|
|
144
|
+
lastSubmission: string;
|
|
145
|
+
offices: {
|
|
146
|
+
officeName: string;
|
|
147
|
+
officeId: number;
|
|
148
|
+
lastSubmission: string;
|
|
149
|
+
campaignStatus?: 'open' | 'closed';
|
|
150
|
+
balance?: number;
|
|
151
|
+
campaignId?: number;
|
|
152
|
+
}[];
|
|
153
|
+
primaryOfficeName?: string;
|
|
154
|
+
campaignStatus?: 'open' | 'closed';
|
|
155
|
+
balance?: number;
|
|
156
|
+
campaignId?: number;
|
|
157
|
+
normalizedOffice?: NormalizedOffice;
|
|
158
|
+
}
|
|
159
|
+
/** Individual campaign report entry */
|
|
160
|
+
export interface CampaignReport {
|
|
161
|
+
reportId: number;
|
|
162
|
+
report: string;
|
|
163
|
+
reportType: string;
|
|
164
|
+
electionDate: string;
|
|
165
|
+
filingPeriod: string;
|
|
166
|
+
contributions: number;
|
|
167
|
+
expenses: number;
|
|
168
|
+
balance: number;
|
|
169
|
+
dateSubmitted: string;
|
|
170
|
+
lastAmendment: string | null;
|
|
171
|
+
year: number;
|
|
172
|
+
}
|
|
173
|
+
/** Detailed breakdown of a single campaign report */
|
|
174
|
+
export interface CampaignReportDetails {
|
|
175
|
+
reportType: string;
|
|
176
|
+
filingPeriod: string;
|
|
177
|
+
electionDate: string;
|
|
178
|
+
electionType: string;
|
|
179
|
+
filerName: string;
|
|
180
|
+
isAmendment: boolean;
|
|
181
|
+
overview: {
|
|
182
|
+
reportSequenceNumber: number;
|
|
183
|
+
submittedDate: string;
|
|
184
|
+
filingFeeAmount: number;
|
|
185
|
+
filingFeePaymentMethod: string;
|
|
186
|
+
income: ReportLineItem[];
|
|
187
|
+
expenditures: ReportLineItem[];
|
|
188
|
+
totals: ReportLineItem[];
|
|
189
|
+
};
|
|
190
|
+
contributions: unknown[];
|
|
191
|
+
expenditures: unknown[];
|
|
192
|
+
loans: unknown[];
|
|
193
|
+
}
|
|
194
|
+
export interface ReportLineItem {
|
|
195
|
+
type: string;
|
|
196
|
+
filingPeriod: number;
|
|
197
|
+
electionCycleTotal: number;
|
|
198
|
+
}
|
|
199
|
+
/** Individual contribution record */
|
|
200
|
+
export interface CampaignContribution {
|
|
201
|
+
id: number;
|
|
202
|
+
date: string;
|
|
203
|
+
report: string;
|
|
204
|
+
paidBy: string;
|
|
205
|
+
credit: number;
|
|
206
|
+
type: string;
|
|
207
|
+
electionCycle: string;
|
|
208
|
+
description: string;
|
|
209
|
+
isRunoff: boolean;
|
|
210
|
+
isDebtSetOff: boolean;
|
|
211
|
+
filingDate: string;
|
|
212
|
+
}
|
|
213
|
+
/** Individual expenditure record */
|
|
214
|
+
export interface CampaignExpenditure {
|
|
215
|
+
id: number;
|
|
216
|
+
date: string;
|
|
217
|
+
report: string;
|
|
218
|
+
paidTo: string;
|
|
219
|
+
debit: number;
|
|
220
|
+
type: string;
|
|
221
|
+
electionCycle: string;
|
|
222
|
+
description: string;
|
|
223
|
+
isRunoff: boolean;
|
|
224
|
+
isDebtSetOff: boolean;
|
|
225
|
+
filingDate: string;
|
|
226
|
+
}
|
|
227
|
+
/** Cross-candidate expenditure search result */
|
|
228
|
+
export interface CrossSearchExpenditure {
|
|
229
|
+
candidateFilerId: number;
|
|
230
|
+
credentialId: number;
|
|
231
|
+
campaignId: number;
|
|
232
|
+
office: string;
|
|
233
|
+
candidateName: string;
|
|
234
|
+
expDate: string;
|
|
235
|
+
expId: number;
|
|
236
|
+
vendorName: string;
|
|
237
|
+
amount: number;
|
|
238
|
+
address: string;
|
|
239
|
+
expDesc: string;
|
|
240
|
+
}
|
|
241
|
+
/** Cross-candidate contribution search result */
|
|
242
|
+
export interface CrossSearchContribution {
|
|
243
|
+
contributionId: number;
|
|
244
|
+
officeRunId: number;
|
|
245
|
+
candidateId: number;
|
|
246
|
+
date: string;
|
|
247
|
+
amount: number;
|
|
248
|
+
candidateName: string;
|
|
249
|
+
officeName: string;
|
|
250
|
+
electionDate: string;
|
|
251
|
+
contributorName: string;
|
|
252
|
+
contributorOccupation: string;
|
|
253
|
+
group: string;
|
|
254
|
+
contributorAddress: string;
|
|
255
|
+
description: string | null;
|
|
256
|
+
}
|
|
257
|
+
export interface SeiReport {
|
|
258
|
+
seiFilerId: number;
|
|
259
|
+
seiReportId: number;
|
|
260
|
+
year: number;
|
|
261
|
+
reportType: string;
|
|
262
|
+
dateSubmitted: string;
|
|
263
|
+
status: string;
|
|
264
|
+
}
|
|
265
|
+
export interface SeiReportBody {
|
|
266
|
+
seiFilerId: number;
|
|
267
|
+
seiReportId: number;
|
|
268
|
+
getUnfiled: boolean;
|
|
269
|
+
}
|
|
270
|
+
export interface SeiPosition {
|
|
271
|
+
id: number;
|
|
272
|
+
reportId: number;
|
|
273
|
+
reportYear: string;
|
|
274
|
+
position: string;
|
|
275
|
+
entity: string;
|
|
276
|
+
start: string;
|
|
277
|
+
startYear: string;
|
|
278
|
+
end: string;
|
|
279
|
+
endYear: string;
|
|
280
|
+
categoryType: string;
|
|
281
|
+
positionType: string;
|
|
282
|
+
}
|
|
283
|
+
export interface SeiBusinessInterest {
|
|
284
|
+
seiFilerId: number;
|
|
285
|
+
seiReportId: number;
|
|
286
|
+
businessInterestsId: number;
|
|
287
|
+
businessName: string;
|
|
288
|
+
relationship: string;
|
|
289
|
+
isDeleted: boolean;
|
|
290
|
+
}
|
|
291
|
+
export interface SeiIncomeSource {
|
|
292
|
+
seiFilerId: number;
|
|
293
|
+
seiReportId: number;
|
|
294
|
+
incomeAndBenefitsId: number;
|
|
295
|
+
source: string;
|
|
296
|
+
type: string;
|
|
297
|
+
amount: number;
|
|
298
|
+
isDeleted: boolean;
|
|
299
|
+
incomeType: string;
|
|
300
|
+
}
|
|
301
|
+
export interface SeiGift {
|
|
302
|
+
seiFilerId: number;
|
|
303
|
+
seiReportId: number;
|
|
304
|
+
source: string;
|
|
305
|
+
description: string;
|
|
306
|
+
value: number;
|
|
307
|
+
isDeleted: boolean;
|
|
308
|
+
}
|
|
309
|
+
export interface SeiTravel {
|
|
310
|
+
seiFilerId: number;
|
|
311
|
+
seiReportId: number;
|
|
312
|
+
destination: string;
|
|
313
|
+
purpose: string;
|
|
314
|
+
paidBy: string;
|
|
315
|
+
dates: string;
|
|
316
|
+
isDeleted: boolean;
|
|
317
|
+
}
|
|
318
|
+
export interface SeiCreditor {
|
|
319
|
+
seiFilerId: number;
|
|
320
|
+
seiReportId: number;
|
|
321
|
+
creditorName: string;
|
|
322
|
+
amount: number;
|
|
323
|
+
isDeleted: boolean;
|
|
324
|
+
}
|
|
325
|
+
export interface SeiLobbyist {
|
|
326
|
+
seiFilerId: number;
|
|
327
|
+
seiReportId: number;
|
|
328
|
+
lobbyistName: string;
|
|
329
|
+
isDeleted: boolean;
|
|
330
|
+
}
|
|
331
|
+
export interface SeiDetails {
|
|
332
|
+
seiFilerId: number;
|
|
333
|
+
seiReportId: number;
|
|
334
|
+
reportYear: number;
|
|
335
|
+
dateSubmitted: string;
|
|
336
|
+
positions: SeiPosition[];
|
|
337
|
+
businessInterests: SeiBusinessInterest[];
|
|
338
|
+
privateIncome: SeiIncomeSource[];
|
|
339
|
+
governmentIncome: SeiIncomeSource[];
|
|
340
|
+
familyPrivateIncome: SeiIncomeSource[];
|
|
341
|
+
familyGovernmentIncome: SeiIncomeSource[];
|
|
342
|
+
gifts: SeiGift[];
|
|
343
|
+
travel: SeiTravel[];
|
|
344
|
+
governmentContracts: unknown[];
|
|
345
|
+
creditors: SeiCreditor[];
|
|
346
|
+
lobbyistFamily: SeiLobbyist[];
|
|
347
|
+
lobbyistPurchases: SeiLobbyist[];
|
|
348
|
+
regulatedBusinessAssociations: unknown[];
|
|
349
|
+
propertyTransactions: unknown[];
|
|
350
|
+
propertyImprovements: unknown[];
|
|
351
|
+
propertyConflicts: unknown[];
|
|
352
|
+
additionalInformation: unknown[];
|
|
353
|
+
}
|
|
354
|
+
export interface VremsElectionYear {
|
|
355
|
+
electionYear: number;
|
|
356
|
+
}
|
|
357
|
+
export interface VremsElection {
|
|
358
|
+
electionId: string;
|
|
359
|
+
electionName: string;
|
|
360
|
+
displayName: string;
|
|
361
|
+
electionDate: string;
|
|
362
|
+
filingPeriodBeginDate: string;
|
|
363
|
+
}
|
|
364
|
+
/** Rich candidate record from CSV export (25 fields) */
|
|
365
|
+
export interface VremsCandidate {
|
|
366
|
+
ballotSortOrder: string;
|
|
367
|
+
filingLevel: string;
|
|
368
|
+
electionName: string;
|
|
369
|
+
office: string;
|
|
370
|
+
district: string;
|
|
371
|
+
counties: string;
|
|
372
|
+
ballotFirstMiddle: string;
|
|
373
|
+
ballotLastSuffix: string;
|
|
374
|
+
runningMate: string;
|
|
375
|
+
firstName: string;
|
|
376
|
+
middleName: string;
|
|
377
|
+
lastName: string;
|
|
378
|
+
suffix: string;
|
|
379
|
+
party: string;
|
|
380
|
+
filingLocation: string;
|
|
381
|
+
dateFiled: string;
|
|
382
|
+
timeFiled: string;
|
|
383
|
+
filingFee: string;
|
|
384
|
+
status: string;
|
|
385
|
+
statusDate: string;
|
|
386
|
+
address: string;
|
|
387
|
+
phone: string;
|
|
388
|
+
email: string;
|
|
389
|
+
runningMateOffice: string;
|
|
390
|
+
}
|
|
391
|
+
/** Lighter candidate record from HTML table parsing (fallback) */
|
|
392
|
+
export interface VremsSearchCandidate {
|
|
393
|
+
candidateId: string;
|
|
394
|
+
electionId: string;
|
|
395
|
+
office: string;
|
|
396
|
+
counties: string;
|
|
397
|
+
name: string;
|
|
398
|
+
runningMate: string;
|
|
399
|
+
party: string;
|
|
400
|
+
filingLocation: string;
|
|
401
|
+
status: string;
|
|
402
|
+
}
|
|
403
|
+
/** Candidate detail from HTML detail page */
|
|
404
|
+
export interface VremsCandidateDetail {
|
|
405
|
+
name: string;
|
|
406
|
+
election: string;
|
|
407
|
+
office: string;
|
|
408
|
+
nameOnBallot: string;
|
|
409
|
+
party: string;
|
|
410
|
+
address: string;
|
|
411
|
+
status: string;
|
|
412
|
+
dateFiled: string;
|
|
413
|
+
locationFiled: string;
|
|
414
|
+
documents: VremsDocument[];
|
|
415
|
+
}
|
|
416
|
+
export interface VremsDocument {
|
|
417
|
+
name: string;
|
|
418
|
+
type: string;
|
|
419
|
+
url: string;
|
|
420
|
+
}
|
|
421
|
+
export interface CandidateSearchParams {
|
|
422
|
+
electionId: string;
|
|
423
|
+
office?: string;
|
|
424
|
+
county?: string;
|
|
425
|
+
status?: string;
|
|
426
|
+
firstName?: string;
|
|
427
|
+
lastName?: string;
|
|
428
|
+
party?: string;
|
|
429
|
+
filingLocation?: string;
|
|
430
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,gDAAgD;AAChD,+DAA+D"}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sc-elections-mcp",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "MCP server for South Carolina elections: campaign finance, contributions, expenditures, SEI disclosures, and candidate filings",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sc-elections-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
15
|
+
"start": "node dist/index.js",
|
|
16
|
+
"dev": "tsx src/index.ts",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"prepare": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"south-carolina",
|
|
23
|
+
"elections",
|
|
24
|
+
"campaign-finance",
|
|
25
|
+
"ethics"
|
|
26
|
+
],
|
|
27
|
+
"author": "Alex Reynolds",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/asreynolds1000/sc-elections-mcp.git"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
35
|
+
"node-html-parser": "^7.0.2"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^20",
|
|
39
|
+
"tsx": "^4.19.4",
|
|
40
|
+
"typescript": "^5",
|
|
41
|
+
"vitest": "^3.2.4"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=20"
|
|
45
|
+
}
|
|
46
|
+
}
|