@ebowwa/mcp-nm 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +135 -0
  2. package/package.json +1 -1
  3. package/src/index.ts +178 -0
package/dist/index.js CHANGED
@@ -14069,6 +14069,112 @@ async function handleXxdFindPattern(args) {
14069
14069
  `);
14070
14070
  return { content: [{ type: "text", text: summary }] };
14071
14071
  }
14072
+ async function handleConvertNumber(args) {
14073
+ let num;
14074
+ const value = args.value.trim().toLowerCase();
14075
+ try {
14076
+ switch (args.fromFormat) {
14077
+ case "hex":
14078
+ num = BigInt(value.startsWith("0x") ? value : `0x${value}`);
14079
+ break;
14080
+ case "decimal":
14081
+ num = BigInt(value);
14082
+ break;
14083
+ case "binary":
14084
+ num = BigInt(value.startsWith("0b") ? value.slice(2) : value, 2);
14085
+ break;
14086
+ case "octal":
14087
+ num = BigInt(value.startsWith("0o") ? value.slice(2) : value, 8);
14088
+ break;
14089
+ case "auto":
14090
+ default:
14091
+ if (value.startsWith("0x") || /^[0-9a-f]+$/i.test(value) && value.length > 4) {
14092
+ num = BigInt(value.startsWith("0x") ? value : `0x${value}`);
14093
+ } else if (value.startsWith("0b")) {
14094
+ num = BigInt(value.slice(2), 2);
14095
+ } else if (value.startsWith("0o")) {
14096
+ num = BigInt(value.slice(2), 8);
14097
+ } else {
14098
+ num = BigInt(value);
14099
+ }
14100
+ break;
14101
+ }
14102
+ } catch {
14103
+ return {
14104
+ content: [{ type: "text", text: `Error: Invalid number format "${args.value}"` }],
14105
+ isError: true
14106
+ };
14107
+ }
14108
+ let signedNum = num;
14109
+ if (args.signed && args.byteSize) {
14110
+ const bits = args.byteSize;
14111
+ const maxUnsigned = (1n << BigInt(bits)) - 1n;
14112
+ const signBit = 1n << BigInt(bits - 1);
14113
+ if (num & signBit) {
14114
+ signedNum = num - (1n << BigInt(bits));
14115
+ }
14116
+ }
14117
+ const toFormat = args.toFormat ?? "all";
14118
+ const results = [];
14119
+ if (toFormat === "all" || toFormat === "decimal") {
14120
+ results.push(`Decimal: ${num.toString()}`);
14121
+ if (args.signed && args.byteSize && signedNum !== num) {
14122
+ results.push(`Signed: ${signedNum.toString()}`);
14123
+ }
14124
+ }
14125
+ if (toFormat === "all" || toFormat === "hex") {
14126
+ const hexStr = num.toString(16).toUpperCase();
14127
+ const paddedHex = args.byteSize ? hexStr.padStart(args.byteSize / 4, "0") : hexStr;
14128
+ results.push(`Hex: 0x${paddedHex}`);
14129
+ }
14130
+ if (toFormat === "all" || toFormat === "binary") {
14131
+ const binStr = num.toString(2);
14132
+ const paddedBin = args.byteSize ? binStr.padStart(args.byteSize, "0") : binStr;
14133
+ const formattedBin = paddedBin.match(/.{1,8}/g)?.join(" ") ?? paddedBin;
14134
+ results.push(`Binary: 0b${formattedBin}`);
14135
+ }
14136
+ if (toFormat === "all" || toFormat === "octal") {
14137
+ results.push(`Octal: 0o${num.toString(8)}`);
14138
+ }
14139
+ if (toFormat === "all" || toFormat === "ascii") {
14140
+ let asciiStr = "";
14141
+ let tempNum = num;
14142
+ while (tempNum > 0n) {
14143
+ const charCode = Number(tempNum & 0xffn);
14144
+ if (charCode >= 32 && charCode <= 126) {
14145
+ asciiStr = String.fromCharCode(charCode) + asciiStr;
14146
+ } else {
14147
+ asciiStr = `\\x${charCode.toString(16).padStart(2, "0")}` + asciiStr;
14148
+ }
14149
+ tempNum = tempNum >> 8n;
14150
+ }
14151
+ results.push(`ASCII: ${asciiStr || "(non-printable)"}`);
14152
+ }
14153
+ if (args.byteSize) {
14154
+ results.push("");
14155
+ results.push(`Size: ${args.byteSize}-bit (${args.byteSize / 8} bytes)`);
14156
+ }
14157
+ if (toFormat === "all" && num > 0xffn) {
14158
+ const bytes = [];
14159
+ let tempNum = num;
14160
+ while (tempNum > 0n) {
14161
+ bytes.unshift(tempNum & 0xffn);
14162
+ tempNum = tempNum >> 8n;
14163
+ }
14164
+ results.push("");
14165
+ results.push("Bytes (big-endian):");
14166
+ results.push(` ${bytes.map((b, i) => `[${i}] 0x${b.toString(16).padStart(2, "0")} (${b})`).join(`
14167
+ `)}`);
14168
+ }
14169
+ const summary = [
14170
+ `Number Conversion: ${args.value}`,
14171
+ `From: ${args.fromFormat}`,
14172
+ "",
14173
+ ...results
14174
+ ].join(`
14175
+ `);
14176
+ return { content: [{ type: "text", text: summary }] };
14177
+ }
14072
14178
  var SYMBOL_TYPE_DESCRIPTIONS = {
14073
14179
  A: "Global absolute symbol",
14074
14180
  B: "Global BSS (uninitialized data)",
@@ -15092,6 +15198,33 @@ var TOOLS = [
15092
15198
  required: ["filePath", "pattern"]
15093
15199
  }
15094
15200
  },
15201
+ {
15202
+ name: "bin_convert",
15203
+ description: "Convert numbers between hex, decimal, binary, octal, and ASCII formats",
15204
+ inputSchema: {
15205
+ type: "object",
15206
+ properties: {
15207
+ value: { type: "string", description: "Number value to convert" },
15208
+ fromFormat: {
15209
+ type: "string",
15210
+ enum: ["hex", "decimal", "binary", "octal", "auto"],
15211
+ description: "Input format (default: auto-detect)"
15212
+ },
15213
+ toFormat: {
15214
+ type: "string",
15215
+ enum: ["all", "hex", "decimal", "binary", "octal", "ascii"],
15216
+ description: "Output format (default: all)"
15217
+ },
15218
+ byteSize: {
15219
+ type: "number",
15220
+ enum: [8, 16, 32, 64],
15221
+ description: "Byte size for padding (8/16/32/64-bit)"
15222
+ },
15223
+ signed: { type: "boolean", description: "Treat as signed integer" }
15224
+ },
15225
+ required: ["value"]
15226
+ }
15227
+ },
15095
15228
  {
15096
15229
  name: "bin_strings",
15097
15230
  description: "Extract readable strings (ASCII, Unicode) from a binary file",
@@ -15381,6 +15514,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
15381
15514
  return await handleNopSled(args);
15382
15515
  case "bin_hex_editor":
15383
15516
  return await handleHexEditor(args);
15517
+ case "bin_convert":
15518
+ return await handleConvertNumber(args);
15384
15519
  default:
15385
15520
  throw new Error(`Unknown tool: ${name}`);
15386
15521
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ebowwa/mcp-nm",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Comprehensive binary analysis MCP server - symbols (nm), hex dumps (xxd), strings, disassembly, security audit, entropy analysis, ELF/Mach-O inspection, and binary patching",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -674,6 +674,147 @@ async function handleXxdFindPattern(args: {
674
674
  return { content: [{ type: "text", text: summary }] };
675
675
  }
676
676
 
677
+ // ============================================================================
678
+ // Number Format Conversion Utility
679
+ // ============================================================================
680
+
681
+ async function handleConvertNumber(args: {
682
+ value: string;
683
+ fromFormat: "hex" | "decimal" | "binary" | "octal" | "auto";
684
+ toFormat?: "all" | "hex" | "decimal" | "binary" | "octal" | "ascii";
685
+ byteSize?: 8 | 16 | 32 | 64;
686
+ signed?: boolean;
687
+ }) {
688
+ let num: bigint;
689
+
690
+ // Parse input value
691
+ const value = args.value.trim().toLowerCase();
692
+
693
+ try {
694
+ switch (args.fromFormat) {
695
+ case "hex":
696
+ num = BigInt(value.startsWith("0x") ? value : `0x${value}`);
697
+ break;
698
+ case "decimal":
699
+ num = BigInt(value);
700
+ break;
701
+ case "binary":
702
+ num = BigInt(value.startsWith("0b") ? value.slice(2) : value, 2);
703
+ break;
704
+ case "octal":
705
+ num = BigInt(value.startsWith("0o") ? value.slice(2) : value, 8);
706
+ break;
707
+ case "auto":
708
+ default:
709
+ // Auto-detect format
710
+ if (value.startsWith("0x") || /^[0-9a-f]+$/i.test(value) && value.length > 4) {
711
+ num = BigInt(value.startsWith("0x") ? value : `0x${value}`);
712
+ } else if (value.startsWith("0b")) {
713
+ num = BigInt(value.slice(2), 2);
714
+ } else if (value.startsWith("0o")) {
715
+ num = BigInt(value.slice(2), 8);
716
+ } else {
717
+ num = BigInt(value);
718
+ }
719
+ break;
720
+ }
721
+ } catch {
722
+ return {
723
+ content: [{ type: "text", text: `Error: Invalid number format "${args.value}"` }],
724
+ isError: true,
725
+ };
726
+ }
727
+
728
+ // Handle signed conversion if requested
729
+ let signedNum: bigint = num;
730
+ if (args.signed && args.byteSize) {
731
+ const bits = args.byteSize;
732
+ const maxUnsigned = (1n << BigInt(bits)) - 1n;
733
+ const signBit = 1n << BigInt(bits - 1);
734
+
735
+ if (num & signBit) {
736
+ signedNum = num - (1n << BigInt(bits));
737
+ }
738
+ }
739
+
740
+ // Format outputs
741
+ const toFormat = args.toFormat ?? "all";
742
+ const results: string[] = [];
743
+
744
+ if (toFormat === "all" || toFormat === "decimal") {
745
+ results.push(`Decimal: ${num.toString()}`);
746
+ if (args.signed && args.byteSize && signedNum !== num) {
747
+ results.push(`Signed: ${signedNum.toString()}`);
748
+ }
749
+ }
750
+
751
+ if (toFormat === "all" || toFormat === "hex") {
752
+ const hexStr = num.toString(16).toUpperCase();
753
+ const paddedHex = args.byteSize
754
+ ? hexStr.padStart(args.byteSize / 4, "0")
755
+ : hexStr;
756
+ results.push(`Hex: 0x${paddedHex}`);
757
+ }
758
+
759
+ if (toFormat === "all" || toFormat === "binary") {
760
+ const binStr = num.toString(2);
761
+ const paddedBin = args.byteSize
762
+ ? binStr.padStart(args.byteSize, "0")
763
+ : binStr;
764
+ // Add spaces every 8 bits for readability
765
+ const formattedBin = paddedBin.match(/.{1,8}/g)?.join(" ") ?? paddedBin;
766
+ results.push(`Binary: 0b${formattedBin}`);
767
+ }
768
+
769
+ if (toFormat === "all" || toFormat === "octal") {
770
+ results.push(`Octal: 0o${num.toString(8)}`);
771
+ }
772
+
773
+ if (toFormat === "all" || toFormat === "ascii") {
774
+ // Try to convert to ASCII string
775
+ let asciiStr = "";
776
+ let tempNum = num;
777
+ while (tempNum > 0n) {
778
+ const charCode = Number(tempNum & 0xffn);
779
+ if (charCode >= 32 && charCode <= 126) {
780
+ asciiStr = String.fromCharCode(charCode) + asciiStr;
781
+ } else {
782
+ asciiStr = `\\x${charCode.toString(16).padStart(2, "0")}` + asciiStr;
783
+ }
784
+ tempNum = tempNum >> 8n;
785
+ }
786
+ results.push(`ASCII: ${asciiStr || "(non-printable)"}`);
787
+ }
788
+
789
+ // Add byte size info
790
+ if (args.byteSize) {
791
+ results.push("");
792
+ results.push(`Size: ${args.byteSize}-bit (${args.byteSize / 8} bytes)`);
793
+ }
794
+
795
+ // Add byte breakdown for multi-byte values
796
+ if (toFormat === "all" && num > 0xffn) {
797
+ const bytes: string[] = [];
798
+ let tempNum = num;
799
+ while (tempNum > 0n) {
800
+ bytes.unshift(tempNum & 0xffn);
801
+ tempNum = tempNum >> 8n;
802
+ }
803
+ results.push("");
804
+ results.push("Bytes (big-endian):");
805
+ results.push(` ${bytes.map((b, i) => `[${i}] 0x${b.toString(16).padStart(2, "0")} (${b})`).join("\n ")}`);
806
+ }
807
+
808
+ const summary = [
809
+ `Number Conversion: ${args.value}`,
810
+ `From: ${args.fromFormat}`,
811
+ "",
812
+ ...results,
813
+ ].join("\n");
814
+
815
+ return { content: [{ type: "text", text: summary }] };
816
+ }
817
+
677
818
  // ============================================================================
678
819
  // Symbol type descriptions
679
820
  // ============================================================================
@@ -1986,6 +2127,34 @@ const TOOLS = [
1986
2127
  required: ["filePath", "pattern"],
1987
2128
  },
1988
2129
  },
2130
+ // Number Format Conversion
2131
+ {
2132
+ name: "bin_convert",
2133
+ description: "Convert numbers between hex, decimal, binary, octal, and ASCII formats",
2134
+ inputSchema: {
2135
+ type: "object" as const,
2136
+ properties: {
2137
+ value: { type: "string", description: "Number value to convert" },
2138
+ fromFormat: {
2139
+ type: "string",
2140
+ enum: ["hex", "decimal", "binary", "octal", "auto"],
2141
+ description: "Input format (default: auto-detect)",
2142
+ },
2143
+ toFormat: {
2144
+ type: "string",
2145
+ enum: ["all", "hex", "decimal", "binary", "octal", "ascii"],
2146
+ description: "Output format (default: all)",
2147
+ },
2148
+ byteSize: {
2149
+ type: "number",
2150
+ enum: [8, 16, 32, 64],
2151
+ description: "Byte size for padding (8/16/32/64-bit)",
2152
+ },
2153
+ signed: { type: "boolean", description: "Treat as signed integer" },
2154
+ },
2155
+ required: ["value"],
2156
+ },
2157
+ },
1989
2158
  // Extended Binary Analysis Tools
1990
2159
  {
1991
2160
  name: "bin_strings",
@@ -2322,6 +2491,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2322
2491
  return await handleNopSled(args as { filePath: string; offset: number; count: number; createBackup?: boolean });
2323
2492
  case "bin_hex_editor":
2324
2493
  return await handleHexEditor(args as { filePath: string; offset: number; length: number; newHex?: string });
2494
+ // Number conversion utility
2495
+ case "bin_convert":
2496
+ return await handleConvertNumber(args as {
2497
+ value: string;
2498
+ fromFormat: "hex" | "decimal" | "binary" | "octal" | "auto";
2499
+ toFormat?: "all" | "hex" | "decimal" | "binary" | "octal" | "ascii";
2500
+ byteSize?: 8 | 16 | 32 | 64;
2501
+ signed?: boolean;
2502
+ });
2325
2503
  default:
2326
2504
  throw new Error(`Unknown tool: ${name}`);
2327
2505
  }