hacktricks-mcp-server 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts DELETED
@@ -1,952 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
- import {
6
- CallToolRequestSchema,
7
- ListToolsRequestSchema,
8
- } from "@modelcontextprotocol/sdk/types.js";
9
- import { execFile } from "child_process";
10
- import { promisify } from "util";
11
- import { fileURLToPath } from "url";
12
- import { dirname, join } from "path";
13
- import { readFile, readdir } from "fs/promises";
14
-
15
- const execFileAsync = promisify(execFile);
16
-
17
- // Get the directory where this script is running
18
- const __filename = fileURLToPath(import.meta.url);
19
- const __dirname = dirname(__filename);
20
- const HACKTRICKS_PATH = join(__dirname, "..", "hacktricks");
21
-
22
- // ============================================================================
23
- // INTERFACES
24
- // ============================================================================
25
-
26
- interface SearchResult {
27
- file: string;
28
- line: number;
29
- content: string;
30
- }
31
-
32
- interface GroupedSearchResult {
33
- file: string;
34
- title: string;
35
- matchCount: number;
36
- relevantSections: string[];
37
- topMatches: {
38
- line: number;
39
- content: string;
40
- }[];
41
- }
42
-
43
- interface CategoryTree {
44
- name: string;
45
- path: string;
46
- type: "file" | "directory";
47
- children?: CategoryTree[];
48
- }
49
-
50
- // ============================================================================
51
- // HELPER FUNCTIONS
52
- // ============================================================================
53
-
54
- /**
55
- * Extract title (first H1) from markdown content
56
- */
57
- function extractTitle(content: string): string {
58
- const match = content.match(/^#\s+(.+)$/m);
59
- return match ? match[1].trim() : "Untitled";
60
- }
61
-
62
- /**
63
- * Extract all section headers from markdown content
64
- */
65
- function extractHeaders(content: string): { level: number; text: string; line: number }[] {
66
- const headers: { level: number; text: string; line: number }[] = [];
67
- const lines = content.split("\n");
68
-
69
- for (let i = 0; i < lines.length; i++) {
70
- const match = lines[i].match(/^(#{1,6})\s+(.+)$/);
71
- if (match) {
72
- headers.push({
73
- level: match[1].length,
74
- text: match[2].trim(),
75
- line: i + 1,
76
- });
77
- }
78
- }
79
-
80
- return headers;
81
- }
82
-
83
- /**
84
- * Find section headers near a given line number
85
- */
86
- function findNearestSection(headers: { level: number; text: string; line: number }[], targetLine: number): string | null {
87
- let nearestHeader: { level: number; text: string; line: number } | null = null;
88
-
89
- for (const header of headers) {
90
- if (header.line <= targetLine) {
91
- nearestHeader = header;
92
- } else {
93
- break;
94
- }
95
- }
96
-
97
- return nearestHeader ? nearestHeader.text : null;
98
- }
99
-
100
- /**
101
- * Extract a specific section from markdown content
102
- */
103
- function extractSection(content: string, sectionName: string): string | null {
104
- const lines = content.split("\n");
105
- const searchLower = sectionName.toLowerCase();
106
-
107
- let startLine = -1;
108
- let startLevel = 0;
109
-
110
- // Find the section header
111
- for (let i = 0; i < lines.length; i++) {
112
- const match = lines[i].match(/^(#{1,6})\s+(.+)$/);
113
- if (match && match[2].toLowerCase().includes(searchLower)) {
114
- startLine = i;
115
- startLevel = match[1].length;
116
- break;
117
- }
118
- }
119
-
120
- if (startLine === -1) return null;
121
-
122
- // Find the end of the section (next header of same or higher level)
123
- let endLine = lines.length;
124
- for (let i = startLine + 1; i < lines.length; i++) {
125
- const match = lines[i].match(/^(#{1,6})\s+/);
126
- if (match && match[1].length <= startLevel) {
127
- endLine = i;
128
- break;
129
- }
130
- }
131
-
132
- return lines.slice(startLine, endLine).join("\n");
133
- }
134
-
135
- /**
136
- * Extract all code blocks from markdown content
137
- */
138
- function extractCodeBlocks(content: string): { language: string; code: string }[] {
139
- const blocks: { language: string; code: string }[] = [];
140
- const regex = /```(\w*)\n([\s\S]*?)```/g;
141
-
142
- let match;
143
- while ((match = regex.exec(content)) !== null) {
144
- blocks.push({
145
- language: match[1] || "text",
146
- code: match[2].trim(),
147
- });
148
- }
149
-
150
- return blocks;
151
- }
152
-
153
- // ============================================================================
154
- // CORE FUNCTIONS
155
- // ============================================================================
156
-
157
- async function searchHackTricks(
158
- query: string,
159
- category?: string,
160
- limit: number = 50
161
- ): Promise<SearchResult[]> {
162
- try {
163
- if (!query || query.trim().length === 0) {
164
- throw new Error("Search query cannot be empty");
165
- }
166
-
167
- const searchPath = category
168
- ? join(HACKTRICKS_PATH, "src", category)
169
- : HACKTRICKS_PATH;
170
-
171
- console.error(
172
- `[HackTricks MCP] Searching for: "${query}"${category ? ` in category: ${category}` : ""}`
173
- );
174
-
175
- const { stdout } = await execFileAsync(
176
- "rg",
177
- ["-n", "-i", "--type", "md", query, searchPath],
178
- { maxBuffer: 1024 * 1024 * 10 }
179
- );
180
-
181
- const results: SearchResult[] = [];
182
- const lines = stdout.trim().split("\n");
183
-
184
- for (const line of lines) {
185
- const match = line.match(/^([^:]+):(\d+):(.+)$/);
186
- if (match) {
187
- const [, file, lineNum, content] = match;
188
- results.push({
189
- file: file.replace(HACKTRICKS_PATH + "/", ""),
190
- line: parseInt(lineNum, 10),
191
- content: content.trim(),
192
- });
193
- }
194
- }
195
-
196
- const limitedResults = results.slice(0, limit);
197
- console.error(`[HackTricks MCP] Found ${results.length} results (showing ${limitedResults.length})`);
198
- return limitedResults;
199
- } catch (error: any) {
200
- if (error.code === 1) {
201
- console.error(`[HackTricks MCP] No results found for: "${query}"`);
202
- return [];
203
- }
204
- if (error.code === 2) {
205
- console.error(`[HackTricks MCP] Invalid search pattern: ${error.message}`);
206
- throw new Error(`Invalid search pattern: ${error.message}`);
207
- }
208
- console.error(`[HackTricks MCP] Search failed: ${error.message}`);
209
- throw new Error(`Search failed: ${error.message}`);
210
- }
211
- }
212
-
213
- /**
214
- * Group search results by file with context
215
- */
216
- async function searchHackTricksGrouped(
217
- query: string,
218
- category?: string,
219
- limit: number = 50
220
- ): Promise<GroupedSearchResult[]> {
221
- const rawResults = await searchHackTricks(query, category, limit * 3); // Get more raw results for better grouping
222
-
223
- // Group by file
224
- const fileGroups = new Map<string, SearchResult[]>();
225
- for (const result of rawResults) {
226
- const existing = fileGroups.get(result.file) || [];
227
- existing.push(result);
228
- fileGroups.set(result.file, existing);
229
- }
230
-
231
- // Process each file group
232
- const groupedResults: GroupedSearchResult[] = [];
233
-
234
- for (const [file, matches] of fileGroups) {
235
- try {
236
- const filePath = join(HACKTRICKS_PATH, file);
237
- const content = await readFile(filePath, "utf-8");
238
- const headers = extractHeaders(content);
239
- const title = extractTitle(content);
240
-
241
- // Find unique sections that contain matches
242
- const sections = new Set<string>();
243
- for (const match of matches) {
244
- const section = findNearestSection(headers, match.line);
245
- if (section) sections.add(section);
246
- }
247
-
248
- groupedResults.push({
249
- file,
250
- title,
251
- matchCount: matches.length,
252
- relevantSections: Array.from(sections).slice(0, 5),
253
- topMatches: matches.slice(0, 3).map((m) => ({
254
- line: m.line,
255
- content: m.content.length > 150 ? m.content.slice(0, 150) + "..." : m.content,
256
- })),
257
- });
258
- } catch {
259
- // If file reading fails, still include basic info
260
- groupedResults.push({
261
- file,
262
- title: file.split("/").pop()?.replace(".md", "") || "Unknown",
263
- matchCount: matches.length,
264
- relevantSections: [],
265
- topMatches: matches.slice(0, 3).map((m) => ({
266
- line: m.line,
267
- content: m.content.length > 150 ? m.content.slice(0, 150) + "..." : m.content,
268
- })),
269
- });
270
- }
271
- }
272
-
273
- // Sort by match count (most relevant first)
274
- groupedResults.sort((a, b) => b.matchCount - a.matchCount);
275
-
276
- return groupedResults.slice(0, limit);
277
- }
278
-
279
- async function getPage(path: string): Promise<string> {
280
- try {
281
- if (!path || path.trim().length === 0) {
282
- throw new Error("File path cannot be empty");
283
- }
284
-
285
- const normalizedPath = path.replace(/\\/g, "/");
286
- if (normalizedPath.includes("..") || normalizedPath.startsWith("/")) {
287
- throw new Error("Invalid file path: directory traversal not allowed");
288
- }
289
-
290
- const filePath = join(HACKTRICKS_PATH, normalizedPath);
291
-
292
- if (!filePath.startsWith(HACKTRICKS_PATH)) {
293
- throw new Error("Invalid file path: must be within HackTricks directory");
294
- }
295
-
296
- console.error(`[HackTricks MCP] Reading file: ${normalizedPath}`);
297
- const content = await readFile(filePath, "utf-8");
298
- console.error(`[HackTricks MCP] File size: ${content.length} bytes`);
299
- return content;
300
- } catch (error: any) {
301
- if (error.code === "ENOENT") {
302
- console.error(`[HackTricks MCP] File not found: ${path}`);
303
- throw new Error(`File not found: ${path}`);
304
- }
305
- if (error.code === "EISDIR") {
306
- console.error(`[HackTricks MCP] Path is a directory: ${path}`);
307
- throw new Error(`Path is a directory, not a file: ${path}`);
308
- }
309
- console.error(`[HackTricks MCP] Error reading file: ${error.message}`);
310
- throw error;
311
- }
312
- }
313
-
314
- async function getPageOutline(path: string): Promise<string> {
315
- const content = await getPage(path);
316
- const headers = extractHeaders(content);
317
-
318
- if (headers.length === 0) {
319
- return "No headers found in this file.";
320
- }
321
-
322
- // Format headers with indentation based on level
323
- return headers
324
- .map((h) => {
325
- const indent = " ".repeat(h.level - 1);
326
- return `${indent}${"#".repeat(h.level)} ${h.text}`;
327
- })
328
- .join("\n");
329
- }
330
-
331
- async function getPageSection(path: string, sectionName: string): Promise<string> {
332
- const content = await getPage(path);
333
- const section = extractSection(content, sectionName);
334
-
335
- if (!section) {
336
- throw new Error(`Section "${sectionName}" not found in ${path}`);
337
- }
338
-
339
- return section;
340
- }
341
-
342
- async function getPageCheatsheet(path: string): Promise<string> {
343
- const content = await getPage(path);
344
- const blocks = extractCodeBlocks(content);
345
-
346
- if (blocks.length === 0) {
347
- return "No code blocks found in this file.";
348
- }
349
-
350
- return blocks
351
- .map((b) => `\`\`\`${b.language}\n${b.code}\n\`\`\``)
352
- .join("\n\n");
353
- }
354
-
355
- async function listDirectoryTree(
356
- dirPath: string,
357
- basePath: string = HACKTRICKS_PATH,
358
- depth: number = 0,
359
- maxDepth: number = 3
360
- ): Promise<CategoryTree[]> {
361
- if (depth > maxDepth) return [];
362
-
363
- const entries = await readdir(dirPath, { withFileTypes: true });
364
- const tree: CategoryTree[] = [];
365
-
366
- for (const entry of entries) {
367
- if (entry.name.startsWith(".") || entry.name === "images") continue;
368
-
369
- const fullPath = join(dirPath, entry.name);
370
- const relativePath = fullPath.replace(basePath + "/", "");
371
-
372
- if (entry.isDirectory()) {
373
- const children = await listDirectoryTree(
374
- fullPath,
375
- basePath,
376
- depth + 1,
377
- maxDepth
378
- );
379
- tree.push({
380
- name: entry.name,
381
- path: relativePath,
382
- type: "directory",
383
- children: children.length > 0 ? children : undefined,
384
- });
385
- } else if (entry.name.endsWith(".md")) {
386
- tree.push({
387
- name: entry.name,
388
- path: relativePath,
389
- type: "file",
390
- });
391
- }
392
- }
393
-
394
- return tree.sort((a, b) => {
395
- if (a.type !== b.type) {
396
- return a.type === "directory" ? -1 : 1;
397
- }
398
- return a.name.localeCompare(b.name);
399
- });
400
- }
401
-
402
- async function listCategories(category?: string): Promise<string[] | CategoryTree[]> {
403
- try {
404
- const srcPath = join(HACKTRICKS_PATH, "src");
405
-
406
- if (category) {
407
- console.error(`[HackTricks MCP] Listing contents of category: ${category}`);
408
- const categoryPath = join(srcPath, category);
409
- const tree = await listDirectoryTree(categoryPath, HACKTRICKS_PATH);
410
- console.error(`[HackTricks MCP] Found ${tree.length} items in ${category}`);
411
- return tree;
412
- } else {
413
- console.error(`[HackTricks MCP] Listing categories in ${srcPath}`);
414
- const entries = await readdir(srcPath, { withFileTypes: true });
415
-
416
- const categories = entries
417
- .filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "images")
418
- .map((entry) => entry.name)
419
- .sort();
420
-
421
- console.error(`[HackTricks MCP] Found ${categories.length} categories`);
422
- return categories;
423
- }
424
- } catch (error: any) {
425
- console.error(`[HackTricks MCP] Error listing categories: ${error.message}`);
426
- throw new Error(`Failed to list categories: ${error.message}`);
427
- }
428
- }
429
-
430
- // Common abbreviation aliases for better search matching
431
- const SEARCH_ALIASES: Record<string, string[]> = {
432
- "sqli": ["SQL injection", "SQLi"],
433
- "xss": ["Cross-site scripting", "XSS"],
434
- "rce": ["Remote code execution", "RCE", "command injection"],
435
- "lfi": ["Local file inclusion", "LFI"],
436
- "rfi": ["Remote file inclusion", "RFI"],
437
- "ssrf": ["Server-side request forgery", "SSRF"],
438
- "csrf": ["Cross-site request forgery", "CSRF"],
439
- "xxe": ["XML external entity", "XXE"],
440
- "ssti": ["Server-side template injection", "SSTI"],
441
- "idor": ["Insecure direct object reference", "IDOR"],
442
- "jwt": ["JSON Web Token", "JWT"],
443
- "suid": ["SUID", "setuid"],
444
- "privesc": ["privilege escalation", "privesc"],
445
- "deserialization": ["deserialization", "insecure deserialization"],
446
- };
447
-
448
- // Priority sections to extract for quick lookup
449
- const PRIORITY_SECTIONS = [
450
- "exploitation",
451
- "exploit",
452
- "example",
453
- "poc",
454
- "proof of concept",
455
- "payload",
456
- "bypass",
457
- "attack",
458
- "abuse",
459
- "technique",
460
- ];
461
-
462
- /**
463
- * Quick lookup: Search + get best page + extract exploitation-relevant sections
464
- * One-shot answer for "how do I exploit X"
465
- */
466
- async function quickLookup(
467
- topic: string,
468
- category?: string
469
- ): Promise<{ page: string; title: string; sections: string; codeBlocks: string }> {
470
- // Expand aliases
471
- const topicLower = topic.toLowerCase();
472
- let searchTerms = [topic];
473
- if (SEARCH_ALIASES[topicLower]) {
474
- searchTerms = [...searchTerms, ...SEARCH_ALIASES[topicLower]];
475
- }
476
-
477
- console.error(`[HackTricks MCP] Quick lookup: "${topic}" (terms: ${searchTerms.join(", ")})`);
478
-
479
- // Search for the topic
480
- let bestResult: GroupedSearchResult | null = null;
481
- let bestScore = 0;
482
-
483
- for (const term of searchTerms) {
484
- try {
485
- const results = await searchHackTricksGrouped(term, category, 10);
486
- if (results.length > 0) {
487
- for (const result of results) {
488
- // Score based on relevance
489
- let score = result.matchCount;
490
- const titleLower = result.title.toLowerCase();
491
- const termLower = term.toLowerCase();
492
- const pathLower = result.file.toLowerCase();
493
-
494
- // Strong preference for title containing search term
495
- if (titleLower.includes(termLower) || titleLower.includes(topicLower)) {
496
- score += 100;
497
- }
498
-
499
- // Prefer README files (main pages for topic folders)
500
- if (pathLower.endsWith("readme.md") && pathLower.includes(topicLower)) {
501
- score += 200; // This is likely THE main page for the topic
502
- }
503
-
504
- // Prefer folder names matching topic (e.g., ssrf-server-side-request-forgery/)
505
- if (pathLower.includes(`/${topicLower}`) || pathLower.includes(`${topicLower}-`)) {
506
- score += 50;
507
- }
508
-
509
- // Bonus for having exploitation-related sections
510
- const hasExploitSection = result.relevantSections.some((s) =>
511
- PRIORITY_SECTIONS.some((p) => s.toLowerCase().includes(p))
512
- );
513
- if (hasExploitSection) {
514
- score += 10;
515
- }
516
-
517
- if (score > bestScore) {
518
- bestScore = score;
519
- bestResult = result;
520
- }
521
- }
522
- }
523
- } catch {
524
- // Continue with other terms
525
- }
526
- }
527
-
528
- if (!bestResult) {
529
- throw new Error(`No results found for: "${topic}". Try a different term or specify a category.`);
530
- }
531
-
532
- console.error(`[HackTricks MCP] Best match: ${bestResult.file} (${bestResult.matchCount} matches)`);
533
-
534
- // Read the page content
535
- const content = await getPage(bestResult.file);
536
- const title = extractTitle(content);
537
- const headers = extractHeaders(content);
538
-
539
- // Extract priority sections
540
- const extractedSections: string[] = [];
541
- for (const header of headers) {
542
- const headerLower = header.text.toLowerCase();
543
- if (PRIORITY_SECTIONS.some((p) => headerLower.includes(p))) {
544
- try {
545
- const section = extractSection(content, header.text);
546
- if (section && section.length > 50) {
547
- extractedSections.push(section);
548
- }
549
- } catch {
550
- // Section extraction failed, skip
551
- }
552
- }
553
- }
554
-
555
- // If no priority sections found, get the first substantial section after title
556
- if (extractedSections.length === 0 && headers.length > 1) {
557
- try {
558
- const section = extractSection(content, headers[1].text);
559
- if (section) {
560
- extractedSections.push(section);
561
- }
562
- } catch {
563
- // Fallback failed
564
- }
565
- }
566
-
567
- // Extract code blocks
568
- const codeBlocks = extractCodeBlocks(content);
569
- const codeOutput = codeBlocks.length > 0
570
- ? codeBlocks.slice(0, 5).map((b) => `\`\`\`${b.language}\n${b.code}\n\`\`\``).join("\n\n")
571
- : "No code blocks found.";
572
-
573
- return {
574
- page: bestResult.file,
575
- title,
576
- sections: extractedSections.length > 0
577
- ? extractedSections.join("\n\n---\n\n")
578
- : "No exploitation sections found. Use get_hacktricks_page for full content.",
579
- codeBlocks: codeOutput,
580
- };
581
- }
582
-
583
- // ============================================================================
584
- // MCP SERVER SETUP
585
- // ============================================================================
586
-
587
- const server = new Server(
588
- {
589
- name: "hacktricks-mcp",
590
- version: "1.3.0",
591
- },
592
- {
593
- capabilities: {
594
- tools: {},
595
- },
596
- }
597
- );
598
-
599
- // List available tools
600
- server.setRequestHandler(ListToolsRequestSchema, async () => {
601
- return {
602
- tools: [
603
- {
604
- name: "search_hacktricks",
605
- description:
606
- "Search HackTricks for pentesting techniques, exploits, and security info. Returns results GROUPED BY FILE with: page title, match count, relevant sections, and top matches. WORKFLOW: search → get_hacktricks_outline (see structure) → get_hacktricks_section (read specific part). ALWAYS use category filter when possible - saves time and tokens.",
607
- inputSchema: {
608
- type: "object",
609
- properties: {
610
- query: {
611
- type: "string",
612
- description: "Search term. Be specific (e.g., 'SUID privilege escalation' not just 'privilege'). Supports regex.",
613
- },
614
- category: {
615
- type: "string",
616
- description: "STRONGLY RECOMMENDED. Common categories: 'pentesting-web' (XSS,SQLi,SSRF), 'linux-hardening' (privesc,capabilities), 'network-services-pentesting' (SMB,FTP,SSH), 'windows-hardening', 'mobile-pentesting', 'cloud-security'. Use list_hacktricks_categories to see all.",
617
- },
618
- limit: {
619
- type: "number",
620
- description: "Max files to return (default: 20). Lower = faster. Set to 5 for quick lookups.",
621
- default: 20,
622
- },
623
- },
624
- required: ["query"],
625
- },
626
- },
627
- {
628
- name: "get_hacktricks_page",
629
- description:
630
- "Get FULL page content. āš ļø EXPENSIVE: Pages average 3000-15000 tokens. PREFER: get_hacktricks_section for specific topics, get_hacktricks_cheatsheet for just commands. Only use this when you need the complete page or multiple sections.",
631
- inputSchema: {
632
- type: "object",
633
- properties: {
634
- path: {
635
- type: "string",
636
- description: "Path from search results (e.g., 'src/linux-hardening/privilege-escalation/README.md')",
637
- },
638
- },
639
- required: ["path"],
640
- },
641
- },
642
- {
643
- name: "get_hacktricks_outline",
644
- description:
645
- "Get TABLE OF CONTENTS (all section headers) of a page. Returns ~20-50 lines showing page structure. Use this FIRST after search to: (1) verify page is relevant, (2) find exact section names for get_hacktricks_section. Cost: ~100 tokens vs 3000+ for full page.",
646
- inputSchema: {
647
- type: "object",
648
- properties: {
649
- path: {
650
- type: "string",
651
- description: "Path from search results",
652
- },
653
- },
654
- required: ["path"],
655
- },
656
- },
657
- {
658
- name: "get_hacktricks_section",
659
- description:
660
- "Extract ONE SECTION from a page. MOST EFFICIENT way to read content. Typical sections: 'Exploitation', 'Enumeration', 'Prevention', 'Example', 'Payload', 'PoC', 'Bypass'. Use get_hacktricks_outline first to see exact section names. Returns ~200-500 tokens vs 3000+ for full page.",
661
- inputSchema: {
662
- type: "object",
663
- properties: {
664
- path: {
665
- type: "string",
666
- description: "Path from search results",
667
- },
668
- section: {
669
- type: "string",
670
- description: "Section name (partial match, case-insensitive). From outline or common: 'exploitation', 'enumeration', 'bypass', 'payload', 'example', 'poc', 'prevention', 'detection'",
671
- },
672
- },
673
- required: ["path", "section"],
674
- },
675
- },
676
- {
677
- name: "get_hacktricks_cheatsheet",
678
- description:
679
- "Extract ALL CODE BLOCKS from a page (commands, payloads, scripts, one-liners). Skips explanatory text. Perfect for: 'give me the exploit command', 'show me the payload', 'what's the syntax'. Returns code with language tags (bash, python, etc.).",
680
- inputSchema: {
681
- type: "object",
682
- properties: {
683
- path: {
684
- type: "string",
685
- description: "Path from search results",
686
- },
687
- },
688
- required: ["path"],
689
- },
690
- },
691
- {
692
- name: "list_hacktricks_categories",
693
- description:
694
- "Browse HackTricks structure. Without params: list all categories. With category: show all pages in that category. Use when: (1) unsure which category to search, (2) want to explore what's available, (3) need exact file paths.",
695
- inputSchema: {
696
- type: "object",
697
- properties: {
698
- category: {
699
- type: "string",
700
- description: "Category to explore. Popular: 'pentesting-web', 'linux-hardening', 'windows-hardening', 'network-services-pentesting', 'mobile-pentesting'",
701
- },
702
- },
703
- },
704
- },
705
- {
706
- name: "hacktricks_quick_lookup",
707
- description:
708
- "⚔ ONE-SHOT exploitation lookup. Searches, finds best page, and returns exploitation sections + code blocks. Use for: 'how do I exploit X', 'give me X payload', 'X attack technique'. Handles aliases (sqli→SQL injection, xss→Cross-site scripting, rce, lfi, ssrf, etc.). Returns: page title, exploitation sections, and top 5 code blocks.",
709
- inputSchema: {
710
- type: "object",
711
- properties: {
712
- topic: {
713
- type: "string",
714
- description: "Attack/technique to look up. Examples: 'SUID', 'sqli', 'xss', 'ssrf', 'jwt', 'docker escape', 'kerberoasting'. Aliases auto-expand.",
715
- },
716
- category: {
717
- type: "string",
718
- description: "Optional category filter. Speeds up search. Common: 'pentesting-web', 'linux-hardening', 'windows-hardening', 'network-services-pentesting'",
719
- },
720
- },
721
- required: ["topic"],
722
- },
723
- },
724
- ],
725
- };
726
- });
727
-
728
- // Handle tool calls
729
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
730
- const { name, arguments: args } = request.params;
731
-
732
- if (!args) {
733
- throw new Error("Arguments are required");
734
- }
735
-
736
- if (name === "search_hacktricks") {
737
- const query = args.query as string;
738
- const category = args.category as string | undefined;
739
- const limit = Math.min((args.limit as number) || 20, 50);
740
-
741
- if (!query) {
742
- throw new Error("Query parameter is required");
743
- }
744
-
745
- const results = await searchHackTricksGrouped(query, category, limit);
746
-
747
- if (results.length === 0) {
748
- return {
749
- content: [
750
- {
751
- type: "text",
752
- text: `No results found for: "${query}"${category ? ` in category: ${category}` : ""}\n\nTip: Try broader terms or different category.`,
753
- },
754
- ],
755
- };
756
- }
757
-
758
- // Format grouped results
759
- let output = `Found matches in ${results.length} files for: "${query}"${category ? ` in category: ${category}` : ""}\n`;
760
- output += `\n${"─".repeat(60)}\n`;
761
-
762
- for (const result of results) {
763
- output += `\nšŸ“„ **${result.title}**\n`;
764
- output += ` Path: ${result.file}\n`;
765
- output += ` Matches: ${result.matchCount}\n`;
766
-
767
- if (result.relevantSections.length > 0) {
768
- output += ` Sections: ${result.relevantSections.join(" | ")}\n`;
769
- }
770
-
771
- output += ` Preview:\n`;
772
- for (const match of result.topMatches) {
773
- output += ` L${match.line}: ${match.content}\n`;
774
- }
775
- output += `\n${"─".repeat(60)}\n`;
776
- }
777
-
778
- output += `\nšŸ’” Tips:\n`;
779
- output += `• Use get_hacktricks_outline(path) to see page structure\n`;
780
- output += `• Use get_hacktricks_section(path, section) to read specific sections\n`;
781
- output += `• Use get_hacktricks_cheatsheet(path) to get just the code/commands`;
782
-
783
- return {
784
- content: [
785
- {
786
- type: "text",
787
- text: output,
788
- },
789
- ],
790
- };
791
- }
792
-
793
- if (name === "get_hacktricks_page") {
794
- const path = args.path as string;
795
- if (!path) {
796
- throw new Error("Path parameter is required");
797
- }
798
-
799
- const content = await getPage(path);
800
-
801
- return {
802
- content: [
803
- {
804
- type: "text",
805
- text: content,
806
- },
807
- ],
808
- };
809
- }
810
-
811
- if (name === "get_hacktricks_outline") {
812
- const path = args.path as string;
813
- if (!path) {
814
- throw new Error("Path parameter is required");
815
- }
816
-
817
- const outline = await getPageOutline(path);
818
-
819
- return {
820
- content: [
821
- {
822
- type: "text",
823
- text: `Outline of ${path}:\n\n${outline}\n\nšŸ’” Use get_hacktricks_section(path, "section name") to read a specific section.`,
824
- },
825
- ],
826
- };
827
- }
828
-
829
- if (name === "get_hacktricks_section") {
830
- const path = args.path as string;
831
- const section = args.section as string;
832
-
833
- if (!path) {
834
- throw new Error("Path parameter is required");
835
- }
836
- if (!section) {
837
- throw new Error("Section parameter is required");
838
- }
839
-
840
- const sectionContent = await getPageSection(path, section);
841
-
842
- return {
843
- content: [
844
- {
845
- type: "text",
846
- text: sectionContent,
847
- },
848
- ],
849
- };
850
- }
851
-
852
- if (name === "get_hacktricks_cheatsheet") {
853
- const path = args.path as string;
854
- if (!path) {
855
- throw new Error("Path parameter is required");
856
- }
857
-
858
- const cheatsheet = await getPageCheatsheet(path);
859
-
860
- return {
861
- content: [
862
- {
863
- type: "text",
864
- text: `Code blocks from ${path}:\n\n${cheatsheet}`,
865
- },
866
- ],
867
- };
868
- }
869
-
870
- if (name === "list_hacktricks_categories") {
871
- const category = args.category as string | undefined;
872
- const result = await listCategories(category);
873
-
874
- let output: string;
875
- if (category) {
876
- const formatTree = (items: CategoryTree[], indent = ""): string => {
877
- return items
878
- .map((item) => {
879
- const icon = item.type === "directory" ? "šŸ“" : "šŸ“„";
880
- let line = `${indent}${icon} ${item.name}`;
881
- if (item.type === "file") {
882
- line += ` → ${item.path}`;
883
- }
884
- if (item.children && item.children.length > 0) {
885
- line += "\n" + formatTree(item.children, indent + " ");
886
- }
887
- return line;
888
- })
889
- .join("\n");
890
- };
891
-
892
- output = `Contents of category: ${category}\n\n${formatTree(result as CategoryTree[])}`;
893
- } else {
894
- const categories = result as string[];
895
- output = `Available HackTricks Categories (${categories.length}):\n\n${categories.map((cat) => `- ${cat}`).join("\n")}\n\nTip: Use category parameter to see contents (e.g., category="pentesting-web")`;
896
- }
897
-
898
- return {
899
- content: [
900
- {
901
- type: "text",
902
- text: output,
903
- },
904
- ],
905
- };
906
- }
907
-
908
- if (name === "hacktricks_quick_lookup") {
909
- const topic = args.topic as string;
910
- const category = args.category as string | undefined;
911
-
912
- if (!topic) {
913
- throw new Error("Topic parameter is required");
914
- }
915
-
916
- const result = await quickLookup(topic, category);
917
-
918
- let output = `⚔ Quick Lookup: ${result.title}\n`;
919
- output += `šŸ“„ Page: ${result.page}\n`;
920
- output += `\n${"═".repeat(60)}\n`;
921
- output += `\n## Exploitation Info\n\n`;
922
- output += result.sections;
923
- output += `\n\n${"═".repeat(60)}\n`;
924
- output += `\n## Code/Payloads\n\n`;
925
- output += result.codeBlocks;
926
- output += `\n\n${"─".repeat(60)}\n`;
927
- output += `šŸ’” Need more? Use get_hacktricks_page("${result.page}") for full content.`;
928
-
929
- return {
930
- content: [
931
- {
932
- type: "text",
933
- text: output,
934
- },
935
- ],
936
- };
937
- }
938
-
939
- throw new Error(`Unknown tool: ${name}`);
940
- });
941
-
942
- // Start server
943
- async function main() {
944
- const transport = new StdioServerTransport();
945
- await server.connect(transport);
946
- console.error("HackTricks MCP Server v1.3.0 running on stdio");
947
- }
948
-
949
- main().catch((error) => {
950
- console.error("Server error:", error);
951
- process.exit(1);
952
- });