@pipeworx/mcp-cfpb 0.1.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/LICENSE +21 -0
- package/README.md +34 -0
- package/package.json +20 -0
- package/server.json +18 -0
- package/src/index.ts +324 -0
- package/tsconfig.json +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pipeworx
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# mcp-cfpb
|
|
2
|
+
|
|
3
|
+
CFPB MCP — Consumer Financial Protection Bureau complaint database (free, no auth)
|
|
4
|
+
|
|
5
|
+
Part of the [Pipeworx](https://pipeworx.io) open MCP gateway.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
| Tool | Description |
|
|
10
|
+
|------|-------------|
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
Add to your MCP client config:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"cfpb": {
|
|
20
|
+
"url": "https://gateway.pipeworx.io/cfpb/mcp"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or use the CLI:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx pipeworx use cfpb
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## License
|
|
33
|
+
|
|
34
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pipeworx/mcp-cfpb",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CFPB MCP — Consumer Financial Protection Bureau complaint database (free, no auth)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"keywords": ["mcp", "mcp-server", "model-context-protocol", "pipeworx", "cfpb"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/pipeworx-io/mcp-cfpb"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.7.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.pipeworx-io/cfpb",
|
|
4
|
+
"title": "cfpb",
|
|
5
|
+
"description": "CFPB MCP — Consumer Financial Protection Bureau complaint database (free, no auth)",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"websiteUrl": "https://pipeworx.io/packs/cfpb",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/pipeworx-io/mcp-cfpb",
|
|
10
|
+
"source": "github"
|
|
11
|
+
},
|
|
12
|
+
"remotes": [
|
|
13
|
+
{
|
|
14
|
+
"type": "streamable-http",
|
|
15
|
+
"url": "https://gateway.pipeworx.io/cfpb/mcp"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
interface McpToolDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: 'object';
|
|
6
|
+
properties: Record<string, unknown>;
|
|
7
|
+
required?: string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface McpToolExport {
|
|
12
|
+
tools: McpToolDefinition[];
|
|
13
|
+
callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* CFPB MCP — Consumer Financial Protection Bureau complaint database (free, no auth)
|
|
18
|
+
*
|
|
19
|
+
* Tools:
|
|
20
|
+
* - cfpb_search_complaints: search consumer complaints by keyword, company, product, or date
|
|
21
|
+
* - cfpb_company_complaints: get complaints for a specific company
|
|
22
|
+
* - cfpb_get_complaint: get a single complaint by ID
|
|
23
|
+
* - cfpb_top_companies: get companies with most complaints in a period
|
|
24
|
+
* - cfpb_product_breakdown: complaint counts by product category
|
|
25
|
+
*
|
|
26
|
+
* API: https://www.consumerfinance.gov/data-research/consumer-complaints/search/api/v1/
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
const BASE_URL = 'https://www.consumerfinance.gov/data-research/consumer-complaints/search/api/v1/';
|
|
31
|
+
|
|
32
|
+
const tools: McpToolExport['tools'] = [
|
|
33
|
+
{
|
|
34
|
+
name: 'cfpb_search_complaints',
|
|
35
|
+
description:
|
|
36
|
+
'Search the CFPB consumer complaint database. Filter by keyword, company, product category, and date range. Returns complaint narratives, company responses, and resolution status.',
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: 'object' as const,
|
|
39
|
+
properties: {
|
|
40
|
+
query: { type: 'string', description: 'Search term (e.g., "overdraft fees", "denied claim"). Optional if other filters provided.' },
|
|
41
|
+
company: { type: 'string', description: 'Company name to filter by (e.g., "BANK OF AMERICA", "WELLS FARGO")' },
|
|
42
|
+
product: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description:
|
|
45
|
+
'Product category (e.g., "Credit card", "Mortgage", "Student loan", "Vehicle loan or lease", "Checking or savings account", "Credit reporting", "Debt collection")',
|
|
46
|
+
},
|
|
47
|
+
start_date: { type: 'string', description: 'Start date in YYYY-MM-DD format' },
|
|
48
|
+
end_date: { type: 'string', description: 'End date in YYYY-MM-DD format' },
|
|
49
|
+
limit: { type: 'number', description: 'Number of results (1-100, default 25)' },
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'cfpb_company_complaints',
|
|
55
|
+
description:
|
|
56
|
+
'Get recent consumer complaints for a specific company, sorted by newest first. Returns complaint details and company response information.',
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: 'object' as const,
|
|
59
|
+
properties: {
|
|
60
|
+
company: { type: 'string', description: 'Company name (e.g., "BANK OF AMERICA", "CITIBANK", "JPMORGAN CHASE")' },
|
|
61
|
+
limit: { type: 'number', description: 'Number of results (1-100, default 25)' },
|
|
62
|
+
},
|
|
63
|
+
required: ['company'],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'cfpb_get_complaint',
|
|
68
|
+
description:
|
|
69
|
+
'Get full details for a single consumer complaint by its complaint ID number.',
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: 'object' as const,
|
|
72
|
+
properties: {
|
|
73
|
+
complaint_id: { type: 'string', description: 'CFPB complaint ID number' },
|
|
74
|
+
},
|
|
75
|
+
required: ['complaint_id'],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'cfpb_top_companies',
|
|
80
|
+
description:
|
|
81
|
+
'Get the companies with the most consumer complaints in a given date range. Useful for identifying which companies receive the most complaints.',
|
|
82
|
+
inputSchema: {
|
|
83
|
+
type: 'object' as const,
|
|
84
|
+
properties: {
|
|
85
|
+
start_date: { type: 'string', description: 'Start date in YYYY-MM-DD format' },
|
|
86
|
+
end_date: { type: 'string', description: 'End date in YYYY-MM-DD format' },
|
|
87
|
+
product: { type: 'string', description: 'Optional product filter (e.g., "Mortgage", "Credit card")' },
|
|
88
|
+
limit: { type: 'number', description: 'Number of top companies to return (default 10)' },
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'cfpb_product_breakdown',
|
|
94
|
+
description:
|
|
95
|
+
'Get complaint counts broken down by product category. Optionally filter by company and/or date range.',
|
|
96
|
+
inputSchema: {
|
|
97
|
+
type: 'object' as const,
|
|
98
|
+
properties: {
|
|
99
|
+
company: { type: 'string', description: 'Optional company name to filter by' },
|
|
100
|
+
start_date: { type: 'string', description: 'Start date in YYYY-MM-DD format' },
|
|
101
|
+
end_date: { type: 'string', description: 'End date in YYYY-MM-DD format' },
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
// ── Types ───────────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
interface CfpbHit {
|
|
110
|
+
_source: {
|
|
111
|
+
complaint_id: string;
|
|
112
|
+
date_received: string;
|
|
113
|
+
product: string;
|
|
114
|
+
sub_product?: string;
|
|
115
|
+
issue: string;
|
|
116
|
+
sub_issue?: string;
|
|
117
|
+
complaint_what_happened?: string;
|
|
118
|
+
company: string;
|
|
119
|
+
state?: string;
|
|
120
|
+
zip_code?: string;
|
|
121
|
+
company_public_response?: string;
|
|
122
|
+
company_response: string;
|
|
123
|
+
timely?: string;
|
|
124
|
+
consumer_disputed?: string;
|
|
125
|
+
consumer_consent_provided?: string;
|
|
126
|
+
submitted_via?: string;
|
|
127
|
+
date_sent_to_company?: string;
|
|
128
|
+
tags?: string;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
interface CfpbResponse {
|
|
133
|
+
hits: {
|
|
134
|
+
hits: CfpbHit[];
|
|
135
|
+
total: number;
|
|
136
|
+
};
|
|
137
|
+
aggregations?: {
|
|
138
|
+
company?: { buckets: { key: string; doc_count: number }[] };
|
|
139
|
+
product?: { buckets: { key: string; doc_count: number }[] };
|
|
140
|
+
};
|
|
141
|
+
_meta?: { total_record_count: number };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
function formatComplaint(hit: CfpbHit) {
|
|
147
|
+
const s = hit._source;
|
|
148
|
+
return {
|
|
149
|
+
complaint_id: s.complaint_id,
|
|
150
|
+
date_received: s.date_received,
|
|
151
|
+
product: s.product,
|
|
152
|
+
sub_product: s.sub_product ?? null,
|
|
153
|
+
issue: s.issue,
|
|
154
|
+
sub_issue: s.sub_issue ?? null,
|
|
155
|
+
narrative: s.complaint_what_happened ?? null,
|
|
156
|
+
company: s.company,
|
|
157
|
+
state: s.state ?? null,
|
|
158
|
+
company_response: s.company_response,
|
|
159
|
+
company_public_response: s.company_public_response ?? null,
|
|
160
|
+
timely: s.timely ?? null,
|
|
161
|
+
consumer_disputed: s.consumer_disputed ?? null,
|
|
162
|
+
submitted_via: s.submitted_via ?? null,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ── Tool implementations ────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
async function searchComplaints(
|
|
169
|
+
query?: string,
|
|
170
|
+
company?: string,
|
|
171
|
+
product?: string,
|
|
172
|
+
startDate?: string,
|
|
173
|
+
endDate?: string,
|
|
174
|
+
limit?: number,
|
|
175
|
+
) {
|
|
176
|
+
const size = Math.min(100, Math.max(1, limit ?? 25));
|
|
177
|
+
const params = new URLSearchParams({ size: String(size), sort: 'created_date_desc', field: 'all' });
|
|
178
|
+
|
|
179
|
+
if (query) params.set('search_term', query);
|
|
180
|
+
if (company) params.set('company', company);
|
|
181
|
+
if (product) params.set('product', product);
|
|
182
|
+
if (startDate) params.set('date_received_min', startDate);
|
|
183
|
+
if (endDate) params.set('date_received_max', endDate);
|
|
184
|
+
|
|
185
|
+
const res = await fetch(`${BASE_URL}?${params}`);
|
|
186
|
+
if (!res.ok) throw new Error(`CFPB API error: ${res.status}`);
|
|
187
|
+
|
|
188
|
+
const data = (await res.json()) as CfpbResponse;
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
query: query ?? null,
|
|
192
|
+
filters: {
|
|
193
|
+
company: company ?? null,
|
|
194
|
+
product: product ?? null,
|
|
195
|
+
date_range: { start: startDate ?? null, end: endDate ?? null },
|
|
196
|
+
},
|
|
197
|
+
total: data.hits?.total ?? 0,
|
|
198
|
+
complaints: (data.hits?.hits ?? []).map(formatComplaint),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function companyComplaints(company: string, limit?: number) {
|
|
203
|
+
const size = Math.min(100, Math.max(1, limit ?? 25));
|
|
204
|
+
const params = new URLSearchParams({
|
|
205
|
+
company,
|
|
206
|
+
size: String(size),
|
|
207
|
+
sort: 'created_date_desc',
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const res = await fetch(`${BASE_URL}?${params}`);
|
|
211
|
+
if (!res.ok) throw new Error(`CFPB API error: ${res.status}`);
|
|
212
|
+
|
|
213
|
+
const data = (await res.json()) as CfpbResponse;
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
company,
|
|
217
|
+
total: data.hits?.total ?? 0,
|
|
218
|
+
complaints: (data.hits?.hits ?? []).map(formatComplaint),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function getComplaint(complaintId: string) {
|
|
223
|
+
const res = await fetch(`${BASE_URL}${encodeURIComponent(complaintId)}`);
|
|
224
|
+
if (!res.ok) throw new Error(`CFPB API error: ${res.status} — complaint ID "${complaintId}" may not exist`);
|
|
225
|
+
|
|
226
|
+
const data = (await res.json()) as { hits: { hits: CfpbHit[]; total: number } };
|
|
227
|
+
|
|
228
|
+
if (!data.hits?.hits?.length) {
|
|
229
|
+
throw new Error(`Complaint not found: ${complaintId}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return formatComplaint(data.hits.hits[0]);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function topCompanies(startDate?: string, endDate?: string, product?: string, limit?: number) {
|
|
236
|
+
const count = Math.min(50, Math.max(1, limit ?? 10));
|
|
237
|
+
const params = new URLSearchParams({ size: '0', field: 'all' });
|
|
238
|
+
|
|
239
|
+
if (startDate) params.set('date_received_min', startDate);
|
|
240
|
+
if (endDate) params.set('date_received_max', endDate);
|
|
241
|
+
if (product) params.set('product', product);
|
|
242
|
+
|
|
243
|
+
const res = await fetch(`${BASE_URL}?${params}`);
|
|
244
|
+
if (!res.ok) throw new Error(`CFPB API error: ${res.status}`);
|
|
245
|
+
|
|
246
|
+
const data = (await res.json()) as CfpbResponse;
|
|
247
|
+
const buckets = data.aggregations?.company?.buckets ?? [];
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
filters: {
|
|
251
|
+
product: product ?? null,
|
|
252
|
+
date_range: { start: startDate ?? null, end: endDate ?? null },
|
|
253
|
+
},
|
|
254
|
+
total_complaints: data.hits?.total ?? 0,
|
|
255
|
+
top_companies: buckets.slice(0, count).map((b, i) => ({
|
|
256
|
+
rank: i + 1,
|
|
257
|
+
company: b.key,
|
|
258
|
+
complaint_count: b.doc_count,
|
|
259
|
+
})),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function productBreakdown(company?: string, startDate?: string, endDate?: string) {
|
|
264
|
+
const params = new URLSearchParams({ size: '0', field: 'all' });
|
|
265
|
+
|
|
266
|
+
if (company) params.set('company', company);
|
|
267
|
+
if (startDate) params.set('date_received_min', startDate);
|
|
268
|
+
if (endDate) params.set('date_received_max', endDate);
|
|
269
|
+
|
|
270
|
+
const res = await fetch(`${BASE_URL}?${params}`);
|
|
271
|
+
if (!res.ok) throw new Error(`CFPB API error: ${res.status}`);
|
|
272
|
+
|
|
273
|
+
const data = (await res.json()) as CfpbResponse;
|
|
274
|
+
const buckets = data.aggregations?.product?.buckets ?? [];
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
filters: {
|
|
278
|
+
company: company ?? null,
|
|
279
|
+
date_range: { start: startDate ?? null, end: endDate ?? null },
|
|
280
|
+
},
|
|
281
|
+
total_complaints: data.hits?.total ?? 0,
|
|
282
|
+
products: buckets.map((b) => ({
|
|
283
|
+
product: b.key,
|
|
284
|
+
complaint_count: b.doc_count,
|
|
285
|
+
})),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ── callTool router ─────────────────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
292
|
+
switch (name) {
|
|
293
|
+
case 'cfpb_search_complaints':
|
|
294
|
+
return searchComplaints(
|
|
295
|
+
args.query as string | undefined,
|
|
296
|
+
args.company as string | undefined,
|
|
297
|
+
args.product as string | undefined,
|
|
298
|
+
args.start_date as string | undefined,
|
|
299
|
+
args.end_date as string | undefined,
|
|
300
|
+
args.limit as number | undefined,
|
|
301
|
+
);
|
|
302
|
+
case 'cfpb_company_complaints':
|
|
303
|
+
return companyComplaints(args.company as string, args.limit as number | undefined);
|
|
304
|
+
case 'cfpb_get_complaint':
|
|
305
|
+
return getComplaint(args.complaint_id as string);
|
|
306
|
+
case 'cfpb_top_companies':
|
|
307
|
+
return topCompanies(
|
|
308
|
+
args.start_date as string | undefined,
|
|
309
|
+
args.end_date as string | undefined,
|
|
310
|
+
args.product as string | undefined,
|
|
311
|
+
args.limit as number | undefined,
|
|
312
|
+
);
|
|
313
|
+
case 'cfpb_product_breakdown':
|
|
314
|
+
return productBreakdown(
|
|
315
|
+
args.company as string | undefined,
|
|
316
|
+
args.start_date as string | undefined,
|
|
317
|
+
args.end_date as string | undefined,
|
|
318
|
+
);
|
|
319
|
+
default:
|
|
320
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export default { tools, callTool, meter: { credits: 5 } } satisfies McpToolExport;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|