diff --git a/AGENTS.md b/AGENTS.md index 1d526534c..25753c98b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,6 +14,7 @@ The server is a layered ESM TypeScript application. Dependencies flow inward: `d - `src/server/`: MCP request translation. `createServer.ts` assembles the MCP server, `registerOfficialTools.ts`/`registerEnhancedTools.ts` register each tool set, `toolResult.ts` maps `Result` values to MCP content. - `tests/`: Vitest suite. `tests/fixtures/` holds the fake launcher and fake Hopper bridge used as production seams. - `scripts/verify-real-hopper.mjs`: real-Hopper end-to-end verifier. +- `scripts/print-mcp-config.mjs`: prints a ready-to-paste MCP server config with absolute paths filled in (`npm run config:print -- /path/to/binary`). ## Build, Test, and Development Commands @@ -25,6 +26,7 @@ The server is a layered ESM TypeScript application. Dependencies flow inward: `d - `npm run format:check`: verify Prettier formatting. - `npm run check`: run typecheck, lint, format:check, and tests. - `npm run verify:hopper`: build and run the real-Hopper verifier (requires a binary). +- `npm run config:print -- /path/to/binary`: print a ready-to-paste MCP server config with absolute paths. - `HOPPER_TARGET_PATH=/path/to/binary npm start`: launch Hopper and run the built stdio MCP server. ## Configuration & Environment Variables diff --git a/README.md b/README.md index d6f6afdac..a572926ea 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,47 @@ A TypeScript MCP server for [Hopper Disassembler](https://www.hopperapp.com/). It preserves 31 familiar Hopper operations and adds 8 reverse-engineering workflows for Swift, Objective-C, decompilation, call graphs, cross-references, and binary summaries. -The server uses the modular `@modelcontextprotocol/server@2.0.0-beta.3` SDK. +## Quick Start + +```bash +git clone && cd betterBinaryMCP +npm ci && npm run build +``` + +Generate a ready-to-paste MCP config with absolute paths filled in: + +```bash +npm run config:print -- /path/to/your/binary +``` + +Paste the output into your MCP client config (Claude Desktop, Cursor, etc.) and restart the client. That's it — the 39 tools are now available. + +```bash +# For a .hop database: +npm run config:print -- /path/to/file.hop --kind database + +# For a FAT binary that needs loader args: +npm run config:print -- /path/to/binary --loader-args '["-l","Mach-O","--aarch64"]' +``` + +### Set up with your agent + +Paste this prompt into Claude, Cursor, or any MCP-capable agent to have it do the setup for you: + +> I want to connect betterBinaryMCP (a Hopper Disassembler MCP server) to this client. +> +> 1. Clone the repo and run `npm ci && npm run build`. +> 2. Run `npm run config:print -- /path/to/my/binary` to generate the MCP server config. +> 3. Paste the output into this client's MCP config and restart. +> 4. Verify the 39 tools are listed. + +## Prerequisites + +- **Node.js 20+** — `node --version` +- **Hopper Disassembler 6+** on macOS — ships at `/Applications/Hopper Disassembler.app/` +- **A binary or Hopper database** to analyze + +Accessibility permission is **not** required. Specify loader arguments for archives that would otherwise show an architecture chooser. ## Architecture @@ -18,24 +58,9 @@ The codebase is a layered ESM TypeScript application. Dependencies flow inward: - `src/application/` composes official operations into the 8 enhanced tools. - `src/domain/` holds pure parsers and the tagged error algebra; `src/contracts/` declares the caller-visible tool schemas. -## Requirements +## Manual MCP Configuration -- Node.js 20 or newer -- Hopper Disassembler 6+ on macOS -- A binary or Hopper database to analyze - -Accessibility permission is **not** required. Specify loader arguments for archives that would otherwise show an architecture chooser. - -## Install and Build - -```bash -npm ci -npm run build -``` - -## MCP Configuration - -Build the project, then configure your MCP client with an absolute path: +If you prefer to write the config by hand, build the project and use absolute paths: ```json { @@ -51,7 +76,7 @@ Build the project, then configure your MCP client with an absolute path: } ``` -Run it directly with `npm start` after building. Stdout is reserved for MCP protocol messages; diagnostics use stderr. +Stdout is reserved for MCP protocol messages; diagnostics use stderr. ### Environment Variables diff --git a/package.json b/package.json index e4604e771..0ca1203fa 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "test": "vitest run", "test:watch": "vitest", "typecheck": "tsc --noEmit", - "verify:hopper": "npm run build && node scripts/verify-real-hopper.mjs" + "verify:hopper": "npm run build && node scripts/verify-real-hopper.mjs", + "config:print": "node scripts/print-mcp-config.mjs" }, "dependencies": { "@modelcontextprotocol/server": "2.0.0-beta.3", diff --git a/scripts/print-mcp-config.mjs b/scripts/print-mcp-config.mjs new file mode 100755 index 000000000..29cd2d939 --- /dev/null +++ b/scripts/print-mcp-config.mjs @@ -0,0 +1,51 @@ +#!/usr/bin/env node +/** + * Print a ready-to-paste MCP server config with absolute paths filled in. + * + * Usage: + * node scripts/print-mcp-config.mjs /path/to/binary + * node scripts/print-mcp-config.mjs /path/to/binary --kind database + * node scripts/print-mcp-config.mjs /path/to/binary --loader-args '["-l","Mach-O","--aarch64"]' + */ +import { resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const targetPath = process.argv[2]; +if (!targetPath) { + process.stderr.write( + "Usage: node scripts/print-mcp-config.mjs [--kind executable|database] [--loader-args JSON]\n", + ); + process.exit(1); +} + +const flags = process.argv.slice(3); +const kind = readFlag(flags, "--kind") ?? "executable"; +const loaderArgs = readFlag(flags, "--loader-args"); + +const serverPath = resolve( + fileURLToPath(new URL("..", import.meta.url)), + "dist/main.js", +); + +const env = { + HOPPER_TARGET_PATH: resolve(targetPath), + HOPPER_TARGET_KIND: kind, +}; +if (loaderArgs) env.HOPPER_LOADER_ARGS_JSON = loaderArgs; + +const config = { + mcpServers: { + betterBinaryMCP: { + command: "node", + args: [serverPath], + env, + }, + }, +}; + +process.stdout.write(`${JSON.stringify(config, null, 2)}\n`); + +function readFlag(args, name) { + const i = args.indexOf(name); + return i !== -1 ? args[i + 1] : undefined; +}