├── test ├── dump.pcapng └── integration.test.js ├── .gitignore ├── sharkmcp-configs.json ├── tsconfig.json ├── LICENSE ├── package.json ├── src ├── types.ts ├── index.ts ├── tools │ ├── analyze-pcap-file.ts │ ├── stop-capture-session.ts │ ├── start-capture-session.ts │ └── manage-config.ts └── utils.ts ├── README.md └── pnpm-lock.yaml /test/dump.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliperis/SharkMCP/HEAD/test/dump.pcapng -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | npm-debug.log* 4 | pnpm-debug.log* 5 | 6 | # Build outputs 7 | dist/ 8 | build/ 9 | *.tsbuildinfo 10 | 11 | *.log 12 | *.pid 13 | *.seed 14 | *.lock 15 | .DS_Store 16 | Thumbs.db 17 | 18 | # OS generated files 19 | .DS_Store 20 | .DS_Store? 21 | ._* 22 | .Spotlight-V100 23 | .Trashes 24 | ehthumbs.db 25 | 26 | /captures/ 27 | sslkeylog.log 28 | 29 | # sharkmcp-configs.json -------------------------------------------------------------------------------- /sharkmcp-configs.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "configs": { 4 | "tls_analysis": { 5 | "name": "tls_analysis", 6 | "description": "TLS traffic analysis with handshake details", 7 | "captureFilter": "port 443", 8 | "displayFilter": "tls.handshake", 9 | "outputFormat": "json", 10 | "maxPackets": 500, 11 | "interface": "en0" 12 | }, 13 | "integration_test": { 14 | "name": "integration_test", 15 | "description": "Short capture for integration testing", 16 | "captureFilter": "", 17 | "displayFilter": "", 18 | "outputFormat": "json", 19 | "maxPackets": 100, 20 | "timeout": 10, 21 | "interface": "en0" 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "declaration": true, 14 | "declarationMap": true, 15 | "sourceMap": true, 16 | "removeComments": false, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "exactOptionalPropertyTypes": true, 20 | "noImplicitReturns": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "noUncheckedIndexedAccess": true, 23 | "noImplicitOverride": true, 24 | "allowUnusedLabels": false, 25 | "allowUnreachableCode": false, 26 | "resolveJsonModule": true, 27 | "isolatedModules": true, 28 | "verbatimModuleSyntax": false 29 | }, 30 | "include": [ 31 | "src/**/*" 32 | ], 33 | "exclude": [ 34 | "node_modules", 35 | "dist", 36 | "test" 37 | ] 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 FS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SharkMCP", 3 | "version": "0.1.0", 4 | "description": "A Wireshark MCP server for network packet analysis", 5 | "main": "src/index.ts", 6 | "type": "module", 7 | "scripts": { 8 | "build": "tsc", 9 | "dev": "node --loader ts-node/esm src/index.ts", 10 | "start": "node dist/index.js", 11 | "test": "pnpm run test:integration", 12 | "test:unit": "echo 'Unit tests not yet implemented'", 13 | "test:integration": "pnpm run build && node test/integration.test.js", 14 | "test:direct": "pnpm run build && node test-client.js" 15 | }, 16 | "keywords": [ 17 | "sharkmcp", 18 | "wireshark", 19 | "mcp", 20 | "network", 21 | "security", 22 | "packet-analysis", 23 | "tshark" 24 | ], 25 | "author": "", 26 | "license": "ISC", 27 | "packageManager": "pnpm@10.11.1", 28 | "dependencies": { 29 | "@modelcontextprotocol/sdk": "^1.12.1", 30 | "@types/node": "24.0.0", 31 | "axios": "1.9.0", 32 | "which": "5.0.0", 33 | "zod": "^3.25.61" 34 | }, 35 | "devDependencies": { 36 | "@types/which": "^3.0.4", 37 | "ts-node": "^10.9.2", 38 | "tsx": "^4.20.1", 39 | "typescript": "^5.8.3" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { ChildProcess } from "child_process"; 2 | 3 | /** 4 | * Represents an active packet capture session 5 | */ 6 | export interface CaptureSession { 7 | id: string; 8 | process: ChildProcess | null; 9 | interface: string; 10 | captureFilter?: string; 11 | timeout: number; 12 | maxPackets: number; 13 | startTime: Date; 14 | tempFile: string; 15 | status: 'running' | 'completed' | 'error'; 16 | endTime?: Date; 17 | exitCode?: number; 18 | } 19 | 20 | /** 21 | * Output format options for packet analysis 22 | */ 23 | export type OutputFormat = 'json' | 'fields' | 'text'; 24 | 25 | /** 26 | * Environment configuration for tshark processes 27 | */ 28 | export interface TsharkEnvironment { 29 | [key: string]: string; 30 | } 31 | 32 | /** 33 | * Configuration for PCAP analysis 34 | */ 35 | export interface AnalysisConfig { 36 | filePath: string; 37 | displayFilter: string; 38 | outputFormat: OutputFormat; 39 | customFields?: string; 40 | sslKeylogFile?: string; 41 | } 42 | 43 | /** 44 | * Reusable filter configuration that LLMs can save and reuse 45 | */ 46 | export interface FilterConfig { 47 | name: string; 48 | description?: string; 49 | captureFilter?: string; 50 | displayFilter?: string; 51 | outputFormat?: OutputFormat; 52 | customFields?: string; 53 | timeout?: number; 54 | maxPackets?: number; 55 | interface?: string; 56 | } 57 | 58 | /** 59 | * Config file structure 60 | */ 61 | export interface ConfigFile { 62 | version: string; 63 | configs: { [name: string]: FilterConfig }; 64 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 3 | import { CaptureSession } from "./types.js"; 4 | import { startCaptureSessionSchema, startCaptureSessionHandler } from "./tools/start-capture-session.js"; 5 | import { stopCaptureSessionSchema, stopCaptureSessionHandler } from "./tools/stop-capture-session.js"; 6 | import { analyzePcapFileSchema, analyzePcapFileHandler } from "./tools/analyze-pcap-file.js"; 7 | import { manageConfigSchema, manageConfigHandler } from "./tools/manage-config.js"; 8 | 9 | // Active capture sessions storage 10 | const activeSessions = new Map(); 11 | 12 | // Initialize MCP server 13 | const server = new McpServer({ 14 | name: 'SharkMCP', 15 | version: '0.1.0', 16 | }); 17 | 18 | /** 19 | * Register all tools with the MCP server 20 | * Each tool is defined in its own module for better organization 21 | */ 22 | 23 | // Tool 1: Start background packet capture session 24 | server.tool( 25 | 'start_capture_session', 26 | 'Start a background packet capture session. LLMs control all capture parameters including filters, interfaces, and packet limits. Can use saved configurations.', 27 | startCaptureSessionSchema, 28 | async (args) => startCaptureSessionHandler(args, activeSessions) 29 | ); 30 | 31 | // Tool 2: Stop capture session and retrieve results 32 | server.tool( 33 | 'stop_capture_session', 34 | 'Stop a running capture session and analyze packets. LLMs control all analysis parameters including display filters and output formats. Can use saved configurations.', 35 | stopCaptureSessionSchema, 36 | async (args) => stopCaptureSessionHandler(args, activeSessions) 37 | ); 38 | 39 | // Tool 3: Analyze an existing PCAP file 40 | server.tool( 41 | 'analyze_pcap_file', 42 | 'Analyze a local pcap/pcapng file. LLMs control all analysis parameters including filters, output formats, and custom fields. Can use saved configurations.', 43 | analyzePcapFileSchema, 44 | async (args) => analyzePcapFileHandler(args) 45 | ); 46 | 47 | // Tool 4: Manage filter configurations 48 | server.tool( 49 | 'manage_config', 50 | 'Save, load, list, or delete reusable filter configurations. Allows LLMs to store commonly used capture and analysis parameters for easy reuse.', 51 | manageConfigSchema, 52 | async (args) => manageConfigHandler(args) 53 | ); 54 | 55 | // Start receiving messages on stdin and sending messages on stdout 56 | const transport = new StdioServerTransport(); 57 | await server.connect(transport); 58 | 59 | console.error("SharkMCP server is running and connected to transport. Ready for requests."); -------------------------------------------------------------------------------- /src/tools/analyze-pcap-file.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import fs from "fs/promises"; 3 | import { analyzePcap, trimOutput, loadFilterConfig } from "../utils.js"; 4 | 5 | /** 6 | * Input schema for analyze pcap file tool 7 | */ 8 | export const analyzePcapFileSchema = { 9 | filePath: z.string().describe('Path to the local .pcap or .pcapng file to analyze.'), 10 | displayFilter: z.string().optional().describe('Wireshark display filter for analysis (e.g., "tls.handshake.type == 1")'), 11 | outputFormat: z.enum(['json', 'fields', 'text']).optional().default('text').describe('Output format: json (-T json), fields (custom -e), or text (default wireshark output)'), 12 | customFields: z.string().optional().describe('Custom tshark field list (only used with outputFormat=fields)'), 13 | sslKeylogFile: z.string().optional().describe('ABSOLUTE path to SSL keylog file for TLS decryption'), 14 | configName: z.string().optional().describe('Name of saved configuration to use for analysis parameters') 15 | }; 16 | 17 | /** 18 | * Tool handler for analyzing an existing PCAP file 19 | * This tool analyzes pre-existing PCAP/PCAPNG files without needing to capture 20 | */ 21 | export async function analyzePcapFileHandler(args: any) { 22 | try { 23 | let { filePath, displayFilter, outputFormat, customFields, sslKeylogFile, configName } = args; 24 | 25 | // If configName is provided, load and use that configuration for analysis 26 | if (configName) { 27 | const savedConfig = await loadFilterConfig(configName); 28 | if (!savedConfig) { 29 | return { 30 | content: [{ 31 | type: 'text' as const, 32 | text: `Error: Configuration '${configName}' not found. Use manage_config with action 'list' to see available configurations.`, 33 | }], 34 | isError: true 35 | }; 36 | } 37 | 38 | // Override analysis parameters with saved config (saved config takes precedence) 39 | if (savedConfig.displayFilter) displayFilter = savedConfig.displayFilter; 40 | if (savedConfig.outputFormat) outputFormat = savedConfig.outputFormat; 41 | if (savedConfig.customFields) customFields = savedConfig.customFields; 42 | 43 | console.error(`Using saved configuration '${configName}' for analysis: ${JSON.stringify(savedConfig)}`); 44 | } 45 | 46 | // Verify file exists before proceeding 47 | await fs.access(filePath); 48 | 49 | // Analyze the file using the reusable function 50 | const output = await analyzePcap( 51 | filePath, 52 | displayFilter, 53 | outputFormat, 54 | customFields, 55 | sslKeylogFile 56 | ); 57 | 58 | const keylogToUse = sslKeylogFile || process.env.SSLKEYLOGFILE; 59 | 60 | // Trim output if too large 61 | const trimmedOutput = trimOutput(output, outputFormat); 62 | 63 | const configInfo = configName ? `\nUsing saved config: ${configName}` : ''; 64 | 65 | return { 66 | content: [{ 67 | type: 'text' as const, 68 | text: `Analysis of '${filePath}' complete!${configInfo}\nDisplay Filter: ${displayFilter || 'none'}\nOutput Format: ${outputFormat}\nSSL Decryption: ${keylogToUse ? 'Enabled' : 'Disabled'}\n\nPacket Analysis Results:\n${trimmedOutput}`, 69 | }], 70 | }; 71 | } catch (error: any) { 72 | console.error(`Error analyzing PCAP file: ${error.message}`); 73 | return { 74 | content: [{ type: 'text' as const, text: `Error: ${error.message}` }], 75 | isError: true 76 | }; 77 | } 78 | } -------------------------------------------------------------------------------- /src/tools/stop-capture-session.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import fs from "fs/promises"; 3 | import { CaptureSession } from "../types.js"; 4 | import { analyzePcap, trimOutput, loadFilterConfig } from "../utils.js"; 5 | 6 | /** 7 | * Input schema for stop capture session tool 8 | */ 9 | export const stopCaptureSessionSchema = { 10 | sessionId: z.string().describe('Session ID returned from start_capture_session'), 11 | displayFilter: z.string().optional().describe('Wireshark display filter for analysis (e.g., "tls.handshake.type == 1")'), 12 | outputFormat: z.enum(['json', 'fields', 'text']).optional().default('text').describe('Output format: json (-T json), fields (custom -e), or text (default wireshark output)'), 13 | customFields: z.string().optional().describe('Custom tshark field list (only used with outputFormat=fields)'), 14 | sslKeylogFile: z.string().optional().describe('ABSOLUTE path to SSL keylog file for TLS decryption'), 15 | configName: z.string().optional().describe('Name of saved configuration to use for analysis parameters') 16 | }; 17 | 18 | /** 19 | * Tool handler for stopping capture session and retrieving results 20 | * This tool stops a running capture session and analyzes the captured packets 21 | */ 22 | export async function stopCaptureSessionHandler(args: any, activeSessions: Map) { 23 | try { 24 | let { sessionId, displayFilter, outputFormat, customFields, sslKeylogFile, configName } = args; 25 | const session = activeSessions.get(sessionId); 26 | 27 | if (!session) { 28 | return { 29 | content: [{ 30 | type: 'text' as const, 31 | text: `Error: No active session found with ID '${sessionId}'. Use 'list_capture_sessions' to see active sessions.`, 32 | }], 33 | isError: true 34 | }; 35 | } 36 | 37 | // If configName is provided, load and use that configuration for analysis 38 | if (configName) { 39 | const savedConfig = await loadFilterConfig(configName); 40 | if (!savedConfig) { 41 | return { 42 | content: [{ 43 | type: 'text' as const, 44 | text: `Error: Configuration '${configName}' not found. Use manage_config with action 'list' to see available configurations.`, 45 | }], 46 | isError: true 47 | }; 48 | } 49 | 50 | // Override analysis parameters with saved config (saved config takes precedence) 51 | if (savedConfig.displayFilter) displayFilter = savedConfig.displayFilter; 52 | if (savedConfig.outputFormat) outputFormat = savedConfig.outputFormat; 53 | if (savedConfig.customFields) customFields = savedConfig.customFields; 54 | 55 | console.error(`Using saved configuration '${configName}' for analysis: ${JSON.stringify(savedConfig)}`); 56 | } 57 | 58 | console.error(`Stopping capture session: ${sessionId}`); 59 | 60 | // Check if the capture process has already completed naturally 61 | if (session.process && !session.process.killed && session.status === 'running') { 62 | console.error(`Terminating capture process for session ${sessionId}`); 63 | session.process.kill('SIGTERM'); 64 | // Wait a moment for graceful termination 65 | await new Promise(resolve => setTimeout(resolve, 2000)); 66 | } else if (session.status === 'completed') { 67 | console.error(`Capture session ${sessionId} already completed naturally`); 68 | } else { 69 | console.error(`Capture session ${sessionId} process already terminated`); 70 | } 71 | 72 | // Remove from active sessions 73 | activeSessions.delete(sessionId); 74 | 75 | try { 76 | // Check if file exists 77 | await fs.access(session.tempFile); 78 | 79 | // Wait a bit more to ensure file is fully written 80 | await new Promise(resolve => setTimeout(resolve, 1000)); 81 | 82 | // Analyze captured file using the reusable function 83 | const output = await analyzePcap( 84 | session.tempFile, 85 | displayFilter, 86 | outputFormat, 87 | customFields, 88 | sslKeylogFile 89 | ); 90 | 91 | const keylogToUse = sslKeylogFile || process.env.SSLKEYLOGFILE; 92 | 93 | // Clean up temporary file 94 | await fs.unlink(session.tempFile).catch(err => 95 | console.error(`Failed to delete ${session.tempFile}: ${err.message}`) 96 | ); 97 | 98 | const duration = new Date().getTime() - session.startTime.getTime(); 99 | const durationSec = (duration / 1000).toFixed(1); 100 | 101 | // Trim output if too large 102 | const trimmedOutput = trimOutput(output, outputFormat); 103 | 104 | const configInfo = configName ? `\nUsing saved config: ${configName}` : ''; 105 | 106 | return { 107 | content: [{ 108 | type: 'text' as const, 109 | text: `Capture session '${sessionId}' completed!${configInfo}\nInterface: ${session.interface}\nDuration: ${durationSec}s\nDisplay Filter: ${displayFilter || 'none'}\nOutput Format: ${outputFormat}\nSSL Decryption: ${keylogToUse ? 'Enabled' : 'Disabled'}\n\nPacket Analysis Results:\n${trimmedOutput}`, 110 | }], 111 | }; 112 | 113 | } catch (fileError: any) { 114 | console.error(`Error analyzing session ${sessionId}: ${fileError.message}`); 115 | return { 116 | content: [{ 117 | type: 'text' as const, 118 | text: `Error analyzing session '${sessionId}': Capture file not found or unreadable. This could mean no packets were captured.\nDetails: ${fileError.message}`, 119 | }], 120 | isError: true, 121 | }; 122 | } 123 | } catch (error: any) { 124 | console.error(`Error stopping capture session: ${error.message}`); 125 | return { 126 | content: [{ type: 'text' as const, text: `Error: ${error.message}` }], 127 | isError: true 128 | }; 129 | } 130 | } -------------------------------------------------------------------------------- /src/tools/start-capture-session.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { spawn } from "child_process"; 3 | import { CaptureSession } from "../types.js"; 4 | import { findTshark, loadFilterConfig } from "../utils.js"; 5 | 6 | /** 7 | * Input schema for start capture session tool 8 | */ 9 | export const startCaptureSessionSchema = { 10 | interface: z.string().optional().default('lo0').describe('Network interface to capture from (e.g., eth0, en0, lo0)'), 11 | captureFilter: z.string().optional().describe('Optional BPF capture filter to apply while capturing (e.g., "port 443")'), 12 | timeout: z.number().optional().default(60).describe('Timeout in seconds before auto-stopping capture (default: 60s to prevent orphaned sessions)'), 13 | maxPackets: z.number().optional().default(100000).describe('Maximum number of packets to capture (safety limit, default: 100,000)'), 14 | sessionName: z.string().optional().describe('Optional session name for easier identification'), 15 | configName: z.string().optional().describe('Name of saved configuration to use (will override other parameters)') 16 | }; 17 | 18 | /** 19 | * Tool handler for starting background packet capture session 20 | * This tool starts a detached tshark process to capture network packets 21 | */ 22 | export async function startCaptureSessionHandler(args: any, activeSessions: Map) { 23 | try { 24 | const tsharkPath = await findTshark(); 25 | let { interface: networkInterface, captureFilter, timeout, maxPackets, sessionName, configName } = args; 26 | 27 | // If configName is provided, load and use that configuration 28 | if (configName) { 29 | const savedConfig = await loadFilterConfig(configName); 30 | if (!savedConfig) { 31 | return { 32 | content: [{ 33 | type: 'text' as const, 34 | text: `Error: Configuration '${configName}' not found. Use manage_config with action 'list' to see available configurations.`, 35 | }], 36 | isError: true 37 | }; 38 | } 39 | 40 | // Override parameters with saved config (saved config takes precedence) 41 | if (savedConfig.interface) networkInterface = savedConfig.interface; 42 | if (savedConfig.captureFilter) captureFilter = savedConfig.captureFilter; 43 | if (savedConfig.timeout) timeout = savedConfig.timeout; 44 | if (savedConfig.maxPackets) maxPackets = savedConfig.maxPackets; 45 | 46 | console.error(`Using saved configuration '${configName}': ${JSON.stringify(savedConfig)}`); 47 | } 48 | 49 | // Generate unique session ID 50 | const sessionId = sessionName || `capture_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; 51 | 52 | // Check if session already exists 53 | if (activeSessions.has(sessionId)) { 54 | return { 55 | content: [{ 56 | type: 'text' as const, 57 | text: `Error: Session '${sessionId}' already exists. Use a different session name or stop the existing session.`, 58 | }], 59 | isError: true 60 | }; 61 | } 62 | 63 | const tempFile = `/tmp/shark_${sessionId}.pcap`; 64 | console.error(`Starting capture session: ${sessionId} on ${networkInterface}`); 65 | 66 | // Build comprehensive tshark command for background capture 67 | // Use timeout as primary stopping mechanism, with maxPackets as safety limit 68 | const args_array = [ 69 | '-i', networkInterface, 70 | '-a', `duration:${timeout}`, // Auto-stop after timeout seconds 71 | '-c', maxPackets.toString(), // Safety limit to prevent excessive capture 72 | '-w', tempFile 73 | ]; 74 | 75 | // Add capture filter if provided (as a single argument to -f) 76 | if (captureFilter) { 77 | args_array.push('-f', captureFilter); 78 | } 79 | 80 | // Set up basic environment 81 | const captureEnv: Record = { 82 | ...process.env, 83 | PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` 84 | }; 85 | 86 | // Log the command with proper quoting for copy-paste debugging 87 | const quotedArgs = args_array.map(arg => { 88 | // Quote arguments that contain spaces or special characters 89 | if (arg.includes(' ') || arg.includes('|') || arg.includes('&') || arg.includes('(') || arg.includes(')')) { 90 | return `"${arg}"`; 91 | } 92 | return arg; 93 | }); 94 | console.error(`Running background command: ${tsharkPath} ${quotedArgs.join(' ')}`); 95 | 96 | // Start background capture process with stderr logging 97 | const captureProcess = spawn(tsharkPath, args_array, { 98 | env: captureEnv, 99 | stdio: ['ignore', 'ignore', 'pipe'], // Capture stderr for error logging 100 | detached: true // Fully detach the process 101 | }); 102 | 103 | // Log any errors from tshark 104 | if (captureProcess.stderr) { 105 | captureProcess.stderr.on('data', (data) => { 106 | console.error(`tshark stderr [${sessionId}]: ${data.toString().trim()}`); 107 | }); 108 | } 109 | 110 | // Unref the process so the parent can exit independently 111 | captureProcess.unref(); 112 | 113 | // Store session info 114 | const session: CaptureSession = { 115 | id: sessionId, 116 | process: captureProcess, 117 | interface: networkInterface, 118 | captureFilter, 119 | timeout, 120 | maxPackets, 121 | startTime: new Date(), 122 | tempFile, 123 | status: 'running' 124 | }; 125 | 126 | activeSessions.set(sessionId, session); 127 | 128 | // Handle process completion - KEEP SESSION ALIVE for result retrieval 129 | captureProcess.on('exit', (code) => { 130 | console.error(`Capture session ${sessionId} exited with code: ${code}`); 131 | if (activeSessions.has(sessionId)) { 132 | const session = activeSessions.get(sessionId)!; 133 | session.process = null; 134 | session.status = code === 0 ? 'completed' : 'error'; 135 | session.endTime = new Date(); 136 | if (code !== null) { 137 | session.exitCode = code; 138 | } 139 | console.error(`Session ${sessionId} marked as ${session.status}, file: ${session.tempFile}`); 140 | } 141 | }); 142 | 143 | captureProcess.on('error', (error) => { 144 | console.error(`Capture session ${sessionId} error: ${error.message}`); 145 | if (activeSessions.has(sessionId)) { 146 | const session = activeSessions.get(sessionId)!; 147 | session.process = null; 148 | session.status = 'error'; 149 | session.endTime = new Date(); 150 | } 151 | }); 152 | 153 | const configInfo = configName ? `\nUsing saved config: ${configName}` : ''; 154 | 155 | return { 156 | content: [{ 157 | type: 'text' as const, 158 | text: `Capture session started successfully!${configInfo}\nSession ID: ${sessionId}\nInterface: ${networkInterface}\nCapture Filter: ${captureFilter || 'none'}\nTimeout: ${timeout}s (auto-stop)\nMax Packets: ${maxPackets} (safety limit)\n\nCapture will auto-stop after ${timeout} seconds or use 'stop_capture_session' with session ID '${sessionId}' to stop manually and retrieve results.`, 159 | }], 160 | }; 161 | } catch (error: any) { 162 | console.error(`Error starting capture session: ${error.message}`); 163 | return { 164 | content: [{ type: 'text' as const, text: `Error: ${error.message}` }], 165 | isError: true 166 | }; 167 | } 168 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SharkMCP - Network Packet Analysis MCP Server 2 | 3 | A Model Context Protocol (MCP) server that provides network packet capture and analysis capabilities through Wireshark/tshark integration. Designed for AI assistants to perform network security analysis, troubleshooting, and packet inspection. 4 | 5 | This server was thought for situations where you want your agent to debug a program that sends requests and verify the packet traffic, allowing the following workflow: 6 | 7 | - Start recording packets 8 | - Run tool or perform request 9 | - Stop recording and analyze results 10 | 11 | ## Architecture 12 | 13 | SharkMCP provides a simple, local development-focused architecture: 14 | 15 | ``` 16 | ┌─────────────────────────────────────────────────────────┐ 17 | │ SharkMCP Server │ 18 | ├─────────────────────────────────────────────────────────┤ 19 | │ MCP Protocol Layer │ 20 | │ ├─ start_capture_session │ 21 | │ ├─ stop_capture_session │ 22 | │ ├─ analyze_pcap_file │ 23 | │ └─ manage_config │ 24 | ├─────────────────────────────────────────────────────────┤ 25 | │ tshark Integration Layer │ 26 | │ ├─ Cross-platform executable detection │ 27 | │ ├─ Process management │ 28 | │ └─ Output parsing (JSON/fields/text) │ 29 | ├─────────────────────────────────────────────────────────┤ 30 | │ Host System Integration │ 31 | │ ├─ Local tshark installation │ 32 | │ ├─ Direct network interface access │ 33 | │ └─ Native file system operations │ 34 | └─────────────────────────────────────────────────────────┘ 35 | ``` 36 | 37 | ## Features 38 | 39 | - **Async Packet Capture**: Start background capture sessions with configurable filters and timeouts. 40 | - **PCAP File Analysis**: Analyze existing packet capture files 41 | - **Flexible Output Formats**: JSON, custom fields, or traditional text output 42 | - **SSL/TLS Decryption**: Support for SSL keylog files to decrypt HTTPS traffic 43 | - **Reusable Configurations**: Save and reuse capture/analysis configurations 44 | 45 | /!\ Packet information can be very extensive. Make sure to use a scoped display filter not to overload the context of your conversation. 46 | 47 | ## Prerequisites 48 | 49 | ### System Requirements 50 | - **Wireshark/tshark**: Must be installed and accessible 51 | - **Node.js**: Version 18+ 52 | - **pnpm**: Package manager (recommended) 53 | 54 | ### Installing Wireshark/tshark 55 | 56 | **macOS** (using Homebrew): 57 | ```bash 58 | brew install wireshark 59 | ``` 60 | 61 | **Ubuntu/Debian**: 62 | ```bash 63 | sudo apt update 64 | sudo apt install tshark wireshark-common 65 | ``` 66 | 67 | **Windows**: 68 | Download from [wireshark.org](https://www.wireshark.org/download.html) 69 | 70 | ## Installation 71 | 72 | 1. **Clone the repository**: 73 | ```bash 74 | git clone https://github.com/kriztalz/SharkMCP.git 75 | cd SharkMCP 76 | ``` 77 | 78 | 2. **Install dependencies**: 79 | ```bash 80 | pnpm install 81 | ``` 82 | 83 | 3. **Build the project**: 84 | ```bash 85 | pnpm run build 86 | ``` 87 | 88 | 4. **Run the server**: 89 | ```bash 90 | pnpm start 91 | ``` 92 | 93 | ## Testing 94 | 95 | SharkMCP includes comprehensive integration tests that verify packet capture functionality. 96 | 97 | ### Running Tests 98 | 99 | ```bash 100 | # Run all tests 101 | pnpm test 102 | ``` 103 | 104 | ## Configuration 105 | 106 | ### MCP Client Setup 107 | 108 | ```json 109 | { 110 | "mcpServers": { 111 | "sharkmcp": { 112 | "command": "node", 113 | "args": ["/path/to/SharkMCP/dist/index.js"], 114 | } 115 | } 116 | } 117 | ``` 118 | 119 | ### SSL/TLS Decryption (Optional) 120 | 121 | To decrypt HTTPS traffic, export the `SSLKEYLOGFILE` environment variable: 122 | 123 | ```bash 124 | export SSLKEYLOGFILE=/path/to/sslkeylog.log 125 | ``` 126 | 127 | Then configure your applications to log SSL keys to this file. Many applications support this automatically when the environment variable is set. 128 | 129 | Then pass the log file pathname to the MCP server in the `stop_capture_session` tool. 130 | 131 | ## Usage 132 | 133 | ### Available Tools 134 | 135 | 1. **start_capture_session**: Start background packet capture 136 | 2. **stop_capture_session**: Stop capture and analyze results 137 | 3. **analyze_pcap_file**: Analyze existing PCAP files 138 | 4. **manage_config**: Save/load reusable configurations 139 | 140 | ### Basic Examples 141 | 142 | **Start a capture session**: 143 | ``` 144 | Interface: en0 145 | Capture Filter: port 443 146 | Timeout: 30 seconds 147 | ``` 148 | 149 | **Analyze captured traffic**: 150 | ``` 151 | Display Filter: tls.handshake.type == 1 152 | Output Format: json 153 | ``` 154 | 155 | **Save a configuration**: 156 | ```json 157 | { 158 | "name": "https-monitoring", 159 | "description": "Monitor HTTPS traffic", 160 | "captureFilter": "port 443", 161 | "displayFilter": "tls.handshake.type == 1", 162 | "outputFormat": "json", 163 | "timeout": 60, 164 | "interface": "en0" 165 | } 166 | ``` 167 | 168 | ## Development 169 | 170 | ### Project Structure 171 | 172 | ``` 173 | SharkMCP/ 174 | ├── src/ 175 | │ ├── index.ts # Main server setup 176 | │ ├── types.ts # TypeScript interfaces 177 | │ ├── utils.ts # Utility functions 178 | │ └── tools/ # Individual tool implementations 179 | │ ├── start-capture-session.ts 180 | │ ├── stop-capture-session.ts 181 | │ ├── analyze-pcap-file.ts 182 | │ └── manage-config.ts 183 | ├── test/ # Test files 184 | │ └── integration.test.js # Integration tests 185 | ├── package.json 186 | └── README.md 187 | ``` 188 | 189 | ### Development Commands 190 | 191 | ```bash 192 | # Development mode with auto-reload 193 | pnpm run dev 194 | 195 | # Build for production 196 | pnpm run build 197 | 198 | # Run tests 199 | pnpm run test 200 | 201 | # Type checking 202 | pnpm run build 203 | ``` 204 | 205 | ## Security Considerations 206 | 207 | - **Network Permissions**: Packet capture requires elevated privileges 208 | - **File Access**: Temporary files are created in `/tmp/` 209 | - **Docker Security**: Container runs as non-root user 210 | - **SSL Keylog**: Sensitive SSL keys should be handled securely 211 | 212 | ## Troubleshooting 213 | 214 | ### Common Issues 215 | 216 | **"tshark not found"**: 217 | - Ensure Wireshark is installed and tshark is in PATH 218 | - Check installation with: `tshark -v` 219 | 220 | **Permission denied for packet capture**: 221 | - On Linux: Add user to `wireshark` group or run with `sudo` 222 | - On macOS: Grant Terminal network access in System Preferences 223 | - On Windows: Run as Administrator 224 | 225 | **No packets captured**: 226 | - Verify network interface name (`ip link` on Linux, `ifconfig` on macOS) 227 | - Check capture filter syntax 228 | - Ensure traffic is present on the interface 229 | 230 | ## Contributing (Very welcome!) 231 | 232 | 1. Fork the repository 233 | 2. Create a feature branch 234 | 3. Make your changes following the existing code style 235 | 4. Add tests for new functionality 236 | 5. Submit a pull request 237 | 238 | ## License 239 | 240 | MIT License 241 | 242 | ## Issues / Suggestions 243 | 244 | Feel free to open an issue with any question or suggestion you may have. -------------------------------------------------------------------------------- /src/tools/manage-config.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { FilterConfig } from "../types.js"; 3 | import { 4 | saveFilterConfig, 5 | loadFilterConfig, 6 | listFilterConfigs, 7 | deleteFilterConfig 8 | } from "../utils.js"; 9 | 10 | /** 11 | * Input schema for config management tool 12 | */ 13 | export const manageConfigSchema = { 14 | action: z.enum(['save', 'load', 'list', 'view', 'delete']).describe('Action to perform: save, load, list (brief), view (detailed), or delete a configuration'), 15 | name: z.string().optional().describe('Name of the configuration (required for save, load, delete)'), 16 | detailed: z.boolean().optional().default(false).describe('Show detailed configuration info when listing (only used with list action)'), 17 | config: z.object({ 18 | description: z.string().optional().describe('Description of what this config does'), 19 | captureFilter: z.string().optional().describe('BPF capture filter for packet capture'), 20 | displayFilter: z.string().optional().describe('Wireshark display filter for analysis'), 21 | outputFormat: z.enum(['json', 'fields', 'text']).optional().describe('Output format for analysis'), 22 | customFields: z.string().optional().describe('Custom field list for fields format'), 23 | timeout: z.number().optional().describe('Timeout in seconds for capture sessions'), 24 | maxPackets: z.number().optional().describe('Maximum packets to capture'), 25 | interface: z.string().optional().describe('Network interface to use') 26 | }).optional().describe('Configuration object (required for save action)') 27 | }; 28 | 29 | /** 30 | * Tool handler for managing filter configurations 31 | * Allows LLMs to save, load, list, and delete reusable filter configurations 32 | */ 33 | export async function manageConfigHandler(args: any) { 34 | try { 35 | const { action, name, config, detailed } = args; 36 | 37 | switch (action) { 38 | case 'save': 39 | if (!name || !config) { 40 | return { 41 | content: [{ 42 | type: 'text' as const, 43 | text: 'Error: Both name and config are required for save action.', 44 | }], 45 | isError: true 46 | }; 47 | } 48 | 49 | const filterConfig: FilterConfig = { 50 | name, 51 | ...config 52 | }; 53 | 54 | await saveFilterConfig(filterConfig); 55 | return { 56 | content: [{ 57 | type: 'text' as const, 58 | text: `Configuration '${name}' saved successfully!\n\nSaved config:\n${JSON.stringify(filterConfig, null, 2)}`, 59 | }], 60 | }; 61 | 62 | case 'load': 63 | if (!name) { 64 | return { 65 | content: [{ 66 | type: 'text' as const, 67 | text: 'Error: Name is required for load action.', 68 | }], 69 | isError: true 70 | }; 71 | } 72 | 73 | const loadedConfig = await loadFilterConfig(name); 74 | if (!loadedConfig) { 75 | return { 76 | content: [{ 77 | type: 'text' as const, 78 | text: `Error: Configuration '${name}' not found.`, 79 | }], 80 | isError: true 81 | }; 82 | } 83 | 84 | return { 85 | content: [{ 86 | type: 'text' as const, 87 | text: `Configuration '${name}' loaded:\n\n${JSON.stringify(loadedConfig, null, 2)}`, 88 | }], 89 | }; 90 | 91 | case 'list': 92 | const allConfigs = await listFilterConfigs(); 93 | if (allConfigs.length === 0) { 94 | return { 95 | content: [{ 96 | type: 'text' as const, 97 | text: 'No saved configurations found.', 98 | }], 99 | }; 100 | } 101 | 102 | if (detailed) { 103 | // Show detailed information for all configurations 104 | const detailedList = allConfigs.map(cfg => { 105 | const configDetails = [ 106 | `Name: ${cfg.name}`, 107 | ...(cfg.description ? [`Description: ${cfg.description}`] : []), 108 | ...(cfg.captureFilter ? [`Capture Filter: ${cfg.captureFilter}`] : []), 109 | ...(cfg.displayFilter ? [`Display Filter: ${cfg.displayFilter}`] : []), 110 | ...(cfg.outputFormat ? [`Output Format: ${cfg.outputFormat}`] : []), 111 | ...(cfg.customFields ? [`Custom Fields: ${cfg.customFields}`] : []), 112 | ...(cfg.interface ? [`Interface: ${cfg.interface}`] : []), 113 | ...(cfg.timeout ? [`Timeout: ${cfg.timeout}s`] : []), 114 | ...(cfg.maxPackets ? [`Max Packets: ${cfg.maxPackets}`] : []) 115 | ]; 116 | return configDetails.join('\n '); 117 | }).join('\n\n' + '─'.repeat(50) + '\n\n'); 118 | 119 | return { 120 | content: [{ 121 | type: 'text' as const, 122 | text: `Available configurations (${allConfigs.length}) - Detailed View:\n\n${'─'.repeat(50)}\n\n${detailedList}\n\n${'─'.repeat(50)}\n\nUse 'load' action with a specific name to get the full JSON configuration.`, 123 | }], 124 | }; 125 | } else { 126 | // Show brief list (existing behavior) 127 | const configList = allConfigs.map(cfg => 128 | `• ${cfg.name}${cfg.description ? `: ${cfg.description}` : ''}` 129 | ).join('\n'); 130 | 131 | return { 132 | content: [{ 133 | type: 'text' as const, 134 | text: `Available configurations (${allConfigs.length}):\n\n${configList}\n\nUse 'load' action to get full details of any configuration, or use 'view' action to see all configurations with full details.`, 135 | }], 136 | }; 137 | } 138 | 139 | case 'view': 140 | const allConfigsForView = await listFilterConfigs(); 141 | if (allConfigsForView.length === 0) { 142 | return { 143 | content: [{ 144 | type: 'text' as const, 145 | text: 'No saved configurations found.', 146 | }], 147 | }; 148 | } 149 | 150 | const configDetails = allConfigsForView.map(cfg => 151 | `${cfg.name}:\n${JSON.stringify(cfg, null, 2)}` 152 | ).join('\n\n' + '─'.repeat(60) + '\n\n'); 153 | 154 | return { 155 | content: [{ 156 | type: 'text' as const, 157 | text: `All configurations (${allConfigsForView.length}) - Full Details:\n\n${'─'.repeat(60)}\n\n${configDetails}\n\n${'─'.repeat(60)}`, 158 | }], 159 | }; 160 | 161 | case 'delete': 162 | if (!name) { 163 | return { 164 | content: [{ 165 | type: 'text' as const, 166 | text: 'Error: Name is required for delete action.', 167 | }], 168 | isError: true 169 | }; 170 | } 171 | 172 | const deleted = await deleteFilterConfig(name); 173 | if (!deleted) { 174 | return { 175 | content: [{ 176 | type: 'text' as const, 177 | text: `Error: Configuration '${name}' not found.`, 178 | }], 179 | isError: true 180 | }; 181 | } 182 | 183 | return { 184 | content: [{ 185 | type: 'text' as const, 186 | text: `Configuration '${name}' deleted successfully.`, 187 | }], 188 | }; 189 | 190 | default: 191 | return { 192 | content: [{ 193 | type: 'text' as const, 194 | text: `Error: Unknown action '${action}'. Use save, load, list, view, or delete.`, 195 | }], 196 | isError: true 197 | }; 198 | } 199 | } catch (error: any) { 200 | console.error(`Error managing config: ${error.message}`); 201 | return { 202 | content: [{ type: 'text' as const, text: `Error: ${error.message}` }], 203 | isError: true 204 | }; 205 | } 206 | } -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from "util"; 2 | import { exec } from "child_process"; 3 | import which from "which"; 4 | import fs from "fs/promises"; 5 | import path from "path"; 6 | import { fileURLToPath } from "url"; 7 | import { OutputFormat, TsharkEnvironment, FilterConfig, ConfigFile } from "./types.js"; 8 | 9 | // Promisify exec for async/await usage 10 | const execAsync = promisify(exec); 11 | 12 | // Get the directory of this file and construct config path relative to project root 13 | const __filename = fileURLToPath(import.meta.url); 14 | const __dirname = path.dirname(__filename); 15 | const CONFIG_FILE_PATH = path.join(__dirname, '..', 'sharkmcp-configs.json'); 16 | 17 | /** 18 | * Dynamically locate tshark executable with cross-platform support 19 | */ 20 | export async function findTshark(): Promise { 21 | // First, try to find tshark in PATH 22 | try { 23 | const tsharkPath = await which('tshark'); 24 | if (!tsharkPath) { 25 | throw new Error('tshark not found in PATH'); 26 | } 27 | const pathString = Array.isArray(tsharkPath) ? tsharkPath[0] : tsharkPath; 28 | 29 | // Verify the executable works 30 | await execAsync(`"${pathString}" -v`, { timeout: 5000 }); 31 | console.error(`Found tshark at: ${pathString}`); 32 | return pathString; 33 | } catch (err: any) { 34 | console.error('tshark not found in PATH, trying fallback locations...'); 35 | } 36 | 37 | // Platform-specific fallback paths 38 | const getFallbackPaths = (): string[] => { 39 | switch (process.platform) { 40 | case 'win32': 41 | return [ 42 | 'C:\\Program Files\\Wireshark\\tshark.exe', 43 | 'C:\\Program Files (x86)\\Wireshark\\tshark.exe', 44 | ...(process.env.ProgramFiles ? [`${process.env.ProgramFiles}\\Wireshark\\tshark.exe`] : []), 45 | ...(process.env['ProgramFiles(x86)'] ? [`${process.env['ProgramFiles(x86)']}\\Wireshark\\tshark.exe`] : []) 46 | ]; 47 | 48 | case 'darwin': 49 | return [ 50 | '/opt/homebrew/bin/tshark', 51 | '/usr/local/bin/tshark', 52 | '/Applications/Wireshark.app/Contents/MacOS/tshark', 53 | '/usr/bin/tshark' 54 | ]; 55 | 56 | case 'linux': 57 | return [ 58 | '/usr/bin/tshark', 59 | '/usr/local/bin/tshark', 60 | '/snap/bin/tshark', 61 | '/usr/sbin/tshark' 62 | ]; 63 | 64 | default: 65 | return ['/usr/bin/tshark', '/usr/local/bin/tshark']; 66 | } 67 | }; 68 | 69 | // Try fallback paths 70 | const fallbackPaths = getFallbackPaths(); 71 | for (const candidatePath of fallbackPaths) { 72 | try { 73 | await execAsync(`"${candidatePath}" -v`, { timeout: 5000 }); 74 | console.error(`Found tshark at fallback: ${candidatePath}`); 75 | return candidatePath; 76 | } catch { 77 | // Continue to next fallback 78 | } 79 | } 80 | 81 | throw new Error( 82 | 'tshark not found. Please install Wireshark (https://www.wireshark.org/download.html) and ensure tshark is in your PATH.' 83 | ); 84 | } 85 | 86 | /** 87 | * Load config file, creating default if it doesn't exist 88 | */ 89 | export async function loadConfigFile(): Promise { 90 | try { 91 | const configContent = await fs.readFile(CONFIG_FILE_PATH, 'utf8'); 92 | return JSON.parse(configContent); 93 | } catch (error) { 94 | // Create default config file if it doesn't exist 95 | const defaultConfig: ConfigFile = { 96 | version: "0.1.0", 97 | configs: {} 98 | }; 99 | await saveConfigFile(defaultConfig); 100 | return defaultConfig; 101 | } 102 | } 103 | 104 | /** 105 | * Save config file 106 | */ 107 | export async function saveConfigFile(config: ConfigFile): Promise { 108 | await fs.writeFile(CONFIG_FILE_PATH, JSON.stringify(config, null, 2)); 109 | } 110 | 111 | /** 112 | * Save a filter configuration 113 | */ 114 | export async function saveFilterConfig(filterConfig: FilterConfig): Promise { 115 | const configFile = await loadConfigFile(); 116 | configFile.configs[filterConfig.name] = filterConfig; 117 | await saveConfigFile(configFile); 118 | } 119 | 120 | /** 121 | * Load a filter configuration by name 122 | */ 123 | export async function loadFilterConfig(name: string): Promise { 124 | const configFile = await loadConfigFile(); 125 | return configFile.configs[name] || null; 126 | } 127 | 128 | /** 129 | * List all available filter configurations 130 | */ 131 | export async function listFilterConfigs(): Promise { 132 | const configFile = await loadConfigFile(); 133 | return Object.values(configFile.configs); 134 | } 135 | 136 | /** 137 | * Delete a filter configuration 138 | */ 139 | export async function deleteFilterConfig(name: string): Promise { 140 | const configFile = await loadConfigFile(); 141 | if (configFile.configs[name]) { 142 | delete configFile.configs[name]; 143 | await saveConfigFile(configFile); 144 | return true; 145 | } 146 | return false; 147 | } 148 | 149 | /** 150 | * Process tshark output based on format 151 | */ 152 | export function processTsharkOutput( 153 | stdout: string, 154 | outputFormat: OutputFormat 155 | ): string { 156 | switch (outputFormat) { 157 | case 'json': 158 | // Try to parse and format JSON for readability 159 | try { 160 | const parsed = JSON.parse(stdout); 161 | return JSON.stringify(parsed, null, 2); 162 | } catch { 163 | return stdout; // Return raw if parsing fails 164 | } 165 | case 'fields': 166 | case 'text': 167 | default: 168 | return stdout; // Return raw output 169 | } 170 | } 171 | 172 | /** 173 | * Reusable function for PCAP analysis with comprehensive cross-platform error handling 174 | */ 175 | export async function analyzePcap( 176 | filePath: string, 177 | displayFilter: string = '', 178 | outputFormat: OutputFormat = 'text', 179 | customFields?: string, 180 | sslKeylogFile?: string 181 | ): Promise { 182 | const tsharkPath = await findTshark(); 183 | 184 | // Set up SSL keylog for decryption during analysis 185 | const analysisEnv: TsharkEnvironment = Object.fromEntries( 186 | Object.entries(process.env).filter(([_, value]) => value !== undefined) 187 | ) as TsharkEnvironment; 188 | 189 | const keylogToUse = sslKeylogFile || process.env.SSLKEYLOGFILE; 190 | if (keylogToUse) { 191 | console.error(`Using SSL keylog file for decryption: ${keylogToUse}`); 192 | analysisEnv.SSLKEYLOGFILE = keylogToUse; 193 | } 194 | 195 | // Build command based on output format using absolute tshark path 196 | let command: string; 197 | const sslOptions = keylogToUse ? `-o tls.keylog_file:"${keylogToUse}"` : ''; 198 | const filterOption = displayFilter ? `-Y "${displayFilter}"` : ''; 199 | const quotedTsharkPath = `"${tsharkPath}"`; 200 | 201 | switch (outputFormat) { 202 | case 'json': 203 | command = `${quotedTsharkPath} -r "${filePath}" ${sslOptions} ${filterOption} -T json`; 204 | break; 205 | case 'fields': 206 | const fieldsToUse = customFields || 'frame.number,frame.time_relative,ip.src,ip.dst,tcp.srcport,tcp.dstport'; 207 | const fieldArgs = fieldsToUse.split(',').map(field => `-e ${field.trim()}`).join(' '); 208 | command = `${quotedTsharkPath} -r "${filePath}" ${sslOptions} ${filterOption} -T fields ${fieldArgs}`; 209 | break; 210 | case 'text': 211 | default: 212 | command = `${quotedTsharkPath} -r "${filePath}" ${sslOptions} ${filterOption}`; 213 | break; 214 | } 215 | 216 | // Execution options with increased buffer 217 | const execOptions = { 218 | env: analysisEnv, 219 | maxBuffer: 50 * 1024 * 1024 // 50MB buffer 220 | }; 221 | 222 | console.error(`Analyzing capture with command: ${command}`); 223 | const { stdout } = await execAsync(command, execOptions); 224 | return processTsharkOutput(stdout, outputFormat); 225 | } 226 | 227 | /** 228 | * Trim output if it exceeds maximum character limits 229 | * Different formats have different optimal limits for readability 230 | */ 231 | export function trimOutput(output: string, outputFormat: OutputFormat): string { 232 | // Format-specific limits for optimal readability 233 | const maxChars = outputFormat === 'json' ? 500000 : 234 | outputFormat === 'fields' ? 800000 : 235 | 720000; // text format default 236 | 237 | if (output.length > maxChars) { 238 | const trimPoint = maxChars - 500; 239 | const formatInfo = outputFormat !== 'text' ? ` (${outputFormat} format)` : ''; 240 | const trimmed = output.substring(0, trimPoint) + `\n\n... [Output truncated due to size${formatInfo}] ...`; 241 | console.error(`Trimmed ${outputFormat} output from ${output.length} to ${maxChars} chars`); 242 | return trimmed; 243 | } 244 | return output; 245 | } -------------------------------------------------------------------------------- /test/integration.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Integration Tests for SharkMCP Server 3 | * Tests the full MCP server functionality using the SDK client 4 | */ 5 | 6 | import { Client } from "@modelcontextprotocol/sdk/client/index.js"; 7 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; 8 | import { spawn } from "child_process"; 9 | import { fileURLToPath } from "url"; 10 | import { dirname, join } from "path"; 11 | import process from "process"; 12 | 13 | const __filename = fileURLToPath(import.meta.url); 14 | const __dirname = dirname(__filename); 15 | const projectRoot = join(__dirname, '..'); 16 | 17 | /** 18 | * Test suite configuration 19 | */ 20 | const TEST_CONFIG = { 21 | serverPath: join(projectRoot, 'dist', 'index.js'), 22 | testInterface: process.platform === 'darwin' ? 'en0' : 'eth0', // Adjust based on platform 23 | captureTimeout: 12, // Slightly longer than config timeout to ensure completion 24 | configName: 'integration_test' 25 | }; 26 | 27 | /** 28 | * Utility function to wait for a specified time 29 | */ 30 | function sleep(ms) { 31 | return new Promise(resolve => setTimeout(resolve, ms)); 32 | } 33 | 34 | /** 35 | * Generate some network traffic to ensure we capture packets 36 | */ 37 | async function generateNetworkTraffic() { 38 | console.log('Generating network traffic...'); 39 | 40 | // Create multiple concurrent network requests to generate traffic 41 | const trafficPromises = [ 42 | // HTTP requests 43 | fetch('http://httpbin.org/get').catch(() => {}), 44 | fetch('http://example.com').catch(() => {}), 45 | // DNS lookups via fetch will generate UDP traffic 46 | fetch('http://google.com').catch(() => {}), 47 | fetch('http://github.com').catch(() => {}), 48 | ]; 49 | 50 | // Don't wait for all to complete, just start them 51 | await Promise.allSettled(trafficPromises.slice(0, 2)); // Wait for first 2 52 | console.log('Network traffic generated'); 53 | } 54 | 55 | /** 56 | * Extract packet count from tshark JSON output 57 | */ 58 | function countPacketsFromOutput(output, outputFormat) { 59 | if (!output || output.trim() === '') { 60 | return 0; 61 | } 62 | 63 | try { 64 | switch (outputFormat) { 65 | case 'json': 66 | // For JSON format, parse and count array elements 67 | const parsed = JSON.parse(output); 68 | if (Array.isArray(parsed)) { 69 | return parsed.length; 70 | } else if (parsed._source) { 71 | // Single packet format 72 | return 1; 73 | } 74 | return 0; 75 | 76 | case 'fields': 77 | // For fields format, count non-empty lines 78 | return output.split('\n').filter(line => line.trim().length > 0).length; 79 | 80 | case 'text': 81 | default: 82 | // For text format, count lines that look like packet headers 83 | const lines = output.split('\n'); 84 | return lines.filter(line => 85 | line.match(/^\s*\d+\s+\d+\.\d+/) || // Standard packet line 86 | line.includes('Ethernet') || 87 | line.includes('Internet Protocol') 88 | ).length; 89 | } 90 | } catch (error) { 91 | console.warn(`Warning: Failed to parse output for packet counting: ${error.message}`); 92 | // Fallback: count non-empty lines 93 | return output.split('\n').filter(line => line.trim().length > 0).length; 94 | } 95 | } 96 | 97 | /** 98 | * Main integration test runner 99 | */ 100 | async function runIntegrationTests() { 101 | console.log('Starting SharkMCP Integration Tests'); 102 | console.log(`Project root: ${projectRoot}`); 103 | console.log(`Server path: ${TEST_CONFIG.serverPath}`); 104 | console.log(`Test interface: ${TEST_CONFIG.testInterface}`); 105 | 106 | let client; 107 | let transport; 108 | 109 | try { 110 | // Initialize MCP client with server transport 111 | console.log('\nSetting up MCP client transport...'); 112 | transport = new StdioClientTransport({ 113 | command: "node", 114 | args: [TEST_CONFIG.serverPath] 115 | }); 116 | 117 | client = new Client({ 118 | name: "sharkmcp-integration-test", 119 | version: "1.0.0" 120 | }); 121 | 122 | console.log('Connecting to MCP server...'); 123 | await client.connect(transport); 124 | console.log('Successfully connected to MCP server'); 125 | 126 | // Test 1: List available tools 127 | console.log('\nTest 1: Listing available tools...'); 128 | const tools = await client.listTools(); 129 | console.log(`Found ${tools.tools.length} tools:`); 130 | tools.tools.forEach(tool => { 131 | console.log(` - ${tool.name}: ${tool.description}`); 132 | }); 133 | 134 | const expectedTools = ['start_capture_session', 'stop_capture_session', 'analyze_pcap_file', 'manage_config']; 135 | const foundTools = tools.tools.map(t => t.name); 136 | const missingTools = expectedTools.filter(tool => !foundTools.includes(tool)); 137 | 138 | if (missingTools.length > 0) { 139 | throw new Error(`Missing expected tools: ${missingTools.join(', ')}`); 140 | } 141 | console.log('All expected tools found'); 142 | 143 | // Test 2: Load and verify test configuration 144 | console.log('\nTest 2: Loading test configuration...'); 145 | const configResult = await client.callTool({ 146 | name: "manage_config", 147 | arguments: { 148 | action: "load", 149 | name: TEST_CONFIG.configName 150 | } 151 | }); 152 | 153 | if (configResult.isError) { 154 | throw new Error(`Failed to load test config: ${configResult.content[0].text}`); 155 | } 156 | console.log('Test configuration loaded successfully'); 157 | console.log(configResult.content[0].text); 158 | 159 | // Test 3: Start capture session using saved config 160 | console.log('\nTest 3: Starting packet capture session...'); 161 | const startResult = await client.callTool({ 162 | name: "start_capture_session", 163 | arguments: { 164 | configName: TEST_CONFIG.configName, 165 | interface: TEST_CONFIG.testInterface 166 | } 167 | }); 168 | 169 | if (startResult.isError) { 170 | throw new Error(`Failed to start capture: ${startResult.content[0].text}`); 171 | } 172 | 173 | const startText = startResult.content[0].text; 174 | console.log('Capture session started'); 175 | console.log(startText); 176 | 177 | // Extract session ID from response 178 | const sessionIdMatch = startText.match(/Session ID: ([\w_]+)/); 179 | if (!sessionIdMatch) { 180 | throw new Error('Could not extract session ID from start response'); 181 | } 182 | const sessionId = sessionIdMatch[1]; 183 | console.log(`Session ID: ${sessionId}`); 184 | 185 | // Test 4: Generate network traffic during capture 186 | console.log('\nTest 4: Generating network traffic...'); 187 | await sleep(2000); // Wait 2 seconds after starting capture 188 | await generateNetworkTraffic(); 189 | 190 | // Wait for remaining capture time 191 | const remainingTime = (TEST_CONFIG.captureTimeout - 3) * 1000; // 3 seconds already passed 192 | console.log(`Waiting ${remainingTime/1000}s for capture to complete...`); 193 | await sleep(remainingTime); 194 | 195 | // Test 5: Stop capture and analyze results 196 | console.log('\nTest 5: Stopping capture and analyzing results...'); 197 | const stopResult = await client.callTool({ 198 | name: "stop_capture_session", 199 | arguments: { 200 | sessionId: sessionId, 201 | outputFormat: "json" 202 | } 203 | }); 204 | 205 | if (stopResult.isError) { 206 | throw new Error(`Failed to stop capture: ${stopResult.content[0].text}`); 207 | } 208 | 209 | const stopText = stopResult.content[0].text; 210 | console.log('Capture session stopped and analyzed'); 211 | 212 | // Test 6: Extract and count packets 213 | console.log('\nTest 6: Counting captured packets...'); 214 | 215 | // Extract the JSON results section 216 | const resultsMatch = stopText.match(/Packet Analysis Results:\n(.*)/s); 217 | if (!resultsMatch) { 218 | console.warn('Could not extract packet analysis results from response'); 219 | console.log('Full response:'); 220 | console.log(stopText); 221 | } else { 222 | const packetData = resultsMatch[1]; 223 | const packetCount = countPacketsFromOutput(packetData, 'json'); 224 | 225 | console.log(`Packet count: ${packetCount}`); 226 | 227 | if (packetCount === 0) { 228 | console.warn('No packets captured - this could indicate:'); 229 | console.warn(' - No network traffic on interface during capture'); 230 | console.warn(' - Interface name incorrect for this system'); 231 | console.warn(' - Permission issues with packet capture'); 232 | console.warn(' - tshark not working properly'); 233 | } else { 234 | console.log(`Successfully captured ${packetCount} packets`); 235 | } 236 | 237 | // Show some sample output 238 | if (packetData.length > 0) { 239 | const sampleLength = Math.min(500, packetData.length); 240 | console.log('\nSample output (first 500 chars):'); 241 | console.log(packetData.substring(0, sampleLength)); 242 | if (packetData.length > sampleLength) { 243 | console.log('... (truncated)'); 244 | } 245 | } 246 | } 247 | 248 | // Test 7: Test PCAP file analysis (if we have the test file) 249 | console.log('\nTest 7: Testing PCAP file analysis...'); 250 | try { 251 | const pcapResult = await client.callTool({ 252 | name: "analyze_pcap_file", 253 | arguments: { 254 | filePath: join(projectRoot, 'test', 'dump.pcapng'), 255 | outputFormat: "json", 256 | displayFilter: "" 257 | } 258 | }); 259 | 260 | if (!pcapResult.isError) { 261 | const pcapText = pcapResult.content[0].text; 262 | const pcapResultsMatch = pcapText.match(/Packet Analysis Results:\n(.*)/s); 263 | 264 | if (pcapResultsMatch) { 265 | const pcapPacketData = pcapResultsMatch[1]; 266 | const pcapPacketCount = countPacketsFromOutput(pcapPacketData, 'json'); 267 | console.log(`PCAP file analysis successful: ${pcapPacketCount} packets found`); 268 | } else { 269 | console.log('PCAP file analysis completed (format parsing issue)'); 270 | } 271 | } else { 272 | console.log('PCAP file analysis failed (test file may not exist)'); 273 | } 274 | } catch (error) { 275 | console.log(`PCAP file analysis test skipped: ${error.message}`); 276 | } 277 | 278 | // Test 8: Test TLS handshake filtering on dump.pcapng 279 | console.log('\nTest 8: Testing TLS handshake filter on dump.pcapng...'); 280 | try { 281 | const tlsResult = await client.callTool({ 282 | name: "analyze_pcap_file", 283 | arguments: { 284 | filePath: join(projectRoot, 'test', 'dump.pcapng'), 285 | outputFormat: "json", 286 | displayFilter: "tls.handshake.type == 1" 287 | } 288 | }); 289 | 290 | if (!tlsResult.isError) { 291 | const tlsText = tlsResult.content[0].text; 292 | const tlsResultsMatch = tlsText.match(/Packet Analysis Results:\n(.*)/s); 293 | 294 | if (tlsResultsMatch) { 295 | const tlsPacketData = tlsResultsMatch[1]; 296 | const tlsPacketCount = countPacketsFromOutput(tlsPacketData, 'json'); 297 | 298 | if (tlsPacketCount > 0) { 299 | console.log(`TLS handshake filter successful: Found ${tlsPacketCount} TLS Client Hello packets`); 300 | 301 | // Show a sample of the TLS handshake data 302 | if (tlsPacketData.length > 0) { 303 | const sampleLength = Math.min(300, tlsPacketData.length); 304 | console.log('\nSample TLS handshake data (first 300 chars):'); 305 | console.log(tlsPacketData.substring(0, sampleLength)); 306 | if (tlsPacketData.length > sampleLength) { 307 | console.log('... (truncated)'); 308 | } 309 | } 310 | } else { 311 | console.log('TLS handshake filter returned no packets - dump.pcapng may not contain TLS Client Hello packets'); 312 | } 313 | } else { 314 | console.log('TLS handshake filter completed but could not parse results'); 315 | } 316 | } else { 317 | console.log(`TLS handshake filter failed: ${tlsResult.content[0].text}`); 318 | } 319 | } catch (error) { 320 | console.log(`TLS handshake filter test failed: ${error.message}`); 321 | } 322 | 323 | console.log('\nIntegration tests completed successfully!'); 324 | console.log('\nTest Summary:'); 325 | console.log('- MCP server connection and communication'); 326 | console.log('- Tool discovery and listing'); 327 | console.log('- Configuration management'); 328 | console.log('- Packet capture session lifecycle'); 329 | console.log('- Network traffic generation and capture'); 330 | console.log('- Packet analysis and counting'); 331 | console.log('- PCAP file analysis with display filters'); 332 | console.log('- TLS handshake packet filtering'); 333 | console.log('- Error handling and edge cases'); 334 | 335 | return true; 336 | 337 | } catch (error) { 338 | console.error('\nIntegration test failed:'); 339 | console.error(error.message); 340 | console.error('\nStack trace:'); 341 | console.error(error.stack); 342 | return false; 343 | 344 | } finally { 345 | // Clean up 346 | if (client && transport) { 347 | try { 348 | console.log('\nCleaning up MCP connection...'); 349 | await client.close(); 350 | console.log('MCP connection closed'); 351 | } catch (error) { 352 | console.warn(`Warning during cleanup: ${error.message}`); 353 | } 354 | } 355 | } 356 | } 357 | 358 | // Run tests if this file is executed directly 359 | if (import.meta.url === `file://${process.argv[1]}`) { 360 | const success = await runIntegrationTests(); 361 | process.exit(success ? 0 : 1); 362 | } 363 | 364 | export { runIntegrationTests }; -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | '@modelcontextprotocol/sdk': 12 | specifier: ^1.12.1 13 | version: 1.12.1 14 | '@types/node': 15 | specifier: 24.0.0 16 | version: 24.0.0 17 | axios: 18 | specifier: 1.9.0 19 | version: 1.9.0 20 | which: 21 | specifier: 5.0.0 22 | version: 5.0.0 23 | zod: 24 | specifier: ^3.25.61 25 | version: 3.25.61 26 | devDependencies: 27 | '@types/which': 28 | specifier: ^3.0.4 29 | version: 3.0.4 30 | ts-node: 31 | specifier: ^10.9.2 32 | version: 10.9.2(@types/node@24.0.0)(typescript@5.8.3) 33 | tsx: 34 | specifier: ^4.20.1 35 | version: 4.20.1 36 | typescript: 37 | specifier: ^5.8.3 38 | version: 5.8.3 39 | 40 | packages: 41 | 42 | '@cspotcode/source-map-support@0.8.1': 43 | resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} 44 | engines: {node: '>=12'} 45 | 46 | '@esbuild/aix-ppc64@0.25.5': 47 | resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} 48 | engines: {node: '>=18'} 49 | cpu: [ppc64] 50 | os: [aix] 51 | 52 | '@esbuild/android-arm64@0.25.5': 53 | resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} 54 | engines: {node: '>=18'} 55 | cpu: [arm64] 56 | os: [android] 57 | 58 | '@esbuild/android-arm@0.25.5': 59 | resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} 60 | engines: {node: '>=18'} 61 | cpu: [arm] 62 | os: [android] 63 | 64 | '@esbuild/android-x64@0.25.5': 65 | resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} 66 | engines: {node: '>=18'} 67 | cpu: [x64] 68 | os: [android] 69 | 70 | '@esbuild/darwin-arm64@0.25.5': 71 | resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} 72 | engines: {node: '>=18'} 73 | cpu: [arm64] 74 | os: [darwin] 75 | 76 | '@esbuild/darwin-x64@0.25.5': 77 | resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} 78 | engines: {node: '>=18'} 79 | cpu: [x64] 80 | os: [darwin] 81 | 82 | '@esbuild/freebsd-arm64@0.25.5': 83 | resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} 84 | engines: {node: '>=18'} 85 | cpu: [arm64] 86 | os: [freebsd] 87 | 88 | '@esbuild/freebsd-x64@0.25.5': 89 | resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} 90 | engines: {node: '>=18'} 91 | cpu: [x64] 92 | os: [freebsd] 93 | 94 | '@esbuild/linux-arm64@0.25.5': 95 | resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} 96 | engines: {node: '>=18'} 97 | cpu: [arm64] 98 | os: [linux] 99 | 100 | '@esbuild/linux-arm@0.25.5': 101 | resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} 102 | engines: {node: '>=18'} 103 | cpu: [arm] 104 | os: [linux] 105 | 106 | '@esbuild/linux-ia32@0.25.5': 107 | resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} 108 | engines: {node: '>=18'} 109 | cpu: [ia32] 110 | os: [linux] 111 | 112 | '@esbuild/linux-loong64@0.25.5': 113 | resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} 114 | engines: {node: '>=18'} 115 | cpu: [loong64] 116 | os: [linux] 117 | 118 | '@esbuild/linux-mips64el@0.25.5': 119 | resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} 120 | engines: {node: '>=18'} 121 | cpu: [mips64el] 122 | os: [linux] 123 | 124 | '@esbuild/linux-ppc64@0.25.5': 125 | resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} 126 | engines: {node: '>=18'} 127 | cpu: [ppc64] 128 | os: [linux] 129 | 130 | '@esbuild/linux-riscv64@0.25.5': 131 | resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} 132 | engines: {node: '>=18'} 133 | cpu: [riscv64] 134 | os: [linux] 135 | 136 | '@esbuild/linux-s390x@0.25.5': 137 | resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} 138 | engines: {node: '>=18'} 139 | cpu: [s390x] 140 | os: [linux] 141 | 142 | '@esbuild/linux-x64@0.25.5': 143 | resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} 144 | engines: {node: '>=18'} 145 | cpu: [x64] 146 | os: [linux] 147 | 148 | '@esbuild/netbsd-arm64@0.25.5': 149 | resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} 150 | engines: {node: '>=18'} 151 | cpu: [arm64] 152 | os: [netbsd] 153 | 154 | '@esbuild/netbsd-x64@0.25.5': 155 | resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} 156 | engines: {node: '>=18'} 157 | cpu: [x64] 158 | os: [netbsd] 159 | 160 | '@esbuild/openbsd-arm64@0.25.5': 161 | resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} 162 | engines: {node: '>=18'} 163 | cpu: [arm64] 164 | os: [openbsd] 165 | 166 | '@esbuild/openbsd-x64@0.25.5': 167 | resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} 168 | engines: {node: '>=18'} 169 | cpu: [x64] 170 | os: [openbsd] 171 | 172 | '@esbuild/sunos-x64@0.25.5': 173 | resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} 174 | engines: {node: '>=18'} 175 | cpu: [x64] 176 | os: [sunos] 177 | 178 | '@esbuild/win32-arm64@0.25.5': 179 | resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} 180 | engines: {node: '>=18'} 181 | cpu: [arm64] 182 | os: [win32] 183 | 184 | '@esbuild/win32-ia32@0.25.5': 185 | resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} 186 | engines: {node: '>=18'} 187 | cpu: [ia32] 188 | os: [win32] 189 | 190 | '@esbuild/win32-x64@0.25.5': 191 | resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} 192 | engines: {node: '>=18'} 193 | cpu: [x64] 194 | os: [win32] 195 | 196 | '@jridgewell/resolve-uri@3.1.2': 197 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 198 | engines: {node: '>=6.0.0'} 199 | 200 | '@jridgewell/sourcemap-codec@1.5.0': 201 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 202 | 203 | '@jridgewell/trace-mapping@0.3.9': 204 | resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} 205 | 206 | '@modelcontextprotocol/sdk@1.12.1': 207 | resolution: {integrity: sha512-KG1CZhZfWg+u8pxeM/mByJDScJSrjjxLc8fwQqbsS8xCjBmQfMNEBTotYdNanKekepnfRI85GtgQlctLFpcYPw==} 208 | engines: {node: '>=18'} 209 | 210 | '@tsconfig/node10@1.0.11': 211 | resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} 212 | 213 | '@tsconfig/node12@1.0.11': 214 | resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} 215 | 216 | '@tsconfig/node14@1.0.3': 217 | resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} 218 | 219 | '@tsconfig/node16@1.0.4': 220 | resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} 221 | 222 | '@types/node@24.0.0': 223 | resolution: {integrity: sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg==} 224 | 225 | '@types/which@3.0.4': 226 | resolution: {integrity: sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w==} 227 | 228 | accepts@2.0.0: 229 | resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} 230 | engines: {node: '>= 0.6'} 231 | 232 | acorn-walk@8.3.4: 233 | resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} 234 | engines: {node: '>=0.4.0'} 235 | 236 | acorn@8.15.0: 237 | resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} 238 | engines: {node: '>=0.4.0'} 239 | hasBin: true 240 | 241 | ajv@6.12.6: 242 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 243 | 244 | arg@4.1.3: 245 | resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} 246 | 247 | asynckit@0.4.0: 248 | resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} 249 | 250 | axios@1.9.0: 251 | resolution: {integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==} 252 | 253 | body-parser@2.2.0: 254 | resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} 255 | engines: {node: '>=18'} 256 | 257 | bytes@3.1.2: 258 | resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} 259 | engines: {node: '>= 0.8'} 260 | 261 | call-bind-apply-helpers@1.0.2: 262 | resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} 263 | engines: {node: '>= 0.4'} 264 | 265 | call-bound@1.0.4: 266 | resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} 267 | engines: {node: '>= 0.4'} 268 | 269 | combined-stream@1.0.8: 270 | resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} 271 | engines: {node: '>= 0.8'} 272 | 273 | content-disposition@1.0.0: 274 | resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} 275 | engines: {node: '>= 0.6'} 276 | 277 | content-type@1.0.5: 278 | resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} 279 | engines: {node: '>= 0.6'} 280 | 281 | cookie-signature@1.2.2: 282 | resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} 283 | engines: {node: '>=6.6.0'} 284 | 285 | cookie@0.7.2: 286 | resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} 287 | engines: {node: '>= 0.6'} 288 | 289 | cors@2.8.5: 290 | resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} 291 | engines: {node: '>= 0.10'} 292 | 293 | create-require@1.1.1: 294 | resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} 295 | 296 | cross-spawn@7.0.6: 297 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 298 | engines: {node: '>= 8'} 299 | 300 | debug@4.4.1: 301 | resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} 302 | engines: {node: '>=6.0'} 303 | peerDependencies: 304 | supports-color: '*' 305 | peerDependenciesMeta: 306 | supports-color: 307 | optional: true 308 | 309 | delayed-stream@1.0.0: 310 | resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} 311 | engines: {node: '>=0.4.0'} 312 | 313 | depd@2.0.0: 314 | resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} 315 | engines: {node: '>= 0.8'} 316 | 317 | diff@4.0.2: 318 | resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} 319 | engines: {node: '>=0.3.1'} 320 | 321 | dunder-proto@1.0.1: 322 | resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} 323 | engines: {node: '>= 0.4'} 324 | 325 | ee-first@1.1.1: 326 | resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} 327 | 328 | encodeurl@2.0.0: 329 | resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} 330 | engines: {node: '>= 0.8'} 331 | 332 | es-define-property@1.0.1: 333 | resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} 334 | engines: {node: '>= 0.4'} 335 | 336 | es-errors@1.3.0: 337 | resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} 338 | engines: {node: '>= 0.4'} 339 | 340 | es-object-atoms@1.1.1: 341 | resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} 342 | engines: {node: '>= 0.4'} 343 | 344 | es-set-tostringtag@2.1.0: 345 | resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} 346 | engines: {node: '>= 0.4'} 347 | 348 | esbuild@0.25.5: 349 | resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} 350 | engines: {node: '>=18'} 351 | hasBin: true 352 | 353 | escape-html@1.0.3: 354 | resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} 355 | 356 | etag@1.8.1: 357 | resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} 358 | engines: {node: '>= 0.6'} 359 | 360 | eventsource-parser@3.0.2: 361 | resolution: {integrity: sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==} 362 | engines: {node: '>=18.0.0'} 363 | 364 | eventsource@3.0.7: 365 | resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} 366 | engines: {node: '>=18.0.0'} 367 | 368 | express-rate-limit@7.5.0: 369 | resolution: {integrity: sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==} 370 | engines: {node: '>= 16'} 371 | peerDependencies: 372 | express: ^4.11 || 5 || ^5.0.0-beta.1 373 | 374 | express@5.1.0: 375 | resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} 376 | engines: {node: '>= 18'} 377 | 378 | fast-deep-equal@3.1.3: 379 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 380 | 381 | fast-json-stable-stringify@2.1.0: 382 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 383 | 384 | finalhandler@2.1.0: 385 | resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} 386 | engines: {node: '>= 0.8'} 387 | 388 | follow-redirects@1.15.9: 389 | resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} 390 | engines: {node: '>=4.0'} 391 | peerDependencies: 392 | debug: '*' 393 | peerDependenciesMeta: 394 | debug: 395 | optional: true 396 | 397 | form-data@4.0.3: 398 | resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==} 399 | engines: {node: '>= 6'} 400 | 401 | forwarded@0.2.0: 402 | resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} 403 | engines: {node: '>= 0.6'} 404 | 405 | fresh@2.0.0: 406 | resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} 407 | engines: {node: '>= 0.8'} 408 | 409 | fsevents@2.3.3: 410 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 411 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 412 | os: [darwin] 413 | 414 | function-bind@1.1.2: 415 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 416 | 417 | get-intrinsic@1.3.0: 418 | resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} 419 | engines: {node: '>= 0.4'} 420 | 421 | get-proto@1.0.1: 422 | resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} 423 | engines: {node: '>= 0.4'} 424 | 425 | get-tsconfig@4.10.1: 426 | resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} 427 | 428 | gopd@1.2.0: 429 | resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} 430 | engines: {node: '>= 0.4'} 431 | 432 | has-symbols@1.1.0: 433 | resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} 434 | engines: {node: '>= 0.4'} 435 | 436 | has-tostringtag@1.0.2: 437 | resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} 438 | engines: {node: '>= 0.4'} 439 | 440 | hasown@2.0.2: 441 | resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 442 | engines: {node: '>= 0.4'} 443 | 444 | http-errors@2.0.0: 445 | resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} 446 | engines: {node: '>= 0.8'} 447 | 448 | iconv-lite@0.6.3: 449 | resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} 450 | engines: {node: '>=0.10.0'} 451 | 452 | inherits@2.0.4: 453 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 454 | 455 | ipaddr.js@1.9.1: 456 | resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} 457 | engines: {node: '>= 0.10'} 458 | 459 | is-promise@4.0.0: 460 | resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} 461 | 462 | isexe@2.0.0: 463 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 464 | 465 | isexe@3.1.1: 466 | resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} 467 | engines: {node: '>=16'} 468 | 469 | json-schema-traverse@0.4.1: 470 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 471 | 472 | make-error@1.3.6: 473 | resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} 474 | 475 | math-intrinsics@1.1.0: 476 | resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} 477 | engines: {node: '>= 0.4'} 478 | 479 | media-typer@1.1.0: 480 | resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} 481 | engines: {node: '>= 0.8'} 482 | 483 | merge-descriptors@2.0.0: 484 | resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} 485 | engines: {node: '>=18'} 486 | 487 | mime-db@1.52.0: 488 | resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} 489 | engines: {node: '>= 0.6'} 490 | 491 | mime-db@1.54.0: 492 | resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} 493 | engines: {node: '>= 0.6'} 494 | 495 | mime-types@2.1.35: 496 | resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} 497 | engines: {node: '>= 0.6'} 498 | 499 | mime-types@3.0.1: 500 | resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} 501 | engines: {node: '>= 0.6'} 502 | 503 | ms@2.1.3: 504 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 505 | 506 | negotiator@1.0.0: 507 | resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} 508 | engines: {node: '>= 0.6'} 509 | 510 | object-assign@4.1.1: 511 | resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 512 | engines: {node: '>=0.10.0'} 513 | 514 | object-inspect@1.13.4: 515 | resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} 516 | engines: {node: '>= 0.4'} 517 | 518 | on-finished@2.4.1: 519 | resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} 520 | engines: {node: '>= 0.8'} 521 | 522 | once@1.4.0: 523 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 524 | 525 | parseurl@1.3.3: 526 | resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} 527 | engines: {node: '>= 0.8'} 528 | 529 | path-key@3.1.1: 530 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 531 | engines: {node: '>=8'} 532 | 533 | path-to-regexp@8.2.0: 534 | resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} 535 | engines: {node: '>=16'} 536 | 537 | pkce-challenge@5.0.0: 538 | resolution: {integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==} 539 | engines: {node: '>=16.20.0'} 540 | 541 | proxy-addr@2.0.7: 542 | resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} 543 | engines: {node: '>= 0.10'} 544 | 545 | proxy-from-env@1.1.0: 546 | resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} 547 | 548 | punycode@2.3.1: 549 | resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 550 | engines: {node: '>=6'} 551 | 552 | qs@6.14.0: 553 | resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} 554 | engines: {node: '>=0.6'} 555 | 556 | range-parser@1.2.1: 557 | resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} 558 | engines: {node: '>= 0.6'} 559 | 560 | raw-body@3.0.0: 561 | resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} 562 | engines: {node: '>= 0.8'} 563 | 564 | resolve-pkg-maps@1.0.0: 565 | resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} 566 | 567 | router@2.2.0: 568 | resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} 569 | engines: {node: '>= 18'} 570 | 571 | safe-buffer@5.2.1: 572 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 573 | 574 | safer-buffer@2.1.2: 575 | resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 576 | 577 | send@1.2.0: 578 | resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} 579 | engines: {node: '>= 18'} 580 | 581 | serve-static@2.2.0: 582 | resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} 583 | engines: {node: '>= 18'} 584 | 585 | setprototypeof@1.2.0: 586 | resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} 587 | 588 | shebang-command@2.0.0: 589 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 590 | engines: {node: '>=8'} 591 | 592 | shebang-regex@3.0.0: 593 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 594 | engines: {node: '>=8'} 595 | 596 | side-channel-list@1.0.0: 597 | resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} 598 | engines: {node: '>= 0.4'} 599 | 600 | side-channel-map@1.0.1: 601 | resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} 602 | engines: {node: '>= 0.4'} 603 | 604 | side-channel-weakmap@1.0.2: 605 | resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} 606 | engines: {node: '>= 0.4'} 607 | 608 | side-channel@1.1.0: 609 | resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} 610 | engines: {node: '>= 0.4'} 611 | 612 | statuses@2.0.1: 613 | resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} 614 | engines: {node: '>= 0.8'} 615 | 616 | statuses@2.0.2: 617 | resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} 618 | engines: {node: '>= 0.8'} 619 | 620 | toidentifier@1.0.1: 621 | resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} 622 | engines: {node: '>=0.6'} 623 | 624 | ts-node@10.9.2: 625 | resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} 626 | hasBin: true 627 | peerDependencies: 628 | '@swc/core': '>=1.2.50' 629 | '@swc/wasm': '>=1.2.50' 630 | '@types/node': '*' 631 | typescript: '>=2.7' 632 | peerDependenciesMeta: 633 | '@swc/core': 634 | optional: true 635 | '@swc/wasm': 636 | optional: true 637 | 638 | tsx@4.20.1: 639 | resolution: {integrity: sha512-JsFUnMHIE+g8KllOvWTrSOwCKM10xLcsesvUQR61znsbrcwZ4U/QaqdymmvTqG5GMD7k2VFv9UG35C4dRy34Ag==} 640 | engines: {node: '>=18.0.0'} 641 | hasBin: true 642 | 643 | type-is@2.0.1: 644 | resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} 645 | engines: {node: '>= 0.6'} 646 | 647 | typescript@5.8.3: 648 | resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} 649 | engines: {node: '>=14.17'} 650 | hasBin: true 651 | 652 | undici-types@7.8.0: 653 | resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} 654 | 655 | unpipe@1.0.0: 656 | resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} 657 | engines: {node: '>= 0.8'} 658 | 659 | uri-js@4.4.1: 660 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 661 | 662 | v8-compile-cache-lib@3.0.1: 663 | resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} 664 | 665 | vary@1.1.2: 666 | resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} 667 | engines: {node: '>= 0.8'} 668 | 669 | which@2.0.2: 670 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 671 | engines: {node: '>= 8'} 672 | hasBin: true 673 | 674 | which@5.0.0: 675 | resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} 676 | engines: {node: ^18.17.0 || >=20.5.0} 677 | hasBin: true 678 | 679 | wrappy@1.0.2: 680 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 681 | 682 | yn@3.1.1: 683 | resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} 684 | engines: {node: '>=6'} 685 | 686 | zod-to-json-schema@3.24.5: 687 | resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} 688 | peerDependencies: 689 | zod: ^3.24.1 690 | 691 | zod@3.25.61: 692 | resolution: {integrity: sha512-fzfJgUw78LTNnHujj9re1Ov/JJQkRZZGDMcYqSx7Hp4rPOkKywaFHq0S6GoHeXs0wGNE/sIOutkXgnwzrVOGCQ==} 693 | 694 | snapshots: 695 | 696 | '@cspotcode/source-map-support@0.8.1': 697 | dependencies: 698 | '@jridgewell/trace-mapping': 0.3.9 699 | 700 | '@esbuild/aix-ppc64@0.25.5': 701 | optional: true 702 | 703 | '@esbuild/android-arm64@0.25.5': 704 | optional: true 705 | 706 | '@esbuild/android-arm@0.25.5': 707 | optional: true 708 | 709 | '@esbuild/android-x64@0.25.5': 710 | optional: true 711 | 712 | '@esbuild/darwin-arm64@0.25.5': 713 | optional: true 714 | 715 | '@esbuild/darwin-x64@0.25.5': 716 | optional: true 717 | 718 | '@esbuild/freebsd-arm64@0.25.5': 719 | optional: true 720 | 721 | '@esbuild/freebsd-x64@0.25.5': 722 | optional: true 723 | 724 | '@esbuild/linux-arm64@0.25.5': 725 | optional: true 726 | 727 | '@esbuild/linux-arm@0.25.5': 728 | optional: true 729 | 730 | '@esbuild/linux-ia32@0.25.5': 731 | optional: true 732 | 733 | '@esbuild/linux-loong64@0.25.5': 734 | optional: true 735 | 736 | '@esbuild/linux-mips64el@0.25.5': 737 | optional: true 738 | 739 | '@esbuild/linux-ppc64@0.25.5': 740 | optional: true 741 | 742 | '@esbuild/linux-riscv64@0.25.5': 743 | optional: true 744 | 745 | '@esbuild/linux-s390x@0.25.5': 746 | optional: true 747 | 748 | '@esbuild/linux-x64@0.25.5': 749 | optional: true 750 | 751 | '@esbuild/netbsd-arm64@0.25.5': 752 | optional: true 753 | 754 | '@esbuild/netbsd-x64@0.25.5': 755 | optional: true 756 | 757 | '@esbuild/openbsd-arm64@0.25.5': 758 | optional: true 759 | 760 | '@esbuild/openbsd-x64@0.25.5': 761 | optional: true 762 | 763 | '@esbuild/sunos-x64@0.25.5': 764 | optional: true 765 | 766 | '@esbuild/win32-arm64@0.25.5': 767 | optional: true 768 | 769 | '@esbuild/win32-ia32@0.25.5': 770 | optional: true 771 | 772 | '@esbuild/win32-x64@0.25.5': 773 | optional: true 774 | 775 | '@jridgewell/resolve-uri@3.1.2': {} 776 | 777 | '@jridgewell/sourcemap-codec@1.5.0': {} 778 | 779 | '@jridgewell/trace-mapping@0.3.9': 780 | dependencies: 781 | '@jridgewell/resolve-uri': 3.1.2 782 | '@jridgewell/sourcemap-codec': 1.5.0 783 | 784 | '@modelcontextprotocol/sdk@1.12.1': 785 | dependencies: 786 | ajv: 6.12.6 787 | content-type: 1.0.5 788 | cors: 2.8.5 789 | cross-spawn: 7.0.6 790 | eventsource: 3.0.7 791 | express: 5.1.0 792 | express-rate-limit: 7.5.0(express@5.1.0) 793 | pkce-challenge: 5.0.0 794 | raw-body: 3.0.0 795 | zod: 3.25.61 796 | zod-to-json-schema: 3.24.5(zod@3.25.61) 797 | transitivePeerDependencies: 798 | - supports-color 799 | 800 | '@tsconfig/node10@1.0.11': {} 801 | 802 | '@tsconfig/node12@1.0.11': {} 803 | 804 | '@tsconfig/node14@1.0.3': {} 805 | 806 | '@tsconfig/node16@1.0.4': {} 807 | 808 | '@types/node@24.0.0': 809 | dependencies: 810 | undici-types: 7.8.0 811 | 812 | '@types/which@3.0.4': {} 813 | 814 | accepts@2.0.0: 815 | dependencies: 816 | mime-types: 3.0.1 817 | negotiator: 1.0.0 818 | 819 | acorn-walk@8.3.4: 820 | dependencies: 821 | acorn: 8.15.0 822 | 823 | acorn@8.15.0: {} 824 | 825 | ajv@6.12.6: 826 | dependencies: 827 | fast-deep-equal: 3.1.3 828 | fast-json-stable-stringify: 2.1.0 829 | json-schema-traverse: 0.4.1 830 | uri-js: 4.4.1 831 | 832 | arg@4.1.3: {} 833 | 834 | asynckit@0.4.0: {} 835 | 836 | axios@1.9.0: 837 | dependencies: 838 | follow-redirects: 1.15.9 839 | form-data: 4.0.3 840 | proxy-from-env: 1.1.0 841 | transitivePeerDependencies: 842 | - debug 843 | 844 | body-parser@2.2.0: 845 | dependencies: 846 | bytes: 3.1.2 847 | content-type: 1.0.5 848 | debug: 4.4.1 849 | http-errors: 2.0.0 850 | iconv-lite: 0.6.3 851 | on-finished: 2.4.1 852 | qs: 6.14.0 853 | raw-body: 3.0.0 854 | type-is: 2.0.1 855 | transitivePeerDependencies: 856 | - supports-color 857 | 858 | bytes@3.1.2: {} 859 | 860 | call-bind-apply-helpers@1.0.2: 861 | dependencies: 862 | es-errors: 1.3.0 863 | function-bind: 1.1.2 864 | 865 | call-bound@1.0.4: 866 | dependencies: 867 | call-bind-apply-helpers: 1.0.2 868 | get-intrinsic: 1.3.0 869 | 870 | combined-stream@1.0.8: 871 | dependencies: 872 | delayed-stream: 1.0.0 873 | 874 | content-disposition@1.0.0: 875 | dependencies: 876 | safe-buffer: 5.2.1 877 | 878 | content-type@1.0.5: {} 879 | 880 | cookie-signature@1.2.2: {} 881 | 882 | cookie@0.7.2: {} 883 | 884 | cors@2.8.5: 885 | dependencies: 886 | object-assign: 4.1.1 887 | vary: 1.1.2 888 | 889 | create-require@1.1.1: {} 890 | 891 | cross-spawn@7.0.6: 892 | dependencies: 893 | path-key: 3.1.1 894 | shebang-command: 2.0.0 895 | which: 2.0.2 896 | 897 | debug@4.4.1: 898 | dependencies: 899 | ms: 2.1.3 900 | 901 | delayed-stream@1.0.0: {} 902 | 903 | depd@2.0.0: {} 904 | 905 | diff@4.0.2: {} 906 | 907 | dunder-proto@1.0.1: 908 | dependencies: 909 | call-bind-apply-helpers: 1.0.2 910 | es-errors: 1.3.0 911 | gopd: 1.2.0 912 | 913 | ee-first@1.1.1: {} 914 | 915 | encodeurl@2.0.0: {} 916 | 917 | es-define-property@1.0.1: {} 918 | 919 | es-errors@1.3.0: {} 920 | 921 | es-object-atoms@1.1.1: 922 | dependencies: 923 | es-errors: 1.3.0 924 | 925 | es-set-tostringtag@2.1.0: 926 | dependencies: 927 | es-errors: 1.3.0 928 | get-intrinsic: 1.3.0 929 | has-tostringtag: 1.0.2 930 | hasown: 2.0.2 931 | 932 | esbuild@0.25.5: 933 | optionalDependencies: 934 | '@esbuild/aix-ppc64': 0.25.5 935 | '@esbuild/android-arm': 0.25.5 936 | '@esbuild/android-arm64': 0.25.5 937 | '@esbuild/android-x64': 0.25.5 938 | '@esbuild/darwin-arm64': 0.25.5 939 | '@esbuild/darwin-x64': 0.25.5 940 | '@esbuild/freebsd-arm64': 0.25.5 941 | '@esbuild/freebsd-x64': 0.25.5 942 | '@esbuild/linux-arm': 0.25.5 943 | '@esbuild/linux-arm64': 0.25.5 944 | '@esbuild/linux-ia32': 0.25.5 945 | '@esbuild/linux-loong64': 0.25.5 946 | '@esbuild/linux-mips64el': 0.25.5 947 | '@esbuild/linux-ppc64': 0.25.5 948 | '@esbuild/linux-riscv64': 0.25.5 949 | '@esbuild/linux-s390x': 0.25.5 950 | '@esbuild/linux-x64': 0.25.5 951 | '@esbuild/netbsd-arm64': 0.25.5 952 | '@esbuild/netbsd-x64': 0.25.5 953 | '@esbuild/openbsd-arm64': 0.25.5 954 | '@esbuild/openbsd-x64': 0.25.5 955 | '@esbuild/sunos-x64': 0.25.5 956 | '@esbuild/win32-arm64': 0.25.5 957 | '@esbuild/win32-ia32': 0.25.5 958 | '@esbuild/win32-x64': 0.25.5 959 | 960 | escape-html@1.0.3: {} 961 | 962 | etag@1.8.1: {} 963 | 964 | eventsource-parser@3.0.2: {} 965 | 966 | eventsource@3.0.7: 967 | dependencies: 968 | eventsource-parser: 3.0.2 969 | 970 | express-rate-limit@7.5.0(express@5.1.0): 971 | dependencies: 972 | express: 5.1.0 973 | 974 | express@5.1.0: 975 | dependencies: 976 | accepts: 2.0.0 977 | body-parser: 2.2.0 978 | content-disposition: 1.0.0 979 | content-type: 1.0.5 980 | cookie: 0.7.2 981 | cookie-signature: 1.2.2 982 | debug: 4.4.1 983 | encodeurl: 2.0.0 984 | escape-html: 1.0.3 985 | etag: 1.8.1 986 | finalhandler: 2.1.0 987 | fresh: 2.0.0 988 | http-errors: 2.0.0 989 | merge-descriptors: 2.0.0 990 | mime-types: 3.0.1 991 | on-finished: 2.4.1 992 | once: 1.4.0 993 | parseurl: 1.3.3 994 | proxy-addr: 2.0.7 995 | qs: 6.14.0 996 | range-parser: 1.2.1 997 | router: 2.2.0 998 | send: 1.2.0 999 | serve-static: 2.2.0 1000 | statuses: 2.0.2 1001 | type-is: 2.0.1 1002 | vary: 1.1.2 1003 | transitivePeerDependencies: 1004 | - supports-color 1005 | 1006 | fast-deep-equal@3.1.3: {} 1007 | 1008 | fast-json-stable-stringify@2.1.0: {} 1009 | 1010 | finalhandler@2.1.0: 1011 | dependencies: 1012 | debug: 4.4.1 1013 | encodeurl: 2.0.0 1014 | escape-html: 1.0.3 1015 | on-finished: 2.4.1 1016 | parseurl: 1.3.3 1017 | statuses: 2.0.2 1018 | transitivePeerDependencies: 1019 | - supports-color 1020 | 1021 | follow-redirects@1.15.9: {} 1022 | 1023 | form-data@4.0.3: 1024 | dependencies: 1025 | asynckit: 0.4.0 1026 | combined-stream: 1.0.8 1027 | es-set-tostringtag: 2.1.0 1028 | hasown: 2.0.2 1029 | mime-types: 2.1.35 1030 | 1031 | forwarded@0.2.0: {} 1032 | 1033 | fresh@2.0.0: {} 1034 | 1035 | fsevents@2.3.3: 1036 | optional: true 1037 | 1038 | function-bind@1.1.2: {} 1039 | 1040 | get-intrinsic@1.3.0: 1041 | dependencies: 1042 | call-bind-apply-helpers: 1.0.2 1043 | es-define-property: 1.0.1 1044 | es-errors: 1.3.0 1045 | es-object-atoms: 1.1.1 1046 | function-bind: 1.1.2 1047 | get-proto: 1.0.1 1048 | gopd: 1.2.0 1049 | has-symbols: 1.1.0 1050 | hasown: 2.0.2 1051 | math-intrinsics: 1.1.0 1052 | 1053 | get-proto@1.0.1: 1054 | dependencies: 1055 | dunder-proto: 1.0.1 1056 | es-object-atoms: 1.1.1 1057 | 1058 | get-tsconfig@4.10.1: 1059 | dependencies: 1060 | resolve-pkg-maps: 1.0.0 1061 | 1062 | gopd@1.2.0: {} 1063 | 1064 | has-symbols@1.1.0: {} 1065 | 1066 | has-tostringtag@1.0.2: 1067 | dependencies: 1068 | has-symbols: 1.1.0 1069 | 1070 | hasown@2.0.2: 1071 | dependencies: 1072 | function-bind: 1.1.2 1073 | 1074 | http-errors@2.0.0: 1075 | dependencies: 1076 | depd: 2.0.0 1077 | inherits: 2.0.4 1078 | setprototypeof: 1.2.0 1079 | statuses: 2.0.1 1080 | toidentifier: 1.0.1 1081 | 1082 | iconv-lite@0.6.3: 1083 | dependencies: 1084 | safer-buffer: 2.1.2 1085 | 1086 | inherits@2.0.4: {} 1087 | 1088 | ipaddr.js@1.9.1: {} 1089 | 1090 | is-promise@4.0.0: {} 1091 | 1092 | isexe@2.0.0: {} 1093 | 1094 | isexe@3.1.1: {} 1095 | 1096 | json-schema-traverse@0.4.1: {} 1097 | 1098 | make-error@1.3.6: {} 1099 | 1100 | math-intrinsics@1.1.0: {} 1101 | 1102 | media-typer@1.1.0: {} 1103 | 1104 | merge-descriptors@2.0.0: {} 1105 | 1106 | mime-db@1.52.0: {} 1107 | 1108 | mime-db@1.54.0: {} 1109 | 1110 | mime-types@2.1.35: 1111 | dependencies: 1112 | mime-db: 1.52.0 1113 | 1114 | mime-types@3.0.1: 1115 | dependencies: 1116 | mime-db: 1.54.0 1117 | 1118 | ms@2.1.3: {} 1119 | 1120 | negotiator@1.0.0: {} 1121 | 1122 | object-assign@4.1.1: {} 1123 | 1124 | object-inspect@1.13.4: {} 1125 | 1126 | on-finished@2.4.1: 1127 | dependencies: 1128 | ee-first: 1.1.1 1129 | 1130 | once@1.4.0: 1131 | dependencies: 1132 | wrappy: 1.0.2 1133 | 1134 | parseurl@1.3.3: {} 1135 | 1136 | path-key@3.1.1: {} 1137 | 1138 | path-to-regexp@8.2.0: {} 1139 | 1140 | pkce-challenge@5.0.0: {} 1141 | 1142 | proxy-addr@2.0.7: 1143 | dependencies: 1144 | forwarded: 0.2.0 1145 | ipaddr.js: 1.9.1 1146 | 1147 | proxy-from-env@1.1.0: {} 1148 | 1149 | punycode@2.3.1: {} 1150 | 1151 | qs@6.14.0: 1152 | dependencies: 1153 | side-channel: 1.1.0 1154 | 1155 | range-parser@1.2.1: {} 1156 | 1157 | raw-body@3.0.0: 1158 | dependencies: 1159 | bytes: 3.1.2 1160 | http-errors: 2.0.0 1161 | iconv-lite: 0.6.3 1162 | unpipe: 1.0.0 1163 | 1164 | resolve-pkg-maps@1.0.0: {} 1165 | 1166 | router@2.2.0: 1167 | dependencies: 1168 | debug: 4.4.1 1169 | depd: 2.0.0 1170 | is-promise: 4.0.0 1171 | parseurl: 1.3.3 1172 | path-to-regexp: 8.2.0 1173 | transitivePeerDependencies: 1174 | - supports-color 1175 | 1176 | safe-buffer@5.2.1: {} 1177 | 1178 | safer-buffer@2.1.2: {} 1179 | 1180 | send@1.2.0: 1181 | dependencies: 1182 | debug: 4.4.1 1183 | encodeurl: 2.0.0 1184 | escape-html: 1.0.3 1185 | etag: 1.8.1 1186 | fresh: 2.0.0 1187 | http-errors: 2.0.0 1188 | mime-types: 3.0.1 1189 | ms: 2.1.3 1190 | on-finished: 2.4.1 1191 | range-parser: 1.2.1 1192 | statuses: 2.0.2 1193 | transitivePeerDependencies: 1194 | - supports-color 1195 | 1196 | serve-static@2.2.0: 1197 | dependencies: 1198 | encodeurl: 2.0.0 1199 | escape-html: 1.0.3 1200 | parseurl: 1.3.3 1201 | send: 1.2.0 1202 | transitivePeerDependencies: 1203 | - supports-color 1204 | 1205 | setprototypeof@1.2.0: {} 1206 | 1207 | shebang-command@2.0.0: 1208 | dependencies: 1209 | shebang-regex: 3.0.0 1210 | 1211 | shebang-regex@3.0.0: {} 1212 | 1213 | side-channel-list@1.0.0: 1214 | dependencies: 1215 | es-errors: 1.3.0 1216 | object-inspect: 1.13.4 1217 | 1218 | side-channel-map@1.0.1: 1219 | dependencies: 1220 | call-bound: 1.0.4 1221 | es-errors: 1.3.0 1222 | get-intrinsic: 1.3.0 1223 | object-inspect: 1.13.4 1224 | 1225 | side-channel-weakmap@1.0.2: 1226 | dependencies: 1227 | call-bound: 1.0.4 1228 | es-errors: 1.3.0 1229 | get-intrinsic: 1.3.0 1230 | object-inspect: 1.13.4 1231 | side-channel-map: 1.0.1 1232 | 1233 | side-channel@1.1.0: 1234 | dependencies: 1235 | es-errors: 1.3.0 1236 | object-inspect: 1.13.4 1237 | side-channel-list: 1.0.0 1238 | side-channel-map: 1.0.1 1239 | side-channel-weakmap: 1.0.2 1240 | 1241 | statuses@2.0.1: {} 1242 | 1243 | statuses@2.0.2: {} 1244 | 1245 | toidentifier@1.0.1: {} 1246 | 1247 | ts-node@10.9.2(@types/node@24.0.0)(typescript@5.8.3): 1248 | dependencies: 1249 | '@cspotcode/source-map-support': 0.8.1 1250 | '@tsconfig/node10': 1.0.11 1251 | '@tsconfig/node12': 1.0.11 1252 | '@tsconfig/node14': 1.0.3 1253 | '@tsconfig/node16': 1.0.4 1254 | '@types/node': 24.0.0 1255 | acorn: 8.15.0 1256 | acorn-walk: 8.3.4 1257 | arg: 4.1.3 1258 | create-require: 1.1.1 1259 | diff: 4.0.2 1260 | make-error: 1.3.6 1261 | typescript: 5.8.3 1262 | v8-compile-cache-lib: 3.0.1 1263 | yn: 3.1.1 1264 | 1265 | tsx@4.20.1: 1266 | dependencies: 1267 | esbuild: 0.25.5 1268 | get-tsconfig: 4.10.1 1269 | optionalDependencies: 1270 | fsevents: 2.3.3 1271 | 1272 | type-is@2.0.1: 1273 | dependencies: 1274 | content-type: 1.0.5 1275 | media-typer: 1.1.0 1276 | mime-types: 3.0.1 1277 | 1278 | typescript@5.8.3: {} 1279 | 1280 | undici-types@7.8.0: {} 1281 | 1282 | unpipe@1.0.0: {} 1283 | 1284 | uri-js@4.4.1: 1285 | dependencies: 1286 | punycode: 2.3.1 1287 | 1288 | v8-compile-cache-lib@3.0.1: {} 1289 | 1290 | vary@1.1.2: {} 1291 | 1292 | which@2.0.2: 1293 | dependencies: 1294 | isexe: 2.0.0 1295 | 1296 | which@5.0.0: 1297 | dependencies: 1298 | isexe: 3.1.1 1299 | 1300 | wrappy@1.0.2: {} 1301 | 1302 | yn@3.1.1: {} 1303 | 1304 | zod-to-json-schema@3.24.5(zod@3.25.61): 1305 | dependencies: 1306 | zod: 3.25.61 1307 | 1308 | zod@3.25.61: {} 1309 | --------------------------------------------------------------------------------