wasm-mcp 0.1.2 → 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.
- package/CHANGELOG.md +19 -0
- package/README.md +11 -6
- package/build/wasm-spec-core-main.json +1 -1
- package/dist/mcp/instructions.js +2 -2
- package/dist/mcp/tool_meta.js +2 -2
- package/dist/mcp/tools/instruction_list.d.ts +14 -0
- package/dist/mcp/tools/instruction_list.js +10 -0
- package/dist/parser/instructions.d.ts +20 -0
- package/dist/parser/instructions.js +6 -0
- package/dist/parser/traps.d.ts +12 -0
- package/dist/parser/traps.js +112 -0
- package/dist/spec/instructions_query.d.ts +4 -0
- package/dist/spec/instructions_query.js +9 -1
- package/package.json +2 -2
package/dist/mcp/instructions.js
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
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
|
package/dist/mcp/tool_meta.js
CHANGED
|
@@ -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,
|
|
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),
|
|
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 {
|
|
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.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"mcpName": "io.github.xyzzylabs/wasm-mcp",
|
|
5
5
|
"private": false,
|
|
6
|
-
"description": "
|
|
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"
|