vaspera 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (712) hide show
  1. package/CHANGELOG.md +184 -0
  2. package/LICENSE +21 -0
  3. package/README.md +809 -0
  4. package/dist/__tests__/integration/certification-flow.test.d.ts +5 -0
  5. package/dist/__tests__/integration/certification-flow.test.d.ts.map +1 -0
  6. package/dist/__tests__/integration/certification-flow.test.js +245 -0
  7. package/dist/__tests__/integration/certification-flow.test.js.map +1 -0
  8. package/dist/__tests__/integration/commands.test.d.ts +5 -0
  9. package/dist/__tests__/integration/commands.test.d.ts.map +1 -0
  10. package/dist/__tests__/integration/commands.test.js +93 -0
  11. package/dist/__tests__/integration/commands.test.js.map +1 -0
  12. package/dist/action/diff-mode.d.ts +34 -0
  13. package/dist/action/diff-mode.d.ts.map +1 -0
  14. package/dist/action/diff-mode.js +201 -0
  15. package/dist/action/diff-mode.js.map +1 -0
  16. package/dist/action/diff-mode.test.d.ts +5 -0
  17. package/dist/action/diff-mode.test.d.ts.map +1 -0
  18. package/dist/action/diff-mode.test.js +162 -0
  19. package/dist/action/diff-mode.test.js.map +1 -0
  20. package/dist/action/index.d.ts +10 -0
  21. package/dist/action/index.d.ts.map +1 -0
  22. package/dist/action/index.js +231 -0
  23. package/dist/action/index.js.map +1 -0
  24. package/dist/action/pr-comment.d.ts +30 -0
  25. package/dist/action/pr-comment.d.ts.map +1 -0
  26. package/dist/action/pr-comment.js +301 -0
  27. package/dist/action/pr-comment.js.map +1 -0
  28. package/dist/action/pr-comment.test.d.ts +5 -0
  29. package/dist/action/pr-comment.test.d.ts.map +1 -0
  30. package/dist/action/pr-comment.test.js +189 -0
  31. package/dist/action/pr-comment.test.js.map +1 -0
  32. package/dist/action/sarif-upload.d.ts +104 -0
  33. package/dist/action/sarif-upload.d.ts.map +1 -0
  34. package/dist/action/sarif-upload.js +188 -0
  35. package/dist/action/sarif-upload.js.map +1 -0
  36. package/dist/action/sarif-upload.test.d.ts +5 -0
  37. package/dist/action/sarif-upload.test.d.ts.map +1 -0
  38. package/dist/action/sarif-upload.test.js +206 -0
  39. package/dist/action/sarif-upload.test.js.map +1 -0
  40. package/dist/action/types.d.ts +104 -0
  41. package/dist/action/types.d.ts.map +1 -0
  42. package/dist/action/types.js +33 -0
  43. package/dist/action/types.js.map +1 -0
  44. package/dist/action/types.test.d.ts +5 -0
  45. package/dist/action/types.test.d.ts.map +1 -0
  46. package/dist/action/types.test.js +79 -0
  47. package/dist/action/types.test.js.map +1 -0
  48. package/dist/agents/agent-integrity.d.ts +111 -0
  49. package/dist/agents/agent-integrity.d.ts.map +1 -0
  50. package/dist/agents/agent-integrity.js +308 -0
  51. package/dist/agents/agent-integrity.js.map +1 -0
  52. package/dist/agents/agent-privacy.d.ts +68 -0
  53. package/dist/agents/agent-privacy.d.ts.map +1 -0
  54. package/dist/agents/agent-privacy.js +345 -0
  55. package/dist/agents/agent-privacy.js.map +1 -0
  56. package/dist/agents/exploit-chain.d.ts +64 -0
  57. package/dist/agents/exploit-chain.d.ts.map +1 -0
  58. package/dist/agents/exploit-chain.js +477 -0
  59. package/dist/agents/exploit-chain.js.map +1 -0
  60. package/dist/agents/exploit-chain.test.d.ts +5 -0
  61. package/dist/agents/exploit-chain.test.d.ts.map +1 -0
  62. package/dist/agents/exploit-chain.test.js +455 -0
  63. package/dist/agents/exploit-chain.test.js.map +1 -0
  64. package/dist/agents/index.d.ts +14 -0
  65. package/dist/agents/index.d.ts.map +1 -0
  66. package/dist/agents/index.js +19 -0
  67. package/dist/agents/index.js.map +1 -0
  68. package/dist/agents/logic-flaw-detector.d.ts +55 -0
  69. package/dist/agents/logic-flaw-detector.d.ts.map +1 -0
  70. package/dist/agents/logic-flaw-detector.js +454 -0
  71. package/dist/agents/logic-flaw-detector.js.map +1 -0
  72. package/dist/agents/zero-day-hunter.d.ts +69 -0
  73. package/dist/agents/zero-day-hunter.d.ts.map +1 -0
  74. package/dist/agents/zero-day-hunter.js +591 -0
  75. package/dist/agents/zero-day-hunter.js.map +1 -0
  76. package/dist/certification/artifacts.d.ts +21 -0
  77. package/dist/certification/artifacts.d.ts.map +1 -0
  78. package/dist/certification/artifacts.js +275 -0
  79. package/dist/certification/artifacts.js.map +1 -0
  80. package/dist/certification/autofix.d.ts +122 -0
  81. package/dist/certification/autofix.d.ts.map +1 -0
  82. package/dist/certification/autofix.js +476 -0
  83. package/dist/certification/autofix.js.map +1 -0
  84. package/dist/certification/badge.d.ts +56 -0
  85. package/dist/certification/badge.d.ts.map +1 -0
  86. package/dist/certification/badge.js +155 -0
  87. package/dist/certification/badge.js.map +1 -0
  88. package/dist/certification/cache.d.ts +121 -0
  89. package/dist/certification/cache.d.ts.map +1 -0
  90. package/dist/certification/cache.js +275 -0
  91. package/dist/certification/cache.js.map +1 -0
  92. package/dist/certification/cache.test.d.ts +5 -0
  93. package/dist/certification/cache.test.d.ts.map +1 -0
  94. package/dist/certification/cache.test.js +270 -0
  95. package/dist/certification/cache.test.js.map +1 -0
  96. package/dist/certification/consensus.d.ts +105 -0
  97. package/dist/certification/consensus.d.ts.map +1 -0
  98. package/dist/certification/consensus.js +353 -0
  99. package/dist/certification/consensus.js.map +1 -0
  100. package/dist/certification/consensus.test.d.ts +5 -0
  101. package/dist/certification/consensus.test.d.ts.map +1 -0
  102. package/dist/certification/consensus.test.js +342 -0
  103. package/dist/certification/consensus.test.js.map +1 -0
  104. package/dist/certification/index.d.ts +14 -0
  105. package/dist/certification/index.d.ts.map +1 -0
  106. package/dist/certification/index.js +14 -0
  107. package/dist/certification/index.js.map +1 -0
  108. package/dist/certification/rules.d.ts +89 -0
  109. package/dist/certification/rules.d.ts.map +1 -0
  110. package/dist/certification/rules.js +317 -0
  111. package/dist/certification/rules.js.map +1 -0
  112. package/dist/certification/sarif.d.ts +107 -0
  113. package/dist/certification/sarif.d.ts.map +1 -0
  114. package/dist/certification/sarif.js +191 -0
  115. package/dist/certification/sarif.js.map +1 -0
  116. package/dist/certification/store.d.ts +255 -0
  117. package/dist/certification/store.d.ts.map +1 -0
  118. package/dist/certification/store.js +835 -0
  119. package/dist/certification/store.js.map +1 -0
  120. package/dist/certification/store.test.d.ts +5 -0
  121. package/dist/certification/store.test.d.ts.map +1 -0
  122. package/dist/certification/store.test.js +468 -0
  123. package/dist/certification/store.test.js.map +1 -0
  124. package/dist/certification/summary.d.ts +72 -0
  125. package/dist/certification/summary.d.ts.map +1 -0
  126. package/dist/certification/summary.js +296 -0
  127. package/dist/certification/summary.js.map +1 -0
  128. package/dist/certification/types.d.ts +138 -0
  129. package/dist/certification/types.d.ts.map +1 -0
  130. package/dist/certification/types.js +34 -0
  131. package/dist/certification/types.js.map +1 -0
  132. package/dist/commands/audits/api-check.d.ts +3 -0
  133. package/dist/commands/audits/api-check.d.ts.map +1 -0
  134. package/dist/commands/audits/api-check.js +71 -0
  135. package/dist/commands/audits/api-check.js.map +1 -0
  136. package/dist/commands/audits/deadcode.d.ts +3 -0
  137. package/dist/commands/audits/deadcode.d.ts.map +1 -0
  138. package/dist/commands/audits/deadcode.js +63 -0
  139. package/dist/commands/audits/deadcode.js.map +1 -0
  140. package/dist/commands/audits/deps.d.ts +3 -0
  141. package/dist/commands/audits/deps.d.ts.map +1 -0
  142. package/dist/commands/audits/deps.js +56 -0
  143. package/dist/commands/audits/deps.js.map +1 -0
  144. package/dist/commands/audits/errors.d.ts +3 -0
  145. package/dist/commands/audits/errors.d.ts.map +1 -0
  146. package/dist/commands/audits/errors.js +65 -0
  147. package/dist/commands/audits/errors.js.map +1 -0
  148. package/dist/commands/audits/index.d.ts +3 -0
  149. package/dist/commands/audits/index.d.ts.map +1 -0
  150. package/dist/commands/audits/index.js +15 -0
  151. package/dist/commands/audits/index.js.map +1 -0
  152. package/dist/commands/audits/perf.d.ts +3 -0
  153. package/dist/commands/audits/perf.d.ts.map +1 -0
  154. package/dist/commands/audits/perf.js +85 -0
  155. package/dist/commands/audits/perf.js.map +1 -0
  156. package/dist/commands/audits/secrets.d.ts +3 -0
  157. package/dist/commands/audits/secrets.d.ts.map +1 -0
  158. package/dist/commands/audits/secrets.js +71 -0
  159. package/dist/commands/audits/secrets.js.map +1 -0
  160. package/dist/commands/certification/certify.d.ts +3 -0
  161. package/dist/commands/certification/certify.d.ts.map +1 -0
  162. package/dist/commands/certification/certify.js +108 -0
  163. package/dist/commands/certification/certify.js.map +1 -0
  164. package/dist/commands/certification/index.d.ts +3 -0
  165. package/dist/commands/certification/index.d.ts.map +1 -0
  166. package/dist/commands/certification/index.js +17 -0
  167. package/dist/commands/certification/index.js.map +1 -0
  168. package/dist/commands/certification/performance.d.ts +3 -0
  169. package/dist/commands/certification/performance.d.ts.map +1 -0
  170. package/dist/commands/certification/performance.js +89 -0
  171. package/dist/commands/certification/performance.js.map +1 -0
  172. package/dist/commands/certification/quality.d.ts +3 -0
  173. package/dist/commands/certification/quality.d.ts.map +1 -0
  174. package/dist/commands/certification/quality.js +92 -0
  175. package/dist/commands/certification/quality.js.map +1 -0
  176. package/dist/commands/certification/redteam.d.ts +3 -0
  177. package/dist/commands/certification/redteam.d.ts.map +1 -0
  178. package/dist/commands/certification/redteam.js +114 -0
  179. package/dist/commands/certification/redteam.js.map +1 -0
  180. package/dist/commands/certification/reliability.d.ts +3 -0
  181. package/dist/commands/certification/reliability.d.ts.map +1 -0
  182. package/dist/commands/certification/reliability.js +93 -0
  183. package/dist/commands/certification/reliability.js.map +1 -0
  184. package/dist/commands/certification/security.d.ts +3 -0
  185. package/dist/commands/certification/security.d.ts.map +1 -0
  186. package/dist/commands/certification/security.js +90 -0
  187. package/dist/commands/certification/security.js.map +1 -0
  188. package/dist/commands/certification/typesafety.d.ts +3 -0
  189. package/dist/commands/certification/typesafety.d.ts.map +1 -0
  190. package/dist/commands/certification/typesafety.js +87 -0
  191. package/dist/commands/certification/typesafety.js.map +1 -0
  192. package/dist/commands/core/add-tests.d.ts +3 -0
  193. package/dist/commands/core/add-tests.d.ts.map +1 -0
  194. package/dist/commands/core/add-tests.js +29 -0
  195. package/dist/commands/core/add-tests.js.map +1 -0
  196. package/dist/commands/core/audit.d.ts +3 -0
  197. package/dist/commands/core/audit.d.ts.map +1 -0
  198. package/dist/commands/core/audit.js +64 -0
  199. package/dist/commands/core/audit.js.map +1 -0
  200. package/dist/commands/core/fix-critical.d.ts +3 -0
  201. package/dist/commands/core/fix-critical.d.ts.map +1 -0
  202. package/dist/commands/core/fix-critical.js +22 -0
  203. package/dist/commands/core/fix-critical.js.map +1 -0
  204. package/dist/commands/core/fix-high.d.ts +3 -0
  205. package/dist/commands/core/fix-high.d.ts.map +1 -0
  206. package/dist/commands/core/fix-high.js +32 -0
  207. package/dist/commands/core/fix-high.js.map +1 -0
  208. package/dist/commands/core/fix-medium.d.ts +3 -0
  209. package/dist/commands/core/fix-medium.d.ts.map +1 -0
  210. package/dist/commands/core/fix-medium.js +29 -0
  211. package/dist/commands/core/fix-medium.js.map +1 -0
  212. package/dist/commands/core/fix-rls.d.ts +3 -0
  213. package/dist/commands/core/fix-rls.d.ts.map +1 -0
  214. package/dist/commands/core/fix-rls.js +17 -0
  215. package/dist/commands/core/fix-rls.js.map +1 -0
  216. package/dist/commands/core/harden.d.ts +3 -0
  217. package/dist/commands/core/harden.d.ts.map +1 -0
  218. package/dist/commands/core/harden.js +19 -0
  219. package/dist/commands/core/harden.js.map +1 -0
  220. package/dist/commands/core/index.d.ts +3 -0
  221. package/dist/commands/core/index.d.ts.map +1 -0
  222. package/dist/commands/core/index.js +21 -0
  223. package/dist/commands/core/index.js.map +1 -0
  224. package/dist/commands/core/preflight.d.ts +3 -0
  225. package/dist/commands/core/preflight.d.ts.map +1 -0
  226. package/dist/commands/core/preflight.js +50 -0
  227. package/dist/commands/core/preflight.js.map +1 -0
  228. package/dist/commands/core/verify.d.ts +3 -0
  229. package/dist/commands/core/verify.d.ts.map +1 -0
  230. package/dist/commands/core/verify.js +32 -0
  231. package/dist/commands/core/verify.js.map +1 -0
  232. package/dist/commands/index.d.ts +28 -0
  233. package/dist/commands/index.d.ts.map +1 -0
  234. package/dist/commands/index.js +37 -0
  235. package/dist/commands/index.js.map +1 -0
  236. package/dist/commands/types.d.ts +9 -0
  237. package/dist/commands/types.d.ts.map +1 -0
  238. package/dist/commands/types.js +5 -0
  239. package/dist/commands/types.js.map +1 -0
  240. package/dist/compliance/cis.d.ts +29 -0
  241. package/dist/compliance/cis.d.ts.map +1 -0
  242. package/dist/compliance/cis.js +316 -0
  243. package/dist/compliance/cis.js.map +1 -0
  244. package/dist/compliance/frameworks/eu-ai-act.d.ts +55 -0
  245. package/dist/compliance/frameworks/eu-ai-act.d.ts.map +1 -0
  246. package/dist/compliance/frameworks/eu-ai-act.js +621 -0
  247. package/dist/compliance/frameworks/eu-ai-act.js.map +1 -0
  248. package/dist/compliance/frameworks/index.d.ts +67 -0
  249. package/dist/compliance/frameworks/index.d.ts.map +1 -0
  250. package/dist/compliance/frameworks/index.js +97 -0
  251. package/dist/compliance/frameworks/index.js.map +1 -0
  252. package/dist/compliance/frameworks/iso-42001.d.ts +59 -0
  253. package/dist/compliance/frameworks/iso-42001.d.ts.map +1 -0
  254. package/dist/compliance/frameworks/iso-42001.js +719 -0
  255. package/dist/compliance/frameworks/iso-42001.js.map +1 -0
  256. package/dist/compliance/frameworks/mitre-atlas.d.ts +58 -0
  257. package/dist/compliance/frameworks/mitre-atlas.d.ts.map +1 -0
  258. package/dist/compliance/frameworks/mitre-atlas.js +686 -0
  259. package/dist/compliance/frameworks/mitre-atlas.js.map +1 -0
  260. package/dist/compliance/frameworks/nist-ai-rmf.d.ts +51 -0
  261. package/dist/compliance/frameworks/nist-ai-rmf.d.ts.map +1 -0
  262. package/dist/compliance/frameworks/nist-ai-rmf.js +677 -0
  263. package/dist/compliance/frameworks/nist-ai-rmf.js.map +1 -0
  264. package/dist/compliance/frameworks/owasp-llm.d.ts +58 -0
  265. package/dist/compliance/frameworks/owasp-llm.d.ts.map +1 -0
  266. package/dist/compliance/frameworks/owasp-llm.js +399 -0
  267. package/dist/compliance/frameworks/owasp-llm.js.map +1 -0
  268. package/dist/compliance/gdpr.d.ts +34 -0
  269. package/dist/compliance/gdpr.d.ts.map +1 -0
  270. package/dist/compliance/gdpr.js +319 -0
  271. package/dist/compliance/gdpr.js.map +1 -0
  272. package/dist/compliance/hipaa.d.ts +29 -0
  273. package/dist/compliance/hipaa.d.ts.map +1 -0
  274. package/dist/compliance/hipaa.js +205 -0
  275. package/dist/compliance/hipaa.js.map +1 -0
  276. package/dist/compliance/index.d.ts +18 -0
  277. package/dist/compliance/index.d.ts.map +1 -0
  278. package/dist/compliance/index.js +26 -0
  279. package/dist/compliance/index.js.map +1 -0
  280. package/dist/compliance/iso27001.d.ts +30 -0
  281. package/dist/compliance/iso27001.d.ts.map +1 -0
  282. package/dist/compliance/iso27001.js +332 -0
  283. package/dist/compliance/iso27001.js.map +1 -0
  284. package/dist/compliance/mapper.d.ts +42 -0
  285. package/dist/compliance/mapper.d.ts.map +1 -0
  286. package/dist/compliance/mapper.js +269 -0
  287. package/dist/compliance/mapper.js.map +1 -0
  288. package/dist/compliance/mapper.test.d.ts +5 -0
  289. package/dist/compliance/mapper.test.d.ts.map +1 -0
  290. package/dist/compliance/mapper.test.js +360 -0
  291. package/dist/compliance/mapper.test.js.map +1 -0
  292. package/dist/compliance/pci-dss.d.ts +29 -0
  293. package/dist/compliance/pci-dss.d.ts.map +1 -0
  294. package/dist/compliance/pci-dss.js +247 -0
  295. package/dist/compliance/pci-dss.js.map +1 -0
  296. package/dist/compliance/report.d.ts +25 -0
  297. package/dist/compliance/report.d.ts.map +1 -0
  298. package/dist/compliance/report.js +254 -0
  299. package/dist/compliance/report.js.map +1 -0
  300. package/dist/compliance/report.test.d.ts +5 -0
  301. package/dist/compliance/report.test.d.ts.map +1 -0
  302. package/dist/compliance/report.test.js +128 -0
  303. package/dist/compliance/report.test.js.map +1 -0
  304. package/dist/compliance/soc2.d.ts +30 -0
  305. package/dist/compliance/soc2.d.ts.map +1 -0
  306. package/dist/compliance/soc2.js +262 -0
  307. package/dist/compliance/soc2.js.map +1 -0
  308. package/dist/compliance/soc2.test.d.ts +5 -0
  309. package/dist/compliance/soc2.test.d.ts.map +1 -0
  310. package/dist/compliance/soc2.test.js +86 -0
  311. package/dist/compliance/soc2.test.js.map +1 -0
  312. package/dist/compliance/types.d.ts +125 -0
  313. package/dist/compliance/types.d.ts.map +1 -0
  314. package/dist/compliance/types.js +10 -0
  315. package/dist/compliance/types.js.map +1 -0
  316. package/dist/config/flags.d.ts +456 -0
  317. package/dist/config/flags.d.ts.map +1 -0
  318. package/dist/config/flags.js +464 -0
  319. package/dist/config/flags.js.map +1 -0
  320. package/dist/config/index.d.ts +10 -0
  321. package/dist/config/index.d.ts.map +1 -0
  322. package/dist/config/index.js +10 -0
  323. package/dist/config/index.js.map +1 -0
  324. package/dist/config/severity-overrides.d.ts +209 -0
  325. package/dist/config/severity-overrides.d.ts.map +1 -0
  326. package/dist/config/severity-overrides.js +380 -0
  327. package/dist/config/severity-overrides.js.map +1 -0
  328. package/dist/cost/index.d.ts +11 -0
  329. package/dist/cost/index.d.ts.map +1 -0
  330. package/dist/cost/index.js +12 -0
  331. package/dist/cost/index.js.map +1 -0
  332. package/dist/cost/pricing.d.ts +57 -0
  333. package/dist/cost/pricing.d.ts.map +1 -0
  334. package/dist/cost/pricing.js +196 -0
  335. package/dist/cost/pricing.js.map +1 -0
  336. package/dist/cost/pricing.test.d.ts +5 -0
  337. package/dist/cost/pricing.test.d.ts.map +1 -0
  338. package/dist/cost/pricing.test.js +195 -0
  339. package/dist/cost/pricing.test.js.map +1 -0
  340. package/dist/cost/tracker.d.ts +100 -0
  341. package/dist/cost/tracker.d.ts.map +1 -0
  342. package/dist/cost/tracker.js +366 -0
  343. package/dist/cost/tracker.js.map +1 -0
  344. package/dist/cost/tracker.test.d.ts +5 -0
  345. package/dist/cost/tracker.test.d.ts.map +1 -0
  346. package/dist/cost/tracker.test.js +360 -0
  347. package/dist/cost/tracker.test.js.map +1 -0
  348. package/dist/cost/types.d.ts +135 -0
  349. package/dist/cost/types.d.ts.map +1 -0
  350. package/dist/cost/types.js +9 -0
  351. package/dist/cost/types.js.map +1 -0
  352. package/dist/enterprise/auth/oidc.d.ts +231 -0
  353. package/dist/enterprise/auth/oidc.d.ts.map +1 -0
  354. package/dist/enterprise/auth/oidc.js +372 -0
  355. package/dist/enterprise/auth/oidc.js.map +1 -0
  356. package/dist/enterprise/auth/oidc.test.d.ts +5 -0
  357. package/dist/enterprise/auth/oidc.test.d.ts.map +1 -0
  358. package/dist/enterprise/auth/oidc.test.js +435 -0
  359. package/dist/enterprise/auth/oidc.test.js.map +1 -0
  360. package/dist/enterprise/index.d.ts +14 -0
  361. package/dist/enterprise/index.d.ts.map +1 -0
  362. package/dist/enterprise/index.js +19 -0
  363. package/dist/enterprise/index.js.map +1 -0
  364. package/dist/enterprise/integrations/chat.d.ts +205 -0
  365. package/dist/enterprise/integrations/chat.d.ts.map +1 -0
  366. package/dist/enterprise/integrations/chat.js +624 -0
  367. package/dist/enterprise/integrations/chat.js.map +1 -0
  368. package/dist/enterprise/integrations/chat.test.d.ts +5 -0
  369. package/dist/enterprise/integrations/chat.test.d.ts.map +1 -0
  370. package/dist/enterprise/integrations/chat.test.js +557 -0
  371. package/dist/enterprise/integrations/chat.test.js.map +1 -0
  372. package/dist/enterprise/integrations/ticketing.d.ts +257 -0
  373. package/dist/enterprise/integrations/ticketing.d.ts.map +1 -0
  374. package/dist/enterprise/integrations/ticketing.js +548 -0
  375. package/dist/enterprise/integrations/ticketing.js.map +1 -0
  376. package/dist/enterprise/integrations/ticketing.test.d.ts +5 -0
  377. package/dist/enterprise/integrations/ticketing.test.d.ts.map +1 -0
  378. package/dist/enterprise/integrations/ticketing.test.js +693 -0
  379. package/dist/enterprise/integrations/ticketing.test.js.map +1 -0
  380. package/dist/enterprise/policy/opa.d.ts +194 -0
  381. package/dist/enterprise/policy/opa.d.ts.map +1 -0
  382. package/dist/enterprise/policy/opa.js +385 -0
  383. package/dist/enterprise/policy/opa.js.map +1 -0
  384. package/dist/enterprise/policy/opa.test.d.ts +5 -0
  385. package/dist/enterprise/policy/opa.test.d.ts.map +1 -0
  386. package/dist/enterprise/policy/opa.test.js +702 -0
  387. package/dist/enterprise/policy/opa.test.js.map +1 -0
  388. package/dist/enterprise/signing/kms.d.ts +211 -0
  389. package/dist/enterprise/signing/kms.d.ts.map +1 -0
  390. package/dist/enterprise/signing/kms.js +480 -0
  391. package/dist/enterprise/signing/kms.js.map +1 -0
  392. package/dist/enterprise/signing/kms.test.d.ts +5 -0
  393. package/dist/enterprise/signing/kms.test.d.ts.map +1 -0
  394. package/dist/enterprise/signing/kms.test.js +511 -0
  395. package/dist/enterprise/signing/kms.test.js.map +1 -0
  396. package/dist/eval/fixtures.d.ts +58 -0
  397. package/dist/eval/fixtures.d.ts.map +1 -0
  398. package/dist/eval/fixtures.js +571 -0
  399. package/dist/eval/fixtures.js.map +1 -0
  400. package/dist/eval/fixtures.test.d.ts +5 -0
  401. package/dist/eval/fixtures.test.d.ts.map +1 -0
  402. package/dist/eval/fixtures.test.js +193 -0
  403. package/dist/eval/fixtures.test.js.map +1 -0
  404. package/dist/eval/harness.d.ts +30 -0
  405. package/dist/eval/harness.d.ts.map +1 -0
  406. package/dist/eval/harness.js +221 -0
  407. package/dist/eval/harness.js.map +1 -0
  408. package/dist/eval/harness.test.d.ts +5 -0
  409. package/dist/eval/harness.test.d.ts.map +1 -0
  410. package/dist/eval/harness.test.js +314 -0
  411. package/dist/eval/harness.test.js.map +1 -0
  412. package/dist/eval/index.d.ts +15 -0
  413. package/dist/eval/index.d.ts.map +1 -0
  414. package/dist/eval/index.js +18 -0
  415. package/dist/eval/index.js.map +1 -0
  416. package/dist/eval/metrics.d.ts +56 -0
  417. package/dist/eval/metrics.d.ts.map +1 -0
  418. package/dist/eval/metrics.js +298 -0
  419. package/dist/eval/metrics.js.map +1 -0
  420. package/dist/eval/metrics.test.d.ts +5 -0
  421. package/dist/eval/metrics.test.d.ts.map +1 -0
  422. package/dist/eval/metrics.test.js +426 -0
  423. package/dist/eval/metrics.test.js.map +1 -0
  424. package/dist/eval/report.d.ts +30 -0
  425. package/dist/eval/report.d.ts.map +1 -0
  426. package/dist/eval/report.js +333 -0
  427. package/dist/eval/report.js.map +1 -0
  428. package/dist/eval/report.test.d.ts +5 -0
  429. package/dist/eval/report.test.d.ts.map +1 -0
  430. package/dist/eval/report.test.js +275 -0
  431. package/dist/eval/report.test.js.map +1 -0
  432. package/dist/eval/types.d.ts +234 -0
  433. package/dist/eval/types.d.ts.map +1 -0
  434. package/dist/eval/types.js +27 -0
  435. package/dist/eval/types.js.map +1 -0
  436. package/dist/http-server.d.ts +3 -0
  437. package/dist/http-server.d.ts.map +1 -0
  438. package/dist/http-server.js +127 -0
  439. package/dist/http-server.js.map +1 -0
  440. package/dist/index.d.ts +33 -0
  441. package/dist/index.d.ts.map +1 -0
  442. package/dist/index.js +4120 -0
  443. package/dist/index.js.map +1 -0
  444. package/dist/logger.d.ts +46 -0
  445. package/dist/logger.d.ts.map +1 -0
  446. package/dist/logger.js +131 -0
  447. package/dist/logger.js.map +1 -0
  448. package/dist/multimodel/consensus.d.ts +49 -0
  449. package/dist/multimodel/consensus.d.ts.map +1 -0
  450. package/dist/multimodel/consensus.js +454 -0
  451. package/dist/multimodel/consensus.js.map +1 -0
  452. package/dist/multimodel/consensus.test.d.ts +5 -0
  453. package/dist/multimodel/consensus.test.d.ts.map +1 -0
  454. package/dist/multimodel/consensus.test.js +415 -0
  455. package/dist/multimodel/consensus.test.js.map +1 -0
  456. package/dist/multimodel/index.d.ts +13 -0
  457. package/dist/multimodel/index.d.ts.map +1 -0
  458. package/dist/multimodel/index.js +14 -0
  459. package/dist/multimodel/index.js.map +1 -0
  460. package/dist/multimodel/runner.d.ts +95 -0
  461. package/dist/multimodel/runner.d.ts.map +1 -0
  462. package/dist/multimodel/runner.js +312 -0
  463. package/dist/multimodel/runner.js.map +1 -0
  464. package/dist/multimodel/runner.test.d.ts +5 -0
  465. package/dist/multimodel/runner.test.d.ts.map +1 -0
  466. package/dist/multimodel/runner.test.js +224 -0
  467. package/dist/multimodel/runner.test.js.map +1 -0
  468. package/dist/multimodel/types.d.ts +202 -0
  469. package/dist/multimodel/types.d.ts.map +1 -0
  470. package/dist/multimodel/types.js +10 -0
  471. package/dist/multimodel/types.js.map +1 -0
  472. package/dist/observability/index.d.ts +9 -0
  473. package/dist/observability/index.d.ts.map +1 -0
  474. package/dist/observability/index.js +9 -0
  475. package/dist/observability/index.js.map +1 -0
  476. package/dist/observability/otel.d.ts +102 -0
  477. package/dist/observability/otel.d.ts.map +1 -0
  478. package/dist/observability/otel.js +284 -0
  479. package/dist/observability/otel.js.map +1 -0
  480. package/dist/plugins/index.d.ts +10 -0
  481. package/dist/plugins/index.d.ts.map +1 -0
  482. package/dist/plugins/index.js +10 -0
  483. package/dist/plugins/index.js.map +1 -0
  484. package/dist/plugins/loader.d.ts +78 -0
  485. package/dist/plugins/loader.d.ts.map +1 -0
  486. package/dist/plugins/loader.js +470 -0
  487. package/dist/plugins/loader.js.map +1 -0
  488. package/dist/plugins/types.d.ts +304 -0
  489. package/dist/plugins/types.d.ts.map +1 -0
  490. package/dist/plugins/types.js +100 -0
  491. package/dist/plugins/types.js.map +1 -0
  492. package/dist/sbom/cyclonedx.d.ts +30 -0
  493. package/dist/sbom/cyclonedx.d.ts.map +1 -0
  494. package/dist/sbom/cyclonedx.js +392 -0
  495. package/dist/sbom/cyclonedx.js.map +1 -0
  496. package/dist/sbom/cyclonedx.test.d.ts +5 -0
  497. package/dist/sbom/cyclonedx.test.d.ts.map +1 -0
  498. package/dist/sbom/cyclonedx.test.js +244 -0
  499. package/dist/sbom/cyclonedx.test.js.map +1 -0
  500. package/dist/sbom/index.d.ts +13 -0
  501. package/dist/sbom/index.d.ts.map +1 -0
  502. package/dist/sbom/index.js +15 -0
  503. package/dist/sbom/index.js.map +1 -0
  504. package/dist/sbom/provenance.d.ts +37 -0
  505. package/dist/sbom/provenance.d.ts.map +1 -0
  506. package/dist/sbom/provenance.js +268 -0
  507. package/dist/sbom/provenance.js.map +1 -0
  508. package/dist/sbom/provenance.test.d.ts +5 -0
  509. package/dist/sbom/provenance.test.d.ts.map +1 -0
  510. package/dist/sbom/provenance.test.js +189 -0
  511. package/dist/sbom/provenance.test.js.map +1 -0
  512. package/dist/sbom/signing.d.ts +87 -0
  513. package/dist/sbom/signing.d.ts.map +1 -0
  514. package/dist/sbom/signing.js +354 -0
  515. package/dist/sbom/signing.js.map +1 -0
  516. package/dist/sbom/signing.test.d.ts +5 -0
  517. package/dist/sbom/signing.test.d.ts.map +1 -0
  518. package/dist/sbom/signing.test.js +170 -0
  519. package/dist/sbom/signing.test.js.map +1 -0
  520. package/dist/sbom/types.d.ts +384 -0
  521. package/dist/sbom/types.d.ts.map +1 -0
  522. package/dist/sbom/types.js +17 -0
  523. package/dist/sbom/types.js.map +1 -0
  524. package/dist/scanners/agent/credential-scope-audit.d.ts +40 -0
  525. package/dist/scanners/agent/credential-scope-audit.d.ts.map +1 -0
  526. package/dist/scanners/agent/credential-scope-audit.js +404 -0
  527. package/dist/scanners/agent/credential-scope-audit.js.map +1 -0
  528. package/dist/scanners/agent/exfil-path-graph.d.ts +50 -0
  529. package/dist/scanners/agent/exfil-path-graph.d.ts.map +1 -0
  530. package/dist/scanners/agent/exfil-path-graph.js +764 -0
  531. package/dist/scanners/agent/exfil-path-graph.js.map +1 -0
  532. package/dist/scanners/agent/index.d.ts +43 -0
  533. package/dist/scanners/agent/index.d.ts.map +1 -0
  534. package/dist/scanners/agent/index.js +616 -0
  535. package/dist/scanners/agent/index.js.map +1 -0
  536. package/dist/scanners/agent/manifest-audit.d.ts +43 -0
  537. package/dist/scanners/agent/manifest-audit.d.ts.map +1 -0
  538. package/dist/scanners/agent/manifest-audit.js +403 -0
  539. package/dist/scanners/agent/manifest-audit.js.map +1 -0
  540. package/dist/scanners/agent/payloads/index.d.ts +44 -0
  541. package/dist/scanners/agent/payloads/index.d.ts.map +1 -0
  542. package/dist/scanners/agent/payloads/index.js +184 -0
  543. package/dist/scanners/agent/payloads/index.js.map +1 -0
  544. package/dist/scanners/agent/permission-minimiser.d.ts +48 -0
  545. package/dist/scanners/agent/permission-minimiser.d.ts.map +1 -0
  546. package/dist/scanners/agent/permission-minimiser.js +551 -0
  547. package/dist/scanners/agent/permission-minimiser.js.map +1 -0
  548. package/dist/scanners/agent/prompt-injection-fuzzer.d.ts +39 -0
  549. package/dist/scanners/agent/prompt-injection-fuzzer.d.ts.map +1 -0
  550. package/dist/scanners/agent/prompt-injection-fuzzer.js +720 -0
  551. package/dist/scanners/agent/prompt-injection-fuzzer.js.map +1 -0
  552. package/dist/scanners/agent/sandbox-audit.d.ts +44 -0
  553. package/dist/scanners/agent/sandbox-audit.d.ts.map +1 -0
  554. package/dist/scanners/agent/sandbox-audit.js +425 -0
  555. package/dist/scanners/agent/sandbox-audit.js.map +1 -0
  556. package/dist/scanners/agent/supply-chain-mcp.d.ts +53 -0
  557. package/dist/scanners/agent/supply-chain-mcp.d.ts.map +1 -0
  558. package/dist/scanners/agent/supply-chain-mcp.js +479 -0
  559. package/dist/scanners/agent/supply-chain-mcp.js.map +1 -0
  560. package/dist/scanners/agent/tool-description-drift.d.ts +62 -0
  561. package/dist/scanners/agent/tool-description-drift.d.ts.map +1 -0
  562. package/dist/scanners/agent/tool-description-drift.js +365 -0
  563. package/dist/scanners/agent/tool-description-drift.js.map +1 -0
  564. package/dist/scanners/agent/types.d.ts +840 -0
  565. package/dist/scanners/agent/types.d.ts.map +1 -0
  566. package/dist/scanners/agent/types.js +149 -0
  567. package/dist/scanners/agent/types.js.map +1 -0
  568. package/dist/scanners/bandit.d.ts +25 -0
  569. package/dist/scanners/bandit.d.ts.map +1 -0
  570. package/dist/scanners/bandit.js +129 -0
  571. package/dist/scanners/bandit.js.map +1 -0
  572. package/dist/scanners/binary-analysis.d.ts +41 -0
  573. package/dist/scanners/binary-analysis.d.ts.map +1 -0
  574. package/dist/scanners/binary-analysis.js +587 -0
  575. package/dist/scanners/binary-analysis.js.map +1 -0
  576. package/dist/scanners/binary-analysis.test.d.ts +5 -0
  577. package/dist/scanners/binary-analysis.test.d.ts.map +1 -0
  578. package/dist/scanners/binary-analysis.test.js +291 -0
  579. package/dist/scanners/binary-analysis.test.js.map +1 -0
  580. package/dist/scanners/brakeman.d.ts +30 -0
  581. package/dist/scanners/brakeman.d.ts.map +1 -0
  582. package/dist/scanners/brakeman.js +271 -0
  583. package/dist/scanners/brakeman.js.map +1 -0
  584. package/dist/scanners/dependencies.d.ts +22 -0
  585. package/dist/scanners/dependencies.d.ts.map +1 -0
  586. package/dist/scanners/dependencies.js +202 -0
  587. package/dist/scanners/dependencies.js.map +1 -0
  588. package/dist/scanners/dependencies.test.d.ts +5 -0
  589. package/dist/scanners/dependencies.test.d.ts.map +1 -0
  590. package/dist/scanners/dependencies.test.js +185 -0
  591. package/dist/scanners/dependencies.test.js.map +1 -0
  592. package/dist/scanners/eslint.d.ts +25 -0
  593. package/dist/scanners/eslint.d.ts.map +1 -0
  594. package/dist/scanners/eslint.js +220 -0
  595. package/dist/scanners/eslint.js.map +1 -0
  596. package/dist/scanners/gosec.d.ts +25 -0
  597. package/dist/scanners/gosec.d.ts.map +1 -0
  598. package/dist/scanners/gosec.js +128 -0
  599. package/dist/scanners/gosec.js.map +1 -0
  600. package/dist/scanners/index.d.ts +128 -0
  601. package/dist/scanners/index.d.ts.map +1 -0
  602. package/dist/scanners/index.js +811 -0
  603. package/dist/scanners/index.js.map +1 -0
  604. package/dist/scanners/index.test.d.ts +5 -0
  605. package/dist/scanners/index.test.d.ts.map +1 -0
  606. package/dist/scanners/index.test.js +424 -0
  607. package/dist/scanners/index.test.js.map +1 -0
  608. package/dist/scanners/memory-safety.d.ts +44 -0
  609. package/dist/scanners/memory-safety.d.ts.map +1 -0
  610. package/dist/scanners/memory-safety.js +571 -0
  611. package/dist/scanners/memory-safety.js.map +1 -0
  612. package/dist/scanners/memory-safety.test.d.ts +5 -0
  613. package/dist/scanners/memory-safety.test.d.ts.map +1 -0
  614. package/dist/scanners/memory-safety.test.js +321 -0
  615. package/dist/scanners/memory-safety.test.js.map +1 -0
  616. package/dist/scanners/race-condition.d.ts +25 -0
  617. package/dist/scanners/race-condition.d.ts.map +1 -0
  618. package/dist/scanners/race-condition.js +443 -0
  619. package/dist/scanners/race-condition.js.map +1 -0
  620. package/dist/scanners/race-condition.test.d.ts +5 -0
  621. package/dist/scanners/race-condition.test.d.ts.map +1 -0
  622. package/dist/scanners/race-condition.test.js +428 -0
  623. package/dist/scanners/race-condition.test.js.map +1 -0
  624. package/dist/scanners/secrets.d.ts +25 -0
  625. package/dist/scanners/secrets.d.ts.map +1 -0
  626. package/dist/scanners/secrets.js +367 -0
  627. package/dist/scanners/secrets.js.map +1 -0
  628. package/dist/scanners/secrets.test.d.ts +5 -0
  629. package/dist/scanners/secrets.test.d.ts.map +1 -0
  630. package/dist/scanners/secrets.test.js +160 -0
  631. package/dist/scanners/secrets.test.js.map +1 -0
  632. package/dist/scanners/semgrep.d.ts +33 -0
  633. package/dist/scanners/semgrep.d.ts.map +1 -0
  634. package/dist/scanners/semgrep.js +350 -0
  635. package/dist/scanners/semgrep.js.map +1 -0
  636. package/dist/scanners/semgrep.test.d.ts +8 -0
  637. package/dist/scanners/semgrep.test.d.ts.map +1 -0
  638. package/dist/scanners/semgrep.test.js +254 -0
  639. package/dist/scanners/semgrep.test.js.map +1 -0
  640. package/dist/scanners/trivy.d.ts +26 -0
  641. package/dist/scanners/trivy.d.ts.map +1 -0
  642. package/dist/scanners/trivy.js +187 -0
  643. package/dist/scanners/trivy.js.map +1 -0
  644. package/dist/scanners/types.d.ts +210 -0
  645. package/dist/scanners/types.d.ts.map +1 -0
  646. package/dist/scanners/types.js +106 -0
  647. package/dist/scanners/types.js.map +1 -0
  648. package/dist/scanners/types.test.d.ts +5 -0
  649. package/dist/scanners/types.test.d.ts.map +1 -0
  650. package/dist/scanners/types.test.js +103 -0
  651. package/dist/scanners/types.test.js.map +1 -0
  652. package/dist/scanners/typescript.d.ts +32 -0
  653. package/dist/scanners/typescript.d.ts.map +1 -0
  654. package/dist/scanners/typescript.js +300 -0
  655. package/dist/scanners/typescript.js.map +1 -0
  656. package/dist/scanners/typescript.test.d.ts +5 -0
  657. package/dist/scanners/typescript.test.d.ts.map +1 -0
  658. package/dist/scanners/typescript.test.js +296 -0
  659. package/dist/scanners/typescript.test.js.map +1 -0
  660. package/dist/transcripts/index.d.ts +13 -0
  661. package/dist/transcripts/index.d.ts.map +1 -0
  662. package/dist/transcripts/index.js +17 -0
  663. package/dist/transcripts/index.js.map +1 -0
  664. package/dist/transcripts/logger.d.ts +190 -0
  665. package/dist/transcripts/logger.d.ts.map +1 -0
  666. package/dist/transcripts/logger.js +385 -0
  667. package/dist/transcripts/logger.js.map +1 -0
  668. package/dist/transcripts/logger.test.d.ts +5 -0
  669. package/dist/transcripts/logger.test.d.ts.map +1 -0
  670. package/dist/transcripts/logger.test.js +227 -0
  671. package/dist/transcripts/logger.test.js.map +1 -0
  672. package/dist/transcripts/redaction.d.ts +125 -0
  673. package/dist/transcripts/redaction.d.ts.map +1 -0
  674. package/dist/transcripts/redaction.js +416 -0
  675. package/dist/transcripts/redaction.js.map +1 -0
  676. package/dist/transcripts/redaction.test.d.ts +5 -0
  677. package/dist/transcripts/redaction.test.d.ts.map +1 -0
  678. package/dist/transcripts/redaction.test.js +267 -0
  679. package/dist/transcripts/redaction.test.js.map +1 -0
  680. package/dist/transcripts/signing.d.ts +108 -0
  681. package/dist/transcripts/signing.d.ts.map +1 -0
  682. package/dist/transcripts/signing.js +173 -0
  683. package/dist/transcripts/signing.js.map +1 -0
  684. package/dist/transcripts/verifier.d.ts +133 -0
  685. package/dist/transcripts/verifier.d.ts.map +1 -0
  686. package/dist/transcripts/verifier.js +489 -0
  687. package/dist/transcripts/verifier.js.map +1 -0
  688. package/dist/transcripts/verifier.test.d.ts +5 -0
  689. package/dist/transcripts/verifier.test.d.ts.map +1 -0
  690. package/dist/transcripts/verifier.test.js +330 -0
  691. package/dist/transcripts/verifier.test.js.map +1 -0
  692. package/dist/util/concurrency.d.ts +221 -0
  693. package/dist/util/concurrency.d.ts.map +1 -0
  694. package/dist/util/concurrency.js +339 -0
  695. package/dist/util/concurrency.js.map +1 -0
  696. package/dist/util/index.d.ts +12 -0
  697. package/dist/util/index.d.ts.map +1 -0
  698. package/dist/util/index.js +12 -0
  699. package/dist/util/index.js.map +1 -0
  700. package/dist/util/json.d.ts +63 -0
  701. package/dist/util/json.d.ts.map +1 -0
  702. package/dist/util/json.js +134 -0
  703. package/dist/util/json.js.map +1 -0
  704. package/dist/util/paths.d.ts +56 -0
  705. package/dist/util/paths.d.ts.map +1 -0
  706. package/dist/util/paths.js +128 -0
  707. package/dist/util/paths.js.map +1 -0
  708. package/dist/util/retry.d.ts +185 -0
  709. package/dist/util/retry.d.ts.map +1 -0
  710. package/dist/util/retry.js +338 -0
  711. package/dist/util/retry.js.map +1 -0
  712. package/package.json +79 -0
