hackerone-mcp-server 0.0.0-stage → 1.0.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 +149 -2
- package/dist/h1client.d.ts +179 -0
- package/dist/h1client.js +701 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +532 -0
- package/package.json +28 -4
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { searchReports, getReport, getReportActivities, getReportSummary, listPrograms, getProgramDetails, getProgramSignalRequirements, getProgramScope, getProgramWeaknesses, getEarnings, getHackerProfile, getBalance, submitReport, addComment, closeReport, searchDisclosedReports, } from "./h1client.js";
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
name: "hackerone",
|
|
8
|
+
version: "2.0.0",
|
|
9
|
+
});
|
|
10
|
+
// ── Tool: search_reports ───────────────────────────────────────────
|
|
11
|
+
server.tool("search_reports", "Search and list your HackerOne reports. Filter by keyword, program, severity, or state. Great for finding past reports to reference when drafting new ones.", {
|
|
12
|
+
query: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Keyword search (e.g. 'SSRF', 'OAuth', 'PassRole', 'S3')"),
|
|
16
|
+
program: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Program handle to filter by (e.g. 'uber', 'amazon')"),
|
|
20
|
+
severity: z
|
|
21
|
+
.enum(["none", "low", "medium", "high", "critical"])
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Filter by severity rating"),
|
|
24
|
+
state: z
|
|
25
|
+
.enum([
|
|
26
|
+
"new",
|
|
27
|
+
"triaged",
|
|
28
|
+
"needs-more-info",
|
|
29
|
+
"resolved",
|
|
30
|
+
"not-applicable",
|
|
31
|
+
"informative",
|
|
32
|
+
"duplicate",
|
|
33
|
+
"spam",
|
|
34
|
+
])
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Filter by report state"),
|
|
37
|
+
page_size: z
|
|
38
|
+
.number()
|
|
39
|
+
.min(1)
|
|
40
|
+
.max(100)
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Results per page (default 25)"),
|
|
43
|
+
page_number: z.number().optional().describe("Page number for pagination"),
|
|
44
|
+
sort: z
|
|
45
|
+
.string()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe("Sort field (e.g. 'reports.created_at' or '-reports.created_at' for desc)"),
|
|
48
|
+
}, async (params) => {
|
|
49
|
+
try {
|
|
50
|
+
const results = await searchReports(params);
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: JSON.stringify(results, null, 2),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
63
|
+
isError: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
// ── Tool: get_report ───────────────────────────────────────────────
|
|
68
|
+
server.tool("get_report", "Get the full details of a specific HackerOne report by ID. Returns title, vulnerability details, impact, severity, full CVSS vector/score, bounty amounts, attachments, timestamps, and program info.", {
|
|
69
|
+
report_id: z.string().describe("The HackerOne report ID"),
|
|
70
|
+
}, async ({ report_id }) => {
|
|
71
|
+
try {
|
|
72
|
+
const report = await getReport(report_id);
|
|
73
|
+
return {
|
|
74
|
+
content: [
|
|
75
|
+
{
|
|
76
|
+
type: "text",
|
|
77
|
+
text: JSON.stringify(report, null, 2),
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
return {
|
|
84
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
85
|
+
isError: true,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
// ── Tool: get_report_with_conversation ─────────────────────────────
|
|
90
|
+
server.tool("get_report_with_conversation", "Get a report with its full triage conversation. Useful for understanding what questions triage asked, how you responded, and what led to resolution. Great for learning what works.", {
|
|
91
|
+
report_id: z.string().describe("The HackerOne report ID"),
|
|
92
|
+
}, async ({ report_id }) => {
|
|
93
|
+
try {
|
|
94
|
+
const summary = await getReportSummary(report_id);
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: "text",
|
|
99
|
+
text: JSON.stringify(summary, null, 2),
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
return {
|
|
106
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
107
|
+
isError: true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
// ── Tool: get_report_activities ────────────────────────────────────
|
|
112
|
+
server.tool("get_report_activities", "Get the activity timeline of a report: comments, state changes, bounty awards, and triage responses.", {
|
|
113
|
+
report_id: z.string().describe("The HackerOne report ID"),
|
|
114
|
+
page_size: z
|
|
115
|
+
.number()
|
|
116
|
+
.min(1)
|
|
117
|
+
.max(100)
|
|
118
|
+
.optional()
|
|
119
|
+
.describe("Number of activities to return (default 50)"),
|
|
120
|
+
}, async ({ report_id, page_size }) => {
|
|
121
|
+
try {
|
|
122
|
+
const activities = await getReportActivities(report_id, page_size);
|
|
123
|
+
return {
|
|
124
|
+
content: [
|
|
125
|
+
{
|
|
126
|
+
type: "text",
|
|
127
|
+
text: JSON.stringify(activities, null, 2),
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
return {
|
|
134
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
135
|
+
isError: true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
// ── Tool: list_programs ────────────────────────────────────────────
|
|
140
|
+
server.tool("list_programs", "List bug bounty programs you have access to on HackerOne. Follows all pages by default; page_size optionally limits the total results. Fails if pagination is malformed or incomplete.", {
|
|
141
|
+
page_size: z
|
|
142
|
+
.number()
|
|
143
|
+
.min(1)
|
|
144
|
+
.max(1000)
|
|
145
|
+
.optional()
|
|
146
|
+
.describe("Max programs to return (default: all)"),
|
|
147
|
+
}, async ({ page_size }) => {
|
|
148
|
+
try {
|
|
149
|
+
const programs = await listPrograms(page_size);
|
|
150
|
+
return {
|
|
151
|
+
content: [
|
|
152
|
+
{
|
|
153
|
+
type: "text",
|
|
154
|
+
text: JSON.stringify(programs, null, 2),
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
162
|
+
isError: true,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
// ── Tool: get_program_details ──────────────────────────────────────
|
|
167
|
+
server.tool("get_program_details", "Get detailed info about a single program: policy, response times, metrics, bounty splitting, and submission state. Unavailable metrics are null. Use get_program_signal_requirements for its minimum signal threshold.", {
|
|
168
|
+
program_handle: z
|
|
169
|
+
.string()
|
|
170
|
+
.describe("Program handle (e.g. 'uber', 'github')"),
|
|
171
|
+
}, async ({ program_handle }) => {
|
|
172
|
+
try {
|
|
173
|
+
const details = await getProgramDetails(program_handle);
|
|
174
|
+
return {
|
|
175
|
+
content: [
|
|
176
|
+
{
|
|
177
|
+
type: "text",
|
|
178
|
+
text: JSON.stringify(details, null, 2),
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
return {
|
|
185
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
186
|
+
isError: true,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
// ── Tool: get_program_signal_requirements ─────────────────────────
|
|
191
|
+
server.tool("get_program_signal_requirements", "Get a public program's minimum past-year signal requirement from HackerOne's website. Returns signal_requirement and signal_requirement_enabled: false means disabled; null means unavailable. Zero is a valid threshold. Does not determine your eligibility or remaining trial reports. Private programs are not supported.", {
|
|
192
|
+
program_handle: z.string().trim().min(1).describe("Public program handle (e.g. 'uber', 'security')"),
|
|
193
|
+
}, async ({ program_handle }) => {
|
|
194
|
+
try {
|
|
195
|
+
const requirements = await getProgramSignalRequirements(program_handle);
|
|
196
|
+
return {
|
|
197
|
+
content: [{ type: "text", text: JSON.stringify(requirements, null, 2) }],
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
return {
|
|
202
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
203
|
+
isError: true,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
// ── Tool: analyze_report_patterns ──────────────────────────────────
|
|
208
|
+
server.tool("analyze_report_patterns", "Fetch your recent reports and analyze patterns: most common vulnerability types, severity distribution, resolution rates, and programs. Useful for understanding your hunting profile.", {
|
|
209
|
+
page_size: z
|
|
210
|
+
.number()
|
|
211
|
+
.min(10)
|
|
212
|
+
.max(100)
|
|
213
|
+
.optional()
|
|
214
|
+
.describe("Number of reports to analyze (default 100)"),
|
|
215
|
+
}, async ({ page_size }) => {
|
|
216
|
+
try {
|
|
217
|
+
const reports = await searchReports({
|
|
218
|
+
page_size: page_size ?? 100,
|
|
219
|
+
sort: "-reports.created_at",
|
|
220
|
+
});
|
|
221
|
+
const severityCounts = {};
|
|
222
|
+
const stateCounts = {};
|
|
223
|
+
const programCounts = {};
|
|
224
|
+
const weaknessCounts = {};
|
|
225
|
+
for (const r of reports) {
|
|
226
|
+
severityCounts[r.severity ?? "unknown"] =
|
|
227
|
+
(severityCounts[r.severity ?? "unknown"] ?? 0) + 1;
|
|
228
|
+
stateCounts[r.state ?? "unknown"] =
|
|
229
|
+
(stateCounts[r.state ?? "unknown"] ?? 0) + 1;
|
|
230
|
+
if (r.program)
|
|
231
|
+
programCounts[r.program] = (programCounts[r.program] ?? 0) + 1;
|
|
232
|
+
if (r.weakness)
|
|
233
|
+
weaknessCounts[r.weakness] = (weaknessCounts[r.weakness] ?? 0) + 1;
|
|
234
|
+
}
|
|
235
|
+
const analysis = {
|
|
236
|
+
total_reports_analyzed: reports.length,
|
|
237
|
+
severity_distribution: severityCounts,
|
|
238
|
+
state_distribution: stateCounts,
|
|
239
|
+
top_programs: Object.entries(programCounts)
|
|
240
|
+
.sort(([, a], [, b]) => b - a)
|
|
241
|
+
.slice(0, 10)
|
|
242
|
+
.map(([prog, count]) => ({ program: prog, count })),
|
|
243
|
+
top_weakness_types: Object.entries(weaknessCounts)
|
|
244
|
+
.sort(([, a], [, b]) => b - a)
|
|
245
|
+
.slice(0, 10)
|
|
246
|
+
.map(([weakness, count]) => ({ weakness, count })),
|
|
247
|
+
};
|
|
248
|
+
return {
|
|
249
|
+
content: [
|
|
250
|
+
{
|
|
251
|
+
type: "text",
|
|
252
|
+
text: JSON.stringify(analysis, null, 2),
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
return {
|
|
259
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
260
|
+
isError: true,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
// ── Tool: get_program_scope ──────────────────────────────────────
|
|
265
|
+
server.tool("get_program_scope", "Get the scope assets for a bug bounty program. Follows all pages by default; page_size optionally limits the total results. Returns asset types, identifiers, submission and bounty eligibility, and severity caps. Pagination failures return an error.", {
|
|
266
|
+
program_handle: z
|
|
267
|
+
.string()
|
|
268
|
+
.describe("Program handle (e.g. 'uber', 'ipc-h1c-aws-tokyo-2026')"),
|
|
269
|
+
page_size: z
|
|
270
|
+
.number()
|
|
271
|
+
.min(1)
|
|
272
|
+
.max(1000)
|
|
273
|
+
.optional()
|
|
274
|
+
.describe("Max scope items to return (default: all)"),
|
|
275
|
+
}, async ({ program_handle, page_size }) => {
|
|
276
|
+
try {
|
|
277
|
+
const scope = await getProgramScope(program_handle, page_size);
|
|
278
|
+
return {
|
|
279
|
+
content: [
|
|
280
|
+
{
|
|
281
|
+
type: "text",
|
|
282
|
+
text: JSON.stringify(scope, null, 2),
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
return {
|
|
289
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
290
|
+
isError: true,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
// ── Tool: get_program_weaknesses ────────────────────────────────
|
|
295
|
+
server.tool("get_program_weaknesses", "Get the accepted vulnerability/weakness types for a program. Follows all pages by default; page_size optionally limits the total results. Pagination failures return an error. Helps select the program's accepted CWE categories.", {
|
|
296
|
+
program_handle: z
|
|
297
|
+
.string()
|
|
298
|
+
.describe("Program handle (e.g. 'uber', 'ipc-h1c-aws-tokyo-2026')"),
|
|
299
|
+
page_size: z
|
|
300
|
+
.number()
|
|
301
|
+
.min(1)
|
|
302
|
+
.max(1000)
|
|
303
|
+
.optional()
|
|
304
|
+
.describe("Max weaknesses to return (default: all)"),
|
|
305
|
+
}, async ({ program_handle, page_size }) => {
|
|
306
|
+
try {
|
|
307
|
+
const weaknesses = await getProgramWeaknesses(program_handle, page_size);
|
|
308
|
+
return {
|
|
309
|
+
content: [
|
|
310
|
+
{
|
|
311
|
+
type: "text",
|
|
312
|
+
text: JSON.stringify(weaknesses, null, 2),
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
return {
|
|
319
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
320
|
+
isError: true,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
// ── Tool: get_earnings ──────────────────────────────────────────
|
|
325
|
+
server.tool("get_earnings", "Get your bounty earnings history. Shows amounts, currency, dates, and which programs paid out.", {
|
|
326
|
+
page_size: z
|
|
327
|
+
.number()
|
|
328
|
+
.min(1)
|
|
329
|
+
.max(100)
|
|
330
|
+
.optional()
|
|
331
|
+
.describe("Number of earnings to return (default 100)"),
|
|
332
|
+
}, async ({ page_size }) => {
|
|
333
|
+
try {
|
|
334
|
+
const earnings = await getEarnings(page_size);
|
|
335
|
+
return {
|
|
336
|
+
content: [
|
|
337
|
+
{
|
|
338
|
+
type: "text",
|
|
339
|
+
text: JSON.stringify(earnings, null, 2),
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
catch (err) {
|
|
345
|
+
return {
|
|
346
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
347
|
+
isError: true,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
// ── Tool: get_hacker_profile ──────────────────────────────────────
|
|
352
|
+
server.tool("get_hacker_profile", "Get profile data from a report's reporter relationship only when its username matches H1_USERNAME. Missing fields are null. Returns source and unavailable_reason; no reports or no matching reporter means profile data is unavailable. An API identifier is not necessarily an account username.", {}, async () => {
|
|
353
|
+
try {
|
|
354
|
+
const profile = await getHackerProfile();
|
|
355
|
+
return {
|
|
356
|
+
content: [
|
|
357
|
+
{
|
|
358
|
+
type: "text",
|
|
359
|
+
text: JSON.stringify(profile, null, 2),
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
return {
|
|
366
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
367
|
+
isError: true,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
// ── Tool: get_balance ─────────────────────────────────────────────
|
|
372
|
+
server.tool("get_balance", "Get your current unpaid bounty balance on HackerOne.", {}, async () => {
|
|
373
|
+
try {
|
|
374
|
+
const balance = await getBalance();
|
|
375
|
+
return {
|
|
376
|
+
content: [
|
|
377
|
+
{
|
|
378
|
+
type: "text",
|
|
379
|
+
text: JSON.stringify(balance, null, 2),
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
catch (err) {
|
|
385
|
+
return {
|
|
386
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
387
|
+
isError: true,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
// ── Tool: submit_report ───────────────────────────────────────────
|
|
392
|
+
server.tool("submit_report", "Submit a new vulnerability report to a HackerOne program. Returns the new report ID and URL. Use get_program_scope and get_program_weaknesses first to get the right scope/weakness IDs.", {
|
|
393
|
+
program_handle: z
|
|
394
|
+
.string()
|
|
395
|
+
.describe("Program handle to submit to (e.g. 'uber')"),
|
|
396
|
+
title: z.string().describe("Report title"),
|
|
397
|
+
vulnerability_information: z
|
|
398
|
+
.string()
|
|
399
|
+
.describe("Full vulnerability details in markdown — steps to reproduce, root cause, and proof of concept"),
|
|
400
|
+
impact: z
|
|
401
|
+
.string()
|
|
402
|
+
.optional()
|
|
403
|
+
.describe("Impact statement — what an attacker can achieve"),
|
|
404
|
+
severity_rating: z
|
|
405
|
+
.enum(["none", "low", "medium", "high", "critical"])
|
|
406
|
+
.optional()
|
|
407
|
+
.describe("Suggested severity rating"),
|
|
408
|
+
weakness_id: z
|
|
409
|
+
.string()
|
|
410
|
+
.optional()
|
|
411
|
+
.describe("Weakness/CWE ID from get_program_weaknesses (the numeric id field)"),
|
|
412
|
+
structured_scope_id: z
|
|
413
|
+
.string()
|
|
414
|
+
.optional()
|
|
415
|
+
.describe("Scope asset ID from get_program_scope (the numeric id field)"),
|
|
416
|
+
}, async (params) => {
|
|
417
|
+
try {
|
|
418
|
+
const result = await submitReport(params);
|
|
419
|
+
return {
|
|
420
|
+
content: [
|
|
421
|
+
{
|
|
422
|
+
type: "text",
|
|
423
|
+
text: JSON.stringify(result, null, 2),
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
catch (err) {
|
|
429
|
+
return {
|
|
430
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
431
|
+
isError: true,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
// ── Tool: add_comment ─────────────────────────────────────────────
|
|
436
|
+
server.tool("add_comment", "Add a comment to an existing HackerOne report. Use this to respond to triage questions or provide additional information.", {
|
|
437
|
+
report_id: z.string().describe("The HackerOne report ID"),
|
|
438
|
+
message: z.string().describe("Comment text (supports markdown)"),
|
|
439
|
+
internal: z
|
|
440
|
+
.boolean()
|
|
441
|
+
.optional()
|
|
442
|
+
.describe("If true, comment is only visible to the team (default false)"),
|
|
443
|
+
}, async ({ report_id, message, internal }) => {
|
|
444
|
+
try {
|
|
445
|
+
const result = await addComment(report_id, message, internal ?? false);
|
|
446
|
+
return {
|
|
447
|
+
content: [
|
|
448
|
+
{
|
|
449
|
+
type: "text",
|
|
450
|
+
text: JSON.stringify(result, null, 2),
|
|
451
|
+
},
|
|
452
|
+
],
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
catch (err) {
|
|
456
|
+
return {
|
|
457
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
458
|
+
isError: true,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
// ── Tool: close_report ────────────────────────────────────────────
|
|
463
|
+
server.tool("close_report", "Withdraw/close one of your own HackerOne reports. Sends a close request with an optional message.", {
|
|
464
|
+
report_id: z.string().describe("The HackerOne report ID to close"),
|
|
465
|
+
message: z
|
|
466
|
+
.string()
|
|
467
|
+
.optional()
|
|
468
|
+
.describe("Reason for closing (default: 'Withdrawing this report.')"),
|
|
469
|
+
}, async ({ report_id, message }) => {
|
|
470
|
+
try {
|
|
471
|
+
const result = await closeReport(report_id, message);
|
|
472
|
+
return {
|
|
473
|
+
content: [
|
|
474
|
+
{
|
|
475
|
+
type: "text",
|
|
476
|
+
text: JSON.stringify(result, null, 2),
|
|
477
|
+
},
|
|
478
|
+
],
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
catch (err) {
|
|
482
|
+
return {
|
|
483
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
484
|
+
isError: true,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
// ── Tool: search_disclosed_reports ────────────────────────────────
|
|
489
|
+
server.tool("search_disclosed_reports", "Search publicly disclosed HackerOne reports (hacktivity). Useful for learning what gets paid, finding prior art, and understanding what a program considers valid.", {
|
|
490
|
+
program: z
|
|
491
|
+
.string()
|
|
492
|
+
.optional()
|
|
493
|
+
.describe("Program handle to filter by (e.g. 'uber')"),
|
|
494
|
+
query: z
|
|
495
|
+
.string()
|
|
496
|
+
.optional()
|
|
497
|
+
.describe("Keyword to filter results (e.g. 'SSRF', 'IDOR')"),
|
|
498
|
+
page_size: z
|
|
499
|
+
.number()
|
|
500
|
+
.min(1)
|
|
501
|
+
.max(100)
|
|
502
|
+
.optional()
|
|
503
|
+
.describe("Number of results (default 25)"),
|
|
504
|
+
}, async (params) => {
|
|
505
|
+
try {
|
|
506
|
+
const results = await searchDisclosedReports(params);
|
|
507
|
+
return {
|
|
508
|
+
content: [
|
|
509
|
+
{
|
|
510
|
+
type: "text",
|
|
511
|
+
text: JSON.stringify(results, null, 2),
|
|
512
|
+
},
|
|
513
|
+
],
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
catch (err) {
|
|
517
|
+
return {
|
|
518
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
519
|
+
isError: true,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
// ── Start server ───────────────────────────────────────────────────
|
|
524
|
+
async function main() {
|
|
525
|
+
const transport = new StdioServerTransport();
|
|
526
|
+
await server.connect(transport);
|
|
527
|
+
console.error("HackerOne MCP server running on stdio");
|
|
528
|
+
}
|
|
529
|
+
main().catch((err) => {
|
|
530
|
+
console.error("Fatal error:", err);
|
|
531
|
+
process.exit(1);
|
|
532
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hackerone-mcp-server",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for accessing HackerOne reports in Claude Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"hackerone-mcp-server": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"prepack": "npm run build",
|
|
16
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
17
|
+
"start": "node dist/index.js",
|
|
18
|
+
"dev": "tsx src/index.ts"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
22
|
+
"node-fetch": "^3.3.2",
|
|
23
|
+
"zod": "^3.22.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^20.0.0",
|
|
27
|
+
"tsx": "^4.7.0",
|
|
28
|
+
"typescript": "^5.3.0"
|
|
29
|
+
}
|
|
30
|
+
}
|