@secretgenerator/mcp 2.0.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/README.md +87 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +342 -0
- package/dist/index.js.map +1 -0
- package/dist/smoke.d.ts +1 -0
- package/dist/smoke.js +48 -0
- package/dist/smoke.js.map +1 -0
- package/dist/wasm.d.ts +57 -0
- package/dist/wasm.js +78 -0
- package/dist/wasm.js.map +1 -0
- package/package.json +48 -0
- package/wasm/keygen.wasm +0 -0
- package/wasm/wasm_exec.js +559 -0
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# @secretgenerator/mcp
|
|
2
|
+
|
|
3
|
+
MCP server exposing [secretgenerator](https://secretgenerator.org) credential
|
|
4
|
+
generation primitives to Claude, Cursor, Cline, Zed, Continue.dev, and any
|
|
5
|
+
other [Model Context Protocol](https://modelcontextprotocol.io/) client.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
LLMs cannot uniformly sample randomness. Asked to generate a strong API key,
|
|
10
|
+
they tend to produce strings with ~20 bits of effective entropy and substantial
|
|
11
|
+
collision rates ([Irregular Security, 2025](https://www.csoonline.com/article/4155166/llm-generated-passwords-are-indefensible-your-codebase-may-already-prove-it.html)).
|
|
12
|
+
This package gives the agent a tool that delegates the sampling to a CSPRNG
|
|
13
|
+
and returns a versioned, auditable JSON record describing the credential.
|
|
14
|
+
|
|
15
|
+
The same Go code that backs the [secretgenerator CLI](https://github.com/rafaelperoco/secretgenerator)
|
|
16
|
+
is compiled to WebAssembly and run inside Node. There is no network call,
|
|
17
|
+
no shelled-out binary; the secret is generated locally inside the MCP server
|
|
18
|
+
process and streamed to the agent.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
### Claude Code / Claude Desktop
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
claude mcp add secretgenerator -- npx -y @secretgenerator/mcp@latest
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Cursor / Cline / Zed / Continue.dev / etc.
|
|
29
|
+
|
|
30
|
+
Add to your client's MCP config (commonly `~/.config/<client>/mcp.json`):
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"secretgenerator": {
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": ["-y", "@secretgenerator/mcp@latest"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The first invocation downloads the package (~600 KB including the WASM
|
|
44
|
+
bundle); subsequent invocations are cached.
|
|
45
|
+
|
|
46
|
+
## Tools exposed
|
|
47
|
+
|
|
48
|
+
| tool | when to use |
|
|
49
|
+
| ------------------------ | -------------------------------------------------------- |
|
|
50
|
+
| `generate_password` | general-purpose passwords, named charsets |
|
|
51
|
+
| `generate_passphrase` | human-memorable secrets, EFF Large Wordlist (8 words ≈ 103 bits) |
|
|
52
|
+
| `generate_secret` | API tokens, OAuth secrets, JWT keys (recommended for agents) |
|
|
53
|
+
| `generate_api_key` | `prefix_base62` Stripe-style tokens |
|
|
54
|
+
| `generate_pin` | numeric PINs with weak-pattern rejection |
|
|
55
|
+
| `assess_entropy` | estimate strength of an existing password |
|
|
56
|
+
| `list_attacker_profiles` | enumerate the 5 named cracking-rate scenarios |
|
|
57
|
+
| `estimate_crack_time` | time-to-break under each attacker profile |
|
|
58
|
+
|
|
59
|
+
Each `generate_*` tool returns a [schema-v1 JSON record](https://secretgenerator.org/schemas/output-v1.json):
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"schema_version": 1,
|
|
64
|
+
"password": "Ay7-Kx9mQ-...",
|
|
65
|
+
"length": 24,
|
|
66
|
+
"charset_id": "alphanum-symbols-v1",
|
|
67
|
+
"entropy_bits": 156.9,
|
|
68
|
+
"algorithm": "crypto/rand+rejection-sampling",
|
|
69
|
+
"subcommand": "password",
|
|
70
|
+
"request_id": "f4c54f9c-0f57-4d58-...",
|
|
71
|
+
"timestamp_utc": "2026-05-02T21:53:34.746Z"
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Auditability
|
|
76
|
+
|
|
77
|
+
The WASM module shipped with this package is built from the
|
|
78
|
+
[secretgenerator source](https://github.com/rafaelperoco/secretgenerator/tree/main/web/wasm)
|
|
79
|
+
under `web/wasm/`. Every release of this package matches a tagged release
|
|
80
|
+
of the CLI; the WASM bundle is reproducible from the source via TinyGo.
|
|
81
|
+
|
|
82
|
+
For the full verification chain (cosign signatures, SLSA provenance, SBOM),
|
|
83
|
+
see [docs/AUDIT.md](https://github.com/rafaelperoco/secretgenerator/blob/main/docs/AUDIT.md).
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT. Source: [github.com/rafaelperoco/secretgenerator](https://github.com/rafaelperoco/secretgenerator).
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// secretgenerator MCP server. Exposes the same generation primitives as
|
|
3
|
+
// the secretgenerator CLI (and as the secretgenerator.org web generator)
|
|
4
|
+
// via the Model Context Protocol so any MCP-aware client (Claude Desktop,
|
|
5
|
+
// Claude Code, Cursor, Cline, Zed, Continue.dev, ...) can call them.
|
|
6
|
+
//
|
|
7
|
+
// Why this matters: LLMs cannot uniformly sample randomness. When asked to
|
|
8
|
+
// "generate an API key", they tend to produce strings with ~20 bits of
|
|
9
|
+
// effective entropy and substantial collision rates (Irregular Security,
|
|
10
|
+
// 2025). Exposing this tool via MCP means the agent can delegate the
|
|
11
|
+
// sampling to a CSPRNG-backed primitive and audit the result via a
|
|
12
|
+
// versioned JSON schema.
|
|
13
|
+
//
|
|
14
|
+
// Install:
|
|
15
|
+
// claude mcp add secretgenerator -- npx -y @secretgenerator/mcp@latest
|
|
16
|
+
//
|
|
17
|
+
// All generation is local. No network calls. The Go code that backs the
|
|
18
|
+
// CLI is compiled to WASM and run in a Node sandbox.
|
|
19
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
20
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
21
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
22
|
+
import { loadBindings, unwrap } from "./wasm.js";
|
|
23
|
+
const server = new Server({
|
|
24
|
+
name: "secretgenerator",
|
|
25
|
+
version: "2.0.0",
|
|
26
|
+
}, {
|
|
27
|
+
capabilities: {
|
|
28
|
+
tools: {},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
// ─── Tool catalog ──────────────────────────────────────────────────────────
|
|
32
|
+
//
|
|
33
|
+
// Tool names use snake_case (MCP convention). Descriptions intentionally
|
|
34
|
+
// long: agents read these to decide when to use a tool, so being explicit
|
|
35
|
+
// about *when* to pick each subcommand prevents the model from picking
|
|
36
|
+
// `generate_password` when `generate_secret` is the right call for an API
|
|
37
|
+
// token, or vice versa.
|
|
38
|
+
const TOOLS = [
|
|
39
|
+
{
|
|
40
|
+
name: "generate_password",
|
|
41
|
+
description: "Generate a high-entropy random password from a named character set. " +
|
|
42
|
+
"Use this for general-purpose passwords (user accounts, encrypted vaults, " +
|
|
43
|
+
"manual logins). For machine-to-machine tokens prefer `generate_secret`. " +
|
|
44
|
+
"For human-memorable secrets prefer `generate_passphrase`. " +
|
|
45
|
+
"Default: 20 characters from `alphanum-v1` (~119 bits of entropy). " +
|
|
46
|
+
"Returns a schema-v1 JSON record describing the credential and its provenance.",
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
length: {
|
|
51
|
+
type: "integer",
|
|
52
|
+
minimum: 1,
|
|
53
|
+
description: "Length of the password in characters. Default 20.",
|
|
54
|
+
},
|
|
55
|
+
charsetId: {
|
|
56
|
+
type: "string",
|
|
57
|
+
description: "Named, versioned character set. Common choices: `alphanum-v1` (a-z A-Z 0-9), " +
|
|
58
|
+
"`alphanum-symbols-v1` (adds !@#$...), `lower-v1`, `upper-v1`, " +
|
|
59
|
+
"`digit-v1`, `hex-v1`, `base62-v1`, `numeric-v1`. Default `alphanum-v1`.",
|
|
60
|
+
},
|
|
61
|
+
exclude: {
|
|
62
|
+
type: "string",
|
|
63
|
+
description: "Characters to remove from the charset BEFORE generation. The output " +
|
|
64
|
+
"length is still honored; the charset just shrinks. Useful for excluding " +
|
|
65
|
+
"look-alikes like `0Ol1iI` when humans will type the password.",
|
|
66
|
+
},
|
|
67
|
+
requiredClasses: {
|
|
68
|
+
type: "string",
|
|
69
|
+
description: "Comma-separated classes the output is guaranteed to contain. " +
|
|
70
|
+
"Valid values: `lower`, `upper`, `digit`, `symbol`. " +
|
|
71
|
+
"Example: `lower,upper,digit,symbol`. Generation uses rejection sampling " +
|
|
72
|
+
"until all required classes are present.",
|
|
73
|
+
},
|
|
74
|
+
minEntropyBits: {
|
|
75
|
+
type: "number",
|
|
76
|
+
description: "Minimum acceptable Shannon entropy in bits. Default 80. Set to 0 to disable. " +
|
|
77
|
+
"If the requested length × log2(charset size) is below this floor and " +
|
|
78
|
+
"`allowWeak` is false, the call fails with a clear error.",
|
|
79
|
+
},
|
|
80
|
+
allowWeak: {
|
|
81
|
+
type: "boolean",
|
|
82
|
+
description: "If true, permit generation below the entropy floor. The result will " +
|
|
83
|
+
"include a `warnings` array documenting that the floor was bypassed. " +
|
|
84
|
+
"Default false.",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
additionalProperties: false,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "generate_passphrase",
|
|
92
|
+
description: "Generate a diceware-style passphrase using the EFF Large Wordlist (7776 words, " +
|
|
93
|
+
"~12.92 bits per word). Use this for human-memorable secrets like master " +
|
|
94
|
+
"passwords, encryption recovery phrases, or anything a human will type. " +
|
|
95
|
+
"Default: 8 words (~103 bits) joined by hyphens — Reinhold's `secure-through-2050` " +
|
|
96
|
+
"threshold. Returns a schema-v1 JSON record.",
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: "object",
|
|
99
|
+
properties: {
|
|
100
|
+
words: {
|
|
101
|
+
type: "integer",
|
|
102
|
+
minimum: 1,
|
|
103
|
+
description: "Number of words. 6 = EFF minimum (~78 bits). 8 = secure-through-2050 (~103 bits). " +
|
|
104
|
+
"10 = wallet-grade (~129 bits). Default 8.",
|
|
105
|
+
},
|
|
106
|
+
separator: {
|
|
107
|
+
type: "string",
|
|
108
|
+
description: "String joining consecutive words. Default `-`. Hyphen is shell/URL/env-file safe. " +
|
|
109
|
+
"Empty string is rejected (would let adjacent words fuse and lose entropy).",
|
|
110
|
+
},
|
|
111
|
+
capitalize: {
|
|
112
|
+
type: "boolean",
|
|
113
|
+
description: "Compatibility flag for sites mandating uppercase. Capitalizes the first letter " +
|
|
114
|
+
"of each word. The result includes a warning that this adds ~0 bits against real " +
|
|
115
|
+
"attackers (Title-Case is in every Hashcat ruleset). Prefer adding a word instead. " +
|
|
116
|
+
"Default false.",
|
|
117
|
+
},
|
|
118
|
+
digitSuffix: {
|
|
119
|
+
type: "boolean",
|
|
120
|
+
description: "Compatibility flag for sites mandating a digit. Appends a single random digit " +
|
|
121
|
+
"(adds log2(10) ≈ 3.32 bits). The result includes a warning. " +
|
|
122
|
+
"Default false.",
|
|
123
|
+
},
|
|
124
|
+
minEntropyBits: {
|
|
125
|
+
type: "number",
|
|
126
|
+
description: "Default 80. See `generate_password`.",
|
|
127
|
+
},
|
|
128
|
+
allowWeak: {
|
|
129
|
+
type: "boolean",
|
|
130
|
+
description: "See `generate_password`. Default false.",
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
additionalProperties: false,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "generate_secret",
|
|
138
|
+
description: "Generate a high-entropy machine-readable secret from raw CSPRNG bytes. " +
|
|
139
|
+
"**Recommended for API tokens, OAuth client secrets, JWT signing keys, and any " +
|
|
140
|
+
"machine-to-machine credential** — humans never see this string, so we don't waste " +
|
|
141
|
+
"entropy on memorable charsets. Default: 32 bytes (256 bits) encoded as URL-safe " +
|
|
142
|
+
"base64 without padding (43 characters). Returns a schema-v1 JSON record.",
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {
|
|
146
|
+
bytes: {
|
|
147
|
+
type: "integer",
|
|
148
|
+
minimum: 1,
|
|
149
|
+
description: "Number of random bytes (8 bits each). 16 = 128 bits, 32 = 256 bits, " +
|
|
150
|
+
"64 = 512 bits. Default 32.",
|
|
151
|
+
},
|
|
152
|
+
encoding: {
|
|
153
|
+
type: "string",
|
|
154
|
+
enum: ["base64url", "base64", "base32", "hex"],
|
|
155
|
+
description: "Output encoding. `base64url` is URL/header/env-file safe (default). " +
|
|
156
|
+
"`base64` includes / and + (safe in JSON, not URLs). `base32` is shorter " +
|
|
157
|
+
"alphabet for case-insensitive systems. `hex` for systems that need only " +
|
|
158
|
+
"ASCII letters and digits.",
|
|
159
|
+
},
|
|
160
|
+
prefix: {
|
|
161
|
+
type: "string",
|
|
162
|
+
description: "Static prefix prepended to the encoded body. Does not contribute to entropy. " +
|
|
163
|
+
"Useful for namespacing (e.g. `sk_live_`, `prod_`).",
|
|
164
|
+
},
|
|
165
|
+
minEntropyBits: {
|
|
166
|
+
type: "number",
|
|
167
|
+
description: "Default 128 — the NIST SP 800-131A 2030 target for symmetric-key strength. " +
|
|
168
|
+
"Set to 0 to disable.",
|
|
169
|
+
},
|
|
170
|
+
allowWeak: {
|
|
171
|
+
type: "boolean",
|
|
172
|
+
description: "See `generate_password`. Default false.",
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
additionalProperties: false,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: "generate_api_key",
|
|
180
|
+
description: "Generate a token in the `<prefix><separator><base62>` form used by Stripe " +
|
|
181
|
+
"(`sk_live_...`), GitHub (`ghp_...`), Slack (`xoxb-...`), Anthropic (`sk-ant-...`), " +
|
|
182
|
+
"and most modern SaaS APIs. Default: `sk_<32 base62 chars>` (~190 bits). " +
|
|
183
|
+
"The base62 body is drawn uniformly from a CSPRNG; the prefix is a static identifier " +
|
|
184
|
+
"and contributes zero entropy. Returns a schema-v1 JSON record.",
|
|
185
|
+
inputSchema: {
|
|
186
|
+
type: "object",
|
|
187
|
+
properties: {
|
|
188
|
+
prefix: {
|
|
189
|
+
type: "string",
|
|
190
|
+
description: "Static identifier (e.g. `sk`, `ghp`, `xoxb`, `sk-ant`). Whitespace is rejected.",
|
|
191
|
+
},
|
|
192
|
+
separator: {
|
|
193
|
+
type: "string",
|
|
194
|
+
description: "Between prefix and body. Default `_`.",
|
|
195
|
+
},
|
|
196
|
+
length: {
|
|
197
|
+
type: "integer",
|
|
198
|
+
minimum: 1,
|
|
199
|
+
description: "Length of the base62 body in characters. Default 32 (~190 bits).",
|
|
200
|
+
},
|
|
201
|
+
minEntropyBits: {
|
|
202
|
+
type: "number",
|
|
203
|
+
description: "Default 128. See `generate_secret`.",
|
|
204
|
+
},
|
|
205
|
+
allowWeak: {
|
|
206
|
+
type: "boolean",
|
|
207
|
+
description: "See `generate_password`. Default false.",
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
additionalProperties: false,
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: "generate_pin",
|
|
215
|
+
description: "Generate a numeric PIN with weak-pattern rejection. PINs are intrinsically low " +
|
|
216
|
+
"entropy (a 6-digit PIN is ~19.9 bits) and **safe only when the verifier enforces " +
|
|
217
|
+
"rate limiting** (banking apps, hardware tokens). Never use a PIN as a standalone " +
|
|
218
|
+
"authenticator. The tool requires `acknowledgeLowEntropy: true` to even generate one. " +
|
|
219
|
+
"Rejects all-same-digit, strict ascending/descending sequences, short repetitions, " +
|
|
220
|
+
"the top-20 DataGenetics 2012 most-common PINs, and calendar-year patterns.",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
digits: {
|
|
225
|
+
type: "integer",
|
|
226
|
+
minimum: 4,
|
|
227
|
+
description: "Number of digits. Must be >= 4. Default 6.",
|
|
228
|
+
},
|
|
229
|
+
acknowledgeLowEntropy: {
|
|
230
|
+
type: "boolean",
|
|
231
|
+
description: "**Required.** The caller must explicitly acknowledge that PINs are " +
|
|
232
|
+
"low-entropy and only safe with verifier-side rate limiting.",
|
|
233
|
+
},
|
|
234
|
+
allowWeakPattern: {
|
|
235
|
+
type: "boolean",
|
|
236
|
+
description: "If true, permit PINs matching weak patterns (NOT RECOMMENDED). Default false.",
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
required: ["acknowledgeLowEntropy"],
|
|
240
|
+
additionalProperties: false,
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "assess_entropy",
|
|
245
|
+
description: "Estimate the entropy of an EXISTING password. Returns the Shannon entropy in bits " +
|
|
246
|
+
"assuming each character was drawn uniformly from the observed character classes — " +
|
|
247
|
+
"an UPPER BOUND. Real entropy is lower if the password follows a memorable pattern " +
|
|
248
|
+
"(dictionary word, year, name). Use this when validating user-supplied passwords " +
|
|
249
|
+
"or auditing legacy credentials. Output never echoes the password in plaintext, only " +
|
|
250
|
+
"its length, observed classes, and computed entropy.",
|
|
251
|
+
inputSchema: {
|
|
252
|
+
type: "object",
|
|
253
|
+
properties: {
|
|
254
|
+
password: {
|
|
255
|
+
type: "string",
|
|
256
|
+
description: "The password to assess. Not echoed in the response.",
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
required: ["password"],
|
|
260
|
+
additionalProperties: false,
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "list_attacker_profiles",
|
|
265
|
+
description: "Return the named attacker profiles used to estimate time-to-break for a given " +
|
|
266
|
+
"entropy. Profiles span 13 orders of magnitude: `online-throttled-v1` " +
|
|
267
|
+
"(rate-limited login API, ~100 g/s), `slow-kdf-v1` (Argon2id at OWASP defaults, " +
|
|
268
|
+
"~1k g/s), `bcrypt-cost12-v1` (single RTX 4090, ~50k g/s), `fast-hash-single-gpu-v1` " +
|
|
269
|
+
"(~1e11 g/s), `nation-state-v1` (10k GPUs against fast hash, ~1e15 g/s). " +
|
|
270
|
+
"Profile IDs are versioned; updating a rate is a breaking change.",
|
|
271
|
+
inputSchema: {
|
|
272
|
+
type: "object",
|
|
273
|
+
properties: {},
|
|
274
|
+
additionalProperties: false,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: "estimate_crack_time",
|
|
279
|
+
description: "Compute time-to-break estimates for a given entropy in bits across all attacker " +
|
|
280
|
+
"profiles. Returns an array of {profile_id, description, seconds, human_readable}. " +
|
|
281
|
+
"Use after `generate_*` or `assess_entropy` to surface a human-readable strength " +
|
|
282
|
+
"summary like '7.2e+08 times the age of the universe'.",
|
|
283
|
+
inputSchema: {
|
|
284
|
+
type: "object",
|
|
285
|
+
properties: {
|
|
286
|
+
entropyBits: {
|
|
287
|
+
type: "number",
|
|
288
|
+
minimum: 0,
|
|
289
|
+
description: "The credential's entropy in bits.",
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
required: ["entropyBits"],
|
|
293
|
+
additionalProperties: false,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
];
|
|
297
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
298
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
299
|
+
const sgen = await loadBindings();
|
|
300
|
+
const args = (req.params.arguments ?? {});
|
|
301
|
+
const handlers = {
|
|
302
|
+
generate_password: () => unwrap(sgen.password(args)),
|
|
303
|
+
generate_passphrase: () => unwrap(sgen.passphrase(args)),
|
|
304
|
+
generate_secret: () => unwrap(sgen.secret(args)),
|
|
305
|
+
generate_api_key: () => unwrap(sgen.apiKey(args)),
|
|
306
|
+
generate_pin: () => unwrap(sgen.pin(args)),
|
|
307
|
+
assess_entropy: () => unwrap(sgen.entropy(args)),
|
|
308
|
+
list_attacker_profiles: () => sgen.attackerProfiles(),
|
|
309
|
+
estimate_crack_time: () => sgen.crackTimes(Number(args.entropyBits)),
|
|
310
|
+
};
|
|
311
|
+
const handler = handlers[req.params.name];
|
|
312
|
+
if (!handler) {
|
|
313
|
+
throw new Error(`unknown tool: ${req.params.name}`);
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
const result = handler();
|
|
317
|
+
return {
|
|
318
|
+
content: [
|
|
319
|
+
{
|
|
320
|
+
type: "text",
|
|
321
|
+
text: JSON.stringify(result, null, 2),
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
328
|
+
return {
|
|
329
|
+
content: [
|
|
330
|
+
{
|
|
331
|
+
type: "text",
|
|
332
|
+
text: JSON.stringify({ error: msg }, null, 2),
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
isError: true,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
const transport = new StdioServerTransport();
|
|
340
|
+
await server.connect(transport);
|
|
341
|
+
// Keep the process alive; the SDK manages the stdio loop.
|
|
342
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/smoke.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/smoke.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Smoke test: load the WASM, generate one credential of each kind, print to
|
|
2
|
+
// stdout. Used by CI to verify the package bundle is functional without
|
|
3
|
+
// pulling in the full MCP transport. Run with: `npm run smoke`.
|
|
4
|
+
import { loadBindings, unwrap } from "./wasm.js";
|
|
5
|
+
async function main() {
|
|
6
|
+
const sgen = await loadBindings();
|
|
7
|
+
console.log(`secretgen ${sgen.version} schema_version=${sgen.schemaVersion}`);
|
|
8
|
+
const checks = [
|
|
9
|
+
["password", () => unwrap(sgen.password({}))],
|
|
10
|
+
["passphrase", () => unwrap(sgen.passphrase({}))],
|
|
11
|
+
["secret", () => unwrap(sgen.secret({}))],
|
|
12
|
+
[
|
|
13
|
+
"api-key",
|
|
14
|
+
() => unwrap(sgen.apiKey({ prefix: "sk", separator: "_", length: 32 })),
|
|
15
|
+
],
|
|
16
|
+
[
|
|
17
|
+
"pin",
|
|
18
|
+
() => unwrap(sgen.pin({ digits: 6, acknowledgeLowEntropy: true })),
|
|
19
|
+
],
|
|
20
|
+
[
|
|
21
|
+
"entropy",
|
|
22
|
+
() => unwrap(sgen.entropy({ password: "Tr0ub4dor&3" })),
|
|
23
|
+
],
|
|
24
|
+
];
|
|
25
|
+
for (const [name, fn] of checks) {
|
|
26
|
+
try {
|
|
27
|
+
const r = fn();
|
|
28
|
+
const preview = r.password
|
|
29
|
+
? r.password.length > 24
|
|
30
|
+
? r.password.slice(0, 12) + "…" + r.password.slice(-8)
|
|
31
|
+
: r.password
|
|
32
|
+
: "(no plaintext)";
|
|
33
|
+
console.log(` ${name.padEnd(11)} ${preview.padEnd(24)} ${r.entropy_bits.toFixed(1)} bits`);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
console.error(` ${name} FAILED: ${err instanceof Error ? err.message : err}`);
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const cracks = sgen.crackTimes(128);
|
|
41
|
+
console.log(`crackTimes(128) → ${cracks.length} profiles`);
|
|
42
|
+
process.exit(process.exitCode ?? 0);
|
|
43
|
+
}
|
|
44
|
+
main().catch((err) => {
|
|
45
|
+
console.error(err);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=smoke.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.js","sourceRoot":"","sources":["../src/smoke.ts"],"names":[],"mappings":"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"}
|
package/dist/wasm.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type CrackTimeEstimate = {
|
|
2
|
+
profile_id: string;
|
|
3
|
+
description: string;
|
|
4
|
+
seconds: number;
|
|
5
|
+
human_readable: string;
|
|
6
|
+
};
|
|
7
|
+
export type Result = {
|
|
8
|
+
schema_version: number;
|
|
9
|
+
password?: string;
|
|
10
|
+
length: number;
|
|
11
|
+
charset_id: string;
|
|
12
|
+
charset_size: number;
|
|
13
|
+
entropy_bits: number;
|
|
14
|
+
excluded_count?: number;
|
|
15
|
+
excluded_sha256?: string;
|
|
16
|
+
required_classes?: string;
|
|
17
|
+
algorithm: string;
|
|
18
|
+
subcommand: string;
|
|
19
|
+
version: string;
|
|
20
|
+
commit: string;
|
|
21
|
+
build_date: string;
|
|
22
|
+
request_id: string;
|
|
23
|
+
timestamp_utc: string;
|
|
24
|
+
warnings?: string[];
|
|
25
|
+
crack_time_estimates?: CrackTimeEstimate[];
|
|
26
|
+
};
|
|
27
|
+
type ResultOrError = Result | {
|
|
28
|
+
error: string;
|
|
29
|
+
};
|
|
30
|
+
export type AttackerProfile = {
|
|
31
|
+
id: string;
|
|
32
|
+
description: string;
|
|
33
|
+
guesses_per_second: number;
|
|
34
|
+
};
|
|
35
|
+
export type GoBindings = {
|
|
36
|
+
password: (opts: Record<string, unknown>) => ResultOrError;
|
|
37
|
+
secret: (opts: Record<string, unknown>) => ResultOrError;
|
|
38
|
+
passphrase: (opts: Record<string, unknown>) => ResultOrError;
|
|
39
|
+
apiKey: (opts: Record<string, unknown>) => ResultOrError;
|
|
40
|
+
pin: (opts: Record<string, unknown>) => ResultOrError;
|
|
41
|
+
entropy: (opts: Record<string, unknown>) => ResultOrError;
|
|
42
|
+
charsets: () => string[];
|
|
43
|
+
attackerProfiles: () => AttackerProfile[];
|
|
44
|
+
crackTimes: (bits: number) => CrackTimeEstimate[];
|
|
45
|
+
schemaVersion: number;
|
|
46
|
+
version: string;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Load and instantiate the WASM module. Idempotent: subsequent calls
|
|
50
|
+
* return the cached bindings without re-instantiating.
|
|
51
|
+
*/
|
|
52
|
+
export declare function loadBindings(): Promise<GoBindings>;
|
|
53
|
+
/** Throw on the {error: ...} shape; return Result on success. */
|
|
54
|
+
export declare function unwrap<T>(r: T | {
|
|
55
|
+
error: string;
|
|
56
|
+
}): T;
|
|
57
|
+
export {};
|
package/dist/wasm.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Node-side loader for keygen.wasm. Mirrors web/src/lib/wasm.ts but reads
|
|
2
|
+
// the binary from disk and uses globalThis instead of window.
|
|
3
|
+
//
|
|
4
|
+
// TinyGo emits a wasm_exec.js shim that, when evaluated, attaches a `Go`
|
|
5
|
+
// constructor to the host global. We evaluate it in a fresh module scope
|
|
6
|
+
// using the vm module so we don't pollute the user's globalThis.
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import vm from "node:vm";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
// dist/ sits at mcp/dist/, wasm/ sits at mcp/wasm/.
|
|
13
|
+
const wasmDir = path.resolve(__dirname, "..", "wasm");
|
|
14
|
+
let bindings = null;
|
|
15
|
+
let initPromise = null;
|
|
16
|
+
/**
|
|
17
|
+
* Load and instantiate the WASM module. Idempotent: subsequent calls
|
|
18
|
+
* return the cached bindings without re-instantiating.
|
|
19
|
+
*/
|
|
20
|
+
export function loadBindings() {
|
|
21
|
+
if (bindings)
|
|
22
|
+
return Promise.resolve(bindings);
|
|
23
|
+
if (initPromise)
|
|
24
|
+
return initPromise;
|
|
25
|
+
initPromise = (async () => {
|
|
26
|
+
const execJs = readFileSync(path.join(wasmDir, "wasm_exec.js"), "utf8");
|
|
27
|
+
const sandbox = {
|
|
28
|
+
// Bridge whatever wasm_exec.js needs from the real globalThis without
|
|
29
|
+
// letting it overwrite ours.
|
|
30
|
+
Go: undefined,
|
|
31
|
+
globalThis: undefined,
|
|
32
|
+
console,
|
|
33
|
+
crypto: globalThis.crypto,
|
|
34
|
+
performance: globalThis.performance,
|
|
35
|
+
TextEncoder: globalThis.TextEncoder,
|
|
36
|
+
TextDecoder: globalThis.TextDecoder,
|
|
37
|
+
fetch: globalThis.fetch,
|
|
38
|
+
Date,
|
|
39
|
+
Math,
|
|
40
|
+
Promise,
|
|
41
|
+
// Some shims look for `window`; alias to the sandbox itself.
|
|
42
|
+
window: undefined,
|
|
43
|
+
};
|
|
44
|
+
sandbox.globalThis = sandbox;
|
|
45
|
+
sandbox.window = sandbox;
|
|
46
|
+
vm.createContext(sandbox);
|
|
47
|
+
vm.runInContext(execJs, sandbox, { filename: "wasm_exec.js" });
|
|
48
|
+
if (!sandbox.Go) {
|
|
49
|
+
throw new Error("wasm_exec.js loaded but Go constructor not exposed");
|
|
50
|
+
}
|
|
51
|
+
const go = new sandbox.Go();
|
|
52
|
+
// Compile and instantiate the WASM module.
|
|
53
|
+
const wasmBytes = readFileSync(path.join(wasmDir, "keygen.wasm"));
|
|
54
|
+
const mod = await WebAssembly.instantiate(wasmBytes, go.importObject);
|
|
55
|
+
// TinyGo's main blocks forever to keep the syscall/js callback table
|
|
56
|
+
// alive — do not await. The bindings we want are attached to the
|
|
57
|
+
// sandbox's `secretgen` global synchronously by the time main() runs
|
|
58
|
+
// its first lines.
|
|
59
|
+
void go.run(mod.instance);
|
|
60
|
+
// Yield once so main() registers the bindings before we read them.
|
|
61
|
+
await new Promise((r) => setImmediate(r));
|
|
62
|
+
const sgen = sandbox.secretgen;
|
|
63
|
+
if (!sgen) {
|
|
64
|
+
throw new Error("WASM ran but did not expose `secretgen` bindings");
|
|
65
|
+
}
|
|
66
|
+
bindings = sgen;
|
|
67
|
+
return bindings;
|
|
68
|
+
})();
|
|
69
|
+
return initPromise;
|
|
70
|
+
}
|
|
71
|
+
/** Throw on the {error: ...} shape; return Result on success. */
|
|
72
|
+
export function unwrap(r) {
|
|
73
|
+
if (typeof r === "object" && r !== null && "error" in r) {
|
|
74
|
+
throw new Error(r.error);
|
|
75
|
+
}
|
|
76
|
+
return r;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=wasm.js.map
|
package/dist/wasm.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wasm.js","sourceRoot":"","sources":["../src/wasm.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@secretgenerator/mcp",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "MCP server exposing secretgenerator CSPRNG-backed credential generation tools to Claude, Cursor, Cline, Zed, and other MCP-aware clients.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://secretgenerator.org",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/rafaelperoco/secretgenerator.git",
|
|
11
|
+
"directory": "mcp"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"model-context-protocol",
|
|
16
|
+
"claude",
|
|
17
|
+
"ai-agent",
|
|
18
|
+
"secret-generation",
|
|
19
|
+
"password-generator",
|
|
20
|
+
"csprng",
|
|
21
|
+
"auditable"
|
|
22
|
+
],
|
|
23
|
+
"bin": {
|
|
24
|
+
"secretgenerator-mcp": "dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"main": "dist/index.js",
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"wasm",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"prebuild": "node scripts/copy-wasm.mjs",
|
|
34
|
+
"build": "tsc -p tsconfig.json",
|
|
35
|
+
"prepack": "npm run build",
|
|
36
|
+
"smoke": "node dist/smoke.js"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22.12.0"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^22.10.0",
|
|
46
|
+
"typescript": "^5.6.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/wasm/keygen.wasm
ADDED
|
Binary file
|
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
// Copyright 2018 The Go Authors. All rights reserved.
|
|
2
|
+
// Use of this source code is governed by a BSD-style
|
|
3
|
+
// license that can be found in the LICENSE file.
|
|
4
|
+
//
|
|
5
|
+
// This file has been modified for use by the TinyGo compiler.
|
|
6
|
+
|
|
7
|
+
(() => {
|
|
8
|
+
// Map multiple JavaScript environments to a single common API,
|
|
9
|
+
// preferring web standards over Node.js API.
|
|
10
|
+
//
|
|
11
|
+
// Environments considered:
|
|
12
|
+
// - Browsers
|
|
13
|
+
// - Node.js
|
|
14
|
+
// - Electron
|
|
15
|
+
// - Parcel
|
|
16
|
+
|
|
17
|
+
if (typeof global !== "undefined") {
|
|
18
|
+
// global already exists
|
|
19
|
+
} else if (typeof window !== "undefined") {
|
|
20
|
+
window.global = window;
|
|
21
|
+
} else if (typeof self !== "undefined") {
|
|
22
|
+
self.global = self;
|
|
23
|
+
} else {
|
|
24
|
+
throw new Error("cannot export Go (neither global, window nor self is defined)");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!global.require && typeof require !== "undefined") {
|
|
28
|
+
global.require = require;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!global.fs && global.require) {
|
|
32
|
+
global.fs = require("node:fs");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const enosys = () => {
|
|
36
|
+
const err = new Error("not implemented");
|
|
37
|
+
err.code = "ENOSYS";
|
|
38
|
+
return err;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (!global.fs) {
|
|
42
|
+
let outputBuf = "";
|
|
43
|
+
global.fs = {
|
|
44
|
+
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
|
|
45
|
+
writeSync(fd, buf) {
|
|
46
|
+
outputBuf += decoder.decode(buf);
|
|
47
|
+
const nl = outputBuf.lastIndexOf("\n");
|
|
48
|
+
if (nl != -1) {
|
|
49
|
+
console.log(outputBuf.substr(0, nl));
|
|
50
|
+
outputBuf = outputBuf.substr(nl + 1);
|
|
51
|
+
}
|
|
52
|
+
return buf.length;
|
|
53
|
+
},
|
|
54
|
+
write(fd, buf, offset, length, position, callback) {
|
|
55
|
+
if (offset !== 0 || length !== buf.length || position !== null) {
|
|
56
|
+
callback(enosys());
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const n = this.writeSync(fd, buf);
|
|
60
|
+
callback(null, n);
|
|
61
|
+
},
|
|
62
|
+
chmod(path, mode, callback) { callback(enosys()); },
|
|
63
|
+
chown(path, uid, gid, callback) { callback(enosys()); },
|
|
64
|
+
close(fd, callback) { callback(enosys()); },
|
|
65
|
+
fchmod(fd, mode, callback) { callback(enosys()); },
|
|
66
|
+
fchown(fd, uid, gid, callback) { callback(enosys()); },
|
|
67
|
+
fstat(fd, callback) { callback(enosys()); },
|
|
68
|
+
fsync(fd, callback) { callback(null); },
|
|
69
|
+
ftruncate(fd, length, callback) { callback(enosys()); },
|
|
70
|
+
lchown(path, uid, gid, callback) { callback(enosys()); },
|
|
71
|
+
link(path, link, callback) { callback(enosys()); },
|
|
72
|
+
lstat(path, callback) { callback(enosys()); },
|
|
73
|
+
mkdir(path, perm, callback) { callback(enosys()); },
|
|
74
|
+
open(path, flags, mode, callback) { callback(enosys()); },
|
|
75
|
+
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
|
|
76
|
+
readdir(path, callback) { callback(enosys()); },
|
|
77
|
+
readlink(path, callback) { callback(enosys()); },
|
|
78
|
+
rename(from, to, callback) { callback(enosys()); },
|
|
79
|
+
rmdir(path, callback) { callback(enosys()); },
|
|
80
|
+
stat(path, callback) { callback(enosys()); },
|
|
81
|
+
symlink(path, link, callback) { callback(enosys()); },
|
|
82
|
+
truncate(path, length, callback) { callback(enosys()); },
|
|
83
|
+
unlink(path, callback) { callback(enosys()); },
|
|
84
|
+
utimes(path, atime, mtime, callback) { callback(enosys()); },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!global.process) {
|
|
89
|
+
global.process = {
|
|
90
|
+
getuid() { return -1; },
|
|
91
|
+
getgid() { return -1; },
|
|
92
|
+
geteuid() { return -1; },
|
|
93
|
+
getegid() { return -1; },
|
|
94
|
+
getgroups() { throw enosys(); },
|
|
95
|
+
pid: -1,
|
|
96
|
+
ppid: -1,
|
|
97
|
+
umask() { throw enosys(); },
|
|
98
|
+
cwd() { throw enosys(); },
|
|
99
|
+
chdir() { throw enosys(); },
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!global.crypto) {
|
|
104
|
+
const nodeCrypto = require("node:crypto");
|
|
105
|
+
global.crypto = {
|
|
106
|
+
getRandomValues(b) {
|
|
107
|
+
nodeCrypto.randomFillSync(b);
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!global.performance) {
|
|
113
|
+
global.performance = {
|
|
114
|
+
now() {
|
|
115
|
+
const [sec, nsec] = process.hrtime();
|
|
116
|
+
return sec * 1000 + nsec / 1000000;
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!global.TextEncoder) {
|
|
122
|
+
global.TextEncoder = require("node:util").TextEncoder;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!global.TextDecoder) {
|
|
126
|
+
global.TextDecoder = require("node:util").TextDecoder;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// End of polyfills for common API.
|
|
130
|
+
|
|
131
|
+
const encoder = new TextEncoder("utf-8");
|
|
132
|
+
const decoder = new TextDecoder("utf-8");
|
|
133
|
+
let reinterpretBuf = new DataView(new ArrayBuffer(8));
|
|
134
|
+
var logLine = [];
|
|
135
|
+
const wasmExit = {}; // thrown to exit via proc_exit (not an error)
|
|
136
|
+
|
|
137
|
+
global.Go = class {
|
|
138
|
+
constructor() {
|
|
139
|
+
this._callbackTimeouts = new Map();
|
|
140
|
+
this._nextCallbackTimeoutID = 1;
|
|
141
|
+
|
|
142
|
+
const mem = () => {
|
|
143
|
+
// The buffer may change when requesting more memory.
|
|
144
|
+
return new DataView(this._inst.exports.memory.buffer);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const unboxValue = (v_ref) => {
|
|
148
|
+
reinterpretBuf.setBigInt64(0, v_ref, true);
|
|
149
|
+
const f = reinterpretBuf.getFloat64(0, true);
|
|
150
|
+
if (f === 0) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
if (!isNaN(f)) {
|
|
154
|
+
return f;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const id = v_ref & 0xffffffffn;
|
|
158
|
+
return this._values[id];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
const loadValue = (addr) => {
|
|
163
|
+
let v_ref = mem().getBigUint64(addr, true);
|
|
164
|
+
return unboxValue(v_ref);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const boxValue = (v) => {
|
|
168
|
+
const nanHead = 0x7FF80000n;
|
|
169
|
+
|
|
170
|
+
if (typeof v === "number") {
|
|
171
|
+
if (isNaN(v)) {
|
|
172
|
+
return nanHead << 32n;
|
|
173
|
+
}
|
|
174
|
+
if (v === 0) {
|
|
175
|
+
return (nanHead << 32n) | 1n;
|
|
176
|
+
}
|
|
177
|
+
reinterpretBuf.setFloat64(0, v, true);
|
|
178
|
+
return reinterpretBuf.getBigInt64(0, true);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
switch (v) {
|
|
182
|
+
case undefined:
|
|
183
|
+
return 0n;
|
|
184
|
+
case null:
|
|
185
|
+
return (nanHead << 32n) | 2n;
|
|
186
|
+
case true:
|
|
187
|
+
return (nanHead << 32n) | 3n;
|
|
188
|
+
case false:
|
|
189
|
+
return (nanHead << 32n) | 4n;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let id = this._ids.get(v);
|
|
193
|
+
if (id === undefined) {
|
|
194
|
+
id = this._idPool.pop();
|
|
195
|
+
if (id === undefined) {
|
|
196
|
+
id = BigInt(this._values.length);
|
|
197
|
+
}
|
|
198
|
+
this._values[id] = v;
|
|
199
|
+
this._goRefCounts[id] = 0;
|
|
200
|
+
this._ids.set(v, id);
|
|
201
|
+
}
|
|
202
|
+
this._goRefCounts[id]++;
|
|
203
|
+
let typeFlag = 1n;
|
|
204
|
+
switch (typeof v) {
|
|
205
|
+
case "string":
|
|
206
|
+
typeFlag = 2n;
|
|
207
|
+
break;
|
|
208
|
+
case "symbol":
|
|
209
|
+
typeFlag = 3n;
|
|
210
|
+
break;
|
|
211
|
+
case "function":
|
|
212
|
+
typeFlag = 4n;
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
return id | ((nanHead | typeFlag) << 32n);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const storeValue = (addr, v) => {
|
|
219
|
+
let v_ref = boxValue(v);
|
|
220
|
+
mem().setBigUint64(addr, v_ref, true);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const loadSlice = (array, len, cap) => {
|
|
224
|
+
return new Uint8Array(this._inst.exports.memory.buffer, array, len);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const loadSliceOfValues = (array, len, cap) => {
|
|
228
|
+
const a = new Array(len);
|
|
229
|
+
for (let i = 0; i < len; i++) {
|
|
230
|
+
a[i] = loadValue(array + i * 8);
|
|
231
|
+
}
|
|
232
|
+
return a;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const loadString = (ptr, len) => {
|
|
236
|
+
return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const timeOrigin = Date.now() - performance.now();
|
|
240
|
+
const wasi_EBADF = 8;
|
|
241
|
+
const wasi_ENOSYS = 52;
|
|
242
|
+
this.importObject = {
|
|
243
|
+
wasi_snapshot_preview1: {
|
|
244
|
+
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
|
|
245
|
+
fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) {
|
|
246
|
+
let nwritten = 0;
|
|
247
|
+
if (fd == 1) {
|
|
248
|
+
for (let iovs_i=0; iovs_i<iovs_len;iovs_i++) {
|
|
249
|
+
let iov_ptr = iovs_ptr+iovs_i*8; // assuming wasm32
|
|
250
|
+
let ptr = mem().getUint32(iov_ptr + 0, true);
|
|
251
|
+
let len = mem().getUint32(iov_ptr + 4, true);
|
|
252
|
+
nwritten += len;
|
|
253
|
+
for (let i=0; i<len; i++) {
|
|
254
|
+
let c = mem().getUint8(ptr+i);
|
|
255
|
+
if (c == 13) { // CR
|
|
256
|
+
// ignore
|
|
257
|
+
} else if (c == 10) { // LF
|
|
258
|
+
// write line
|
|
259
|
+
let line = decoder.decode(new Uint8Array(logLine));
|
|
260
|
+
logLine = [];
|
|
261
|
+
console.log(line);
|
|
262
|
+
} else {
|
|
263
|
+
logLine.push(c);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
console.error('invalid file descriptor:', fd);
|
|
269
|
+
}
|
|
270
|
+
mem().setUint32(nwritten_ptr, nwritten, true);
|
|
271
|
+
return 0;
|
|
272
|
+
},
|
|
273
|
+
fd_read: () => wasi_ENOSYS,
|
|
274
|
+
fd_close: () => wasi_ENOSYS,
|
|
275
|
+
fd_fdstat_get: () => wasi_ENOSYS,
|
|
276
|
+
fd_prestat_get: () => wasi_EBADF, // wasi-libc relies on this errno value
|
|
277
|
+
fd_prestat_dir_name: () => wasi_ENOSYS,
|
|
278
|
+
fd_seek: () => wasi_ENOSYS,
|
|
279
|
+
path_open: () => wasi_ENOSYS,
|
|
280
|
+
proc_exit: (code) => {
|
|
281
|
+
this.exited = true;
|
|
282
|
+
this.exitCode = code;
|
|
283
|
+
this._resolveExitPromise();
|
|
284
|
+
throw wasmExit;
|
|
285
|
+
},
|
|
286
|
+
random_get: (bufPtr, bufLen) => {
|
|
287
|
+
crypto.getRandomValues(loadSlice(bufPtr, bufLen));
|
|
288
|
+
return 0;
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
gojs: {
|
|
292
|
+
// func ticks() int64
|
|
293
|
+
"runtime.ticks": () => {
|
|
294
|
+
return BigInt((timeOrigin + performance.now()) * 1e6);
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
// func sleepTicks(timeout int64)
|
|
298
|
+
"runtime.sleepTicks": (timeout) => {
|
|
299
|
+
// Do not sleep, only reactivate scheduler after the given timeout.
|
|
300
|
+
setTimeout(() => {
|
|
301
|
+
if (this.exited) return;
|
|
302
|
+
try {
|
|
303
|
+
this._inst.exports.go_scheduler();
|
|
304
|
+
} catch (e) {
|
|
305
|
+
if (e !== wasmExit) throw e;
|
|
306
|
+
}
|
|
307
|
+
}, Number(timeout)/1e6);
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
// func finalizeRef(v ref)
|
|
311
|
+
"syscall/js.finalizeRef": (v_ref) => {
|
|
312
|
+
// Note: TinyGo does not support finalizers so this is only called
|
|
313
|
+
// for one specific case, by js.go:jsString. and can/might leak memory.
|
|
314
|
+
const id = v_ref & 0xffffffffn;
|
|
315
|
+
if (this._goRefCounts?.[id] !== undefined) {
|
|
316
|
+
this._goRefCounts[id]--;
|
|
317
|
+
if (this._goRefCounts[id] === 0) {
|
|
318
|
+
const v = this._values[id];
|
|
319
|
+
this._values[id] = null;
|
|
320
|
+
this._ids.delete(v);
|
|
321
|
+
this._idPool.push(id);
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
console.error("syscall/js.finalizeRef: unknown id", id);
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
// func stringVal(value string) ref
|
|
329
|
+
"syscall/js.stringVal": (value_ptr, value_len) => {
|
|
330
|
+
value_ptr >>>= 0;
|
|
331
|
+
const s = loadString(value_ptr, value_len);
|
|
332
|
+
return boxValue(s);
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
// func valueGet(v ref, p string) ref
|
|
336
|
+
"syscall/js.valueGet": (v_ref, p_ptr, p_len) => {
|
|
337
|
+
let prop = loadString(p_ptr, p_len);
|
|
338
|
+
let v = unboxValue(v_ref);
|
|
339
|
+
let result = Reflect.get(v, prop);
|
|
340
|
+
return boxValue(result);
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
// func valueSet(v ref, p string, x ref)
|
|
344
|
+
"syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => {
|
|
345
|
+
const v = unboxValue(v_ref);
|
|
346
|
+
const p = loadString(p_ptr, p_len);
|
|
347
|
+
const x = unboxValue(x_ref);
|
|
348
|
+
Reflect.set(v, p, x);
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
// func valueDelete(v ref, p string)
|
|
352
|
+
"syscall/js.valueDelete": (v_ref, p_ptr, p_len) => {
|
|
353
|
+
const v = unboxValue(v_ref);
|
|
354
|
+
const p = loadString(p_ptr, p_len);
|
|
355
|
+
Reflect.deleteProperty(v, p);
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
// func valueIndex(v ref, i int) ref
|
|
359
|
+
"syscall/js.valueIndex": (v_ref, i) => {
|
|
360
|
+
return boxValue(Reflect.get(unboxValue(v_ref), i));
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
// valueSetIndex(v ref, i int, x ref)
|
|
364
|
+
"syscall/js.valueSetIndex": (v_ref, i, x_ref) => {
|
|
365
|
+
Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref));
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
|
369
|
+
"syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => {
|
|
370
|
+
const v = unboxValue(v_ref);
|
|
371
|
+
const name = loadString(m_ptr, m_len);
|
|
372
|
+
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
|
|
373
|
+
try {
|
|
374
|
+
const m = Reflect.get(v, name);
|
|
375
|
+
storeValue(ret_addr, Reflect.apply(m, v, args));
|
|
376
|
+
mem().setUint8(ret_addr + 8, 1);
|
|
377
|
+
} catch (err) {
|
|
378
|
+
storeValue(ret_addr, err);
|
|
379
|
+
mem().setUint8(ret_addr + 8, 0);
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
// func valueInvoke(v ref, args []ref) (ref, bool)
|
|
384
|
+
"syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => {
|
|
385
|
+
try {
|
|
386
|
+
const v = unboxValue(v_ref);
|
|
387
|
+
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
|
|
388
|
+
storeValue(ret_addr, Reflect.apply(v, undefined, args));
|
|
389
|
+
mem().setUint8(ret_addr + 8, 1);
|
|
390
|
+
} catch (err) {
|
|
391
|
+
storeValue(ret_addr, err);
|
|
392
|
+
mem().setUint8(ret_addr + 8, 0);
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
|
|
396
|
+
// func valueNew(v ref, args []ref) (ref, bool)
|
|
397
|
+
"syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => {
|
|
398
|
+
const v = unboxValue(v_ref);
|
|
399
|
+
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
|
|
400
|
+
try {
|
|
401
|
+
storeValue(ret_addr, Reflect.construct(v, args));
|
|
402
|
+
mem().setUint8(ret_addr + 8, 1);
|
|
403
|
+
} catch (err) {
|
|
404
|
+
storeValue(ret_addr, err);
|
|
405
|
+
mem().setUint8(ret_addr+ 8, 0);
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
|
|
409
|
+
// func valueLength(v ref) int
|
|
410
|
+
"syscall/js.valueLength": (v_ref) => {
|
|
411
|
+
return unboxValue(v_ref).length;
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
// valuePrepareString(v ref) (ref, int)
|
|
415
|
+
"syscall/js.valuePrepareString": (ret_addr, v_ref) => {
|
|
416
|
+
const s = String(unboxValue(v_ref));
|
|
417
|
+
const str = encoder.encode(s);
|
|
418
|
+
storeValue(ret_addr, str);
|
|
419
|
+
mem().setInt32(ret_addr + 8, str.length, true);
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
// valueLoadString(v ref, b []byte)
|
|
423
|
+
"syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => {
|
|
424
|
+
const str = unboxValue(v_ref);
|
|
425
|
+
loadSlice(slice_ptr, slice_len, slice_cap).set(str);
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
// func valueInstanceOf(v ref, t ref) bool
|
|
429
|
+
"syscall/js.valueInstanceOf": (v_ref, t_ref) => {
|
|
430
|
+
return unboxValue(v_ref) instanceof unboxValue(t_ref);
|
|
431
|
+
},
|
|
432
|
+
|
|
433
|
+
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
|
434
|
+
"syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => {
|
|
435
|
+
let num_bytes_copied_addr = ret_addr;
|
|
436
|
+
let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable
|
|
437
|
+
|
|
438
|
+
const dst = loadSlice(dest_addr, dest_len);
|
|
439
|
+
const src = unboxValue(src_ref);
|
|
440
|
+
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
|
|
441
|
+
mem().setUint8(returned_status_addr, 0); // Return "not ok" status
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const toCopy = src.subarray(0, dst.length);
|
|
445
|
+
dst.set(toCopy);
|
|
446
|
+
mem().setUint32(num_bytes_copied_addr, toCopy.length, true);
|
|
447
|
+
mem().setUint8(returned_status_addr, 1); // Return "ok" status
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
// copyBytesToJS(dst ref, src []byte) (int, bool)
|
|
451
|
+
// Originally copied from upstream Go project, then modified:
|
|
452
|
+
// https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416
|
|
453
|
+
"syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => {
|
|
454
|
+
let num_bytes_copied_addr = ret_addr;
|
|
455
|
+
let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable
|
|
456
|
+
|
|
457
|
+
const dst = unboxValue(dst_ref);
|
|
458
|
+
const src = loadSlice(src_addr, src_len);
|
|
459
|
+
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
|
|
460
|
+
mem().setUint8(returned_status_addr, 0); // Return "not ok" status
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const toCopy = src.subarray(0, dst.length);
|
|
464
|
+
dst.set(toCopy);
|
|
465
|
+
mem().setUint32(num_bytes_copied_addr, toCopy.length, true);
|
|
466
|
+
mem().setUint8(returned_status_addr, 1); // Return "ok" status
|
|
467
|
+
},
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
// Go 1.20 uses 'env'. Go 1.21 uses 'gojs'.
|
|
472
|
+
// For compatibility, we use both as long as Go 1.20 is supported.
|
|
473
|
+
this.importObject.env = this.importObject.gojs;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async run(instance) {
|
|
477
|
+
this._inst = instance;
|
|
478
|
+
this._values = [ // JS values that Go currently has references to, indexed by reference id
|
|
479
|
+
NaN,
|
|
480
|
+
0,
|
|
481
|
+
null,
|
|
482
|
+
true,
|
|
483
|
+
false,
|
|
484
|
+
global,
|
|
485
|
+
this,
|
|
486
|
+
];
|
|
487
|
+
this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id
|
|
488
|
+
this._ids = new Map(); // mapping from JS values to reference ids
|
|
489
|
+
this._idPool = []; // unused ids that have been garbage collected
|
|
490
|
+
this.exited = false; // whether the Go program has exited
|
|
491
|
+
this.exitCode = 0;
|
|
492
|
+
|
|
493
|
+
if (this._inst.exports._start) {
|
|
494
|
+
let exitPromise = new Promise((resolve, reject) => {
|
|
495
|
+
this._resolveExitPromise = resolve;
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// Run program, but catch the wasmExit exception that's thrown
|
|
499
|
+
// to return back here.
|
|
500
|
+
try {
|
|
501
|
+
this._inst.exports._start();
|
|
502
|
+
} catch (e) {
|
|
503
|
+
if (e !== wasmExit) throw e;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
await exitPromise;
|
|
507
|
+
return this.exitCode;
|
|
508
|
+
} else {
|
|
509
|
+
this._inst.exports._initialize();
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
_resume() {
|
|
514
|
+
if (this.exited) {
|
|
515
|
+
throw new Error("Go program has already exited");
|
|
516
|
+
}
|
|
517
|
+
try {
|
|
518
|
+
this._inst.exports.resume();
|
|
519
|
+
} catch (e) {
|
|
520
|
+
if (e !== wasmExit) throw e;
|
|
521
|
+
}
|
|
522
|
+
if (this.exited) {
|
|
523
|
+
this._resolveExitPromise();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
_makeFuncWrapper(id) {
|
|
528
|
+
const go = this;
|
|
529
|
+
return function () {
|
|
530
|
+
const event = { id: id, this: this, args: arguments };
|
|
531
|
+
go._pendingEvent = event;
|
|
532
|
+
go._resume();
|
|
533
|
+
return event.result;
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (
|
|
539
|
+
global.require &&
|
|
540
|
+
global.require.main === module &&
|
|
541
|
+
global.process &&
|
|
542
|
+
global.process.versions &&
|
|
543
|
+
!global.process.versions.electron
|
|
544
|
+
) {
|
|
545
|
+
if (process.argv.length != 3) {
|
|
546
|
+
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const go = new Go();
|
|
551
|
+
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => {
|
|
552
|
+
let exitCode = await go.run(result.instance);
|
|
553
|
+
process.exit(exitCode);
|
|
554
|
+
}).catch((err) => {
|
|
555
|
+
console.error(err);
|
|
556
|
+
process.exit(1);
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
})();
|