wasm-mcp 0.1.1 → 0.2.0

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.
@@ -11,8 +11,8 @@ specification (https://github.com/WebAssembly/spec). Every response
11
11
  is deterministic over data pinned to a specific upstream commit and
12
12
  baked into the package at build time.
13
13
 
14
- Unofficial, community-maintained — not affiliated with, endorsed by,
15
- or sponsored by the W3C WebAssembly Community Group or Working Group.
14
+ Not affiliated with, endorsed by, or sponsored by the W3C WebAssembly
15
+ Community Group or Working Group.
16
16
 
17
17
  Common workflow:
18
18
  1. \`spec_version\` — call first when you need to cite the spec or
@@ -25,7 +25,7 @@ export const TOOLS = [
25
25
  {
26
26
  name: "instruction_get",
27
27
  title: "Get instruction",
28
- description: "Fetch one WebAssembly instruction by mnemonic (`i32.add`, `br_if`) or binary opcode (`0x6a`, `0xfd 0x89 0x02`) as structured JSON: opcode bytes, category, introducing version, stack type signature, and validation/execution prose anchors + spec URLs. Provide `mnemonic` or `opcode` (mnemonic wins if both match).",
28
+ description: "Fetch one WebAssembly instruction by mnemonic (`i32.add`, `br_if`) or binary opcode (`0x6a`, `0xfd 0x89 0x02`) as structured JSON: opcode bytes, category, introducing version, stack type signature, validation/execution prose anchors + spec URLs, and `traps` — the runtime conditions under which it traps (each with the spec's canonical trap name; empty + `can_trap: false` for instructions that never trap). Provide `mnemonic` or `opcode` (mnemonic wins if both match).",
29
29
  inputSchema: instructionGetSchema,
30
30
  examples: instructionGetExamples,
31
31
  handler: (a) => instructionGet(a),
@@ -33,7 +33,7 @@ export const TOOLS = [
33
33
  {
34
34
  name: "instruction_list",
35
35
  title: "List instructions",
36
- description: "Enumerate WebAssembly instructions with optional filters: `category` (control, numeric, parametric, variable, table, memory, ref, i31, struct, array, extern, vec), `introduced_in` (1.0 | 2.0 | 3.0), and `prefix` (mnemonic prefix like `i32.`). Returns lightweight rows sorted by opcode; follow up with instruction_get for full detail.",
36
+ description: "Enumerate WebAssembly instructions with optional filters: `category` (control, numeric, parametric, variable, table, memory, ref, i31, struct, array, extern, vec), `introduced_in` (1.0 | 2.0 | 3.0), `prefix` (mnemonic prefix like `i32.`), and `can_trap` (only trapping / only non-trapping instructions). Returns lightweight rows (incl. `can_trap`) sorted by opcode; follow up with instruction_get for full detail incl. trap conditions.",
37
37
  inputSchema: instructionListSchema,
38
38
  examples: instructionListExamples,
39
39
  handler: (a) => instructionList(a),
@@ -24,6 +24,7 @@ export declare const instructionListSchema: {
24
24
  "3.0": "3.0";
25
25
  }>>;
26
26
  prefix: z.ZodOptional<z.ZodString>;
27
+ can_trap: z.ZodOptional<z.ZodBoolean>;
27
28
  version: z.ZodDefault<z.ZodEnum<{
28
29
  main: "main";
29
30
  latest: "latest";
@@ -33,6 +34,7 @@ export type InstructionListArgs = {
33
34
  category?: InstructionCategory;
34
35
  introduced_in?: (typeof WASM_VERSIONS)[number];
35
36
  prefix?: string;
37
+ can_trap?: boolean;
36
38
  version?: VersionValue;
37
39
  };
38
40
  export declare const instructionListExamples: ({
@@ -41,6 +43,7 @@ export declare const instructionListExamples: ({
41
43
  category: string;
42
44
  introduced_in?: undefined;
43
45
  prefix?: undefined;
46
+ can_trap?: undefined;
44
47
  };
45
48
  note: string;
46
49
  } | {
@@ -49,6 +52,7 @@ export declare const instructionListExamples: ({
49
52
  category: string;
50
53
  introduced_in: string;
51
54
  prefix?: undefined;
55
+ can_trap?: undefined;
52
56
  };
53
57
  note: string;
54
58
  } | {
@@ -57,6 +61,16 @@ export declare const instructionListExamples: ({
57
61
  prefix: string;
58
62
  category?: undefined;
59
63
  introduced_in?: undefined;
64
+ can_trap?: undefined;
65
+ };
66
+ note: string;
67
+ } | {
68
+ q: string;
69
+ input: {
70
+ can_trap: boolean;
71
+ category?: undefined;
72
+ introduced_in?: undefined;
73
+ prefix?: undefined;
60
74
  };
61
75
  note: string;
62
76
  })[];
@@ -22,6 +22,10 @@ export const instructionListSchema = {
22
22
  .min(1)
23
23
  .optional()
24
24
  .describe("Filter to mnemonics starting with this prefix, e.g. `i32.` or `v128.`. Case-insensitive."),
25
+ can_trap: z
26
+ .boolean()
27
+ .optional()
28
+ .describe("Filter by trapping behavior: `true` keeps only instructions that can trap at runtime, `false` keeps only those that never trap. See instruction_get for the per-instruction trap conditions."),
25
29
  version: versionArg,
26
30
  };
27
31
  export const instructionListExamples = [
@@ -40,6 +44,11 @@ export const instructionListExamples = [
40
44
  input: { prefix: "i32." },
41
45
  note: "Prefix filter is the quickest way to scope to one type family.",
42
46
  },
47
+ {
48
+ q: "Which instructions can trap?",
49
+ input: { can_trap: true },
50
+ note: "can_trap filters to the finite trapping set; each row's can_trap mirrors instruction_get's traps.",
51
+ },
43
52
  ];
44
53
  export function instructionList(args) {
45
54
  const records = loadInstructions(args.version);
@@ -47,6 +56,7 @@ export function instructionList(args) {
47
56
  category: args.category,
48
57
  version: args.introduced_in,
49
58
  prefix: args.prefix,
59
+ can_trap: args.can_trap,
50
60
  });
51
61
  return { count: instructions.length, instructions };
52
62
  }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { type TrapCondition } from "./traps.js";
2
3
  export declare const INSTRUCTION_CATEGORIES: readonly ["control", "numeric", "parametric", "variable", "table", "memory", "ref", "i31", "struct", "array", "extern", "vec"];
3
4
  export type InstructionCategory = (typeof INSTRUCTION_CATEGORIES)[number];
4
5
  export declare const WASM_VERSIONS: readonly ["1.0", "2.0", "3.0"];
@@ -51,6 +52,20 @@ export interface InstructionRecord {
51
52
  validation: string;
52
53
  execution: string;
53
54
  };
55
+ /**
56
+ * Whether this instruction can trap at runtime. `true` iff `traps`
57
+ * is non-empty. A quick boolean for "does this trap?" without
58
+ * inspecting the conditions.
59
+ */
60
+ can_trap: boolean;
61
+ /**
62
+ * The runtime conditions under which this instruction traps, each
63
+ * with the spec's canonical trap name. Empty for instructions that
64
+ * never trap (e.g. `i32.add`). Derived at build time from the
65
+ * spec's operator partiality + trapping instruction families; see
66
+ * `src/parser/traps.ts`.
67
+ */
68
+ traps: TrapCondition[];
54
69
  }
55
70
  export declare const InstructionRecordSchema: z.ZodObject<{
56
71
  mnemonic: z.ZodString;
@@ -86,6 +101,11 @@ export declare const InstructionRecordSchema: z.ZodObject<{
86
101
  validation: z.ZodURL;
87
102
  execution: z.ZodURL;
88
103
  }, z.core.$strip>;
104
+ can_trap: z.ZodBoolean;
105
+ traps: z.ZodArray<z.ZodObject<{
106
+ condition: z.ZodString;
107
+ name: z.ZodString;
108
+ }, z.core.$strip>>;
89
109
  }, z.core.$strip>;
90
110
  export interface RawInstruction {
91
111
  version: number | null;
@@ -9,6 +9,7 @@
9
9
  // joins the two and emits a stable record shape that the runtime
10
10
  // tools query directly — no LaTeX in the surface area.
11
11
  import { z } from "zod";
12
+ import { deriveTraps } from "./traps.js";
12
13
  export const INSTRUCTION_CATEGORIES = [
13
14
  "control",
14
15
  "numeric",
@@ -32,6 +33,8 @@ export const InstructionRecordSchema = z.object({
32
33
  signature: z.object({ params_raw: z.string(), results_raw: z.string() }),
33
34
  anchors: z.object({ validation: z.string(), execution: z.string() }),
34
35
  urls: z.object({ validation: z.url(), execution: z.url() }),
36
+ can_trap: z.boolean(),
37
+ traps: z.array(z.object({ condition: z.string(), name: z.string() })),
35
38
  });
36
39
  const SPEC_BASE = "https://webassembly.github.io/spec/core";
37
40
  /** Build a full anchor URL into the rendered spec. */
@@ -224,6 +227,7 @@ export function normalizeInstructions(dump) {
224
227
  // String(1.0) drops the trailing `.0`, so use toFixed(1) to get
225
228
  // the canonical `"1.0"`/`"2.0"`/`"3.0"` form.
226
229
  const version = raw.version.toFixed(1);
230
+ const traps = deriveTraps(mnemonic);
227
231
  records.push({
228
232
  mnemonic,
229
233
  opcodes,
@@ -235,6 +239,8 @@ export function normalizeInstructions(dump) {
235
239
  validation: instructionUrl(raw.validation),
236
240
  execution: instructionUrl(raw.execution),
237
241
  },
242
+ can_trap: traps.length > 0,
243
+ traps,
238
244
  });
239
245
  }
240
246
  return { records, skipped: report };
@@ -0,0 +1,12 @@
1
+ /** One way an instruction can trap. */
2
+ export interface TrapCondition {
3
+ /** Terse description of the runtime condition that triggers the trap. */
4
+ condition: string;
5
+ /** The spec's canonical trap name (matches `.wast` `assert_trap` text). */
6
+ name: string;
7
+ }
8
+ /**
9
+ * Derive the trap conditions for an instruction mnemonic. Returns an
10
+ * empty array for instructions that cannot trap. Pure and total.
11
+ */
12
+ export declare function deriveTraps(mnemonic: string): TrapCondition[];
@@ -0,0 +1,112 @@
1
+ // Per-instruction trap conditions.
2
+ //
3
+ // WebAssembly's trapping behaviour is a finite, well-defined property
4
+ // of a small set of instruction families. This module encodes that
5
+ // set as a pure mnemonic → conditions mapping, applied at build time
6
+ // to the pinned instruction list. No execution, no scraping — the
7
+ // rules express the spec's operator partiality (which operators are
8
+ // partial, and on what input) and the trapping instruction families
9
+ // (memory/table accesses, indirect calls, null dereferences).
10
+ //
11
+ // Trap NAMES are the spec's canonical strings, verified against the
12
+ // pinned spec's own conformance suite (`test/core/*.wast`
13
+ // `assert_trap` messages): `integer divide by zero`, `integer
14
+ // overflow`, `invalid conversion to integer`, `out of bounds memory
15
+ // access`, `out of bounds table access`, `undefined element`,
16
+ // `uninitialized element`, `indirect call type mismatch`,
17
+ // `unreachable`, `null reference`.
18
+ //
19
+ // Scope note: array accessors and `ref.cast` also trap, but with
20
+ // conditions whose canonical names are NOT present in the pinned core
21
+ // test suite (e.g. out-of-bounds array access, cast failure). Rather
22
+ // than ship partial or unverified trap data, they are intentionally
23
+ // omitted here; structs (no out-of-bounds, null-deref only) and the
24
+ // single-condition reference instructions are modelled in full.
25
+ // Canonical conditions, named once so the mapping reads cleanly.
26
+ const DIVIDE_BY_ZERO = { condition: "divisor is zero", name: "integer divide by zero" };
27
+ const DIV_OVERFLOW = {
28
+ condition: "signed overflow (INT_MIN / -1)",
29
+ name: "integer overflow",
30
+ };
31
+ const TRUNC_INVALID = {
32
+ condition: "operand is NaN or an infinity",
33
+ name: "invalid conversion to integer",
34
+ };
35
+ const TRUNC_OVERFLOW = {
36
+ condition: "truncated value is outside the target integer range",
37
+ name: "integer overflow",
38
+ };
39
+ const MEM_OOB = {
40
+ condition: "effective address + access size is outside the memory bounds",
41
+ name: "out of bounds memory access",
42
+ };
43
+ const MEM_RANGE_OOB = {
44
+ condition: "source or destination range is outside the memory bounds",
45
+ name: "out of bounds memory access",
46
+ };
47
+ const TABLE_OOB = {
48
+ condition: "index is outside the table bounds",
49
+ name: "out of bounds table access",
50
+ };
51
+ const TABLE_RANGE_OOB = {
52
+ condition: "source or destination range is outside the table bounds",
53
+ name: "out of bounds table access",
54
+ };
55
+ const CALL_INDIRECT_UNDEF = {
56
+ condition: "table index is outside the table bounds",
57
+ name: "undefined element",
58
+ };
59
+ const CALL_INDIRECT_UNINIT = {
60
+ condition: "table element is a null reference",
61
+ name: "uninitialized element",
62
+ };
63
+ const CALL_INDIRECT_TYPE = {
64
+ condition: "runtime function type does not match the expected type",
65
+ name: "indirect call type mismatch",
66
+ };
67
+ const nullRef = (condition) => ({ condition, name: "null reference" });
68
+ /**
69
+ * Derive the trap conditions for an instruction mnemonic. Returns an
70
+ * empty array for instructions that cannot trap. Pure and total.
71
+ */
72
+ export function deriveTraps(mnemonic) {
73
+ const m = mnemonic;
74
+ // `unreachable` always traps.
75
+ if (m === "unreachable") {
76
+ return [{ condition: "always — executing this instruction unconditionally traps", name: "unreachable" }];
77
+ }
78
+ // Integer division / remainder (partial operators).
79
+ if (/^i(32|64)\.div_s$/.test(m))
80
+ return [DIVIDE_BY_ZERO, DIV_OVERFLOW];
81
+ if (/^i(32|64)\.div_u$/.test(m))
82
+ return [DIVIDE_BY_ZERO];
83
+ // rem_s does NOT overflow (INT_MIN % -1 is defined as 0).
84
+ if (/^i(32|64)\.rem_[su]$/.test(m))
85
+ return [DIVIDE_BY_ZERO];
86
+ // Non-saturating float→int truncation. `trunc_sat_*` saturate and
87
+ // never trap, so the `_sat_` form deliberately fails this pattern.
88
+ if (/^i(32|64)\.trunc_f(32|64)_[su]$/.test(m))
89
+ return [TRUNC_INVALID, TRUNC_OVERFLOW];
90
+ // Memory accesses: every load / store variant, including SIMD
91
+ // lane/splat loads (`v128.load32_zero`, `v128.store8_lane`, …).
92
+ if (/\.(load|store)/.test(m))
93
+ return [MEM_OOB];
94
+ if (/^memory\.(init|copy|fill)$/.test(m))
95
+ return [MEM_RANGE_OOB];
96
+ // Table accesses.
97
+ if (/^table\.(get|set)$/.test(m))
98
+ return [TABLE_OOB];
99
+ if (/^table\.(init|copy|fill)$/.test(m))
100
+ return [TABLE_RANGE_OOB];
101
+ // Indirect call: out-of-bounds index, null element, type mismatch.
102
+ if (m === "call_indirect")
103
+ return [CALL_INDIRECT_UNDEF, CALL_INDIRECT_UNINIT, CALL_INDIRECT_TYPE];
104
+ // Reference instructions with a single null-dereference trap.
105
+ if (m === "ref.as_non_null")
106
+ return [nullRef("operand is a null reference")];
107
+ if (m === "call_ref")
108
+ return [nullRef("the function reference operand is null")];
109
+ if (/^struct\.(get|get_s|get_u|set)$/.test(m))
110
+ return [nullRef("the struct reference operand is null")];
111
+ return [];
112
+ }
@@ -5,6 +5,8 @@ export interface InstructionSummary {
5
5
  opcodes: number[];
6
6
  category: InstructionCategory;
7
7
  version: string;
8
+ /** Whether this instruction can trap at runtime (see instruction_get for conditions). */
9
+ can_trap: boolean;
8
10
  }
9
11
  export declare function toSummary(r: InstructionRecord): InstructionSummary;
10
12
  /** Format a byte array as the conventional space-separated hex string,
@@ -29,6 +31,8 @@ export interface ListFilter {
29
31
  version?: string;
30
32
  /** Substring matched against the mnemonic prefix, case-insensitive. */
31
33
  prefix?: string;
34
+ /** When set, keep only instructions that can (true) / cannot (false) trap. */
35
+ can_trap?: boolean;
32
36
  }
