@cyanheads/openfec-mcp-server 0.2.2

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.
Files changed (98) hide show
  1. package/CLAUDE.md +328 -0
  2. package/Dockerfile +99 -0
  3. package/LICENSE +191 -0
  4. package/README.md +297 -0
  5. package/dist/config/server-config.d.ts +17 -0
  6. package/dist/config/server-config.d.ts.map +1 -0
  7. package/dist/config/server-config.js +31 -0
  8. package/dist/config/server-config.js.map +1 -0
  9. package/dist/index.d.ts +8 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +20 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/mcp-server/prompts/definitions/campaign-analysis.prompt.d.ts +13 -0
  14. package/dist/mcp-server/prompts/definitions/campaign-analysis.prompt.d.ts.map +1 -0
  15. package/dist/mcp-server/prompts/definitions/campaign-analysis.prompt.js +79 -0
  16. package/dist/mcp-server/prompts/definitions/campaign-analysis.prompt.js.map +1 -0
  17. package/dist/mcp-server/prompts/definitions/index.d.ts +12 -0
  18. package/dist/mcp-server/prompts/definitions/index.d.ts.map +1 -0
  19. package/dist/mcp-server/prompts/definitions/index.js +10 -0
  20. package/dist/mcp-server/prompts/definitions/index.js.map +1 -0
  21. package/dist/mcp-server/prompts/definitions/money-trail.prompt.d.ts +13 -0
  22. package/dist/mcp-server/prompts/definitions/money-trail.prompt.d.ts.map +1 -0
  23. package/dist/mcp-server/prompts/definitions/money-trail.prompt.js +78 -0
  24. package/dist/mcp-server/prompts/definitions/money-trail.prompt.js.map +1 -0
  25. package/dist/mcp-server/resources/definitions/candidate.resource.d.ts +10 -0
  26. package/dist/mcp-server/resources/definitions/candidate.resource.d.ts.map +1 -0
  27. package/dist/mcp-server/resources/definitions/candidate.resource.js +28 -0
  28. package/dist/mcp-server/resources/definitions/candidate.resource.js.map +1 -0
  29. package/dist/mcp-server/resources/definitions/committee.resource.d.ts +10 -0
  30. package/dist/mcp-server/resources/definitions/committee.resource.d.ts.map +1 -0
  31. package/dist/mcp-server/resources/definitions/committee.resource.js +26 -0
  32. package/dist/mcp-server/resources/definitions/committee.resource.js.map +1 -0
  33. package/dist/mcp-server/resources/definitions/election.resource.d.ts +25 -0
  34. package/dist/mcp-server/resources/definitions/election.resource.d.ts.map +1 -0
  35. package/dist/mcp-server/resources/definitions/election.resource.js +73 -0
  36. package/dist/mcp-server/resources/definitions/election.resource.js.map +1 -0
  37. package/dist/mcp-server/resources/definitions/index.d.ts +16 -0
  38. package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -0
  39. package/dist/mcp-server/resources/definitions/index.js +18 -0
  40. package/dist/mcp-server/resources/definitions/index.js.map +1 -0
  41. package/dist/mcp-server/tools/definitions/index.d.ts +285 -0
  42. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -0
  43. package/dist/mcp-server/tools/definitions/index.js +34 -0
  44. package/dist/mcp-server/tools/definitions/index.js.map +1 -0
  45. package/dist/mcp-server/tools/definitions/lookup-calendar.tool.d.ts +37 -0
  46. package/dist/mcp-server/tools/definitions/lookup-calendar.tool.d.ts.map +1 -0
  47. package/dist/mcp-server/tools/definitions/lookup-calendar.tool.js +172 -0
  48. package/dist/mcp-server/tools/definitions/lookup-calendar.tool.js.map +1 -0
  49. package/dist/mcp-server/tools/definitions/lookup-elections.tool.d.ts +31 -0
  50. package/dist/mcp-server/tools/definitions/lookup-elections.tool.d.ts.map +1 -0
  51. package/dist/mcp-server/tools/definitions/lookup-elections.tool.js +134 -0
  52. package/dist/mcp-server/tools/definitions/lookup-elections.tool.js.map +1 -0
  53. package/dist/mcp-server/tools/definitions/search-candidates.tool.d.ts +46 -0
  54. package/dist/mcp-server/tools/definitions/search-candidates.tool.d.ts.map +1 -0
  55. package/dist/mcp-server/tools/definitions/search-candidates.tool.js +170 -0
  56. package/dist/mcp-server/tools/definitions/search-candidates.tool.js.map +1 -0
  57. package/dist/mcp-server/tools/definitions/search-committees.tool.d.ts +29 -0
  58. package/dist/mcp-server/tools/definitions/search-committees.tool.d.ts.map +1 -0
  59. package/dist/mcp-server/tools/definitions/search-committees.tool.js +120 -0
  60. package/dist/mcp-server/tools/definitions/search-committees.tool.js.map +1 -0
  61. package/dist/mcp-server/tools/definitions/search-contributions.tool.d.ts +47 -0
  62. package/dist/mcp-server/tools/definitions/search-contributions.tool.d.ts.map +1 -0
  63. package/dist/mcp-server/tools/definitions/search-contributions.tool.js +252 -0
  64. package/dist/mcp-server/tools/definitions/search-contributions.tool.js.map +1 -0
  65. package/dist/mcp-server/tools/definitions/search-disbursements.tool.d.ts +43 -0
  66. package/dist/mcp-server/tools/definitions/search-disbursements.tool.d.ts.map +1 -0
  67. package/dist/mcp-server/tools/definitions/search-disbursements.tool.js +212 -0
  68. package/dist/mcp-server/tools/definitions/search-disbursements.tool.js.map +1 -0
  69. package/dist/mcp-server/tools/definitions/search-expenditures.tool.d.ts +52 -0
  70. package/dist/mcp-server/tools/definitions/search-expenditures.tool.d.ts.map +1 -0
  71. package/dist/mcp-server/tools/definitions/search-expenditures.tool.js +247 -0
  72. package/dist/mcp-server/tools/definitions/search-expenditures.tool.js.map +1 -0
  73. package/dist/mcp-server/tools/definitions/search-filings.tool.d.ts +31 -0
  74. package/dist/mcp-server/tools/definitions/search-filings.tool.d.ts.map +1 -0
  75. package/dist/mcp-server/tools/definitions/search-filings.tool.js +123 -0
  76. package/dist/mcp-server/tools/definitions/search-filings.tool.js.map +1 -0
  77. package/dist/mcp-server/tools/definitions/search-legal.tool.d.ts +32 -0
  78. package/dist/mcp-server/tools/definitions/search-legal.tool.d.ts.map +1 -0
  79. package/dist/mcp-server/tools/definitions/search-legal.tool.js +179 -0
  80. package/dist/mcp-server/tools/definitions/search-legal.tool.js.map +1 -0
  81. package/dist/mcp-server/tools/definitions/utils/format-helpers.d.ts +19 -0
  82. package/dist/mcp-server/tools/definitions/utils/format-helpers.d.ts.map +1 -0
  83. package/dist/mcp-server/tools/definitions/utils/format-helpers.js +19 -0
  84. package/dist/mcp-server/tools/definitions/utils/format-helpers.js.map +1 -0
  85. package/dist/mcp-server/tools/definitions/utils/id-validators.d.ts +11 -0
  86. package/dist/mcp-server/tools/definitions/utils/id-validators.d.ts.map +1 -0
  87. package/dist/mcp-server/tools/definitions/utils/id-validators.js +22 -0
  88. package/dist/mcp-server/tools/definitions/utils/id-validators.js.map +1 -0
  89. package/dist/services/openfec/openfec-service.d.ts +54 -0
  90. package/dist/services/openfec/openfec-service.d.ts.map +1 -0
  91. package/dist/services/openfec/openfec-service.js +384 -0
  92. package/dist/services/openfec/openfec-service.js.map +1 -0
  93. package/dist/services/openfec/types.d.ts +86 -0
  94. package/dist/services/openfec/types.d.ts.map +1 -0
  95. package/dist/services/openfec/types.js +8 -0
  96. package/dist/services/openfec/types.js.map +1 -0
  97. package/package.json +91 -0
  98. package/server.json +97 -0
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @fileoverview Tool for searching FEC filings and reports. Covers all
3
+ * disclosure documents: financial reports (F3/F3P/F3X), statements of
4
+ * candidacy, organizational filings, and amendments.
5
+ * @module mcp-server/tools/definitions/search-filings.tool
6
+ */
7
+ import { tool, z } from '@cyanheads/mcp-ts-core';
8
+ import { getOpenFecService } from '../../../services/openfec/openfec-service.js';
9
+ import { fmt$, PaginationSchema, str } from './utils/format-helpers.js';
10
+ export const searchFilings = tool('openfec_search_filings', {
11
+ description: 'Search FEC filings and reports by committee, candidate, form type, or date range. ' +
12
+ 'Covers financial reports (F3/F3P/F3X), statements of candidacy (F2), ' +
13
+ 'organizational filings (F1), 24-hour IE notices (F24), and amendments.',
14
+ annotations: { readOnlyHint: true, idempotentHint: true },
15
+ input: z.object({
16
+ committee_id: z.string().optional().describe('Filing committee ID.'),
17
+ candidate_id: z.string().optional().describe('Associated candidate ID.'),
18
+ filer_name: z.string().optional().describe('Full-text filer name search.'),
19
+ form_type: z
20
+ .string()
21
+ .optional()
22
+ .describe('FEC form type. Common: F3 (House/Senate quarterly), F3P (Presidential), F3X (PAC/party), F24 (24-hour IE notice), F1 (statement of organization), F2 (statement of candidacy), F5 (IE by persons).'),
23
+ report_type: z
24
+ .string()
25
+ .optional()
26
+ .describe('Report type code. Common: Q1/Q2/Q3 (quarterly), YE (year-end), M3-M12 (monthly), 12G/12P/30G (pre/post election).'),
27
+ report_year: z.number().optional().describe('Filing year.'),
28
+ cycle: z.number().optional().describe('Two-year election cycle (even year).'),
29
+ is_amended: z.boolean().optional().describe('Filter to original or amended filings only.'),
30
+ most_recent: z
31
+ .boolean()
32
+ .optional()
33
+ .describe('Only the most recent version (filters out superseded amendments). Default true.'),
34
+ min_receipt_date: z
35
+ .string()
36
+ .optional()
37
+ .describe('Earliest date FEC received the filing (YYYY-MM-DD).'),
38
+ max_receipt_date: z.string().optional().describe('Latest FEC receipt date (YYYY-MM-DD).'),
39
+ page: z.number().optional().describe('Page number (1-indexed). Default 1.'),
40
+ per_page: z.number().optional().describe('Results per page. Default 20, max 100.'),
41
+ }),
42
+ output: z.object({
43
+ filings: z
44
+ .array(z.record(z.string(), z.unknown()))
45
+ .describe('Filing records with form_type, committee, report_type, financial totals, pdf_url, etc.'),
46
+ pagination: PaginationSchema.describe('Page-based pagination metadata.'),
47
+ }),
48
+ async handler(input, ctx) {
49
+ const fec = getOpenFecService();
50
+ const params = {
51
+ committee_id: input.committee_id,
52
+ candidate_id: input.candidate_id,
53
+ q_filer: input.filer_name,
54
+ form_type: input.form_type,
55
+ report_type: input.report_type,
56
+ report_year: input.report_year,
57
+ cycle: input.cycle,
58
+ is_amended: input.is_amended,
59
+ most_recent: input.most_recent ?? true,
60
+ min_receipt_date: input.min_receipt_date,
61
+ max_receipt_date: input.max_receipt_date,
62
+ page: input.page,
63
+ per_page: input.per_page,
64
+ };
65
+ ctx.log.info('Searching filings', {
66
+ committee_id: input.committee_id,
67
+ form_type: input.form_type,
68
+ });
69
+ const result = await fec.searchFilings(params, ctx);
70
+ return {
71
+ filings: result.results,
72
+ pagination: result.pagination,
73
+ };
74
+ },
75
+ format(result) {
76
+ if (result.filings.length === 0) {
77
+ return [
78
+ {
79
+ type: 'text',
80
+ text: 'No filings found. Try removing the form_type or report_type filter, broadening the date range, or verifying the committee_id.',
81
+ },
82
+ ];
83
+ }
84
+ const lines = result.filings.map((f) => {
85
+ const formType = str(f, 'form_type');
86
+ const committeeName = str(f, 'committee_name');
87
+ const committeeId = str(f, 'committee_id');
88
+ const reportType = str(f, 'report_type_full') || str(f, 'report_type');
89
+ const receiptDate = str(f, 'receipt_date');
90
+ const coverageStart = str(f, 'coverage_start_date');
91
+ const coverageEnd = str(f, 'coverage_end_date');
92
+ const candidateName = str(f, 'candidate_name');
93
+ const pdfUrl = str(f, 'pdf_url');
94
+ let line = `**${formType}** — ${committeeName} (${committeeId})`;
95
+ if (reportType)
96
+ line += `\n Report: ${reportType}`;
97
+ if (f.report_year)
98
+ line += ` (${f.report_year})`;
99
+ if (receiptDate)
100
+ line += ` | Filed: ${receiptDate}`;
101
+ if (candidateName)
102
+ line += `\n Candidate: ${candidateName}`;
103
+ // Financial totals
104
+ const hasFinancials = typeof f.total_receipts === 'number' || typeof f.total_disbursements === 'number';
105
+ if (hasFinancials) {
106
+ line += `\n Receipts: ${fmt$(f.total_receipts)} | Disbursements: ${fmt$(f.total_disbursements)}`;
107
+ line += ` | Cash: ${fmt$(f.cash_on_hand_end_period)} | Debt: ${fmt$(f.debts_owed_by_committee)}`;
108
+ }
109
+ if (coverageStart && coverageEnd) {
110
+ line += `\n Coverage: ${coverageStart} to ${coverageEnd}`;
111
+ }
112
+ if (f.is_amended === true)
113
+ line += ' | AMENDED';
114
+ if (pdfUrl)
115
+ line += `\n PDF: ${pdfUrl}`;
116
+ return line;
117
+ });
118
+ const { page, pages, count } = result.pagination;
119
+ lines.push(`\n---\nPage ${page} of ${pages} (${count} total)`);
120
+ return [{ type: 'text', text: lines.join('\n\n') }];
121
+ },
122
+ });
123
+ //# sourceMappingURL=search-filings.tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-filings.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-filings.tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAE1E,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAExE,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC,wBAAwB,EAAE;IAC1D,WAAW,EACT,oFAAoF;QACpF,uEAAuE;QACvE,wEAAwE;IAC1E,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE;IAEzD,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;QACd,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QACpE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;QACxE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8BAA8B,CAAC;QAC1E,SAAS,EAAE,CAAC;aACT,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,oMAAoM,CACrM;QACH,WAAW,EAAE,CAAC;aACX,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,mHAAmH,CACpH;QACH,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;QAC3D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;QAC7E,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;QAC1F,WAAW,EAAE,CAAC;aACX,OAAO,EAAE;aACT,QAAQ,EAAE;aACV,QAAQ,CAAC,iFAAiF,CAAC;QAC9F,gBAAgB,EAAE,CAAC;aAChB,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,qDAAqD,CAAC;QAClE,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;QACzF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;QAC3E,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wCAAwC,CAAC;KACnF,CAAC;IAEF,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,OAAO,EAAE,CAAC;aACP,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;aACxC,QAAQ,CACP,wFAAwF,CACzF;QACH,UAAU,EAAE,gBAAgB,CAAC,QAAQ,CAAC,iCAAiC,CAAC;KACzE,CAAC;IAEF,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG;QACtB,MAAM,GAAG,GAAG,iBAAiB,EAAE,CAAC;QAEhC,MAAM,MAAM,GAAc;YACxB,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,OAAO,EAAE,KAAK,CAAC,UAAU;YACzB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,IAAI;YACtC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;YACxC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;YACxC,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACzB,CAAC;QAEF,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,mBAAmB,EAAE;YAChC,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,SAAS,EAAE,KAAK,CAAC,SAAS;SAC3B,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAEpD,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,UAAU,EAAE,MAAM,CAAC,UAAU;SAC9B,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,MAAM;QACX,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAChC,OAAO;gBACL;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,+HAA+H;iBACtI;aACF,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACrC,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;YACrC,MAAM,aAAa,GAAG,GAAG,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;YAC/C,MAAM,WAAW,GAAG,GAAG,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;YAC3C,MAAM,UAAU,GAAG,GAAG,CAAC,CAAC,EAAE,kBAAkB,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;YACvE,MAAM,WAAW,GAAG,GAAG,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;YAC3C,MAAM,aAAa,GAAG,GAAG,CAAC,CAAC,EAAE,qBAAqB,CAAC,CAAC;YACpD,MAAM,WAAW,GAAG,GAAG,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC;YAChD,MAAM,aAAa,GAAG,GAAG,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;YAEjC,IAAI,IAAI,GAAG,KAAK,QAAQ,QAAQ,aAAa,KAAK,WAAW,GAAG,CAAC;YACjE,IAAI,UAAU;gBAAE,IAAI,IAAI,eAAe,UAAU,EAAE,CAAC;YACpD,IAAI,CAAC,CAAC,WAAW;gBAAE,IAAI,IAAI,KAAK,CAAC,CAAC,WAAW,GAAG,CAAC;YACjD,IAAI,WAAW;gBAAE,IAAI,IAAI,aAAa,WAAW,EAAE,CAAC;YACpD,IAAI,aAAa;gBAAE,IAAI,IAAI,kBAAkB,aAAa,EAAE,CAAC;YAE7D,mBAAmB;YACnB,MAAM,aAAa,GACjB,OAAO,CAAC,CAAC,cAAc,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,mBAAmB,KAAK,QAAQ,CAAC;YACpF,IAAI,aAAa,EAAE,CAAC;gBAClB,IAAI,IAAI,iBAAiB,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,qBAAqB,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,EAAE,CAAC;gBAClG,IAAI,IAAI,YAAY,IAAI,CAAC,CAAC,CAAC,uBAAuB,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC,uBAAuB,CAAC,EAAE,CAAC;YACnG,CAAC;YAED,IAAI,aAAa,IAAI,WAAW,EAAE,CAAC;gBACjC,IAAI,IAAI,iBAAiB,aAAa,OAAO,WAAW,EAAE,CAAC;YAC7D,CAAC;YAED,IAAI,CAAC,CAAC,UAAU,KAAK,IAAI;gBAAE,IAAI,IAAI,YAAY,CAAC;YAChD,IAAI,MAAM;gBAAE,IAAI,IAAI,YAAY,MAAM,EAAE,CAAC;YAEzC,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;QAEH,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC;QACjD,KAAK,CAAC,IAAI,CAAC,eAAe,IAAI,OAAO,KAAK,KAAK,KAAK,SAAS,CAAC,CAAC;QAE/D,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACtD,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @fileoverview Legal document search tool — search FEC advisory opinions,
3
+ * enforcement cases (MURs), alternative dispute resolutions, administrative
4
+ * fines, and statutes.
5
+ * @module mcp-server/tools/definitions/search-legal.tool
6
+ */
7
+ import { z } from '@cyanheads/mcp-ts-core';
8
+ export declare const searchLegal: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
9
+ query: z.ZodOptional<z.ZodString>;
10
+ type: z.ZodOptional<z.ZodEnum<{
11
+ advisory_opinions: "advisory_opinions";
12
+ murs: "murs";
13
+ adrs: "adrs";
14
+ admin_fines: "admin_fines";
15
+ statutes: "statutes";
16
+ }>>;
17
+ ao_number: z.ZodOptional<z.ZodString>;
18
+ case_number: z.ZodOptional<z.ZodString>;
19
+ respondent: z.ZodOptional<z.ZodString>;
20
+ regulatory_citation: z.ZodOptional<z.ZodString>;
21
+ statutory_citation: z.ZodOptional<z.ZodString>;
22
+ min_penalty_amount: z.ZodOptional<z.ZodNumber>;
23
+ max_penalty_amount: z.ZodOptional<z.ZodNumber>;
24
+ min_date: z.ZodOptional<z.ZodString>;
25
+ max_date: z.ZodOptional<z.ZodString>;
26
+ from_hit: z.ZodDefault<z.ZodNumber>;
27
+ hits_returned: z.ZodDefault<z.ZodNumber>;
28
+ }, z.core.$strip>, z.ZodObject<{
29
+ results: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
30
+ total_count: z.ZodNumber;
31
+ }, z.core.$strip>>;
32
+ //# sourceMappingURL=search-legal.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-legal.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-legal.tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAejD,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;kBA6KtB,CAAC"}
@@ -0,0 +1,179 @@
1
+ /**
2
+ * @fileoverview Legal document search tool — search FEC advisory opinions,
3
+ * enforcement cases (MURs), alternative dispute resolutions, administrative
4
+ * fines, and statutes.
5
+ * @module mcp-server/tools/definitions/search-legal.tool
6
+ */
7
+ import { tool, z } from '@cyanheads/mcp-ts-core';
8
+ import { invalidParams } from '@cyanheads/mcp-ts-core/errors';
9
+ import { getOpenFecService } from '../../../services/openfec/openfec-service.js';
10
+ import { fmt$ } from './utils/format-helpers.js';
11
+ /** Human-readable labels for document type discriminators. */
12
+ const typeLabels = {
13
+ advisory_opinion: 'Advisory Opinion',
14
+ mur: 'Matter Under Review (MUR)',
15
+ adr: 'Alternative Dispute Resolution',
16
+ admin_fine: 'Administrative Fine',
17
+ statute: 'Statute',
18
+ };
19
+ export const searchLegal = tool('openfec_search_legal', {
20
+ description: 'Search FEC legal documents: advisory opinions, enforcement cases (MURs), ' +
21
+ 'alternative dispute resolutions, and administrative fines.',
22
+ annotations: { readOnlyHint: true, idempotentHint: true },
23
+ input: z.object({
24
+ query: z.string().optional().describe('Full-text search across legal documents.'),
25
+ type: z
26
+ .enum(['advisory_opinions', 'murs', 'adrs', 'admin_fines', 'statutes'])
27
+ .optional()
28
+ .describe('Document type filter. Omit to search all types.'),
29
+ ao_number: z.string().optional().describe('Specific advisory opinion number (e.g. "2024-01").'),
30
+ case_number: z.string().optional().describe('Specific MUR or ADR case number.'),
31
+ respondent: z.string().optional().describe('Respondent name (enforcement cases).'),
32
+ regulatory_citation: z.string().optional().describe('CFR citation (e.g. "11 CFR 112.4").'),
33
+ statutory_citation: z.string().optional().describe('U.S.C. citation (e.g. "52 U.S.C. 30106").'),
34
+ min_penalty_amount: z
35
+ .number()
36
+ .optional()
37
+ .describe('Minimum penalty amount (enforcement cases).'),
38
+ max_penalty_amount: z.number().optional().describe('Maximum penalty amount.'),
39
+ min_date: z.string().optional().describe('Earliest document date (YYYY-MM-DD).'),
40
+ max_date: z.string().optional().describe('Latest document date (YYYY-MM-DD).'),
41
+ from_hit: z
42
+ .number()
43
+ .int()
44
+ .min(0)
45
+ .default(0)
46
+ .describe('Offset for pagination (0-indexed). Default 0.'),
47
+ hits_returned: z
48
+ .number()
49
+ .int()
50
+ .min(1)
51
+ .max(200)
52
+ .default(20)
53
+ .describe('Results per page. Default 20, max 200.'),
54
+ }),
55
+ output: z.object({
56
+ results: z
57
+ .array(z.record(z.string(), z.unknown()))
58
+ .describe('Legal documents with a document_type discriminator (advisory_opinion, mur, adr, admin_fine, statute).'),
59
+ total_count: z.number().describe('Total matching documents across all types.'),
60
+ }),
61
+ async handler(input, ctx) {
62
+ const hasFilter = input.query || input.type || input.ao_number || input.case_number;
63
+ if (!hasFilter) {
64
+ throw invalidParams('Provide at least a search query, document type, or specific identifier (ao_number, case_number).');
65
+ }
66
+ const fec = getOpenFecService();
67
+ const params = {
68
+ from_hit: input.from_hit,
69
+ hits_returned: input.hits_returned,
70
+ };
71
+ if (input.query)
72
+ params.q = input.query;
73
+ if (input.type)
74
+ params.type = input.type;
75
+ if (input.ao_number)
76
+ params.ao_no = input.ao_number;
77
+ if (input.case_number)
78
+ params.case_no = input.case_number;
79
+ if (input.respondent)
80
+ params.case_respondents = input.respondent;
81
+ if (input.regulatory_citation)
82
+ params.ao_regulatory_citation = input.regulatory_citation;
83
+ if (input.statutory_citation)
84
+ params.ao_statutory_citation = input.statutory_citation;
85
+ if (input.min_penalty_amount !== undefined)
86
+ params.min_penalty_amount = input.min_penalty_amount;
87
+ if (input.max_penalty_amount !== undefined)
88
+ params.max_penalty_amount = input.max_penalty_amount;
89
+ if (input.min_date)
90
+ params.min_date = input.min_date;
91
+ if (input.max_date)
92
+ params.max_date = input.max_date;
93
+ ctx.log.info('Searching legal documents', {
94
+ query: input.query,
95
+ type: input.type,
96
+ resultCount: input.hits_returned,
97
+ });
98
+ const data = await fec.searchLegal(params, ctx);
99
+ // Trim bulky fields to keep payloads manageable for LLM context windows.
100
+ // Full document lists and verbose highlights can easily exceed 100KB per result.
101
+ const trimmed = data.results.map((doc) => {
102
+ const d = { ...doc };
103
+ // Keep only the first 3 highlights and drop per-document highlight maps
104
+ if (Array.isArray(d.highlights) && d.highlights.length > 3) {
105
+ d.highlights = d.highlights.slice(0, 3);
106
+ }
107
+ delete d.document_highlights;
108
+ // Summarize documents as a count + categories instead of full arrays
109
+ if (Array.isArray(d.documents) && d.documents.length > 0) {
110
+ const docs = d.documents;
111
+ const categories = [...new Set(docs.map((dd) => dd.category).filter(Boolean))];
112
+ d.document_count = docs.length;
113
+ d.document_categories = categories;
114
+ delete d.documents;
115
+ }
116
+ // Trim verbose commission_votes to just the vote dates
117
+ if (Array.isArray(d.commission_votes) && d.commission_votes.length > 0) {
118
+ const votes = d.commission_votes;
119
+ d.commission_votes = votes.map((v) => ({
120
+ vote_date: v.vote_date,
121
+ action: typeof v.action === 'string' ? v.action.slice(0, 200) : v.action,
122
+ }));
123
+ }
124
+ return d;
125
+ });
126
+ return { results: trimmed, total_count: data.totalCount };
127
+ },
128
+ format: (result) => {
129
+ if (result.results.length === 0) {
130
+ return [
131
+ {
132
+ type: 'text',
133
+ text: 'No legal documents found. Try different search terms, remove the type filter to search all document types, or check the ao_number/case_number format.',
134
+ },
135
+ ];
136
+ }
137
+ /** Group results by document_type for readable output. */
138
+ const grouped = new Map();
139
+ for (const doc of result.results) {
140
+ const docType = String(doc.document_type ?? 'unknown');
141
+ let group = grouped.get(docType);
142
+ if (!group) {
143
+ group = [];
144
+ grouped.set(docType, group);
145
+ }
146
+ group.push(doc);
147
+ }
148
+ const sections = [];
149
+ for (const [docType, docs] of grouped) {
150
+ const label = typeLabels[docType] ?? docType;
151
+ const lines = docs.map((doc) => {
152
+ const id = doc.ao_no ?? doc.case_no ?? doc.no ?? '';
153
+ const name = doc.name ?? '';
154
+ const summary = doc.summary ??
155
+ (Array.isArray(doc.highlights) && doc.highlights.length > 0
156
+ ? doc.highlights.join(' ... ')
157
+ : '');
158
+ const date = doc.issue_date ?? doc.open_date ?? doc.close_date ?? doc.date ?? '';
159
+ const penalty = doc.penalty_amount != null ? ` | Penalty: ${fmt$(doc.penalty_amount)}` : '';
160
+ const parts = [];
161
+ if (id)
162
+ parts.push(`**${id}**`);
163
+ if (name)
164
+ parts.push(String(name));
165
+ if (date)
166
+ parts.push(`(${date})`);
167
+ if (penalty)
168
+ parts.push(penalty);
169
+ if (summary)
170
+ parts.push(`\n ${String(summary).slice(0, 300)}`);
171
+ return parts.join(' ');
172
+ });
173
+ sections.push(`### ${label}\n${lines.join('\n\n')}`);
174
+ }
175
+ sections.push(`\n_${result.total_count} total matching document(s)_`);
176
+ return [{ type: 'text', text: sections.join('\n\n') }];
177
+ },
178
+ });
179
+ //# sourceMappingURL=search-legal.tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-legal.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-legal.tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @fileoverview Shared formatting utilities for tool definition format() functions.
3
+ * Centralizes USD formatting, safe record field access, and the pagination schema
4
+ * reused across multiple tools.
5
+ * @module src/mcp-server/tools/definitions/format-helpers
6
+ */
7
+ import { z } from '@cyanheads/mcp-ts-core';
8
+ /** Format a number as USD or return 'N/A' for non-numeric values. */
9
+ export declare const fmt$: (n: unknown) => string;
10
+ /** Safely read a string field from an untyped record. */
11
+ export declare const str: (rec: Record<string, unknown>, key: string) => string;
12
+ /** Reusable page-based pagination output schema. */
13
+ export declare const PaginationSchema: z.ZodObject<{
14
+ page: z.ZodNumber;
15
+ pages: z.ZodNumber;
16
+ count: z.ZodNumber;
17
+ per_page: z.ZodNumber;
18
+ }, z.core.$strip>;
19
+ //# sourceMappingURL=format-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-helpers.d.ts","sourceRoot":"","sources":["../../../../../src/mcp-server/tools/definitions/utils/format-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAE3C,qEAAqE;AACrE,eAAO,MAAM,IAAI,GAAI,GAAG,OAAO,KAAG,MACwB,CAAC;AAE3D,yDAAyD;AACzD,eAAO,MAAM,GAAG,GAAI,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,MAAM,KAAG,MACN,CAAC;AAE3D,oDAAoD;AACpD,eAAO,MAAM,gBAAgB;;;;;iBAK3B,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @fileoverview Shared formatting utilities for tool definition format() functions.
3
+ * Centralizes USD formatting, safe record field access, and the pagination schema
4
+ * reused across multiple tools.
5
+ * @module src/mcp-server/tools/definitions/format-helpers
6
+ */
7
+ import { z } from '@cyanheads/mcp-ts-core';
8
+ /** Format a number as USD or return 'N/A' for non-numeric values. */
9
+ export const fmt$ = (n) => typeof n === 'number' ? `$${n.toLocaleString()}` : 'N/A';
10
+ /** Safely read a string field from an untyped record. */
11
+ export const str = (rec, key) => typeof rec[key] === 'string' ? rec[key] : '';
12
+ /** Reusable page-based pagination output schema. */
13
+ export const PaginationSchema = z.object({
14
+ page: z.number().describe('Current page number (1-indexed).'),
15
+ pages: z.number().describe('Total number of pages.'),
16
+ count: z.number().describe('Total result count.'),
17
+ per_page: z.number().describe('Results per page.'),
18
+ });
19
+ //# sourceMappingURL=format-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-helpers.js","sourceRoot":"","sources":["../../../../../src/mcp-server/tools/definitions/utils/format-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAE3C,qEAAqE;AACrE,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAU,EAAU,EAAE,CACzC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AAE3D,yDAAyD;AACzD,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,GAA4B,EAAE,GAAW,EAAU,EAAE,CACvE,OAAO,GAAG,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,GAAG,CAAC,GAAG,CAAY,CAAC,CAAC,CAAC,EAAE,CAAC;AAE3D,oDAAoD;AACpD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;IAC7D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAC;IACpD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qBAAqB,CAAC;IACjD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;CACnD,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @fileoverview Shared FEC ID format validators for tool handlers.
3
+ * Validates candidate_id and committee_id formats before hitting the API,
4
+ * producing clear error messages instead of silent empty results.
5
+ * @module src/mcp-server/tools/definitions/id-validators
6
+ */
7
+ /** Throw invalidParams if `candidateId` doesn't match the FEC candidate ID pattern. */
8
+ export declare function validateCandidateId(candidateId: string): void;
9
+ /** Throw invalidParams if `committeeId` doesn't match the FEC committee ID pattern. */
10
+ export declare function validateCommitteeId(committeeId: string): void;
11
+ //# sourceMappingURL=id-validators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id-validators.d.ts","sourceRoot":"","sources":["../../../../../src/mcp-server/tools/definitions/utils/id-validators.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,uFAAuF;AACvF,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAO7D;AAED,uFAAuF;AACvF,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAO7D"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @fileoverview Shared FEC ID format validators for tool handlers.
3
+ * Validates candidate_id and committee_id formats before hitting the API,
4
+ * producing clear error messages instead of silent empty results.
5
+ * @module src/mcp-server/tools/definitions/id-validators
6
+ */
7
+ import { invalidParams } from '@cyanheads/mcp-ts-core/errors';
8
+ const CANDIDATE_ID_RE = /^[HSP][0-9A-Z]+$/i;
9
+ const COMMITTEE_ID_RE = /^C\d+$/i;
10
+ /** Throw invalidParams if `candidateId` doesn't match the FEC candidate ID pattern. */
11
+ export function validateCandidateId(candidateId) {
12
+ if (!CANDIDATE_ID_RE.test(candidateId)) {
13
+ throw invalidParams("Invalid candidate ID format. FEC candidate IDs start with H (House), S (Senate), or P (President) followed by digits (e.g., 'P00003392').", { candidate_id: candidateId });
14
+ }
15
+ }
16
+ /** Throw invalidParams if `committeeId` doesn't match the FEC committee ID pattern. */
17
+ export function validateCommitteeId(committeeId) {
18
+ if (!COMMITTEE_ID_RE.test(committeeId)) {
19
+ throw invalidParams("Invalid committee ID format. FEC committee IDs start with 'C' followed by digits (e.g., 'C00358796').", { committee_id: committeeId });
20
+ }
21
+ }
22
+ //# sourceMappingURL=id-validators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id-validators.js","sourceRoot":"","sources":["../../../../../src/mcp-server/tools/definitions/utils/id-validators.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAE9D,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAC5C,MAAM,eAAe,GAAG,SAAS,CAAC;AAElC,uFAAuF;AACvF,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QACvC,MAAM,aAAa,CACjB,2IAA2I,EAC3I,EAAE,YAAY,EAAE,WAAW,EAAE,CAC9B,CAAC;IACJ,CAAC;AACH,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QACvC,MAAM,aAAa,CACjB,uGAAuG,EACvG,EAAE,YAAY,EAAE,WAAW,EAAE,CAC9B,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @fileoverview OpenFEC API service. Wraps all FEC REST API interactions
3
+ * with timeout, retry, and pagination handling. Single service used by
4
+ * all tools and resources.
5
+ * @module src/services/openfec/openfec-service
6
+ */
7
+ import type { Context } from '@cyanheads/mcp-ts-core';
8
+ import type { ElectionSummary, FecParams, LegalResult, PageResult, SeekResult } from './types.js';
9
+ /** Encode `last_indexes` from SEEK pagination into an opaque cursor. */
10
+ export declare function encodeCursor(lastIndexes: Record<string, string>): string;
11
+ /** Decode an opaque cursor back to `last_indexes` query params. */
12
+ export declare function decodeCursor(cursor: string): Record<string, string>;
13
+ export declare class OpenFecService {
14
+ private readonly config;
15
+ constructor();
16
+ /** Build a full URL with query params, injecting the API key. */
17
+ private buildUrl;
18
+ /**
19
+ * Fetch JSON from a page-based endpoint with retry.
20
+ * Wraps the full pipeline (fetch + JSON parse) in the retry boundary.
21
+ */
22
+ private fetchPage;
23
+ /**
24
+ * Fetch JSON from a keyset (SEEK) endpoint with retry.
25
+ * Returns a `nextCursor` from `last_indexes` when more results exist.
26
+ */
27
+ private fetchSeek;
28
+ /** Fetch legal search results with retry. Normalizes type-keyed arrays. */
29
+ private fetchLegalSearch;
30
+ /** Validate that the API returned a recognizable envelope, not an HTML error page. */
31
+ private validateEnvelope;
32
+ searchCandidates(params: FecParams, ctx: Context): Promise<PageResult>;
33
+ getCandidate(candidateId: string, ctx: Context): Promise<PageResult>;
34
+ getCandidateTotals(params: FecParams, ctx: Context): Promise<PageResult>;
35
+ searchCommittees(params: FecParams, ctx: Context): Promise<PageResult>;
36
+ getCommittee(committeeId: string, ctx: Context): Promise<PageResult>;
37
+ searchContributions(params: FecParams, ctx: Context): Promise<SeekResult>;
38
+ getContributionAggregates(mode: string, params: FecParams, ctx: Context): Promise<PageResult>;
39
+ searchDisbursements(params: FecParams, ctx: Context): Promise<SeekResult>;
40
+ getDisbursementAggregates(mode: string, params: FecParams, ctx: Context): Promise<PageResult>;
41
+ searchExpenditures(params: FecParams, ctx: Context): Promise<SeekResult>;
42
+ getExpendituresByCandidate(params: FecParams, ctx: Context): Promise<PageResult>;
43
+ searchFilings(params: FecParams, ctx: Context): Promise<PageResult>;
44
+ searchElections(params: FecParams, ctx: Context): Promise<PageResult>;
45
+ /** Fetch election summary — flat response (no pagination wrapper). */
46
+ getElectionSummary(params: FecParams, ctx: Context): Promise<ElectionSummary>;
47
+ searchLegal(params: FecParams, ctx: Context): Promise<LegalResult>;
48
+ getCalendarDates(params: FecParams, ctx: Context): Promise<PageResult>;
49
+ getReportingDates(params: FecParams, ctx: Context): Promise<PageResult>;
50
+ getElectionDates(params: FecParams, ctx: Context): Promise<PageResult>;
51
+ }
52
+ export declare function initOpenFecService(): void;
53
+ export declare function getOpenFecService(): OpenFecService;
54
+ //# sourceMappingURL=openfec-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openfec-service.d.ts","sourceRoot":"","sources":["../../../src/services/openfec/openfec-service.ts"],"names":[],"mappings":"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"}