package/dist/index.js ADDED
@@ -0,0 +1,4120 @@
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 { readdir, readFile, writeFile, mkdir, stat, access } from "fs/promises";
6
+ import { join, basename } from "path";
7
+ import { COMMANDS, listCommands, getCommand } from "./commands/index.js";
8
+ import { logger, createChildLogger } from "./logger.js";
9
+ import { generateCertificationId, initializeCertification, getCertification, startAgent, getAgentFindings, submitFinding, completeAgent, addCrossVerification, addRedTeamChallenge, saveConsensus, finalizeCertification, getLatestCertification, isCertificationValid, calculateConsensus, canFinalize, writeCertificationArtifacts, updateCertificationMetadata,
10
+ // Cross-verification
11
+ autoCrossVerify, allAgentsCompleted,
12
+ // Auto-fix
13
+ previewFix, applyFix, listFixPatterns, batchApplySafeFixes, getSafeFixPatterns,
14
+ // Summary
15
+ generateSummary, filterFindings, getActionItems,
16
+ // SARIF
17
+ exportToSarif, exportForGitHub,
18
+ // Rules
19
+ loadCustomRules, checkFileAgainstRules, matchesToFindings, listRuleTemplates, generateSampleConfig, } from "./certification/index.js";
20
+ // Deterministic scanners
21
+ import { runAllScanners, checkScannersAvailable, scannerFindingsToCertificationFindings, generateScannerSummary, getScannerInstallCommands,
22
+ // Mythos-class scanners
23
+ runBinaryAnalysis, runMemorySafetyAnalysis, runRaceConditionAnalysis, } from "./scanners/index.js";
24
+ // Mythos-class agents
25
+ import { runZeroDayHunter, runLogicFlawDetector, analyzeExploitChains, getChainSummary, } from "./agents/index.js";
26
+ // M6: Agent & MCP Security Scanners
27
+ import { runAgentScanners, checkAgentScannersAvailable, runPromptInjectionFuzzer, discoverManifest, generateAgentScannerSummary, AGENT_SCANNER_TYPES, } from "./scanners/agent/index.js";
28
+ // M6: AI Compliance Frameworks
29
+ import { getAIFrameworkControls, AI_FRAMEWORKS_METADATA, } from "./compliance/frameworks/index.js";
30
+ // Evaluation harness
31
+ import { runEvaluation, runStabilityTest, calculateMetrics, calculateStabilityMetrics, meetsTargets, generateEvaluationReport, formatReportAsMarkdown, generateCompactSummary, generateReadmeBadges, getFixtureStats, ALL_FIXTURES, TARGET_METRICS, } from "./eval/index.js";
32
+ // Compliance frameworks
33
+ import { generateComplianceReport, generateMultiFrameworkReport, formatComplianceReportAsMarkdown, formatMultiFrameworkReportAsMarkdown, generateCompactComplianceSummary, getControlsForFramework, getSOC2Categories, getISO27001Categories, } from "./compliance/index.js";
34
+ // SBOM, Provenance, and Sigstore Signing (uses @sigstore/sign for real signing)
35
+ import { generateSBOM, generateSBOMSummary, generateProvenance, generateProvenanceSummary, verifyProvenance, signContent, isSigningAvailable, generateSigningSummary, detectCIEnvironment, } from "./sbom/index.js";
36
+ // Cost tracking
37
+ import { getTracker, formatCost, formatTokens, estimateCost, getSupportedModels, MODEL_PRICING, } from "./cost/index.js";
38
+ // Multi-model consensus
39
+ import { getRunner, DEFAULT_MODELS, formatProvider, } from "./multimodel/index.js";
40
+ // ---------------------------------------------------------------------------
41
+ // Config
42
+ // ---------------------------------------------------------------------------
43
+ const DEFAULT_PROJECTS_DIR = process.env.VASPERA_PROJECTS_DIR ||
44
+ join(process.env.HOME || "~", "Documents", "GitHub");
45
+ // ---------------------------------------------------------------------------
46
+ // Helpers
47
+ // ---------------------------------------------------------------------------
48
+ async function dirExists(p) {
49
+ try {
50
+ const s = await stat(p);
51
+ return s.isDirectory();
52
+ }
53
+ catch (error) {
54
+ logger.debug("helpers.dir_not_found", { path: p, error: String(error) });
55
+ return false;
56
+ }
57
+ }
58
+ async function fileExists(p) {
59
+ try {
60
+ await access(p);
61
+ return true;
62
+ }
63
+ catch (error) {
64
+ logger.debug("helpers.file_not_found", { path: p, error: String(error) });
65
+ return false;
66
+ }
67
+ }
68
+ async function safeReadFile(p) {
69
+ try {
70
+ return await readFile(p, "utf-8");
71
+ }
72
+ catch (error) {
73
+ logger.debug("helpers.read_failed", { path: p, error: String(error) });
74
+ return null;
75
+ }
76
+ }
77
+ /**
78
+ * Create a standard MCP text response
79
+ */
80
+ function textResponse(text) {
81
+ return {
82
+ content: [{ type: "text", text }],
83
+ };
84
+ }
85
+ /**
86
+ * Create a standard MCP JSON response
87
+ */
88
+ function jsonResponse(data) {
89
+ return {
90
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
91
+ structuredContent: data,
92
+ };
93
+ }
94
+ /**
95
+ * Create a standard MCP error response
96
+ */
97
+ function errorResponse(message) {
98
+ return {
99
+ content: [{ type: "text", text: `Error: ${message}` }],
100
+ };
101
+ }
102
+ async function discoverProjects(baseDir) {
103
+ const entries = await readdir(baseDir, { withFileTypes: true });
104
+ const projects = [];
105
+ for (const entry of entries) {
106
+ if (!entry.isDirectory() || entry.name.startsWith("."))
107
+ continue;
108
+ const fullPath = join(baseDir, entry.name);
109
+ // A project has a package.json or .git
110
+ const hasPkg = await fileExists(join(fullPath, "package.json"));
111
+ const hasGit = await dirExists(join(fullPath, ".git"));
112
+ if (hasPkg || hasGit) {
113
+ projects.push(fullPath);
114
+ }
115
+ }
116
+ return projects;
117
+ }
118
+ function parseAuditScore(content) {
119
+ const match = content.match(/readiness score[:\s]*(\d+)/i);
120
+ return match ? parseInt(match[1], 10) : null;
121
+ }
122
+ function parseAuditCounts(content) {
123
+ const counts = { critical: 0, high: 0, medium: 0, low: 0 };
124
+ const critMatch = content.match(/CRITICAL[:\s]*(\d+)/i);
125
+ const highMatch = content.match(/HIGH[:\s]*(\d+)/i);
126
+ const medMatch = content.match(/MEDIUM[:\s]*(\d+)/i);
127
+ const lowMatch = content.match(/LOW[:\s]*(\d+)/i);
128
+ if (critMatch)
129
+ counts.critical = parseInt(critMatch[1], 10);
130
+ if (highMatch)
131
+ counts.high = parseInt(highMatch[1], 10);
132
+ if (medMatch)
133
+ counts.medium = parseInt(medMatch[1], 10);
134
+ if (lowMatch)
135
+ counts.low = parseInt(lowMatch[1], 10);
136
+ return counts;
137
+ }
138
+ // ---------------------------------------------------------------------------
139
+ // Server
140
+ // ---------------------------------------------------------------------------
141
+ const server = new McpServer({
142
+ name: "vaspera-hardening-mcp-server",
143
+ version: "2.0.0",
144
+ });
145
+ // ---------------------------------------------------------------------------
146
+ // Tool: List Projects
147
+ // ---------------------------------------------------------------------------
148
+ server.registerTool("hardening_list_projects", {
149
+ title: "List Vaspera Projects",
150
+ description: `Discover all projects in the Vaspera workspace directory. Returns project names, paths, and whether hardening commands are already installed. Set VASPERA_PROJECTS_DIR env var to override the default directory (~/Documents/GitHub).`,
151
+ inputSchema: {
152
+ base_dir: z
153
+ .string()
154
+ .optional()
155
+ .describe("Base directory to scan. Defaults to VASPERA_PROJECTS_DIR or ~/Documents/GitHub"),
156
+ },
157
+ annotations: {
158
+ readOnlyHint: true,
159
+ destructiveHint: false,
160
+ idempotentHint: true,
161
+ openWorldHint: false,
162
+ },
163
+ }, async ({ base_dir }) => {
164
+ const dir = base_dir || DEFAULT_PROJECTS_DIR;
165
+ if (!(await dirExists(dir))) {
166
+ return {
167
+ content: [
168
+ {
169
+ type: "text",
170
+ text: `Error: Directory not found: ${dir}\nSet VASPERA_PROJECTS_DIR or pass base_dir parameter.`,
171
+ },
172
+ ],
173
+ };
174
+ }
175
+ const projects = await discoverProjects(dir);
176
+ const results = await Promise.all(projects.map(async (p) => {
177
+ const name = basename(p);
178
+ const hasCommands = await dirExists(join(p, ".claude", "commands"));
179
+ const hasAudit = await fileExists(join(p, "AUDIT.md"));
180
+ const hasReport = await fileExists(join(p, "HARDENING-REPORT.md"));
181
+ let score = null;
182
+ if (hasAudit) {
183
+ const auditContent = await safeReadFile(join(p, "AUDIT.md"));
184
+ if (auditContent)
185
+ score = parseAuditScore(auditContent);
186
+ }
187
+ return {
188
+ name,
189
+ path: p,
190
+ hardening_installed: hasCommands,
191
+ has_audit: hasAudit,
192
+ has_report: hasReport,
193
+ readiness_score: score,
194
+ };
195
+ }));
196
+ const output = {
197
+ base_dir: dir,
198
+ project_count: results.length,
199
+ projects: results,
200
+ };
201
+ return {
202
+ content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
203
+ structuredContent: output,
204
+ };
205
+ });
206
+ // ---------------------------------------------------------------------------
207
+ // Tool: Install Commands
208
+ // ---------------------------------------------------------------------------
209
+ server.registerTool("hardening_install", {
210
+ title: "Install Hardening Commands",
211
+ description: `Install the production hardening slash commands into a project's .claude/commands/ directory. After installation, Claude Code can use /audit, /fix-critical, /fix-high, /fix-medium, /fix-rls, /add-tests, /verify, and /harden in that project.`,
212
+ inputSchema: {
213
+ project_path: z
214
+ .string()
215
+ .describe("Absolute path to the project root directory"),
216
+ },
217
+ annotations: {
218
+ readOnlyHint: false,
219
+ destructiveHint: false,
220
+ idempotentHint: true,
221
+ openWorldHint: false,
222
+ },
223
+ }, async ({ project_path }) => {
224
+ if (!(await dirExists(project_path))) {
225
+ return {
226
+ content: [
227
+ {
228
+ type: "text",
229
+ text: `Error: Project directory not found: ${project_path}`,
230
+ },
231
+ ],
232
+ };
233
+ }
234
+ try {
235
+ const commandsDir = join(project_path, ".claude", "commands");
236
+ await mkdir(commandsDir, { recursive: true });
237
+ const installed = [];
238
+ const errors = [];
239
+ for (const [key, cmd] of Object.entries(COMMANDS)) {
240
+ const filename = `${key}.md`;
241
+ const filepath = join(commandsDir, filename);
242
+ try {
243
+ await writeFile(filepath, `# ${cmd.description}\n\n${cmd.content}`, "utf-8");
244
+ installed.push(filename);
245
+ }
246
+ catch (err) {
247
+ errors.push(`Failed to write ${filename}: ${err instanceof Error ? err.message : String(err)}`);
248
+ }
249
+ }
250
+ const output = {
251
+ project: project_path,
252
+ commands_dir: commandsDir,
253
+ installed_commands: installed,
254
+ errors: errors.length > 0 ? errors : undefined,
255
+ usage: "Open Claude Code in the project and type /audit to start",
256
+ };
257
+ return {
258
+ content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
259
+ structuredContent: output,
260
+ };
261
+ }
262
+ catch (err) {
263
+ return {
264
+ content: [
265
+ {
266
+ type: "text",
267
+ text: `Error installing commands: ${err instanceof Error ? err.message : String(err)}`,
268
+ },
269
+ ],
270
+ };
271
+ }
272
+ });
273
+ // ---------------------------------------------------------------------------
274
+ // Tool: Install to All Projects
275
+ // ---------------------------------------------------------------------------
276
+ server.registerTool("hardening_install_all", {
277
+ title: "Install Hardening Commands to All Projects",
278
+ description: `Install hardening slash commands into every discovered project in the workspace. Scans the base directory, finds all projects, and installs commands to each one. Defaults to dry-run mode for safety - set dry_run: false to apply changes.`,
279
+ inputSchema: {
280
+ base_dir: z
281
+ .string()
282
+ .optional()
283
+ .describe("Base directory. Defaults to VASPERA_PROJECTS_DIR or ~/Documents/GitHub"),
284
+ dry_run: z
285
+ .boolean()
286
+ .optional()
287
+ .default(true)
288
+ .describe("Preview changes without modifying files. Set to false to apply changes."),
289
+ },
290
+ annotations: {
291
+ readOnlyHint: false,
292
+ destructiveHint: false,
293
+ idempotentHint: true,
294
+ openWorldHint: false,
295
+ },
296
+ }, async ({ base_dir, dry_run = true }) => {
297
+ const dir = base_dir || DEFAULT_PROJECTS_DIR;
298
+ if (!(await dirExists(dir))) {
299
+ return {
300
+ content: [
301
+ {
302
+ type: "text",
303
+ text: `Error: Directory not found: ${dir}`,
304
+ },
305
+ ],
306
+ };
307
+ }
308
+ try {
309
+ const projects = await discoverProjects(dir);
310
+ const commandCount = Object.keys(COMMANDS).length;
311
+ const results = [];
312
+ for (const projectPath of projects) {
313
+ if (dry_run) {
314
+ // Dry-run mode: just report what would happen
315
+ results.push({
316
+ name: basename(projectPath),
317
+ path: projectPath,
318
+ commands_would_install: commandCount,
319
+ });
320
+ }
321
+ else {
322
+ // Apply mode: actually write files
323
+ try {
324
+ const commandsDir = join(projectPath, ".claude", "commands");
325
+ await mkdir(commandsDir, { recursive: true });
326
+ let count = 0;
327
+ for (const [key, cmd] of Object.entries(COMMANDS)) {
328
+ const filepath = join(commandsDir, `${key}.md`);
329
+ await writeFile(filepath, `# ${cmd.description}\n\n${cmd.content}`, "utf-8");
330
+ count++;
331
+ }
332
+ results.push({
333
+ name: basename(projectPath),
334
+ path: projectPath,
335
+ commands_installed: count,
336
+ });
337
+ }
338
+ catch (err) {
339
+ results.push({
340
+ name: basename(projectPath),
341
+ path: projectPath,
342
+ commands_installed: 0,
343
+ error: err instanceof Error ? err.message : String(err),
344
+ });
345
+ }
346
+ }
347
+ }
348
+ const output = {
349
+ mode: dry_run ? "dry-run" : "applied",
350
+ base_dir: dir,
351
+ projects_scanned: projects.length,
352
+ commands_per_project: commandCount,
353
+ projects_updated: dry_run ? 0 : results.filter((r) => !r.error && r.commands_installed).length,
354
+ projects_would_update: dry_run ? results.length : 0,
355
+ projects_failed: results.filter((r) => r.error).length,
356
+ projects: results,
357
+ ...(dry_run && { note: "This is a dry-run preview. Set dry_run: false to apply changes." }),
358
+ };
359
+ return {
360
+ content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
361
+ structuredContent: output,
362
+ };
363
+ }
364
+ catch (err) {
365
+ return {
366
+ content: [
367
+ {
368
+ type: "text",
369
+ text: `Error discovering projects: ${err instanceof Error ? err.message : String(err)}`,
370
+ },
371
+ ],
372
+ };
373
+ }
374
+ });
375
+ // ---------------------------------------------------------------------------
376
+ // Tool: Get Command Prompt
377
+ // ---------------------------------------------------------------------------
378
+ server.registerTool("hardening_get_command", {
379
+ title: "Get Hardening Command Prompt",
380
+ description: `Retrieve the full prompt text for a specific hardening command. Use this to execute a hardening phase against a project. Available commands: audit, fix-critical, fix-high, fix-medium, fix-rls, add-tests, verify, harden.`,
381
+ inputSchema: {
382
+ command: z
383
+ .enum([
384
+ "audit",
385
+ "fix-critical",
386
+ "fix-high",
387
+ "fix-medium",
388
+ "fix-rls",
389
+ "add-tests",
390
+ "verify",
391
+ "harden",
392
+ "preflight",
393
+ "deps",
394
+ "deadcode",
395
+ "errors",
396
+ "secrets",
397
+ "api-check",
398
+ "perf",
399
+ "certification-security",
400
+ "certification-reliability",
401
+ "certification-typesafety",
402
+ "certification-performance",
403
+ "certification-quality",
404
+ "certification-redteam",
405
+ "certify",
406
+ ])
407
+ .describe("The hardening command to retrieve"),
408
+ },
409
+ annotations: {
410
+ readOnlyHint: true,
411
+ destructiveHint: false,
412
+ idempotentHint: true,
413
+ openWorldHint: false,
414
+ },
415
+ }, async ({ command }) => {
416
+ const cmd = getCommand(command);
417
+ if (!cmd) {
418
+ return {
419
+ content: [
420
+ { type: "text", text: `Error: Unknown command: ${command}` },
421
+ ],
422
+ };
423
+ }
424
+ const output = {
425
+ command: cmd.name,
426
+ description: cmd.description,
427
+ prompt: cmd.content,
428
+ };
429
+ return {
430
+ content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
431
+ structuredContent: output,
432
+ };
433
+ });
434
+ // ---------------------------------------------------------------------------
435
+ // Tool: Read Audit Report
436
+ // ---------------------------------------------------------------------------
437
+ server.registerTool("hardening_read_audit", {
438
+ title: "Read Project Audit Report",
439
+ description: `Read the AUDIT.md file from a project. Returns the full audit content including severity counts and readiness score. Use after running /audit in a project.`,
440
+ inputSchema: {
441
+ project_path: z.string().describe("Absolute path to the project root"),
442
+ },
443
+ annotations: {
444
+ readOnlyHint: true,
445
+ destructiveHint: false,
446
+ idempotentHint: true,
447
+ openWorldHint: false,
448
+ },
449
+ }, async ({ project_path }) => {
450
+ const auditPath = join(project_path, "AUDIT.md");
451
+ const content = await safeReadFile(auditPath);
452
+ if (!content) {
453
+ return {
454
+ content: [
455
+ {
456
+ type: "text",
457
+ text: `No AUDIT.md found in ${project_path}. Run /audit in Claude Code first.`,
458
+ },
459
+ ],
460
+ };
461
+ }
462
+ const score = parseAuditScore(content);
463
+ const counts = parseAuditCounts(content);
464
+ const output = {
465
+ project: basename(project_path),
466
+ path: auditPath,
467
+ readiness_score: score,
468
+ issue_counts: counts,
469
+ content,
470
+ };
471
+ return {
472
+ content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
473
+ structuredContent: output,
474
+ };
475
+ });
476
+ // ---------------------------------------------------------------------------
477
+ // Tool: Read Hardening Report
478
+ // ---------------------------------------------------------------------------
479
+ server.registerTool("hardening_read_report", {
480
+ title: "Read Hardening Report",
481
+ description: `Read the HARDENING-REPORT.md from a project. Contains the before/after comparison and deployment checklist. Available after running /verify.`,
482
+ inputSchema: {
483
+ project_path: z.string().describe("Absolute path to the project root"),
484
+ },
485
+ annotations: {
486
+ readOnlyHint: true,
487
+ destructiveHint: false,
488
+ idempotentHint: true,
489
+ openWorldHint: false,
490
+ },
491
+ }, async ({ project_path }) => {
492
+ const reportPath = join(project_path, "HARDENING-REPORT.md");
493
+ const content = await safeReadFile(reportPath);
494
+ if (!content) {
495
+ return {
496
+ content: [
497
+ {
498
+ type: "text",
499
+ text: `No HARDENING-REPORT.md found in ${project_path}. Run the full hardening pipeline first.`,
500
+ },
501
+ ],
502
+ };
503
+ }
504
+ return {
505
+ content: [{ type: "text", text: content }],
506
+ };
507
+ });
508
+ // ---------------------------------------------------------------------------
509
+ // Tool: Portfolio Dashboard
510
+ // ---------------------------------------------------------------------------
511
+ server.registerTool("hardening_dashboard", {
512
+ title: "Portfolio Production Readiness Dashboard",
513
+ description: `Compare production readiness across all Vaspera projects. Shows which projects have been audited, their readiness scores, issue counts, and what phases have been completed. Use to prioritize which project to harden next.`,
514
+ inputSchema: {
515
+ base_dir: z
516
+ .string()
517
+ .optional()
518
+ .describe("Base directory. Defaults to VASPERA_PROJECTS_DIR or ~/Documents/GitHub"),
519
+ },
520
+ annotations: {
521
+ readOnlyHint: true,
522
+ destructiveHint: false,
523
+ idempotentHint: true,
524
+ openWorldHint: false,
525
+ },
526
+ }, async ({ base_dir }) => {
527
+ const dir = base_dir || DEFAULT_PROJECTS_DIR;
528
+ if (!(await dirExists(dir))) {
529
+ return {
530
+ content: [
531
+ { type: "text", text: `Error: Directory not found: ${dir}` },
532
+ ],
533
+ };
534
+ }
535
+ const projects = await discoverProjects(dir);
536
+ const dashboard = await Promise.all(projects.map(async (p) => {
537
+ const name = basename(p);
538
+ const hasCommands = await dirExists(join(p, ".claude", "commands"));
539
+ const auditContent = await safeReadFile(join(p, "AUDIT.md"));
540
+ const reportContent = await safeReadFile(join(p, "HARDENING-REPORT.md"));
541
+ const rlsContent = await safeReadFile(join(p, "RLS-REPORT.md"));
542
+ const hasPkg = await safeReadFile(join(p, "package.json"));
543
+ let stack = "unknown";
544
+ if (hasPkg) {
545
+ try {
546
+ const pkg = JSON.parse(hasPkg);
547
+ const deps = {
548
+ ...pkg.dependencies,
549
+ ...pkg.devDependencies,
550
+ };
551
+ const frameworks = [];
552
+ if (deps["next"])
553
+ frameworks.push("Next.js");
554
+ if (deps["react"])
555
+ frameworks.push("React");
556
+ if (deps["@supabase/supabase-js"])
557
+ frameworks.push("Supabase");
558
+ if (deps["expo"])
559
+ frameworks.push("Expo");
560
+ stack = frameworks.join(" + ") || "Node.js";
561
+ }
562
+ catch {
563
+ stack = "unknown";
564
+ }
565
+ }
566
+ let score = null;
567
+ let counts = null;
568
+ if (auditContent) {
569
+ score = parseAuditScore(auditContent);
570
+ counts = parseAuditCounts(auditContent);
571
+ }
572
+ return {
573
+ name,
574
+ path: p,
575
+ stack,
576
+ hardening_installed: hasCommands,
577
+ audited: !!auditContent,
578
+ readiness_score: score,
579
+ issue_counts: counts,
580
+ hardening_complete: !!reportContent,
581
+ rls_audited: !!rlsContent,
582
+ status: reportContent
583
+ ? "hardened"
584
+ : auditContent
585
+ ? "audited"
586
+ : hasCommands
587
+ ? "commands_installed"
588
+ : "not_started",
589
+ };
590
+ }));
591
+ // Sort: not_started first, then by score ascending
592
+ dashboard.sort((a, b) => {
593
+ const statusOrder = {
594
+ not_started: 0,
595
+ commands_installed: 1,
596
+ audited: 2,
597
+ hardened: 3,
598
+ };
599
+ const aOrder = statusOrder[a.status] ?? 99;
600
+ const bOrder = statusOrder[b.status] ?? 99;
601
+ if (aOrder !== bOrder)
602
+ return aOrder - bOrder;
603
+ return (a.readiness_score ?? 0) - (b.readiness_score ?? 0);
604
+ });
605
+ const output = {
606
+ base_dir: dir,
607
+ total_projects: dashboard.length,
608
+ summary: {
609
+ not_started: dashboard.filter((p) => p.status === "not_started").length,
610
+ commands_installed: dashboard.filter((p) => p.status === "commands_installed").length,
611
+ audited: dashboard.filter((p) => p.status === "audited").length,
612
+ hardened: dashboard.filter((p) => p.status === "hardened").length,
613
+ },
614
+ projects: dashboard,
615
+ };
616
+ return {
617
+ content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
618
+ structuredContent: output,
619
+ };
620
+ });
621
+ // ---------------------------------------------------------------------------
622
+ // Tool: List Available Commands
623
+ // ---------------------------------------------------------------------------
624
+ server.registerTool("hardening_list_commands", {
625
+ title: "List Available Hardening Commands",
626
+ description: `List all available production hardening commands with their descriptions. Use to understand what commands are available and what order to run them.`,
627
+ inputSchema: {},
628
+ annotations: {
629
+ readOnlyHint: true,
630
+ destructiveHint: false,
631
+ idempotentHint: true,
632
+ openWorldHint: false,
633
+ },
634
+ }, async () => {
635
+ const commands = listCommands().map((cmd) => ({
636
+ name: cmd.name,
637
+ description: cmd.description,
638
+ }));
639
+ const output = {
640
+ command_count: commands.length,
641
+ recommended_order: [
642
+ "audit",
643
+ "fix-critical",
644
+ "fix-high",
645
+ "fix-rls",
646
+ "fix-medium",
647
+ "add-tests",
648
+ "verify",
649
+ ],
650
+ full_pipeline: "harden (runs all of the above in sequence)",
651
+ commands,
652
+ };
653
+ return {
654
+ content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
655
+ structuredContent: output,
656
+ };
657
+ });
658
+ // ---------------------------------------------------------------------------
659
+ // Tool: Start Certification
660
+ // ---------------------------------------------------------------------------
661
+ server.registerTool("certification_start", {
662
+ title: "Start Enterprise Certification",
663
+ description: `Initialize an enterprise certification process for a project. Creates certification directory and metadata. Returns certification ID for tracking.`,
664
+ inputSchema: {
665
+ project_path: z.string().describe("Absolute path to the project root"),
666
+ agents: z
667
+ .array(z.enum(["security", "reliability", "typesafety", "performance", "quality", "redteam"]))
668
+ .optional()
669
+ .describe("Which agents to run. Defaults to all."),
670
+ },
671
+ annotations: {
672
+ readOnlyHint: false,
673
+ destructiveHint: false,
674
+ idempotentHint: false,
675
+ openWorldHint: false,
676
+ },
677
+ }, async ({ project_path, agents }) => {
678
+ const allAgents = ["security", "reliability", "typesafety", "performance", "quality", "redteam"];
679
+ const requestedAgents = agents || allAgents;
680
+ const certId = generateCertificationId(project_path);
681
+ const certLogger = createChildLogger({ certId, project: basename(project_path) });
682
+ certLogger.info("certification.started", { agents: requestedAgents });
683
+ const metadata = await initializeCertification(project_path, certId, requestedAgents);
684
+ return {
685
+ content: [{ type: "text", text: JSON.stringify(metadata, null, 2) }],
686
+ };
687
+ });
688
+ // ---------------------------------------------------------------------------
689
+ // Tool: Run Deterministic Scanners
690
+ // ---------------------------------------------------------------------------
691
+ server.registerTool("certification_scan", {
692
+ title: "Run Deterministic Scanners",
693
+ description: `Run deterministic security scanners before LLM agents. Returns findings with confidence: 100.
694
+
695
+ **Scanners**: Semgrep, npm audit, gitleaks, TypeScript, ESLint, Bandit, Gosec, Trivy
696
+
697
+ **Auto-detection**: When auto_detect is true, automatically enables language-specific scanners based on project files (e.g., Python files → Bandit, Go files → Gosec).`,
698
+ inputSchema: {
699
+ project_path: z.string().describe("Absolute path to the project root"),
700
+ certification_id: z.string().optional().describe("Certification ID to associate findings with"),
701
+ auto_detect: z.boolean().optional().default(false).describe("Auto-detect languages and enable appropriate scanners"),
702
+ scanners: z
703
+ .object({
704
+ semgrep: z.boolean().optional().default(true).describe("Run Semgrep for OWASP patterns"),
705
+ dependencies: z.boolean().optional().default(true).describe("Run npm audit for dependency vulns"),
706
+ secrets: z.boolean().optional().default(true).describe("Run gitleaks for secrets"),
707
+ typescript: z.boolean().optional().default(true).describe("Run TypeScript analysis"),
708
+ eslint: z.boolean().optional().describe("Run ESLint for code quality"),
709
+ bandit: z.boolean().optional().describe("Run Bandit for Python security"),
710
+ gosec: z.boolean().optional().describe("Run Gosec for Go security"),
711
+ trivy: z.boolean().optional().describe("Run Trivy for container/IaC vulns"),
712
+ })
713
+ .optional()
714
+ .describe("Which scanners to run. Ignored if auto_detect is true."),
715
+ submit_findings: z.boolean().optional().default(false).describe("Submit findings to certification"),
716
+ },
717
+ annotations: {
718
+ readOnlyHint: false,
719
+ destructiveHint: false,
720
+ idempotentHint: true,
721
+ openWorldHint: false,
722
+ },
723
+ }, async ({ project_path, certification_id, auto_detect, scanners, submit_findings }) => {
724
+ const scanLogger = createChildLogger({
725
+ certId: certification_id,
726
+ project: basename(project_path),
727
+ });
728
+ scanLogger.info("scanners.starting", { scanners, auto_detect });
729
+ // Use auto-detection or manual scanner selection
730
+ let result;
731
+ let detectedLanguages;
732
+ if (auto_detect) {
733
+ const { runAllScannersWithAutoDetect } = await import("./scanners/index.js");
734
+ const autoResult = await runAllScannersWithAutoDetect(project_path);
735
+ result = autoResult;
736
+ detectedLanguages = autoResult.detectedLanguages;
737
+ scanLogger.info("scanners.auto_detected", { languages: detectedLanguages });
738
+ }
739
+ else {
740
+ result = await runAllScanners(project_path, scanners);
741
+ }
742
+ // If certification_id provided and submit_findings is true, submit to certification
743
+ if (certification_id && submit_findings && result.totalFindings > 0) {
744
+ const certFindings = scannerFindingsToCertificationFindings(result);
745
+ // Ensure security agent is started for scanner findings
746
+ let agentFindings = await getAgentFindings(project_path, certification_id, "security");
747
+ if (!agentFindings) {
748
+ await startAgent(project_path, certification_id, "security");
749
+ }
750
+ // Submit each finding
751
+ for (const finding of certFindings) {
752
+ await submitFinding(project_path, certification_id, "security", {
753
+ id: finding.id,
754
+ severity: finding.severity,
755
+ category: finding.category,
756
+ file: finding.file,
757
+ line: finding.line,
758
+ description: finding.description,
759
+ evidence: finding.evidence,
760
+ confidence: 100,
761
+ // Store scanner metadata
762
+ });
763
+ }
764
+ // Update certification metadata
765
+ await updateCertificationMetadata(project_path, certification_id, {
766
+ scanners_run: true,
767
+ scanners_completed_at: new Date().toISOString(),
768
+ scanner_findings_count: result.totalFindings,
769
+ });
770
+ scanLogger.info("scanners.findings_submitted", {
771
+ count: certFindings.length,
772
+ certId: certification_id,
773
+ });
774
+ }
775
+ // Generate summary
776
+ const summary = generateScannerSummary(result);
777
+ // Add auto-detection info to summary if used
778
+ let summaryWithDetection = summary;
779
+ if (detectedLanguages) {
780
+ const detected = Object.entries(detectedLanguages)
781
+ .filter(([, v]) => v)
782
+ .map(([k]) => k);
783
+ summaryWithDetection = `## Auto-Detected Languages\n\n${detected.join(", ") || "None detected"}\n\n${summary}`;
784
+ }
785
+ return {
786
+ content: [
787
+ { type: "text", text: summaryWithDetection },
788
+ { type: "text", text: "\n\n---\n\nRaw results:\n" + JSON.stringify({
789
+ totalFindings: result.totalFindings,
790
+ bySeverity: result.bySeverity,
791
+ byScanner: result.byScanner,
792
+ duration: result.totalDuration,
793
+ allSucceeded: result.allSucceeded,
794
+ failedScanners: result.failedScanners,
795
+ detectedLanguages,
796
+ }, null, 2) },
797
+ ],
798
+ structuredContent: {
799
+ totalFindings: result.totalFindings,
800
+ bySeverity: result.bySeverity,
801
+ byScanner: result.byScanner,
802
+ duration: result.totalDuration,
803
+ allSucceeded: result.allSucceeded,
804
+ failedScanners: result.failedScanners,
805
+ findingsSubmitted: submit_findings && certification_id ? result.totalFindings : 0,
806
+ detectedLanguages,
807
+ },
808
+ };
809
+ });
810
+ // ---------------------------------------------------------------------------
811
+ // Tool: Check Scanner Availability
812
+ // ---------------------------------------------------------------------------
813
+ server.registerTool("certification_scanners_available", {
814
+ title: "Check Scanner Availability",
815
+ description: `Check which deterministic scanners are available on the system.`,
816
+ inputSchema: {},
817
+ annotations: {
818
+ readOnlyHint: true,
819
+ destructiveHint: false,
820
+ idempotentHint: true,
821
+ openWorldHint: false,
822
+ },
823
+ }, async () => {
824
+ const availability = await checkScannersAvailable();
825
+ const lines = [
826
+ "# Scanner Availability",
827
+ "",
828
+ "| Scanner | Available | Version | Notes |",
829
+ "|---------|-----------|---------|-------|",
830
+ ];
831
+ for (const [scanner, info] of Object.entries(availability)) {
832
+ const status = info.available ? "✅" : "❌";
833
+ const version = info.version || "-";
834
+ const notes = info.error || "";
835
+ lines.push(`| ${scanner} | ${status} | ${version} | ${notes} |`);
836
+ }
837
+ lines.push("");
838
+ lines.push("## Installation");
839
+ lines.push("");
840
+ lines.push("- **Semgrep**: `pip install semgrep` or `brew install semgrep`");
841
+ lines.push("- **gitleaks**: `brew install gitleaks` or download from GitHub releases");
842
+ lines.push("- **npm**: Built-in with Node.js");
843
+ lines.push("- **TypeScript**: Included as package dependency");
844
+ return {
845
+ content: [{ type: "text", text: lines.join("\n") }],
846
+ structuredContent: availability,
847
+ };
848
+ });
849
+ // ---------------------------------------------------------------------------
850
+ // Tool: Detect Project Languages
851
+ // ---------------------------------------------------------------------------
852
+ server.registerTool("certification_detect_languages", {
853
+ title: "Detect Project Languages",
854
+ description: `Automatically detect programming languages and technologies used in a project. Returns which scanners will be auto-enabled based on the detected languages.
855
+
856
+ **Detection includes:**
857
+ - JavaScript/TypeScript (package.json, *.js, *.ts)
858
+ - Python (requirements.txt, setup.py, *.py)
859
+ - Go (go.mod, *.go)
860
+ - Ruby (Gemfile, *.rb)
861
+ - Java (pom.xml, build.gradle, *.java)
862
+ - Docker (Dockerfile, docker-compose.yml)
863
+ - Terraform (*.tf)`,
864
+ inputSchema: {
865
+ project_path: z.string().describe("Path to the project directory to analyze"),
866
+ },
867
+ annotations: {
868
+ readOnlyHint: true,
869
+ destructiveHint: false,
870
+ idempotentHint: true,
871
+ openWorldHint: false,
872
+ },
873
+ }, async ({ project_path }) => {
874
+ const { detectProjectLanguages } = await import("./scanners/index.js");
875
+ const languages = await detectProjectLanguages(project_path);
876
+ // Build scanner recommendations
877
+ const recommendedScanners = [];
878
+ // Always recommended
879
+ recommendedScanners.push("semgrep (security patterns)");
880
+ recommendedScanners.push("gitleaks (secrets detection)");
881
+ if (languages.javascript) {
882
+ recommendedScanners.push("npm-audit (dependency vulnerabilities)");
883
+ recommendedScanners.push("tsc (TypeScript type safety)");
884
+ recommendedScanners.push("eslint (code quality)");
885
+ }
886
+ if (languages.python) {
887
+ recommendedScanners.push("bandit (Python security)");
888
+ }
889
+ if (languages.go) {
890
+ recommendedScanners.push("gosec (Go security)");
891
+ }
892
+ if (languages.ruby) {
893
+ recommendedScanners.push("brakeman (Ruby on Rails security)");
894
+ }
895
+ if (languages.docker || languages.terraform) {
896
+ recommendedScanners.push("trivy (container/IaC vulnerabilities)");
897
+ }
898
+ const lines = [
899
+ "# Project Language Detection",
900
+ "",
901
+ `**Project**: ${project_path}`,
902
+ "",
903
+ "## Detected Languages",
904
+ "",
905
+ `| Language/Tech | Detected |`,
906
+ `|---------------|----------|`,
907
+ `| JavaScript/TypeScript | ${languages.javascript ? "✅" : "❌"} |`,
908
+ `| Python | ${languages.python ? "✅" : "❌"} |`,
909
+ `| Go | ${languages.go ? "✅" : "❌"} |`,
910
+ `| Ruby | ${languages.ruby ? "✅" : "❌"} |`,
911
+ `| Java | ${languages.java ? "✅" : "❌"} |`,
912
+ `| Docker | ${languages.docker ? "✅" : "❌"} |`,
913
+ `| Terraform | ${languages.terraform ? "✅" : "❌"} |`,
914
+ "",
915
+ "## Recommended Scanners",
916
+ "",
917
+ ...recommendedScanners.map(s => `- ${s}`),
918
+ "",
919
+ "## Usage",
920
+ "",
921
+ "Use `certification_scan` with auto-detect or specify scanners manually:",
922
+ "```",
923
+ "certification_scan({ project_path, auto_detect: true })",
924
+ "```",
925
+ ];
926
+ return {
927
+ content: [{ type: "text", text: lines.join("\n") }],
928
+ structuredContent: {
929
+ project_path,
930
+ languages,
931
+ recommended_scanners: recommendedScanners,
932
+ },
933
+ };
934
+ });
935
+ // ---------------------------------------------------------------------------
936
+ // Tool: Scanner Install Helper
937
+ // ---------------------------------------------------------------------------
938
+ server.registerTool("certification_install_scanners", {
939
+ title: "Get Scanner Installation Commands",
940
+ description: `Get platform-specific installation commands for missing security scanners. Use this to help users install semgrep, gitleaks, trivy, and other scanners. Set run_install: true to execute installation commands (requires confirmation).`,
941
+ inputSchema: {
942
+ platform: z.enum(["macos", "linux", "windows"]).optional().describe("Target platform (auto-detected if not specified)"),
943
+ scanners: z.array(z.enum(["semgrep", "npm-audit", "gitleaks", "tsc", "eslint", "bandit", "gosec", "brakeman", "trivy"])).optional().describe("Specific scanners to get install commands for (all if not specified)"),
944
+ run_install: z.boolean().optional().describe("Execute the installation commands. Default: false (just show commands)"),
945
+ confirm_install: z.boolean().optional().describe("Required when run_install is true - confirms you want to install"),
946
+ },
947
+ annotations: {
948
+ readOnlyHint: false,
949
+ destructiveHint: false,
950
+ idempotentHint: false,
951
+ openWorldHint: true,
952
+ },
953
+ }, async ({ platform, scanners, run_install, confirm_install }) => {
954
+ // Auto-detect platform
955
+ const detectedPlatform = platform || (process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : "linux");
956
+ // Get availability status
957
+ const availability = await checkScannersAvailable();
958
+ // Get install commands
959
+ const allCommands = getScannerInstallCommands();
960
+ // Filter to requested scanners
961
+ const scannerList = scanners || Object.keys(allCommands);
962
+ const lines = [
963
+ "# Scanner Installation Guide",
964
+ "",
965
+ `**Platform**: ${detectedPlatform}`,
966
+ "",
967
+ "## Missing Scanners",
968
+ "",
969
+ ];
970
+ const missing = [];
971
+ const installed = [];
972
+ for (const scanner of scannerList) {
973
+ const info = availability[scanner];
974
+ if (!info?.available) {
975
+ missing.push(scanner);
976
+ }
977
+ else {
978
+ installed.push(scanner);
979
+ }
980
+ }
981
+ if (missing.length === 0) {
982
+ lines.push("✅ All requested scanners are installed!");
983
+ lines.push("");
984
+ }
985
+ else {
986
+ for (const scanner of missing) {
987
+ const cmd = allCommands[scanner];
988
+ if (!cmd)
989
+ continue;
990
+ lines.push(`### ${cmd.name}`);
991
+ lines.push("");
992
+ lines.push(`*${cmd.description}*`);
993
+ lines.push("");
994
+ lines.push("**Install command:**");
995
+ lines.push("```bash");
996
+ lines.push(cmd.installCommands[detectedPlatform]);
997
+ lines.push("```");
998
+ lines.push("");
999
+ lines.push(`📚 [Documentation](${cmd.documentation})`);
1000
+ lines.push("");
1001
+ }
1002
+ }
1003
+ if (installed.length > 0) {
1004
+ lines.push("## Already Installed");
1005
+ lines.push("");
1006
+ for (const scanner of installed) {
1007
+ const info = availability[scanner];
1008
+ lines.push(`- ✅ **${scanner}** (${info?.version || "unknown version"})`);
1009
+ }
1010
+ lines.push("");
1011
+ }
1012
+ // Quick install script for all missing
1013
+ if (missing.length > 0) {
1014
+ lines.push("## Quick Install Script");
1015
+ lines.push("");
1016
+ lines.push("Install all missing scanners at once:");
1017
+ lines.push("");
1018
+ lines.push("```bash");
1019
+ for (const scanner of missing) {
1020
+ const cmd = allCommands[scanner];
1021
+ if (cmd) {
1022
+ lines.push(`# ${cmd.name}`);
1023
+ lines.push(cmd.installCommands[detectedPlatform]);
1024
+ lines.push("");
1025
+ }
1026
+ }
1027
+ lines.push("```");
1028
+ }
1029
+ // Handle run_install option
1030
+ if (run_install && missing.length > 0) {
1031
+ if (!confirm_install) {
1032
+ lines.push("");
1033
+ lines.push("## ⚠️ Confirmation Required");
1034
+ lines.push("");
1035
+ lines.push("To install scanners, run again with `confirm_install: true`");
1036
+ lines.push("");
1037
+ lines.push("This will execute the following commands:");
1038
+ for (const scanner of missing) {
1039
+ const cmd = allCommands[scanner];
1040
+ if (cmd) {
1041
+ lines.push(`- ${cmd.installCommands[detectedPlatform]}`);
1042
+ }
1043
+ }
1044
+ return {
1045
+ content: [{ type: "text", text: lines.join("\n") }],
1046
+ structuredContent: {
1047
+ platform: detectedPlatform,
1048
+ missing,
1049
+ installed,
1050
+ requiresConfirmation: true,
1051
+ },
1052
+ };
1053
+ }
1054
+ // Execute installation
1055
+ lines.push("");
1056
+ lines.push("## Installation Results");
1057
+ lines.push("");
1058
+ const installResults = {};
1059
+ for (const scanner of missing) {
1060
+ const cmd = allCommands[scanner];
1061
+ if (!cmd)
1062
+ continue;
1063
+ const installCmd = cmd.installCommands[detectedPlatform];
1064
+ lines.push(`### Installing ${cmd.name}...`);
1065
+ lines.push("");
1066
+ lines.push(`\`${installCmd}\``);
1067
+ lines.push("");
1068
+ try {
1069
+ const { exec } = await import("child_process");
1070
+ const { promisify } = await import("util");
1071
+ const execAsync = promisify(exec);
1072
+ const { stdout, stderr } = await execAsync(installCmd, {
1073
+ timeout: 300000, // 5 minutes per scanner
1074
+ });
1075
+ installResults[scanner] = { success: true, output: stdout };
1076
+ lines.push("✅ **Success**");
1077
+ if (stdout.trim()) {
1078
+ lines.push("");
1079
+ lines.push("```");
1080
+ lines.push(stdout.trim().slice(0, 500));
1081
+ lines.push("```");
1082
+ }
1083
+ }
1084
+ catch (error) {
1085
+ const errMsg = error instanceof Error ? error.message : String(error);
1086
+ installResults[scanner] = { success: false, error: errMsg };
1087
+ lines.push(`❌ **Failed**: ${errMsg.slice(0, 200)}`);
1088
+ }
1089
+ lines.push("");
1090
+ }
1091
+ // Verify installations
1092
+ lines.push("## Verification");
1093
+ lines.push("");
1094
+ const newAvailability = await checkScannersAvailable();
1095
+ for (const scanner of missing) {
1096
+ const info = newAvailability[scanner];
1097
+ if (info?.available) {
1098
+ lines.push(`✅ **${scanner}** installed (${info.version || "version unknown"})`);
1099
+ }
1100
+ else {
1101
+ lines.push(`❌ **${scanner}** not available: ${info?.error || "unknown error"}`);
1102
+ }
1103
+ }
1104
+ return {
1105
+ content: [{ type: "text", text: lines.join("\n") }],
1106
+ structuredContent: {
1107
+ platform: detectedPlatform,
1108
+ installResults,
1109
+ newAvailability: Object.fromEntries(missing.map((s) => [s, newAvailability[s]])),
1110
+ },
1111
+ };
1112
+ }
1113
+ return {
1114
+ content: [{ type: "text", text: lines.join("\n") }],
1115
+ structuredContent: {
1116
+ platform: detectedPlatform,
1117
+ missing,
1118
+ installed,
1119
+ installCommands: Object.fromEntries(missing.map((s) => [s, allCommands[s]?.installCommands[detectedPlatform]])),
1120
+ },
1121
+ };
1122
+ });
1123
+ // ---------------------------------------------------------------------------
1124
+ // Tool: Certification Status
1125
+ // ---------------------------------------------------------------------------
1126
+ server.registerTool("certification_status", {
1127
+ title: "Get Certification Status",
1128
+ description: `Check the current status of a certification process including which agents have completed.`,
1129
+ inputSchema: {
1130
+ project_path: z.string().describe("Absolute path to the project root"),
1131
+ certification_id: z.string().describe("Certification ID"),
1132
+ },
1133
+ annotations: {
1134
+ readOnlyHint: true,
1135
+ destructiveHint: false,
1136
+ idempotentHint: true,
1137
+ openWorldHint: false,
1138
+ },
1139
+ }, async ({ project_path, certification_id }) => {
1140
+ const certification = await getCertification(project_path, certification_id);
1141
+ if (!certification) {
1142
+ return {
1143
+ content: [{ type: "text", text: `Certification ${certification_id} not found` }],
1144
+ };
1145
+ }
1146
+ const status = {
1147
+ metadata: certification.metadata,
1148
+ agents_status: Object.entries(certification.agents).map(([name, data]) => ({
1149
+ agent: name,
1150
+ status: data?.status,
1151
+ findings_count: data?.findings.length ?? 0,
1152
+ })),
1153
+ total_findings: Object.values(certification.agents).reduce((sum, a) => sum + (a?.findings.length ?? 0), 0),
1154
+ cross_verifications: certification.cross_verifications.length,
1155
+ red_team_challenges: certification.red_team_challenges.length,
1156
+ };
1157
+ return {
1158
+ content: [{ type: "text", text: JSON.stringify(status, null, 2) }],
1159
+ };
1160
+ });
1161
+ // ---------------------------------------------------------------------------
1162
+ // Tool: Submit Finding
1163
+ // ---------------------------------------------------------------------------
1164
+ server.registerTool("agent_submit_finding", {
1165
+ title: "Submit Agent Finding",
1166
+ description: `Submit a finding from a validation agent during certification. Each finding must have a unique ID, severity, and evidence.`,
1167
+ inputSchema: {
1168
+ project_path: z.string().describe("Absolute path to the project root"),
1169
+ certification_id: z.string().describe("Certification ID"),
1170
+ agent: z.enum(["security", "reliability", "typesafety", "performance", "quality", "redteam"]),
1171
+ finding: z.object({
1172
+ id: z.string().min(1).max(50).regex(/^[a-z]+-\d{3}$/).describe("Unique finding ID (e.g., sec-001, rel-002)"),
1173
+ severity: z.enum(["critical", "high", "medium", "low", "info"]),
1174
+ category: z.string().min(1).max(100).describe("Category of the finding"),
1175
+ file: z.string().max(500).optional().describe("File path where issue found"),
1176
+ line: z.number().int().min(1).max(1000000).optional().describe("Line number"),
1177
+ description: z.string().min(1).max(10000).describe("Description of the issue"),
1178
+ evidence: z.string().min(1).max(50000).describe("Evidence supporting the finding"),
1179
+ confidence: z.number().min(0).max(100).describe("Confidence score 0-100"),
1180
+ cross_references: z.array(z.string().max(50)).max(20).optional().describe("Related finding IDs"),
1181
+ }),
1182
+ },
1183
+ annotations: {
1184
+ readOnlyHint: false,
1185
+ destructiveHint: false,
1186
+ idempotentHint: false,
1187
+ openWorldHint: false,
1188
+ },
1189
+ }, async ({ project_path, certification_id, agent, finding }) => {
1190
+ const certLogger = createChildLogger({ certId: certification_id, agent });
1191
+ // Ensure agent is started
1192
+ let agentFindings = await getAgentFindings(project_path, certification_id, agent);
1193
+ if (!agentFindings) {
1194
+ certLogger.info("agent.started");
1195
+ agentFindings = await startAgent(project_path, certification_id, agent);
1196
+ }
1197
+ const submittedFinding = await submitFinding(project_path, certification_id, agent, finding);
1198
+ certLogger.info("finding.submitted", {
1199
+ findingId: finding.id,
1200
+ severity: finding.severity,
1201
+ confidence: finding.confidence,
1202
+ });
1203
+ return {
1204
+ content: [{ type: "text", text: JSON.stringify({ submitted: submittedFinding }, null, 2) }],
1205
+ };
1206
+ });
1207
+ // ---------------------------------------------------------------------------
1208
+ // Tool: Complete Agent
1209
+ // ---------------------------------------------------------------------------
1210
+ server.registerTool("agent_complete", {
1211
+ title: "Complete Agent Run",
1212
+ description: `Signal that an agent has completed its validation run. Must include summary with findings count and confidence score.`,
1213
+ inputSchema: {
1214
+ project_path: z.string().describe("Absolute path to the project root"),
1215
+ certification_id: z.string().describe("Certification ID"),
1216
+ agent: z.enum(["security", "reliability", "typesafety", "performance", "quality", "redteam"]),
1217
+ summary: z.object({
1218
+ total_findings: z.number(),
1219
+ by_severity: z.object({
1220
+ critical: z.number(),
1221
+ high: z.number(),
1222
+ medium: z.number(),
1223
+ low: z.number(),
1224
+ info: z.number(),
1225
+ }),
1226
+ confidence_score: z.number().min(0).max(100),
1227
+ coverage_areas: z.array(z.string()),
1228
+ notes: z.string().optional(),
1229
+ }),
1230
+ },
1231
+ annotations: {
1232
+ readOnlyHint: false,
1233
+ destructiveHint: false,
1234
+ idempotentHint: false,
1235
+ openWorldHint: false,
1236
+ },
1237
+ }, async ({ project_path, certification_id, agent, summary }) => {
1238
+ const certLogger = createChildLogger({ certId: certification_id, agent });
1239
+ const completed = await completeAgent(project_path, certification_id, agent, summary);
1240
+ certLogger.info("agent.completed", {
1241
+ totalFindings: summary.total_findings,
1242
+ confidenceScore: summary.confidence_score,
1243
+ bySeverity: summary.by_severity,
1244
+ });
1245
+ return {
1246
+ content: [{ type: "text", text: JSON.stringify({ completed: agent, summary: completed.summary }, null, 2) }],
1247
+ };
1248
+ });
1249
+ // ---------------------------------------------------------------------------
1250
+ // Tool: Cross-Verify Finding
1251
+ // ---------------------------------------------------------------------------
1252
+ server.registerTool("agent_cross_verify", {
1253
+ title: "Cross-Verify Finding",
1254
+ description: `Cross-verify a finding from another agent. Increases or decreases confidence based on verification.`,
1255
+ inputSchema: {
1256
+ project_path: z.string().describe("Absolute path to the project root"),
1257
+ certification_id: z.string().describe("Certification ID"),
1258
+ verifying_agent: z.enum(["security", "reliability", "typesafety", "performance", "quality", "redteam"]),
1259
+ finding_id: z.string().describe("ID of the finding to verify"),
1260
+ verdict: z.enum(["confirmed", "disputed", "inconclusive"]),
1261
+ evidence: z.string().describe("Evidence for the verdict"),
1262
+ adjusted_confidence: z.number().min(0).max(100).optional(),
1263
+ },
1264
+ annotations: {
1265
+ readOnlyHint: false,
1266
+ destructiveHint: false,
1267
+ idempotentHint: false,
1268
+ openWorldHint: false,
1269
+ },
1270
+ }, async ({ project_path, certification_id, verifying_agent, finding_id, verdict, evidence, adjusted_confidence }) => {
1271
+ const verification = await addCrossVerification(project_path, certification_id, {
1272
+ finding_id,
1273
+ verifying_agent,
1274
+ verdict,
1275
+ evidence,
1276
+ adjusted_confidence,
1277
+ });
1278
+ return {
1279
+ content: [{ type: "text", text: JSON.stringify({ verification }, null, 2) }],
1280
+ };
1281
+ });
1282
+ // ---------------------------------------------------------------------------
1283
+ // Tool: Auto Cross-Verify (Batch)
1284
+ // ---------------------------------------------------------------------------
1285
+ server.registerTool("certification_cross_verify", {
1286
+ title: "Cross-Verify Critical Findings",
1287
+ description: `Batch cross-verify critical findings. In "auto" mode (default), automatically verifies all critical findings based on agent domain overlap. In "manual" mode, verifies only specified finding IDs. Run this after all agents complete to unblock consensus calculation.`,
1288
+ inputSchema: {
1289
+ project_path: z.string().describe("Absolute path to the project root"),
1290
+ certification_id: z.string().describe("Certification ID"),
1291
+ mode: z.enum(["auto", "manual"]).optional().default("auto").describe("auto: verify all critical findings; manual: verify specific findings"),
1292
+ finding_ids: z.array(z.string()).optional().describe("Specific finding IDs to verify (required for manual mode)"),
1293
+ },
1294
+ annotations: {
1295
+ readOnlyHint: false,
1296
+ destructiveHint: false,
1297
+ idempotentHint: false,
1298
+ openWorldHint: false,
1299
+ },
1300
+ }, async ({ project_path, certification_id, mode = "auto", finding_ids }) => {
1301
+ const certLogger = createChildLogger({ certId: certification_id, project: basename(project_path) });
1302
+ // Check if all agents have completed
1303
+ const allComplete = await allAgentsCompleted(project_path, certification_id);
1304
+ if (!allComplete) {
1305
+ certLogger.warn("cross_verify.agents_not_complete");
1306
+ return {
1307
+ content: [{
1308
+ type: "text",
1309
+ text: JSON.stringify({
1310
+ error: "Not all agents have completed. Wait for all agents to finish before cross-verification.",
1311
+ ready: false,
1312
+ }, null, 2),
1313
+ }],
1314
+ };
1315
+ }
1316
+ // Validate manual mode has finding_ids
1317
+ if (mode === "manual" && (!finding_ids || finding_ids.length === 0)) {
1318
+ return {
1319
+ content: [{
1320
+ type: "text",
1321
+ text: JSON.stringify({
1322
+ error: "Manual mode requires finding_ids to be specified",
1323
+ ready: false,
1324
+ }, null, 2),
1325
+ }],
1326
+ };
1327
+ }
1328
+ const result = await autoCrossVerify(project_path, certification_id, mode, finding_ids);
1329
+ certLogger.info("cross_verify.completed", {
1330
+ mode,
1331
+ criticalFindings: result.criticalFindingsCount,
1332
+ verificationsCreated: result.verificationsCreated,
1333
+ allVerified: result.allCriticalVerified,
1334
+ });
1335
+ return {
1336
+ content: [{
1337
+ type: "text",
1338
+ text: JSON.stringify({
1339
+ mode,
1340
+ criticalFindingsCount: result.criticalFindingsCount,
1341
+ verificationsCreated: result.verificationsCreated,
1342
+ verifiedFindingIds: result.verifiedFindingIds,
1343
+ allCriticalVerified: result.allCriticalVerified,
1344
+ readyForConsensus: result.allCriticalVerified,
1345
+ nextStep: result.allCriticalVerified
1346
+ ? "Run certification_consensus to calculate final score"
1347
+ : "Some critical findings could not be auto-verified. Use agent_cross_verify for manual verification.",
1348
+ }, null, 2),
1349
+ }],
1350
+ };
1351
+ });
1352
+ // ---------------------------------------------------------------------------
1353
+ // Tool: Red Team Challenge
1354
+ // ---------------------------------------------------------------------------
1355
+ server.registerTool("redteam_challenge", {
1356
+ title: "Red Team Challenge",
1357
+ description: `Red team challenges an area marked clean by another agent. Used to dispute false negatives.`,
1358
+ inputSchema: {
1359
+ project_path: z.string().describe("Absolute path to the project root"),
1360
+ certification_id: z.string().describe("Certification ID"),
1361
+ target_agent: z.enum(["security", "reliability", "typesafety", "performance", "quality"]),
1362
+ area: z.string().describe("The area being challenged"),
1363
+ challenge_type: z.enum(["missed_issue", "false_negative", "incomplete_coverage", "edge_case"]),
1364
+ evidence: z.string().describe("Evidence for the challenge"),
1365
+ severity_if_valid: z.enum(["critical", "high", "medium", "low"]),
1366
+ },
1367
+ annotations: {
1368
+ readOnlyHint: false,
1369
+ destructiveHint: false,
1370
+ idempotentHint: false,
1371
+ openWorldHint: false,
1372
+ },
1373
+ }, async ({ project_path, certification_id, target_agent, area, challenge_type, evidence, severity_if_valid }) => {
1374
+ const challenge = await addRedTeamChallenge(project_path, certification_id, {
1375
+ target_agent,
1376
+ area,
1377
+ challenge_type,
1378
+ evidence,
1379
+ severity_if_valid,
1380
+ });
1381
+ return {
1382
+ content: [{ type: "text", text: JSON.stringify({ challenge }, null, 2) }],
1383
+ };
1384
+ });
1385
+ // ---------------------------------------------------------------------------
1386
+ // Tool: Calculate Consensus
1387
+ // ---------------------------------------------------------------------------
1388
+ server.registerTool("certification_consensus", {
1389
+ title: "Calculate Certification Consensus",
1390
+ description: `Calculate consensus score and certification level from all agent findings. Requires all requested agents to have completed. Set auto_cross_verify: true (default) to automatically cross-verify critical findings if blocked.`,
1391
+ inputSchema: {
1392
+ project_path: z.string().describe("Absolute path to the project root"),
1393
+ certification_id: z.string().describe("Certification ID"),
1394
+ auto_cross_verify: z.boolean().optional().default(true).describe("Automatically cross-verify critical findings if consensus is blocked"),
1395
+ },
1396
+ annotations: {
1397
+ readOnlyHint: false,
1398
+ destructiveHint: false,
1399
+ idempotentHint: true,
1400
+ openWorldHint: false,
1401
+ },
1402
+ }, async ({ project_path, certification_id, auto_cross_verify = true }) => {
1403
+ const certLogger = createChildLogger({ certId: certification_id, project: basename(project_path) });
1404
+ let certification = await getCertification(project_path, certification_id);
1405
+ if (!certification) {
1406
+ certLogger.warn("consensus.certification_not_found");
1407
+ return {
1408
+ content: [{ type: "text", text: `Certification ${certification_id} not found` }],
1409
+ };
1410
+ }
1411
+ let { ready, missing } = canFinalize(certification);
1412
+ // If not ready due to missing cross-verifications and auto_cross_verify is enabled
1413
+ if (!ready && auto_cross_verify) {
1414
+ const crossVerifyMissing = missing.filter((m) => m.includes("not cross-verified"));
1415
+ if (crossVerifyMissing.length > 0 && missing.length === crossVerifyMissing.length) {
1416
+ // All blockers are cross-verification issues - auto-verify
1417
+ certLogger.info("consensus.auto_cross_verify_triggered", {
1418
+ missingVerifications: crossVerifyMissing.length,
1419
+ });
1420
+ const crossVerifyResult = await autoCrossVerify(project_path, certification_id, "auto");
1421
+ // Reload certification and re-check
1422
+ certification = await getCertification(project_path, certification_id);
1423
+ if (!certification) {
1424
+ return {
1425
+ content: [{ type: "text", text: `Certification ${certification_id} not found after cross-verify` }],
1426
+ };
1427
+ }
1428
+ const recheck = canFinalize(certification);
1429
+ ready = recheck.ready;
1430
+ missing = recheck.missing;
1431
+ certLogger.info("consensus.auto_cross_verify_complete", {
1432
+ verificationsCreated: crossVerifyResult.verificationsCreated,
1433
+ nowReady: ready,
1434
+ });
1435
+ }
1436
+ }
1437
+ if (!ready) {
1438
+ certLogger.warn("consensus.not_ready", { missing });
1439
+ return {
1440
+ content: [{ type: "text", text: JSON.stringify({ ready: false, missing }, null, 2) }],
1441
+ };
1442
+ }
1443
+ const consensus = calculateConsensus(certification);
1444
+ await saveConsensus(project_path, certification_id, consensus);
1445
+ certLogger.info("consensus.calculated", {
1446
+ level: consensus.certification_level,
1447
+ score: consensus.overall_score,
1448
+ totalFindings: consensus.total_findings,
1449
+ });
1450
+ return {
1451
+ content: [{ type: "text", text: JSON.stringify(consensus, null, 2) }],
1452
+ };
1453
+ });
1454
+ // ---------------------------------------------------------------------------
1455
+ // Tool: Finalize Certification
1456
+ // ---------------------------------------------------------------------------
1457
+ server.registerTool("certification_finalize", {
1458
+ title: "Finalize Certification",
1459
+ description: `Finalize certification and generate CERTIFICATION.md and CERTIFICATION.json artifacts.`,
1460
+ inputSchema: {
1461
+ project_path: z.string().describe("Absolute path to the project root"),
1462
+ certification_id: z.string().describe("Certification ID"),
1463
+ },
1464
+ annotations: {
1465
+ readOnlyHint: false,
1466
+ destructiveHint: false,
1467
+ idempotentHint: false,
1468
+ openWorldHint: false,
1469
+ },
1470
+ }, async ({ project_path, certification_id }) => {
1471
+ const certLogger = createChildLogger({ certId: certification_id, project: basename(project_path) });
1472
+ const certification = await getCertification(project_path, certification_id);
1473
+ if (!certification) {
1474
+ certLogger.warn("certification.not_found");
1475
+ return {
1476
+ content: [{ type: "text", text: `Certification ${certification_id} not found` }],
1477
+ };
1478
+ }
1479
+ if (!certification.consensus) {
1480
+ certLogger.warn("certification.no_consensus");
1481
+ return {
1482
+ content: [{ type: "text", text: `Run certification_consensus first` }],
1483
+ };
1484
+ }
1485
+ // Finalize metadata
1486
+ await finalizeCertification(project_path, certification_id, certification.consensus.certification_level, certification.consensus.overall_score);
1487
+ // Get updated certification
1488
+ const finalCert = await getCertification(project_path, certification_id);
1489
+ if (!finalCert) {
1490
+ certLogger.error("certification.finalize_failed");
1491
+ return {
1492
+ content: [{ type: "text", text: `Failed to finalize certification` }],
1493
+ };
1494
+ }
1495
+ // Generate artifacts
1496
+ const artifacts = await writeCertificationArtifacts(project_path, finalCert);
1497
+ certLogger.info("certification.finalized", {
1498
+ level: finalCert.consensus?.certification_level,
1499
+ score: finalCert.consensus?.overall_score,
1500
+ artifacts,
1501
+ expiresAt: finalCert.metadata.expires_at,
1502
+ });
1503
+ return {
1504
+ content: [{ type: "text", text: JSON.stringify({
1505
+ certification_level: finalCert.consensus?.certification_level,
1506
+ score: finalCert.consensus?.overall_score,
1507
+ artifacts,
1508
+ expires_at: finalCert.metadata.expires_at,
1509
+ }, null, 2) }],
1510
+ };
1511
+ });
1512
+ // ---------------------------------------------------------------------------
1513
+ // Tool: Certification Dashboard
1514
+ // ---------------------------------------------------------------------------
1515
+ server.registerTool("certification_dashboard", {
1516
+ title: "Certification Dashboard",
1517
+ description: `Portfolio-wide view of enterprise certifications across all projects. Shows certification status, levels, and expiry.`,
1518
+ inputSchema: {
1519
+ base_dir: z.string().optional().describe("Base directory. Defaults to VASPERA_PROJECTS_DIR"),
1520
+ },
1521
+ annotations: {
1522
+ readOnlyHint: true,
1523
+ destructiveHint: false,
1524
+ idempotentHint: true,
1525
+ openWorldHint: false,
1526
+ },
1527
+ }, async ({ base_dir }) => {
1528
+ const dir = base_dir || DEFAULT_PROJECTS_DIR;
1529
+ if (!(await dirExists(dir))) {
1530
+ return {
1531
+ content: [{ type: "text", text: `Error: Directory not found: ${dir}` }],
1532
+ };
1533
+ }
1534
+ const projects = await discoverProjects(dir);
1535
+ const dashboard = await Promise.all(projects.map(async (p) => {
1536
+ const name = basename(p);
1537
+ const latest = await getLatestCertification(p);
1538
+ if (!latest) {
1539
+ return {
1540
+ name,
1541
+ path: p,
1542
+ certification_status: "not_certified",
1543
+ certification_level: null,
1544
+ score: null,
1545
+ certified_at: null,
1546
+ expires_at: null,
1547
+ days_until_expiry: null,
1548
+ };
1549
+ }
1550
+ const validation = await isCertificationValid(p, latest.metadata);
1551
+ const daysUntilExpiry = latest.metadata.expires_at
1552
+ ? Math.ceil((new Date(latest.metadata.expires_at).getTime() - Date.now()) / (1000 * 60 * 60 * 24))
1553
+ : null;
1554
+ // Determine certification status based on validation result
1555
+ let certStatus;
1556
+ if (latest.metadata.status !== "completed") {
1557
+ certStatus = latest.metadata.status;
1558
+ }
1559
+ else if (validation.valid) {
1560
+ certStatus = "certified";
1561
+ }
1562
+ else if (validation.reason === "code_changed") {
1563
+ certStatus = "code_changed";
1564
+ }
1565
+ else {
1566
+ certStatus = "expired";
1567
+ }
1568
+ return {
1569
+ name,
1570
+ path: p,
1571
+ certification_status: certStatus,
1572
+ certification_level: latest.metadata.certification_level,
1573
+ score: latest.metadata.final_score,
1574
+ certified_at: latest.metadata.completed_at,
1575
+ expires_at: latest.metadata.expires_at,
1576
+ days_until_expiry: daysUntilExpiry,
1577
+ };
1578
+ }));
1579
+ // Sort by certification status and expiry
1580
+ dashboard.sort((a, b) => {
1581
+ const statusOrder = {
1582
+ not_certified: 0,
1583
+ code_changed: 1,
1584
+ expired: 2,
1585
+ in_progress: 3,
1586
+ certified: 4,
1587
+ };
1588
+ const aOrder = statusOrder[a.certification_status] ?? 99;
1589
+ const bOrder = statusOrder[b.certification_status] ?? 99;
1590
+ if (aOrder !== bOrder)
1591
+ return aOrder - bOrder;
1592
+ return (a.days_until_expiry ?? 999) - (b.days_until_expiry ?? 999);
1593
+ });
1594
+ const summary = {
1595
+ total_projects: dashboard.length,
1596
+ certified: dashboard.filter((p) => p.certification_status === "certified").length,
1597
+ code_changed: dashboard.filter((p) => p.certification_status === "code_changed").length,
1598
+ expired: dashboard.filter((p) => p.certification_status === "expired").length,
1599
+ in_progress: dashboard.filter((p) => p.certification_status === "in_progress").length,
1600
+ not_certified: dashboard.filter((p) => p.certification_status === "not_certified").length,
1601
+ expiring_soon: dashboard.filter((p) => p.days_until_expiry !== null && p.days_until_expiry <= 7).length,
1602
+ };
1603
+ const output = {
1604
+ base_dir: dir,
1605
+ summary,
1606
+ projects: dashboard,
1607
+ };
1608
+ return {
1609
+ content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
1610
+ };
1611
+ });
1612
+ // ---------------------------------------------------------------------------
1613
+ // Tool: Preview Auto-Fix
1614
+ // ---------------------------------------------------------------------------
1615
+ server.registerTool("autofix_preview", {
1616
+ title: "Preview Auto-Fix for Finding",
1617
+ description: `Preview an automatic fix for a certification finding without applying it. Returns the before/after diff.`,
1618
+ inputSchema: {
1619
+ project_path: z.string().describe("Absolute path to the project root"),
1620
+ certification_id: z.string().describe("Certification ID"),
1621
+ finding_id: z.string().describe("Finding ID to preview fix for"),
1622
+ },
1623
+ annotations: {
1624
+ readOnlyHint: true,
1625
+ destructiveHint: false,
1626
+ idempotentHint: true,
1627
+ openWorldHint: false,
1628
+ },
1629
+ }, async ({ project_path, certification_id, finding_id }) => {
1630
+ const certification = await getCertification(project_path, certification_id);
1631
+ if (!certification) {
1632
+ return errorResponse(`Certification ${certification_id} not found`);
1633
+ }
1634
+ // Find the finding
1635
+ let finding = null;
1636
+ for (const agentData of Object.values(certification.agents)) {
1637
+ if (agentData) {
1638
+ finding = agentData.findings.find((f) => f.id === finding_id);
1639
+ if (finding)
1640
+ break;
1641
+ }
1642
+ }
1643
+ if (!finding) {
1644
+ return errorResponse(`Finding ${finding_id} not found`);
1645
+ }
1646
+ const preview = await previewFix(project_path, finding);
1647
+ return jsonResponse({ ...preview });
1648
+ });
1649
+ // ---------------------------------------------------------------------------
1650
+ // Tool: Apply Auto-Fix
1651
+ // ---------------------------------------------------------------------------
1652
+ server.registerTool("autofix_apply", {
1653
+ title: "Apply Auto-Fix for Finding",
1654
+ description: `Apply an automatic fix for a certification finding. Use autofix_preview first to see what will change.`,
1655
+ inputSchema: {
1656
+ project_path: z.string().describe("Absolute path to the project root"),
1657
+ certification_id: z.string().describe("Certification ID"),
1658
+ finding_id: z.string().describe("Finding ID to fix"),
1659
+ dry_run: z.boolean().optional().describe("If true, show what would be changed without applying"),
1660
+ },
1661
+ annotations: {
1662
+ readOnlyHint: false,
1663
+ destructiveHint: false,
1664
+ idempotentHint: false,
1665
+ openWorldHint: false,
1666
+ },
1667
+ }, async ({ project_path, certification_id, finding_id, dry_run }) => {
1668
+ const certification = await getCertification(project_path, certification_id);
1669
+ if (!certification) {
1670
+ return errorResponse(`Certification ${certification_id} not found`);
1671
+ }
1672
+ let finding = null;
1673
+ for (const agentData of Object.values(certification.agents)) {
1674
+ if (agentData) {
1675
+ finding = agentData.findings.find((f) => f.id === finding_id);
1676
+ if (finding)
1677
+ break;
1678
+ }
1679
+ }
1680
+ if (!finding) {
1681
+ return errorResponse(`Finding ${finding_id} not found`);
1682
+ }
1683
+ const result = await applyFix(project_path, finding, dry_run);
1684
+ return jsonResponse({ ...result });
1685
+ });
1686
+ // ---------------------------------------------------------------------------
1687
+ // Tool: List Fix Patterns
1688
+ // ---------------------------------------------------------------------------
1689
+ server.registerTool("autofix_list_patterns", {
1690
+ title: "List Available Fix Patterns",
1691
+ description: `List all available auto-fix patterns that can be applied to findings.`,
1692
+ inputSchema: {},
1693
+ annotations: {
1694
+ readOnlyHint: true,
1695
+ destructiveHint: false,
1696
+ idempotentHint: true,
1697
+ openWorldHint: false,
1698
+ },
1699
+ }, async () => {
1700
+ const patterns = listFixPatterns();
1701
+ return jsonResponse({ patterns, count: patterns.length });
1702
+ });
1703
+ // ---------------------------------------------------------------------------
1704
+ // Tool: Batch Apply Safe Fixes
1705
+ // ---------------------------------------------------------------------------
1706
+ server.registerTool("autofix_batch", {
1707
+ title: "Batch Apply Safe Fixes",
1708
+ description: `Apply all safe auto-fixes to certification findings at once. Only applies fixes marked as "safeToAutoApply" (low risk, no manual review needed). Use dry_run: true to preview what would be changed.`,
1709
+ inputSchema: {
1710
+ project_path: z.string().describe("Absolute path to the project root"),
1711
+ certification_id: z.string().describe("Certification ID"),
1712
+ dry_run: z.boolean().optional().default(true).describe("If true (default), show what would be changed without applying"),
1713
+ include_unsafe: z.boolean().optional().default(false).describe("If true, also apply unsafe fixes (use with caution)"),
1714
+ },
1715
+ annotations: {
1716
+ readOnlyHint: false,
1717
+ destructiveHint: false,
1718
+ idempotentHint: false,
1719
+ openWorldHint: false,
1720
+ },
1721
+ }, async ({ project_path, certification_id, dry_run, include_unsafe }) => {
1722
+ const certification = await getCertification(project_path, certification_id);
1723
+ if (!certification) {
1724
+ return errorResponse(`Certification ${certification_id} not found`);
1725
+ }
1726
+ // Collect all findings from all agents
1727
+ const allFindings = [];
1728
+ for (const agentData of Object.values(certification.agents)) {
1729
+ if (agentData) {
1730
+ allFindings.push(...agentData.findings);
1731
+ }
1732
+ }
1733
+ if (allFindings.length === 0) {
1734
+ return jsonResponse({
1735
+ message: "No findings to fix",
1736
+ totalFindings: 0,
1737
+ applied: 0,
1738
+ });
1739
+ }
1740
+ const result = await batchApplySafeFixes(project_path, allFindings, {
1741
+ dryRun: dry_run,
1742
+ includeUnsafe: include_unsafe,
1743
+ });
1744
+ // Format output
1745
+ const safePatterns = getSafeFixPatterns();
1746
+ const lines = [
1747
+ `# Batch Auto-Fix ${dry_run ? "(Dry Run)" : "Applied"}`,
1748
+ "",
1749
+ `**Total Findings**: ${result.totalFindings}`,
1750
+ `**Safe Fixes Available**: ${result.safeFixesAvailable}`,
1751
+ `**Applied**: ${result.applied}`,
1752
+ `**Skipped**: ${result.skipped}`,
1753
+ `**Failed**: ${result.failed}`,
1754
+ "",
1755
+ ];
1756
+ if (Object.keys(result.summary.byPattern).length > 0) {
1757
+ lines.push("## Fixes by Pattern");
1758
+ lines.push("");
1759
+ for (const [pattern, count] of Object.entries(result.summary.byPattern)) {
1760
+ lines.push(`- **${pattern}**: ${count} fixes`);
1761
+ }
1762
+ lines.push("");
1763
+ }
1764
+ if (Object.keys(result.summary.byFile).length > 0) {
1765
+ lines.push("## Fixes by File");
1766
+ lines.push("");
1767
+ for (const [file, count] of Object.entries(result.summary.byFile)) {
1768
+ lines.push(`- \`${file}\`: ${count} fixes`);
1769
+ }
1770
+ lines.push("");
1771
+ }
1772
+ if (dry_run && result.applied > 0) {
1773
+ lines.push("## Next Steps");
1774
+ lines.push("");
1775
+ lines.push("Run with `dry_run: false` to apply these fixes.");
1776
+ lines.push("");
1777
+ }
1778
+ lines.push("## Safe Patterns");
1779
+ lines.push("");
1780
+ for (const pattern of safePatterns) {
1781
+ lines.push(`- **${pattern.name}** (${pattern.id}): ${pattern.description}`);
1782
+ }
1783
+ return jsonResponse({
1784
+ message: lines.join("\n"),
1785
+ ...result,
1786
+ });
1787
+ });
1788
+ // ---------------------------------------------------------------------------
1789
+ // Tool: Certification Summary
1790
+ // ---------------------------------------------------------------------------
1791
+ server.registerTool("certification_summary", {
1792
+ title: "Get Certification Summary",
1793
+ description: `Get a progressive disclosure summary of certification findings grouped by severity, agent, file, and category.`,
1794
+ inputSchema: {
1795
+ project_path: z.string().describe("Absolute path to the project root"),
1796
+ certification_id: z.string().describe("Certification ID"),
1797
+ },
1798
+ annotations: {
1799
+ readOnlyHint: true,
1800
+ destructiveHint: false,
1801
+ idempotentHint: true,
1802
+ openWorldHint: false,
1803
+ },
1804
+ }, async ({ project_path, certification_id }) => {
1805
+ const certification = await getCertification(project_path, certification_id);
1806
+ if (!certification) {
1807
+ return errorResponse(`Certification ${certification_id} not found`);
1808
+ }
1809
+ const summary = generateSummary(certification);
1810
+ const actions = getActionItems(certification);
1811
+ return jsonResponse({
1812
+ summary,
1813
+ actionItems: actions,
1814
+ });
1815
+ });
1816
+ // ---------------------------------------------------------------------------
1817
+ // Tool: Filter Findings
1818
+ // ---------------------------------------------------------------------------
1819
+ server.registerTool("certification_filter", {
1820
+ title: "Filter Certification Findings",
1821
+ description: `Filter certification findings by severity, agent, category, file, or confidence level.`,
1822
+ inputSchema: {
1823
+ project_path: z.string().describe("Absolute path to the project root"),
1824
+ certification_id: z.string().describe("Certification ID"),
1825
+ severity: z.array(z.enum(["critical", "high", "medium", "low", "info"])).optional(),
1826
+ agents: z.array(z.enum(["security", "reliability", "typesafety", "performance", "quality", "redteam"])).optional(),
1827
+ categories: z.array(z.string()).optional(),
1828
+ min_confidence: z.number().min(0).max(100).optional(),
1829
+ },
1830
+ annotations: {
1831
+ readOnlyHint: true,
1832
+ destructiveHint: false,
1833
+ idempotentHint: true,
1834
+ openWorldHint: false,
1835
+ },
1836
+ }, async ({ project_path, certification_id, severity, agents, categories, min_confidence }) => {
1837
+ const certification = await getCertification(project_path, certification_id);
1838
+ if (!certification) {
1839
+ return errorResponse(`Certification ${certification_id} not found`);
1840
+ }
1841
+ const findings = filterFindings(certification, {
1842
+ severity,
1843
+ agents,
1844
+ categories,
1845
+ minConfidence: min_confidence,
1846
+ });
1847
+ return jsonResponse({
1848
+ count: findings.length,
1849
+ findings: findings.map((f) => ({
1850
+ id: f.id,
1851
+ severity: f.severity,
1852
+ category: f.category,
1853
+ file: f.file,
1854
+ description: f.description,
1855
+ confidence: f.confidence,
1856
+ instances: f.instances?.length,
1857
+ })),
1858
+ });
1859
+ });
1860
+ // ---------------------------------------------------------------------------
1861
+ // Tool: Export to SARIF
1862
+ // ---------------------------------------------------------------------------
1863
+ server.registerTool("certification_export_sarif", {
1864
+ title: "Export to SARIF Format",
1865
+ description: `Export certification findings to SARIF format for GitHub Security, SonarQube, or other tools.`,
1866
+ inputSchema: {
1867
+ project_path: z.string().describe("Absolute path to the project root"),
1868
+ certification_id: z.string().describe("Certification ID"),
1869
+ format: z.enum(["sarif", "github", "sonarqube"]).optional().describe("Output format"),
1870
+ },
1871
+ annotations: {
1872
+ readOnlyHint: false,
1873
+ destructiveHint: false,
1874
+ idempotentHint: true,
1875
+ openWorldHint: false,
1876
+ },
1877
+ }, async ({ project_path, certification_id, format }) => {
1878
+ const certification = await getCertification(project_path, certification_id);
1879
+ if (!certification) {
1880
+ return errorResponse(`Certification ${certification_id} not found`);
1881
+ }
1882
+ let outputPath;
1883
+ if (format === "github") {
1884
+ outputPath = await exportForGitHub(project_path, certification);
1885
+ }
1886
+ else {
1887
+ outputPath = await exportToSarif(project_path, certification);
1888
+ }
1889
+ return jsonResponse({
1890
+ exported: true,
1891
+ format: format || "sarif",
1892
+ outputPath,
1893
+ });
1894
+ });
1895
+ // ---------------------------------------------------------------------------
1896
+ // Tool: Load Custom Rules
1897
+ // ---------------------------------------------------------------------------
1898
+ server.registerTool("rules_load", {
1899
+ title: "Load Custom Rules",
1900
+ description: `Load custom validation rules from .vaspera/hardening-rules.yaml or .json`,
1901
+ inputSchema: {
1902
+ project_path: z.string().describe("Absolute path to the project root"),
1903
+ },
1904
+ annotations: {
1905
+ readOnlyHint: true,
1906
+ destructiveHint: false,
1907
+ idempotentHint: true,
1908
+ openWorldHint: false,
1909
+ },
1910
+ }, async ({ project_path }) => {
1911
+ const rules = await loadCustomRules(project_path);
1912
+ return jsonResponse({
1913
+ rulesLoaded: rules.length,
1914
+ rules: rules.map((r) => ({
1915
+ id: r.id,
1916
+ name: r.name,
1917
+ severity: r.severity,
1918
+ enabled: r.enabled,
1919
+ })),
1920
+ });
1921
+ });
1922
+ // ---------------------------------------------------------------------------
1923
+ // Tool: List Rule Templates
1924
+ // ---------------------------------------------------------------------------
1925
+ server.registerTool("rules_templates", {
1926
+ title: "List Rule Templates",
1927
+ description: `List built-in rule templates that can be used in custom rule configuration.`,
1928
+ inputSchema: {},
1929
+ annotations: {
1930
+ readOnlyHint: true,
1931
+ destructiveHint: false,
1932
+ idempotentHint: true,
1933
+ openWorldHint: false,
1934
+ },
1935
+ }, async () => {
1936
+ const templates = listRuleTemplates();
1937
+ return jsonResponse({ templates, count: templates.length });
1938
+ });
1939
+ // ---------------------------------------------------------------------------
1940
+ // Tool: Generate Rules Config Sample
1941
+ // ---------------------------------------------------------------------------
1942
+ server.registerTool("rules_generate_config", {
1943
+ title: "Generate Sample Rules Config",
1944
+ description: `Generate a sample .vaspera/hardening-rules.yaml configuration file.`,
1945
+ inputSchema: {
1946
+ project_path: z.string().describe("Absolute path to the project root"),
1947
+ },
1948
+ annotations: {
1949
+ readOnlyHint: false,
1950
+ destructiveHint: false,
1951
+ idempotentHint: true,
1952
+ openWorldHint: false,
1953
+ },
1954
+ }, async ({ project_path }) => {
1955
+ const configContent = generateSampleConfig();
1956
+ const configDir = join(project_path, ".vaspera");
1957
+ const configPath = join(configDir, "hardening-rules.yaml");
1958
+ await mkdir(configDir, { recursive: true });
1959
+ await writeFile(configPath, configContent, "utf-8");
1960
+ return jsonResponse({
1961
+ created: true,
1962
+ path: configPath,
1963
+ content: configContent,
1964
+ });
1965
+ });
1966
+ // ---------------------------------------------------------------------------
1967
+ // Tool: Check File Against Rules
1968
+ // ---------------------------------------------------------------------------
1969
+ server.registerTool("rules_check_file", {
1970
+ title: "Check File Against Custom Rules",
1971
+ description: `Run custom rules against a specific file and return matches.`,
1972
+ inputSchema: {
1973
+ project_path: z.string().describe("Absolute path to the project root"),
1974
+ file_path: z.string().describe("Relative path to the file to check"),
1975
+ },
1976
+ annotations: {
1977
+ readOnlyHint: true,
1978
+ destructiveHint: false,
1979
+ idempotentHint: true,
1980
+ openWorldHint: false,
1981
+ },
1982
+ }, async ({ project_path, file_path }) => {
1983
+ const rules = await loadCustomRules(project_path);
1984
+ if (rules.length === 0) {
1985
+ return jsonResponse({
1986
+ matches: [],
1987
+ message: "No custom rules configured. Use rules_generate_config to create a sample.",
1988
+ });
1989
+ }
1990
+ const fullPath = join(project_path, file_path);
1991
+ const content = await readFile(fullPath, "utf-8");
1992
+ const matches = await checkFileAgainstRules(file_path, content, rules);
1993
+ const findings = matchesToFindings(matches);
1994
+ return jsonResponse({
1995
+ rulesChecked: rules.length,
1996
+ matchCount: matches.length,
1997
+ findings: findings.map((f) => ({
1998
+ id: f.id,
1999
+ severity: f.severity,
2000
+ file: f.file,
2001
+ line: f.line,
2002
+ description: f.description,
2003
+ })),
2004
+ });
2005
+ });
2006
+ // ---------------------------------------------------------------------------
2007
+ // Tool: Run Evaluation
2008
+ // ---------------------------------------------------------------------------
2009
+ server.registerTool("certification_eval", {
2010
+ title: "Run Scanner Evaluation",
2011
+ description: `Run the evaluation harness against labeled test fixtures to measure scanner precision, recall, and F1 score. Use this to verify scanner accuracy before trusting results.`,
2012
+ inputSchema: {
2013
+ scanners: z
2014
+ .array(z.string())
2015
+ .optional()
2016
+ .describe("Scanners to evaluate (semgrep, gitleaks, tsc). Defaults to all."),
2017
+ categories: z
2018
+ .array(z.string())
2019
+ .optional()
2020
+ .describe("Fixture categories to include (sql-injection, xss, secrets, etc.)"),
2021
+ stability_runs: z
2022
+ .number()
2023
+ .min(1)
2024
+ .max(10)
2025
+ .optional()
2026
+ .describe("Number of runs for stability testing. Default 1."),
2027
+ output_format: z
2028
+ .enum(["summary", "markdown", "json"])
2029
+ .optional()
2030
+ .describe("Output format. Default: summary"),
2031
+ },
2032
+ annotations: {
2033
+ readOnlyHint: true,
2034
+ destructiveHint: false,
2035
+ idempotentHint: true,
2036
+ openWorldHint: false,
2037
+ },
2038
+ }, async ({ scanners, categories, stability_runs, output_format }) => {
2039
+ const evalLogger = createChildLogger({ component: "eval" });
2040
+ evalLogger.info("eval.starting", { scanners, categories, stability_runs });
2041
+ try {
2042
+ const config = {
2043
+ scanners: scanners || ["semgrep", "gitleaks", "tsc"],
2044
+ includeFixtures: categories,
2045
+ stabilityRuns: stability_runs || 1,
2046
+ timeout: 60000,
2047
+ parallel: true,
2048
+ };
2049
+ let results;
2050
+ let stability;
2051
+ if (stability_runs && stability_runs > 1) {
2052
+ // Run stability test
2053
+ const allRuns = await runStabilityTest(config, stability_runs);
2054
+ results = allRuns[0]; // Use first run for metrics
2055
+ stability = calculateStabilityMetrics(allRuns);
2056
+ }
2057
+ else {
2058
+ results = await runEvaluation(config);
2059
+ }
2060
+ const metrics = calculateMetrics(results);
2061
+ const { meetsAll, results: targetResults } = meetsTargets(metrics);
2062
+ evalLogger.info("eval.completed", {
2063
+ precision: metrics.precision,
2064
+ recall: metrics.recall,
2065
+ f1Score: metrics.f1Score,
2066
+ meetsTargets: meetsAll,
2067
+ });
2068
+ // Generate output based on format
2069
+ if (output_format === "markdown") {
2070
+ const report = generateEvaluationReport(metrics, results, config, stability);
2071
+ const markdown = formatReportAsMarkdown(report);
2072
+ return textResponse(markdown);
2073
+ }
2074
+ else if (output_format === "json") {
2075
+ const report = generateEvaluationReport(metrics, results, config, stability);
2076
+ return jsonResponse(report);
2077
+ }
2078
+ else {
2079
+ // Summary format (default)
2080
+ const summary = generateCompactSummary(metrics);
2081
+ const badges = generateReadmeBadges(metrics);
2082
+ const lines = [
2083
+ "# Scanner Evaluation Results",
2084
+ "",
2085
+ summary,
2086
+ "",
2087
+ "## Badges for README",
2088
+ "",
2089
+ badges,
2090
+ "",
2091
+ "## Target Comparison",
2092
+ "",
2093
+ "| Metric | Target | Actual | Status |",
2094
+ "|--------|--------|--------|--------|",
2095
+ ];
2096
+ for (const [metric, data] of Object.entries(targetResults)) {
2097
+ const icon = data.met ? "✅" : "❌";
2098
+ lines.push(`| ${metric} | ${(data.target * 100).toFixed(1)}% | ${(data.actual * 100).toFixed(1)}% | ${icon} |`);
2099
+ }
2100
+ if (stability) {
2101
+ lines.push("", "## Stability", "", `Runs: ${stability.runs}`, `Stability: ${stability.stabilityPercent.toFixed(1)}%`, `Consistent: ${stability.consistentFindings}`, `Inconsistent: ${stability.inconsistentFindings}`);
2102
+ }
2103
+ lines.push("", "## Fixture Stats", "", `Total fixtures: ${metrics.totalFixtures}`, `Successful: ${metrics.successfulFixtures}`, `Failed: ${metrics.failedFixtures}`, `Total expected findings: ${metrics.totalExpected}`, `Duration: ${(metrics.totalDuration / 1000).toFixed(1)}s`);
2104
+ return textResponse(lines.join("\n"));
2105
+ }
2106
+ }
2107
+ catch (error) {
2108
+ evalLogger.error("eval.failed", { error: error instanceof Error ? error.message : String(error) });
2109
+ return errorResponse(`Evaluation failed: ${error instanceof Error ? error.message : String(error)}`);
2110
+ }
2111
+ });
2112
+ // ---------------------------------------------------------------------------
2113
+ // Tool: Get Fixture Stats
2114
+ // ---------------------------------------------------------------------------
2115
+ server.registerTool("certification_eval_fixtures", {
2116
+ title: "Get Evaluation Fixture Statistics",
2117
+ description: `Get statistics about available evaluation fixtures including categories and expected findings.`,
2118
+ inputSchema: {},
2119
+ annotations: {
2120
+ readOnlyHint: true,
2121
+ destructiveHint: false,
2122
+ idempotentHint: true,
2123
+ openWorldHint: false,
2124
+ },
2125
+ }, async () => {
2126
+ const stats = getFixtureStats();
2127
+ const fixtures = ALL_FIXTURES.map((f) => ({
2128
+ id: f.id,
2129
+ name: f.name,
2130
+ category: f.category,
2131
+ expectedFindings: f.expectedFindings.length,
2132
+ tags: f.tags,
2133
+ }));
2134
+ return jsonResponse({
2135
+ ...stats,
2136
+ targetMetrics: TARGET_METRICS,
2137
+ fixtures,
2138
+ });
2139
+ });
2140
+ // ---------------------------------------------------------------------------
2141
+ // Tool: Generate Compliance Report
2142
+ // ---------------------------------------------------------------------------
2143
+ server.registerTool("compliance_report", {
2144
+ title: "Generate Compliance Report",
2145
+ description: `Generate a compliance report mapping certification findings to framework controls (SOC 2, ISO 27001). Shows which controls are at risk or non-compliant based on security findings.`,
2146
+ inputSchema: {
2147
+ project_path: z.string().describe("Absolute path to the project root"),
2148
+ certification_id: z.string().describe("Certification ID to assess"),
2149
+ framework: z
2150
+ .enum(["SOC2", "ISO27001"])
2151
+ .describe("Compliance framework to assess"),
2152
+ output_format: z
2153
+ .enum(["markdown", "json", "summary"])
2154
+ .optional()
2155
+ .describe("Output format. Default: markdown"),
2156
+ },
2157
+ annotations: {
2158
+ readOnlyHint: true,
2159
+ destructiveHint: false,
2160
+ idempotentHint: true,
2161
+ openWorldHint: false,
2162
+ },
2163
+ }, async ({ project_path, certification_id, framework, output_format }) => {
2164
+ const certification = await getCertification(project_path, certification_id);
2165
+ if (!certification) {
2166
+ return errorResponse(`Certification ${certification_id} not found`);
2167
+ }
2168
+ // Collect all findings from all agents
2169
+ const allFindings = Object.values(certification.agents)
2170
+ .filter(Boolean)
2171
+ .flatMap((a) => a.findings);
2172
+ if (allFindings.length === 0) {
2173
+ return errorResponse("No findings to assess. Run certification agents first.");
2174
+ }
2175
+ const report = generateComplianceReport(allFindings, framework, project_path, certification_id);
2176
+ if (output_format === "json") {
2177
+ return jsonResponse(report);
2178
+ }
2179
+ else if (output_format === "summary") {
2180
+ return textResponse(generateCompactComplianceSummary(report));
2181
+ }
2182
+ else {
2183
+ return textResponse(formatComplianceReportAsMarkdown(report));
2184
+ }
2185
+ });
2186
+ // ---------------------------------------------------------------------------
2187
+ // Tool: Multi-Framework Compliance Report
2188
+ // ---------------------------------------------------------------------------
2189
+ server.registerTool("compliance_multi_report", {
2190
+ title: "Multi-Framework Compliance Report",
2191
+ description: `Generate a compliance report across multiple frameworks (SOC 2 and ISO 27001). Shows comparative status and prioritized recommendations.`,
2192
+ inputSchema: {
2193
+ project_path: z.string().describe("Absolute path to the project root"),
2194
+ certification_id: z.string().describe("Certification ID to assess"),
2195
+ frameworks: z
2196
+ .array(z.enum(["SOC2", "ISO27001"]))
2197
+ .optional()
2198
+ .describe("Frameworks to assess. Default: both"),
2199
+ output_format: z
2200
+ .enum(["markdown", "json"])
2201
+ .optional()
2202
+ .describe("Output format. Default: markdown"),
2203
+ },
2204
+ annotations: {
2205
+ readOnlyHint: true,
2206
+ destructiveHint: false,
2207
+ idempotentHint: true,
2208
+ openWorldHint: false,
2209
+ },
2210
+ }, async ({ project_path, certification_id, frameworks, output_format }) => {
2211
+ const certification = await getCertification(project_path, certification_id);
2212
+ if (!certification) {
2213
+ return errorResponse(`Certification ${certification_id} not found`);
2214
+ }
2215
+ const allFindings = Object.values(certification.agents)
2216
+ .filter(Boolean)
2217
+ .flatMap((a) => a.findings);
2218
+ if (allFindings.length === 0) {
2219
+ return errorResponse("No findings to assess. Run certification agents first.");
2220
+ }
2221
+ const selectedFrameworks = (frameworks || ["SOC2", "ISO27001"]);
2222
+ const report = generateMultiFrameworkReport(allFindings, selectedFrameworks, project_path, certification_id);
2223
+ if (output_format === "json") {
2224
+ return jsonResponse(report);
2225
+ }
2226
+ else {
2227
+ return textResponse(formatMultiFrameworkReportAsMarkdown(report));
2228
+ }
2229
+ });
2230
+ // ---------------------------------------------------------------------------
2231
+ // Tool: List Compliance Controls
2232
+ // ---------------------------------------------------------------------------
2233
+ server.registerTool("compliance_controls", {
2234
+ title: "List Compliance Controls",
2235
+ description: `List available compliance controls for a framework. Use to understand what controls are assessed and their mapping to finding categories.`,
2236
+ inputSchema: {
2237
+ framework: z
2238
+ .enum(["SOC2", "ISO27001"])
2239
+ .describe("Compliance framework"),
2240
+ category: z
2241
+ .string()
2242
+ .optional()
2243
+ .describe("Filter by category"),
2244
+ },
2245
+ annotations: {
2246
+ readOnlyHint: true,
2247
+ destructiveHint: false,
2248
+ idempotentHint: true,
2249
+ openWorldHint: false,
2250
+ },
2251
+ }, async ({ framework, category }) => {
2252
+ let controls = getControlsForFramework(framework);
2253
+ if (category) {
2254
+ controls = controls.filter((c) => c.category === category);
2255
+ }
2256
+ const categories = framework === "SOC2" ? getSOC2Categories() : getISO27001Categories();
2257
+ return jsonResponse({
2258
+ framework,
2259
+ totalControls: controls.length,
2260
+ categories,
2261
+ controls: controls.map((c) => ({
2262
+ id: c.id,
2263
+ category: c.category,
2264
+ title: c.title,
2265
+ findingCategories: c.findingCategories,
2266
+ cweIds: c.cweIds,
2267
+ severityThreshold: c.severityThreshold,
2268
+ })),
2269
+ });
2270
+ });
2271
+ // ---------------------------------------------------------------------------
2272
+ // Tool: Generate SBOM
2273
+ // ---------------------------------------------------------------------------
2274
+ server.registerTool("sbom_generate", {
2275
+ title: "Generate CycloneDX SBOM",
2276
+ description: `Generate a CycloneDX 1.5 Software Bill of Materials (SBOM) for a project. Includes all dependencies from package.json/package-lock.json with integrity hashes. Use output_file to write directly to a file.`,
2277
+ inputSchema: {
2278
+ project_path: z.string().describe("Absolute path to the project root"),
2279
+ include_dev: z.boolean().optional().describe("Include dev dependencies. Default: false"),
2280
+ include_vulnerabilities: z.boolean().optional().describe("Include vulnerabilities from certification. Default: false"),
2281
+ certification_id: z.string().optional().describe("Certification ID to include vulnerabilities from"),
2282
+ output_format: z.enum(["json", "summary"]).optional().describe("Output format. Default: json"),
2283
+ output_file: z.string().optional().describe("Write SBOM to this file path (e.g., 'sbom.json' or 'sbom.cdx.json')"),
2284
+ },
2285
+ annotations: {
2286
+ readOnlyHint: true,
2287
+ destructiveHint: false,
2288
+ idempotentHint: true,
2289
+ openWorldHint: false,
2290
+ },
2291
+ }, async ({ project_path, include_dev, include_vulnerabilities, certification_id, output_format, output_file }) => {
2292
+ try {
2293
+ // Get findings if including vulnerabilities
2294
+ let findings;
2295
+ if (include_vulnerabilities && certification_id) {
2296
+ const certification = await getCertification(project_path, certification_id);
2297
+ if (certification) {
2298
+ // Get scanner findings (deterministic)
2299
+ const securityAgent = certification.agents.security;
2300
+ if (securityAgent) {
2301
+ findings = securityAgent.findings
2302
+ .filter((f) => f.confidence === 100 && f.scanner_source)
2303
+ .map((f) => ({
2304
+ scanner: f.scanner_source,
2305
+ ruleId: f.scanner_rule_id || f.id,
2306
+ file: f.file || "",
2307
+ line: f.line || 0,
2308
+ message: f.description,
2309
+ severity: f.severity,
2310
+ confidence: 100,
2311
+ cweIds: f.cwe_ids,
2312
+ cveIds: f.cve_ids,
2313
+ }));
2314
+ }
2315
+ }
2316
+ }
2317
+ const sbom = await generateSBOM(project_path, {
2318
+ includeDevDependencies: include_dev,
2319
+ includeVulnerabilities: include_vulnerabilities,
2320
+ }, findings);
2321
+ // Write to file if requested
2322
+ if (output_file) {
2323
+ const filePath = output_file.startsWith("/") ? output_file : join(project_path, output_file);
2324
+ const content = output_format === "summary"
2325
+ ? generateSBOMSummary(sbom)
2326
+ : JSON.stringify(sbom, null, 2);
2327
+ await writeFile(filePath, content, "utf-8");
2328
+ return textResponse(`SBOM written to: ${filePath}\n\nComponents: ${sbom.components?.length || 0}`);
2329
+ }
2330
+ if (output_format === "summary") {
2331
+ const summary = generateSBOMSummary(sbom);
2332
+ return textResponse(summary);
2333
+ }
2334
+ return jsonResponse(sbom);
2335
+ }
2336
+ catch (error) {
2337
+ return errorResponse(`Failed to generate SBOM: ${error instanceof Error ? error.message : String(error)}`);
2338
+ }
2339
+ });
2340
+ // ---------------------------------------------------------------------------
2341
+ // Tool: Generate SLSA Provenance
2342
+ // ---------------------------------------------------------------------------
2343
+ server.registerTool("sbom_provenance", {
2344
+ title: "Generate SLSA Provenance",
2345
+ description: `Generate SLSA v1.0 provenance for a certification. Creates an in-toto statement with build metadata and subject digests.`,
2346
+ inputSchema: {
2347
+ project_path: z.string().describe("Absolute path to the project root"),
2348
+ certification_id: z.string().describe("Certification ID to generate provenance for"),
2349
+ artifact_content: z.string().optional().describe("Content to generate provenance for. Defaults to certification JSON."),
2350
+ builder_id: z.string().optional().describe("Builder ID URL. Default: https://github.com/RCOLKITT/vaspera-hardening-mcp"),
2351
+ output_format: z.enum(["json", "summary"]).optional().describe("Output format. Default: json"),
2352
+ },
2353
+ annotations: {
2354
+ readOnlyHint: true,
2355
+ destructiveHint: false,
2356
+ idempotentHint: true,
2357
+ openWorldHint: false,
2358
+ },
2359
+ }, async ({ project_path, certification_id, artifact_content, builder_id, output_format }) => {
2360
+ try {
2361
+ const certification = await getCertification(project_path, certification_id);
2362
+ if (!certification) {
2363
+ return errorResponse(`Certification ${certification_id} not found`);
2364
+ }
2365
+ // Use certification JSON as default artifact
2366
+ const content = artifact_content || JSON.stringify(certification, null, 2);
2367
+ const provenance = await generateProvenance(certification, content, {
2368
+ builderId: builder_id,
2369
+ });
2370
+ if (output_format === "summary") {
2371
+ const summary = generateProvenanceSummary(provenance);
2372
+ return textResponse(summary);
2373
+ }
2374
+ return jsonResponse(provenance);
2375
+ }
2376
+ catch (error) {
2377
+ return errorResponse(`Failed to generate provenance: ${error instanceof Error ? error.message : String(error)}`);
2378
+ }
2379
+ });
2380
+ // ---------------------------------------------------------------------------
2381
+ // Tool: Sign Artifact with Sigstore
2382
+ // ---------------------------------------------------------------------------
2383
+ server.registerTool("sbom_sign", {
2384
+ title: "Sign Artifact with Sigstore",
2385
+ description: `Sign an artifact using Sigstore (Fulcio + Rekor) for supply chain security.
2386
+
2387
+ Requires OIDC identity token:
2388
+ - GitHub Actions: Add 'permissions: id-token: write' to your workflow
2389
+ - GitLab CI: Enable CI_JOB_JWT_V2
2390
+ - Manual: Set SIGSTORE_ID_TOKEN environment variable
2391
+
2392
+ Returns a Sigstore bundle with:
2393
+ - Short-lived certificate from Fulcio
2394
+ - Entry in Rekor transparency log
2395
+ - Cryptographic signature`,
2396
+ inputSchema: {
2397
+ content: z.string().describe("Content to sign (JSON string, SBOM, or provenance)"),
2398
+ skip_signing: z.boolean().optional().describe("Skip signing and return unsigned artifact (for testing). Default: false"),
2399
+ },
2400
+ annotations: {
2401
+ readOnlyHint: true,
2402
+ destructiveHint: false,
2403
+ idempotentHint: false, // Signing creates new log entries
2404
+ openWorldHint: true, // Contacts Sigstore services
2405
+ },
2406
+ }, async ({ content, skip_signing }) => {
2407
+ try {
2408
+ // Detect CI environment
2409
+ const ciEnv = detectCIEnvironment();
2410
+ // Check if signing is available
2411
+ const available = isSigningAvailable({ skipSigning: skip_signing });
2412
+ if (!available && !skip_signing) {
2413
+ return jsonResponse({
2414
+ signed: false,
2415
+ available: false,
2416
+ ciEnvironment: ciEnv,
2417
+ message: ciEnv.setupInstructions ||
2418
+ "Sigstore signing requires OIDC identity. Run in CI with OIDC support or set SIGSTORE_ID_TOKEN.",
2419
+ hint: "Use skip_signing: true to generate unsigned artifacts for testing.",
2420
+ });
2421
+ }
2422
+ // Perform signing
2423
+ const signedArtifact = await signContent(content, { skipSigning: skip_signing });
2424
+ return jsonResponse({
2425
+ signed: signedArtifact.signed,
2426
+ digest: signedArtifact.digest,
2427
+ signedAt: signedArtifact.signedAt,
2428
+ error: signedArtifact.error,
2429
+ hasBundle: !!signedArtifact.bundle,
2430
+ bundleMediaType: signedArtifact.bundle?.mediaType,
2431
+ transparencyLogEntry: signedArtifact.bundle?.verificationMaterial?.tlogEntries?.[0]
2432
+ ? {
2433
+ logIndex: signedArtifact.bundle.verificationMaterial.tlogEntries[0].logIndex,
2434
+ integratedTime: signedArtifact.bundle.verificationMaterial.tlogEntries[0].integratedTime,
2435
+ }
2436
+ : null,
2437
+ summary: generateSigningSummary(signedArtifact),
2438
+ ciEnvironment: ciEnv,
2439
+ });
2440
+ }
2441
+ catch (error) {
2442
+ return errorResponse(`Failed to sign content: ${error instanceof Error ? error.message : String(error)}`);
2443
+ }
2444
+ });
2445
+ // ---------------------------------------------------------------------------
2446
+ // Tool: Verify Provenance
2447
+ // ---------------------------------------------------------------------------
2448
+ server.registerTool("sbom_verify_provenance", {
2449
+ title: "Verify SLSA Provenance",
2450
+ description: `Verify that SLSA provenance matches a certification and artifact content.`,
2451
+ inputSchema: {
2452
+ project_path: z.string().describe("Absolute path to the project root"),
2453
+ certification_id: z.string().describe("Certification ID"),
2454
+ provenance_json: z.string().describe("Provenance JSON to verify"),
2455
+ artifact_content: z.string().optional().describe("Artifact content to verify against. Defaults to certification JSON."),
2456
+ },
2457
+ annotations: {
2458
+ readOnlyHint: true,
2459
+ destructiveHint: false,
2460
+ idempotentHint: true,
2461
+ openWorldHint: false,
2462
+ },
2463
+ }, async ({ project_path, certification_id, provenance_json, artifact_content }) => {
2464
+ try {
2465
+ const certification = await getCertification(project_path, certification_id);
2466
+ if (!certification) {
2467
+ return errorResponse(`Certification ${certification_id} not found`);
2468
+ }
2469
+ const provenance = JSON.parse(provenance_json);
2470
+ const content = artifact_content || JSON.stringify(certification, null, 2);
2471
+ const result = verifyProvenance(provenance, certification, content);
2472
+ return jsonResponse({
2473
+ valid: result.valid,
2474
+ errors: result.errors,
2475
+ certificationId: certification_id,
2476
+ });
2477
+ }
2478
+ catch (error) {
2479
+ return errorResponse(`Failed to verify provenance: ${error instanceof Error ? error.message : String(error)}`);
2480
+ }
2481
+ });
2482
+ // ---------------------------------------------------------------------------
2483
+ // Tool: Estimate Cost
2484
+ // ---------------------------------------------------------------------------
2485
+ server.registerTool("cost_estimate", {
2486
+ title: "Estimate API Cost",
2487
+ description: `Estimate the cost of an API call based on token count or content. Supports Anthropic, OpenAI, and Google models.`,
2488
+ inputSchema: {
2489
+ model: z.string().optional().describe("Model ID (e.g., claude-3.5-sonnet, gpt-4-turbo). Default: claude-3.5-sonnet"),
2490
+ input_tokens: z.number().optional().describe("Number of input tokens"),
2491
+ output_tokens: z.number().optional().describe("Number of output tokens"),
2492
+ content: z.string().optional().describe("Content to estimate tokens for (alternative to input_tokens)"),
2493
+ },
2494
+ annotations: {
2495
+ readOnlyHint: true,
2496
+ destructiveHint: false,
2497
+ idempotentHint: true,
2498
+ openWorldHint: false,
2499
+ },
2500
+ }, async ({ model, input_tokens, output_tokens, content }) => {
2501
+ const modelId = (model || "claude-3.5-sonnet");
2502
+ // Get model pricing
2503
+ const pricing = MODEL_PRICING[modelId];
2504
+ if (!pricing) {
2505
+ const supported = getSupportedModels();
2506
+ return errorResponse(`Unknown model: ${modelId}. Supported models: ${supported.join(", ")}`);
2507
+ }
2508
+ // Estimate tokens from content if provided
2509
+ let estimatedInputTokens = input_tokens || 0;
2510
+ if (content && !input_tokens) {
2511
+ // Rough estimate: ~4 chars per token
2512
+ estimatedInputTokens = Math.ceil(content.length / 4);
2513
+ }
2514
+ const estimatedOutputTokens = output_tokens || 0;
2515
+ const estimate = estimateCost(modelId, estimatedInputTokens, estimatedOutputTokens);
2516
+ return jsonResponse({
2517
+ model: modelId,
2518
+ provider: pricing.provider,
2519
+ inputTokens: estimatedInputTokens,
2520
+ outputTokens: estimatedOutputTokens,
2521
+ totalTokens: estimatedInputTokens + estimatedOutputTokens,
2522
+ estimatedCost: formatCost(estimate),
2523
+ breakdown: {
2524
+ inputCost: formatCost((estimatedInputTokens / 1_000_000) * pricing.inputPer1M),
2525
+ outputCost: formatCost((estimatedOutputTokens / 1_000_000) * pricing.outputPer1M),
2526
+ },
2527
+ pricing: {
2528
+ inputPer1M: `$${pricing.inputPer1M}`,
2529
+ outputPer1M: `$${pricing.outputPer1M}`,
2530
+ cachedInputPer1M: pricing.cachedInputPer1M ? `$${pricing.cachedInputPer1M}` : undefined,
2531
+ },
2532
+ });
2533
+ });
2534
+ // ---------------------------------------------------------------------------
2535
+ // Tool: Track API Call Cost
2536
+ // ---------------------------------------------------------------------------
2537
+ server.registerTool("cost_track", {
2538
+ title: "Track API Call Cost",
2539
+ description: `Record an API call's token usage and cost for a certification. Creates or updates a cost tracker for the certification.`,
2540
+ inputSchema: {
2541
+ project_path: z.string().describe("Absolute path to the project root"),
2542
+ certification_id: z.string().describe("Certification ID to track costs for"),
2543
+ model: z.string().describe("Model ID used for the API call"),
2544
+ input_tokens: z.number().describe("Number of input tokens used"),
2545
+ output_tokens: z.number().describe("Number of output tokens used"),
2546
+ cached_tokens: z.number().optional().describe("Number of cached input tokens"),
2547
+ agent: z.string().optional().describe("Agent name making the call"),
2548
+ operation: z.string().optional().describe("Operation description"),
2549
+ },
2550
+ annotations: {
2551
+ readOnlyHint: false,
2552
+ destructiveHint: false,
2553
+ idempotentHint: false,
2554
+ openWorldHint: false,
2555
+ },
2556
+ }, async ({ project_path, certification_id, model, input_tokens, output_tokens, cached_tokens, agent, operation }) => {
2557
+ try {
2558
+ const modelId = model;
2559
+ // Verify model is supported
2560
+ if (!MODEL_PRICING[modelId]) {
2561
+ const supported = getSupportedModels();
2562
+ return errorResponse(`Unknown model: ${modelId}. Supported: ${supported.join(", ")}`);
2563
+ }
2564
+ // Get or create tracker
2565
+ const tracker = getTracker(certification_id, project_path);
2566
+ // Record the call
2567
+ const record = tracker.recordCall(modelId, {
2568
+ inputTokens: input_tokens,
2569
+ outputTokens: output_tokens,
2570
+ cachedInputTokens: cached_tokens,
2571
+ totalTokens: input_tokens + output_tokens + (cached_tokens || 0),
2572
+ }, {
2573
+ agent,
2574
+ operation,
2575
+ });
2576
+ // Check budget
2577
+ try {
2578
+ tracker.checkBudget();
2579
+ }
2580
+ catch (budgetError) {
2581
+ // Budget exceeded, persist state and report
2582
+ await tracker.persist();
2583
+ return jsonResponse({
2584
+ recorded: true,
2585
+ budgetExceeded: true,
2586
+ error: budgetError instanceof Error ? budgetError.message : String(budgetError),
2587
+ record: {
2588
+ id: record.id,
2589
+ cost: formatCost(record.cost.totalCost),
2590
+ tokens: formatTokens(record.usage.totalTokens),
2591
+ },
2592
+ summary: tracker.generateCompactSummary(),
2593
+ });
2594
+ }
2595
+ // Persist state
2596
+ await tracker.persist();
2597
+ return jsonResponse({
2598
+ recorded: true,
2599
+ budgetExceeded: false,
2600
+ record: {
2601
+ id: record.id,
2602
+ model: record.model,
2603
+ cost: formatCost(record.cost.totalCost),
2604
+ tokens: formatTokens(record.usage.totalTokens),
2605
+ agent: record.agent,
2606
+ },
2607
+ summary: tracker.generateCompactSummary(),
2608
+ });
2609
+ }
2610
+ catch (error) {
2611
+ return errorResponse(`Failed to track cost: ${error instanceof Error ? error.message : String(error)}`);
2612
+ }
2613
+ });
2614
+ // ---------------------------------------------------------------------------
2615
+ // Tool: Get Cost Status
2616
+ // ---------------------------------------------------------------------------
2617
+ server.registerTool("cost_status", {
2618
+ title: "Get Cost Status",
2619
+ description: `Get current cost tracking status for a certification, including budget status and usage summary.`,
2620
+ inputSchema: {
2621
+ project_path: z.string().describe("Absolute path to the project root"),
2622
+ certification_id: z.string().describe("Certification ID to get status for"),
2623
+ },
2624
+ annotations: {
2625
+ readOnlyHint: true,
2626
+ destructiveHint: false,
2627
+ idempotentHint: true,
2628
+ openWorldHint: false,
2629
+ },
2630
+ }, async ({ project_path, certification_id }) => {
2631
+ try {
2632
+ const tracker = getTracker(certification_id, project_path);
2633
+ // Try to load existing state
2634
+ await tracker.load();
2635
+ const summary = tracker.getSummary();
2636
+ const status = tracker.getStatus();
2637
+ const budget = tracker.getBudget();
2638
+ return jsonResponse({
2639
+ certificationId: certification_id,
2640
+ summary: {
2641
+ totalCost: formatCost(summary.totalCost),
2642
+ totalTokens: formatTokens(summary.totalInputTokens + summary.totalOutputTokens),
2643
+ inputTokens: formatTokens(summary.totalInputTokens),
2644
+ outputTokens: formatTokens(summary.totalOutputTokens),
2645
+ cachedTokens: formatTokens(summary.totalCachedTokens),
2646
+ totalCalls: summary.totalCalls,
2647
+ startedAt: summary.startedAt,
2648
+ updatedAt: summary.updatedAt,
2649
+ },
2650
+ budget: {
2651
+ maxCost: budget.maxCost ? formatCost(budget.maxCost) : "unlimited",
2652
+ maxTokens: budget.maxTotalTokens ? formatTokens(budget.maxTotalTokens) : "unlimited",
2653
+ maxCalls: budget.maxCalls || "unlimited",
2654
+ warnAtPercent: budget.warnAtPercent,
2655
+ abortOnExceeded: budget.abortOnExceeded,
2656
+ },
2657
+ status: {
2658
+ withinBudget: status.withinBudget,
2659
+ costUsedPercent: `${status.costUsedPercent.toFixed(1)}%`,
2660
+ warnings: status.warnings,
2661
+ exceeded: status.exceeded,
2662
+ },
2663
+ compactSummary: tracker.generateCompactSummary(),
2664
+ });
2665
+ }
2666
+ catch (error) {
2667
+ return errorResponse(`Failed to get cost status: ${error instanceof Error ? error.message : String(error)}`);
2668
+ }
2669
+ });
2670
+ // ---------------------------------------------------------------------------
2671
+ // Tool: Generate Cost Report
2672
+ // ---------------------------------------------------------------------------
2673
+ server.registerTool("cost_report", {
2674
+ title: "Generate Cost Report",
2675
+ description: `Generate a detailed cost report for a certification, including breakdown by model and agent.`,
2676
+ inputSchema: {
2677
+ project_path: z.string().describe("Absolute path to the project root"),
2678
+ certification_id: z.string().describe("Certification ID to generate report for"),
2679
+ output_format: z.enum(["markdown", "json"]).optional().describe("Output format. Default: markdown"),
2680
+ },
2681
+ annotations: {
2682
+ readOnlyHint: true,
2683
+ destructiveHint: false,
2684
+ idempotentHint: true,
2685
+ openWorldHint: false,
2686
+ },
2687
+ }, async ({ project_path, certification_id, output_format }) => {
2688
+ try {
2689
+ const tracker = getTracker(certification_id, project_path);
2690
+ // Try to load existing state
2691
+ const loaded = await tracker.load();
2692
+ if (!loaded) {
2693
+ return errorResponse(`No cost tracking data found for certification ${certification_id}`);
2694
+ }
2695
+ if (output_format === "json") {
2696
+ const state = tracker.getState();
2697
+ return jsonResponse({
2698
+ certificationId: state.certificationId,
2699
+ summary: state.summary,
2700
+ budget: state.budget,
2701
+ status: state.status,
2702
+ callCount: state.calls.length,
2703
+ });
2704
+ }
2705
+ return textResponse(tracker.generateReport());
2706
+ }
2707
+ catch (error) {
2708
+ return errorResponse(`Failed to generate cost report: ${error instanceof Error ? error.message : String(error)}`);
2709
+ }
2710
+ });
2711
+ // ---------------------------------------------------------------------------
2712
+ // Tool: Set Budget Configuration
2713
+ // ---------------------------------------------------------------------------
2714
+ server.registerTool("cost_budget", {
2715
+ title: "Configure Cost Budget",
2716
+ description: `Set or get budget configuration for cost tracking. Can set limits on total cost, tokens, or API calls.`,
2717
+ inputSchema: {
2718
+ project_path: z.string().describe("Absolute path to the project root"),
2719
+ certification_id: z.string().describe("Certification ID to configure budget for"),
2720
+ max_cost: z.number().optional().describe("Maximum cost in dollars. Default: 10.00"),
2721
+ max_tokens: z.number().optional().describe("Maximum total tokens"),
2722
+ max_calls: z.number().optional().describe("Maximum API calls"),
2723
+ warn_at_percent: z.number().optional().describe("Warn when budget used exceeds this percent. Default: 80"),
2724
+ abort_on_exceeded: z.boolean().optional().describe("Abort certification when budget exceeded. Default: true"),
2725
+ },
2726
+ annotations: {
2727
+ readOnlyHint: false,
2728
+ destructiveHint: false,
2729
+ idempotentHint: true,
2730
+ openWorldHint: false,
2731
+ },
2732
+ }, async ({ project_path, certification_id, max_cost, max_tokens, max_calls, warn_at_percent, abort_on_exceeded }) => {
2733
+ try {
2734
+ const tracker = getTracker(certification_id, project_path);
2735
+ // Try to load existing state
2736
+ await tracker.load();
2737
+ // Update budget if any values provided
2738
+ const updates = {};
2739
+ if (max_cost !== undefined)
2740
+ updates.maxCost = max_cost;
2741
+ if (max_tokens !== undefined)
2742
+ updates.maxTotalTokens = max_tokens;
2743
+ if (max_calls !== undefined)
2744
+ updates.maxCalls = max_calls;
2745
+ if (warn_at_percent !== undefined)
2746
+ updates.warnAtPercent = warn_at_percent;
2747
+ if (abort_on_exceeded !== undefined)
2748
+ updates.abortOnExceeded = abort_on_exceeded;
2749
+ if (Object.keys(updates).length > 0) {
2750
+ tracker.setBudget(updates);
2751
+ await tracker.persist();
2752
+ }
2753
+ const budget = tracker.getBudget();
2754
+ const status = tracker.getStatus();
2755
+ return jsonResponse({
2756
+ certificationId: certification_id,
2757
+ budget: {
2758
+ maxCost: budget.maxCost ? formatCost(budget.maxCost) : "unlimited",
2759
+ maxTokens: budget.maxTotalTokens ? formatTokens(budget.maxTotalTokens) : "unlimited",
2760
+ maxCalls: budget.maxCalls || "unlimited",
2761
+ warnAtPercent: budget.warnAtPercent,
2762
+ abortOnExceeded: budget.abortOnExceeded,
2763
+ },
2764
+ currentStatus: {
2765
+ withinBudget: status.withinBudget,
2766
+ costUsedPercent: `${status.costUsedPercent.toFixed(1)}%`,
2767
+ warnings: status.warnings,
2768
+ },
2769
+ updated: Object.keys(updates).length > 0,
2770
+ });
2771
+ }
2772
+ catch (error) {
2773
+ return errorResponse(`Failed to configure budget: ${error instanceof Error ? error.message : String(error)}`);
2774
+ }
2775
+ });
2776
+ // ---------------------------------------------------------------------------
2777
+ // Tool: List Supported Models
2778
+ // ---------------------------------------------------------------------------
2779
+ server.registerTool("cost_models", {
2780
+ title: "List Supported Models",
2781
+ description: `List all supported models and their pricing for cost tracking.`,
2782
+ inputSchema: {
2783
+ provider: z.enum(["anthropic", "openai", "google"]).optional().describe("Filter by provider"),
2784
+ },
2785
+ annotations: {
2786
+ readOnlyHint: true,
2787
+ destructiveHint: false,
2788
+ idempotentHint: true,
2789
+ openWorldHint: false,
2790
+ },
2791
+ }, async ({ provider }) => {
2792
+ const models = getSupportedModels();
2793
+ const modelList = models
2794
+ .filter((modelId) => {
2795
+ if (!provider)
2796
+ return true;
2797
+ const pricing = MODEL_PRICING[modelId];
2798
+ return pricing?.provider === provider;
2799
+ })
2800
+ .map((modelId) => {
2801
+ const pricing = MODEL_PRICING[modelId];
2802
+ return {
2803
+ id: modelId,
2804
+ provider: pricing.provider,
2805
+ inputPer1M: `$${pricing.inputPer1M}`,
2806
+ outputPer1M: `$${pricing.outputPer1M}`,
2807
+ cachedInputPer1M: pricing.cachedInputPer1M ? `$${pricing.cachedInputPer1M}` : undefined,
2808
+ };
2809
+ });
2810
+ return jsonResponse({
2811
+ totalModels: modelList.length,
2812
+ models: modelList,
2813
+ });
2814
+ });
2815
+ // ---------------------------------------------------------------------------
2816
+ // Tool: Record Findings for Consensus Aggregation
2817
+ // ---------------------------------------------------------------------------
2818
+ // NOTE: These tools aggregate findings from EXTERNAL model runs. They do NOT
2819
+ // call LLM APIs directly. Run agents externally, then record results here.
2820
+ server.registerTool("consensus_record", {
2821
+ title: "Record Findings for Consensus",
2822
+ description: `Record findings from an EXTERNAL agent run for consensus aggregation. This tool does NOT call LLM APIs - you must run agents separately (via MCP client, API, etc.) and record the results here. Call once per model/agent combination.`,
2823
+ inputSchema: {
2824
+ project_path: z.string().describe("Absolute path to the project root"),
2825
+ certification_id: z.string().describe("Certification ID"),
2826
+ agent: z.enum(["security", "reliability", "typesafety", "performance", "quality", "redteam"]).describe("Agent type"),
2827
+ model: z.string().describe("Model ID (e.g., claude-3.5-sonnet, gpt-4-turbo, gemini-1.5-pro)"),
2828
+ findings: z.array(z.object({
2829
+ id: z.string(),
2830
+ description: z.string(),
2831
+ severity: z.enum(["critical", "high", "medium", "low", "info"]),
2832
+ category: z.string(),
2833
+ evidence: z.string().optional(),
2834
+ file: z.string().optional(),
2835
+ line: z.number().optional(),
2836
+ confidence: z.number().optional(),
2837
+ })).describe("Findings from this model run"),
2838
+ duration: z.number().describe("Duration in milliseconds"),
2839
+ input_tokens: z.number().describe("Input tokens used"),
2840
+ output_tokens: z.number().describe("Output tokens used"),
2841
+ },
2842
+ annotations: {
2843
+ readOnlyHint: false,
2844
+ destructiveHint: false,
2845
+ idempotentHint: false,
2846
+ openWorldHint: false,
2847
+ },
2848
+ }, async ({ certification_id, agent, model, findings, duration, input_tokens, output_tokens }) => {
2849
+ try {
2850
+ const runner = getRunner();
2851
+ const result = runner.recordResult(certification_id, agent, model, findings.map((f) => ({
2852
+ id: f.id,
2853
+ description: f.description,
2854
+ severity: f.severity,
2855
+ category: f.category,
2856
+ evidence: f.evidence || "",
2857
+ file: f.file,
2858
+ line: f.line,
2859
+ confidence: f.confidence ?? 80,
2860
+ verifications: [],
2861
+ created_at: new Date().toISOString(),
2862
+ })), {
2863
+ duration,
2864
+ inputTokens: input_tokens,
2865
+ outputTokens: output_tokens,
2866
+ });
2867
+ const hasEnough = runner.hasEnoughResults(certification_id, agent);
2868
+ return jsonResponse({
2869
+ recorded: true,
2870
+ model: result.model,
2871
+ provider: result.provider,
2872
+ findings: result.findings.length,
2873
+ cost: formatCost(result.cost),
2874
+ tokens: formatTokens(result.tokens.total),
2875
+ hasEnoughForConsensus: hasEnough,
2876
+ message: hasEnough
2877
+ ? "Ready to calculate multi-model consensus"
2878
+ : "Need more model results for consensus",
2879
+ });
2880
+ }
2881
+ catch (error) {
2882
+ return errorResponse(`Failed to record result: ${error instanceof Error ? error.message : String(error)}`);
2883
+ }
2884
+ });
2885
+ // ---------------------------------------------------------------------------
2886
+ // Tool: Calculate Consensus from Recorded Findings
2887
+ // ---------------------------------------------------------------------------
2888
+ server.registerTool("consensus_calculate", {
2889
+ title: "Calculate Consensus",
2890
+ description: `Calculate consensus from previously recorded findings (via consensus_record). Uses Fleiss' kappa for inter-rater reliability. Identifies agreements, disagreements, and merges findings with confidence scores.`,
2891
+ inputSchema: {
2892
+ certification_id: z.string().describe("Certification ID"),
2893
+ agent: z.enum(["security", "reliability", "typesafety", "performance", "quality", "redteam"]).describe("Agent type"),
2894
+ match_threshold: z.number().optional().describe("Threshold for matching findings (0-100). Default: 70"),
2895
+ require_majority: z.boolean().optional().describe("Only include findings with majority agreement. Default: false"),
2896
+ output_format: z.enum(["json", "markdown"]).optional().describe("Output format. Default: json"),
2897
+ },
2898
+ annotations: {
2899
+ readOnlyHint: true,
2900
+ destructiveHint: false,
2901
+ idempotentHint: true,
2902
+ openWorldHint: false,
2903
+ },
2904
+ }, async ({ certification_id, agent, match_threshold, require_majority, output_format }) => {
2905
+ try {
2906
+ const runner = getRunner();
2907
+ const consensus = runner.calculateConsensus(certification_id, agent, { matchThreshold: match_threshold, requireMajority: require_majority });
2908
+ if (!consensus) {
2909
+ return errorResponse(`No results found for ${certification_id}/${agent}. Record model results first.`);
2910
+ }
2911
+ if (output_format === "markdown") {
2912
+ return textResponse(runner.generateReport(certification_id, agent));
2913
+ }
2914
+ return jsonResponse({
2915
+ certificationId: consensus.certificationId,
2916
+ agent: consensus.agent,
2917
+ models: consensus.models,
2918
+ metrics: {
2919
+ agreementRate: `${consensus.metrics.agreementRate}%`,
2920
+ fleissKappa: consensus.metrics.fleissKappa,
2921
+ reliability: interpretKappa(consensus.metrics.fleissKappa),
2922
+ unanimousFindings: consensus.metrics.unanimousFindings,
2923
+ majorityFindings: consensus.metrics.majorityFindings,
2924
+ disputedFindings: consensus.metrics.disputedFindings,
2925
+ uniqueFindings: consensus.metrics.uniqueFindings,
2926
+ },
2927
+ disagreements: consensus.disagreements.length,
2928
+ mergedFindings: consensus.mergedFindings.length,
2929
+ bySeverity: consensus.metrics.bySeverity,
2930
+ });
2931
+ }
2932
+ catch (error) {
2933
+ return errorResponse(`Failed to calculate consensus: ${error instanceof Error ? error.message : String(error)}`);
2934
+ }
2935
+ });
2936
+ // ---------------------------------------------------------------------------
2937
+ // Tool: Get Disagreements from Consensus
2938
+ // ---------------------------------------------------------------------------
2939
+ server.registerTool("consensus_disagreements", {
2940
+ title: "Get Disagreements",
2941
+ description: `Get detailed disagreements from recorded findings. Shows where different runs/models produced conflicting results. Useful for manual review.`,
2942
+ inputSchema: {
2943
+ certification_id: z.string().describe("Certification ID"),
2944
+ agent: z.enum(["security", "reliability", "typesafety", "performance", "quality", "redteam"]).describe("Agent type"),
2945
+ severity_filter: z.enum(["high", "medium", "low"]).optional().describe("Filter by disagreement severity"),
2946
+ },
2947
+ annotations: {
2948
+ readOnlyHint: true,
2949
+ destructiveHint: false,
2950
+ idempotentHint: true,
2951
+ openWorldHint: false,
2952
+ },
2953
+ }, async ({ certification_id, agent, severity_filter }) => {
2954
+ try {
2955
+ const runner = getRunner();
2956
+ const consensus = runner.calculateConsensus(certification_id, agent);
2957
+ if (!consensus) {
2958
+ return errorResponse(`No results found for ${certification_id}/${agent}`);
2959
+ }
2960
+ let disagreements = consensus.disagreements;
2961
+ if (severity_filter) {
2962
+ disagreements = disagreements.filter((d) => d.severity === severity_filter);
2963
+ }
2964
+ return jsonResponse({
2965
+ certificationId: certification_id,
2966
+ agent,
2967
+ totalDisagreements: consensus.disagreements.length,
2968
+ filtered: disagreements.length,
2969
+ disagreements: disagreements.map((d) => ({
2970
+ type: d.type,
2971
+ findingId: d.findingId,
2972
+ severity: d.severity,
2973
+ models: d.models,
2974
+ values: d.details.values,
2975
+ resolution: d.details.resolution,
2976
+ })),
2977
+ });
2978
+ }
2979
+ catch (error) {
2980
+ return errorResponse(`Failed to get disagreements: ${error instanceof Error ? error.message : String(error)}`);
2981
+ }
2982
+ });
2983
+ // ---------------------------------------------------------------------------
2984
+ // Tool: Get Merged Findings from Consensus
2985
+ // ---------------------------------------------------------------------------
2986
+ server.registerTool("consensus_merged", {
2987
+ title: "Get Merged Findings",
2988
+ description: `Get deduplicated findings after consensus calculation. Returns high-confidence findings that were agreed upon by multiple recorded runs.`,
2989
+ inputSchema: {
2990
+ certification_id: z.string().describe("Certification ID"),
2991
+ agent: z.enum(["security", "reliability", "typesafety", "performance", "quality", "redteam"]).describe("Agent type"),
2992
+ require_majority: z.boolean().optional().describe("Only include findings with majority agreement. Default: true"),
2993
+ min_confidence: z.number().optional().describe("Minimum confidence score (0-100). Default: 50"),
2994
+ },
2995
+ annotations: {
2996
+ readOnlyHint: true,
2997
+ destructiveHint: false,
2998
+ idempotentHint: true,
2999
+ openWorldHint: false,
3000
+ },
3001
+ }, async ({ certification_id, agent, require_majority, min_confidence }) => {
3002
+ try {
3003
+ const runner = getRunner();
3004
+ const consensus = runner.calculateConsensus(certification_id, agent, { requireMajority: require_majority ?? true });
3005
+ if (!consensus) {
3006
+ return errorResponse(`No results found for ${certification_id}/${agent}`);
3007
+ }
3008
+ let findings = consensus.mergedFindings;
3009
+ if (min_confidence !== undefined) {
3010
+ findings = findings.filter((f) => (f.confidence ?? 0) >= min_confidence);
3011
+ }
3012
+ return jsonResponse({
3013
+ certificationId: certification_id,
3014
+ agent,
3015
+ models: consensus.models,
3016
+ totalMerged: consensus.mergedFindings.length,
3017
+ filtered: findings.length,
3018
+ agreementRate: `${consensus.metrics.agreementRate}%`,
3019
+ findings: findings.map((f) => ({
3020
+ id: f.id,
3021
+ description: f.description,
3022
+ severity: f.severity,
3023
+ category: f.category,
3024
+ file: f.file,
3025
+ line: f.line,
3026
+ confidence: f.confidence,
3027
+ })),
3028
+ });
3029
+ }
3030
+ catch (error) {
3031
+ return errorResponse(`Failed to get merged findings: ${error instanceof Error ? error.message : String(error)}`);
3032
+ }
3033
+ });
3034
+ // ---------------------------------------------------------------------------
3035
+ // Tool: Consensus Summary
3036
+ // ---------------------------------------------------------------------------
3037
+ server.registerTool("consensus_summary", {
3038
+ title: "Consensus Summary",
3039
+ description: `Get a summary of consensus results from recorded findings. Includes agreement rates, Fleiss' kappa reliability, and cost tracking.`,
3040
+ inputSchema: {
3041
+ certification_id: z.string().describe("Certification ID"),
3042
+ agent: z.enum(["security", "reliability", "typesafety", "performance", "quality", "redteam"]).describe("Agent type"),
3043
+ },
3044
+ annotations: {
3045
+ readOnlyHint: true,
3046
+ destructiveHint: false,
3047
+ idempotentHint: true,
3048
+ openWorldHint: false,
3049
+ },
3050
+ }, async ({ certification_id, agent }) => {
3051
+ try {
3052
+ const runner = getRunner();
3053
+ const summary = runner.generateSummary(certification_id, agent);
3054
+ if (!summary) {
3055
+ return errorResponse(`No results found for ${certification_id}/${agent}`);
3056
+ }
3057
+ return jsonResponse({
3058
+ certificationId: certification_id,
3059
+ agent,
3060
+ models: {
3061
+ total: summary.totalModels,
3062
+ successful: summary.successfulModels,
3063
+ },
3064
+ findings: {
3065
+ total: summary.totalFindings,
3066
+ unique: summary.uniqueFindings,
3067
+ highConfidence: summary.highConfidenceFindings,
3068
+ disputed: summary.disputedFindings,
3069
+ },
3070
+ consensus: {
3071
+ agreementRate: `${summary.agreementRate}%`,
3072
+ reliability: summary.reliability,
3073
+ interpretation: interpretKappa(summary.reliability),
3074
+ },
3075
+ cost: {
3076
+ total: formatCost(summary.totalCost),
3077
+ tokens: formatTokens(summary.totalTokens),
3078
+ },
3079
+ });
3080
+ }
3081
+ catch (error) {
3082
+ return errorResponse(`Failed to get summary: ${error instanceof Error ? error.message : String(error)}`);
3083
+ }
3084
+ });
3085
+ // ---------------------------------------------------------------------------
3086
+ // Tool: List Model Configurations
3087
+ // ---------------------------------------------------------------------------
3088
+ server.registerTool("consensus_models", {
3089
+ title: "List Model Configurations",
3090
+ description: `List model configurations for consensus aggregation. These are models you can record results from - this tool does NOT run models.`,
3091
+ inputSchema: {
3092
+ enabled_only: z.boolean().optional().describe("Only show enabled models. Default: false"),
3093
+ },
3094
+ annotations: {
3095
+ readOnlyHint: true,
3096
+ destructiveHint: false,
3097
+ idempotentHint: true,
3098
+ openWorldHint: false,
3099
+ },
3100
+ }, async ({ enabled_only }) => {
3101
+ const runner = getRunner();
3102
+ let models = enabled_only ? runner.getEnabledModels() : DEFAULT_MODELS;
3103
+ return jsonResponse({
3104
+ totalModels: models.length,
3105
+ models: models.map((m) => ({
3106
+ id: m.id,
3107
+ provider: formatProvider(m.provider),
3108
+ weight: m.weight ?? 1.0,
3109
+ enabled: m.enabled ?? true,
3110
+ })),
3111
+ recommended: DEFAULT_MODELS.map((m) => m.id),
3112
+ });
3113
+ });
3114
+ // ---------------------------------------------------------------------------
3115
+ // Tool: Clear Recorded Results
3116
+ // ---------------------------------------------------------------------------
3117
+ server.registerTool("consensus_clear", {
3118
+ title: "Clear Recorded Results",
3119
+ description: `Clear previously recorded findings for a certification. Use before re-recording results for fresh consensus calculation.`,
3120
+ inputSchema: {
3121
+ certification_id: z.string().describe("Certification ID"),
3122
+ agent: z.enum(["security", "reliability", "typesafety", "performance", "quality", "redteam"]).optional().describe("Specific agent to clear, or all if not specified"),
3123
+ },
3124
+ annotations: {
3125
+ readOnlyHint: false,
3126
+ destructiveHint: true,
3127
+ idempotentHint: true,
3128
+ openWorldHint: false,
3129
+ },
3130
+ }, async ({ certification_id, agent }) => {
3131
+ try {
3132
+ const runner = getRunner();
3133
+ runner.clearResults(certification_id, agent);
3134
+ return jsonResponse({
3135
+ cleared: true,
3136
+ certificationId: certification_id,
3137
+ agent: agent || "all",
3138
+ });
3139
+ }
3140
+ catch (error) {
3141
+ return errorResponse(`Failed to clear results: ${error instanceof Error ? error.message : String(error)}`);
3142
+ }
3143
+ });
3144
+ /**
3145
+ * Interpret Fleiss' kappa value
3146
+ */
3147
+ function interpretKappa(kappa) {
3148
+ if (kappa < 0)
3149
+ return "Poor (less than chance)";
3150
+ if (kappa < 0.2)
3151
+ return "Slight";
3152
+ if (kappa < 0.4)
3153
+ return "Fair";
3154
+ if (kappa < 0.6)
3155
+ return "Moderate";
3156
+ if (kappa < 0.8)
3157
+ return "Substantial";
3158
+ return "Almost Perfect";
3159
+ }
3160
+ // ===========================================================================
3161
+ // M6: Agent & MCP Security Certification Tools
3162
+ // ===========================================================================
3163
+ // ---------------------------------------------------------------------------
3164
+ // Tool: Agent Cert Scan - Full agent system certification
3165
+ // ---------------------------------------------------------------------------
3166
+ server.registerTool("agent_cert_scan", {
3167
+ title: "Run Agent System Certification",
3168
+ description: `Full agent-system security certification against MCP servers and AI tool chains.
3169
+
3170
+ Runs all 8 agent scanners:
3171
+ - Manifest Audit: Validates MCP manifest security (hints, schemas, origins)
3172
+ - Tool Description Drift: Detects silent changes to tool definitions ("rug-pull detector")
3173
+ - Prompt Injection Fuzzer: Tests tools against 200+ injection payloads
3174
+ - Exfiltration Path Graph: Maps secret→network data flow paths
3175
+ - Permission Minimiser: Proposes tightened permissions based on actual usage
3176
+ - Supply Chain MCP: CVE scan, license compliance, typosquatting detection
3177
+ - Sandbox Audit: Detects sandbox escapes (eval, child_process, etc.)
3178
+ - Credential Scope Audit: Checks for over-scoped tokens and credentials
3179
+
3180
+ Maps findings to AI compliance frameworks (OWASP LLM, NIST AI RMF, EU AI Act).`,
3181
+ inputSchema: {
3182
+ target: z.string().describe("MCP server URL, config file path, or npm package name"),
3183
+ certification_id: z.string().optional().describe("Existing certification ID to add findings to"),
3184
+ scanners: z.array(z.enum([
3185
+ "manifest-audit",
3186
+ "tool-description-drift",
3187
+ "prompt-injection-fuzzer",
3188
+ "exfil-path-graph",
3189
+ "permission-minimiser",
3190
+ "supply-chain-mcp",
3191
+ "sandbox-audit",
3192
+ "credential-scope-audit",
3193
+ ])).optional().describe("Specific scanners to run (default: all)"),
3194
+ frameworks: z.array(z.enum([
3195
+ "OWASP-LLM",
3196
+ "NIST-AI-RMF",
3197
+ "MITRE-ATLAS",
3198
+ "EU-AI-ACT",
3199
+ "ISO-42001",
3200
+ ])).optional().describe("AI compliance frameworks to map findings against"),
3201
+ authorized: z.boolean().describe("Explicit authorization for scanning (required)"),
3202
+ source_path: z.string().optional().describe("Path to MCP server source code for sandbox audit"),
3203
+ traces_dir: z.string().optional().describe("Path to tool usage traces for permission analysis"),
3204
+ },
3205
+ annotations: {
3206
+ readOnlyHint: true,
3207
+ destructiveHint: false,
3208
+ idempotentHint: true,
3209
+ openWorldHint: true,
3210
+ },
3211
+ }, async ({ target, certification_id, scanners, frameworks, authorized, source_path, traces_dir }) => {
3212
+ if (!authorized) {
3213
+ return errorResponse("Agent scanning requires explicit authorization. Set authorized=true to confirm you have permission to scan this target.");
3214
+ }
3215
+ try {
3216
+ // Build scan target
3217
+ const scanTarget = {};
3218
+ if (target.startsWith("http://") || target.startsWith("https://")) {
3219
+ scanTarget.url = target;
3220
+ }
3221
+ else if (target.endsWith(".json")) {
3222
+ scanTarget.configFile = target;
3223
+ }
3224
+ else if (target.includes("/") || target.includes("\\")) {
3225
+ scanTarget.configFile = target;
3226
+ }
3227
+ else {
3228
+ scanTarget.npmPackage = target;
3229
+ }
3230
+ // Build scanner options
3231
+ const enabledScanners = scanners || AGENT_SCANNER_TYPES;
3232
+ const scannerFlags = {
3233
+ manifestAudit: enabledScanners.includes("manifest-audit"),
3234
+ toolDrift: enabledScanners.includes("tool-description-drift"),
3235
+ promptInjection: enabledScanners.includes("prompt-injection-fuzzer"),
3236
+ exfilPath: enabledScanners.includes("exfil-path-graph"),
3237
+ permissionMinimiser: enabledScanners.includes("permission-minimiser"),
3238
+ supplyChain: enabledScanners.includes("supply-chain-mcp"),
3239
+ sandboxAudit: enabledScanners.includes("sandbox-audit"),
3240
+ credentialScope: enabledScanners.includes("credential-scope-audit"),
3241
+ };
3242
+ const options = {
3243
+ target: scanTarget,
3244
+ authorized: true,
3245
+ scanners: scannerFlags,
3246
+ sandboxSourcePath: source_path,
3247
+ tracesDir: traces_dir,
3248
+ };
3249
+ // Run agent scanners
3250
+ const result = await runAgentScanners(options);
3251
+ // Flatten all findings from scanners
3252
+ const allFindings = result.scanners.flatMap((s) => s.findings);
3253
+ // Generate compliance mapping if frameworks specified
3254
+ let complianceMapping;
3255
+ if (frameworks && frameworks.length > 0) {
3256
+ complianceMapping = {};
3257
+ for (const fw of frameworks) {
3258
+ const controls = getAIFrameworkControls(fw);
3259
+ const meta = AI_FRAMEWORKS_METADATA[fw];
3260
+ complianceMapping[fw] = {
3261
+ framework: meta?.name || fw,
3262
+ version: meta?.version,
3263
+ controlCount: controls.length,
3264
+ relevantFindings: allFindings.filter((f) => controls.some((c) => c.findingCategories.includes(f.ruleId.split(":")[0]))).length,
3265
+ };
3266
+ }
3267
+ }
3268
+ // Generate summary
3269
+ const summary = generateAgentScannerSummary(result);
3270
+ return jsonResponse({
3271
+ success: result.allSucceeded,
3272
+ target,
3273
+ certificationId: certification_id,
3274
+ manifestHash: result.manifestHash,
3275
+ mcpServer: result.manifest?.name,
3276
+ mcpVersion: result.manifest?.version,
3277
+ duration: result.totalDuration,
3278
+ timestamp: result.timestamp,
3279
+ scannersRun: result.scanners.map((s) => s.scanner),
3280
+ scannersFailed: result.failedScanners,
3281
+ totalFindings: result.totalFindings,
3282
+ bySeverity: result.bySeverity,
3283
+ byScanner: result.byScanner,
3284
+ riskScore: result.riskScore,
3285
+ certificationReadiness: result.certificationReadiness,
3286
+ complianceMapping,
3287
+ summary,
3288
+ findings: allFindings.slice(0, 50), // First 50 findings
3289
+ hasMore: allFindings.length > 50,
3290
+ });
3291
+ }
3292
+ catch (error) {
3293
+ return errorResponse(`Agent certification failed: ${error instanceof Error ? error.message : String(error)}`);
3294
+ }
3295
+ });
3296
+ // ---------------------------------------------------------------------------
3297
+ // Tool: Agent Cert Fuzz - Fast prompt injection fuzzing
3298
+ // ---------------------------------------------------------------------------
3299
+ server.registerTool("agent_cert_fuzz", {
3300
+ title: "Run Prompt Injection Fuzzer",
3301
+ description: `Quick CI-friendly prompt injection fuzzing for MCP tools.
3302
+
3303
+ Tests tool inputs against injection payloads to detect:
3304
+ - Direct instruction overrides ("Ignore previous instructions...")
3305
+ - Indirect injections via file/URL content
3306
+ - Exfiltration attempts ("Send to http://...")
3307
+ - Jailbreaks and safety bypasses
3308
+ - Unicode obfuscation and homoglyphs
3309
+
3310
+ Corpus sizes:
3311
+ - quick: ~50 payloads (<30s)
3312
+ - standard: ~100 payloads (~60s)
3313
+ - thorough: 200+ payloads (~2min)`,
3314
+ inputSchema: {
3315
+ target: z.string().describe("MCP server config file, URL, or npm package"),
3316
+ corpus: z.enum(["quick", "standard", "thorough"]).default("quick").describe("Payload corpus size"),
3317
+ custom_payloads: z.string().optional().describe("Path to custom payload file (JSON array)"),
3318
+ authorized: z.boolean().describe("Explicit authorization for fuzzing (required)"),
3319
+ },
3320
+ annotations: {
3321
+ readOnlyHint: true,
3322
+ destructiveHint: false,
3323
+ idempotentHint: false,
3324
+ openWorldHint: true,
3325
+ },
3326
+ }, async ({ target, corpus, custom_payloads, authorized }) => {
3327
+ if (!authorized) {
3328
+ return errorResponse("Fuzzing requires explicit authorization. Set authorized=true to confirm you have permission to test this target.");
3329
+ }
3330
+ try {
3331
+ // Build scan target
3332
+ const scanTarget = {};
3333
+ if (target.startsWith("http://") || target.startsWith("https://")) {
3334
+ scanTarget.url = target;
3335
+ }
3336
+ else if (target.endsWith(".json")) {
3337
+ scanTarget.configFile = target;
3338
+ }
3339
+ else {
3340
+ scanTarget.npmPackage = target;
3341
+ }
3342
+ // Discover manifest
3343
+ const manifest = await discoverManifest(scanTarget);
3344
+ if (!manifest) {
3345
+ return errorResponse("Could not discover MCP manifest from target");
3346
+ }
3347
+ // Run fuzzer
3348
+ const result = await runPromptInjectionFuzzer(manifest, {
3349
+ corpus: corpus,
3350
+ customCorpus: custom_payloads,
3351
+ });
3352
+ const passedCount = result.findings.filter((f) => f.severity === "info").length;
3353
+ const failedCount = result.findings.filter((f) => f.severity !== "info").length;
3354
+ return jsonResponse({
3355
+ success: result.success,
3356
+ target,
3357
+ corpus,
3358
+ duration: result.duration,
3359
+ mcpServer: result.mcpServerName,
3360
+ toolsTested: manifest.tools.length,
3361
+ totalTests: passedCount + failedCount,
3362
+ passed: passedCount,
3363
+ failed: failedCount,
3364
+ passRate: ((passedCount / (passedCount + failedCount)) * 100).toFixed(1) + "%",
3365
+ findings: result.findings.filter((f) => f.severity !== "info").slice(0, 20),
3366
+ recommendation: failedCount === 0
3367
+ ? "All injection tests passed. Consider running 'thorough' corpus for comprehensive coverage."
3368
+ : `${failedCount} injection(s) detected. Review findings and implement input validation.`,
3369
+ });
3370
+ }
3371
+ catch (error) {
3372
+ return errorResponse(`Fuzzing failed: ${error instanceof Error ? error.message : String(error)}`);
3373
+ }
3374
+ });
3375
+ // ---------------------------------------------------------------------------
3376
+ // Tool: Agent Cert Attest - Generate signed attestation bundle
3377
+ // ---------------------------------------------------------------------------
3378
+ server.registerTool("agent_cert_attest", {
3379
+ title: "Generate Signed Attestation",
3380
+ description: `Create a Sigstore-signed attestation bundle for agent certification.
3381
+
3382
+ The attestation includes:
3383
+ - Manifest hash (SHA-256)
3384
+ - Scanner results summary
3385
+ - Compliance mapping
3386
+ - Sigstore signature (keyless)
3387
+ - in-toto provenance statement
3388
+
3389
+ Attestation can be verified with: cosign verify-blob`,
3390
+ inputSchema: {
3391
+ certification_id: z.string().describe("Certification ID from agent_cert_scan"),
3392
+ project_path: z.string().describe("Path to MCP server project"),
3393
+ output_file: z.string().optional().describe("Output path for attestation bundle (default: .vaspera/attestations/)"),
3394
+ include_findings: z.boolean().optional().describe("Include finding details in attestation (default: false)"),
3395
+ },
3396
+ annotations: {
3397
+ readOnlyHint: false,
3398
+ destructiveHint: false,
3399
+ idempotentHint: true,
3400
+ openWorldHint: false,
3401
+ },
3402
+ }, async ({ certification_id, project_path, output_file, include_findings }) => {
3403
+ try {
3404
+ // Check if signing is available
3405
+ const signingAvailable = await isSigningAvailable();
3406
+ if (!signingAvailable) {
3407
+ return errorResponse("Sigstore signing not available. Ensure you have @sigstore/sign installed and network access to Fulcio/Rekor.");
3408
+ }
3409
+ // Get certification data
3410
+ const cert = await getCertification(project_path, certification_id);
3411
+ if (!cert) {
3412
+ return errorResponse(`Certification not found: ${certification_id}`);
3413
+ }
3414
+ // Build attestation payload
3415
+ const attestation = {
3416
+ _type: "https://in-toto.io/Statement/v0.1",
3417
+ predicateType: "https://vaspera.dev/attestation/agent-cert/v1",
3418
+ subject: [{
3419
+ name: cert.metadata.project_name,
3420
+ digest: { sha256: cert.metadata.project_hash || "unknown" },
3421
+ }],
3422
+ predicate: {
3423
+ certificationId: certification_id,
3424
+ certificationLevel: cert.consensus?.certification_level || "REVIEW_REQUIRED",
3425
+ score: cert.consensus?.overall_score || 0,
3426
+ timestamp: new Date().toISOString(),
3427
+ expiresAt: cert.metadata.expires_at,
3428
+ scannersRun: cert.metadata.scanners_run,
3429
+ findingsCount: cert.metadata.scanner_findings_count,
3430
+ bySeverity: cert.consensus?.by_severity,
3431
+ agentsCompleted: cert.metadata.agents_completed,
3432
+ ...(include_findings ? { findings: Object.values(cert.agents).flatMap((a) => a?.findings || []).slice(0, 100) } : {}),
3433
+ },
3434
+ };
3435
+ // Sign the attestation
3436
+ const attestationJson = JSON.stringify(attestation, null, 2);
3437
+ const signature = await signContent(attestationJson);
3438
+ // Build output path
3439
+ const attestDir = join(project_path, ".vaspera", "attestations");
3440
+ await mkdir(attestDir, { recursive: true });
3441
+ const outputPath = output_file || join(attestDir, `${certification_id}.sigstore.json`);
3442
+ // Create bundle
3443
+ const bundle = {
3444
+ attestation,
3445
+ signature,
3446
+ signedAt: new Date().toISOString(),
3447
+ verifyWith: "cosign verify-blob --certificate-identity-regexp=.* --certificate-oidc-issuer-regexp=.*",
3448
+ };
3449
+ await writeFile(outputPath, JSON.stringify(bundle, null, 2));
3450
+ return jsonResponse({
3451
+ success: true,
3452
+ certificationId: certification_id,
3453
+ attestationPath: outputPath,
3454
+ certificationLevel: cert.consensus?.certification_level,
3455
+ score: cert.consensus?.overall_score,
3456
+ signedAt: bundle.signedAt,
3457
+ expiresAt: cert.metadata.expires_at,
3458
+ verifyCommand: `cosign verify-blob --bundle ${outputPath}`,
3459
+ });
3460
+ }
3461
+ catch (error) {
3462
+ return errorResponse(`Attestation generation failed: ${error instanceof Error ? error.message : String(error)}`);
3463
+ }
3464
+ });
3465
+ // ---------------------------------------------------------------------------
3466
+ // Tool: Agent Cert Verify - Verify attestation against live server
3467
+ // ---------------------------------------------------------------------------
3468
+ server.registerTool("agent_cert_verify", {
3469
+ title: "Verify Attestation Bundle",
3470
+ description: `Verify an attestation bundle against a live MCP server.
3471
+
3472
+ Checks:
3473
+ - Sigstore signature validity
3474
+ - Manifest drift from attested state
3475
+ - Certification expiration
3476
+ - Revocation status (if applicable)
3477
+
3478
+ Returns verification status and any detected changes.`,
3479
+ inputSchema: {
3480
+ attestation: z.string().describe("Path to attestation bundle (.sigstore.json)"),
3481
+ target: z.string().describe("Live MCP server config, URL, or package to verify against"),
3482
+ strict: z.boolean().optional().describe("Fail on any drift, not just security-relevant changes (default: false)"),
3483
+ },
3484
+ annotations: {
3485
+ readOnlyHint: true,
3486
+ destructiveHint: false,
3487
+ idempotentHint: true,
3488
+ openWorldHint: true,
3489
+ },
3490
+ }, async ({ attestation, target, strict }) => {
3491
+ try {
3492
+ // Load attestation
3493
+ const attestationContent = await safeReadFile(attestation);
3494
+ if (!attestationContent) {
3495
+ return errorResponse(`Attestation file not found: ${attestation}`);
3496
+ }
3497
+ const bundle = JSON.parse(attestationContent);
3498
+ const attestedData = bundle.attestation;
3499
+ // Verify signature (basic check - full Sigstore verification requires cosign)
3500
+ if (!bundle.signature) {
3501
+ return jsonResponse({
3502
+ verified: false,
3503
+ error: "Attestation is unsigned",
3504
+ recommendation: "Re-generate attestation with agent_cert_attest",
3505
+ });
3506
+ }
3507
+ // Check expiration
3508
+ const expiresAt = attestedData.predicate?.expiresAt;
3509
+ const isExpired = expiresAt ? new Date(expiresAt) < new Date() : false;
3510
+ // Discover current manifest
3511
+ const scanTarget = {};
3512
+ if (target.startsWith("http://") || target.startsWith("https://")) {
3513
+ scanTarget.url = target;
3514
+ }
3515
+ else if (target.endsWith(".json")) {
3516
+ scanTarget.configFile = target;
3517
+ }
3518
+ else {
3519
+ scanTarget.npmPackage = target;
3520
+ }
3521
+ const currentManifest = await discoverManifest(scanTarget);
3522
+ if (!currentManifest) {
3523
+ return errorResponse("Could not discover current MCP manifest from target");
3524
+ }
3525
+ // Compare manifests (basic drift detection)
3526
+ const attestedSubject = attestedData.subject?.[0];
3527
+ const attestedName = attestedSubject?.name;
3528
+ const attestedHash = attestedSubject?.digest?.sha256;
3529
+ const driftDetected = [];
3530
+ if (attestedName && attestedName !== currentManifest.name) {
3531
+ driftDetected.push(`Server name changed: ${attestedName} → ${currentManifest.name}`);
3532
+ }
3533
+ // Simple tool count check (full drift detection via tool-description-drift scanner)
3534
+ const attestedToolCount = attestedData.predicate?.toolCount;
3535
+ if (attestedToolCount && attestedToolCount !== currentManifest.tools.length) {
3536
+ driftDetected.push(`Tool count changed: ${attestedToolCount} → ${currentManifest.tools.length}`);
3537
+ }
3538
+ const verified = !isExpired && (driftDetected.length === 0 || !strict);
3539
+ return jsonResponse({
3540
+ verified,
3541
+ attestation: {
3542
+ certificationId: attestedData.predicate?.certificationId,
3543
+ certificationLevel: attestedData.predicate?.certificationLevel,
3544
+ score: attestedData.predicate?.score,
3545
+ signedAt: bundle.signedAt,
3546
+ expiresAt,
3547
+ },
3548
+ checks: {
3549
+ signaturePresent: !!bundle.signature,
3550
+ expired: isExpired,
3551
+ driftDetected: driftDetected.length > 0,
3552
+ driftDetails: driftDetected,
3553
+ },
3554
+ currentServer: {
3555
+ name: currentManifest.name,
3556
+ version: currentManifest.version,
3557
+ toolCount: currentManifest.tools.length,
3558
+ },
3559
+ recommendation: isExpired
3560
+ ? "Attestation expired. Run agent_cert_scan and agent_cert_attest to renew."
3561
+ : driftDetected.length > 0
3562
+ ? "Drift detected. Run agent_cert_scan to re-certify current state."
3563
+ : "Attestation valid. Server matches certified state.",
3564
+ });
3565
+ }
3566
+ catch (error) {
3567
+ return errorResponse(`Verification failed: ${error instanceof Error ? error.message : String(error)}`);
3568
+ }
3569
+ });
3570
+ // ---------------------------------------------------------------------------
3571
+ // Tool: Agent Cert Watch - Continuous monitoring with webhooks
3572
+ // ---------------------------------------------------------------------------
3573
+ server.registerTool("agent_cert_watch", {
3574
+ title: "Continuous Agent Monitoring",
3575
+ description: `Subscribe to tool-use telemetry and detect drift in MCP servers.
3576
+
3577
+ Monitors for:
3578
+ - Manifest changes (tool additions/removals/modifications)
3579
+ - Unusual tool invocation patterns
3580
+ - Permission escalation attempts
3581
+ - Certification expiration warnings
3582
+
3583
+ Note: This tool sets up monitoring configuration. Actual continuous monitoring
3584
+ requires a separate daemon process or integration with your observability stack.`,
3585
+ inputSchema: {
3586
+ target: z.string().describe("MCP server config, URL, or package to monitor"),
3587
+ project_path: z.string().describe("Project path for storing monitoring config"),
3588
+ webhook_url: z.string().optional().describe("Webhook URL for drift notifications"),
3589
+ interval_minutes: z.number().optional().describe("Check interval in minutes (default: 60)"),
3590
+ alert_on: z.array(z.enum([
3591
+ "manifest-drift",
3592
+ "tool-drift",
3593
+ "permission-change",
3594
+ "expiration-warning",
3595
+ "security-finding",
3596
+ ])).optional().describe("Events to alert on"),
3597
+ },
3598
+ annotations: {
3599
+ readOnlyHint: false,
3600
+ destructiveHint: false,
3601
+ idempotentHint: true,
3602
+ openWorldHint: true,
3603
+ },
3604
+ }, async ({ target, project_path, webhook_url, interval_minutes, alert_on }) => {
3605
+ try {
3606
+ // Build scan target and discover manifest
3607
+ const scanTarget = {};
3608
+ if (target.startsWith("http://") || target.startsWith("https://")) {
3609
+ scanTarget.url = target;
3610
+ }
3611
+ else if (target.endsWith(".json")) {
3612
+ scanTarget.configFile = target;
3613
+ }
3614
+ else {
3615
+ scanTarget.npmPackage = target;
3616
+ }
3617
+ const manifest = await discoverManifest(scanTarget);
3618
+ if (!manifest) {
3619
+ return errorResponse("Could not discover MCP manifest from target");
3620
+ }
3621
+ // Create monitoring config
3622
+ const monitoringConfig = {
3623
+ version: "1.0.0",
3624
+ target,
3625
+ mcpServer: manifest.name,
3626
+ mcpVersion: manifest.version,
3627
+ createdAt: new Date().toISOString(),
3628
+ interval: interval_minutes || 60,
3629
+ webhookUrl: webhook_url,
3630
+ alertOn: alert_on || ["manifest-drift", "security-finding", "expiration-warning"],
3631
+ baseline: {
3632
+ toolCount: manifest.tools.length,
3633
+ tools: manifest.tools.map((t) => ({
3634
+ name: t.name,
3635
+ hash: Buffer.from(JSON.stringify({ name: t.name, description: t.description })).toString("base64").slice(0, 16),
3636
+ })),
3637
+ capturedAt: new Date().toISOString(),
3638
+ },
3639
+ };
3640
+ // Save config
3641
+ const configDir = join(project_path, ".vaspera", "monitoring");
3642
+ await mkdir(configDir, { recursive: true });
3643
+ const configPath = join(configDir, `${manifest.name.replace(/[^a-zA-Z0-9]/g, "-")}.json`);
3644
+ await writeFile(configPath, JSON.stringify(monitoringConfig, null, 2));
3645
+ return jsonResponse({
3646
+ success: true,
3647
+ target,
3648
+ mcpServer: manifest.name,
3649
+ configPath,
3650
+ interval: `${interval_minutes || 60} minutes`,
3651
+ webhookConfigured: !!webhook_url,
3652
+ alertEvents: monitoringConfig.alertOn,
3653
+ baseline: {
3654
+ toolCount: manifest.tools.length,
3655
+ capturedAt: monitoringConfig.baseline.capturedAt,
3656
+ },
3657
+ nextSteps: [
3658
+ "Run a cron job or daemon to execute drift checks at the configured interval",
3659
+ "Use agent_cert_verify with the attestation to check for drift",
3660
+ "Integrate webhook notifications with your alerting system (Slack, PagerDuty, etc.)",
3661
+ ],
3662
+ exampleCron: `*/${interval_minutes || 60} * * * * cd ${project_path} && npx vaspera agent-check ${target}`,
3663
+ });
3664
+ }
3665
+ catch (error) {
3666
+ return errorResponse(`Watch setup failed: ${error instanceof Error ? error.message : String(error)}`);
3667
+ }
3668
+ });
3669
+ // ---------------------------------------------------------------------------
3670
+ // Tool: Agent Scanners Available - Check which scanners are installed
3671
+ // ---------------------------------------------------------------------------
3672
+ server.registerTool("agent_scanners_available", {
3673
+ title: "Check Agent Scanners",
3674
+ description: `Check which agent security scanners are available and their versions.`,
3675
+ inputSchema: {},
3676
+ annotations: {
3677
+ readOnlyHint: true,
3678
+ destructiveHint: false,
3679
+ idempotentHint: true,
3680
+ openWorldHint: false,
3681
+ },
3682
+ }, async () => {
3683
+ try {
3684
+ const availability = await checkAgentScannersAvailable();
3685
+ const available = availability.filter((a) => a.available);
3686
+ const unavailable = availability.filter((a) => !a.available);
3687
+ return jsonResponse({
3688
+ totalScanners: AGENT_SCANNER_TYPES.length,
3689
+ available: available.length,
3690
+ unavailable: unavailable.length,
3691
+ scanners: availability.map((a) => ({
3692
+ scanner: a.scanner,
3693
+ available: a.available,
3694
+ version: a.version,
3695
+ error: a.error,
3696
+ })),
3697
+ aiFrameworks: Object.entries(AI_FRAMEWORKS_METADATA).map(([id, meta]) => ({
3698
+ id,
3699
+ name: meta.name,
3700
+ version: meta.version,
3701
+ controlCount: meta.controlCount,
3702
+ })),
3703
+ });
3704
+ }
3705
+ catch (error) {
3706
+ return errorResponse(`Scanner check failed: ${error instanceof Error ? error.message : String(error)}`);
3707
+ }
3708
+ });
3709
+ // ===========================================================================
3710
+ // Mythos-Class Security Scanners & Agents
3711
+ // ===========================================================================
3712
+ // ---------------------------------------------------------------------------
3713
+ // Tool: Binary Analysis Scanner
3714
+ // ---------------------------------------------------------------------------
3715
+ server.registerTool("certification_scan_binary", {
3716
+ title: "Scan Binary/Native Modules",
3717
+ description: `Analyze compiled code and native modules for security hardening flags.
3718
+
3719
+ Checks:
3720
+ - Stack canary protection (CWE-121)
3721
+ - NX (No Execute) bit (CWE-119)
3722
+ - PIE (Position Independent Executable) (CWE-120)
3723
+ - RELRO (Relocation Read-Only) (CWE-134)
3724
+ - FORTIFY_SOURCE
3725
+ - RPATH/RUNPATH (library injection risk)
3726
+
3727
+ Targets:
3728
+ - Node.js native addons (binding.gyp, *.node)
3729
+ - Shared libraries (*.so, *.dll, *.dylib)
3730
+ - Rust FFI (Cargo.toml with cdylib)
3731
+ - Go CGO (import "C")
3732
+
3733
+ Requires checksec for full analysis, falls back to nm/pattern analysis.`,
3734
+ inputSchema: {
3735
+ project_path: z.string().describe("Path to project directory"),
3736
+ timeout: z.number().optional().describe("Timeout in ms (default: 120000)"),
3737
+ },
3738
+ annotations: {
3739
+ readOnlyHint: true,
3740
+ destructiveHint: false,
3741
+ idempotentHint: true,
3742
+ openWorldHint: false,
3743
+ },
3744
+ }, async ({ project_path, timeout }) => {
3745
+ try {
3746
+ const result = await runBinaryAnalysis(project_path, { timeout });
3747
+ return jsonResponse({
3748
+ scanner: "binary-analysis",
3749
+ success: result.success,
3750
+ duration: result.duration,
3751
+ targetsScanned: result.metadata?.targetsScanned || 0,
3752
+ toolsAvailable: result.metadata?.toolsAvailable,
3753
+ totalFindings: result.findings.length,
3754
+ bySeverity: {
3755
+ critical: result.findings.filter(f => f.severity === "critical").length,
3756
+ high: result.findings.filter(f => f.severity === "high").length,
3757
+ medium: result.findings.filter(f => f.severity === "medium").length,
3758
+ low: result.findings.filter(f => f.severity === "low").length,
3759
+ info: result.findings.filter(f => f.severity === "info").length,
3760
+ },
3761
+ findings: result.findings.slice(0, 30),
3762
+ hasMore: result.findings.length > 30,
3763
+ error: result.error,
3764
+ });
3765
+ }
3766
+ catch (error) {
3767
+ return errorResponse(`Binary analysis failed: ${error instanceof Error ? error.message : String(error)}`);
3768
+ }
3769
+ });
3770
+ // ---------------------------------------------------------------------------
3771
+ // Tool: Memory Safety Scanner
3772
+ // ---------------------------------------------------------------------------
3773
+ server.registerTool("certification_scan_memory", {
3774
+ title: "Scan for Memory Safety Issues",
3775
+ description: `Detect memory corruption vulnerabilities in C/C++/Rust code.
3776
+
3777
+ Vulnerability types:
3778
+ - Buffer overflow (CWE-120, CWE-787)
3779
+ - Use-after-free (CWE-416)
3780
+ - Double free (CWE-415)
3781
+ - Null dereference (CWE-476)
3782
+ - Uninitialized memory (CWE-908)
3783
+ - Format string (CWE-134)
3784
+
3785
+ Uses:
3786
+ - cppcheck for C/C++ (if available)
3787
+ - cargo-geiger for Rust unsafe code audit
3788
+ - Pattern matching for dangerous function calls (strcpy, gets, etc.)`,
3789
+ inputSchema: {
3790
+ project_path: z.string().describe("Path to project directory"),
3791
+ timeout: z.number().optional().describe("Timeout in ms (default: 120000)"),
3792
+ },
3793
+ annotations: {
3794
+ readOnlyHint: true,
3795
+ destructiveHint: false,
3796
+ idempotentHint: true,
3797
+ openWorldHint: false,
3798
+ },
3799
+ }, async ({ project_path, timeout }) => {
3800
+ try {
3801
+ const result = await runMemorySafetyAnalysis(project_path, { timeout });
3802
+ return jsonResponse({
3803
+ scanner: "memory-safety",
3804
+ success: result.success,
3805
+ duration: result.duration,
3806
+ languages: result.metadata?.languages,
3807
+ toolsUsed: result.metadata?.toolsUsed,
3808
+ totalFindings: result.findings.length,
3809
+ bySeverity: {
3810
+ critical: result.findings.filter(f => f.severity === "critical").length,
3811
+ high: result.findings.filter(f => f.severity === "high").length,
3812
+ medium: result.findings.filter(f => f.severity === "medium").length,
3813
+ low: result.findings.filter(f => f.severity === "low").length,
3814
+ info: result.findings.filter(f => f.severity === "info").length,
3815
+ },
3816
+ findings: result.findings.slice(0, 30),
3817
+ hasMore: result.findings.length > 30,
3818
+ error: result.error,
3819
+ });
3820
+ }
3821
+ catch (error) {
3822
+ return errorResponse(`Memory safety analysis failed: ${error instanceof Error ? error.message : String(error)}`);
3823
+ }
3824
+ });
3825
+ // ---------------------------------------------------------------------------
3826
+ // Tool: Race Condition Scanner
3827
+ // ---------------------------------------------------------------------------
3828
+ server.registerTool("certification_scan_race", {
3829
+ title: "Scan for Race Conditions",
3830
+ description: `Detect concurrency bugs that could lead to security vulnerabilities.
3831
+
3832
+ Detection targets:
3833
+ - TOCTOU (Time-of-check to time-of-use) - CWE-367
3834
+ - Data races - CWE-362
3835
+ - Missing synchronization - CWE-820
3836
+ - Check-then-act bugs - CWE-366
3837
+
3838
+ Languages:
3839
+ - Go: go vet -race detection, goroutine patterns
3840
+ - Node.js: Shared state + async, fs.exists TOCTOU
3841
+ - Python: threading issues, GIL bypass
3842
+ - Java: Non-atomic operations, HashMap concurrency`,
3843
+ inputSchema: {
3844
+ project_path: z.string().describe("Path to project directory"),
3845
+ timeout: z.number().optional().describe("Timeout in ms (default: 60000)"),
3846
+ },
3847
+ annotations: {
3848
+ readOnlyHint: true,
3849
+ destructiveHint: false,
3850
+ idempotentHint: true,
3851
+ openWorldHint: false,
3852
+ },
3853
+ }, async ({ project_path, timeout }) => {
3854
+ try {
3855
+ const result = await runRaceConditionAnalysis(project_path, { timeout });
3856
+ return jsonResponse({
3857
+ scanner: "race-condition",
3858
+ success: result.success,
3859
+ duration: result.duration,
3860
+ languages: result.metadata?.languages,
3861
+ toolsUsed: result.metadata?.toolsUsed,
3862
+ totalFindings: result.findings.length,
3863
+ bySeverity: {
3864
+ critical: result.findings.filter(f => f.severity === "critical").length,
3865
+ high: result.findings.filter(f => f.severity === "high").length,
3866
+ medium: result.findings.filter(f => f.severity === "medium").length,
3867
+ low: result.findings.filter(f => f.severity === "low").length,
3868
+ info: result.findings.filter(f => f.severity === "info").length,
3869
+ },
3870
+ findings: result.findings.slice(0, 30),
3871
+ hasMore: result.findings.length > 30,
3872
+ error: result.error,
3873
+ });
3874
+ }
3875
+ catch (error) {
3876
+ return errorResponse(`Race condition analysis failed: ${error instanceof Error ? error.message : String(error)}`);
3877
+ }
3878
+ });
3879
+ // ---------------------------------------------------------------------------
3880
+ // Tool: Zero-Day Hunter Agent
3881
+ // ---------------------------------------------------------------------------
3882
+ server.registerTool("certification_semantic_analysis", {
3883
+ title: "AI Semantic Security Analysis",
3884
+ description: `Run AI-powered semantic code analysis for zero-day vulnerabilities.
3885
+
3886
+ Focus areas:
3887
+ - Logic flaws: Inconsistent state handling, broken assumptions
3888
+ - Authentication bypasses: Ways to skip auth checks
3889
+ - Authorization issues: Privilege escalation paths
3890
+ - Cryptographic weaknesses: Weak algorithms, key management
3891
+ - Injection vectors: Novel injection patterns beyond OWASP
3892
+
3893
+ Uses pattern-based detection with AI reasoning.`,
3894
+ inputSchema: {
3895
+ project_path: z.string().describe("Path to project directory"),
3896
+ focus_areas: z.array(z.enum([
3897
+ "auth", "crypto", "injection", "logic", "state",
3898
+ "access-control", "data-validation", "session", "api"
3899
+ ])).optional().describe("Focus areas for analysis"),
3900
+ analysis_depth: z.enum(["quick", "standard", "thorough"]).optional().describe("Analysis depth (default: standard)"),
3901
+ max_files: z.number().optional().describe("Max files to analyze (default: 50)"),
3902
+ },
3903
+ annotations: {
3904
+ readOnlyHint: true,
3905
+ destructiveHint: false,
3906
+ idempotentHint: true,
3907
+ openWorldHint: false,
3908
+ },
3909
+ }, async ({ project_path, focus_areas, analysis_depth, max_files }) => {
3910
+ try {
3911
+ const config = {
3912
+ analysisDepth: analysis_depth || "standard",
3913
+ focusAreas: focus_areas || ["auth", "crypto", "injection", "logic"],
3914
+ maxFilesToAnalyze: max_files,
3915
+ };
3916
+ const result = await runZeroDayHunter(project_path, config);
3917
+ return jsonResponse({
3918
+ agent: "zero-day-hunter",
3919
+ filesAnalyzed: result.filesAnalyzed,
3920
+ analysisDepth: result.analysisDepth,
3921
+ focusAreas: result.focusAreas,
3922
+ modelUsed: result.modelUsed,
3923
+ totalFindings: result.findings.length,
3924
+ bySeverity: {
3925
+ critical: result.findings.filter(f => f.severity === "critical").length,
3926
+ high: result.findings.filter(f => f.severity === "high").length,
3927
+ medium: result.findings.filter(f => f.severity === "medium").length,
3928
+ low: result.findings.filter(f => f.severity === "low").length,
3929
+ },
3930
+ recommendations: result.recommendations,
3931
+ findings: result.findings.slice(0, 20).map(f => ({
3932
+ id: f.id,
3933
+ title: f.title,
3934
+ severity: f.severity,
3935
+ confidence: f.confidence,
3936
+ category: f.category,
3937
+ file: f.file,
3938
+ line: f.line,
3939
+ attackScenario: f.attackScenario,
3940
+ cweIds: f.cweIds,
3941
+ })),
3942
+ hasMore: result.findings.length > 20,
3943
+ });
3944
+ }
3945
+ catch (error) {
3946
+ return errorResponse(`Semantic analysis failed: ${error instanceof Error ? error.message : String(error)}`);
3947
+ }
3948
+ });
3949
+ // ---------------------------------------------------------------------------
3950
+ // Tool: Logic Flaw Detector
3951
+ // ---------------------------------------------------------------------------
3952
+ server.registerTool("certification_logic_analysis", {
3953
+ title: "Logic Flaw Detection",
3954
+ description: `Detect business logic vulnerabilities that pattern-based scanners miss.
3955
+
3956
+ Categories:
3957
+ - State inconsistency: Mutable when should be immutable
3958
+ - Race conditions: Check-then-act without locks
3959
+ - Boundary violations: Off-by-one, integer overflow
3960
+ - Error handling: Swallowed exceptions, incomplete cleanup
3961
+ - Trust boundary: Client-supplied data used unsafely
3962
+ - Business logic: Incorrect rule implementations`,
3963
+ inputSchema: {
3964
+ project_path: z.string().describe("Path to project directory"),
3965
+ focus_files: z.array(z.string()).optional().describe("Specific files to analyze"),
3966
+ analysis_depth: z.enum(["quick", "standard", "thorough"]).optional().describe("Analysis depth (default: standard)"),
3967
+ categories: z.array(z.enum([
3968
+ "state-inconsistency", "race-condition", "boundary-violation",
3969
+ "error-handling", "trust-boundary", "business-logic",
3970
+ "null-safety", "resource-leak", "invariant-violation"
3971
+ ])).optional().describe("Categories to focus on"),
3972
+ },
3973
+ annotations: {
3974
+ readOnlyHint: true,
3975
+ destructiveHint: false,
3976
+ idempotentHint: true,
3977
+ openWorldHint: false,
3978
+ },
3979
+ }, async ({ project_path, focus_files, analysis_depth, categories }) => {
3980
+ try {
3981
+ const config = {
3982
+ analysisDepth: analysis_depth || "standard",
3983
+ focusFiles: focus_files,
3984
+ categories: categories,
3985
+ };
3986
+ const result = await runLogicFlawDetector(project_path, config);
3987
+ return jsonResponse({
3988
+ agent: "logic-flaw-detector",
3989
+ filesAnalyzed: result.filesAnalyzed,
3990
+ categoriesDetected: result.categories,
3991
+ totalFindings: result.findings.length,
3992
+ bySeverity: {
3993
+ critical: result.findings.filter(f => f.severity === "critical").length,
3994
+ high: result.findings.filter(f => f.severity === "high").length,
3995
+ medium: result.findings.filter(f => f.severity === "medium").length,
3996
+ low: result.findings.filter(f => f.severity === "low").length,
3997
+ },
3998
+ byCategory: Object.fromEntries(result.categories.map(cat => [cat, result.findings.filter(f => f.category === cat).length])),
3999
+ recommendations: result.recommendations,
4000
+ findings: result.findings.slice(0, 20).map(f => ({
4001
+ id: f.id,
4002
+ title: f.title,
4003
+ severity: f.severity,
4004
+ confidence: f.confidence,
4005
+ category: f.category,
4006
+ file: f.file,
4007
+ line: f.line,
4008
+ impact: f.impact,
4009
+ cweIds: f.cweIds,
4010
+ })),
4011
+ hasMore: result.findings.length > 20,
4012
+ });
4013
+ }
4014
+ catch (error) {
4015
+ return errorResponse(`Logic flaw detection failed: ${error instanceof Error ? error.message : String(error)}`);
4016
+ }
4017
+ });
4018
+ // ---------------------------------------------------------------------------
4019
+ // Tool: Exploit Chain Analyzer
4020
+ // ---------------------------------------------------------------------------
4021
+ server.registerTool("certification_analyze_chains", {
4022
+ title: "Analyze Exploit Chains",
4023
+ description: `Analyze findings for exploitable vulnerability chains.
4024
+
4025
+ Chain types detected:
4026
+ - Information Disclosure → RCE
4027
+ - SSRF → Internal Network Access
4028
+ - XSS → Session Hijacking
4029
+ - Auth Bypass → Data Exfiltration
4030
+ - SQL Injection → Database Takeover
4031
+ - Path Traversal → Secret Exposure
4032
+ - Insecure Deserialization → RCE
4033
+ - Race Condition → Privilege Escalation
4034
+
4035
+ Calculates escalated severity based on chain impact.`,
4036
+ inputSchema: {
4037
+ project_path: z.string().describe("Path to the project being certified"),
4038
+ certification_id: z.string().describe("Certification ID with existing findings"),
4039
+ },
4040
+ annotations: {
4041
+ readOnlyHint: true,
4042
+ destructiveHint: false,
4043
+ idempotentHint: true,
4044
+ openWorldHint: false,
4045
+ },
4046
+ }, async ({ project_path, certification_id }) => {
4047
+ try {
4048
+ // Get certification and all findings
4049
+ const cert = await getCertification(project_path, certification_id);
4050
+ if (!cert) {
4051
+ return errorResponse(`Certification ${certification_id} not found`);
4052
+ }
4053
+ // Collect all findings from all agents
4054
+ const allFindings = Object.values(cert.agents)
4055
+ .filter((a) => a !== undefined)
4056
+ .flatMap(a => a.findings);
4057
+ if (allFindings.length < 2) {
4058
+ return jsonResponse({
4059
+ certificationId: certification_id,
4060
+ totalFindings: allFindings.length,
4061
+ chainsDetected: 0,
4062
+ message: "Need at least 2 findings to detect chains",
4063
+ chains: [],
4064
+ });
4065
+ }
4066
+ // Run chain analysis
4067
+ const result = await analyzeExploitChains(allFindings);
4068
+ return jsonResponse({
4069
+ certificationId: certification_id,
4070
+ totalFindings: result.totalFindings,
4071
+ chainsDetected: result.chainsDetected,
4072
+ riskScore: result.riskScore,
4073
+ summary: getChainSummary(result),
4074
+ recommendations: result.recommendations,
4075
+ chains: result.chains.slice(0, 10).map(chain => ({
4076
+ id: chain.id,
4077
+ name: chain.name,
4078
+ totalSeverity: chain.totalSeverity,
4079
+ originalSeverities: chain.originalSeverities,
4080
+ confidence: chain.confidence,
4081
+ difficulty: chain.difficulty,
4082
+ attackScenario: chain.attackScenario,
4083
+ impact: chain.impact,
4084
+ steps: chain.steps.map(s => ({
4085
+ findingId: s.findingId,
4086
+ role: s.role,
4087
+ category: s.finding.category,
4088
+ enables: s.enables,
4089
+ })),
4090
+ mitreAttackIds: chain.mitreAttackIds,
4091
+ })),
4092
+ hasMore: result.chains.length > 10,
4093
+ });
4094
+ }
4095
+ catch (error) {
4096
+ return errorResponse(`Chain analysis failed: ${error instanceof Error ? error.message : String(error)}`);
4097
+ }
4098
+ });
4099
+ // ---------------------------------------------------------------------------
4100
+ // Exports (for HTTP server and client integration)
4101
+ // ---------------------------------------------------------------------------
4102
+ export { server, textResponse, jsonResponse, errorResponse };
4103
+ // ---------------------------------------------------------------------------
4104
+ // Start (stdio mode - only when run directly)
4105
+ // ---------------------------------------------------------------------------
4106
+ async function main() {
4107
+ logger.info("server.starting", { version: "2.0.0" });
4108
+ const transport = new StdioServerTransport();
4109
+ await server.connect(transport);
4110
+ logger.info("server.started", { transport: "stdio" });
4111
+ }
4112
+ // Only run main() when this file is executed directly (not imported)
4113
+ const isDirectRun = process.argv[1]?.endsWith("index.js") || process.argv[1]?.endsWith("index.ts");
4114
+ if (isDirectRun) {
4115
+ main().catch((error) => {
4116
+ logger.error("server.fatal", { error: error instanceof Error ? error.message : String(error) });
4117
+ process.exit(1);
4118
+ });
4119
+ }
4120
+ //# sourceMappingURL=index.js.map