bug-bounty-intelligence-mcp 1.0.5 → 1.2.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/BENCHMARK.md +174 -174
- package/LICENSE +21 -21
- package/METHODOLOGY.md +141 -94
- package/README.md +266 -201
- package/index.mjs +289 -284
- package/package.json +43 -40
- package/vulnerability-acceptance-rates.json +92 -97
package/index.mjs
CHANGED
|
@@ -1,284 +1,289 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Bug Bounty Intelligence — MCP Server
|
|
4
|
-
*
|
|
5
|
-
* Tools:
|
|
6
|
-
* scan_contract(repo_url, protocol_type?) → submit for analysis, returns job_id
|
|
7
|
-
* get_scan_report(job_id) → poll status + get report URL
|
|
8
|
-
* list_vulnerability_patterns(protocol?) → show historical patterns from 27K findings
|
|
9
|
-
*
|
|
10
|
-
* Payment: $5 USDC on Base (eip155:8453) via x402.
|
|
11
|
-
* If you receive a payment_required response, pay to the address shown and retry.
|
|
12
|
-
*
|
|
13
|
-
* Run: node index.mjs (or: npx -y bug-bounty-intelligence-mcp@latest)
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
|
17
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
18
|
-
import {
|
|
19
|
-
CallToolRequestSchema,
|
|
20
|
-
ListToolsRequestSchema,
|
|
21
|
-
} from '@modelcontextprotocol/sdk/types.js'
|
|
22
|
-
const API_URL = 'https://x402.tokenizen.nl'
|
|
23
|
-
|
|
24
|
-
// Embedded pattern library — Sherlock audit-competition findings, updated 2026-
|
|
25
|
-
// Methodology: only contests where
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
server
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
'
|
|
102
|
-
'
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
description: '
|
|
115
|
-
},
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
'',
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Bug Bounty Intelligence — MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Tools:
|
|
6
|
+
* scan_contract(repo_url, protocol_type?) → submit for analysis, returns job_id
|
|
7
|
+
* get_scan_report(job_id) → poll status + get report URL
|
|
8
|
+
* list_vulnerability_patterns(protocol?) → show historical patterns from 27K findings
|
|
9
|
+
*
|
|
10
|
+
* Payment: $5 USDC on Base (eip155:8453) via x402.
|
|
11
|
+
* If you receive a payment_required response, pay to the address shown and retry.
|
|
12
|
+
*
|
|
13
|
+
* Run: node index.mjs (or: npx -y bug-bounty-intelligence-mcp@latest)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
|
17
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
18
|
+
import {
|
|
19
|
+
CallToolRequestSchema,
|
|
20
|
+
ListToolsRequestSchema,
|
|
21
|
+
} from '@modelcontextprotocol/sdk/types.js'
|
|
22
|
+
const API_URL = 'https://x402.tokenizen.nl'
|
|
23
|
+
|
|
24
|
+
// Embedded pattern library — Sherlock + Code4rena audit-competition findings, updated 2026-11-12.
|
|
25
|
+
// Methodology: only contests where the judging repo's own result structure could be exactly
|
|
26
|
+
// reconciled (accepted + invalid === total submissions) are included — 35 Sherlock contests
|
|
27
|
+
// (7,388 findings) plus 3 Code4rena contests (1,812 findings) that happen to reconcile under
|
|
28
|
+
// the same zero-tolerance rule, 38 contests, 9,200 findings total. Code4rena contributes far
|
|
29
|
+
// less than Sherlock because most of its history does not use a consistently-applied
|
|
30
|
+
// satisfactory/unsatisfactory label (verified by sampling, not assumed), so those contests are
|
|
31
|
+
// excluded rather than estimated, exactly like the excluded Sherlock contests. See
|
|
32
|
+
// METHODOLOGY.md for the full methodology note, including the 2026-11-12 re-verification that
|
|
33
|
+
// replaced an unreliable local cache.
|
|
34
|
+
const PATTERN_LIBRARY = {
|
|
35
|
+
'flash-loan': { acceptance_rate: '0.66', total: 193, accepted: 127, examples_accepted: ['duc - Borrower can reduce the interest of shortly loans', 'ShadowForce - Bypass the VAULT_ACCOUNT_MIN_TIME check in VaultAccountAction.sol when exiting the vau', 'mstpr-brainbot - Compound exchange rate can be manipulated to withdraw more underlying tokens from N'] },
|
|
36
|
+
'liquidation': { acceptance_rate: '0.66', total: 877, accepted: 576, examples_accepted: ['w42d3n - The protocol is not protected against Signature malleability', 'gryphon - Buyer may not be able to liquidate the loan', 'peanuts - NFTs are not stored in the appropriate contracts'] },
|
|
37
|
+
'fee-miscalculation': { acceptance_rate: '0.54', total: 583, accepted: 313, examples_accepted: ['7siech - Total loss of funds when using certain popular ERC20 tokens', 'iglyx - MigratePrimeCash\'s _calculateInterestRateCurves calculates new kinkRate1 and kinkRate2 incor', 'xiaoming90 - Prime vaults do not consider secondary debt when assessing vault fees'] },
|
|
38
|
+
'oracle-manipulation':{ acceptance_rate: '0.53', total: 949, accepted: 502, examples_accepted: ['iglyx - TokenHandler\'s _redeemMoneyMarketIfRequired can fail valid withdrawal requests', 'xiaoming90 - Underlying delta is calculated on internal token balance', 'xiaoming90 - Rebalance stops working when more holdings are added'] },
|
|
39
|
+
'staleness': { acceptance_rate: '0.51', total: 1315, accepted: 675, examples_accepted: ['w42d3n - The protocol is not protected against Signature malleability', 'peanuts - Some NFTs like CryptoKitty and CryptoFighter can be paused, which block repaying/liquidati', 'csanuragjain - Liquidation occur at incorrect time'] },
|
|
40
|
+
'mev-slippage': { acceptance_rate: '0.47', total: 379, accepted: 179, examples_accepted: ['xiaoming90 - Liquidation discount does not take slippage into consideration', 'xiaoming90 - Inadequate slippage control', 'ShadowForce - Bypass the VAULT_ACCOUNT_MIN_TIME check in VaultAccountAction.sol when exiting the vau'] },
|
|
41
|
+
'trusted-actor': { acceptance_rate: '0.47', total: 2127, accepted: 1000, examples_accepted: ['gryphon - Buyer may not be able to liquidate the loan', 'peanuts - Some NFTs like CryptoKitty and CryptoFighter can be paused, which block repaying/liquidati', 'Go-langer - Init function left open for anyone to call and initialize the protocol.'] },
|
|
42
|
+
'dos-griefing': { acceptance_rate: '0.47', total: 816, accepted: 383, examples_accepted: ['w42d3n - The protocol is not protected against Signature malleability', 'duc - Borrower can reduce the interest of shortly loans', '0xd1r4cde17a - lender gets minInterestsToRepay / loan.nbOfPositions'] },
|
|
43
|
+
'overflow': { acceptance_rate: '0.43', total: 385, accepted: 167, examples_accepted: ['peanuts - Some NFTs like CryptoKitty and CryptoFighter can be paused, which block repaying/liquidati', 'Robert - checkedTransfer In claim() and claimAsBorrower() Functions Can Permanently Lock Tokens', 'Inspex - Denial-of-Service in the liquidation flow results in the collateral NTF will be stuck in th'] },
|
|
44
|
+
'access-control': { acceptance_rate: '0.42', total: 846, accepted: 356, examples_accepted: ['Go-langer - Init function left open for anyone to call and initialize the protocol.', 'Englave - Signature Replay Attack on OfferArgs', 'xiaoming90 - It may be possible to liquidate on behalf of another account'] },
|
|
45
|
+
'rounding': { acceptance_rate: '0.39', total: 532, accepted: 205, examples_accepted: ['usmannk - Borrowers may steal funds from lenders', 'jekapi - Borrower can lock lender assets if offer has large LTV', 'iglyx - MigratePrimeCash\'s _calculateInterestRateCurves calculates new kinkRate1 and kinkRate2 incor'] },
|
|
46
|
+
'reentrancy': { acceptance_rate: '0.34', total: 332, accepted: 114, examples_accepted: ['SPYBOY - A malicious signer can exploite reentrancy while Borrowing', 'glcanvas - Possible reentrancy which allows to write incorrect supplyPositionIndex', 'Robert - A Loan\'s supplyPositionIndex Can Be Duplicated Through Re-entrancy'] },
|
|
47
|
+
'idor': { acceptance_rate: '0.26', total: 35, accepted: 9, examples_accepted: ['0Kage - A borrower with multiple loans of which one is liquidated can withdraw much more than his sh', '0xbepresent - Claimable amount could be sent to the approved address instead the provision/position', 'GimelSec - ClaimFacet.sendShareOfSaleAsSupplier() calculates unfair shares, the money should compe'] },
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
async function callApi(path, method = 'GET', body = null, extraHeaders = {}) {
|
|
53
|
+
const opts = {
|
|
54
|
+
method,
|
|
55
|
+
headers: { 'Content-Type': 'application/json', ...extraHeaders },
|
|
56
|
+
signal: AbortSignal.timeout(30_000),
|
|
57
|
+
}
|
|
58
|
+
if (body) opts.body = JSON.stringify(body)
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch(`${API_URL}${path}`, opts)
|
|
61
|
+
const text = await res.text()
|
|
62
|
+
let json
|
|
63
|
+
try { json = JSON.parse(text) } catch { json = { raw: text } }
|
|
64
|
+
return { status: res.status, ok: res.ok, json }
|
|
65
|
+
} catch (err) {
|
|
66
|
+
const reason = err.name === 'TimeoutError' || err.name === 'AbortError' ? 'timed out after 30s' : err.message
|
|
67
|
+
return { status: 0, ok: false, json: { error: `Could not reach ${API_URL} (${reason})` } }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function ilmPatterns(protocolType) {
|
|
72
|
+
const TYPE = {
|
|
73
|
+
DEX: ['oracle-manipulation','mev-slippage','rounding','flash-loan','fee-miscalculation','reentrancy'],
|
|
74
|
+
LENDING: ['oracle-manipulation','liquidation','staleness','rounding','flash-loan','access-control'],
|
|
75
|
+
BRIDGE: ['access-control','reentrancy','overflow','dos-griefing'],
|
|
76
|
+
GOVERNANCE: ['access-control','dos-griefing','trusted-actor'],
|
|
77
|
+
STAKING: ['rounding','fee-miscalculation','access-control','overflow','staleness'],
|
|
78
|
+
GENERAL: ['oracle-manipulation','rounding','access-control','fee-miscalculation','reentrancy','flash-loan'],
|
|
79
|
+
}
|
|
80
|
+
const keys = TYPE[protocolType?.toUpperCase()] ?? TYPE.GENERAL
|
|
81
|
+
return keys.filter(k => PATTERN_LIBRARY[k]).map(k => ({
|
|
82
|
+
pattern: k,
|
|
83
|
+
acceptance_rate: PATTERN_LIBRARY[k].acceptance_rate,
|
|
84
|
+
total_cases: PATTERN_LIBRARY[k].total,
|
|
85
|
+
example: (PATTERN_LIBRARY[k].examples_accepted ?? [])[0]?.slice(0, 120),
|
|
86
|
+
}))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── MCP server ────────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
const server = new Server(
|
|
92
|
+
{ name: 'bug-bounty-intelligence', version: '1.0.0' },
|
|
93
|
+
{ capabilities: { tools: {} } }
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
97
|
+
tools: [
|
|
98
|
+
{
|
|
99
|
+
name: 'scan_contract',
|
|
100
|
+
description: [
|
|
101
|
+
'Submit a public GitHub repository for an automated Solidity analysis. The result is a list of AI-generated hypotheses to review by hand. It is not an audit and not a list of confirmed bugs.',
|
|
102
|
+
'The scan reads the files most likely to move funds first, in at most six parts. Tests, mocks, scripts, interfaces and vendored dependencies are left out. The report states what share of the in-scope code was actually read, and a report can say that no hypotheses came out of the part that was read.',
|
|
103
|
+
'Check the repository first, for free: GET https://x402.tokenizen.nl/api/bug-intel/precheck?repo=owner/name says whether the scan counts any Solidity in it and roughly how much of it will be read.',
|
|
104
|
+
'How often the scan finds known issues on closed audit contests, including the misses, is published in the README of the repository (github.com/holistis/bug-bounty-intelligence-mcp). The prompt uses acceptance-rate hints from the 9,200 findings that could be exact-reconciled against contest outcomes, see METHODOLOGY.md.',
|
|
105
|
+
'Cost: $5 USDC on Base (eip155:8453) via x402. Reports usually arrive within the hour. If the scan fails or the report does not help, write to info@holistischadviseur.nl for a new attempt or a refund, see the terms.',
|
|
106
|
+
'Returns a job_id. Use get_scan_report to poll for results.',
|
|
107
|
+
'If payment_required is true, pay $5 USDC to the payTo address on Base, then retry scan_contract with the same repo_url.',
|
|
108
|
+
].join(' '),
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
properties: {
|
|
112
|
+
repo_url: {
|
|
113
|
+
type: 'string',
|
|
114
|
+
description: 'Public GitHub repo URL containing Solidity contracts (e.g. https://github.com/org/repo)',
|
|
115
|
+
},
|
|
116
|
+
protocol_type: {
|
|
117
|
+
type: 'string',
|
|
118
|
+
enum: ['DEX', 'LENDING', 'BRIDGE', 'GOVERNANCE', 'STAKING', 'DERIVATIVES', 'GENERAL'],
|
|
119
|
+
description: 'Protocol category for targeted analysis (optional, default GENERAL)',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
required: ['repo_url'],
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'get_scan_report',
|
|
127
|
+
description: [
|
|
128
|
+
'Poll the status of a job_id returned by scan_contract.',
|
|
129
|
+
'Call this every few minutes after submitting. Reports usually arrive within the hour; polling faster just re-checks a job that is still processing.',
|
|
130
|
+
'Returns status (queued/processing/complete/failed), the repo under scan, and once complete the reportUrl (the report of hypotheses, with the share of code that was read), the outcome and the coverage. A failed job says why and where to write for a new attempt or a refund.',
|
|
131
|
+
'A 404 means the job_id does not exist — double-check what scan_contract actually returned rather than retrying blindly.',
|
|
132
|
+
].join(' '),
|
|
133
|
+
inputSchema: {
|
|
134
|
+
type: 'object',
|
|
135
|
+
properties: {
|
|
136
|
+
job_id: { type: 'string', description: 'The job_id returned by scan_contract' },
|
|
137
|
+
},
|
|
138
|
+
required: ['job_id'],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'list_vulnerability_patterns',
|
|
143
|
+
description: [
|
|
144
|
+
'Returns historical vulnerability patterns and acceptance rates from Sherlock and Code4rena audit-competition findings.',
|
|
145
|
+
'Numbers are limited to contests where results could be exactly reconciled against the platform\'s own published outcomes (9,200 findings across 38 contests: 35 Sherlock, 3 Code4rena), no estimated or unverifiable figures.',
|
|
146
|
+
'Useful for understanding what types of bugs are most likely to be valid in a given protocol type.',
|
|
147
|
+
'This tool is FREE — no payment required.',
|
|
148
|
+
].join(' '),
|
|
149
|
+
inputSchema: {
|
|
150
|
+
type: 'object',
|
|
151
|
+
properties: {
|
|
152
|
+
protocol_type: {
|
|
153
|
+
type: 'string',
|
|
154
|
+
enum: ['DEX', 'LENDING', 'BRIDGE', 'GOVERNANCE', 'STAKING', 'DERIVATIVES', 'GENERAL'],
|
|
155
|
+
description: 'Protocol type to filter patterns (default GENERAL)',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
}))
|
|
162
|
+
|
|
163
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
164
|
+
const { name, arguments: args } = req.params
|
|
165
|
+
|
|
166
|
+
// ── scan_contract ──────────────────────────────────────────────────────────
|
|
167
|
+
if (name === 'scan_contract') {
|
|
168
|
+
const { repo_url, protocol_type = 'GENERAL' } = args ?? {}
|
|
169
|
+
if (!repo_url) {
|
|
170
|
+
return { content: [{ type: 'text', text: 'Error: repo_url is required' }], isError: true }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const { status, json } = await callApi('/api/bug-intel', 'POST', { repo: repo_url, protocolType: protocol_type })
|
|
174
|
+
|
|
175
|
+
if (status === 402) {
|
|
176
|
+
const accept = json.accepts?.[0] ?? {}
|
|
177
|
+
const amtUsdc = accept.amount ? (parseInt(accept.amount) / 1_000_000).toFixed(2) : '5.00'
|
|
178
|
+
if (!accept.payTo || !accept.asset) {
|
|
179
|
+
// Never fall back to a hardcoded address here: a stale one silently
|
|
180
|
+
// sends real USDC to a wallet nobody is watching. Fail loud instead.
|
|
181
|
+
return {
|
|
182
|
+
content: [{
|
|
183
|
+
type: 'text',
|
|
184
|
+
text: 'PAYMENT REQUIRED, but the server did not return a payment address. Do not guess one. Retry scan_contract; if this repeats, the payment endpoint is broken, not the address.',
|
|
185
|
+
}],
|
|
186
|
+
isError: true,
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
content: [{
|
|
191
|
+
type: 'text',
|
|
192
|
+
text: [
|
|
193
|
+
'PAYMENT REQUIRED to start the scan.',
|
|
194
|
+
'',
|
|
195
|
+
`Amount: ${amtUsdc} USDC`,
|
|
196
|
+
`Network: Base mainnet (eip155:8453)`,
|
|
197
|
+
`Pay to: ${accept.payTo}`,
|
|
198
|
+
`Asset: USDC — ${accept.asset}`,
|
|
199
|
+
'',
|
|
200
|
+
'After paying, retry scan_contract with the same repo_url.',
|
|
201
|
+
'Terms: https://x402.tokenizen.nl/terms',
|
|
202
|
+
].join('\n'),
|
|
203
|
+
}],
|
|
204
|
+
isError: false,
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (status === 200 || status === 201) {
|
|
209
|
+
return {
|
|
210
|
+
content: [{
|
|
211
|
+
type: 'text',
|
|
212
|
+
text: [
|
|
213
|
+
`Scan submitted. Job ID: ${json.jobId}`,
|
|
214
|
+
`Status: ${json.status ?? 'queued'}`,
|
|
215
|
+
'',
|
|
216
|
+
`Poll for results: get_scan_report({ job_id: "${json.jobId}" })`,
|
|
217
|
+
`Reports usually arrive within the hour. The report states how much of the code was read.`,
|
|
218
|
+
].join('\n'),
|
|
219
|
+
}],
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
content: [{ type: 'text', text: `API error ${status}: ${JSON.stringify(json).slice(0, 300)}` }],
|
|
225
|
+
isError: true,
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ── get_scan_report ────────────────────────────────────────────────────────
|
|
230
|
+
if (name === 'get_scan_report') {
|
|
231
|
+
const { job_id } = args ?? {}
|
|
232
|
+
if (!job_id) {
|
|
233
|
+
return { content: [{ type: 'text', text: 'Error: job_id is required' }], isError: true }
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const { status, ok, json } = await callApi(`/api/bug-intel/${encodeURIComponent(job_id)}`)
|
|
237
|
+
|
|
238
|
+
if (status === 404) {
|
|
239
|
+
return { content: [{ type: 'text', text: `Job ${job_id} not found. Check the job_id.` }], isError: true }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!ok || !json.status) {
|
|
243
|
+
return {
|
|
244
|
+
content: [{ type: 'text', text: `Could not get status for job ${job_id}: ${json.error ?? `API error ${status}`}. Try again shortly.` }],
|
|
245
|
+
isError: true,
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const lines = [`Job ID: ${job_id}`, `Status: ${json.status}`]
|
|
250
|
+
if (json.repo) lines.push(`Repo: ${json.repo}`)
|
|
251
|
+
if (json.reportUrl) lines.push(`Report: ${json.reportUrl}`)
|
|
252
|
+
if (json.findingsCount !== undefined) lines.push(`Findings: ${json.findingsCount}`)
|
|
253
|
+
if (json.status === 'queued' || json.status === 'processing') {
|
|
254
|
+
lines.push('', 'Analysis in progress. Try again in a few minutes.')
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ── list_vulnerability_patterns ───────────────────────────────────────────
|
|
261
|
+
if (name === 'list_vulnerability_patterns') {
|
|
262
|
+
const { protocol_type = 'GENERAL' } = args ?? {}
|
|
263
|
+
const patterns = ilmPatterns(protocol_type)
|
|
264
|
+
|
|
265
|
+
if (!patterns || patterns.length === 0) {
|
|
266
|
+
return {
|
|
267
|
+
content: [{ type: 'text', text: 'No patterns found for this protocol type. Try GENERAL.' }],
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const lines = [`Vulnerability patterns for ${protocol_type} protocols (from Sherlock and Code4rena audit findings, exact-reconciled contests only):`, '']
|
|
272
|
+
for (const p of patterns) {
|
|
273
|
+
const pct = Math.round(parseFloat(p.acceptance_rate) * 100)
|
|
274
|
+
lines.push(`${p.pattern.padEnd(24)} ${pct}% accepted (${p.total_cases} cases)`)
|
|
275
|
+
if (p.example) lines.push(` Example: ${p.example}`)
|
|
276
|
+
}
|
|
277
|
+
lines.push('', 'Run scan_contract to check your contracts against these patterns.')
|
|
278
|
+
|
|
279
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true }
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
// ── start ─────────────────────────────────────────────────────────────────────
|
|
286
|
+
|
|
287
|
+
const transport = new StdioServerTransport()
|
|
288
|
+
await server.connect(transport)
|
|
289
|
+
// server runs until process exits
|