@yichus/mcp 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Steven Klaiber-Noble
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # @yichus/mcp
2
+
3
+ MCP server that exposes the [Yichus](https://yich.us) calculators and the
4
+ in-browser Bosatsu playground to any agent that speaks the Model Context
5
+ Protocol.
6
+
7
+ It bundles the local WebMCP relay and adds calculator discovery/opening tools.
8
+ A connected agent works with the same page the user can see: source edits,
9
+ compiler diagnostics, results, and program diagrams.
10
+
11
+ ## Install and connect
12
+
13
+ Node 20 or newer. The relay does not need Java.
14
+
15
+ ```sh
16
+ npm install -g @yichus/mcp
17
+ ```
18
+
19
+ Configure your MCP client to launch this stdio server:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "yichus": {
25
+ "command": "npx",
26
+ "args": ["-y", "@yichus/mcp"]
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ A global install can use `"command": "yichus-mcp"` instead of `npx`.
33
+
34
+ To work on the relay from a checkout of this repository:
35
+
36
+ ```sh
37
+ pnpm install
38
+ pnpm run yichus-mcp:build
39
+ node packages/yichus-mcp/dist/cli.js
40
+ ```
41
+
42
+ Open the desired page with `?webmcp=1`. Keep it open, call
43
+ `webmcp_list_sources`, and select the source id for that tab. Discover its
44
+ schemas with `webmcp_list_tools`, then use `webmcp_call_tool` with `sourceId`,
45
+ `name`, and `arguments`. The relay and browser must run on the same computer.
46
+ For a local site, set `--base-url` to the origin you opened.
47
+
48
+ A browser host with native WebMCP support can call page tools without this
49
+ relay. See [connection instructions](https://yich.us/reference/agent.html).
50
+
51
+ ## Choose the tools for the page
52
+
53
+ - **An editable Code & run example** on the website: call
54
+ `yichus_example_guide`, `yichus_examples_list`, and `yichus_example_read`.
55
+ Use `yichus_example_update` with exact file names and the read revision;
56
+ `yichus_example_run` compiles/runs or checks the program. Inspect
57
+ `yichus_example_feedback`. Once a calculator preview is ready,
58
+ `yichus_example_preview` changes its visible controls or clicks Why.
59
+ Pass the current source revision and preview id, so an old call cannot
60
+ act on a replacement preview. Read-only demo drawers report that limit.
61
+ - **Calculator editor**, `editor.html`: `yichus_playground_get_sources`,
62
+ `yichus_playground_update_sources`, `yichus_playground_compile`,
63
+ `yichus_playground_get_output`, and `yichus_playground_reset`. Read sources
64
+ first and pass their revision as `expectedRevision` when updating. Output
65
+ feedback includes rendered text, controls, and runtime errors after loading.
66
+ - **API workbench**, `api-mcp.html`: call `api_guide` first. Discover the
67
+ current `api_*` catalog for compilation, checks, organization lenses,
68
+ and evaluation. `api_abstraction_map`, `api_organize` with lens `stack`,
69
+ and `api_why` update the visible diagrams/evaluator. The `yichus_app_*`
70
+ tools open a generated app, exercise routes, inspect rows, and test
71
+ configured crash/restart behavior. The page links their reference.
72
+ - **Standalone calculator**: `yichus_list_inputs`, `yichus_set_inputs`,
73
+ `yichus_list_outputs`, `yichus_explain_output`. These operate the running
74
+ calculator; use an editable source drawer or the editor to change code.
75
+ - **Explorer and state-machine spec editors**: `yichus_autocomplete_*`
76
+ provides suggestions for the user to accept. The user then runs the page's
77
+ checker. These tools do not execute a check themselves. For a complete agent edit/run loop, use page edit/action/feedback below.
78
+ - **Page feedback** on the source drawers, editor, Explorer, API workbench,
79
+ and spec demo: `yichus_page_feedback` reads visible text, source buffers,
80
+ statuses, and embedded-view titles. It links the agent instructions.
81
+ `yichus_page_edit` replaces a visible editor only when its full current text matches `expectedSource`; `yichus_page_action` activates a button by its listed id. Read feedback for asynchronous completion and diagnostics. It is text feedback, not a screenshot or a judgment of visual design.
82
+
83
+ The static `yichus_list_calculators` tool lists curated demo URLs;
84
+ `yichus_open_calculator` opens a chosen slug with the relay flag. The
85
+ `playground` slug opens the standalone calculator editor.
86
+
87
+ ## Agent editing loop
88
+
89
+ 1. Read the instructions, source, inputs, and assumptions on the chosen tab.
90
+ 2. Make a specific change in the visible source using its current revision.
91
+ If that revision is stale, read the user's newer edit before proceeding.
92
+ 3. Compile and fix diagnostics. For an API example, rerun the appropriate
93
+ structural check and a behavior check.
94
+ 4. Inspect feedback from the current result. Change a calculator input and
95
+ inspect Why. Report tested inputs, actual results, and remaining limits.
96
+ 5. Save the edited sources before leaving the page. A successful compilation
97
+ and a few executions do not establish that every model assumption is sound.
98
+
99
+ For autocomplete, call `yichus_autocomplete_wait_for_request`, then respond
100
+ with `yichus_autocomplete_suggest` using the request's editor id and revision.
101
+ The user accepts the ghost text with Tab or dismisses it with Escape.
102
+ `yichus_autocomplete_list_editors`, `yichus_autocomplete_get_context`, and
103
+ `yichus_autocomplete_dismiss` support inspection and cancellation.
104
+
105
+ ## CLI
106
+
107
+ ```text
108
+ yichus-mcp [options]
109
+
110
+ --base-url Yichus deployment to talk to (default: https://yich.us).
111
+ Used as the relay --widget-origin and as the URL base for
112
+ yichus_open_calculator.
113
+ --host Bridge bind host (default: 127.0.0.1).
114
+ --port Bridge WebSocket port (default: 9333, falls back to 9334+).
115
+ --label Human label reported during relay discovery (default: "Yichus").
116
+ --workspace Workspace name reported during relay discovery (default: "yichus").
117
+ --relay-id Relay id reported during relay discovery (default: "yichus").
118
+ --help, -h Show this help.
119
+ ```
120
+
121
+ The workspace and relay id are an agreement with the pages: every Yichus
122
+ page hints the relay widget workspace `yichus` and relay id `yichus`, and
123
+ the widget refuses a relay whose discovery announcement differs from
124
+ either. Change them only for a page built to hint the same values.
125
+
126
+ For local development against a self-hosted Yichus build, point both the
127
+ relay's allowed origin and the URL builder at your dev server:
128
+
129
+ ```bash
130
+ node packages/yichus-mcp/dist/cli.js --base-url http://localhost:8000
131
+ ```
132
+
133
+ ## What this package does and does not do
134
+
135
+ This package is a thin yichus-curated wrapper around
136
+ `@mcp-b/webmcp-local-relay`. It does not fork or reimplement the relay;
137
+ it constructs a `LocalRelayMcpServer` from the relay package and registers
138
+ two extra static tools on it. The relay version is pinned exactly so an
139
+ upstream rename of an internal field is a deliberate audit step rather
140
+ than a silent breakage.
141
+
142
+ For the underlying relay itself (its CLI flags, security model, and
143
+ runtime compatibility), see the
144
+ [`@mcp-b/webmcp-local-relay` README](https://www.npmjs.com/package/@mcp-b/webmcp-local-relay).
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,430 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/server.ts
4
+ import { LocalRelayMcpServer } from "@mcp-b/webmcp-local-relay";
5
+ import { z } from "zod";
6
+
7
+ // src/calculators.ts
8
+ var DEFAULT_BASE_URL = "https://yich.us";
9
+ var CALCULATORS = [
10
+ {
11
+ slug: "event-budget",
12
+ title: "Event budget",
13
+ description: "Ticket income, event costs, and the amount left over. Change attendance or adapt the cost rules.",
14
+ path: "demos/simulation/event-budget.html"
15
+ },
16
+ {
17
+ slug: "rainwater",
18
+ title: "Rainwater tank",
19
+ description: "How much water does one rainfall event collect, and how much overflows the tank?",
20
+ path: "demos/simulation/rainwater.html"
21
+ },
22
+ {
23
+ slug: "battery-runtime",
24
+ title: "Battery runtime",
25
+ description: "Estimate runtime from a battery energy budget, a constant load, and a usable-capacity assumption.",
26
+ path: "demos/simulation/battery-runtime.html"
27
+ },
28
+ {
29
+ slug: "recipe-scale",
30
+ title: "Scale a recipe",
31
+ description: "Scale ingredient weights to a different number of servings. Replace the ingredients with your own.",
32
+ path: "demos/simulation/recipe-scale.html"
33
+ },
34
+ {
35
+ slug: "download-time",
36
+ title: "Download time",
37
+ description: "Convert file size and connection speed into transfer time, allowing for a chosen efficiency.",
38
+ path: "demos/simulation/download-time.html"
39
+ },
40
+ {
41
+ slug: "loan",
42
+ title: "Loan Calculator",
43
+ description: "Fixed monthly payment and total interest for a loan repaid in equal monthly installments.",
44
+ path: "demos/simulation/loan-calculator.html"
45
+ },
46
+ {
47
+ slug: "investment",
48
+ title: "Investment Growth Calculator",
49
+ description: "Compound growth of a recurring investment under configurable return assumptions.",
50
+ path: "demos/simulation/investment.html"
51
+ },
52
+ {
53
+ slug: "tax",
54
+ title: "Tax Calculator",
55
+ description: "Flat-rate example: subtract a deduction and apply one percentage. No tax brackets or jurisdiction-specific rules.",
56
+ path: "demos/simulation/tax-calculator.html"
57
+ },
58
+ {
59
+ slug: "carbon",
60
+ title: "Carbon Footprint Calculator",
61
+ description: "Illustrative annual CO2 totals from driving, flights, electricity, and gas, using fixed emission factors.",
62
+ path: "demos/simulation/carbon-footprint.html"
63
+ },
64
+ {
65
+ slug: "rate",
66
+ title: "Rate Calculator",
67
+ description: "Apply a basis-point rate to an amount, then subtract a percentage fee. Inspect both helper calculations in Why.",
68
+ path: "demos/simulation/rate-calculator.html"
69
+ },
70
+ {
71
+ slug: "compound-fold",
72
+ title: "CompoundFold",
73
+ description: "Compound a starting amount over several periods, increasing the rate by 0.1 percentage points each period.",
74
+ path: "demos/simulation/compound-fold.html"
75
+ },
76
+ {
77
+ slug: "closure-capture",
78
+ title: "ClosureCapture",
79
+ description: "Scale two amounts with the same input rate captured by a shared inner function.",
80
+ path: "demos/simulation/closure-capture.html"
81
+ },
82
+ {
83
+ slug: "lambda-factory",
84
+ title: "Fee pipeline",
85
+ description: "Apply processing, platform, and regulatory fees in sequence, with each helper capturing its chosen rate.",
86
+ path: "demos/simulation/lambda-factory-selector.html"
87
+ },
88
+ {
89
+ slug: "portfolio",
90
+ title: "Three growth rates",
91
+ description: "Split an amount into three equal parts and apply a chosen growth rate to each. This example does not estimate risk.",
92
+ path: "demos/simulation/portfolio-analysis.html"
93
+ },
94
+ {
95
+ slug: "strategy-portfolio",
96
+ title: "Choosing a growth multiplier",
97
+ description: "Choose between three illustrative growth multipliers. This is a branching example, not an investment-risk model.",
98
+ path: "demos/simulation/strategy-portfolio.html"
99
+ },
100
+ {
101
+ slug: "tiered-policy",
102
+ title: "Claim payout rules",
103
+ description: "A toy claim payout: choose an incident severity and policy tier, then inspect the adjustments and deduction.",
104
+ path: "demos/simulation/tiered-policy-engine.html"
105
+ },
106
+ {
107
+ slug: "what-if-path-switch",
108
+ title: "WhatIfPathSwitch",
109
+ description: 'Switches computation paths via assumption toggles ("what-if" branches).',
110
+ path: "demos/simulation/what-if-path-switch.html"
111
+ },
112
+ {
113
+ slug: "deadweight_loss",
114
+ title: "Deadweight Loss Calculator",
115
+ description: "Equilibrium, surplus, and deadweight loss of a per-unit tax on a linear market.",
116
+ path: "demos/simulation/deadweight-loss.html"
117
+ },
118
+ {
119
+ slug: "trend_projection",
120
+ title: "Vintage Forecasts",
121
+ description: "An illustrative series with synthetic forecast vintages. Compare compounding assumptions; these are model-generated values, not agency data.",
122
+ path: "demos/simulation/trend-projection.html"
123
+ },
124
+ {
125
+ slug: "carbon_abatement",
126
+ title: "Carbon Abatement Path",
127
+ description: "A rising marginal abatement cost path as a sampled curve, with derived cheapest-step and tranche-cost marks.",
128
+ path: "demos/simulation/carbon-abatement.html"
129
+ }
130
+ ];
131
+ var PLAYGROUND = {
132
+ slug: "playground",
133
+ title: "Yichus Bosatsu Playground",
134
+ description: "In-browser editor for Bosatsu simulations with live compile and Why explainer.",
135
+ path: "editor.html"
136
+ };
137
+ function findCalculator(slug) {
138
+ if (slug === PLAYGROUND.slug) return PLAYGROUND;
139
+ return CALCULATORS.find((calc) => calc.slug === slug);
140
+ }
141
+ function listCalculators() {
142
+ return [...CALCULATORS, PLAYGROUND];
143
+ }
144
+ function calculatorUrl(calc, baseUrl = DEFAULT_BASE_URL) {
145
+ const base = new URL(baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
146
+ return new URL(calc.path, base).toString();
147
+ }
148
+ function withWebMcpFlag(rawUrl) {
149
+ const url = new URL(rawUrl);
150
+ url.searchParams.set("webmcp", "1");
151
+ return url.toString();
152
+ }
153
+
154
+ // src/openUrl.ts
155
+ import { spawn } from "child_process";
156
+ function openUrlInBrowser(rawUrl) {
157
+ const validated = new URL(rawUrl).href;
158
+ const platform = process.platform;
159
+ let command;
160
+ let args;
161
+ if (platform === "darwin") {
162
+ command = "open";
163
+ args = [validated];
164
+ } else if (platform === "win32") {
165
+ command = "powershell";
166
+ args = ["-NoProfile", "-NonInteractive", "-Command", `Start-Process '${validated.replace(/'/g, "''")}'`];
167
+ } else {
168
+ command = "xdg-open";
169
+ args = [validated];
170
+ }
171
+ spawn(command, args, { detached: true, stdio: "ignore" }).unref();
172
+ return { command, args };
173
+ }
174
+
175
+ // src/server.ts
176
+ var PACKAGE_NAME = "yichus-mcp";
177
+ var PACKAGE_VERSION = "0.1.0";
178
+ function createYichusMcpServer(options = {}) {
179
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? "https://yich.us");
180
+ const bridgeOverrides = options.bridgeOverrides ?? {};
181
+ const server = new LocalRelayMcpServer({
182
+ serverName: PACKAGE_NAME,
183
+ serverVersion: PACKAGE_VERSION,
184
+ bridgeOptions: {
185
+ host: bridgeOverrides.host,
186
+ port: bridgeOverrides.port,
187
+ label: bridgeOverrides.label ?? "Yichus",
188
+ // The pair every page hints the widget (web/webmcp-autocomplete.js,
189
+ // the generated calculators); the widget rejects any other hello.
190
+ workspace: bridgeOverrides.workspace ?? "yichus",
191
+ relayId: bridgeOverrides.relayId ?? "yichus",
192
+ allowedOrigins: [baseUrl]
193
+ }
194
+ });
195
+ registerYichusTools(server, { baseUrl, disableOpener: options.disableOpener ?? false });
196
+ return server;
197
+ }
198
+ function asInternal(server) {
199
+ return server;
200
+ }
201
+ function registerYichusTools(server, ctx) {
202
+ const internal = asInternal(server);
203
+ registerStatic(internal, {
204
+ name: "yichus_list_calculators",
205
+ description: "List the curated Yichus calculator demos and the playground editor available on the configured Yichus origin. Returns a slug, title, description, and absolute URL for each.",
206
+ annotations: { readOnlyHint: true, idempotentHint: true },
207
+ inputSchemaJson: { type: "object", properties: {}, additionalProperties: false },
208
+ inputSchemaZod: {},
209
+ handler: async () => {
210
+ const entries = listCalculators().map((calc) => ({
211
+ slug: calc.slug,
212
+ title: calc.title,
213
+ description: calc.description,
214
+ url: calculatorUrl(calc, ctx.baseUrl)
215
+ }));
216
+ const payload = { baseUrl: ctx.baseUrl, calculators: entries };
217
+ return {
218
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
219
+ structuredContent: payload
220
+ };
221
+ }
222
+ });
223
+ registerStatic(internal, {
224
+ name: "yichus_open_calculator",
225
+ description: "Open a curated Yichus calculator (or the playground) in the user's default browser with WebMCP enabled (?webmcp=1). After opening, the relay tools below will see the page's tools (yichus_list_inputs, yichus_set_inputs, yichus_list_outputs, yichus_explain_output for calculators; yichus_playground_* for the playground). Use yichus_list_calculators to discover slugs.",
226
+ annotations: { openWorldHint: true },
227
+ inputSchemaJson: {
228
+ type: "object",
229
+ properties: {
230
+ slug: {
231
+ type: "string",
232
+ description: 'Calculator slug from yichus_list_calculators (for example "loan", "investment", "tax", "playground").'
233
+ }
234
+ },
235
+ required: ["slug"],
236
+ additionalProperties: false
237
+ },
238
+ inputSchemaZod: { slug: z.string() },
239
+ handler: async (rawArgs) => {
240
+ const args = z.object({ slug: z.string() }).parse(rawArgs);
241
+ const calc = findCalculator(args.slug);
242
+ if (!calc) {
243
+ const known = listCalculators().map((c) => c.slug).join(", ");
244
+ return {
245
+ content: [
246
+ {
247
+ type: "text",
248
+ text: `Unknown calculator slug "${args.slug}". Known slugs: ${known}.`
249
+ }
250
+ ],
251
+ isError: true
252
+ };
253
+ }
254
+ const url = withWebMcpFlag(calculatorUrl(calc, ctx.baseUrl));
255
+ let opener = null;
256
+ if (!ctx.disableOpener) {
257
+ opener = openUrlInBrowser(url);
258
+ }
259
+ const payload = {
260
+ calculator: {
261
+ slug: calc.slug,
262
+ title: calc.title,
263
+ description: calc.description
264
+ },
265
+ url,
266
+ opened: !ctx.disableOpener,
267
+ opener
268
+ };
269
+ return {
270
+ content: [
271
+ {
272
+ type: "text",
273
+ text: ctx.disableOpener ? `Would open ${calc.title} at ${url} (browser opener disabled).` : `Opening ${calc.title} at ${url}.`
274
+ }
275
+ ],
276
+ structuredContent: payload
277
+ };
278
+ }
279
+ });
280
+ }
281
+ function registerStatic(internal, spec) {
282
+ const config = {
283
+ description: spec.description,
284
+ inputSchema: spec.inputSchemaZod
285
+ };
286
+ if (spec.annotations && Object.keys(spec.annotations).length > 0) {
287
+ config.annotations = spec.annotations;
288
+ }
289
+ internal.mcpServer.registerTool(spec.name, config, spec.handler);
290
+ internal.staticToolData.set(spec.name, {
291
+ name: spec.name,
292
+ description: spec.description,
293
+ inputSchema: spec.inputSchemaJson,
294
+ ...spec.annotations && Object.keys(spec.annotations).length > 0 ? { annotations: spec.annotations } : {}
295
+ });
296
+ }
297
+ function normalizeBaseUrl(raw) {
298
+ return new URL(raw).origin;
299
+ }
300
+
301
+ // src/cli.ts
302
+ function parseFlags(argv) {
303
+ const flags = {};
304
+ for (let i = 0; i < argv.length; i++) {
305
+ const arg = argv[i];
306
+ const next = () => {
307
+ const value = argv[i + 1];
308
+ if (value === void 0) {
309
+ throw new Error(`Missing value for ${arg}`);
310
+ }
311
+ i += 1;
312
+ return value;
313
+ };
314
+ switch (arg) {
315
+ case "--base-url":
316
+ flags.baseUrl = next();
317
+ break;
318
+ case "--host":
319
+ flags.host = next();
320
+ break;
321
+ case "--port": {
322
+ const value = next();
323
+ const parsed = Number.parseInt(value, 10);
324
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
325
+ throw new Error(`Invalid --port value: ${value}`);
326
+ }
327
+ flags.port = parsed;
328
+ break;
329
+ }
330
+ case "--label":
331
+ flags.label = next();
332
+ break;
333
+ case "--workspace":
334
+ flags.workspace = next();
335
+ break;
336
+ case "--relay-id":
337
+ flags.relayId = next();
338
+ break;
339
+ case "--help":
340
+ case "-h":
341
+ printHelp();
342
+ process.exit(0);
343
+ // eslint-disable-next-line no-fallthrough
344
+ default:
345
+ throw new Error(`Unknown flag: ${arg}`);
346
+ }
347
+ }
348
+ return flags;
349
+ }
350
+ function printHelp() {
351
+ process.stdout.write(`yichus-mcp [options]
352
+
353
+ Yichus MCP server: bundles the WebMCP local relay with two extra static
354
+ tools (yichus_list_calculators, yichus_open_calculator) so an MCP client
355
+ can drive any deployed Yichus calculator end to end.
356
+
357
+ Options:
358
+ --base-url Yichus deployment to talk to (default: https://yich.us).
359
+ Used as the relay --widget-origin and as the URL base for
360
+ yichus_open_calculator.
361
+ --host Bridge bind host (default: 127.0.0.1).
362
+ --port Bridge WebSocket port (default: 9333, falls back to 9334+).
363
+ --label Human label reported during relay discovery (default: "Yichus").
364
+ --workspace Workspace name reported during relay discovery (default: "yichus").
365
+ --relay-id Relay id reported during relay discovery (default: "yichus").
366
+ Every Yichus page hints the widget workspace "yichus" and
367
+ relay id "yichus" and the widget rejects a relay whose
368
+ announcement differs, so change these only for a page
369
+ built to hint the same values.
370
+ --help, -h Show this help.
371
+
372
+ Add to your MCP client config (Claude Desktop, Cursor, etc.):
373
+
374
+ {
375
+ "mcpServers": {
376
+ "yichus": {
377
+ "command": "npx",
378
+ "args": ["-y", "@yichus/mcp"]
379
+ }
380
+ }
381
+ }
382
+ `);
383
+ }
384
+ async function main() {
385
+ let flags;
386
+ try {
387
+ flags = parseFlags(process.argv.slice(2));
388
+ } catch (err) {
389
+ process.stderr.write(`[yichus-mcp] ${err instanceof Error ? err.message : String(err)}
390
+ `);
391
+ printHelp();
392
+ process.exit(2);
393
+ }
394
+ const server = createYichusMcpServer({
395
+ baseUrl: flags.baseUrl,
396
+ bridgeOverrides: {
397
+ host: flags.host,
398
+ port: flags.port,
399
+ label: flags.label,
400
+ workspace: flags.workspace,
401
+ relayId: flags.relayId
402
+ }
403
+ });
404
+ const shutdown = async () => {
405
+ try {
406
+ await server.stop();
407
+ } catch (err) {
408
+ process.stderr.write(
409
+ `[yichus-mcp] warn: shutdown error: ${err instanceof Error ? err.message : String(err)}
410
+ `
411
+ );
412
+ }
413
+ };
414
+ process.on("SIGINT", () => {
415
+ void shutdown().then(() => process.exit(0));
416
+ });
417
+ process.on("SIGTERM", () => {
418
+ void shutdown().then(() => process.exit(0));
419
+ });
420
+ await server.start();
421
+ await server.startStdio();
422
+ }
423
+ main().catch((err) => {
424
+ process.stderr.write(
425
+ `[yichus-mcp] fatal: ${err instanceof Error ? err.stack ?? err.message : String(err)}
426
+ `
427
+ );
428
+ process.exit(1);
429
+ });
430
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/calculators.ts","../src/openUrl.ts","../src/cli.ts"],"sourcesContent":["import { LocalRelayMcpServer } from '@mcp-b/webmcp-local-relay';\nimport { z } from 'zod';\nimport {\n CALCULATORS,\n PLAYGROUND,\n calculatorUrl,\n findCalculator,\n listCalculators,\n withWebMcpFlag,\n type Calculator,\n} from './calculators.js';\nimport { openUrlInBrowser } from './openUrl.js';\n\nconst PACKAGE_NAME = 'yichus-mcp';\n// Bumped via packages/yichus-mcp/package.json. Kept inline so the bin works\n// without reading package.json from disk at startup.\nconst PACKAGE_VERSION = '0.1.0';\n\nexport interface YichusMcpServerOptions {\n /**\n * Origin of the deployed Yichus instance. The relay rejects browser\n * connections from any other origin and `yichus_open_calculator` builds\n * URLs against this base.\n *\n * @defaultValue `https://yich.us`\n */\n baseUrl?: string;\n\n /**\n * Override the WebMCP local-relay options that get passed straight through\n * to the bundled `LocalRelayMcpServer`. Use this to change the bind port,\n * label, workspace name, or relay id.\n *\n * The page-side embed (`web/webmcp-autocomplete.js`, `ensureRelayEmbed`,\n * and the generated calculator pages) hints the widget a workspace and a\n * relay id, and the widget refuses any relay whose discovery hello\n * differs from either. Every shipped page hints `yichus` for both, so the\n * defaults here are the one agreement; override both sides together.\n */\n bridgeOverrides?: {\n host?: string;\n port?: number;\n label?: string;\n workspace?: string;\n relayId?: string;\n };\n\n /**\n * If `true`, the `yichus_open_calculator` tool returns the URL it would\n * have opened without actually launching a browser process. Used by tests\n * and by anyone running the server in a headless environment.\n *\n * @defaultValue `false`\n */\n disableOpener?: boolean;\n}\n\n/**\n * Construct a fully-wired Yichus MCP server.\n *\n * The returned `LocalRelayMcpServer` already has every default WebMCP relay\n * tool plus the two yichus-curated tools (`yichus_list_calculators` and\n * `yichus_open_calculator`) registered. Call `start()` then `startStdio()`\n * (or `connect(transport)`) to bring it online.\n */\nexport function createYichusMcpServer(options: YichusMcpServerOptions = {}): LocalRelayMcpServer {\n const baseUrl = normalizeBaseUrl(options.baseUrl ?? 'https://yich.us');\n const bridgeOverrides = options.bridgeOverrides ?? {};\n\n const server = new LocalRelayMcpServer({\n serverName: PACKAGE_NAME,\n serverVersion: PACKAGE_VERSION,\n bridgeOptions: {\n host: bridgeOverrides.host,\n port: bridgeOverrides.port,\n label: bridgeOverrides.label ?? 'Yichus',\n // The pair every page hints the widget (web/webmcp-autocomplete.js,\n // the generated calculators); the widget rejects any other hello.\n workspace: bridgeOverrides.workspace ?? 'yichus',\n relayId: bridgeOverrides.relayId ?? 'yichus',\n allowedOrigins: [baseUrl],\n },\n });\n\n registerYichusTools(server, { baseUrl, disableOpener: options.disableOpener ?? false });\n return server;\n}\n\ninterface RelayInternals {\n mcpServer: {\n registerTool: (\n name: string,\n config: Record<string, unknown>,\n handler: (args: unknown) => Promise<unknown> | unknown\n ) => unknown;\n };\n staticToolData: Map<\n string,\n {\n name: string;\n description?: string;\n inputSchema: object;\n annotations?: object;\n }\n >;\n}\n\n/**\n * `@mcp-b/webmcp-local-relay@2.2.1` marks `mcpServer` and `staticToolData`\n * as TypeScript-private but the JavaScript fields are public. We pin the\n * relay version exactly so an upstream rename here is a deliberate audit\n * step rather than a silent breakage.\n */\nfunction asInternal(server: LocalRelayMcpServer): RelayInternals {\n return server as unknown as RelayInternals;\n}\n\nfunction registerYichusTools(\n server: LocalRelayMcpServer,\n ctx: { baseUrl: string; disableOpener: boolean }\n): void {\n const internal = asInternal(server);\n\n registerStatic(internal, {\n name: 'yichus_list_calculators',\n description:\n 'List the curated Yichus calculator demos and the playground editor available on the configured Yichus origin. Returns a slug, title, description, and absolute URL for each.',\n annotations: { readOnlyHint: true, idempotentHint: true },\n inputSchemaJson: { type: 'object', properties: {}, additionalProperties: false },\n inputSchemaZod: {} as z.ZodRawShape,\n handler: async () => {\n const entries = listCalculators().map((calc) => ({\n slug: calc.slug,\n title: calc.title,\n description: calc.description,\n url: calculatorUrl(calc, ctx.baseUrl),\n }));\n const payload = { baseUrl: ctx.baseUrl, calculators: entries };\n return {\n content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],\n structuredContent: payload,\n };\n },\n });\n\n registerStatic(internal, {\n name: 'yichus_open_calculator',\n description:\n \"Open a curated Yichus calculator (or the playground) in the user's default browser with WebMCP enabled (?webmcp=1). After opening, the relay tools below will see the page's tools (yichus_list_inputs, yichus_set_inputs, yichus_list_outputs, yichus_explain_output for calculators; yichus_playground_* for the playground). Use yichus_list_calculators to discover slugs.\",\n annotations: { openWorldHint: true },\n inputSchemaJson: {\n type: 'object',\n properties: {\n slug: {\n type: 'string',\n description:\n 'Calculator slug from yichus_list_calculators (for example \"loan\", \"investment\", \"tax\", \"playground\").',\n },\n },\n required: ['slug'],\n additionalProperties: false,\n },\n inputSchemaZod: { slug: z.string() },\n handler: async (rawArgs) => {\n const args = z.object({ slug: z.string() }).parse(rawArgs);\n const calc: Calculator | undefined = findCalculator(args.slug);\n if (!calc) {\n const known = listCalculators().map((c) => c.slug).join(', ');\n return {\n content: [\n {\n type: 'text',\n text: `Unknown calculator slug \"${args.slug}\". Known slugs: ${known}.`,\n },\n ],\n isError: true,\n };\n }\n const url = withWebMcpFlag(calculatorUrl(calc, ctx.baseUrl));\n let opener: { command: string; args: string[] } | null = null;\n if (!ctx.disableOpener) {\n opener = openUrlInBrowser(url);\n }\n const payload = {\n calculator: {\n slug: calc.slug,\n title: calc.title,\n description: calc.description,\n },\n url,\n opened: !ctx.disableOpener,\n opener,\n };\n return {\n content: [\n {\n type: 'text',\n text: ctx.disableOpener\n ? `Would open ${calc.title} at ${url} (browser opener disabled).`\n : `Opening ${calc.title} at ${url}.`,\n },\n ],\n structuredContent: payload,\n };\n },\n });\n}\n\ninterface StaticToolSpec {\n name: string;\n description: string;\n annotations?: Record<string, unknown>;\n inputSchemaJson: object;\n inputSchemaZod: z.ZodRawShape;\n handler: (args: unknown) => Promise<unknown> | unknown;\n}\n\nfunction registerStatic(internal: RelayInternals, spec: StaticToolSpec): void {\n const config: Record<string, unknown> = {\n description: spec.description,\n inputSchema: spec.inputSchemaZod,\n };\n if (spec.annotations && Object.keys(spec.annotations).length > 0) {\n config.annotations = spec.annotations;\n }\n internal.mcpServer.registerTool(spec.name, config, spec.handler);\n internal.staticToolData.set(spec.name, {\n name: spec.name,\n description: spec.description,\n inputSchema: spec.inputSchemaJson,\n ...(spec.annotations && Object.keys(spec.annotations).length > 0\n ? { annotations: spec.annotations }\n : {}),\n });\n}\n\nfunction normalizeBaseUrl(raw: string): string {\n // `URL` validates the input and re-serializes to the canonical form.\n return new URL(raw).origin;\n}\n\nexport const _testing = {\n CALCULATORS,\n PLAYGROUND,\n asInternal,\n};\n","/**\n * Curated catalogue of Yichus simulation demos.\n *\n * GENERATED from deploy/manifest.json by `yichus deploy --emit-calculators`.\n * Do not edit the CALCULATORS array by hand: the manifest row that deploys\n * a calculator is also its catalog entry, so the two cannot drift.\n */\n\nexport interface Calculator {\n /** Stable short identifier used by `yichus_open_calculator`. */\n readonly slug: string;\n /** Human-readable title that matches the page `<title>`. */\n readonly title: string;\n /** One-line description suitable for an agent to choose between calculators. */\n readonly description: string;\n /** Path under the deploy base URL (no leading slash). */\n readonly path: string;\n}\n\nconst DEFAULT_BASE_URL = 'https://yich.us';\n\nexport const CALCULATORS: readonly Calculator[] = [\n {\n slug: 'event-budget',\n title: 'Event budget',\n description: 'Ticket income, event costs, and the amount left over. Change attendance or adapt the cost rules.',\n path: 'demos/simulation/event-budget.html',\n },\n {\n slug: 'rainwater',\n title: 'Rainwater tank',\n description: 'How much water does one rainfall event collect, and how much overflows the tank?',\n path: 'demos/simulation/rainwater.html',\n },\n {\n slug: 'battery-runtime',\n title: 'Battery runtime',\n description: 'Estimate runtime from a battery energy budget, a constant load, and a usable-capacity assumption.',\n path: 'demos/simulation/battery-runtime.html',\n },\n {\n slug: 'recipe-scale',\n title: 'Scale a recipe',\n description: 'Scale ingredient weights to a different number of servings. Replace the ingredients with your own.',\n path: 'demos/simulation/recipe-scale.html',\n },\n {\n slug: 'download-time',\n title: 'Download time',\n description: 'Convert file size and connection speed into transfer time, allowing for a chosen efficiency.',\n path: 'demos/simulation/download-time.html',\n },\n {\n slug: 'loan',\n title: 'Loan Calculator',\n description: 'Fixed monthly payment and total interest for a loan repaid in equal monthly installments.',\n path: 'demos/simulation/loan-calculator.html',\n },\n {\n slug: 'investment',\n title: 'Investment Growth Calculator',\n description: 'Compound growth of a recurring investment under configurable return assumptions.',\n path: 'demos/simulation/investment.html',\n },\n {\n slug: 'tax',\n title: 'Tax Calculator',\n description: 'Flat-rate example: subtract a deduction and apply one percentage. No tax brackets or jurisdiction-specific rules.',\n path: 'demos/simulation/tax-calculator.html',\n },\n {\n slug: 'carbon',\n title: 'Carbon Footprint Calculator',\n description: 'Illustrative annual CO2 totals from driving, flights, electricity, and gas, using fixed emission factors.',\n path: 'demos/simulation/carbon-footprint.html',\n },\n {\n slug: 'rate',\n title: 'Rate Calculator',\n description: 'Apply a basis-point rate to an amount, then subtract a percentage fee. Inspect both helper calculations in Why.',\n path: 'demos/simulation/rate-calculator.html',\n },\n {\n slug: 'compound-fold',\n title: 'CompoundFold',\n description: 'Compound a starting amount over several periods, increasing the rate by 0.1 percentage points each period.',\n path: 'demos/simulation/compound-fold.html',\n },\n {\n slug: 'closure-capture',\n title: 'ClosureCapture',\n description: 'Scale two amounts with the same input rate captured by a shared inner function.',\n path: 'demos/simulation/closure-capture.html',\n },\n {\n slug: 'lambda-factory',\n title: 'Fee pipeline',\n description: 'Apply processing, platform, and regulatory fees in sequence, with each helper capturing its chosen rate.',\n path: 'demos/simulation/lambda-factory-selector.html',\n },\n {\n slug: 'portfolio',\n title: 'Three growth rates',\n description: 'Split an amount into three equal parts and apply a chosen growth rate to each. This example does not estimate risk.',\n path: 'demos/simulation/portfolio-analysis.html',\n },\n {\n slug: 'strategy-portfolio',\n title: 'Choosing a growth multiplier',\n description: 'Choose between three illustrative growth multipliers. This is a branching example, not an investment-risk model.',\n path: 'demos/simulation/strategy-portfolio.html',\n },\n {\n slug: 'tiered-policy',\n title: 'Claim payout rules',\n description: 'A toy claim payout: choose an incident severity and policy tier, then inspect the adjustments and deduction.',\n path: 'demos/simulation/tiered-policy-engine.html',\n },\n {\n slug: 'what-if-path-switch',\n title: 'WhatIfPathSwitch',\n description: 'Switches computation paths via assumption toggles (\"what-if\" branches).',\n path: 'demos/simulation/what-if-path-switch.html',\n },\n {\n slug: 'deadweight_loss',\n title: 'Deadweight Loss Calculator',\n description: 'Equilibrium, surplus, and deadweight loss of a per-unit tax on a linear market.',\n path: 'demos/simulation/deadweight-loss.html',\n },\n {\n slug: 'trend_projection',\n title: 'Vintage Forecasts',\n description: 'An illustrative series with synthetic forecast vintages. Compare compounding assumptions; these are model-generated values, not agency data.',\n path: 'demos/simulation/trend-projection.html',\n },\n {\n slug: 'carbon_abatement',\n title: 'Carbon Abatement Path',\n description: 'A rising marginal abatement cost path as a sampled curve, with derived cheapest-step and tranche-cost marks.',\n path: 'demos/simulation/carbon-abatement.html',\n },\n] as const;\n\nexport const PLAYGROUND: Calculator = {\n slug: 'playground',\n title: 'Yichus Bosatsu Playground',\n description: 'In-browser editor for Bosatsu simulations with live compile and Why explainer.',\n path: 'editor.html',\n} as const;\n\n/** Look up a curated calculator by its slug, including the playground. */\nexport function findCalculator(slug: string): Calculator | undefined {\n if (slug === PLAYGROUND.slug) return PLAYGROUND;\n return CALCULATORS.find((calc) => calc.slug === slug);\n}\n\n/** Returns every entry exposed by `yichus_list_calculators`. */\nexport function listCalculators(): readonly Calculator[] {\n return [...CALCULATORS, PLAYGROUND];\n}\n\n/** Build the full URL for a calculator at the given base. */\nexport function calculatorUrl(calc: Calculator, baseUrl = DEFAULT_BASE_URL): string {\n const base = new URL(baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`);\n return new URL(calc.path, base).toString();\n}\n\n/**\n * Force `?webmcp=1` on a URL so the page loads the WebMCP relay embed.\n *\n * Pages generated by `SimulationGenerator` and `web/editor.html` only inject\n * the relay embed when this flag (or `localStorage.yichus_webmcp = \"on\"`) is\n * present, so an agent that opens a calculator must set it.\n */\nexport function withWebMcpFlag(rawUrl: string): string {\n const url = new URL(rawUrl);\n url.searchParams.set('webmcp', '1');\n return url.toString();\n}\n","import { spawn } from 'node:child_process';\n\n/**\n * Opens a URL in the user's default browser.\n *\n * Uses `URL.href` so shell metacharacters in the original string are escaped\n * before they reach the platform opener. Mirrors the approach the upstream\n * `webmcp-local-relay` `webmcp_open_page` tool uses.\n */\nexport function openUrlInBrowser(rawUrl: string): { command: string; args: string[] } {\n const validated = new URL(rawUrl).href;\n const platform = process.platform;\n\n let command: string;\n let args: string[];\n if (platform === 'darwin') {\n command = 'open';\n args = [validated];\n } else if (platform === 'win32') {\n command = 'powershell';\n args = ['-NoProfile', '-NonInteractive', '-Command', `Start-Process '${validated.replace(/'/g, \"''\")}'`];\n } else {\n command = 'xdg-open';\n args = [validated];\n }\n\n spawn(command, args, { detached: true, stdio: 'ignore' }).unref();\n return { command, args };\n}\n","#!/usr/bin/env node\nimport { createYichusMcpServer } from './server.js';\n\ninterface ParsedFlags {\n baseUrl?: string;\n port?: number;\n host?: string;\n label?: string;\n workspace?: string;\n relayId?: string;\n}\n\nfunction parseFlags(argv: string[]): ParsedFlags {\n const flags: ParsedFlags = {};\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = (): string => {\n const value = argv[i + 1];\n if (value === undefined) {\n throw new Error(`Missing value for ${arg}`);\n }\n i += 1;\n return value;\n };\n switch (arg) {\n case '--base-url':\n flags.baseUrl = next();\n break;\n case '--host':\n flags.host = next();\n break;\n case '--port': {\n const value = next();\n const parsed = Number.parseInt(value, 10);\n if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {\n throw new Error(`Invalid --port value: ${value}`);\n }\n flags.port = parsed;\n break;\n }\n case '--label':\n flags.label = next();\n break;\n case '--workspace':\n flags.workspace = next();\n break;\n case '--relay-id':\n flags.relayId = next();\n break;\n case '--help':\n case '-h':\n printHelp();\n process.exit(0);\n // eslint-disable-next-line no-fallthrough\n default:\n throw new Error(`Unknown flag: ${arg}`);\n }\n }\n return flags;\n}\n\nfunction printHelp(): void {\n process.stdout.write(`yichus-mcp [options]\n\nYichus MCP server: bundles the WebMCP local relay with two extra static\ntools (yichus_list_calculators, yichus_open_calculator) so an MCP client\ncan drive any deployed Yichus calculator end to end.\n\nOptions:\n --base-url Yichus deployment to talk to (default: https://yich.us).\n Used as the relay --widget-origin and as the URL base for\n yichus_open_calculator.\n --host Bridge bind host (default: 127.0.0.1).\n --port Bridge WebSocket port (default: 9333, falls back to 9334+).\n --label Human label reported during relay discovery (default: \"Yichus\").\n --workspace Workspace name reported during relay discovery (default: \"yichus\").\n --relay-id Relay id reported during relay discovery (default: \"yichus\").\n Every Yichus page hints the widget workspace \"yichus\" and\n relay id \"yichus\" and the widget rejects a relay whose\n announcement differs, so change these only for a page\n built to hint the same values.\n --help, -h Show this help.\n\nAdd to your MCP client config (Claude Desktop, Cursor, etc.):\n\n {\n \"mcpServers\": {\n \"yichus\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@yichus/mcp\"]\n }\n }\n }\n`);\n}\n\nasync function main(): Promise<void> {\n let flags: ParsedFlags;\n try {\n flags = parseFlags(process.argv.slice(2));\n } catch (err) {\n process.stderr.write(`[yichus-mcp] ${err instanceof Error ? err.message : String(err)}\\n`);\n printHelp();\n process.exit(2);\n }\n\n const server = createYichusMcpServer({\n baseUrl: flags.baseUrl,\n bridgeOverrides: {\n host: flags.host,\n port: flags.port,\n label: flags.label,\n workspace: flags.workspace,\n relayId: flags.relayId,\n },\n });\n\n const shutdown = async (): Promise<void> => {\n try {\n await server.stop();\n } catch (err) {\n process.stderr.write(\n `[yichus-mcp] warn: shutdown error: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n }\n };\n process.on('SIGINT', () => {\n void shutdown().then(() => process.exit(0));\n });\n process.on('SIGTERM', () => {\n void shutdown().then(() => process.exit(0));\n });\n\n await server.start();\n await server.startStdio();\n}\n\nmain().catch((err) => {\n process.stderr.write(\n `[yichus-mcp] fatal: ${err instanceof Error ? err.stack ?? err.message : String(err)}\\n`\n );\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
@@ -0,0 +1,95 @@
1
+ import { LocalRelayMcpServer } from '@mcp-b/webmcp-local-relay';
2
+
3
+ /**
4
+ * Curated catalogue of Yichus simulation demos.
5
+ *
6
+ * GENERATED from deploy/manifest.json by `yichus deploy --emit-calculators`.
7
+ * Do not edit the CALCULATORS array by hand: the manifest row that deploys
8
+ * a calculator is also its catalog entry, so the two cannot drift.
9
+ */
10
+ interface Calculator {
11
+ /** Stable short identifier used by `yichus_open_calculator`. */
12
+ readonly slug: string;
13
+ /** Human-readable title that matches the page `<title>`. */
14
+ readonly title: string;
15
+ /** One-line description suitable for an agent to choose between calculators. */
16
+ readonly description: string;
17
+ /** Path under the deploy base URL (no leading slash). */
18
+ readonly path: string;
19
+ }
20
+ declare const CALCULATORS: readonly Calculator[];
21
+ declare const PLAYGROUND: Calculator;
22
+ /** Look up a curated calculator by its slug, including the playground. */
23
+ declare function findCalculator(slug: string): Calculator | undefined;
24
+ /** Returns every entry exposed by `yichus_list_calculators`. */
25
+ declare function listCalculators(): readonly Calculator[];
26
+ /** Build the full URL for a calculator at the given base. */
27
+ declare function calculatorUrl(calc: Calculator, baseUrl?: string): string;
28
+ /**
29
+ * Force `?webmcp=1` on a URL so the page loads the WebMCP relay embed.
30
+ *
31
+ * Pages generated by `SimulationGenerator` and `web/editor.html` only inject
32
+ * the relay embed when this flag (or `localStorage.yichus_webmcp = "on"`) is
33
+ * present, so an agent that opens a calculator must set it.
34
+ */
35
+ declare function withWebMcpFlag(rawUrl: string): string;
36
+
37
+ interface YichusMcpServerOptions {
38
+ /**
39
+ * Origin of the deployed Yichus instance. The relay rejects browser
40
+ * connections from any other origin and `yichus_open_calculator` builds
41
+ * URLs against this base.
42
+ *
43
+ * @defaultValue `https://yich.us`
44
+ */
45
+ baseUrl?: string;
46
+ /**
47
+ * Override the WebMCP local-relay options that get passed straight through
48
+ * to the bundled `LocalRelayMcpServer`. Use this to change the bind port,
49
+ * label, workspace name, or relay id.
50
+ *
51
+ * The page-side embed (`web/webmcp-autocomplete.js`, `ensureRelayEmbed`,
52
+ * and the generated calculator pages) hints the widget a workspace and a
53
+ * relay id, and the widget refuses any relay whose discovery hello
54
+ * differs from either. Every shipped page hints `yichus` for both, so the
55
+ * defaults here are the one agreement; override both sides together.
56
+ */
57
+ bridgeOverrides?: {
58
+ host?: string;
59
+ port?: number;
60
+ label?: string;
61
+ workspace?: string;
62
+ relayId?: string;
63
+ };
64
+ /**
65
+ * If `true`, the `yichus_open_calculator` tool returns the URL it would
66
+ * have opened without actually launching a browser process. Used by tests
67
+ * and by anyone running the server in a headless environment.
68
+ *
69
+ * @defaultValue `false`
70
+ */
71
+ disableOpener?: boolean;
72
+ }
73
+ /**
74
+ * Construct a fully-wired Yichus MCP server.
75
+ *
76
+ * The returned `LocalRelayMcpServer` already has every default WebMCP relay
77
+ * tool plus the two yichus-curated tools (`yichus_list_calculators` and
78
+ * `yichus_open_calculator`) registered. Call `start()` then `startStdio()`
79
+ * (or `connect(transport)`) to bring it online.
80
+ */
81
+ declare function createYichusMcpServer(options?: YichusMcpServerOptions): LocalRelayMcpServer;
82
+
83
+ /**
84
+ * Opens a URL in the user's default browser.
85
+ *
86
+ * Uses `URL.href` so shell metacharacters in the original string are escaped
87
+ * before they reach the platform opener. Mirrors the approach the upstream
88
+ * `webmcp-local-relay` `webmcp_open_page` tool uses.
89
+ */
90
+ declare function openUrlInBrowser(rawUrl: string): {
91
+ command: string;
92
+ args: string[];
93
+ };
94
+
95
+ export { CALCULATORS, type Calculator, PLAYGROUND, type YichusMcpServerOptions, calculatorUrl, createYichusMcpServer, findCalculator, listCalculators, openUrlInBrowser, withWebMcpFlag };
package/dist/index.js ADDED
@@ -0,0 +1,308 @@
1
+ // src/server.ts
2
+ import { LocalRelayMcpServer } from "@mcp-b/webmcp-local-relay";
3
+ import { z } from "zod";
4
+
5
+ // src/calculators.ts
6
+ var DEFAULT_BASE_URL = "https://yich.us";
7
+ var CALCULATORS = [
8
+ {
9
+ slug: "event-budget",
10
+ title: "Event budget",
11
+ description: "Ticket income, event costs, and the amount left over. Change attendance or adapt the cost rules.",
12
+ path: "demos/simulation/event-budget.html"
13
+ },
14
+ {
15
+ slug: "rainwater",
16
+ title: "Rainwater tank",
17
+ description: "How much water does one rainfall event collect, and how much overflows the tank?",
18
+ path: "demos/simulation/rainwater.html"
19
+ },
20
+ {
21
+ slug: "battery-runtime",
22
+ title: "Battery runtime",
23
+ description: "Estimate runtime from a battery energy budget, a constant load, and a usable-capacity assumption.",
24
+ path: "demos/simulation/battery-runtime.html"
25
+ },
26
+ {
27
+ slug: "recipe-scale",
28
+ title: "Scale a recipe",
29
+ description: "Scale ingredient weights to a different number of servings. Replace the ingredients with your own.",
30
+ path: "demos/simulation/recipe-scale.html"
31
+ },
32
+ {
33
+ slug: "download-time",
34
+ title: "Download time",
35
+ description: "Convert file size and connection speed into transfer time, allowing for a chosen efficiency.",
36
+ path: "demos/simulation/download-time.html"
37
+ },
38
+ {
39
+ slug: "loan",
40
+ title: "Loan Calculator",
41
+ description: "Fixed monthly payment and total interest for a loan repaid in equal monthly installments.",
42
+ path: "demos/simulation/loan-calculator.html"
43
+ },
44
+ {
45
+ slug: "investment",
46
+ title: "Investment Growth Calculator",
47
+ description: "Compound growth of a recurring investment under configurable return assumptions.",
48
+ path: "demos/simulation/investment.html"
49
+ },
50
+ {
51
+ slug: "tax",
52
+ title: "Tax Calculator",
53
+ description: "Flat-rate example: subtract a deduction and apply one percentage. No tax brackets or jurisdiction-specific rules.",
54
+ path: "demos/simulation/tax-calculator.html"
55
+ },
56
+ {
57
+ slug: "carbon",
58
+ title: "Carbon Footprint Calculator",
59
+ description: "Illustrative annual CO2 totals from driving, flights, electricity, and gas, using fixed emission factors.",
60
+ path: "demos/simulation/carbon-footprint.html"
61
+ },
62
+ {
63
+ slug: "rate",
64
+ title: "Rate Calculator",
65
+ description: "Apply a basis-point rate to an amount, then subtract a percentage fee. Inspect both helper calculations in Why.",
66
+ path: "demos/simulation/rate-calculator.html"
67
+ },
68
+ {
69
+ slug: "compound-fold",
70
+ title: "CompoundFold",
71
+ description: "Compound a starting amount over several periods, increasing the rate by 0.1 percentage points each period.",
72
+ path: "demos/simulation/compound-fold.html"
73
+ },
74
+ {
75
+ slug: "closure-capture",
76
+ title: "ClosureCapture",
77
+ description: "Scale two amounts with the same input rate captured by a shared inner function.",
78
+ path: "demos/simulation/closure-capture.html"
79
+ },
80
+ {
81
+ slug: "lambda-factory",
82
+ title: "Fee pipeline",
83
+ description: "Apply processing, platform, and regulatory fees in sequence, with each helper capturing its chosen rate.",
84
+ path: "demos/simulation/lambda-factory-selector.html"
85
+ },
86
+ {
87
+ slug: "portfolio",
88
+ title: "Three growth rates",
89
+ description: "Split an amount into three equal parts and apply a chosen growth rate to each. This example does not estimate risk.",
90
+ path: "demos/simulation/portfolio-analysis.html"
91
+ },
92
+ {
93
+ slug: "strategy-portfolio",
94
+ title: "Choosing a growth multiplier",
95
+ description: "Choose between three illustrative growth multipliers. This is a branching example, not an investment-risk model.",
96
+ path: "demos/simulation/strategy-portfolio.html"
97
+ },
98
+ {
99
+ slug: "tiered-policy",
100
+ title: "Claim payout rules",
101
+ description: "A toy claim payout: choose an incident severity and policy tier, then inspect the adjustments and deduction.",
102
+ path: "demos/simulation/tiered-policy-engine.html"
103
+ },
104
+ {
105
+ slug: "what-if-path-switch",
106
+ title: "WhatIfPathSwitch",
107
+ description: 'Switches computation paths via assumption toggles ("what-if" branches).',
108
+ path: "demos/simulation/what-if-path-switch.html"
109
+ },
110
+ {
111
+ slug: "deadweight_loss",
112
+ title: "Deadweight Loss Calculator",
113
+ description: "Equilibrium, surplus, and deadweight loss of a per-unit tax on a linear market.",
114
+ path: "demos/simulation/deadweight-loss.html"
115
+ },
116
+ {
117
+ slug: "trend_projection",
118
+ title: "Vintage Forecasts",
119
+ description: "An illustrative series with synthetic forecast vintages. Compare compounding assumptions; these are model-generated values, not agency data.",
120
+ path: "demos/simulation/trend-projection.html"
121
+ },
122
+ {
123
+ slug: "carbon_abatement",
124
+ title: "Carbon Abatement Path",
125
+ description: "A rising marginal abatement cost path as a sampled curve, with derived cheapest-step and tranche-cost marks.",
126
+ path: "demos/simulation/carbon-abatement.html"
127
+ }
128
+ ];
129
+ var PLAYGROUND = {
130
+ slug: "playground",
131
+ title: "Yichus Bosatsu Playground",
132
+ description: "In-browser editor for Bosatsu simulations with live compile and Why explainer.",
133
+ path: "editor.html"
134
+ };
135
+ function findCalculator(slug) {
136
+ if (slug === PLAYGROUND.slug) return PLAYGROUND;
137
+ return CALCULATORS.find((calc) => calc.slug === slug);
138
+ }
139
+ function listCalculators() {
140
+ return [...CALCULATORS, PLAYGROUND];
141
+ }
142
+ function calculatorUrl(calc, baseUrl = DEFAULT_BASE_URL) {
143
+ const base = new URL(baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
144
+ return new URL(calc.path, base).toString();
145
+ }
146
+ function withWebMcpFlag(rawUrl) {
147
+ const url = new URL(rawUrl);
148
+ url.searchParams.set("webmcp", "1");
149
+ return url.toString();
150
+ }
151
+
152
+ // src/openUrl.ts
153
+ import { spawn } from "child_process";
154
+ function openUrlInBrowser(rawUrl) {
155
+ const validated = new URL(rawUrl).href;
156
+ const platform = process.platform;
157
+ let command;
158
+ let args;
159
+ if (platform === "darwin") {
160
+ command = "open";
161
+ args = [validated];
162
+ } else if (platform === "win32") {
163
+ command = "powershell";
164
+ args = ["-NoProfile", "-NonInteractive", "-Command", `Start-Process '${validated.replace(/'/g, "''")}'`];
165
+ } else {
166
+ command = "xdg-open";
167
+ args = [validated];
168
+ }
169
+ spawn(command, args, { detached: true, stdio: "ignore" }).unref();
170
+ return { command, args };
171
+ }
172
+
173
+ // src/server.ts
174
+ var PACKAGE_NAME = "yichus-mcp";
175
+ var PACKAGE_VERSION = "0.1.0";
176
+ function createYichusMcpServer(options = {}) {
177
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? "https://yich.us");
178
+ const bridgeOverrides = options.bridgeOverrides ?? {};
179
+ const server = new LocalRelayMcpServer({
180
+ serverName: PACKAGE_NAME,
181
+ serverVersion: PACKAGE_VERSION,
182
+ bridgeOptions: {
183
+ host: bridgeOverrides.host,
184
+ port: bridgeOverrides.port,
185
+ label: bridgeOverrides.label ?? "Yichus",
186
+ // The pair every page hints the widget (web/webmcp-autocomplete.js,
187
+ // the generated calculators); the widget rejects any other hello.
188
+ workspace: bridgeOverrides.workspace ?? "yichus",
189
+ relayId: bridgeOverrides.relayId ?? "yichus",
190
+ allowedOrigins: [baseUrl]
191
+ }
192
+ });
193
+ registerYichusTools(server, { baseUrl, disableOpener: options.disableOpener ?? false });
194
+ return server;
195
+ }
196
+ function asInternal(server) {
197
+ return server;
198
+ }
199
+ function registerYichusTools(server, ctx) {
200
+ const internal = asInternal(server);
201
+ registerStatic(internal, {
202
+ name: "yichus_list_calculators",
203
+ description: "List the curated Yichus calculator demos and the playground editor available on the configured Yichus origin. Returns a slug, title, description, and absolute URL for each.",
204
+ annotations: { readOnlyHint: true, idempotentHint: true },
205
+ inputSchemaJson: { type: "object", properties: {}, additionalProperties: false },
206
+ inputSchemaZod: {},
207
+ handler: async () => {
208
+ const entries = listCalculators().map((calc) => ({
209
+ slug: calc.slug,
210
+ title: calc.title,
211
+ description: calc.description,
212
+ url: calculatorUrl(calc, ctx.baseUrl)
213
+ }));
214
+ const payload = { baseUrl: ctx.baseUrl, calculators: entries };
215
+ return {
216
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
217
+ structuredContent: payload
218
+ };
219
+ }
220
+ });
221
+ registerStatic(internal, {
222
+ name: "yichus_open_calculator",
223
+ description: "Open a curated Yichus calculator (or the playground) in the user's default browser with WebMCP enabled (?webmcp=1). After opening, the relay tools below will see the page's tools (yichus_list_inputs, yichus_set_inputs, yichus_list_outputs, yichus_explain_output for calculators; yichus_playground_* for the playground). Use yichus_list_calculators to discover slugs.",
224
+ annotations: { openWorldHint: true },
225
+ inputSchemaJson: {
226
+ type: "object",
227
+ properties: {
228
+ slug: {
229
+ type: "string",
230
+ description: 'Calculator slug from yichus_list_calculators (for example "loan", "investment", "tax", "playground").'
231
+ }
232
+ },
233
+ required: ["slug"],
234
+ additionalProperties: false
235
+ },
236
+ inputSchemaZod: { slug: z.string() },
237
+ handler: async (rawArgs) => {
238
+ const args = z.object({ slug: z.string() }).parse(rawArgs);
239
+ const calc = findCalculator(args.slug);
240
+ if (!calc) {
241
+ const known = listCalculators().map((c) => c.slug).join(", ");
242
+ return {
243
+ content: [
244
+ {
245
+ type: "text",
246
+ text: `Unknown calculator slug "${args.slug}". Known slugs: ${known}.`
247
+ }
248
+ ],
249
+ isError: true
250
+ };
251
+ }
252
+ const url = withWebMcpFlag(calculatorUrl(calc, ctx.baseUrl));
253
+ let opener = null;
254
+ if (!ctx.disableOpener) {
255
+ opener = openUrlInBrowser(url);
256
+ }
257
+ const payload = {
258
+ calculator: {
259
+ slug: calc.slug,
260
+ title: calc.title,
261
+ description: calc.description
262
+ },
263
+ url,
264
+ opened: !ctx.disableOpener,
265
+ opener
266
+ };
267
+ return {
268
+ content: [
269
+ {
270
+ type: "text",
271
+ text: ctx.disableOpener ? `Would open ${calc.title} at ${url} (browser opener disabled).` : `Opening ${calc.title} at ${url}.`
272
+ }
273
+ ],
274
+ structuredContent: payload
275
+ };
276
+ }
277
+ });
278
+ }
279
+ function registerStatic(internal, spec) {
280
+ const config = {
281
+ description: spec.description,
282
+ inputSchema: spec.inputSchemaZod
283
+ };
284
+ if (spec.annotations && Object.keys(spec.annotations).length > 0) {
285
+ config.annotations = spec.annotations;
286
+ }
287
+ internal.mcpServer.registerTool(spec.name, config, spec.handler);
288
+ internal.staticToolData.set(spec.name, {
289
+ name: spec.name,
290
+ description: spec.description,
291
+ inputSchema: spec.inputSchemaJson,
292
+ ...spec.annotations && Object.keys(spec.annotations).length > 0 ? { annotations: spec.annotations } : {}
293
+ });
294
+ }
295
+ function normalizeBaseUrl(raw) {
296
+ return new URL(raw).origin;
297
+ }
298
+ export {
299
+ CALCULATORS,
300
+ PLAYGROUND,
301
+ calculatorUrl,
302
+ createYichusMcpServer,
303
+ findCalculator,
304
+ listCalculators,
305
+ openUrlInBrowser,
306
+ withWebMcpFlag
307
+ };
308
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/calculators.ts","../src/openUrl.ts"],"sourcesContent":["import { LocalRelayMcpServer } from '@mcp-b/webmcp-local-relay';\nimport { z } from 'zod';\nimport {\n CALCULATORS,\n PLAYGROUND,\n calculatorUrl,\n findCalculator,\n listCalculators,\n withWebMcpFlag,\n type Calculator,\n} from './calculators.js';\nimport { openUrlInBrowser } from './openUrl.js';\n\nconst PACKAGE_NAME = 'yichus-mcp';\n// Bumped via packages/yichus-mcp/package.json. Kept inline so the bin works\n// without reading package.json from disk at startup.\nconst PACKAGE_VERSION = '0.1.0';\n\nexport interface YichusMcpServerOptions {\n /**\n * Origin of the deployed Yichus instance. The relay rejects browser\n * connections from any other origin and `yichus_open_calculator` builds\n * URLs against this base.\n *\n * @defaultValue `https://yich.us`\n */\n baseUrl?: string;\n\n /**\n * Override the WebMCP local-relay options that get passed straight through\n * to the bundled `LocalRelayMcpServer`. Use this to change the bind port,\n * label, workspace name, or relay id.\n *\n * The page-side embed (`web/webmcp-autocomplete.js`, `ensureRelayEmbed`,\n * and the generated calculator pages) hints the widget a workspace and a\n * relay id, and the widget refuses any relay whose discovery hello\n * differs from either. Every shipped page hints `yichus` for both, so the\n * defaults here are the one agreement; override both sides together.\n */\n bridgeOverrides?: {\n host?: string;\n port?: number;\n label?: string;\n workspace?: string;\n relayId?: string;\n };\n\n /**\n * If `true`, the `yichus_open_calculator` tool returns the URL it would\n * have opened without actually launching a browser process. Used by tests\n * and by anyone running the server in a headless environment.\n *\n * @defaultValue `false`\n */\n disableOpener?: boolean;\n}\n\n/**\n * Construct a fully-wired Yichus MCP server.\n *\n * The returned `LocalRelayMcpServer` already has every default WebMCP relay\n * tool plus the two yichus-curated tools (`yichus_list_calculators` and\n * `yichus_open_calculator`) registered. Call `start()` then `startStdio()`\n * (or `connect(transport)`) to bring it online.\n */\nexport function createYichusMcpServer(options: YichusMcpServerOptions = {}): LocalRelayMcpServer {\n const baseUrl = normalizeBaseUrl(options.baseUrl ?? 'https://yich.us');\n const bridgeOverrides = options.bridgeOverrides ?? {};\n\n const server = new LocalRelayMcpServer({\n serverName: PACKAGE_NAME,\n serverVersion: PACKAGE_VERSION,\n bridgeOptions: {\n host: bridgeOverrides.host,\n port: bridgeOverrides.port,\n label: bridgeOverrides.label ?? 'Yichus',\n // The pair every page hints the widget (web/webmcp-autocomplete.js,\n // the generated calculators); the widget rejects any other hello.\n workspace: bridgeOverrides.workspace ?? 'yichus',\n relayId: bridgeOverrides.relayId ?? 'yichus',\n allowedOrigins: [baseUrl],\n },\n });\n\n registerYichusTools(server, { baseUrl, disableOpener: options.disableOpener ?? false });\n return server;\n}\n\ninterface RelayInternals {\n mcpServer: {\n registerTool: (\n name: string,\n config: Record<string, unknown>,\n handler: (args: unknown) => Promise<unknown> | unknown\n ) => unknown;\n };\n staticToolData: Map<\n string,\n {\n name: string;\n description?: string;\n inputSchema: object;\n annotations?: object;\n }\n >;\n}\n\n/**\n * `@mcp-b/webmcp-local-relay@2.2.1` marks `mcpServer` and `staticToolData`\n * as TypeScript-private but the JavaScript fields are public. We pin the\n * relay version exactly so an upstream rename here is a deliberate audit\n * step rather than a silent breakage.\n */\nfunction asInternal(server: LocalRelayMcpServer): RelayInternals {\n return server as unknown as RelayInternals;\n}\n\nfunction registerYichusTools(\n server: LocalRelayMcpServer,\n ctx: { baseUrl: string; disableOpener: boolean }\n): void {\n const internal = asInternal(server);\n\n registerStatic(internal, {\n name: 'yichus_list_calculators',\n description:\n 'List the curated Yichus calculator demos and the playground editor available on the configured Yichus origin. Returns a slug, title, description, and absolute URL for each.',\n annotations: { readOnlyHint: true, idempotentHint: true },\n inputSchemaJson: { type: 'object', properties: {}, additionalProperties: false },\n inputSchemaZod: {} as z.ZodRawShape,\n handler: async () => {\n const entries = listCalculators().map((calc) => ({\n slug: calc.slug,\n title: calc.title,\n description: calc.description,\n url: calculatorUrl(calc, ctx.baseUrl),\n }));\n const payload = { baseUrl: ctx.baseUrl, calculators: entries };\n return {\n content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],\n structuredContent: payload,\n };\n },\n });\n\n registerStatic(internal, {\n name: 'yichus_open_calculator',\n description:\n \"Open a curated Yichus calculator (or the playground) in the user's default browser with WebMCP enabled (?webmcp=1). After opening, the relay tools below will see the page's tools (yichus_list_inputs, yichus_set_inputs, yichus_list_outputs, yichus_explain_output for calculators; yichus_playground_* for the playground). Use yichus_list_calculators to discover slugs.\",\n annotations: { openWorldHint: true },\n inputSchemaJson: {\n type: 'object',\n properties: {\n slug: {\n type: 'string',\n description:\n 'Calculator slug from yichus_list_calculators (for example \"loan\", \"investment\", \"tax\", \"playground\").',\n },\n },\n required: ['slug'],\n additionalProperties: false,\n },\n inputSchemaZod: { slug: z.string() },\n handler: async (rawArgs) => {\n const args = z.object({ slug: z.string() }).parse(rawArgs);\n const calc: Calculator | undefined = findCalculator(args.slug);\n if (!calc) {\n const known = listCalculators().map((c) => c.slug).join(', ');\n return {\n content: [\n {\n type: 'text',\n text: `Unknown calculator slug \"${args.slug}\". Known slugs: ${known}.`,\n },\n ],\n isError: true,\n };\n }\n const url = withWebMcpFlag(calculatorUrl(calc, ctx.baseUrl));\n let opener: { command: string; args: string[] } | null = null;\n if (!ctx.disableOpener) {\n opener = openUrlInBrowser(url);\n }\n const payload = {\n calculator: {\n slug: calc.slug,\n title: calc.title,\n description: calc.description,\n },\n url,\n opened: !ctx.disableOpener,\n opener,\n };\n return {\n content: [\n {\n type: 'text',\n text: ctx.disableOpener\n ? `Would open ${calc.title} at ${url} (browser opener disabled).`\n : `Opening ${calc.title} at ${url}.`,\n },\n ],\n structuredContent: payload,\n };\n },\n });\n}\n\ninterface StaticToolSpec {\n name: string;\n description: string;\n annotations?: Record<string, unknown>;\n inputSchemaJson: object;\n inputSchemaZod: z.ZodRawShape;\n handler: (args: unknown) => Promise<unknown> | unknown;\n}\n\nfunction registerStatic(internal: RelayInternals, spec: StaticToolSpec): void {\n const config: Record<string, unknown> = {\n description: spec.description,\n inputSchema: spec.inputSchemaZod,\n };\n if (spec.annotations && Object.keys(spec.annotations).length > 0) {\n config.annotations = spec.annotations;\n }\n internal.mcpServer.registerTool(spec.name, config, spec.handler);\n internal.staticToolData.set(spec.name, {\n name: spec.name,\n description: spec.description,\n inputSchema: spec.inputSchemaJson,\n ...(spec.annotations && Object.keys(spec.annotations).length > 0\n ? { annotations: spec.annotations }\n : {}),\n });\n}\n\nfunction normalizeBaseUrl(raw: string): string {\n // `URL` validates the input and re-serializes to the canonical form.\n return new URL(raw).origin;\n}\n\nexport const _testing = {\n CALCULATORS,\n PLAYGROUND,\n asInternal,\n};\n","/**\n * Curated catalogue of Yichus simulation demos.\n *\n * GENERATED from deploy/manifest.json by `yichus deploy --emit-calculators`.\n * Do not edit the CALCULATORS array by hand: the manifest row that deploys\n * a calculator is also its catalog entry, so the two cannot drift.\n */\n\nexport interface Calculator {\n /** Stable short identifier used by `yichus_open_calculator`. */\n readonly slug: string;\n /** Human-readable title that matches the page `<title>`. */\n readonly title: string;\n /** One-line description suitable for an agent to choose between calculators. */\n readonly description: string;\n /** Path under the deploy base URL (no leading slash). */\n readonly path: string;\n}\n\nconst DEFAULT_BASE_URL = 'https://yich.us';\n\nexport const CALCULATORS: readonly Calculator[] = [\n {\n slug: 'event-budget',\n title: 'Event budget',\n description: 'Ticket income, event costs, and the amount left over. Change attendance or adapt the cost rules.',\n path: 'demos/simulation/event-budget.html',\n },\n {\n slug: 'rainwater',\n title: 'Rainwater tank',\n description: 'How much water does one rainfall event collect, and how much overflows the tank?',\n path: 'demos/simulation/rainwater.html',\n },\n {\n slug: 'battery-runtime',\n title: 'Battery runtime',\n description: 'Estimate runtime from a battery energy budget, a constant load, and a usable-capacity assumption.',\n path: 'demos/simulation/battery-runtime.html',\n },\n {\n slug: 'recipe-scale',\n title: 'Scale a recipe',\n description: 'Scale ingredient weights to a different number of servings. Replace the ingredients with your own.',\n path: 'demos/simulation/recipe-scale.html',\n },\n {\n slug: 'download-time',\n title: 'Download time',\n description: 'Convert file size and connection speed into transfer time, allowing for a chosen efficiency.',\n path: 'demos/simulation/download-time.html',\n },\n {\n slug: 'loan',\n title: 'Loan Calculator',\n description: 'Fixed monthly payment and total interest for a loan repaid in equal monthly installments.',\n path: 'demos/simulation/loan-calculator.html',\n },\n {\n slug: 'investment',\n title: 'Investment Growth Calculator',\n description: 'Compound growth of a recurring investment under configurable return assumptions.',\n path: 'demos/simulation/investment.html',\n },\n {\n slug: 'tax',\n title: 'Tax Calculator',\n description: 'Flat-rate example: subtract a deduction and apply one percentage. No tax brackets or jurisdiction-specific rules.',\n path: 'demos/simulation/tax-calculator.html',\n },\n {\n slug: 'carbon',\n title: 'Carbon Footprint Calculator',\n description: 'Illustrative annual CO2 totals from driving, flights, electricity, and gas, using fixed emission factors.',\n path: 'demos/simulation/carbon-footprint.html',\n },\n {\n slug: 'rate',\n title: 'Rate Calculator',\n description: 'Apply a basis-point rate to an amount, then subtract a percentage fee. Inspect both helper calculations in Why.',\n path: 'demos/simulation/rate-calculator.html',\n },\n {\n slug: 'compound-fold',\n title: 'CompoundFold',\n description: 'Compound a starting amount over several periods, increasing the rate by 0.1 percentage points each period.',\n path: 'demos/simulation/compound-fold.html',\n },\n {\n slug: 'closure-capture',\n title: 'ClosureCapture',\n description: 'Scale two amounts with the same input rate captured by a shared inner function.',\n path: 'demos/simulation/closure-capture.html',\n },\n {\n slug: 'lambda-factory',\n title: 'Fee pipeline',\n description: 'Apply processing, platform, and regulatory fees in sequence, with each helper capturing its chosen rate.',\n path: 'demos/simulation/lambda-factory-selector.html',\n },\n {\n slug: 'portfolio',\n title: 'Three growth rates',\n description: 'Split an amount into three equal parts and apply a chosen growth rate to each. This example does not estimate risk.',\n path: 'demos/simulation/portfolio-analysis.html',\n },\n {\n slug: 'strategy-portfolio',\n title: 'Choosing a growth multiplier',\n description: 'Choose between three illustrative growth multipliers. This is a branching example, not an investment-risk model.',\n path: 'demos/simulation/strategy-portfolio.html',\n },\n {\n slug: 'tiered-policy',\n title: 'Claim payout rules',\n description: 'A toy claim payout: choose an incident severity and policy tier, then inspect the adjustments and deduction.',\n path: 'demos/simulation/tiered-policy-engine.html',\n },\n {\n slug: 'what-if-path-switch',\n title: 'WhatIfPathSwitch',\n description: 'Switches computation paths via assumption toggles (\"what-if\" branches).',\n path: 'demos/simulation/what-if-path-switch.html',\n },\n {\n slug: 'deadweight_loss',\n title: 'Deadweight Loss Calculator',\n description: 'Equilibrium, surplus, and deadweight loss of a per-unit tax on a linear market.',\n path: 'demos/simulation/deadweight-loss.html',\n },\n {\n slug: 'trend_projection',\n title: 'Vintage Forecasts',\n description: 'An illustrative series with synthetic forecast vintages. Compare compounding assumptions; these are model-generated values, not agency data.',\n path: 'demos/simulation/trend-projection.html',\n },\n {\n slug: 'carbon_abatement',\n title: 'Carbon Abatement Path',\n description: 'A rising marginal abatement cost path as a sampled curve, with derived cheapest-step and tranche-cost marks.',\n path: 'demos/simulation/carbon-abatement.html',\n },\n] as const;\n\nexport const PLAYGROUND: Calculator = {\n slug: 'playground',\n title: 'Yichus Bosatsu Playground',\n description: 'In-browser editor for Bosatsu simulations with live compile and Why explainer.',\n path: 'editor.html',\n} as const;\n\n/** Look up a curated calculator by its slug, including the playground. */\nexport function findCalculator(slug: string): Calculator | undefined {\n if (slug === PLAYGROUND.slug) return PLAYGROUND;\n return CALCULATORS.find((calc) => calc.slug === slug);\n}\n\n/** Returns every entry exposed by `yichus_list_calculators`. */\nexport function listCalculators(): readonly Calculator[] {\n return [...CALCULATORS, PLAYGROUND];\n}\n\n/** Build the full URL for a calculator at the given base. */\nexport function calculatorUrl(calc: Calculator, baseUrl = DEFAULT_BASE_URL): string {\n const base = new URL(baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`);\n return new URL(calc.path, base).toString();\n}\n\n/**\n * Force `?webmcp=1` on a URL so the page loads the WebMCP relay embed.\n *\n * Pages generated by `SimulationGenerator` and `web/editor.html` only inject\n * the relay embed when this flag (or `localStorage.yichus_webmcp = \"on\"`) is\n * present, so an agent that opens a calculator must set it.\n */\nexport function withWebMcpFlag(rawUrl: string): string {\n const url = new URL(rawUrl);\n url.searchParams.set('webmcp', '1');\n return url.toString();\n}\n","import { spawn } from 'node:child_process';\n\n/**\n * Opens a URL in the user's default browser.\n *\n * Uses `URL.href` so shell metacharacters in the original string are escaped\n * before they reach the platform opener. Mirrors the approach the upstream\n * `webmcp-local-relay` `webmcp_open_page` tool uses.\n */\nexport function openUrlInBrowser(rawUrl: string): { command: string; args: string[] } {\n const validated = new URL(rawUrl).href;\n const platform = process.platform;\n\n let command: string;\n let args: string[];\n if (platform === 'darwin') {\n command = 'open';\n args = [validated];\n } else if (platform === 'win32') {\n command = 'powershell';\n args = ['-NoProfile', '-NonInteractive', '-Command', `Start-Process '${validated.replace(/'/g, \"''\")}'`];\n } else {\n command = 'xdg-open';\n args = [validated];\n }\n\n spawn(command, args, { detached: true, stdio: 'ignore' }).unref();\n return { command, args };\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@yichus/mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server that exposes the Yichus calculators and playground over WebMCP",
5
+ "license": "MIT",
6
+ "homepage": "https://yich.us",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/snoble/yichus.git",
10
+ "directory": "packages/yichus-mcp"
11
+ },
12
+ "type": "module",
13
+ "bin": {
14
+ "yichus-mcp": "./dist/cli.js"
15
+ },
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "README.md"
27
+ ],
28
+ "scripts": {
29
+ "build": "tsup",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "typecheck": "tsc --noEmit",
33
+ "prepublishOnly": "npm run build && npm test"
34
+ },
35
+ "dependencies": {
36
+ "@mcp-b/webmcp-local-relay": "2.2.1",
37
+ "zod": "^4.3.6"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.0.0",
41
+ "tsup": "^8.3.0",
42
+ "typescript": "^5.6.0",
43
+ "vitest": "^2.1.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=20"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "keywords": [
52
+ "mcp",
53
+ "model-context-protocol",
54
+ "webmcp",
55
+ "yichus",
56
+ "calculator",
57
+ "bosatsu"
58
+ ]
59
+ }