diff --git a/.gitignore b/.gitignore index 8602dad..4a22be3 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ Desktop.ini .env .mcp_auto_setup_done + +bridge/node_modules diff --git a/README.md b/README.md index ecac33e..47e843d 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,43 @@ python scripts/mcp_client_installer.py --uninstall # remove entries and delete python scripts/mcp_client_installer.py --config # print a generic JSON config snippet ``` -For other MCP clients, this is an example config: +#### Using npm package (Recommended) + +The recommended way to set up the MCP client is using the official npm package: + +```bash +npx -y binary-ninja-mcp +``` + +For MCP clients, use this configuration: + +```json +{ + "mcpServers": { + "binary-ninja-mcp": { + "command": "npx", + "args": ["-y", "binary-ninja-mcp", "--host", "localhost", "--port", "9009"] + } + } +} +``` + +Or if installed globally: + +```json +{ + "mcpServers": { + "binary-ninja-mcp": { + "command": "binary-ninja-mcp", + "args": ["--host", "localhost", "--port", "9009"] + } + } +} +``` + +#### Using Python Bridge (Legacy) + +For other MCP clients, use the Python bridge directly: ```json { @@ -136,7 +172,7 @@ The following table lists the available MCP functions for use: | `declare_c_type(c_declaration)` | Create/update a local type from a single C declaration. | | `format_value(address, text, size)` | Convert a value and annotate it at an address in BN (adds a comment). | | `function_at` | Retrieve the name of the function the address belongs to. | -| `get_assembly_function` | Get the assembly representation of a function by name or address. | +| `fetch_disassembly` | Get the assembly representation of a function by name or address. | | `get_entry_points()` | List entry point(s) of the loaded binary. | | `get_binary_status` | Get the current status of the loaded binary. | | `get_comment` | Get the comment at a specific address. | diff --git a/bridge/README.md b/bridge/README.md new file mode 100644 index 0000000..4faed97 --- /dev/null +++ b/bridge/README.md @@ -0,0 +1,191 @@ +# Binary Ninja MCP Server (TypeScript) + +This is the TypeScript implementation of the Binary Ninja MCP bridge server. It provides a standalone MCP server that connects to a running Binary Ninja instance and exposes its capabilities through the Model Context Protocol. + +## Features + +- **Standalone MCP Server**: Run independently with any MCP client +- **50+ Tools**: Full access to Binary Ninja's reverse engineering capabilities +- **Easy Configuration**: CLI options and environment variables for host/port +- **TypeScript**: Full type safety and better developer experience + +## Installation + +### Using npx (Recommended) + +```bash +npx -y binary-ninja-mcp +``` + +### Global Installation + +```bash +npm install -g binary-ninja-mcp +binary-ninja-mcp +``` + +### From Source + +```bash +cd bridge +npm install +npm run build +``` + +## Usage + +### Command Line Options + +```bash +# Connect to default (localhost:9009) +npx -y binary-ninja-mcp + +# Connect to custom host/port +npx -y binary-ninja-mcp --host 192.168.1.100 --port 9009 + +# Show help +npx -y binary-ninja-mcp --help +``` + +### Environment Variables + +```bash +# Set host and port via environment +BINJA_MCP_HOST=localhost BINJA_MCP_PORT=9009 npx -y binary-ninja-mcp +``` + +### MCP Client Configuration + +#### Claude Desktop + +Add to `~/.config/claude-desktop/claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "binary-ninja-mcp": { + "command": "npx", + "args": ["-y", "binary-ninja-mcp", "--host", "localhost", "--port", "9009"] + } + } +} +``` + +#### Cline + +Add to your Cline MCP configuration: + +```json +{ + "mcpServers": { + "binary-ninja-mcp": { + "command": "npx", + "args": ["-y", "binary-ninja-mcp"] + } + } +} +``` + +#### Custom Installation + +If installed globally: + +```json +{ + "mcpServers": { + "binary-ninja-mcp": { + "command": "binary-ninja-mcp", + "args": ["--host", "localhost", "--port", "9009"] + } + } +} +``` + +## Available Tools + +### Function Analysis + +- `list_methods` - List all function names with pagination +- `get_entry_points` - List entry point(s) of the loaded binary +- `search_functions_by_name` - Search functions by name substring +- `decompile_function` - Decompile a function to C code +- `get_il` - Get IL (HLIL/MLIL/LLIL) for a function +- `fetch_disassembly` - Get assembly mnemonics for a function + +### Rename Tools + +- `rename_function` - Rename a function +- `rename_single_variable` - Rename a single variable +- `rename_multi_variables` - Batch rename multiple variables +- `rename_data` - Rename a data label + +### Comment Tools + +- `set_comment` - Set comment at an address +- `get_comment` - Get comment at an address +- `delete_comment` - Delete comment at an address +- `set_function_comment` - Set function comment +- `get_function_comment` - Get function comment +- `delete_function_comment` - Delete function comment + +### Type Tools + +- `define_types` - Define types from C code +- `list_local_types` - List local types +- `search_types` - Search types by name +- `get_user_defined_type` - Get user defined type definition +- `get_type_info` - Get type information +- `declare_c_type` - Declare C type +- `retype_variable` - Retype a variable +- `set_local_variable_type` - Set local variable type + +### Data Tools + +- `list_data_items` - List data labels +- `hexdump_address` - Hexdump at address +- `hexdump_data` - Hexdump data symbol +- `get_data_decl` - Get data declaration and hexdump + +### Cross-Reference Tools + +- `get_xrefs_to` - Get xrefs to address +- `get_xrefs_to_field` - Get xrefs to struct field +- `get_xrefs_to_struct` - Get xrefs to struct +- `get_xrefs_to_type` - Get xrefs to type +- `get_xrefs_to_enum` - Get xrefs to enum +- `get_xrefs_to_union` - Get xrefs to union + +### Binary Modification Tools + +- `set_function_prototype` - Set function prototype +- `make_function_at` - Create function at address +- `list_platforms` - List available platforms +- `patch_bytes` - Patch bytes in binary + +### Utility Tools + +- `function_at` - Find function at address +- `get_stack_frame_vars` - Get stack frame variables +- `list_classes` - List classes/namespaces +- `list_namespaces` - List namespaces +- `list_segments` - List memory segments +- `list_sections` - List sections +- `list_imports` - List imports +- `list_exports` - List exports +- `list_strings` - List strings +- `list_all_strings` - List all strings (aggregated) +- `get_binary_status` - Get binary status +- `list_binaries` - List open binaries +- `select_binary` - Select active binary +- `format_value` - Format and annotate value +- `convert_number` - Convert number representations + +## Requirements + +- Node.js 18.0.0 or higher +- A running Binary Ninja instance with the MCP plugin +- MCP client (Claude Desktop, Cline, etc.) + +## License + +GPL-3.0 - See LICENSE file for details. diff --git a/bridge/package-lock.json b/bridge/package-lock.json new file mode 100644 index 0000000..de8c24c --- /dev/null +++ b/bridge/package-lock.json @@ -0,0 +1,1840 @@ +{ + "name": "@binary-ninja/mcp-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@binary-ninja/mcp-server", + "version": "1.0.0", + "license": "GPL-3.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.0", + "axios": "^1.7.0", + "zod": "^3.25.76" + }, + "bin": { + "binary-ninja-mcp": "dist/index.js" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "tsx": "^4.0.0", + "typescript": "^5.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.25.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", + "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.7", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@types/node": { + "version": "20.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", + "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz", + "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + } + } +} diff --git a/bridge/package.json b/bridge/package.json new file mode 100644 index 0000000..de4ca9a --- /dev/null +++ b/bridge/package.json @@ -0,0 +1,45 @@ +{ + "name": "binary-ninja-mcp", + "version": "1.0.0", + "description": "Model Context Protocol server for Binary Ninja - enables seamless integration of Binary Ninja's capabilities with MCP clients", + "author": "fosdickio, CX330Blake", + "license": "GPL-3.0", + "type": "module", + "bin": { + "binary-ninja-mcp": "./dist/index.js" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist/**/*" + ], + "scripts": { + "build": "tsc", + "start": "node dist/index.js", + "dev": "tsx src/index.ts", + "typecheck": "tsc --noEmit" + }, + "keywords": [ + "binary-ninja", + "mcp", + "reverse-engineering", + "model-context-protocol" + ], + "repository": { + "type": "git", + "url": "https://github.com/fosdickio/binary_ninja_mcp" + }, + "engines": { + "node": ">=18.0.0" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.0", + "axios": "^1.7.0", + "zod": "^3.25.76" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "tsx": "^4.0.0", + "typescript": "^5.0.0" + } +} diff --git a/bridge/src/client.ts b/bridge/src/client.ts new file mode 100644 index 0000000..f35c4c8 --- /dev/null +++ b/bridge/src/client.ts @@ -0,0 +1,121 @@ +/** + * HTTP client for communicating with the Binary Ninja MCP server. + */ + +import axios, { AxiosInstance, AxiosResponse } from "axios"; + +export interface BinjaServerConfig { + host: string; + port: number; +} + +export class BinjaHttpClient { + private client: AxiosInstance; + private baseUrl: string; + + constructor(config: BinjaServerConfig) { + this.baseUrl = `http://${config.host}:${config.port}`; + this.client = axios.create({ + baseURL: this.baseUrl, + timeout: 30000, // 30 seconds + }); + } + + /** + * Perform a GET request and return raw text. + */ + async getText(endpoint: string, params: Record = {}, timeout?: number): Promise { + try { + const response = await this.client.get(endpoint, { + params, + timeout, + responseType: "text", + }); + if (response.status >= 200 && response.status < 300) { + return response.data as string; + } + return `Error ${response.status}: ${response.data}`; + } catch (error) { + return this.handleError(error, "GET", endpoint); + } + } + + /** + * Perform a GET request and return parsed JSON. + */ + async getJson(endpoint: string, params: Record = {}, timeout?: number): Promise { + try { + const response = await this.client.get(endpoint, { + params, + timeout, + }); + response.data as T; + if (response.status >= 200 && response.status < 300) { + return response.data as T; + } + // Non-OK: return parsed error object if available + if (response.data && typeof response.data === "object" && "error" in response.data) { + return response.data as T; + } + return { error: `Error ${response.status}: ${response.statusText}` }; + } catch (error) { + const errorMsg = this.getErrorMessage(error); + return { error: `Request failed: ${errorMsg}` }; + } + } + + /** + * Perform a GET request and return lines of text. + */ + async getLines(endpoint: string, params: Record = {}, timeout?: number): Promise { + const text = await this.getText(endpoint, params, timeout); + return text.split("\n"); + } + + /** + * Perform a POST request. + */ + async post(endpoint: string, data: Record | string): Promise { + try { + let response: AxiosResponse; + if (typeof data === "string") { + response = await this.client.post(endpoint, data, { + headers: { "Content-Type": "text/plain" }, + responseType: "text", + }); + } else { + response = await this.client.post(endpoint, data, { + responseType: "text", + }); + } + if (response.status >= 200 && response.status < 300) { + return response.data.trim(); + } + return `Error ${response.status}: ${response.data}`; + } catch (error) { + return this.handleError(error, "POST", endpoint); + } + } + + private handleError(error: unknown, method: string, endpoint: string): string { + const msg = this.getErrorMessage(error); + return `Error: ${method} ${endpoint} failed: ${msg}`; + } + + private getErrorMessage(error: unknown): string { + if (axios.isAxiosError(error)) { + if (error.response) { + return `Server returned ${error.response.status}: ${error.response.statusText}`; + } + if (error.request) { + return "No response from server - is Binary Ninja running with the MCP plugin?"; + } + return error.message; + } + return String(error); + } +} + +export function createClient(host: string = "localhost", port: number = 9009): BinjaHttpClient { + return new BinjaHttpClient({ host, port }); +} diff --git a/bridge/src/index.ts b/bridge/src/index.ts new file mode 100644 index 0000000..ad94084 --- /dev/null +++ b/bridge/src/index.ts @@ -0,0 +1,106 @@ +#!/usr/bin/env node + +/** + * Binary Ninja MCP Server - Main entry point + * + * This MCP server connects to a running Binary Ninja instance with the MCP plugin + * and exposes its capabilities through the Model Context Protocol. + * + * Usage: + * npx @binary-ninja/mcp-server [options] + * + * Options: + * --host Binary Ninja MCP server host (default: localhost) + * --port Binary Ninja MCP server port (default: 9009) + * --help Show this help message + * + * Environment variables: + * BINJA_MCP_HOST Binary Ninja MCP server host + * BINJA_MCP_PORT Binary Ninja MCP server port + */ + +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createClient } from "./client.js"; +import { registerTools } from "./tools.js"; + +// Parse command line arguments +function parseArgs(): { host: string; port: number } { + const args = process.argv.slice(2); + let host = "localhost"; + let port = 9009; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + switch (arg) { + case "--host": + if (i + 1 < args.length) { + host = args[++i]; + } + break; + case "--port": + if (i + 1 < args.length) { + port = parseInt(args[++i], 10); + } + break; + case "--help": + case "-h": + console.log(`Binary Ninja MCP Server + +Usage: npx binary-ninja-mcp [options] + +Options: + --host Binary Ninja MCP server host (default: localhost) + --port Binary Ninja MCP server port (default: 9009) + --help, -h Show this help message + +Environment variables: + BINJA_MCP_HOST Binary Ninja MCP server host + BINJA_MCP_PORT Binary Ninja MCP server port +`); + process.exit(0); + break; + } + } + + // Environment variables override defaults + if (process.env.BINJA_MCP_HOST) { + host = process.env.BINJA_MCP_HOST; + } + if (process.env.BINJA_MCP_PORT) { + port = parseInt(process.env.BINJA_MCP_PORT, 10) || port; + } + + return { host, port }; +} + +// Main entry point +async function main(): Promise { + const { host, port } = parseArgs(); + + console.error(`Binary Ninja MCP Server connecting to ${host}:${port}`); + + // Create MCP server + const server = new McpServer({ + name: "binary-ninja-mcp", + version: "1.0.0", + }); + + // Create HTTP client for Binary Ninja + const client = createClient(host, port); + + // Register all tools + registerTools(server, client); + + // Create stdio transport + const transport = new StdioServerTransport(); + + // Connect and run + await server.connect(transport); + console.error("Binary Ninja MCP Server ready"); +} + +main().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); +}); diff --git a/bridge/src/tools.ts b/bridge/src/tools.ts new file mode 100644 index 0000000..b6ecb66 --- /dev/null +++ b/bridge/src/tools.ts @@ -0,0 +1,1098 @@ +/** + * MCP Tool definitions for Binary Ninja integration. + */ + +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { BinjaHttpClient } from "./client.js"; + +export function registerTools(server: McpServer, client: BinjaHttpClient): void { + // Helper function to get active filename + async function getActiveFilename(): Promise { + const data = await client.getJson<{ filename?: string }>("status"); + if (data && typeof data === "object" && "filename" in data) { + return (data as { filename: string }).filename || "(none)"; + } + return "(none)"; + } + + // ===== Function Analysis Tools ===== + + server.tool( + "list_methods", + "List all function names in the program with pagination.", + { + offset: z.number().default(0).describe("Offset for pagination"), + limit: z.number().default(100).describe("Number of results to return"), + }, + async ({ offset = 0, limit = 100 }) => { + const filename = await getActiveFilename(); + const lines = await client.getLines("methods", { offset, limit }); + return { + content: [{ type: "text", text: `File: ${filename}\n${lines.join("\n")}` }], + }; + } + ); + + server.tool( + "get_entry_points", + "List entry point(s) of the loaded binary.", + {}, + async () => { + const data = await client.getJson<{ entry_points?: Array<{ address: string; name?: string }> }>("entryPoints"); + if (!data || "error" in data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + const entryPoints = (data as { entry_points?: Array<{ address: string; name?: string }> }).entry_points || []; + const lines = entryPoints.map((ep) => { + const addr = ep.address; + const name = ep.name || "(unknown)"; + return `${addr}\t${name}`; + }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "search_functions_by_name", + "Search for functions whose name contains the given substring.", + { + query: z.string().describe("Search query string"), + offset: z.number().default(0).describe("Offset for pagination"), + limit: z.number().default(100).describe("Number of results to return"), + }, + async ({ query, offset = 0, limit = 100 }) => { + if (!query) { + return { content: [{ type: "text", text: "Error: query string is required" }] }; + } + const lines = await client.getLines("searchFunctions", { query, offset, limit }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "decompile_function", + "Decompile a specific function by name and return the decompiled C code.", + { + name: z.string().describe("Function name or address"), + }, + async ({ name }) => { + const filename = await getActiveFilename(); + const data = await client.getJson<{ decompiled?: string; error?: string }>("decompile", { name }); + if (!data) { + return { content: [{ type: "text", text: `File: ${filename}\n\nError: no response` }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: `File: ${filename}\n\nError: ${data.error}` }] }; + } + const decompiled = (data as { decompiled?: string }).decompiled; + return { content: [{ type: "text", text: `File: ${filename}\n\n${decompiled || ""}` }] }; + } + ); + + server.tool( + "get_il", + "Get IL for a function in the selected view (hlil, mlil, llil).", + { + name_or_address: z.string().describe("Function name or address (hex like 0x401000)"), + view: z.enum(["hlil", "mlil", "llil"]).default("hlil").describe("IL view: hlil, mlil, or llil"), + ssa: z.boolean().default(false).describe("Request SSA form (MLIL/LLIL only)"), + }, + async ({ name_or_address, view = "hlil", ssa = false }) => { + const filename = await getActiveFilename(); + const ident = name_or_address.trim(); + const params: Record = { view, ssa: ssa ? 1 : 0 }; + if (ident.toLowerCase().startsWith("0x") || /^\d+$/.test(ident)) { + params.address = ident; + } else { + params.name = ident; + } + const data = await client.getJson<{ il?: string; error?: unknown }>("il", params); + if (!data) { + return { content: [{ type: "text", text: `File: ${filename}\n\nError: no response` }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: `File: ${filename}\n\nError: ${JSON.stringify(data.error)}` }] }; + } + const il = (data as { il?: string }).il; + return { content: [{ type: "text", text: `File: ${filename}\n\n${il || ""}` }] }; + } + ); + + server.tool( + "fetch_disassembly", + "Retrieve the disassembled code of a function as assembly mnemonic instructions.", + { + name: z.string().describe("Function name"), + }, + async ({ name }) => { + const filename = await getActiveFilename(); + const data = await client.getJson<{ assembly?: string; error?: string }>("assembly", { name }); + if (!data) { + return { content: [{ type: "text", text: `File: ${filename}\n\nError: no response` }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: `File: ${filename}\n\nError: ${(data as { error: string }).error}` }] }; + } + const assembly = (data as { assembly?: string }).assembly; + return { content: [{ type: "text", text: `File: ${filename}\n\n${assembly || ""}` }] }; + } + ); + + // ===== Rename Tools ===== + + server.tool( + "rename_function", + "Rename a function by its current name. The configured prefix will be automatically prepended if not present.", + { + old_name: z.string().describe("Current function name"), + new_name: z.string().describe("New function name"), + }, + async ({ old_name, new_name }) => { + const result = await client.post("renameFunction", { oldName: old_name, newName: new_name }); + return { content: [{ type: "text", text: result }] }; + } + ); + + server.tool( + "rename_single_variable", + "Rename a variable in a function.", + { + function_name: z.string().describe("Function name"), + variable_name: z.string().describe("Current variable name"), + new_name: z.string().describe("New variable name"), + }, + async ({ function_name, variable_name, new_name }) => { + const data = await client.getJson<{ status?: string; error?: string }>("renameVariable", { + functionName: function_name, + variableName: variable_name, + newName: new_name, + }); + if (!data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + if ("status" in data) { + return { content: [{ type: "text", text: (data as { status: string }).status }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: `Error: ${(data as { error: string }).error}` }] }; + } + return { content: [{ type: "text", text: JSON.stringify(data) }] }; + } + ); + + server.tool( + "rename_multi_variables", + "Rename multiple local variables in one call.", + { + function_identifier: z.string().describe("Function name or address (hex)"), + mapping_json: z.string().optional().describe("JSON object old->new"), + pairs: z.string().optional().describe("Comma-separated old:new pairs"), + renames_json: z.string().optional().describe("JSON array of {old,new} objects"), + }, + async ({ function_identifier, mapping_json, pairs, renames_json }) => { + const ident = function_identifier.trim(); + const params: Record = {}; + if (ident.toLowerCase().startsWith("0x") || /^\d+$/.test(ident)) { + params.address = ident; + } else { + params.functionName = ident; + } + + if (renames_json) { + try { + JSON.parse(renames_json); + params.renames = renames_json; + } catch { + return { content: [{ type: "text", text: "Error: renames_json is not valid JSON" }] }; + } + } else if (mapping_json) { + try { + JSON.parse(mapping_json); + params.mapping = mapping_json; + } catch { + return { content: [{ type: "text", text: "Error: mapping_json is not valid JSON" }] }; + } + } else if (pairs) { + params.pairs = pairs; + } else { + return { content: [{ type: "text", text: "Error: provide mapping_json, renames_json, or pairs" }] }; + } + + const data = await client.getJson<{ error?: string; total?: number; renamed?: number }>("renameVariables", params); + if (!data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: `Error: ${(data as { error: string }).error}` }] }; + } + const total = (data as { total?: number }).total; + const renamed = (data as { renamed?: number }).renamed; + return { content: [{ type: "text", text: `Batch rename: ${renamed}/${total} applied` }] }; + } + ); + + server.tool( + "rename_data", + "Rename a data label at the specified address.", + { + address: z.string().describe("Address (hex like 0x401000)"), + new_name: z.string().describe("New name for the data label"), + }, + async ({ address, new_name }) => { + const result = await client.post("renameData", { address, newName: new_name }); + return { content: [{ type: "text", text: result }] }; + } + ); + + // ===== Comment Tools ===== + + server.tool( + "set_comment", + "Set a comment at a specific address.", + { + address: z.string().describe("Address (hex like 0x401000)"), + comment: z.string().describe("Comment text"), + }, + async ({ address, comment }) => { + const result = await client.post("comment", { address, comment }); + return { content: [{ type: "text", text: result }] }; + } + ); + + server.tool( + "get_comment", + "Get the comment at a specific address.", + { + address: z.string().describe("Address (hex like 0x401000)"), + }, + async ({ address }) => { + const lines = await client.getLines("comment", { address }); + return { content: [{ type: "text", text: lines[0] ?? "" }] }; + } + ); + + server.tool( + "delete_comment", + "Delete the comment at a specific address.", + { + address: z.string().describe("Address (hex like 0x401000)"), + }, + async ({ address }) => { + const result = await client.post("comment", { address, _method: "DELETE" }); + return { content: [{ type: "text", text: result }] }; + } + ); + + server.tool( + "set_function_comment", + "Set a comment for a function.", + { + function_name: z.string().describe("Function name"), + comment: z.string().describe("Comment text"), + }, + async ({ function_name, comment }) => { + const result = await client.post("comment/function", { name: function_name, comment }); + return { content: [{ type: "text", text: result }] }; + } + ); + + server.tool( + "get_function_comment", + "Get the comment for a function.", + { + function_name: z.string().describe("Function name"), + }, + async ({ function_name }) => { + const lines = await client.getLines("comment/function", { name: function_name }); + return { content: [{ type: "text", text: lines[0] || "" }] }; + } + ); + + server.tool( + "delete_function_comment", + "Delete the comment for a function.", + { + function_name: z.string().describe("Function name"), + }, + async ({ function_name }) => { + const result = await client.post("comment/function", { name: function_name, _method: "DELETE" }); + return { content: [{ type: "text", text: result }] }; + } + ); + + // ===== Type Tools ===== + + server.tool( + "define_types", + "Define types from a C code string.", + { + c_code: z.string().describe("C code containing type definitions"), + }, + async ({ c_code }) => { + const data = await client.getJson<{ error?: string } | unknown[]>("defineTypes", { cCode: c_code }); + if (!data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + if (Array.isArray(data)) { + return { content: [{ type: "text", text: `Defined types: ${data.join(", ")}` }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: `Error: ${(data as { error: string }).error}` }] }; + } + return { content: [{ type: "text", text: JSON.stringify(data) }] }; + } + ); + + server.tool( + "list_local_types", + "List all local types in the database (paginated).", + { + offset: z.number().default(0).describe("Offset for pagination"), + count: z.number().default(200).describe("Number of results to return"), + include_libraries: z.boolean().default(false).describe("Include library types"), + }, + async ({ offset = 0, count = 200, include_libraries = false }) => { + const lines = await client.getLines("localTypes", { + offset, + limit: count, + includeLibraries: include_libraries ? 1 : 0, + }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "search_types", + "Search local types whose name or declaration contains the substring.", + { + query: z.string().describe("Search query"), + offset: z.number().default(0).describe("Offset for pagination"), + count: z.number().default(200).describe("Number of results to return"), + include_libraries: z.boolean().default(false).describe("Include library types"), + }, + async ({ query, offset = 0, count = 200, include_libraries = false }) => { + const lines = await client.getLines("searchTypes", { + query, + offset, + limit: count, + includeLibraries: include_libraries ? 1 : 0, + }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "get_user_defined_type", + "Retrieve definition of a user defined type (struct, enumeration, typedef, union).", + { + type_name: z.string().describe("Type name"), + }, + async ({ type_name }) => { + const lines = await client.getLines("getUserDefinedType", { name: type_name }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "get_type_info", + "Resolve a type name and return its declaration and details (kind, members, enum values).", + { + type_name: z.string().describe("Type name"), + }, + async ({ type_name }) => { + const data = await client.getJson>("getTypeInfo", { name: type_name }); + if (!data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: `Error: ${String(data.error)}` }] }; + } + return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; + } + ); + + server.tool( + "declare_c_type", + "Create or update a local type from a C declaration.", + { + c_declaration: z.string().describe("C type declaration"), + }, + async ({ c_declaration }) => { + const data = await client.getJson<{ error?: string; defined_types?: Record; count?: number }>("declareCType", { declaration: c_declaration }); + if (!data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: `Error: ${(data as { error: string }).error}` }] }; + } + if ((data as { defined_types?: Record }).defined_types) { + const names = Object.keys((data as { defined_types: Record }).defined_types).join(", "); + const count = (data as { count?: number }).count || 0; + return { content: [{ type: "text", text: `Declared types (${count}): ${names}` }] }; + } + return { content: [{ type: "text", text: JSON.stringify(data) }] }; + } + ); + + server.tool( + "retype_variable", + "Retype a variable in a function.", + { + function_name: z.string().describe("Function name"), + variable_name: z.string().describe("Variable name"), + type_str: z.string().describe("New type for the variable"), + }, + async ({ function_name, variable_name, type_str }) => { + const data = await client.getJson<{ status?: string; error?: string }>("retypeVariable", { + functionName: function_name, + variableName: variable_name, + type: type_str, + }); + if (!data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + if ("status" in data) { + return { content: [{ type: "text", text: (data as { status: string }).status }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: `Error: ${(data as { error: string }).error}` }] }; + } + return { content: [{ type: "text", text: JSON.stringify(data) }] }; + } + ); + + server.tool( + "set_local_variable_type", + "Set a local variable's type.", + { + function_address: z.string().describe("Function address or name"), + variable_name: z.string().describe("Variable name"), + new_type: z.string().describe("New type"), + }, + async ({ function_address, variable_name, new_type }) => { + const data = await client.getJson<{ status?: string; error?: string }>("setLocalVariableType", { + functionAddress: function_address, + variableName: variable_name, + newType: new_type, + }); + if (!data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + if ((data as { status?: string }).status === "ok") { + const d = data as { variable?: string; function?: string; applied_type?: string }; + return { content: [{ type: "text", text: `Retyped ${d.variable} in ${d.function} to ${d.applied_type}` }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: `Error: ${(data as { error: string }).error}` }] }; + } + return { content: [{ type: "text", text: JSON.stringify(data) }] }; + } + ); + + // ===== Data Tools ===== + + server.tool( + "list_data_items", + "List defined data labels and their values with pagination.", + { + offset: z.number().default(0).describe("Offset for pagination"), + limit: z.number().default(100).describe("Number of results to return"), + }, + async ({ offset = 0, limit = 100 }) => { + const lines = await client.getLines("data", { offset, limit }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "hexdump_address", + "Hexdump data starting at an address.", + { + address: z.string().describe("Address (hex like 0x401000)"), + length: z.number().default(-1).describe("Number of bytes (use -1 for defined size)"), + }, + async ({ address, length = -1 }) => { + const params: Record = { address }; + if (length !== undefined && length !== -1) { + params.length = length; + } + const text = await client.getText("hexdump", params); + return { content: [{ type: "text", text }] }; + } + ); + + server.tool( + "hexdump_data", + "Hexdump a data symbol by name or address.", + { + name_or_address: z.string().describe("Symbol name or address (hex)"), + length: z.number().default(-1).describe("Number of bytes (use -1 for defined size)"), + }, + async ({ name_or_address, length = -1 }) => { + const ident = name_or_address.trim(); + if (ident.startsWith("0x")) { + const params: Record = { address: ident }; + if (length !== undefined && length !== -1) { + params.length = length; + } + const text = await client.getText("hexdump", params); + return { content: [{ type: "text", text }] }; + } + const params: Record = { name: ident }; + if (length !== undefined && length !== -1) { + params.length = length; + } + const text = await client.getText("hexdumpByName", params); + return { content: [{ type: "text", text }] }; + } + ); + + server.tool( + "get_data_decl", + "Return a declaration-like string and hexdump for a data symbol.", + { + name_or_address: z.string().describe("Symbol name or address (hex)"), + length: z.number().default(-1).describe("Number of bytes"), + }, + async ({ name_or_address, length = -1 }) => { + const ident = name_or_address.trim(); + const params: Record = ident.startsWith("0x") + ? { address: ident } + : { name: ident }; + if (length !== undefined && length !== -1) { + params.length = length; + } + const data = await client.getJson<{ + error?: string; + decl?: string; + hexdump?: string; + address?: string; + name?: string; + }>("getDataDecl", params); + if (!data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: `Error: ${(data as { error: string }).error}` }] }; + } + const decl = (data as { decl?: string }).decl || "(no declaration)"; + const hexdump = (data as { hexdump?: string }).hexdump || ""; + const addr = (data as { address?: string }).address || ""; + const name = (data as { name?: string }).name || ident; + return { + content: [{ type: "text", text: `Declaration (${addr} ${name}):\n${decl}\n\nHexdump:\n${hexdump}` }], + }; + } + ); + + // ===== Cross-Reference Tools ===== + + server.tool( + "get_xrefs_to", + "Get all cross references (code and data) to the given address.", + { + address: z.string().describe("Address (hex or decimal)"), + }, + async ({ address }) => { + const lines = await client.getLines("getXrefsTo", { address }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "get_xrefs_to_field", + "Get all cross references to a named struct field (member).", + { + struct_name: z.string().describe("Struct name"), + field_name: z.string().describe("Field name"), + }, + async ({ struct_name, field_name }) => { + const lines = await client.getLines("getXrefsToField", { struct: struct_name, field: field_name }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "get_xrefs_to_struct", + "Get cross references/usages related to a struct name.", + { + struct_name: z.string().describe("Struct name"), + }, + async ({ struct_name }) => { + const lines = await client.getLines("getXrefsToStruct", { name: struct_name }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "get_xrefs_to_type", + "Get xrefs/usages related to a struct or type name.", + { + type_name: z.string().describe("Type name"), + }, + async ({ type_name }) => { + const lines = await client.getLines("getXrefsToType", { name: type_name }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "get_xrefs_to_enum", + "Get usages/xrefs of an enum by scanning for member values and matches.", + { + enum_name: z.string().describe("Enum name"), + }, + async ({ enum_name }) => { + const lines = await client.getLines("getXrefsToEnum", { name: enum_name }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "get_xrefs_to_union", + "Get cross references/usages related to a union type by name.", + { + union_name: z.string().describe("Union name"), + }, + async ({ union_name }) => { + const lines = await client.getLines("getXrefsToUnion", { name: union_name }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + // ===== Utility Tools ===== + + server.tool( + "function_at", + "Retrieve the name of the function the address belongs to.", + { + address: z.string().describe("Address (hex format 0x00001)"), + }, + async ({ address }) => { + const lines = await client.getLines("functionAt", { address }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "get_stack_frame_vars", + "Get stack frame variable information for a function (names, offsets, sizes, types).", + { + function_identifier: z.string().describe("Function name or address (hex)"), + }, + async ({ function_identifier }) => { + const ident = function_identifier.trim(); + const params: Record = ident.toLowerCase().startsWith("0x") || /^\d+$/.test(ident) + ? { address: ident } + : { name: ident }; + const data = await client.getJson<{ error?: string; stack_frame_vars?: string[] }>("getStackFrameVars", params); + if (!data) { + return { content: [{ type: "text", text: "" }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: "" }] }; + } + const vars = (data as { stack_frame_vars?: string[] }).stack_frame_vars; + return { content: [{ type: "text", text: (vars || []).join("\n") }] }; + } + ); + + server.tool( + "list_classes", + "List all namespace/class names in the program with pagination.", + { + offset: z.number().default(0).describe("Offset for pagination"), + limit: z.number().default(100).describe("Number of results to return"), + }, + async ({ offset = 0, limit = 100 }) => { + const lines = await client.getLines("classes", { offset, limit }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "list_namespaces", + "List all non-global namespaces in the program with pagination.", + { + offset: z.number().default(0).describe("Offset for pagination"), + limit: z.number().default(100).describe("Number of results to return"), + }, + async ({ offset = 0, limit = 100 }) => { + const lines = await client.getLines("namespaces", { offset, limit }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "list_segments", + "List all memory segments in the program with pagination.", + { + offset: z.number().default(0).describe("Offset for pagination"), + limit: z.number().default(100).describe("Number of results to return"), + }, + async ({ offset = 0, limit = 100 }) => { + const lines = await client.getLines("segments", { offset, limit }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "list_sections", + "List sections in the program with pagination.", + { + offset: z.number().default(0).describe("Offset for pagination"), + limit: z.number().default(100).describe("Number of results to return"), + }, + async ({ offset = 0, limit = 100 }) => { + const data = await client.getJson<{ error?: string; sections?: Array> }>("sections", { offset, limit }); + if (!data || "error" in data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + const filename = await getActiveFilename(); + const sections = (data as { sections?: Array> }).sections || []; + const lines = [`File: ${filename}`]; + for (const s of sections) { + const start = (s as { start?: string }).start || ""; + const end = (s as { end?: string }).end || ""; + const size = (s as { size?: number }).size; + const name = (s as { name?: string }).name || "(unnamed)"; + const sem = ((s as { semantics?: string }).semantics || (s as { type?: string }).type) || ""; + const tail = sem ? `\t${sem}` : ""; + lines.push(`${start}-${end}\t${size}\t${name}${tail}`); + } + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "list_imports", + "List imported symbols in the program with pagination.", + { + offset: z.number().default(0).describe("Offset for pagination"), + limit: z.number().default(100).describe("Number of results to return"), + }, + async ({ offset = 0, limit = 100 }) => { + const lines = await client.getLines("imports", { offset, limit }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "list_exports", + "List exported functions/symbols with pagination.", + { + offset: z.number().default(0).describe("Offset for pagination"), + limit: z.number().default(100).describe("Number of results to return"), + }, + async ({ offset = 0, limit = 100 }) => { + const lines = await client.getLines("exports", { offset, limit }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "list_strings", + "List all strings in the database (paginated).", + { + offset: z.number().default(0).describe("Offset for pagination"), + count: z.number().default(100).describe("Number of results to return"), + }, + async ({ offset = 0, count = 100 }) => { + const lines = await client.getLines("strings", { offset, limit: count }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "list_strings_filter", + "List matching strings in the database (paginated, filtered).", + { + offset: z.number().default(0).describe("Offset for pagination"), + count: z.number().default(100).describe("Number of results to return"), + filter: z.string().optional().describe("Filter string"), + }, + async ({ offset = 0, count = 100, filter = "" }) => { + const lines = await client.getLines("strings/filter", { offset, limit: count, filter }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "list_all_strings", + "List all strings in the database (aggregated across pages).", + { + batch_size: z.number().default(500).describe("Batch size for aggregation"), + }, + async ({ batch_size = 500 }) => { + const results: string[] = []; + let offset = 0; + while (true) { + const data = await client.getJson>("strings", { offset, limit: batch_size }); + if (!data || !("strings" in data)) { + break; + } + const stringsData = data.strings as Array<{ address?: string; length?: number; type?: string; value?: string }>; + const items = Array.isArray(stringsData) ? stringsData : []; + if (!items.length) { + break; + } + for (const s of items) { + const addr = s.address || ""; + const length = s.length; + const stype = s.type || ""; + const value = s.value || ""; + results.push(`${addr}\t${length}\t${stype}\t${value}`); + } + if (items.length < batch_size) { + break; + } + offset += batch_size; + } + return { content: [{ type: "text", text: results.join("\n") }] }; + } + ); + + server.tool( + "get_binary_status", + "Get the current status of the loaded binary.", + {}, + async () => { + const lines = await client.getLines("status"); + return { content: [{ type: "text", text: lines[0] || "" }] }; + } + ); + + server.tool( + "list_binaries", + "List managed/open binaries known to the server with ids and active flag.", + {}, + async () => { + const data = await client.getJson<{ error?: string; binaries?: Array> }>("binaries"); + if (!data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: (data as { error: string }).error }] }; + } + const binaries = (data as { binaries?: Array> }).binaries || []; + const lines = binaries.map((it) => { + const vid = (it as { id?: number }).id; + const view_id = (it as { view_id?: number }).view_id; + const fn = (it as { filename?: string }).filename; + const basename = (it as { basename?: string }).basename || ""; + const selectors = (it as { selectors?: number[] }).selectors || []; + const active = (it as { active?: boolean }).active; + const label = basename || fn || "(unknown)"; + const full = fn || "(no filename)"; + const selectorText = selectors.map(String).join(", "); + const mark = active ? " *active*" : ""; + const viewPart = view_id ? ` view=${view_id}` : ""; + return `${vid}. ${label}${viewPart}${mark}\n path: ${full}\n selectors: ${selectorText}`; + }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "select_binary", + "Select which binary to analyze by ordinal, internal view id, full path, or basename.", + { + view: z.string().describe("Ordinal, view id, full path, or basename"), + }, + async ({ view }) => { + const data = await client.getJson<{ error?: string; selected?: Record }>("selectBinary", { view }); + if (!data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; + } + const sel = (data as { selected?: Record }).selected; + if (sel) { + const ordinal = (sel as { id?: number }).id || "?"; + const view_id = (sel as { view_id?: number }).view_id; + const fn = (sel as { filename?: string }).filename || ""; + const basename = (sel as { basename?: string }).basename || ""; + const selectors = (sel as { selectors?: number[] }).selectors || []; + const selectorText = selectors.map(String).join(", "); + const displayName = basename || fn || "(unknown)"; + const viewPart = view_id ? ` (view ${view_id})` : ""; + const pathPart = fn ? `\nFull path: ${fn}` : ""; + return { content: [{ type: "text", text: `Selected ${ordinal}: ${displayName}${viewPart}${pathPart}\nSelectors: ${selectorText}` }] }; + } + return { content: [{ type: "text", text: JSON.stringify(data) }] }; + } + ); + + // ===== Binary Modification Tools ===== + + server.tool( + "set_function_prototype", + "Set a function's prototype by name or address.", + { + name_or_address: z.string().describe("Function name or address"), + prototype: z.string().describe("New function prototype"), + }, + async ({ name_or_address, prototype }) => { + const ident = name_or_address.trim(); + const params: Record = { prototype }; + if (ident.toLowerCase().startsWith("0x") || /^\d+$/.test(ident)) { + params.address = ident; + } else { + params.name = ident; + } + const data = await client.getJson<{ status?: string; error?: string; address?: string; applied_type?: string }>("setFunctionPrototype", params); + if (!data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + if ("status" in data) { + return { content: [{ type: "text", text: `Applied prototype at ${(data as { address?: string }).address}: ${(data as { applied_type?: string }).applied_type}` }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: `Error: ${(data as { error: string }).error}` }] }; + } + return { content: [{ type: "text", text: JSON.stringify(data) }] }; + } + ); + + server.tool( + "make_function_at", + "Create a function at the given address.", + { + address: z.string().describe("Address (hex like 0x401000 or decimal)"), + platform: z.string().optional().describe("Platform (e.g., linux-x86_64)"), + }, + async ({ address, platform }) => { + const params: Record = { address }; + if (platform) { + params.platform = platform; + } + const data = await client.getJson<{ error?: string; status?: string; address?: string; name?: string }>("makeFunctionAt", params); + if (!data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; + } + if ((data as { status?: string }).status === "exists") { + return { content: [{ type: "text", text: `Function already exists at ${(data as { address?: string }).address}: ${(data as { name?: string }).name}` }] }; + } + if ((data as { status?: string }).status === "ok") { + return { content: [{ type: "text", text: `Created function at ${(data as { address?: string }).address}: ${(data as { name?: string }).name}` }] }; + } + return { content: [{ type: "text", text: JSON.stringify(data) }] }; + } + ); + + server.tool( + "list_platforms", + "List all available platform names from Binary Ninja.", + {}, + async () => { + const data = await client.getJson<{ error?: string; platforms?: string[] }>("platforms"); + if (!data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; + } + const plats = (data as { platforms?: string[] }).platforms; + if (!plats) { + return { content: [{ type: "text", text: "(no platforms)" }] }; + } + return { content: [{ type: "text", text: plats.join("\n") }] }; + } + ); + + server.tool( + "patch_bytes", + "Patch bytes at a given address in the binary.", + { + address: z.string().describe("Address (hex like 0x401000 or decimal)"), + data: z.string().describe("Hex string of bytes to write (e.g., '90 90' or '9090')"), + save_to_file: z.boolean().default(true).describe("Save patched binary to disk"), + }, + async ({ address, data, save_to_file = true }) => { + const result = await client.getJson<{ + error?: string; + status?: string; + original_bytes?: string; + patched_bytes?: string; + bytes_written?: number; + bytes_requested?: number; + saved_to_file?: boolean; + saved_path?: string; + warning?: string; + }>("patch", { address, data, save_to_file: save_to_file ? 1 : 0 }); + if (!result) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + if ("error" in result) { + return { content: [{ type: "text", text: `Error: ${(result as { error: string }).error}` }] }; + } + const status = (result as { status?: string }).status; + if (status === "ok" || status === "partial") { + const orig = (result as { original_bytes?: string }).original_bytes || ""; + const patched = (result as { patched_bytes?: string }).patched_bytes || ""; + const written = (result as { bytes_written?: number }).bytes_written || 0; + const requested = (result as { bytes_requested?: number }).bytes_requested || 0; + const saved = (result as { saved_to_file?: boolean }).saved_to_file || false; + const savedPath = (result as { saved_path?: string }).saved_path || ""; + const warning = (result as { warning?: string }).warning || ""; + + let msg = `Patched ${written}/${requested} bytes at ${address}`; + if (status === "partial") { + msg += " (PARTIAL WRITE)"; + } + if (warning) { + msg += `\nWarning: ${warning}`; + } + if (orig) { + msg += `\nOriginal: ${orig}`; + } + if (patched) { + msg += `\nPatched: ${patched}`; + } + if (saved) { + msg += `\nSaved to file: ${savedPath}`; + } + return { content: [{ type: "text", text: msg }] }; + } + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + server.tool( + "format_value", + "Convert and annotate a value at an address in Binary Ninja.", + { + address: z.string().describe("Address (hex like 0x401000)"), + text: z.string().describe("Text to convert"), + size: z.number().default(0).describe("Size in bytes"), + }, + async ({ address, text, size = 0 }) => { + const lines = await client.getLines("formatValue", { address, text, size }); + return { content: [{ type: "text", text: lines.join("\n") }] }; + } + ); + + server.tool( + "convert_number", + "Convert a number or string to multiple representations (hex/dec/bin, C literals).", + { + text: z.string().describe("Number or string to convert (decimal, hex 0x7b, binary 0b1111011, char 'A', or string)"), + size: z.number().default(0).describe("Size in bytes (0 for auto)"), + }, + async ({ text, size = 0 }) => { + const data = await client.getJson>("convertNumber", { text, size }); + if (!data) { + return { content: [{ type: "text", text: "Error: no response" }] }; + } + if ("error" in data) { + return { content: [{ type: "text", text: `Error: ${String(data.error)}` }] }; + } + return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; + } + ); +} diff --git a/bridge/tsconfig.json b/bridge/tsconfig.json new file mode 100644 index 0000000..4b20d60 --- /dev/null +++ b/bridge/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/plugin/__init__.py b/plugin/__init__.py index 12b3e8d..9fd85b8 100644 --- a/plugin/__init__.py +++ b/plugin/__init__.py @@ -4,6 +4,28 @@ from .core.config import Config from .server.http_server import MCPServer + +def _apply_settings_to_config(): + """Apply Binary Ninja settings to the configuration.""" + try: + settings = Settings() + + # Apply expose to network setting + expose_to_network = settings.get_bool("mcp.exposeToNetwork") + plugin.config.server.host = "0.0.0.0" if expose_to_network else "localhost" + + # Apply port setting + port_str = settings.get_string("mcp.port") + if port_str: + try: + plugin.config.server.port = int(port_str) + except ValueError: + pass + + except Exception as e: + bn.log_error(f"Failed to apply settings to config: {e}") + + # When true, suppress auto-start while a BinaryView is open until the user # explicitly starts the server again. This prevents immediate re-start after stop. _mcp_user_stopped = False @@ -16,6 +38,9 @@ def __init__(self): def start_server(self, bv): try: + # Apply latest settings from Binary Ninja configuration + _apply_settings_to_config() + # Require an active BinaryView (match menu behavior) if bv is None: bn.log_debug("MCP Max start requested but no BinaryView is active; deferring") @@ -88,15 +113,19 @@ def _register_settings(): "mcp.showStatusButton", '{ "title": "Show Status Button", "type": "boolean", "default": true, "description": "Show MCP server status button in the status bar." }', ) + settings.register_setting( + "mcp.exposeToNetwork", + '{ "title": "Expose to Network", "type": "boolean", "default": false, "description": "When enabled, the server binds to 0.0.0.0 and is accessible from other machines. When disabled, the server only binds to localhost for local-only access." }', + ) + settings.register_setting( + "mcp.port", + '{"title": "Server Port", "type": "string", "default": "9009", "description": "Port number for the MCP server."}', + ) _register_settings() -def _apply_settings_to_config(): - return - - def _try_autostart_for_bv(bv): try: # Respect manual stop; do not auto-start until user starts explicitly