33
37
  /** Enumerate instructions, optionally filtered, sorted by opcode. */
34
38
  export declare function listInstructions(records: InstructionRecord[], filter?: ListFilter): InstructionSummary[];
@@ -4,7 +4,13 @@
4
4
  // the Worker can bundle it directly. Callers pass in the already
5
5
  // loaded instruction records.
6
6
  export function toSummary(r) {
7
- return { mnemonic: r.mnemonic, opcodes: r.opcodes, category: r.category, version: r.version };
7
+ return {
8
+ mnemonic: r.mnemonic,
9
+ opcodes: r.opcodes,
10
+ category: r.category,
11
+ version: r.version,
12
+ can_trap: r.can_trap,
13
+ };
8
14
  }
9
15
  /** Format a byte array as the conventional space-separated hex string,
10
16
  * e.g. `[0xFD, 0x89, 0x02]` → `"0xfd 0x89 0x02"`. */
@@ -68,6 +74,8 @@ export function listInstructions(records, filter = {}) {
68
74
  const p = filter.prefix.toLowerCase();
69
75
  out = out.filter((r) => r.mnemonic.toLowerCase().startsWith(p));
70
76
  }
77
+ if (filter.can_trap !== undefined)
78
+ out = out.filter((r) => r.can_trap === filter.can_trap);
71
79
  return [...out]
72
80
  .sort((a, b) => compareOpcodes(a.opcodes, b.opcodes))
73
81
  .map(toSummary);
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "wasm-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "mcpName": "io.github.xyzzylabs/wasm-mcp",
5
5
  "private": false,
6
- "description": "Unofficial MCP server for the WebAssembly core specification — SHA-pinned instructions, types, sections, and search. Not affiliated with the W3C WebAssembly CG.",
6
+ "description": "MCP server for the WebAssembly core specification — SHA-pinned instructions, types, sections, and search. Not affiliated with the W3C WebAssembly CG.",
7
7
  "type": "module",
8
8
  "bin": {
9
9
  "wasm-mcp": "dist/mcp/server.js"